📄 renqual.cpp
字号:
// We are going to play this frame. We may want to play it early.
// We will play it early if we think we are in slow machine mode.
// If we think we are NOT in slow machine mode, we will still play
// it early by m_trEarliness as this controls the graceful slide back.
// and in addition we aim at being m_trTarget late rather than "on time".
BOOL bPlayASAP = FALSE;
// we will play it AT ONCE (slow machine mode) if...
// ...we are playing catch-up
if ( bJustDroppedFrame) {
bPlayASAP = TRUE;
MSR_INTEGER(m_idDecision, 9001);
}
// ...or if we are running below the true frame rate
// exact comparisons are glitchy, for these measurements,
// so add an extra 5% or so
else if ( (m_trFrameAvg > trDuration + trDuration/16)
// It's possible to get into a state where we are losing ground, but
// are a very long way ahead. To avoid this or recover from it
// we refuse to play early by more than 10 frames.
&& (trLate > - trDuration*10)
){
bPlayASAP = TRUE;
MSR_INTEGER(m_idDecision, 9002);
}
#if 0
// ...or if we have been late and are less than one frame early
else if ( (trLate + trDuration > 0)
&& (m_trWaitAvg<=20000)
) {
bPlayASAP = TRUE;
MSR_INTEGER(m_idDecision, 9003);
}
#endif
// We will NOT play it at once if we are grossly early. On very slow frame
// rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
// because we got starved (for instance by the net) and dropped one frame
// some time or other. If we are more than 900mSec early, then wait.
if (trLate<-9000000) {
bPlayASAP = FALSE;
}
if (bPlayASAP) {
m_nNormal = 0;
MSR_INTEGER(m_idDecision, 0);
// When we are here, we are in slow-machine mode. trLate may well
// oscillate between negative and positive when the supplier is
// dropping frames to keep sync. We should not let that mislead
// us into thinking that we have as much as zero spare time!
// We just update with a zero wait.
m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
// Assume that we draw it immediately. Update inter-frame stats
m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
#ifndef PERF
// if this is NOT a perf build, then report what we know so far
// without looking at the clock any more. This assumes that we
// actually wait for exactly the time we hope to. it also reports
// how close we get to the hacked up time stamps that we now have
// rather than the ones we originally started with. It will
// therefore be a little optimistic. However it's fast.
PreparePerformanceData(trTrueLate, trFrame);
#endif
m_trLastDraw = trRealStream;
if (m_trEarliness > trLate) {
m_trEarliness = trLate; // if we are actually early, this is neg
}
Result = S_OK; // Draw it now
} else {
++m_nNormal;
// Set the average frame rate to EXACTLY the ideal rate.
// If we are exiting slow-machine mode then we will have caught up
// and be running ahead, so as we slide back to exact timing we will
// have a longer than usual gap at this point. If we record this
// real gap then we'll think that we're running slow and go back
// into slow-machine mode and vever get it straight.
m_trFrameAvg = trDuration;
MSR_INTEGER(m_idDecision, 1);
// Play it early by m_trEarliness and by m_trTarget
{
int trE = m_trEarliness;
if (trE < -m_trFrameAvg) {
trE = -m_trFrameAvg;
}
*ptrStart += trE; // N.B. earliness is negative
}
int Delay = -trTrueLate;
Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait
m_trWaitAvg = trWaitAvg;
// Predict when it will actually be drawn and update frame stats
if (Result==S_FALSE) { // We are going to wait
trFrame = TimeDiff(*ptrStart-m_trLastDraw);
m_trLastDraw = *ptrStart;
} else {
// trFrame is already = trRealStream-m_trLastDraw;
m_trLastDraw = trRealStream;
}
#ifndef PERF
int iAccuracy;
if (Delay>0) {
// Report lateness based on when we intend to play it
iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf);
} else {
// Report lateness based on playing it *now*.
iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf;
}
PreparePerformanceData(iAccuracy, trFrame);
#endif
}
return Result;
}
// We are going to drop this frame!
// Of course in DirectDraw mode the guy upstream may draw it anyway.
// This will probably give a large negative wack to the wait avg.
m_trWaitAvg = trWaitAvg;
#ifdef PERF
// Respect registry setting - debug only!
if (m_bDrawLateFrames) {
return S_OK; // draw it when it's ready
} // even though it's late.
#endif
// We are going to drop this frame so draw the next one early
// n.b. if the supplier is doing direct draw then he may draw it anyway
// but he's doing something funny to arrive here in that case.
MSR_INTEGER(m_idDecision, 2);
m_nNormal = -1;
return E_FAIL; // drop it
} // ShouldDrawSampleNow
// NOTE we're called by both the window thread and the source filter thread
// so we have to be protected by a critical section (locked before called)
// Also, when the window thread gets signalled to render an image, it always
// does so regardless of how late it is. All the degradation is done when we
// are scheduling the next sample to be drawn. Hence when we start an advise
// link to draw a sample, that sample's time will always become the last one
// drawn - unless of course we stop streaming in which case we cancel links
void CVideoRendererQuality::FrameDropped (IMediaSample *pMediaSample)
{
++m_cFramesDropped;
PERFLOG_TRACE_OBJECT(PERFLOG_EV_RENDERER_OBJECT_DROPPED, PERFLOG_STREAM_VIDEO, -1, pMediaSample, -1, -1, -1, -1, -1);
// m_cFramesDrawn must NOT be updated here. It has to be updated
// in RecordFrameLateness at the same time as the other statistics.
}
// Implementation of IQualProp interface needed to support the property page
// This is how the property page gets the data out of the scheduler. We are
// passed into the constructor the owning object in the COM sense, this will
// either be the video renderer or an external IUnknown if we're aggregated.
// We initialise our CUnknown base class with this interface pointer. Then
// all we have to do is to override NonDelegatingQueryInterface to expose
// our IQualProp interface. The AddRef and Release are handled automatically
// by the base class and will be passed on to the appropriate outer object
STDMETHODIMP CVideoRendererQuality::get_FramesDroppedInRenderer(int *pcFramesDropped)
{
CheckPointer(pcFramesDropped,E_POINTER);
*pcFramesDropped = m_cFramesDropped;
return NOERROR;
} // get_FramesDroppedInRenderer
// Set *pcFramesDrawn to the number of frames drawn since
// streaming started.
STDMETHODIMP CVideoRendererQuality::get_FramesDrawn( int *pcFramesDrawn)
{
CheckPointer(pcFramesDrawn,E_POINTER);
*pcFramesDrawn = m_cFramesDrawn;
return NOERROR;
} // get_FramesDrawn
// Set iAvgFrameRate to the frames per hundred secs since
// streaming started. 0 otherwise.
STDMETHODIMP CVideoRendererQuality::get_AvgFrameRate( int *piAvgFrameRate, BOOL bStreaming)
{
CheckPointer(piAvgFrameRate,E_POINTER);
int t;
if (bStreaming) {
t = timeGetTime()-m_tStreamingStart;
} else {
t = m_tStreamingStart;
}
if (t<=0) {
*piAvgFrameRate = 0;
ASSERT(m_cFramesDrawn == 0);
} else if (m_cFramesDrawn < 2) {
// until we have drawn a few frames the computation
// of the average frame rate can return wild values
*piAvgFrameRate = 0;
} else {
// i is frames per hundred seconds
*piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
}
return NOERROR;
} // get_AvgFrameRate
// Set *piAvg to the average sync offset since streaming started
// in mSec. The sync offset is the time in mSec between when the frame
// should have been drawn and when the frame was actually drawn.
STDMETHODIMP CVideoRendererQuality::get_AvgSyncOffset( int *piAvg)
{
CheckPointer(piAvg,E_POINTER);
// Note that we didn't gather the stats on the first frame
// so we use m_cFramesDrawn-1 here
if (m_cFramesDrawn<=1) {
*piAvg = 0;
} else {
*piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1));
}
return NOERROR;
} // get_AvgSyncOffset
// To avoid dragging in the maths library - a cheap
// approximate integer square root.
// We do this by getting a starting guess which is between 1
// and 2 times too large, followed by THREE iterations of
// Newton Raphson. (That will give accuracy to the nearest mSec
// for the range in question - roughly 0..1000)
//
// It would be faster to use a linear interpolation and ONE NR, but
// who cares. If anyone does - the best linear interpolation is
// to approximates sqrt(x) by
// y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
// 0r y = x*0.41421 + 0.59467
// This minimises the maximal error in the range in question.
// (error is about +0.008883 and then one NR will give error .0000something
// (Of course these are integers, so you can't just multiply by 0.41421
// you'd have to do some sort of MulDiv).
// Anyone wanna check my maths? (This is only for a property display!)
int isqrt(int x)
{
int s = 1;
// Make s an initial guess for sqrt(x)
if (x > 0x40000000) {
s = 0x8000; // prevent any conceivable closed loop
} else {
while (s*s<x) { // loop cannot possible go more than 31 times
s = 2*s; // normally it goes about 6 times
}
// Three NR iterations.
if (x==0) {
s= 0; // Wouldn't it be tragic to divide by zero whenever our
// accuracy was perfect!
} else {
s = (s*s+x)/(2*s);
if (s>=0) s = (s*s+x)/(2*s);
if (s>=0) s = (s*s+x)/(2*s);
}
}
return s;
}
//
// Do estimates for standard deviations for per-frame
// statistics
//
HRESULT CVideoRendererQuality::GetStdDev(
int nSamples,
int *piResult,
LONGLONG llSumSq,
LONGLONG iTot
)
{
CheckPointer(piResult,E_POINTER);
// If S is the Sum of the Squares of observations and
// T the Total (i.e. sum) of the observations and there were
// N observations, then an estimate of the standard deviation is
// sqrt( (S - T**2/N) / (N-1) )
if (nSamples<=1) {
*piResult = 0;
} else {
LONGLONG x;
// First frames have bogus stamps, so we get no stats for them
// So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
// so we use m_cFramesDrawn-1 here
x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0);
x = x / (nSamples-1);
ASSERT(x>=0);
*piResult = isqrt((LONG)x);
}
return NOERROR;
}
// Set *piDev to the standard deviation in mSec of the sync offset
// of each frame since streaming started.
STDMETHODIMP CVideoRendererQuality::get_DevSyncOffset( int *piDev)
{
// First frames have bogus stamps, so we get no stats for them
// So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
return GetStdDev(m_cFramesDrawn - 1,
piDev,
m_iSumSqAcc,
m_iTotAcc);
} // get_DevSyncOffset
// Set *piJitter to the standard deviation in mSec of the inter-frame time
// of frames since streaming started.
STDMETHODIMP CVideoRendererQuality::get_Jitter( int *piJitter)
{
// First frames have bogus stamps, so we get no stats for them
// So second frame gives bogus inter-frame time
// So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
return GetStdDev(m_cFramesDrawn - 2,
piJitter,
m_iSumSqFrameTime,
m_iSumFrameTime);
} // get_Jitter
// Overidden to return our IQualProp interface
STDMETHODIMP
CVideoRendererQuality::NonDelegatingQueryInterface(REFIID riid,VOID **ppv)
{
// We return IQualProp and delegate everything else
if (riid == IID_IQualProp) {
return GetInterface( (IQualProp *)this, ppv);
} else {
return E_NOINTERFACE ;
}
}
// This removes a large number of level 4 warnings from the
// Microsoft compiler which in this case are not very useful
#pragma warning(disable: 4514)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -