1509 lines
40 KiB
C++
1509 lines
40 KiB
C++
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
qccall.cpp
|
|
|
|
Abstract:
|
|
|
|
Implementation of CCallQualityControlRelay
|
|
|
|
Author:
|
|
|
|
Qianbo Huai (qhuai) 03/10/2000
|
|
|
|
--*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
HRESULT TypeStream (IUnknown *p, LONG *pMediaType, TERMINAL_DIRECTION *pDirection);
|
|
|
|
class CInnerStreamLock
|
|
{
|
|
private:
|
|
|
|
// no refcount
|
|
IInnerStreamQualityControl *m_pQC;
|
|
|
|
public:
|
|
|
|
CInnerStreamLock(IInnerStreamQualityControl *pQC, BOOL *pfLocked)
|
|
:m_pQC(NULL)
|
|
{
|
|
DWORD dwCount = 0;
|
|
|
|
*pfLocked = FALSE;
|
|
|
|
do
|
|
{
|
|
// try lock
|
|
if (S_OK == pQC->TryLockStream())
|
|
{
|
|
m_pQC = pQC;
|
|
*pfLocked = TRUE;
|
|
|
|
if (dwCount > 0)
|
|
{
|
|
LOG((MSP_TRACE, "InnerStreamLock: Succeed after %d tries %p", dwCount, pQC));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// check if stream is accessing QC
|
|
if (S_OK == pQC->IsAccessingQC())
|
|
{
|
|
LOG((MSP_WARN, "InnerStreamLock: Giving up to avoid deadlock %p", pQC));
|
|
return;
|
|
}
|
|
|
|
// try again
|
|
if (dwCount++ == 10)
|
|
{
|
|
LOG((MSP_WARN, "InnerStreamLock: Giving up after 10 tries %p", pQC));
|
|
return;
|
|
}
|
|
|
|
// sleep 10 ms, default callback threshold is 7000 ms
|
|
SleepEx(10, TRUE);
|
|
|
|
} while (TRUE);
|
|
|
|
// should never hit this line
|
|
return;
|
|
}
|
|
|
|
~CInnerStreamLock()
|
|
{
|
|
if (m_pQC != NULL)
|
|
{
|
|
m_pQC->UnlockStream();
|
|
m_pQC = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID NTAPI WaitOrTimerCallback (
|
|
PVOID pCallQCRelay,
|
|
BOOLEAN bTimerFired
|
|
)
|
|
{
|
|
((CCallQualityControlRelay*)pCallQCRelay)->CallbackProc (bTimerFired);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
CCallQualityControlRelay::CCallQualityControlRelay ()
|
|
:m_fInitiated (FALSE)
|
|
,m_pCall (NULL)
|
|
,m_hWait (NULL)
|
|
,m_hQCEvent (NULL)
|
|
,m_dwControlInterval (QCDEFAULT_QUALITY_CONTROL_INTERVAL)
|
|
,m_fStop (FALSE)
|
|
,m_fStopAck (FALSE)
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
,m_hQCDbg (NULL)
|
|
,m_fQCDbgTraceCPULoad (FALSE)
|
|
,m_fQCDbgTraceBitrate (FALSE)
|
|
#endif // DEBUG_QUALITY_CONTROL
|
|
,m_lConfBitrate (QCDEFAULT_QUALITY_UNSET)
|
|
,m_lPrefMaxCPULoad (QCDEFAULT_MAX_CPU_LOAD)
|
|
,m_lPrefMaxOutputBitrate (QCDEFAULT_QUALITY_UNSET)
|
|
{
|
|
m_lCPUUpThreshold = m_lPrefMaxCPULoad + (LONG)(100 * QCDEFAULT_UP_THRESHOLD);
|
|
if (m_lCPUUpThreshold > 100)
|
|
m_lCPUUpThreshold = 100;
|
|
|
|
m_lCPULowThreshold = m_lPrefMaxCPULoad - (LONG)(100 * QCDEFAULT_LOW_THRESHOLD);
|
|
if (m_lCPULowThreshold < 0)
|
|
m_lCPULowThreshold = 0;
|
|
|
|
m_lOutBitUpThreshold = QCDEFAULT_QUALITY_UNSET;
|
|
m_lOutBitLowThreshold = QCDEFAULT_QUALITY_UNSET;
|
|
}
|
|
|
|
CCallQualityControlRelay::~CCallQualityControlRelay ()
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::~CCallQualityControlRelay");
|
|
|
|
HRESULT hr;
|
|
|
|
// if not initialized, no resource has been allocated
|
|
if (!m_fInitiated) return;
|
|
|
|
_ASSERT (m_fStopAck);
|
|
|
|
CloseHandle (m_hQCEvent);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
create event handle, create main thread, start cpu usage collection
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::Initialize (CIPConfMSPCall *pCall)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::Initialize");
|
|
|
|
CLock lock (m_lock_QualityData);
|
|
|
|
LOG ((MSP_TRACE, "%s entered. call=%p", __fxName, pCall));
|
|
|
|
// avoid re-entry
|
|
if (m_fInitiated)
|
|
{
|
|
LOG ((MSP_WARN, "%s is re-entered", __fxName));
|
|
return S_OK;
|
|
}
|
|
|
|
// create qc event
|
|
m_hQCEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
if (NULL == m_hQCEvent)
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to create qc event", __fxName));
|
|
return E_FAIL;
|
|
}
|
|
|
|
// keep a refcount on msp call
|
|
pCall->MSPCallAddRef ();
|
|
m_pCall = pCall;
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
QCDbgInitiate ();
|
|
#endif // DEBUG_QUALITY_CONTROL
|
|
|
|
m_fInitiated = TRUE;
|
|
|
|
// we want to distribute resources based on default value before graphs are running
|
|
CallbackProc (TRUE);
|
|
|
|
LOG ((MSP_TRACE, "%s returns. call=%p", __fxName, pCall));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Decription:
|
|
stop main thread, close qc event handle, release stream qc helpers,
|
|
stop cpu usage collection
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::Shutdown (VOID)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::Shutdown");
|
|
|
|
// quality data should alway be locked before inner stream qc
|
|
CLock lock1 (m_lock_QualityData);
|
|
CLock lock2 (m_lock_aInnerStreamQC);
|
|
|
|
LOG ((MSP_TRACE, "%s entered. call=%p. init=%d. stop=%d",
|
|
__fxName, m_pCall, m_fInitiated, m_fStop));
|
|
|
|
if (!m_fInitiated) return S_OK;
|
|
if (m_fStop) return S_OK;
|
|
|
|
// set stop signal
|
|
m_fStop = TRUE;
|
|
|
|
if (!SetEvent (m_hQCEvent))
|
|
LOG ((MSP_ERROR, "%s failed to set event, %d", __fxName, GetLastError ()));
|
|
|
|
// release stream qc helper
|
|
int i;
|
|
for (i=0; i<m_aInnerStreamQC.GetSize (); i++)
|
|
{
|
|
// an false input to unlink inner call qc on stream
|
|
// forces the stream to remove its pointer to call but not to call
|
|
// deregister again.
|
|
m_aInnerStreamQC[i]->UnlinkInnerCallQC (FALSE);
|
|
m_aInnerStreamQC[i]->Release ();
|
|
}
|
|
m_aInnerStreamQC.RemoveAll ();
|
|
|
|
//StopCPUUsageCollection ();
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
QCDbgShutdown ();
|
|
#endif // DEBUG_QUALITY_CONTROL
|
|
|
|
LOG ((MSP_TRACE, "%s returns. call=%p", __fxName, m_pCall));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
store conference-wide bandwidth
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::SetConfBitrate (
|
|
LONG lConfBitrate
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::SetConfBitrate");
|
|
|
|
CLock lock (m_lock_QualityData);
|
|
|
|
// check if the limit is valid
|
|
if (lConfBitrate < QCLIMIT_MIN_CONFBITRATE)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_lConfBitrate = lConfBitrate;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
return stored conference-wide bandwidth
|
|
////*/
|
|
LONG
|
|
CCallQualityControlRelay::GetConfBitrate ()
|
|
{
|
|
CLock lock (m_lock_QualityData);
|
|
|
|
if (m_lConfBitrate == QCDEFAULT_QUALITY_UNSET)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return m_lConfBitrate;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
store inner stream QC interface
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::RegisterInnerStreamQC (
|
|
IN IInnerStreamQualityControl *pIInnerStreamQC
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::RegisterInnerStreamQC");
|
|
|
|
// check input pointer
|
|
if (IsBadReadPtr (pIInnerStreamQC, sizeof (IInnerStreamQualityControl)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s got bad read pointer", __fxName));
|
|
return E_POINTER;
|
|
}
|
|
|
|
// store the pointer
|
|
CLock lock (m_lock_aInnerStreamQC);
|
|
if (m_aInnerStreamQC.Find (pIInnerStreamQC) > 0)
|
|
{
|
|
LOG ((MSP_ERROR, "%s already stored inner stream qc", __fxName));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!m_aInnerStreamQC.Add (pIInnerStreamQC))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to add inner stream QC", __fxName));
|
|
return E_FAIL;
|
|
}
|
|
|
|
pIInnerStreamQC->AddRef ();
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
remove the inner stream QC
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::DeRegisterInnerStreamQC (
|
|
IN IInnerStreamQualityControl *pIInnerStreamQC
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::DeRegisterInnerStreamQC");
|
|
|
|
// check input pointer
|
|
if (IsBadReadPtr (pIInnerStreamQC, sizeof (IInnerStreamQualityControl)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s got bad read pointer", __fxName));
|
|
return E_POINTER;
|
|
}
|
|
|
|
// remove the pointer
|
|
CLock lock (m_lock_aInnerStreamQC);
|
|
if (!m_aInnerStreamQC.Remove (pIInnerStreamQC))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to remove inner stream QC, %x", __fxName, pIInnerStreamQC));
|
|
return E_FAIL;
|
|
}
|
|
|
|
pIInnerStreamQC->Release ();
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
this method might be supported in the future
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::ProcessQCEvent (
|
|
IN QCEvent event,
|
|
IN DWORD dwParam
|
|
)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
set quality control related properies on a call
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::Set(
|
|
IN CallQualityProperty property,
|
|
IN LONG lValue,
|
|
IN TAPIControlFlags lFlags
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::Set CallQualityProperty");
|
|
|
|
HRESULT hr;
|
|
|
|
CLock lock (m_lock_QualityData);
|
|
switch (property)
|
|
{
|
|
case CallQuality_ControlInterval:
|
|
// timeout for the thread
|
|
if (lValue < QCLIMIT_MIN_QUALITY_CONTROL_INTERVAL ||
|
|
lValue > QCLIMIT_MAX_QUALITY_CONTROL_INTERVAL)
|
|
{
|
|
LOG ((MSP_ERROR, "%s, control interval %d is out of range", __fxName, lValue));
|
|
return E_INVALIDARG;
|
|
}
|
|
m_dwControlInterval = (DWORD)lValue;
|
|
break;
|
|
|
|
case CallQuality_MaxCPULoad:
|
|
// perfered maximum cpu load
|
|
if ((lValue < QCLIMIT_MIN_CPU_LOAD) ||
|
|
(lValue > QCLIMIT_MAX_CPU_LOAD))
|
|
{
|
|
LOG ((MSP_ERROR, "%s got out-of-limit cpu load. %d", __fxName, lValue));
|
|
return E_INVALIDARG;
|
|
}
|
|
m_lPrefMaxCPULoad = lValue;
|
|
|
|
m_lCPUUpThreshold = lValue + (LONG)(100 * QCDEFAULT_UP_THRESHOLD);
|
|
if (m_lCPUUpThreshold > 100)
|
|
m_lCPUUpThreshold = 100;
|
|
|
|
m_lCPULowThreshold = lValue - (LONG)(100 * QCDEFAULT_LOW_THRESHOLD);
|
|
if (m_lCPULowThreshold < 0)
|
|
m_lCPULowThreshold = 0;
|
|
|
|
break;
|
|
|
|
case CallQuality_MaxOutputBitrate:
|
|
// prefered maximum bitrate for the call
|
|
if (lValue < QCLIMIT_MIN_BITRATE)
|
|
{
|
|
LOG ((MSP_ERROR, "%s, bitrate %d is less than min limit", __fxName, lValue));
|
|
return E_INVALIDARG;
|
|
}
|
|
m_lPrefMaxOutputBitrate = lValue;
|
|
|
|
m_lOutBitUpThreshold = (LONG)(lValue * (1 + QCDEFAULT_UP_THRESHOLD));
|
|
|
|
m_lOutBitLowThreshold = (LONG)(lValue * (1 - QCDEFAULT_LOW_THRESHOLD));
|
|
if (m_lOutBitLowThreshold < QCLIMIT_MIN_BITRATE)
|
|
m_lOutBitLowThreshold = QCLIMIT_MIN_BITRATE;
|
|
|
|
break;
|
|
|
|
default:
|
|
LOG ((MSP_ERROR, "%s got invalid property %d", __fxName, property));
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
Description:
|
|
retrieve call quality control property
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::Get(
|
|
IN CallQualityProperty property,
|
|
OUT LONG *plValue,
|
|
OUT TAPIControlFlags *plFlags
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::Get QCCall_e");
|
|
|
|
// check input pointer
|
|
if (IsBadWritePtr (plValue, sizeof (LONG)) ||
|
|
IsBadWritePtr (plFlags, sizeof (LONG)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s got bad write pointer", __fxName));
|
|
return E_POINTER;
|
|
}
|
|
|
|
CLock lock (m_lock_QualityData);
|
|
|
|
*plFlags = TAPIControl_Flags_None;
|
|
*plValue = QCDEFAULT_QUALITY_UNSET;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
switch (property)
|
|
{
|
|
case CallQuality_ControlInterval:
|
|
*plValue = (LONG)m_dwControlInterval;
|
|
break;
|
|
|
|
case CallQuality_ConfBitrate:
|
|
*plValue = GetConfBitrate ();
|
|
break;
|
|
|
|
case CallQuality_CurrCPULoad:
|
|
|
|
DWORD dw;
|
|
if (!GetCPUUsage (&dw))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to retrieve CPU usage", __fxName));
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
*plValue = (LONG)dw;
|
|
break;
|
|
|
|
case CallQuality_CurrInputBitrate:
|
|
// !!! BOTH locks are locked
|
|
// !!! MUST: QualityData lock first, InnerStreamQC second
|
|
|
|
m_lock_aInnerStreamQC.Lock ();
|
|
|
|
if (FAILED (hr = GetCallBitrate (
|
|
TAPIMEDIATYPE_AUDIO | TAPIMEDIATYPE_VIDEO, TD_RENDER, plValue)))
|
|
|
|
LOG ((MSP_ERROR, "%s failed to compute input bitrate, %x", __fxName, hr));
|
|
|
|
m_lock_aInnerStreamQC.Unlock ();
|
|
break;
|
|
|
|
case CallQuality_CurrOutputBitrate:
|
|
// !!! BOTH locks are locked
|
|
// !!! MUST: QualityData lock first, InnerStreamQC second
|
|
|
|
m_lock_aInnerStreamQC.Lock ();
|
|
|
|
if (FAILED (hr = GetCallBitrate (
|
|
TAPIMEDIATYPE_AUDIO | TAPIMEDIATYPE_VIDEO, TD_CAPTURE, plValue)))
|
|
|
|
LOG ((MSP_ERROR, "%s failed to compute output bitrate, %x", __fxName, hr));
|
|
|
|
m_lock_aInnerStreamQC.Unlock ();
|
|
break;
|
|
|
|
default:
|
|
LOG ((MSP_ERROR, "%s got invalid property %d", __fxName, property));
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CCallQualityControlRelay::GetRange (
|
|
IN CallQualityProperty Property,
|
|
OUT long *plMin,
|
|
OUT long *plMax,
|
|
OUT long *plSteppingDelta,
|
|
OUT long *plDefault,
|
|
OUT TAPIControlFlags *plFlags
|
|
)
|
|
{
|
|
// no need to lock
|
|
|
|
if (IsBadWritePtr (plMin, sizeof (long)) ||
|
|
IsBadWritePtr (plMax, sizeof (long)) ||
|
|
IsBadWritePtr (plSteppingDelta, sizeof (long)) ||
|
|
IsBadWritePtr (plDefault, sizeof (long)) ||
|
|
IsBadWritePtr (plFlags, sizeof (TAPIControlFlags)))
|
|
{
|
|
LOG ((MSP_ERROR, "CCallQualityControlRelay::GetRange bad write pointer"));
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr;
|
|
switch (Property)
|
|
{
|
|
case CallQuality_ControlInterval:
|
|
|
|
*plMin = QCLIMIT_MIN_QUALITY_CONTROL_INTERVAL;
|
|
*plMax = QCLIMIT_MAX_QUALITY_CONTROL_INTERVAL;
|
|
*plSteppingDelta = 1;
|
|
*plDefault = QCDEFAULT_QUALITY_CONTROL_INTERVAL;
|
|
*plFlags = TAPIControl_Flags_None;
|
|
hr = S_OK;
|
|
|
|
break;
|
|
|
|
case CallQuality_MaxCPULoad:
|
|
|
|
*plMin = QCLIMIT_MIN_CPU_LOAD;
|
|
*plMax = QCLIMIT_MAX_CPU_LOAD;
|
|
*plSteppingDelta = 1;
|
|
*plDefault = QCDEFAULT_MAX_CPU_LOAD;
|
|
*plFlags = TAPIControl_Flags_None;
|
|
hr = S_OK;
|
|
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::CallbackProc (BOOLEAN bTimerFired)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::CallbackProc");
|
|
|
|
DWORD dwResult;
|
|
|
|
// always lock quality data first
|
|
m_lock_QualityData.Lock ();
|
|
m_lock_aInnerStreamQC.Lock ();
|
|
|
|
// set wait handle to null
|
|
if (m_hWait) UnregisterWait (m_hWait);
|
|
m_hWait = NULL;
|
|
|
|
if (m_fStop) {
|
|
LOG ((MSP_TRACE, "%s is being stopped. call=%p", __fxName, m_pCall));
|
|
|
|
m_fStopAck = TRUE;
|
|
|
|
m_lock_aInnerStreamQC.Unlock ();
|
|
m_lock_QualityData.Unlock ();
|
|
|
|
m_pCall->MSPCallRelease ();
|
|
return;
|
|
}
|
|
|
|
if (!bTimerFired)
|
|
LOG ((MSP_ERROR, "%s, QC events are not supported", __fxName));
|
|
else
|
|
ReDistributeResources ();
|
|
|
|
BOOL fSuccess = RegisterWaitForSingleObject (
|
|
&m_hWait,
|
|
m_hQCEvent,
|
|
WaitOrTimerCallback,
|
|
(PVOID) this,
|
|
m_dwControlInterval,
|
|
WT_EXECUTEONLYONCE
|
|
);
|
|
|
|
if (!fSuccess || NULL == m_hWait)
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to register wait, %d", __fxName, GetLastError ()));
|
|
LOG ((MSP_TRACE, "%s self-stops. call=%p", __fxName, m_pCall));
|
|
|
|
m_fStopAck = TRUE;
|
|
|
|
m_hWait = NULL;
|
|
|
|
m_lock_aInnerStreamQC.Unlock ();
|
|
m_lock_QualityData.Unlock ();
|
|
|
|
m_pCall->MSPCallRelease ();
|
|
|
|
return;
|
|
}
|
|
|
|
m_lock_aInnerStreamQC.Unlock ();
|
|
m_lock_QualityData.Unlock ();
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
HRESULT
|
|
CCallQualityControlRelay::GetCallBitrate (
|
|
LONG MediaType,
|
|
TERMINAL_DIRECTION Direction,
|
|
LONG *plValue
|
|
)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::GetCallBitrate");
|
|
|
|
LONG sum = 0;
|
|
LONG bitrate;
|
|
TAPIControlFlags flags;
|
|
HRESULT hr;
|
|
|
|
*plValue = 0;
|
|
ITStream *pStream;
|
|
LONG mediatype;
|
|
TERMINAL_DIRECTION direction;
|
|
|
|
int i;
|
|
for (i=0; i<m_aInnerStreamQC.GetSize (); i++)
|
|
{
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->QueryInterface (
|
|
__uuidof (ITStream), (void**)&pStream)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get ITStream interface. %x", __fxName, hr));
|
|
return hr;
|
|
}
|
|
|
|
hr = pStream->get_Direction (&direction);
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get stream direction. %x", __fxName, hr));
|
|
pStream->Release ();
|
|
return hr;
|
|
}
|
|
|
|
hr = pStream->get_MediaType (&mediatype);
|
|
pStream->Release ();
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get stream media type. %x", __fxName, hr));
|
|
return hr;
|
|
}
|
|
|
|
if (!(MediaType & mediatype) || // skip if mediatype not match
|
|
!(direction == TD_BIDIRECTIONAL || Direction == direction))
|
|
continue;
|
|
|
|
// get bitrate from each stream
|
|
hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_CurrBitrate, &bitrate, &flags);
|
|
|
|
if (E_NOTIMPL == hr)
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get bitrate from stream. %x", __fxName, hr));
|
|
return hr;
|
|
}
|
|
|
|
sum += bitrate;
|
|
}
|
|
|
|
*plValue = sum;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::ReDistributeResources (VOID)
|
|
{
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
// read quality settings from registry
|
|
QCDbgRead ();
|
|
#endif // DEBUG_QUALITY_CONTROL
|
|
|
|
ReDistributeBandwidth ();
|
|
|
|
ReDistributeCPU ();
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::ReDistributeCPU (VOID)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::ReDistributeCPU");
|
|
|
|
HRESULT hr;
|
|
int i, num_manual=0, num_total=m_aInnerStreamQC.GetSize ();
|
|
LONG framerate;
|
|
TAPIControlFlags flag;
|
|
|
|
// check each stream, if manual, adjust based on preferred value
|
|
for (i=0; i<num_total; i++)
|
|
{
|
|
BOOL fStreamLocked = FALSE;
|
|
|
|
CInnerStreamLock lock(m_aInnerStreamQC[i], &fStreamLocked);
|
|
|
|
if (!fStreamLocked)
|
|
{
|
|
// abort re-distribute resources
|
|
return;
|
|
}
|
|
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_PrefMinFrameInterval, &framerate, &flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get pref max frame rate (unset) on stream, %x", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
if (flag == TAPIControl_Flags_Manual)
|
|
{
|
|
num_manual ++;
|
|
|
|
// use preferred value
|
|
hr = m_aInnerStreamQC[i]->Set (InnerStreamQuality_AdjMinFrameInterval, framerate, flag);
|
|
|
|
if (E_NOTIMPL == hr)
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to set adj max frame interval. %x", __fxName, hr));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if global cpu load out of range, just return
|
|
// it should not happen but we have a back door in registry for debugging purpose
|
|
// just be careful here
|
|
if (QCLIMIT_MIN_CPU_LOAD > m_lPrefMaxCPULoad ||
|
|
QCLIMIT_MAX_CPU_LOAD < m_lPrefMaxCPULoad)
|
|
return;
|
|
|
|
// compute current usage
|
|
DWORD dw;
|
|
if (!GetCPUUsage (&dw))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get CPU usage", __fxName));
|
|
return;
|
|
}
|
|
LONG usage = (LONG)dw;
|
|
|
|
// return if within thresholds
|
|
if (usage >= m_lCPULowThreshold &&
|
|
usage <= m_lCPUUpThreshold)
|
|
return;
|
|
|
|
// percent to be adjusted
|
|
FLOAT percent = ((FLOAT)(m_lPrefMaxCPULoad - usage)) / m_lPrefMaxCPULoad;
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceCPULoad)
|
|
LOG ((MSP_TRACE, "QCTrace CPU: overall = %d, target = %d", usage, m_lPrefMaxCPULoad));
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
for (i=0; i<num_total; i++)
|
|
{
|
|
BOOL fStreamLocked = FALSE;
|
|
|
|
CInnerStreamLock lock(m_aInnerStreamQC[i], &fStreamLocked);
|
|
|
|
if (!fStreamLocked)
|
|
{
|
|
// abort re-distribute resources
|
|
return;
|
|
}
|
|
|
|
// get flag
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_PrefMinFrameInterval, &framerate, &flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get pref max frame rate (unset) on stream, %d", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
// if manual, skip
|
|
if (flag == TAPIControl_Flags_Manual)
|
|
continue;
|
|
|
|
// get current frame rate on the stream
|
|
if (E_NOTIMPL == (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_AvgFrameInterval,
|
|
&framerate, &flag)))
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get frame rate on stream, %x", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
// need to low cpu but interval is already maximum
|
|
if (percent <0 && framerate >= QCLIMIT_MAX_FRAME_INTERVAL)
|
|
continue;
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceCPULoad)
|
|
{
|
|
ITStream *pStream = NULL;
|
|
BSTR bstr = NULL;
|
|
|
|
if (S_OK == m_aInnerStreamQC[i]->QueryInterface (__uuidof (ITStream), (void**)&pStream))
|
|
{
|
|
pStream->get_Name (&bstr);
|
|
pStream->Release ();
|
|
}
|
|
|
|
LOG ((MSP_TRACE, "QCTrace CPU: %ws frameinterval = %d", bstr, framerate));
|
|
|
|
if (bstr) SysFreeString (bstr);
|
|
}
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
// heuristic here is to take into consideration of stream not having been adjusted
|
|
framerate -= (LONG) (framerate * percent * (1 + num_manual*0.2));
|
|
|
|
if (framerate > QCLIMIT_MAX_FRAME_INTERVAL)
|
|
framerate = QCLIMIT_MAX_FRAME_INTERVAL;
|
|
if (framerate < QCLIMIT_MIN_FRAME_INTERVAL)
|
|
framerate = QCLIMIT_MIN_FRAME_INTERVAL;
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceCPULoad)
|
|
LOG ((MSP_TRACE, "QCTrace CPU: target frameinterval = %d", framerate));
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
// set new value
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->Set (InnerStreamQuality_AdjMinFrameInterval,
|
|
framerate, flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to set frame interval on stream, %x", __fxName, hr));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::ReDistributeBandwidth (VOID)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::ReDistributeBandwidth");
|
|
|
|
HRESULT hr;
|
|
int i, num_manual=0, num_total=m_aInnerStreamQC.GetSize ();
|
|
LONG bitrate;
|
|
TAPIControlFlags flag;
|
|
|
|
LONG mediatype;
|
|
TERMINAL_DIRECTION direction;
|
|
|
|
// video out bitrate based on conference-wide bitrate
|
|
LONG vidoutbitrate = GetVideoOutBitrate ();
|
|
|
|
// check each stream, if manual, adjust based on preferred value
|
|
for (i=0; i<num_total; i++)
|
|
{
|
|
BOOL fStreamLocked = FALSE;
|
|
|
|
CInnerStreamLock lock(m_aInnerStreamQC[i], &fStreamLocked);
|
|
|
|
if (!fStreamLocked)
|
|
{
|
|
// abort re-distribute resources
|
|
return;
|
|
}
|
|
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_PrefMaxBitrate, &bitrate, &flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get pref max bitrate (unset) on stream, %d", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
if (flag == TAPIControl_Flags_Manual)
|
|
{
|
|
num_manual ++;
|
|
|
|
// check stream type
|
|
if (FAILED (::TypeStream (m_aInnerStreamQC[i], &mediatype, &direction)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get stream type", __fxName));
|
|
continue;
|
|
}
|
|
|
|
// if it is video out stream and conference-wide bitrate is set
|
|
// and the limit on video out stream is smaller than preferred value
|
|
if ((mediatype & TAPIMEDIATYPE_VIDEO) &&
|
|
direction == TD_CAPTURE &&
|
|
vidoutbitrate > QCLIMIT_MIN_BITRATE &&
|
|
vidoutbitrate < bitrate)
|
|
{
|
|
bitrate = vidoutbitrate;
|
|
}
|
|
|
|
hr = m_aInnerStreamQC[i]->Set (InnerStreamQuality_AdjMaxBitrate, bitrate, flag);
|
|
|
|
if (E_NOTIMPL == hr)
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to set adj max bitrate. %d", __fxName, hr));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return if target is not set
|
|
if (m_lPrefMaxOutputBitrate == QCDEFAULT_QUALITY_UNSET &&
|
|
vidoutbitrate < QCLIMIT_MIN_CONFBITRATE)
|
|
return;
|
|
|
|
// compute bitrate target based on preferred value and conference-wide limit
|
|
LONG usage;
|
|
if (S_OK != (hr = GetCallBitrate (
|
|
TAPIMEDIATYPE_VIDEO | TAPIMEDIATYPE_AUDIO, TD_CAPTURE, &usage)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get bandwidth usage, %x", __fxName, hr));
|
|
return;
|
|
}
|
|
|
|
// return if usage is within threshold
|
|
FLOAT percent = 0;
|
|
if (m_lPrefMaxOutputBitrate != QCDEFAULT_QUALITY_UNSET &&
|
|
(usage > m_lOutBitUpThreshold || usage < m_lOutBitLowThreshold))
|
|
{
|
|
percent = ((FLOAT)(m_lPrefMaxOutputBitrate - usage)) / m_lPrefMaxOutputBitrate;
|
|
}
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceBitrate && m_lPrefMaxOutputBitrate != QCDEFAULT_QUALITY_UNSET)
|
|
LOG ((MSP_TRACE, "QCTrace Bitrate: overall = %d, target = %d", usage, m_lPrefMaxOutputBitrate));
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
for (i=0; i<num_total; i++)
|
|
{
|
|
BOOL fStreamLocked = FALSE;
|
|
|
|
CInnerStreamLock lock(m_aInnerStreamQC[i], &fStreamLocked);
|
|
|
|
if (!fStreamLocked)
|
|
{
|
|
// abort re-distribute resources
|
|
return;
|
|
}
|
|
|
|
// get flag
|
|
if (FAILED (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_PrefMaxBitrate, &bitrate, &flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get pref max bitrate (unset) on stream, %d", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
if (FAILED (::TypeStream (m_aInnerStreamQC[i], &mediatype, &direction)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get stream type", __fxName));
|
|
continue;
|
|
}
|
|
|
|
// return if render
|
|
if (direction == TD_RENDER)
|
|
{
|
|
// only count manual for capture or bidirectional
|
|
if (flag == TAPIControl_Flags_Manual)
|
|
num_manual --;
|
|
|
|
continue;
|
|
}
|
|
|
|
// if manual, skip
|
|
if (flag == TAPIControl_Flags_Manual)
|
|
continue;
|
|
|
|
// we only deal with video capture stream
|
|
if (!(TAPIMEDIATYPE_VIDEO & mediatype))
|
|
continue;
|
|
|
|
// get current bit rate on the stream
|
|
if (E_NOTIMPL == (hr = m_aInnerStreamQC[i]->Get (InnerStreamQuality_CurrBitrate,
|
|
&bitrate, &flag)))
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get bitrate on stream, %x", __fxName, hr));
|
|
continue;
|
|
}
|
|
|
|
// need to low bandwidth but bitrate is already minimum
|
|
if (percent <0 && bitrate <= QCLIMIT_MIN_BITRATE)
|
|
continue;
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceBitrate)
|
|
{
|
|
ITStream *pStream = NULL;
|
|
BSTR bstr = NULL;
|
|
|
|
if (S_OK == m_aInnerStreamQC[i]->QueryInterface (__uuidof (ITStream), (void**)&pStream))
|
|
{
|
|
pStream->get_Name (&bstr);
|
|
pStream->Release ();
|
|
}
|
|
|
|
LOG ((MSP_TRACE, "QCTrace Bitrate: %ws bitrate = %d", bstr, bitrate));
|
|
|
|
if (bstr) SysFreeString (bstr);
|
|
}
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
//
|
|
// we are here because either m_lPrefMaxOutputBitrate is set by app,
|
|
// and/or conference-wide bandwidth is specified.
|
|
//
|
|
if (m_lPrefMaxOutputBitrate != QCDEFAULT_QUALITY_UNSET)
|
|
{
|
|
// percent makes sense here
|
|
// heuristic here is to take into consideration of stream not having been adjusted
|
|
bitrate += (LONG) (bitrate * percent * (1 + num_manual*0.3));
|
|
|
|
if (vidoutbitrate > QCLIMIT_MIN_BITRATE)
|
|
if (bitrate > vidoutbitrate)
|
|
bitrate = vidoutbitrate;
|
|
}
|
|
else
|
|
{
|
|
if (vidoutbitrate > QCLIMIT_MIN_BITRATE)
|
|
bitrate = vidoutbitrate;
|
|
}
|
|
|
|
if (bitrate < QCLIMIT_MIN_BITRATE)
|
|
bitrate = QCLIMIT_MIN_BITRATE;
|
|
|
|
if (bitrate < QCLIMIT_MIN_BITRATE*10)
|
|
{
|
|
// we want very lower bitrate, try to decrease frame rate as well
|
|
m_lPrefMaxCPULoad -= 5;
|
|
|
|
if (m_lPrefMaxCPULoad < QCLIMIT_MIN_CPU_LOAD)
|
|
m_lPrefMaxCPULoad = QCLIMIT_MIN_CPU_LOAD;
|
|
}
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
|
|
if (m_fQCDbgTraceBitrate)
|
|
LOG ((MSP_TRACE, "QCTrace Bitrate: target bitrate = %d", bitrate));
|
|
|
|
#endif //DEBUG_QUALITY_CONTROL
|
|
|
|
// set new value
|
|
if (E_NOTIMPL == (hr = m_aInnerStreamQC[i]->Set (InnerStreamQuality_AdjMaxBitrate,
|
|
bitrate, flag)))
|
|
continue;
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to set bitrate on stream, %x", __fxName, hr));
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_QUALITY_CONTROL
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::QCDbgInitiate (VOID)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::QCDbgInitiate");
|
|
|
|
if (ERROR_SUCCESS != RegOpenKeyEx (
|
|
HKEY_LOCAL_MACHINE,
|
|
_T("SOFTWARE\\Microsoft\\Tracing\\confqc"),
|
|
NULL,
|
|
KEY_READ,
|
|
&m_hQCDbg
|
|
))
|
|
{
|
|
LOG ((MSP_TRACE, "%s failed to open reg key", __fxName));
|
|
m_hQCDbg = NULL;
|
|
}
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::QCDbgRead (VOID)
|
|
{
|
|
ENTER_FUNCTION ("CCallQualityControlRelay::QCDbgRead");
|
|
|
|
m_fQCDbgTraceCPULoad = FALSE;
|
|
m_fQCDbgTraceBitrate = FALSE;
|
|
|
|
if (!m_hQCDbg)
|
|
return;
|
|
|
|
DWORD dwType, dwSize;
|
|
LONG lValue;
|
|
|
|
// if debug is enabled
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("DebugEnabled"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD != dwType || 1 != lValue)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LOG ((MSP_WARN, "%s failed to query debug flag", __fxName));
|
|
return;
|
|
}
|
|
|
|
// if print out trace info
|
|
m_fQCDbgTraceCPULoad = FALSE;
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("TraceCPULoad"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD == dwType && 1 == lValue)
|
|
m_fQCDbgTraceCPULoad = TRUE;
|
|
}
|
|
|
|
m_fQCDbgTraceBitrate = FALSE;
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("TraceBitrate"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD == dwType && 1 == lValue)
|
|
m_fQCDbgTraceBitrate = TRUE;
|
|
}
|
|
|
|
// control interval
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("ControlInterval"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD == dwType && lValue >= QCLIMIT_MIN_QUALITY_CONTROL_INTERVAL)
|
|
m_dwControlInterval = (DWORD)lValue;
|
|
else
|
|
LOG ((MSP_ERROR, "%s: qeury control interval wrong type %d or wrong value %d", __fxName, dwType, lValue));
|
|
}
|
|
|
|
// max cpu load
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("MaxCPULoad"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD == dwType && QCLIMIT_MIN_CPU_LOAD <= lValue && lValue <= QCLIMIT_MAX_CPU_LOAD)
|
|
m_lPrefMaxCPULoad = lValue;
|
|
else
|
|
LOG ((MSP_ERROR, "%s: qeury max cpu load wrong type %d or wrong value %d", __fxName, dwType, lValue));
|
|
|
|
// update threshold
|
|
m_lCPUUpThreshold = m_lPrefMaxCPULoad + (LONG)(100 * QCDEFAULT_UP_THRESHOLD);
|
|
if (m_lCPUUpThreshold > 100)
|
|
m_lCPUUpThreshold = 100;
|
|
|
|
m_lCPULowThreshold = m_lPrefMaxCPULoad - (LONG)(100 * QCDEFAULT_LOW_THRESHOLD);
|
|
if (m_lCPULowThreshold < 0)
|
|
m_lCPULowThreshold = 0;
|
|
}
|
|
|
|
// max call bitrate
|
|
if (ERROR_SUCCESS == RegQueryValueEx (
|
|
m_hQCDbg,
|
|
_T("MaxOutputBitrate"),
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&lValue,
|
|
&dwSize))
|
|
{
|
|
if (REG_DWORD == dwType && QCLIMIT_MIN_BITRATE <= lValue)
|
|
m_lPrefMaxOutputBitrate = lValue;
|
|
else
|
|
LOG ((MSP_ERROR, "%s: qeury max call bitrate wrong type %d or wrong value %d", __fxName, dwType, lValue));
|
|
|
|
// update threshold
|
|
m_lOutBitUpThreshold = (LONG)(lValue * (1 + QCDEFAULT_UP_THRESHOLD));
|
|
|
|
m_lOutBitLowThreshold = (LONG)(lValue * (1 - QCDEFAULT_LOW_THRESHOLD));
|
|
if (m_lOutBitLowThreshold < QCLIMIT_MIN_BITRATE)
|
|
m_lOutBitLowThreshold = QCLIMIT_MIN_BITRATE;
|
|
}
|
|
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
////*/
|
|
VOID
|
|
CCallQualityControlRelay::QCDbgShutdown (VOID)
|
|
{
|
|
if (m_hQCDbg)
|
|
{
|
|
RegCloseKey (m_hQCDbg);
|
|
m_hQCDbg = NULL;
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG_QUALITY_CONTROL
|
|
|
|
|
|
#pragma warning( disable: 4244 )
|
|
|
|
BOOL CCallQualityControlRelay::GetCPUUsage(PDWORD pdwOverallCPUUsage) {
|
|
|
|
SYSTEM_PERFORMANCE_INFORMATION PerfInfo;
|
|
static BOOL Initialized = FALSE;
|
|
static SYSTEM_PERFORMANCE_INFORMATION PreviousPerfInfo;
|
|
static SYSTEM_BASIC_INFORMATION BasicInfo;
|
|
static FILETIME PreviousFileTime;
|
|
static FILETIME CurrentFileTime;
|
|
|
|
LARGE_INTEGER EndTime, BeginTime, ElapsedTime;
|
|
int PercentBusy;
|
|
|
|
*pdwOverallCPUUsage = 0;
|
|
|
|
//
|
|
NTSTATUS Status =
|
|
NtQuerySystemInformation(
|
|
SystemPerformanceInformation,
|
|
&PerfInfo,
|
|
sizeof(PerfInfo),
|
|
NULL
|
|
);
|
|
|
|
if (NT_ERROR(Status))
|
|
return FALSE;
|
|
|
|
|
|
// first-time query...
|
|
if (!Initialized) {
|
|
|
|
// Get basic info (number of CPU)
|
|
Status =
|
|
NtQuerySystemInformation(
|
|
SystemBasicInformation,
|
|
&BasicInfo,
|
|
sizeof(BasicInfo),
|
|
NULL
|
|
);
|
|
|
|
if (NT_ERROR(Status))
|
|
return FALSE;
|
|
|
|
GetSystemTimeAsFileTime(&PreviousFileTime);
|
|
|
|
PreviousPerfInfo = PerfInfo;
|
|
*pdwOverallCPUUsage = 0;
|
|
Initialized = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GetSystemTimeAsFileTime(&CurrentFileTime);
|
|
|
|
LARGE_INTEGER TimeBetweenQueries;
|
|
|
|
//TimeBetweenQueries.QuadPart = (LARGE_INTEGER)CurrentFileTime - (LARGE_INTEGER)PreviousFileTime;
|
|
TimeBetweenQueries.HighPart = CurrentFileTime.dwHighDateTime - PreviousFileTime.dwHighDateTime;
|
|
TimeBetweenQueries.LowPart = CurrentFileTime.dwLowDateTime - PreviousFileTime.dwLowDateTime;
|
|
|
|
EndTime = *(PLARGE_INTEGER)&PerfInfo.IdleProcessTime;
|
|
BeginTime = *(PLARGE_INTEGER)&PreviousPerfInfo.IdleProcessTime;
|
|
|
|
ElapsedTime.QuadPart = EndTime.QuadPart - BeginTime.QuadPart;
|
|
|
|
if (TimeBetweenQueries.QuadPart <= 0)
|
|
{
|
|
PercentBusy = 0;
|
|
LOG ((MSP_WARN, "GetCPUUsage: TimeBetweenQueries.QuadPart, %d", TimeBetweenQueries.QuadPart));
|
|
}
|
|
else
|
|
{
|
|
PercentBusy = (int)
|
|
( ((TimeBetweenQueries.QuadPart - ElapsedTime.QuadPart) * 100) /
|
|
(BasicInfo.NumberOfProcessors * TimeBetweenQueries.QuadPart)
|
|
);
|
|
}
|
|
|
|
if ( PercentBusy > 100 )
|
|
PercentBusy = 100;
|
|
else if ( PercentBusy < 0 )
|
|
PercentBusy = 0;
|
|
|
|
PreviousFileTime = CurrentFileTime;
|
|
PreviousPerfInfo = PerfInfo;
|
|
|
|
*pdwOverallCPUUsage = (DWORD)PercentBusy;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////////
|
|
|
|
Description:
|
|
|
|
Computes video out bitrate based on conference-wide bandwidth
|
|
|
|
////*/
|
|
LONG CCallQualityControlRelay::GetVideoOutBitrate ()
|
|
{
|
|
//
|
|
// compute
|
|
// number of video in sub streams
|
|
// audio stream bitrate
|
|
//
|
|
|
|
HRESULT hr;
|
|
LONG videooutbps = QCDEFAULT_QUALITY_UNSET;
|
|
LONG audiobps = 0;
|
|
LONG bitrate = 0;
|
|
INT numvideoin = 0;
|
|
|
|
IEnumStream *pEnum = NULL;
|
|
ITStream *pStream = NULL;
|
|
ITStreamQualityControl *pStreamQC = NULL;
|
|
|
|
ULONG fetched = 0;
|
|
|
|
CStreamVideoRecv *pVideoRecv = NULL;
|
|
|
|
LONG mediatype;
|
|
TERMINAL_DIRECTION direction;
|
|
TAPIControlFlags flag;
|
|
|
|
ENTER_FUNCTION ("Relay::GetVideoOutBitrate");
|
|
|
|
if (m_lConfBitrate < QCLIMIT_MIN_CONFBITRATE)
|
|
return videooutbps;
|
|
|
|
if (FAILED (hr = m_pCall->EnumerateStreams (&pEnum)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to get IEnumStream. %x", __fxName, hr));
|
|
return videooutbps;
|
|
}
|
|
|
|
while (S_OK == pEnum->Next (1, &pStream, &fetched))
|
|
{
|
|
// check each stream
|
|
if (FAILED (hr = ::TypeStream (pStream, &mediatype, &direction)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to type stream. %x", __fxName, hr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// if audio out, get bitrate
|
|
if ((mediatype & TAPIMEDIATYPE_AUDIO) &&
|
|
direction == TD_CAPTURE)
|
|
{
|
|
if (FAILED (hr = pStream->QueryInterface (&pStreamQC)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to query stream quality control. %x", __fxName, hr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (FAILED (hr = pStreamQC->Get (StreamQuality_CurrBitrate, &bitrate, &flag)))
|
|
{
|
|
LOG ((MSP_ERROR, "%s failed to query bitrate. %x", __fxName, hr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
pStreamQC->Release ();
|
|
pStreamQC = NULL;
|
|
|
|
audiobps += bitrate;
|
|
}
|
|
|
|
// we only need video in here
|
|
if (!(mediatype & TAPIMEDIATYPE_VIDEO) || direction != TD_RENDER)
|
|
{
|
|
pStream->Release ();
|
|
pStream = NULL;
|
|
continue;
|
|
}
|
|
|
|
pVideoRecv = dynamic_cast<CStreamVideoRecv *>(pStream);
|
|
|
|
if (pVideoRecv != NULL)
|
|
numvideoin += pVideoRecv->GetSubStreamCount ();
|
|
|
|
pStream->Release ();
|
|
pStream = NULL;
|
|
}
|
|
|
|
pEnum->Release ();
|
|
pEnum = NULL;
|
|
|
|
// compute
|
|
numvideoin ++; // count self
|
|
|
|
// assume on average there are 1.5 persons talking in the conference.
|
|
// we ignore network overhead.
|
|
videooutbps = (LONG)(((FLOAT)m_lConfBitrate - 1.5*audiobps) / numvideoin);
|
|
|
|
Return:
|
|
|
|
return videooutbps;
|
|
|
|
Cleanup:
|
|
|
|
if (pEnum) pEnum->Release ();
|
|
if (pStream) pStream->Release ();
|
|
if (pStreamQC) pStreamQC->Release ();
|
|
|
|
goto Return;
|
|
}
|
|
|
|
HRESULT TypeStream (IUnknown *p, LONG *pMediaType, TERMINAL_DIRECTION *pDirection)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// get ITStream interface
|
|
ITStream *pStream = dynamic_cast<ITStream *>(p);
|
|
|
|
if (pStream == NULL)
|
|
{
|
|
LOG ((MSP_ERROR, "TypeStream failed to cast ITStream"));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// get stream direction
|
|
if (FAILED (hr = pStream->get_Direction (pDirection)))
|
|
{
|
|
LOG ((MSP_ERROR, "TypeStream failed to get stream direction. %x", hr));
|
|
return hr;
|
|
}
|
|
|
|
// get stream mediatype
|
|
if (FAILED (hr = pStream->get_MediaType (pMediaType)))
|
|
{
|
|
LOG ((MSP_ERROR, "TypeStream failed to get stream media type. %x", hr));
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|