windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dvoice/dxvutils/inqueue2.cpp

956 lines
36 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*==========================================================================
*
* Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
*
* File: inqueue2.cpp
* Content:
*
* History:
* Date By Reason
* ==== == ======
* 07/16/99 pnewson Created
* 07/27/99 pnewson Overhauled to support new message numbering method
* 08/03/99 pnewson General clean up
* 08/24/99 rodtoll Fixed for release builds -- removed m_wQueueId from debug block
* 10/28/99 pnewson Bug #113933 debug spew too verbose
* 01/31/2000 pnewson replace SAssert with DNASSERT
* 02/17/2000 rodtoll Bug #133691 - Choppy audio - queue was not adapting
* 07/09/2000 rodtoll Added signature bytes
* 08/28/2000 masonb Voice Merge: Change #if DEBUG to #ifdef DEBUG
* 09/13/2000 rodtoll Bug #44519 - Fix for fix.
* 10/24/2000 rodtoll Bug #47645 - DPVOICE: Memory corruption - quality array end being overwritten
*
***************************************************************************/
#include "dxvutilspch.h"
#undef DPF_SUBCOMP
#define DPF_SUBCOMP DN_SUBCOMP_VOICE
#define MODULE_ID INPUTQUEUE2
const int c_iHighestQualitySliderValue = 31;
const int c_iHighestRecentBiasSliderValue = 31;
const double c_dHighestPossibleQuality = 0.001;
const double c_dLowestPossibleQuality = 0.05;
const double c_dHighestPossibleAggr = 5000.0;
const double c_dLowestPossibleAggr = 120000.0;
const double c_dMaxDistanceFromOpt = 100.0;
const double c_dQualityTimeFactor = 1000.0; // in ms
const double c_dQualityFactor = 2.0;
const int c_iFinishedQueueLifetime = 2000; // in ms
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::CInputQueue2"
CInputQueue2::CInputQueue2( )
: m_dwSignature(VSIG_INPUTQUEUE2)
, m_fFirstDequeue(TRUE)
, m_fFirstEnqueue(TRUE)
, m_bCurMsgNum(0)
, m_vdQualityRatings(0)
, m_vdFactoredOptQuals(0)
, m_bCurHighWaterMark(0)
, m_bMaxHighWaterMark(0)
, m_bInitHighWaterMark(0)
, m_wQueueId(0)
, m_dwTotalFrames(0)
, m_dwTotalMessages(0)
, m_dwTotalBadMessages(0)
, m_dwDiscardedFrames(0)
, m_dwDuplicateFrames(0)
, m_dwLostFrames(0)
, m_dwLateFrames(0)
, m_dwOverflowFrames(0)
, m_wMSPerFrame(0)
, m_pFramePool(NULL)
{
}
HRESULT CInputQueue2::Initialize( PQUEUE_PARAMS pParams )
{
m_fFirstDequeue = TRUE;
m_fFirstEnqueue = TRUE;
m_bCurMsgNum = 0;
m_vdQualityRatings.resize(pParams->bMaxHighWaterMark);
m_vdFactoredOptQuals.resize(pParams->bMaxHighWaterMark);
m_bCurHighWaterMark = pParams->bInitHighWaterMark;
m_bMaxHighWaterMark = pParams->bMaxHighWaterMark;
m_bInitHighWaterMark = pParams->bInitHighWaterMark;
m_wQueueId = pParams->wQueueId;
m_dwTotalFrames = 0;
m_dwTotalMessages = 0;
m_dwTotalBadMessages = 0;
m_dwDiscardedFrames = 0;
m_dwDuplicateFrames = 0;
m_dwLostFrames = 0;
m_dwLateFrames = 0;
m_dwOverflowFrames = 0;
m_wMSPerFrame = pParams->wMSPerFrame;
m_pFramePool = pParams->pFramePool;
if (!DNInitializeCriticalSection(&m_csQueue))
{
return DVERR_OUTOFMEMORY;
}
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::CInputQueue2() bInnerQueueSize: %i"), m_wQueueId, bInnerQueueSize);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::CInputQueue2() bMaxHighWaterMark: %i"), m_wQueueId, bMaxHighWaterMark);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::CInputQueue2() bInitHighWaterMark: %i"), m_wQueueId, bInitHighWaterMark);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::CInputQueue2() pFramePool: %p"), m_wQueueId, m_pFramePool);
#endif
//// TODO(pnewson, "use one inner queuepool for all queues")
m_pInnerQueuePool =
new CInnerQueuePool(
pParams->bInnerQueueSize,
pParams->wFrameSize,
m_pFramePool,
&m_csQueue);
if( m_pInnerQueuePool == NULL )
{
DPFX(DPFPREP, 0, "Error allocating innerqueue pool!" );
DNDeleteCriticalSection(&m_csQueue);
return DVERR_OUTOFMEMORY;
}
if (!m_pInnerQueuePool->Init())
{
delete m_pInnerQueuePool;
DNDeleteCriticalSection(&m_csQueue);
return DVERR_OUTOFMEMORY;
}
// see header for explanation
// since this is the first time, init the
// member variables, before we call the set
// functions. Weird, but it makes the debug
// messages cleaner. It doesn't acutally
// fix a real problem.
#ifdef DEBUG
m_iQuality = pParams->iQuality;
m_iHops = pParams->iHops;
m_iAggr = pParams->iAggr;
#endif
SetQuality(pParams->iQuality, pParams->iHops);
SetAggr(pParams->iAggr);
// set the queue to an empty state
Reset();
return DV_OK;
}
void CInputQueue2::GetStatistics( PQUEUE_STATISTICS pQueueStats )
{
pQueueStats->dwTotalFrames = GetTotalFrames();
pQueueStats->dwTotalMessages = GetTotalMessages();
pQueueStats->dwTotalBadMessages = GetTotalBadMessages();
pQueueStats->dwDiscardedFrames = GetDiscardedFrames();
pQueueStats->dwDuplicateFrames = GetDuplicateFrames();
pQueueStats->dwLostFrames = GetLostFrames();
pQueueStats->dwLateFrames = GetLateFrames();
pQueueStats->dwOverflowFrames = GetOverflowFrames();
}
void CInputQueue2::DeInitialize()
{
// delete anything remaining in the inner queue list
for (std::list<CInnerQueue*>::iterator iter = m_lpiqInnerQueues.begin(); iter != m_lpiqInnerQueues.end(); ++iter)
{
delete *iter;
}
m_lpiqInnerQueues.clear();
if( m_pInnerQueuePool )
{
delete m_pInnerQueuePool;
m_pInnerQueuePool = NULL;
}
DNDeleteCriticalSection(&m_csQueue);
}
// The destructor. Release all the resources we acquired in the
// constructor
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::~CInputQueue2"
CInputQueue2::~CInputQueue2()
{
DeInitialize();
m_dwSignature = VSIG_INPUTQUEUE2_FREE;
}
// This function clears all the input buffers and
// resets the other class information to an initial
// state. The queue should not be in use when this
// function is called. i.e. there should not be any
// locked frames.
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::Reset"
void CInputQueue2::Reset()
{
// make sure no one is using the queue right now
BFCSingleLock csl(&m_csQueue);
csl.Lock();
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Reset()"), m_wQueueId);
#endif
// loop through and return all inner queues to the pool
for (std::list<CInnerQueue*>::iterator iter = m_lpiqInnerQueues.begin(); iter != m_lpiqInnerQueues.end(); ++iter)
{
m_pInnerQueuePool->Return(*iter);
}
// the next frame will be the first one we accept
m_fFirstEnqueue = TRUE;
// we have not yet received a dequeue request
m_fFirstDequeue = TRUE;
// we don't yet know the first message number, so just use zero
m_bCurMsgNum = 0;
// we should reset back to zero for the current high water mark
m_bCurHighWaterMark = m_bInitHighWaterMark;
// reset the track record on the various high water marks
for (int i = 0; i < m_bMaxHighWaterMark; ++i)
{
m_vdQualityRatings[i] = m_vdFactoredOptQuals[i];
}
// reset all the other stats too
m_dwDiscardedFrames = 0;
m_dwDuplicateFrames = 0;
m_dwLateFrames = 0;
m_dwLostFrames = 0;
m_dwOverflowFrames = 0;
m_dwQueueErrors = 0;
m_dwTotalBadMessages = 0;
m_dwTotalFrames = 0;
m_dwTotalMessages = 0;
}
// Call this function to add a frame to the queue. I
// considered returning a reference to a frame which
// the caller could then stuff, but because the frames
// will not always arrive in order, that would mean I would have
// to copy the frame sometimes anyway. So, for simplicity, the
// caller has allocated a frame, which it passes a reference
// to, and this function will copy that frame into the
// appropriate place in the queue, according to its
// sequence number.
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::Enqueue"
void CInputQueue2::Enqueue(const CFrame& fr)
{
// start the critical section
BFCSingleLock csl(&m_csQueue);
csl.Lock();
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** ******************************************"), m_wQueueId);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() MsgNum[%i] SeqNum[%i]"), m_wQueueId, fr.GetMsgNum(), fr.GetSeqNum());
#endif
// Only add the frame if a dequeue has been
// requested. This allows the producer and
// consumer threads to sync up during their
// startup, or after a reset.
if (m_fFirstDequeue == TRUE)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() First Dequeue Not Yet Received - Frame Discarded"), m_wQueueId);
#endif
return;
}
// check to see if this is the first enqueue request
// we've accepted.
if (m_fFirstEnqueue == TRUE)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() First Enqueue"), m_wQueueId);
#endif
// clear the first frame flag
m_fFirstEnqueue = FALSE;
// Since this is the first frame we are accepting,
// we can just get a new inner queue without
// worry that one already exists for this message.
// Note that there should not be any queues already!
DNASSERT(m_lpiqInnerQueues.size() == 0);
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() Creating Inner queue for MsgNum %i"), m_wQueueId, fr.GetMsgNum());
#endif
m_lpiqInnerQueues.push_back(m_pInnerQueuePool->Get(m_bCurHighWaterMark, m_wQueueId, fr.GetMsgNum()));
// stuff the frame into the inner queue
(*m_lpiqInnerQueues.begin())->Enqueue(fr);
}
else
{
// see if we already have a queue started for this message number
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() Checking for an inner queue to put this frame into"), m_wQueueId);
#endif
bool fDone = false;
for (std::list<CInnerQueue*>::iterator iter = m_lpiqInnerQueues.begin(); iter != m_lpiqInnerQueues.end(); ++iter)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() found inner queue for msg number %i"), m_wQueueId, (*iter)->GetMsgNum());
#endif
if ((*iter)->GetMsgNum() == fr.GetMsgNum())
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() this is the one, queue size: %i"), m_wQueueId, (*iter)->GetSize());
#endif
// we have found the queue for this frame
switch ((*iter)->GetState())
{
case CInnerQueue::empty:
// we should not get here, since this state is
// only valid for the first frame of a message,
// which is added to the queue below, not in this
// case statement.
DNASSERT(false);
break;
case CInnerQueue::filling:
// check to see if the queue was empty
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() inner queue in filling state"), m_wQueueId);
#endif
if ((*iter)->GetSize() == 0)
{
// the queue was empty. If we have been
// trying to dequeue from it, we now know
// that the message was not done, so those
// dequeues were breaks in the speech.
// update the stats accordingly
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() - converting possible zero length dequeues to known in MsgNum[%i]"), m_wQueueId, fr.GetMsgNum());
#endif
(*iter)->AddToKnownZeroLengthDequeues(
(*iter)->GetPossibleZeroLengthDequeues());
}
// NOTE: falling through!!!
case CInnerQueue::ready:
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() inner queue in ready state (unless the previous message said filling)"), m_wQueueId);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() calling InnerQueue->Enqueue MsgNum[%i]"), m_wQueueId, fr.GetMsgNum());
#endif
(*iter)->Enqueue(fr);
break;
case CInnerQueue::finished:
// do nothing, just discard the frame
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() not calling InnerQueue->Enqueue - MsgNum[%i] in finished state, discarding frame"), m_wQueueId, fr.GetMsgNum());
#endif
break;
}
// done, get out.
return;
}
}
// If we get here, there is not already a queue active for this
// message number, so create one and stuff it with the frame and add
// it to the list.
//// TODO(pnewson, "Use the message number to insert the new inner queue in the right place")
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() Creating Inner queue for MsgNum %i"), m_wQueueId, fr.GetMsgNum());
#endif
CInnerQueue* piq = m_pInnerQueuePool->Get(m_bCurHighWaterMark, m_wQueueId, fr.GetMsgNum());
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Enqueue() calling InnerQueue->Enqueue MsgNum[%i]"), m_wQueueId, fr.GetMsgNum());
#endif
piq->Enqueue(fr);
m_lpiqInnerQueues.push_back(piq);
}
}
// This function retrieves the next frame from the head of
// the queue. For speed, it does not copy the data out of the
// buffer, but instead returns a pointer to the actual
// frame from the queue. When the caller is finished with
// the CFrame object, it should call Return() on it. This will
// return the frame to the frame pool, and update the queue's
// internal pointers to show that the queue slot is now free.
// If the caller doesn't call Return() in time, when the queue
// attempts to reuse the slot, it will DNASSERT(). The caller
// should always Return frame before it attempts to dequeue
// another one.
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::Dequeue"
CFrame* CInputQueue2::Dequeue()
{
// start the critical section
BFCSingleLock csl(&m_csQueue);
csl.Lock();
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** ******************************************"), m_wQueueId);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue()"), m_wQueueId);
#endif
CFrame* pfrReturn = 0;
if (m_fFirstDequeue == TRUE)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() First Dequeue"), m_wQueueId);
#endif
// trigger the interlock, so we're free to start enqueueing
m_fFirstDequeue = FALSE;
// since we're not allowed to enqueue until after the
// first dequeue, there will be no inner queues.
// So return a silent frame
pfrReturn = m_pFramePool->Get(&m_csQueue, NULL);
pfrReturn->SetIsSilence(TRUE);
pfrReturn->SetIsLost(false);
return pfrReturn;
}
else
{
pfrReturn = 0;
int iDeadTime;
// cycle through the list of active inner queues
std::list<CInnerQueue*>::iterator iter = m_lpiqInnerQueues.begin();
while (iter != m_lpiqInnerQueues.end())
{
std::list<CInnerQueue*>::iterator cur = iter;
std::list<CInnerQueue*>::iterator next = ++iter;
switch ((*cur)->GetState())
{
case CInnerQueue::finished:
// We keep these old, dead inner queues around for a while
// so that any late straggling frames that were part of this
// message get discarded. We know when to kill them off for
// good because we keep incrementing the possible zero length
// dequeue count. If this finished queue is stale enough,
// return it to the pool.
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() current queue in finished state"), m_wQueueId);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() PossibleZeroLengthDequeues: %i"), m_wQueueId, (*cur)->GetPossibleZeroLengthDequeues());
#endif
(*cur)->IncPossibleZeroLengthDequeues();
iDeadTime = (*cur)->GetPossibleZeroLengthDequeues() * m_wMSPerFrame;
if (iDeadTime > c_iFinishedQueueLifetime)
{
// this queue has been dead long enough, kill it off
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, "***** RETURNING INNER QUEUE TO POOL *****");
#endif
m_pInnerQueuePool->Return(*cur);
m_lpiqInnerQueues.erase(cur);
}
break;
case CInnerQueue::filling:
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() current queue in filling state"), m_wQueueId);
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() queue size: %i"), m_wQueueId, (*cur)->GetSize());
#endif
if ((*cur)->GetSize() > 0)
{
// If there is a message after this one, then release this
// message for playback.
// OR
// If we have been spinning trying to release this message
// for a while, then just let it go... The message may be
// too short to trip the high water mark.
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() filling dequeue reqs: %i"), m_wQueueId, (*cur)->GetFillingDequeueReqs());
#endif
if (next != m_lpiqInnerQueues.end()
|| (*cur)->GetFillingDequeueReqs() > (*cur)->GetHighWaterMark())
{
// set the state to ready, and dequeue
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() setting state to ready and dequeing"), m_wQueueId);
#endif
(*cur)->SetState(CInnerQueue::ready);
return (*cur)->Dequeue();
}
}
else
{
// there is nothing in this queue
// check to see if another message has begun to arrive
if (next != m_lpiqInnerQueues.end())
{
// there is another message coming in after this
// one, so flip this queue to the finished state
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() new message arriving, setting state to finished"), m_wQueueId);
#endif
(*cur)->SetState(CInnerQueue::finished);
// harvest the stats from this message, now that it
// is done
HarvestStats(*cur);
// Go to the next iteration of this loop, which will
// either dequeue a frame from the next message, or
// return an empty frame
break;
}
}
// if we get here, there is something in this queue, but we are
// not yet ready to release it yet.
// we should return an extra frame and remember
// that we've been here...
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() not ready to release message, returning empty frame"), m_wQueueId);
#endif
(*cur)->IncFillingDequeueReqs();
pfrReturn = m_pFramePool->Get(&m_csQueue, NULL);
pfrReturn->SetIsSilence(TRUE);
pfrReturn->SetIsLost(false);
return pfrReturn;
case CInnerQueue::ready:
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() Queue Size: %i"), m_wQueueId, (*cur)->GetSize());
#endif
// check to see if this ready queue is empty
if ((*cur)->GetSize() == 0)
{
// increment the possible zero length dequeue count
(*cur)->IncPossibleZeroLengthDequeues();
// check to see if another message has begun to arrive
if (next != m_lpiqInnerQueues.end())
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() queue is empty, new message arriving, setting state to finished"), m_wQueueId);
#endif
// there is another message coming in after this
// one, so flip this queue to the finished state
(*cur)->SetState(CInnerQueue::finished);
// harvest the stats from this message, now that it
// is done
HarvestStats(*cur);
// Go to the next iteration of this loop, which will
// either dequeue a frame from the next message, or
// return an empty frame
break;
}
// there is nothing in this queue, and there are no more
// messages arriving after this one, so boot this inner
// queue into the filling state so if this is just a long
// pause in the message, it will fill to the high water mark
// again before it begins to play again.
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() queue is empty, setting state to filling, returning empty frame"), m_wQueueId);
#endif
(*cur)->SetState(CInnerQueue::filling);
// return an extra frame
pfrReturn = m_pFramePool->Get(&m_csQueue, NULL);
pfrReturn->SetIsSilence(TRUE);
pfrReturn->SetIsLost(false);
return pfrReturn;
}
else
{
// there's something to return, so do it
return (*cur)->Dequeue();
}
}
}
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::Dequeue() nothing available in inner queues, returning empty frame"), m_wQueueId);
#endif
// if we get here, there was nothing suitable in the queues
// (if there were any queues) so return an extra frame
pfrReturn = m_pFramePool->Get(&m_csQueue, NULL);
pfrReturn->SetIsSilence(TRUE);
pfrReturn->SetIsLost(false);
return pfrReturn;
}
}
// This function should be called each time a queue is moved to the finished
// state. That's when we have officially declared that the message is finished,
// so it's a good time to see how we handled it. This will also reset the
// stats so the next message starts fresh.
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::HarvestStats"
void CInputQueue2::HarvestStats(CInnerQueue* piq)
{
m_dwDuplicateFrames += piq->GetDuplicateFrames();
// now that the message is actually complete, we're in a better
// position to decide accurately how many frames were late
// vs. how many were actually lost. When something isn't
// there when it's needed, we increment the missing frames
// count. If it subsequently arrives, it's counted as late.
// Therefore the true count of lost frames is the difference
// between the missing and late counts. Ditto if a frame,
// overflows. We discard it so it's not there when we need it,
// it will then get reported as missing. So subtract that too.
m_dwLostFrames += piq->GetMissingFrames()
- piq->GetLateFrames() - piq->GetOverflowFrames();
m_dwLateFrames += piq->GetLateFrames();
m_dwOverflowFrames += piq->GetOverflowFrames();
// What to do with the zero length dequeue stat? From a
// certain point of view, only one frame was late. From
// another point of view, all the subsequent frames were
// late. Hmmm.... Lets take the middle road and say that
// for each failed zero length dequeue we'll count it as
// equivalent to a late frame
m_dwLateFrames += piq->GetKnownZeroLengthDequeues();
m_dwTotalFrames += piq->GetMsgLen();
m_dwTotalMessages++;
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::HarvestStats() DuplicateFrames:%i; MissingFrames:%i; LateFrames:%i; OverflowFrames:%i; KnownZeroLengthDequeues:%i; MsgLen:%i;"),
m_wQueueId, piq->GetDuplicateFrames(), piq->GetMissingFrames(), piq->GetLateFrames(), piq->GetOverflowFrames(), piq->GetKnownZeroLengthDequeues(), piq->GetMsgLen());
#endif
// Build a carefully formatted debug string that will give me some data,
// but not give away all of our wonderful queue secrets.
// dump the string to the debug log
// Note!!! If you change the format of this debug string,
// please roll the version number, i.e. HVT1A -> HVT2A so
// we can decode any logs!
DPFX(DPFPREP, DVF_INFOLEVEL, _T("HVT1A:%i:%i:%i:%i:%i:%i"),
m_wQueueId,
m_bCurHighWaterMark,
(int)(m_vdQualityRatings[m_bCurHighWaterMark] * 10000),
piq->GetMsgLen(),
piq->GetKnownZeroLengthDequeues() + piq->GetLateFrames(),
piq->GetMissingFrames());
// The idea:
// The quality quotient is a number between 0 and 1 that indicates
// the quality of the current connection. 0 means perfect - no bad
// stuff, ever. 1 means awful - 100% bad stuff. The number
// is really a weighted average of the number of good frames vs the number
// of bad frames, biased towards the recent frames. The message
// we just received makes up 'm_wFrameStrength * m_wMsgLen' percent of the
// total value. The past history is deweighted by (1 - m_wFrameStrength*m_wMsgLen)
// so the significance of a message decays as time goes by
// and according to the size of the message (number of frames).
//
// Another idea:
// Keep a vector that tracks how good the quality is at each
// high water mark.
// That way, when we want to make a jump up or down in the
// water mark, we can consider it carefully first. This
// gives the adaptive algorithm some memory of what life
// was like at each queue level.
// We choose an optimum level, like .02, that we are
// shooting for. We keep searching the space of high water
// marks until we find the one that's closest to the
// optimum. This optimum is configurable.
//
// The initial quality of each of the high water marks
// is set to the optimum. This quality will then
// vary as that high water mark gains experience.
// If it dips below a certain threshold, then
// we'll jump to the next level up. If that one
// is too good, it will go above a threshold, at
// which point we can consider going back down.
//
// The problem with this is the sudden things that games
// dish out (like when Q2 starts up) when they don't
// lets us have the CPU for a while. These could
// unduly punish a particular high water mark,
// there's really not much we can do about it.
// Oh, well. We'll give it a shot.
// Adjust the quality rating of this watermark.
// The algorithm is requires the results from the last
// message, contained in the inner queue, along with the
// current quality rating.
DNASSERT( m_bCurHighWaterMark < m_bMaxHighWaterMark );
m_vdQualityRatings[m_bCurHighWaterMark] =
AdjustQuality(piq, m_vdQualityRatings[m_bCurHighWaterMark]);
// see if this put us above the highest acceptable quality rating
// we asserted that m_dOptimumQuality != 0.0 in SetQuality, so we
// don't need to check for division by zero
if (m_vdQualityRatings[m_bCurHighWaterMark] / m_vdFactoredOptQuals[m_bCurHighWaterMark] > m_dQualityThreshold)
{
// Check to see which is closer
// the the optimum quality - the current high water
// mark or the one above it. Only do this test if
// we're not already at the maximum high water mark
if (m_bCurHighWaterMark < (m_bMaxHighWaterMark-1) )
{
// To check the "distance" from the optimum, use the
// inverse of the qualities. That normalizes it to
// our perception of quality
// calculate how far the current high water mark
// is from optimum
double dCurDistFromOpt = m_vdQualityRatings[m_bCurHighWaterMark] / m_vdFactoredOptQuals[m_bCurHighWaterMark];
if (dCurDistFromOpt < 1)
{
// quality ratings can never go to zero (enforced in
// AdjustQuality() so this division will never by by zero
dCurDistFromOpt = 1.0 / dCurDistFromOpt;
}
// calculate how far the next high water mark is from
// optimum
//
// quality ratings can never go to zero (enforced in
// AdjustQuality() so this division will never by by zero
double dNextDistFromOpt = m_vdFactoredOptQuals[m_bCurHighWaterMark + 1] / m_vdQualityRatings[m_bCurHighWaterMark + 1];
if (dNextDistFromOpt < 1)
{
dNextDistFromOpt = 1.0 / dNextDistFromOpt;
}
// if the next high water mark is closer to the
// optimum than this one, switch to it.
if (dNextDistFromOpt < dCurDistFromOpt)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::HarvestStats() Raising High Water Mark to %i"), m_wQueueId, m_bCurHighWaterMark + 1);
#endif
SetNewHighWaterMark(m_bCurHighWaterMark + 1);
}
}
}
// see if this put us below the low threshold
//
// quality ratings can never go to zero (enforced in
// AdjustQuality() so this division will never by by zero
if (m_vdFactoredOptQuals[m_bCurHighWaterMark] / m_vdQualityRatings[m_bCurHighWaterMark] > m_dQualityThreshold)
{
// The quality has moved below the high quality threshold
// Check to see what is closer to the optimum quality -
// the current high water mark or the one below this one.
// Only do this test if we're not already at a zero
// high water mark.
if (m_bCurHighWaterMark > 0)
{
// To check the "distance" from the optimum, use the
// inverse of the qualities. That normalizes it to
// our perception of quality
// calculate how far the current high water mark
// is from optimum
double dCurDistFromOpt = m_vdQualityRatings[m_bCurHighWaterMark] / m_vdFactoredOptQuals[m_bCurHighWaterMark];
if (dCurDistFromOpt < 1)
{
dCurDistFromOpt = 1.0 / dCurDistFromOpt;
}
// calculate how far the previous (lower) high water mark is from
// optimum
double dPrevDistFromOpt = m_vdFactoredOptQuals[m_bCurHighWaterMark - 1] / m_vdQualityRatings[m_bCurHighWaterMark - 1];
if (dPrevDistFromOpt < 1)
{
dPrevDistFromOpt = 1.0 / dPrevDistFromOpt;
}
// if the prev high water mark is closer to the
// optimum than this one, switch to it.
if (dPrevDistFromOpt < dCurDistFromOpt)
{
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::HarvestStats() Lowering High Water Mark to %i"), m_wQueueId, m_bCurHighWaterMark - 1);
#endif
SetNewHighWaterMark(m_bCurHighWaterMark - 1);
}
}
}
// clear the stats on the inner queue
piq->ResetStats();
}
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::AdjustQuality"
double CInputQueue2::AdjustQuality(CInnerQueue* piq, double dCurrentQuality)
{
// if the message is zero length, no adjustment is made
// to the queue...
if (piq->GetMsgLen() == 0)
{
return dCurrentQuality;
}
// The longer a message, the stronger its effect on the
// current quality rating.
double dWeighting = min(piq->GetMsgLen() * m_dFrameStrength, 1.0);
// The message quality is the quotient of bad
// stuff that happened (zero length dequeues
// and late frames) to the total number of
// frames in the message. Note that we do not
// count lost frames against the message since
// moving to a higher water mark wouldn't help.
// Note that we impose a "worst case" of 1.0
double dMsgQuality = min(((double)(piq->GetKnownZeroLengthDequeues() + piq->GetLateFrames()) / (double)piq->GetMsgLen()), 1.0);
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::AdjustQuality() dWeighting: %g; dMsgQuality: %g; dCurrentQuality %g;"),
m_wQueueId, dWeighting, dMsgQuality, dCurrentQuality);
#endif
// The new quality rating is a combination of the
// current quality rating, and the quality of the
// most recent message, weighted by the message length.
double dNewQuality = (dCurrentQuality * (1.0 - dWeighting)) + (dMsgQuality * dWeighting);
// We don't want to allow extremes of quality, or else they will set up
// barriers in the queue statistics that can never be overcome (especially
// a "perfect" quality of zero). So we check here to make sure that the
// new quality is within reason.
double dCurDistFromOpt = dNewQuality / m_dOptimumQuality;
if (dCurDistFromOpt < 1.0 / c_dMaxDistanceFromOpt)
{
dNewQuality = m_dOptimumQuality / c_dMaxDistanceFromOpt;
}
else if (dCurDistFromOpt > c_dMaxDistanceFromOpt)
{
dNewQuality = m_dOptimumQuality * c_dMaxDistanceFromOpt;
}
return dNewQuality;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::SetNewHighWaterMark"
void CInputQueue2::SetNewHighWaterMark(BYTE bNewHighWaterMark)
{
DNASSERT( bNewHighWaterMark < m_bMaxHighWaterMark );
if( bNewHighWaterMark >= m_bMaxHighWaterMark )
{
DNASSERT( FALSE );
return;
}
m_bCurHighWaterMark = bNewHighWaterMark;
for (std::list<CInnerQueue*>::iterator iter = m_lpiqInnerQueues.begin(); iter != m_lpiqInnerQueues.end(); iter++)
{
(*iter)->SetHighWaterMark(bNewHighWaterMark);
}
return;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::SetQuality"
void CInputQueue2::SetQuality(int iQuality, int iHops)
{
m_iQuality = iQuality;
m_iHops = iHops;
double dQualityRatio = c_dHighestPossibleQuality / c_dLowestPossibleQuality;
double dInputRatio = (double) iQuality / (double) c_iHighestQualitySliderValue;
m_dOptimumQuality = pow(dQualityRatio, dInputRatio) * c_dLowestPossibleQuality;
#if defined(DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::SetQuality(%i, %i): m_dOptimumQuality: %f" ), m_wQueueId, iQuality, iHops, m_dOptimumQuality);
#endif
// The quality that the user has requested should be considered
// over the number of hops involved. At the end of each hop is
// a queue who's watermark will be set according to this rating,
// To get an end to end quality that reflects the user's choice,
// this queue's quality rating must be better if it is not the
// only queue in the path. The total number of on time packets is
// the product (as in multiple) of on time packets for each hop.
// Therefore we need to take the Nth root of 1-m_dOptimumQuality
// where N is the number of hops, and subtract that value from 1
// to get the appropriate quality rating for this queue. (get that?)
if (m_iHops > 1)
{
m_dOptimumQuality = (1 - pow((1.0 - m_dOptimumQuality), 1.0 / (double)m_iHops));
}
// the optimum quality should never be zero, or completely perfect,
// or the algorithm will not work.
DNASSERT(m_dOptimumQuality != 0.0);
// update the vector of factored qualities
// We don't just use the raw optimum as provided by the
// caller. We "factor" it such that as the high watermark
// gets larger (and the latency therefore longer) we are
// willing to accept a lower quality.
for (int i = 0; i < m_bMaxHighWaterMark; ++i)
{
m_vdFactoredOptQuals[i] = m_dOptimumQuality *
pow(c_dQualityFactor, (double)(i * m_wMSPerFrame) / c_dQualityTimeFactor);
}
// Build a carefully formatted debug string that will give me some data,
// but not give away all of our wonderful queue secrets.
// dump the string to the debug log
// Note!!! If you change the format of this debug string,
// please roll the version number, i.e. HVT1B -> HVT2B so
// we can decode any logs!
DPFX(DPFPREP, DVF_INFOLEVEL, _T("HVT1B:%i:%i:%i:%i"), m_wQueueId, m_iQuality, m_iHops, m_iAggr);
}
#undef DPF_MODNAME
#define DPF_MODNAME "CInputQueue2::SetAggr"
void CInputQueue2::SetAggr(int iAggr)
{
m_iAggr = iAggr;
double dAggrRatio = c_dHighestPossibleAggr / c_dLowestPossibleAggr;
double dInputRatio = (double) iAggr / (double) c_iHighestQualitySliderValue;
double dAggr = pow(dAggrRatio, dInputRatio) * c_dLowestPossibleAggr;
// The aggressiveness is the number of milliseconds of "memory" the queue
// has for each watermark. To find the frame strength, divide the
// number of milliseconds per frame by the "memory".
m_dFrameStrength = (double)m_wMSPerFrame / dAggr;
// We are using a fixed 1% threshold now - the aggressiveness is now set
// through the frame strength. This low threshold will make the queue
// adapt very quickly at first, while it is learning something about
// the various watermarks, but will settle down more after that.
m_dQualityThreshold = 1.01;
#if defined (DPVOICE_QUEUE_DEBUG)
DPFX(DPFPREP, DVF_INFOLEVEL, _T("** QUEUE ** %i ** CInputQueue2::SetAggr(%i): dAggr: %f, m_dFrameStrength: %f, m_dQualityThreshold %f"), m_wQueueId, m_iAggr, dAggr, m_dFrameStrength, m_dQualityThreshold);
#endif
// Build a carefully formatted debug string that will give me some data,
// but not give away all of our wonderful queue secrets.
// dump the string to the debug log
// Note!!! If you change the format of this debug string,
// please roll the version number, i.e. HVT1C -> HVT2C so
// we can decode any logs!
DPFX(DPFPREP, DVF_INFOLEVEL, _T("HVT1C:%i:%i:%i:%i"), m_wQueueId, m_iQuality, m_iHops, m_iAggr);
}