windows-nt/Source/XPSP1/NT/net/tapi/skywalker/ipconf/msp/qccall.cpp
2020-09-26 16:20:57 +08:00

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;
}