1088 lines
27 KiB
C++
1088 lines
27 KiB
C++
#include "precomp.h"
|
||
#include "dtmf.h"
|
||
|
||
|
||
HRESULT
|
||
SendAudioStream::Initialize( DataPump *pDP)
|
||
{
|
||
HRESULT hr = DPR_OUT_OF_MEMORY;
|
||
DWORD dwFlags = DP_FLAG_FULL_DUPLEX | DP_FLAG_AUTO_SWITCH ;
|
||
MEDIACTRLINIT mcInit;
|
||
FX_ENTRY ("SendAudioStream::Initialize")
|
||
|
||
dwFlags |= DP_FLAG_ACM | DP_FLAG_MMSYSTEM | DP_FLAG_AUTO_SILENCE_DETECT;
|
||
|
||
// store the platform flags
|
||
// enable Send and Recv by default
|
||
m_DPFlags = (dwFlags & DP_MASK_PLATFORM) | DPFLAG_ENABLE_SEND;
|
||
// store a back pointer to the datapump container
|
||
m_pDP = pDP;
|
||
|
||
m_Net = NULL; // this object (RTPSession) no longer used;
|
||
m_pRTPSend = NULL; // replaced with this object (RTPSend)
|
||
|
||
// Initialize data (should be in constructor)
|
||
m_CaptureDevice = (UINT) -1; // use VIDEO_MAPPER
|
||
|
||
|
||
|
||
// Create and Transmit audio streams
|
||
|
||
DBG_SAVE_FILE_LINE
|
||
m_SendStream = new TxStream();
|
||
if ( !m_SendStream)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: TxStream new failed\r\n", _fx_));
|
||
goto StreamAllocError;
|
||
}
|
||
|
||
|
||
// Create Input and Output audio filters
|
||
DBG_SAVE_FILE_LINE
|
||
m_pAudioFilter = new AcmFilter(); // audio filter will replace m_SendFilter
|
||
if (!m_pAudioFilter)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: FilterManager new failed\r\n", _fx_));
|
||
goto FilterAllocError;
|
||
}
|
||
|
||
//Create MultiMedia device control objects
|
||
DBG_SAVE_FILE_LINE
|
||
m_InMedia = new WaveInControl();
|
||
if (!m_InMedia )
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: MediaControl new failed\r\n", _fx_));
|
||
goto MediaAllocError;
|
||
}
|
||
|
||
// Initialize the send-stream media control object
|
||
mcInit.dwFlags = dwFlags | DP_FLAG_SEND;
|
||
hr = m_InMedia->Initialize(&mcInit);
|
||
if (hr != DPR_SUCCESS)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: IMedia->Init failed, hr=0x%lX\r\n", _fx_, hr));
|
||
goto MediaAllocError;
|
||
}
|
||
|
||
DBG_SAVE_FILE_LINE
|
||
m_pDTMF = new DTMFQueue;
|
||
if (!m_pDTMF)
|
||
{
|
||
return DPR_OUT_OF_MEMORY;
|
||
}
|
||
|
||
|
||
|
||
// determine if the wave devices are available
|
||
if (waveInGetNumDevs()) m_DPFlags |= DP_FLAG_RECORD_CAP;
|
||
|
||
// set media to half duplex mode by default
|
||
m_InMedia->SetProp(MC_PROP_DUPLEX_TYPE, DP_FLAG_HALF_DUPLEX);
|
||
|
||
m_SavedTickCount = timeGetTime(); //so we start with low timestamps
|
||
m_DPFlags |= DPFLAG_INITIALIZED;
|
||
|
||
m_bAutoMix = FALSE; // where else do you initialize this ?
|
||
|
||
return DPR_SUCCESS;
|
||
|
||
|
||
MediaAllocError:
|
||
if (m_InMedia) delete m_InMedia;
|
||
FilterAllocError:
|
||
if (m_pAudioFilter) delete m_pAudioFilter;
|
||
StreamAllocError:
|
||
if (m_SendStream) delete m_SendStream;
|
||
|
||
ERRORMESSAGE( ("%s: exit, hr=0x%lX\r\n", _fx_, hr));
|
||
|
||
return hr;
|
||
}
|
||
|
||
|
||
SendAudioStream::~SendAudioStream()
|
||
{
|
||
|
||
if (m_DPFlags & DPFLAG_INITIALIZED) {
|
||
m_DPFlags &= ~DPFLAG_INITIALIZED;
|
||
|
||
if (m_DPFlags & DPFLAG_CONFIGURED_SEND )
|
||
UnConfigure();
|
||
|
||
if (m_pRTPSend)
|
||
{
|
||
m_pRTPSend->Release();
|
||
m_pRTPSend = NULL;
|
||
}
|
||
|
||
if (m_pDTMF)
|
||
{
|
||
delete m_pDTMF;
|
||
m_pDTMF = NULL;
|
||
}
|
||
|
||
// Close the receive and transmit streams
|
||
if (m_SendStream) delete m_SendStream;
|
||
|
||
// Close the wave devices
|
||
if (m_InMedia) { delete m_InMedia;}
|
||
|
||
|
||
if (m_pAudioFilter)
|
||
delete m_pAudioFilter;
|
||
|
||
m_pDP->RemoveMediaChannel(MCF_SEND|MCF_AUDIO, (IMediaChannel*)(SendMediaStream*)this);
|
||
}
|
||
}
|
||
|
||
|
||
HRESULT STDMETHODCALLTYPE SendAudioStream::QueryInterface(REFIID iid, void **ppVoid)
|
||
{
|
||
// resolve duplicate inheritance to the SendMediaStream;
|
||
|
||
extern IID IID_IProperty;
|
||
|
||
if (iid == IID_IUnknown)
|
||
{
|
||
*ppVoid = (IUnknown*)((SendMediaStream*)this);
|
||
}
|
||
else if (iid == IID_IMediaChannel)
|
||
{
|
||
*ppVoid = (IMediaChannel*)((SendMediaStream *)this);
|
||
}
|
||
else if (iid == IID_IAudioChannel)
|
||
{
|
||
*ppVoid = (IAudioChannel*)this;
|
||
}
|
||
else if (iid == IID_IDTMFSend)
|
||
{
|
||
*ppVoid = (IDTMFSend*)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 SendAudioStream::AddRef(void)
|
||
{
|
||
return InterlockedIncrement(&m_lRefCount);
|
||
}
|
||
|
||
ULONG STDMETHODCALLTYPE SendAudioStream::Release(void)
|
||
{
|
||
LONG lRet;
|
||
|
||
lRet = InterlockedDecrement(&m_lRefCount);
|
||
|
||
if (lRet == 0)
|
||
{
|
||
delete this;
|
||
return 0;
|
||
}
|
||
|
||
else
|
||
return lRet;
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
HRESULT STDMETHODCALLTYPE SendAudioStream::Configure(
|
||
BYTE *pFormat,
|
||
UINT cbFormat,
|
||
BYTE *pChannelParams,
|
||
UINT cbParams,
|
||
IUnknown *pUnknown)
|
||
{
|
||
HRESULT hr;
|
||
BOOL fRet;
|
||
MEDIAPACKETINIT apInit;
|
||
MEDIACTRLCONFIG mcConfig;
|
||
MediaPacket **ppAudPckt;
|
||
ULONG cAudPckt;
|
||
DWORD_PTR dwPropVal;
|
||
DWORD dwSourceSize, dwDestSize;
|
||
UINT ringSize = MAX_RXRING_SIZE;
|
||
WAVEFORMATEX *pwfSend;
|
||
DWORD dwPacketDuration, dwPacketSize;
|
||
AUDIO_CHANNEL_PARAMETERS audChannelParams;
|
||
audChannelParams.RTP_Payload = 0;
|
||
MMRESULT mmr;
|
||
int nIndex;
|
||
|
||
FX_ENTRY ("SendAudioStream::Configure")
|
||
|
||
// basic parameter checking
|
||
if (! (m_DPFlags & DPFLAG_INITIALIZED))
|
||
return DPR_OUT_OF_MEMORY; //BUGBUG: return proper error;
|
||
|
||
// Not a good idea to change anything while in mid-stream
|
||
if (m_DPFlags & DPFLAG_STARTED_SEND)
|
||
{
|
||
return DPR_IO_PENDING; // anything better to return
|
||
}
|
||
|
||
if (m_DPFlags & DPFLAG_CONFIGURED_SEND)
|
||
{
|
||
DEBUGMSG(ZONE_DP, ("Stream Re-Configuration - calling UnConfigure"));
|
||
UnConfigure();
|
||
}
|
||
|
||
|
||
if ((NULL == pFormat) || (NULL == pChannelParams) ||
|
||
(cbParams < sizeof(AUDIO_CHANNEL_PARAMETERS)) ||
|
||
(cbFormat < sizeof(WAVEFORMATEX)))
|
||
{
|
||
return DPR_INVALID_PARAMETER;
|
||
}
|
||
|
||
audChannelParams = *(AUDIO_CHANNEL_PARAMETERS *)pChannelParams;
|
||
pwfSend = (WAVEFORMATEX *)pFormat;
|
||
m_wfCompressed = *pwfSend;
|
||
m_wfCompressed.cbSize = 0;
|
||
|
||
mmr = AcmFilter::SuggestDecodeFormat(pwfSend, &m_fDevSend);
|
||
|
||
UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfSend->wFormatTag, REP_SEND_AUDIO_FORMAT);
|
||
UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfSend->nSamplesPerSec, REP_SEND_AUDIO_SAMPLING);
|
||
UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfSend->nAvgBytesPerSec * 8, REP_SEND_AUDIO_BITRATE);
|
||
RETAILMSG(("NAC: Audio Send Format: %s", (pwfSend->wFormatTag == 66) ? "G723.1" : (pwfSend->wFormatTag == 112) ? "LHCELP" : (pwfSend->wFormatTag == 113) ? "LHSB08" : (pwfSend->wFormatTag == 114) ? "LHSB12" : (pwfSend->wFormatTag == 115) ? "LHSB16" : (pwfSend->wFormatTag == 6) ? "MSALAW" : (pwfSend->wFormatTag == 7) ? "MSULAW" : (pwfSend->wFormatTag == 130) ? "MSRT24" : "??????"));
|
||
RETAILMSG(("NAC: Audio Send Sampling Rate (Hz): %ld", pwfSend->nSamplesPerSec));
|
||
RETAILMSG(("NAC: Audio Send Bitrate (w/o network overhead - bps): %ld", pwfSend->nAvgBytesPerSec*8));
|
||
|
||
// Initialize the send-stream media control object
|
||
mcConfig.uDuration = MC_USING_DEFAULT; // set duration by samples per pkt
|
||
mcConfig.pDevFmt = &m_fDevSend;
|
||
mcConfig.hStrm = (DPHANDLE) m_SendStream;
|
||
mcConfig.uDevId = m_CaptureDevice;
|
||
mcConfig.cbSamplesPerPkt = audChannelParams.ns_params.wFrameSize
|
||
*audChannelParams.ns_params.wFramesPerPkt;
|
||
|
||
UPDATE_REPORT_ENTRY(g_prptCallParameters, mcConfig.cbSamplesPerPkt, REP_SEND_AUDIO_PACKET);
|
||
RETAILMSG(("NAC: Audio Send Packetization (ms/packet): %ld", pwfSend->nSamplesPerSec ? mcConfig.cbSamplesPerPkt * 1000UL / pwfSend->nSamplesPerSec : 0));
|
||
INIT_COUNTER_MAX(g_pctrAudioSendBytes, (pwfSend->nAvgBytesPerSec + pwfSend->nSamplesPerSec * (sizeof(RTP_HDR) + IP_HEADER_SIZE + UDP_HEADER_SIZE) / mcConfig.cbSamplesPerPkt) << 3);
|
||
|
||
hr = m_InMedia->Configure(&mcConfig);
|
||
if (hr != DPR_SUCCESS)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: IMedia->Config failed, hr=0x%lX\r\n", _fx_, hr));
|
||
goto IMediaInitError;
|
||
}
|
||
|
||
|
||
// initialize the ACM filter
|
||
mmr = m_pAudioFilter->Open(&m_fDevSend, pwfSend);
|
||
if (mmr != 0)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: AcmFilter->Open failed, mmr=%d\r\n", _fx_, mmr));
|
||
hr = DPR_CANT_OPEN_CODEC;
|
||
goto SendFilterInitError;
|
||
}
|
||
|
||
|
||
// Initialize the send stream and the packets
|
||
ZeroMemory (&apInit, sizeof (apInit));
|
||
|
||
apInit.dwFlags = DP_FLAG_SEND | DP_FLAG_ACM | DP_FLAG_MMSYSTEM;
|
||
m_InMedia->FillMediaPacketInit (&apInit);
|
||
|
||
m_InMedia->GetProp (MC_PROP_SIZE, &dwPropVal);
|
||
dwSourceSize = (DWORD)dwPropVal;
|
||
|
||
m_pAudioFilter->SuggestDstSize(dwSourceSize, &dwDestSize);
|
||
|
||
apInit.cbSizeRawData = dwSourceSize;
|
||
apInit.cbOffsetRawData = 0;
|
||
apInit.cbSizeNetData = dwDestSize;
|
||
dwPacketSize = dwDestSize;
|
||
|
||
apInit.pStrmConvSrcFmt = &m_fDevSend;
|
||
apInit.pStrmConvDstFmt = &m_wfCompressed;
|
||
|
||
|
||
m_InMedia->GetProp (MC_PROP_DURATION, &dwPropVal);
|
||
dwPacketDuration = (DWORD)dwPropVal;
|
||
|
||
apInit.cbOffsetNetData = sizeof (RTP_HDR);
|
||
apInit.payload = audChannelParams.RTP_Payload;
|
||
fRet = m_SendStream->Initialize (DP_FLAG_MMSYSTEM, MAX_TXRING_SIZE, m_pDP, &apInit);
|
||
if (! fRet)
|
||
{
|
||
DEBUGMSG (ZONE_DP, ("%s: TxStream->Init failed, fRet=0%u\r\n", _fx_, fRet));
|
||
hr = DPR_CANT_INIT_TX_STREAM;
|
||
goto TxStreamInitError;
|
||
}
|
||
|
||
// prepare headers for TxStream
|
||
m_SendStream->GetRing (&ppAudPckt, &cAudPckt);
|
||
m_InMedia->RegisterData (ppAudPckt, cAudPckt);
|
||
m_InMedia->PrepareHeaders ();
|
||
|
||
m_pAudioFilter->PrepareAudioPackets((AudioPacket**)ppAudPckt, cAudPckt, AP_ENCODE);
|
||
|
||
// Open the play from wav file
|
||
OpenSrcFile();
|
||
|
||
|
||
// Initialize DTMF support
|
||
m_pDTMF->Initialize(&m_fDevSend);
|
||
m_pDTMF->ClearQueue();
|
||
|
||
|
||
// WS2Qos will be called in Start to communicate stream reservations to the
|
||
// remote endpoint using a RESV message
|
||
//
|
||
// We use a peak-rate allocation approach based on our target bitrates
|
||
// Note that for the token bucket size and the maximum SDU size, we now
|
||
// account for IP header overhead, and use the max frame fragment size
|
||
// instead of the maximum compressed image size returned by the codec
|
||
//
|
||
// Some of the parameters are left unspecified because they are set
|
||
// in the sender Tspec.
|
||
|
||
|
||
InitAudioFlowspec(&m_flowspec, pwfSend, dwPacketSize);
|
||
|
||
if (m_pDP->m_pIQoS)
|
||
{
|
||
// Initialize our requests. One for CPU usage, one for bandwidth usage.
|
||
m_aRRq.cResourceRequests = 2;
|
||
m_aRRq.aResourceRequest[0].resourceID = RESOURCE_OUTGOING_BANDWIDTH;
|
||
if (dwPacketDuration)
|
||
m_aRRq.aResourceRequest[0].nUnitsMin = (DWORD)(dwPacketSize + sizeof(RTP_HDR) + IP_HEADER_SIZE + UDP_HEADER_SIZE) * 8000 / dwPacketDuration;
|
||
else
|
||
m_aRRq.aResourceRequest[0].nUnitsMin = 0;
|
||
m_aRRq.aResourceRequest[1].resourceID = RESOURCE_CPU_CYCLES;
|
||
m_aRRq.aResourceRequest[1].nUnitsMin = 800;
|
||
|
||
/*
|
||
BUGBUG. This is, in theory the correct calculation, but until we do more investigation, go with a known value
|
||
|
||
m_aRRq.aResourceRequest[1].nUnitsMin = (audDetails.wCPUUtilizationEncode+audDetails.wCPUUtilizationDecode)*10;
|
||
|
||
*/
|
||
// Initialize QoS structure
|
||
ZeroMemory(&m_Stats, sizeof(m_Stats));
|
||
|
||
// Initialize oldest QoS callback timestamp
|
||
// Register with the QoS module. Even if this call fails, that's Ok, we'll do without the QoS support
|
||
m_pDP->m_pIQoS->RequestResources((GUID *)&MEDIA_TYPE_H323AUDIO, (LPRESOURCEREQUESTLIST)&m_aRRq, QosNotifyAudioCB, (DWORD_PTR)this);
|
||
}
|
||
|
||
m_DPFlags |= DPFLAG_CONFIGURED_SEND;
|
||
|
||
|
||
return DPR_SUCCESS;
|
||
|
||
TxStreamInitError:
|
||
SendFilterInitError:
|
||
m_InMedia->Close();
|
||
m_pAudioFilter->Close();
|
||
IMediaInitError:
|
||
ERRORMESSAGE(("%s: failed, hr=0%u\r\n", _fx_, hr));
|
||
return hr;
|
||
}
|
||
|
||
|
||
|
||
|
||
void SendAudioStream::UnConfigure()
|
||
{
|
||
AudioPacket **ppAudPckt;
|
||
ULONG uPackets;
|
||
|
||
|
||
if ((m_DPFlags & DPFLAG_CONFIGURED_SEND)) {
|
||
|
||
if (m_hCapturingThread) {
|
||
Stop();
|
||
}
|
||
|
||
// Close the wave devices
|
||
m_InMedia->Reset();
|
||
m_InMedia->UnprepareHeaders();
|
||
m_InMedia->Close();
|
||
// Close the play from wav file
|
||
CloseSrcFile();
|
||
|
||
// Close the filters
|
||
m_SendStream->GetRing ((MediaPacket***)&ppAudPckt, &uPackets);
|
||
m_pAudioFilter->UnPrepareAudioPackets(ppAudPckt, uPackets, AP_ENCODE);
|
||
m_pAudioFilter->Close();
|
||
|
||
// Close the transmit streams
|
||
m_SendStream->Destroy();
|
||
m_DPFlags &= ~DPFLAG_CONFIGURED_SEND;
|
||
m_ThreadFlags = 0; // invalidate previous call to SetMaxBitrate
|
||
|
||
|
||
// Release the QoS Resources
|
||
// If the associated RequestResources had failed, the ReleaseResources can be
|
||
// still called... it will just come back without having freed anything.
|
||
if (m_pDP->m_pIQoS)
|
||
{
|
||
m_pDP->m_pIQoS->ReleaseResources((GUID *)&MEDIA_TYPE_H323AUDIO, (LPRESOURCEREQUESTLIST)&m_aRRq);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
DWORD CALLBACK SendAudioStream::StartRecordingThread (LPVOID pVoid)
|
||
{
|
||
SendAudioStream *pThisStream = (SendAudioStream*)pVoid;
|
||
return pThisStream->RecordingThread();
|
||
}
|
||
|
||
|
||
|
||
|
||
// LOOK: identical to SendVideoStream version.
|
||
HRESULT
|
||
SendAudioStream::Start()
|
||
{
|
||
FX_ENTRY ("SendAudioStream::Start")
|
||
if (m_DPFlags & DPFLAG_STARTED_SEND)
|
||
return DPR_SUCCESS;
|
||
// TODO: remove this check once audio UI calls the IComChan PAUSE_ prop
|
||
if (!(m_DPFlags & DPFLAG_ENABLE_SEND))
|
||
return DPR_SUCCESS;
|
||
if ((!(m_DPFlags & DPFLAG_CONFIGURED_SEND)) || (m_pRTPSend==NULL))
|
||
return DPR_NOT_CONFIGURED;
|
||
ASSERT(!m_hCapturingThread);
|
||
m_ThreadFlags &= ~(DPTFLAG_STOP_RECORD|DPTFLAG_STOP_SEND);
|
||
|
||
SetFlowSpec();
|
||
|
||
// Start recording thread
|
||
if (!(m_ThreadFlags & DPTFLAG_STOP_RECORD))
|
||
m_hCapturingThread = CreateThread(NULL,0, SendAudioStream::StartRecordingThread,(LPVOID)this,0,&m_CaptureThId);
|
||
|
||
m_DPFlags |= DPFLAG_STARTED_SEND;
|
||
|
||
DEBUGMSG (ZONE_DP, ("%s: Record threadid=%x,\r\n", _fx_, m_CaptureThId));
|
||
return DPR_SUCCESS;
|
||
}
|
||
|
||
// LOOK: identical to SendVideoStream version.
|
||
HRESULT
|
||
SendAudioStream::Stop()
|
||
{
|
||
DWORD dwWait;
|
||
|
||
if(!(m_DPFlags & DPFLAG_STARTED_SEND))
|
||
{
|
||
return DPR_SUCCESS;
|
||
}
|
||
|
||
m_ThreadFlags = m_ThreadFlags |
|
||
DPTFLAG_STOP_SEND | DPTFLAG_STOP_RECORD ;
|
||
|
||
if(m_SendStream)
|
||
m_SendStream->Stop();
|
||
|
||
DEBUGMSG (ZONE_VERBOSE, ("STOP1: Waiting for record thread to exit\r\n"));
|
||
|
||
/*
|
||
* we want to wait for all the threads to exit, but we need to handle windows
|
||
* messages (mostly from winsock) while waiting.
|
||
*/
|
||
|
||
if(m_hCapturingThread)
|
||
{
|
||
dwWait = WaitForSingleObject (m_hCapturingThread, INFINITE);
|
||
|
||
DEBUGMSG (ZONE_VERBOSE, ("STOP2: Recording thread exited\r\n"));
|
||
ASSERT(dwWait != WAIT_FAILED);
|
||
|
||
CloseHandle(m_hCapturingThread);
|
||
m_hCapturingThread = NULL;
|
||
}
|
||
m_DPFlags &= ~DPFLAG_STARTED_SEND;
|
||
|
||
return DPR_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
// low order word is the signal strength
|
||
// high order work contains bits to indicate status
|
||
// (0x01 - transmitting)
|
||
// (0x02 - audio device is jammed)
|
||
STDMETHODIMP SendAudioStream::GetSignalLevel(UINT *pSignalStrength)
|
||
{
|
||
UINT uLevel;
|
||
DWORD dwJammed;
|
||
DWORD_PTR dwPropVal;
|
||
|
||
if(!(m_DPFlags & DPFLAG_STARTED_SEND))
|
||
{
|
||
uLevel = 0;
|
||
}
|
||
else
|
||
{
|
||
uLevel = m_AudioMonitor.GetSignalStrength();
|
||
|
||
m_InMedia->GetProp(MC_PROP_AUDIO_JAMMED, &dwPropVal);
|
||
dwJammed = (DWORD)dwPropVal;
|
||
|
||
if (dwJammed)
|
||
{
|
||
uLevel = (2 << 16); // 0x0200
|
||
}
|
||
else if (m_fSending)
|
||
{
|
||
uLevel |= (1 << 16); // 0x0100 + uLevel
|
||
}
|
||
}
|
||
|
||
*pSignalStrength = uLevel;
|
||
return S_OK;
|
||
};
|
||
|
||
|
||
|
||
// this interface method is primarily for H.245 flow control messages
|
||
// it will pause the stream if uMaxBitrate is less than the codec
|
||
// output bitrate. Only valid on a Configure'd stream.
|
||
HRESULT STDMETHODCALLTYPE SendAudioStream::SetMaxBitrate(UINT uMaxBitrate)
|
||
{
|
||
UINT uMinBitrate;
|
||
|
||
if (!(m_DPFlags & DPFLAG_CONFIGURED_SEND))
|
||
{
|
||
return DPR_NOT_CONFIGURED;
|
||
}
|
||
|
||
uMinBitrate = 8 * m_wfCompressed.nAvgBytesPerSec;
|
||
|
||
if (uMaxBitrate < uMinBitrate)
|
||
{
|
||
DEBUGMSG(1, ("SendAudioStream::SetMaxBitrate - PAUSING"));
|
||
m_ThreadFlags |= DPTFLAG_PAUSE_SEND;
|
||
}
|
||
else
|
||
{
|
||
DEBUGMSG(1, ("SendAudioStream::SetMaxBitrate - UnPausing"));
|
||
m_ThreadFlags = m_ThreadFlags & ~(DPTFLAG_PAUSE_SEND);
|
||
}
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
// IProperty::GetProperty / SetProperty
|
||
// (DataPump::MediaChannel::GetProperty)
|
||
// Properties of the MediaChannel. Supports properties for both audio
|
||
// and video channels.
|
||
|
||
STDMETHODIMP
|
||
SendAudioStream::GetProperty(
|
||
DWORD prop,
|
||
PVOID pBuf,
|
||
LPUINT pcbBuf
|
||
)
|
||
{
|
||
HRESULT hr = DPR_SUCCESS;
|
||
RTP_STATS RTPStats;
|
||
DWORD_PTR dwPropVal;
|
||
UINT len = sizeof(DWORD); // most props are DWORDs
|
||
|
||
if (!pBuf || *pcbBuf < len)
|
||
{
|
||
*pcbBuf = len;
|
||
return DPR_INVALID_PARAMETER;
|
||
}
|
||
|
||
switch (prop)
|
||
{
|
||
case PROP_AUDIO_STRENGTH:
|
||
return GetSignalLevel((UINT *)pBuf);
|
||
|
||
case PROP_AUDIO_JAMMED:
|
||
hr = m_InMedia->GetProp(MC_PROP_AUDIO_JAMMED, &dwPropVal);
|
||
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
break;
|
||
|
||
#ifdef OLDSTUFF
|
||
case PROP_NET_SEND_STATS:
|
||
if (m_Net && *pcbBuf >= sizeof(RTP_STATS))
|
||
{
|
||
m_Net->GetSendStats((RTP_STATS *)pBuf);
|
||
*pcbBuf = sizeof(RTP_STATS);
|
||
} else
|
||
hr = DPR_INVALID_PROP_VAL;
|
||
|
||
break;
|
||
#endif
|
||
|
||
case PROP_DURATION:
|
||
hr = m_InMedia->GetProp(MC_PROP_DURATION, &dwPropVal);
|
||
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
break;
|
||
|
||
case PROP_SILENCE_LEVEL:
|
||
*(DWORD *)pBuf = m_AudioMonitor.GetSilenceLevel();
|
||
break;
|
||
|
||
case PROP_SILENCE_DURATION:
|
||
hr = m_InMedia->GetProp(MC_PROP_SILENCE_DURATION, &dwPropVal);
|
||
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
break;
|
||
|
||
case PROP_DUPLEX_TYPE:
|
||
hr = m_InMedia->GetProp(MC_PROP_DUPLEX_TYPE, &dwPropVal);
|
||
if(HR_SUCCEEDED(hr))
|
||
{
|
||
if(dwPropVal & DP_FLAG_FULL_DUPLEX)
|
||
*(DWORD *)pBuf = DUPLEX_TYPE_FULL;
|
||
else
|
||
*(DWORD *)pBuf = DUPLEX_TYPE_HALF;
|
||
}
|
||
break;
|
||
|
||
case PROP_AUDIO_SPP:
|
||
hr = m_InMedia->GetProp(MC_PROP_SPP, &dwPropVal);
|
||
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
break;
|
||
|
||
case PROP_AUDIO_SPS:
|
||
hr = m_InMedia->GetProp(MC_PROP_SPS, &dwPropVal);
|
||
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
break;
|
||
|
||
case PROP_WAVE_DEVICE_TYPE:
|
||
*(DWORD *)pBuf = m_DPFlags & DP_MASK_WAVE_DEVICE;
|
||
break;
|
||
|
||
case PROP_RECORD_ON:
|
||
*(DWORD *)pBuf = (m_DPFlags & DPFLAG_ENABLE_SEND) !=0;
|
||
break;
|
||
|
||
case PROP_AUDIO_AUTOMIX:
|
||
*(DWORD *)pBuf = m_bAutoMix;
|
||
break;
|
||
|
||
case PROP_RECORD_DEVICE:
|
||
*(DWORD *)pBuf = m_CaptureDevice;
|
||
break;
|
||
|
||
default:
|
||
hr = DPR_INVALID_PROP_ID;
|
||
break;
|
||
}
|
||
|
||
return hr;
|
||
}
|
||
|
||
|
||
STDMETHODIMP
|
||
SendAudioStream::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_SILENCE_LEVEL:
|
||
m_AudioMonitor.SetSilenceLevel(*(DWORD *)pBuf);
|
||
RETAILMSG(("NAC: Silence Level set to %d / 1000",*(DWORD*)pBuf));
|
||
break;
|
||
|
||
case PROP_DUPLEX_TYPE:
|
||
ASSERT(0);
|
||
break;
|
||
|
||
|
||
case DP_PROP_DUPLEX_TYPE:
|
||
dw = *(DWORD*)pBuf;
|
||
if (dw)
|
||
{
|
||
dw = DP_FLAG_FULL_DUPLEX;
|
||
}
|
||
else
|
||
{
|
||
dw = DP_FLAG_HALF_DUPLEX;
|
||
}
|
||
|
||
m_InMedia->SetProp(MC_PROP_DUPLEX_TYPE, dw);
|
||
break;
|
||
|
||
case PROP_VOICE_SWITCH:
|
||
// set duplex type of both input and output
|
||
dw = *(DWORD*)pBuf;
|
||
switch(dw)
|
||
{
|
||
case VOICE_SWITCH_MIC_ON:
|
||
dw = DP_FLAG_MIC_ON;
|
||
break;
|
||
case VOICE_SWITCH_MIC_OFF:
|
||
dw = DP_FLAG_MIC_OFF;
|
||
break;
|
||
default:
|
||
case VOICE_SWITCH_AUTO:
|
||
dw = DP_FLAG_AUTO_SWITCH;
|
||
break;
|
||
}
|
||
|
||
hr = m_InMedia->SetProp(MC_PROP_VOICE_SWITCH, dw);
|
||
RETAILMSG(("NAC: Setting voice switch to %s", (DP_FLAG_AUTO_SWITCH & dw) ? "Auto" : ((DP_FLAG_MIC_ON & dw)? "MicOn":"MicOff")));
|
||
break;
|
||
|
||
case PROP_SILENCE_DURATION:
|
||
hr = m_InMedia->SetProp(MC_PROP_SILENCE_DURATION, *(DWORD*)pBuf);
|
||
RETAILMSG(("NAC: setting silence duration to %d ms",*(DWORD*)pBuf));
|
||
break;
|
||
// TODO: remove this property once UI calls IComChan version
|
||
case PROP_RECORD_ON:
|
||
{
|
||
DWORD flag = DPFLAG_ENABLE_SEND ;
|
||
if (*(DWORD *)pBuf) {
|
||
m_DPFlags |= flag; // set the flag
|
||
Start();
|
||
}
|
||
else
|
||
{
|
||
m_DPFlags &= ~flag; // clear the flag
|
||
Stop();
|
||
}
|
||
RETAILMSG(("NAC: %s", *(DWORD*)pBuf ? "Enabling":"Disabling"));
|
||
break;
|
||
}
|
||
|
||
case PROP_AUDIO_AUTOMIX:
|
||
m_bAutoMix = *(DWORD*)pBuf;
|
||
break;
|
||
|
||
|
||
case PROP_RECORD_DEVICE:
|
||
m_CaptureDevice = *(DWORD*)pBuf;
|
||
RETAILMSG(("NAC: Setting default record device to %d", m_CaptureDevice));
|
||
break;
|
||
|
||
default:
|
||
return DPR_INVALID_PROP_ID;
|
||
break;
|
||
}
|
||
return hr;
|
||
}
|
||
|
||
void SendAudioStream::EndSend()
|
||
{
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/*************************************************************************
|
||
|
||
Function: SendAudioStream::OpenSrcFile(void)
|
||
|
||
Purpose : Opens wav file to read audio data from.
|
||
|
||
Returns : HRESULT.
|
||
|
||
Params : None
|
||
|
||
Comments: * Registry keys:
|
||
\\HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Audio\PlayFromFile\fPlayFromFile
|
||
If set to zero, data will not be read from wav file.
|
||
If set to a non null value <= INT_MAX, data will be read from wav file.
|
||
\\HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Audio\PlayFromFile\szInputFileName
|
||
Name of the wav file to read audio data from.
|
||
\\HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Audio\PlayFromFile\fLoop
|
||
If set to zero, the file will only be read once.
|
||
If set to a non null value <= INT_MAX, the file will be read circularly.
|
||
\\HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Audio\PlayFromFile\cchIOBuffer
|
||
If set to zero, size of the MM IO buffer is set to its default value (8Kbytes).
|
||
If set to one, size of the MM IO buffer is set to match maximum size of the wav file.
|
||
If set a non null value between 2 and INT_MAX, size of the MM IO buffer is set to cchIOBuffer bytes.
|
||
|
||
History : Date Reason
|
||
06/02/96 Created - PhilF
|
||
|
||
*************************************************************************/
|
||
HRESULT SendAudioStream::OpenSrcFile (void)
|
||
{
|
||
return AudioFile::OpenSourceFile(&m_mmioSrc, &m_fDevSend);
|
||
}
|
||
|
||
|
||
/*************************************************************************
|
||
|
||
Function: DataPump::CloseSrcFile(void)
|
||
|
||
Purpose : Close wav file used to read audio data from.
|
||
|
||
Returns : HRESULT.
|
||
|
||
Params : None
|
||
|
||
Comments:
|
||
|
||
History : Date Reason
|
||
06/02/96 Created - PhilF
|
||
|
||
*************************************************************************/
|
||
HRESULT SendAudioStream::CloseSrcFile (void)
|
||
{
|
||
return AudioFile::CloseSourceFile(&m_mmioSrc);
|
||
}
|
||
|
||
|
||
HRESULT CALLBACK SendAudioStream::QosNotifyAudioCB(LPRESOURCEREQUESTLIST lpResourceRequestList, DWORD_PTR dwThis)
|
||
{
|
||
HRESULT hr=NOERROR;
|
||
LPRESOURCEREQUESTLIST prrl=lpResourceRequestList;
|
||
int i;
|
||
#ifdef LOGSTATISTICS_ON
|
||
int iMaxBWUsage, iMaxCPUUsage;
|
||
char szDebug[256];
|
||
#endif
|
||
DWORD dwCPUUsage, dwBWUsage;
|
||
int iCPUUsageId, iBWUsageId;
|
||
UINT dwSize = sizeof(int);
|
||
SendMediaStream *pThis = (SendMediaStream *)dwThis;
|
||
|
||
// Enter critical section to allow QoS thread to read the statistics while recording
|
||
EnterCriticalSection(&(pThis->m_crsQos));
|
||
|
||
// Record the time of this callback call
|
||
pThis->m_Stats.dwNewestTs = timeGetTime();
|
||
|
||
// Only do anything if we have at least captured a frame in the previous epoch
|
||
if ((pThis->m_Stats.dwCount) && (pThis->m_Stats.dwNewestTs > pThis->m_Stats.dwOldestTs))
|
||
{
|
||
#ifdef LOGSTATISTICS_ON
|
||
wsprintf(szDebug, " Epoch = %ld\r\n", pThis->m_Stats.dwNewestTs - pThis->m_Stats.dwOldestTs);
|
||
OutputDebugString(szDebug);
|
||
#endif
|
||
// Read the stats
|
||
dwCPUUsage = pThis->m_Stats.dwMsComp * 1000UL / (pThis->m_Stats.dwNewestTs - pThis->m_Stats.dwOldestTs);
|
||
dwBWUsage = pThis->m_Stats.dwBits * 1000UL / (pThis->m_Stats.dwNewestTs - pThis->m_Stats.dwOldestTs);
|
||
|
||
// Initialize QoS structure. Only the four first fields should be zeroed.
|
||
ZeroMemory(&(pThis->m_Stats), 4UL * sizeof(DWORD));
|
||
|
||
// Record the time of this call for the next callback call
|
||
pThis->m_Stats.dwOldestTs = pThis->m_Stats.dwNewestTs;
|
||
}
|
||
else
|
||
dwBWUsage = dwCPUUsage = 0UL;
|
||
|
||
// Get the latest RTCP stats and update the counters.
|
||
// we do this here because it is called periodically.
|
||
if (pThis->m_pRTPSend)
|
||
{
|
||
UINT lastPacketsLost = pThis->m_RTPStats.packetsLost;
|
||
if (g_pctrAudioSendLost && SUCCEEDED(pThis->m_pRTPSend->GetSendStats(&pThis->m_RTPStats)))
|
||
UPDATE_COUNTER(g_pctrAudioSendLost, pThis->m_RTPStats.packetsLost-lastPacketsLost);
|
||
}
|
||
|
||
// Leave critical section
|
||
LeaveCriticalSection(&(pThis->m_crsQos));
|
||
|
||
|
||
// Get the max for the resources.
|
||
#ifdef LOGSTATISTICS_ON
|
||
iMaxCPUUsage = -1L; iMaxBWUsage = -1L;
|
||
#endif
|
||
for (i=0, iCPUUsageId = -1L, iBWUsageId = -1L; i<(int)lpResourceRequestList->cRequests; i++)
|
||
if (lpResourceRequestList->aRequests[i].resourceID == RESOURCE_OUTGOING_BANDWIDTH)
|
||
iBWUsageId = i;
|
||
else if (lpResourceRequestList->aRequests[i].resourceID == RESOURCE_CPU_CYCLES)
|
||
iCPUUsageId = i;
|
||
|
||
#ifdef LOGSTATISTICS_ON
|
||
if (iBWUsageId != -1L)
|
||
iMaxBWUsage = lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin;
|
||
if (iCPUUsageId != -1L)
|
||
iMaxCPUUsage = lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin;
|
||
#endif
|
||
|
||
// Update the QoS resources (only if you need less than what's available)
|
||
if (iCPUUsageId != -1L)
|
||
{
|
||
if ((int)dwCPUUsage < lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin)
|
||
lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin = dwCPUUsage;
|
||
}
|
||
|
||
if (iBWUsageId != -1L)
|
||
{
|
||
if ((int)dwBWUsage < lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin)
|
||
lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin = dwBWUsage;
|
||
}
|
||
|
||
#ifdef LOGSTATISTICS_ON
|
||
// How are we doing?
|
||
if (iCPUUsageId != -1L)
|
||
{
|
||
wsprintf(szDebug, " A: Max CPU Usage: %ld, Current CPU Usage: %ld\r\n", iMaxCPUUsage, dwCPUUsage);
|
||
OutputDebugString(szDebug);
|
||
}
|
||
if (iBWUsageId != -1L)
|
||
{
|
||
wsprintf(szDebug, " A: Max BW Usage: %ld, Current BW Usage: %ld\r\n", iMaxBWUsage, dwBWUsage);
|
||
OutputDebugString(szDebug);
|
||
}
|
||
#endif
|
||
|
||
return hr;
|
||
}
|
||
|
||
|
||
|
||
HRESULT __stdcall SendAudioStream::AddDigit(int nDigit)
|
||
{
|
||
IMediaChannel *pIMC = NULL;
|
||
RecvMediaStream *pRecv = NULL;
|
||
BOOL bIsStarted;
|
||
|
||
if ((!(m_DPFlags & DPFLAG_CONFIGURED_SEND)) || (m_pRTPSend==NULL))
|
||
{
|
||
return DPR_NOT_CONFIGURED;
|
||
}
|
||
|
||
bIsStarted = (m_DPFlags & DPFLAG_STARTED_SEND);
|
||
|
||
if (bIsStarted)
|
||
{
|
||
Stop();
|
||
}
|
||
|
||
m_pDTMF->AddDigitToQueue(nDigit);
|
||
SendDTMF();
|
||
|
||
|
||
|
||
m_pDP->GetMediaChannelInterface(MCF_RECV | MCF_AUDIO, &pIMC);
|
||
if (pIMC)
|
||
{
|
||
pRecv = static_cast<RecvMediaStream *> (pIMC);
|
||
pRecv->DTMFBeep();
|
||
pIMC->Release();
|
||
}
|
||
|
||
if (bIsStarted)
|
||
{
|
||
Start();
|
||
}
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
|
||
HRESULT __stdcall SendAudioStream::SendDTMF()
|
||
{
|
||
HRESULT hr;
|
||
MediaPacket **ppAudPckt, *pPacket;
|
||
ULONG uCount;
|
||
UINT uBufferSize, uBytesSent;
|
||
void *pBuffer;
|
||
bool bMark = true;
|
||
DWORD dwSamplesPerPkt;
|
||
MMRESULT mmr;
|
||
DWORD dwSamplesPerSec;
|
||
DWORD dwPacketTimeMS;
|
||
DWORD_PTR dwPropVal;
|
||
UINT uTimerID;
|
||
HANDLE hEvent = m_pDTMF->GetEvent();
|
||
|
||
|
||
m_InMedia->GetProp (MC_PROP_SPP, &dwPropVal);
|
||
dwSamplesPerPkt = (DWORD)dwPropVal;
|
||
|
||
m_InMedia->GetProp (MC_PROP_SPS, &dwPropVal);
|
||
dwSamplesPerSec = (DWORD)dwPropVal;
|
||
|
||
dwPacketTimeMS = (dwSamplesPerPkt * 1000) / dwSamplesPerSec;
|
||
|
||
timeBeginPeriod(5);
|
||
ResetEvent(hEvent);
|
||
uTimerID = timeSetEvent(dwPacketTimeMS-1, 5, (LPTIMECALLBACK)hEvent, 0, TIME_CALLBACK_EVENT_SET|TIME_PERIODIC);
|
||
|
||
// since the stream is stopped, just grab any packet
|
||
// from the TxStream
|
||
|
||
m_SendStream->GetRing(&ppAudPckt, &uCount);
|
||
pPacket = ppAudPckt[0];
|
||
pPacket->GetDevData(&pBuffer, &uBufferSize);
|
||
|
||
hr = m_pDTMF->ReadFromQueue((BYTE*)pBuffer, uBufferSize);
|
||
|
||
while (SUCCEEDED(hr))
|
||
{
|
||
|
||
// there should be only 1 tone in the queue (it can handle more)
|
||
// so assume we only need to set the mark bit on the first packet
|
||
|
||
|
||
pPacket->m_fMark = bMark;
|
||
bMark = false;
|
||
|
||
pPacket->SetProp(MP_PROP_TIMESTAMP, m_SendTimestamp);
|
||
m_SendTimestamp += dwSamplesPerPkt;
|
||
|
||
pPacket->SetState (MP_STATE_RECORDED);
|
||
|
||
// compress
|
||
mmr = m_pAudioFilter->Convert((AudioPacket*)pPacket, AP_ENCODE);
|
||
if (mmr == MMSYSERR_NOERROR)
|
||
{
|
||
pPacket->SetState(MP_STATE_ENCODED);
|
||
SendPacket((AudioPacket*)pPacket, &uBytesSent);
|
||
pPacket->m_fMark=false;
|
||
pPacket->SetState(MP_STATE_RESET);
|
||
}
|
||
|
||
hr = m_pDTMF->ReadFromQueue((BYTE*)pBuffer, uBufferSize);
|
||
|
||
// so that we don't overload the receive jitter buffer on the remote
|
||
// side, sleep a few milliseconds between sending packets
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
WaitForSingleObject(hEvent, dwPacketTimeMS);
|
||
ResetEvent(hEvent);
|
||
}
|
||
}
|
||
|
||
timeKillEvent(uTimerID);
|
||
timeEndPeriod(5);
|
||
return S_OK;
|
||
}
|
||
|
||
|
||
HRESULT __stdcall SendAudioStream::ResetDTMF()
|
||
{
|
||
if(!(m_DPFlags & DPFLAG_STARTED_SEND))
|
||
{
|
||
return S_OK;
|
||
}
|
||
|
||
return m_pDTMF->ClearQueue();
|
||
}
|
||
|
||
|
||
|