📄 renqual.cpp
字号:
// This is heuristics, these numbers are aimed at being "what works"
// rather than anything based on some theory.
// We use a hyperbola because it's easy to calculate and it includes
// a panic button asymptote (which we push off just to the left)
// The throttling fits the following table (roughly)
// Proportion Throttle (msec)
// >=1000 0
// 900 3
// 800 7
// 700 11
// 600 17
// 500 25
// 400 35
// 300 50
// 200 72
// 125 100
// 100 112
// 50 146
// 0 200
// (some evidence that we could go for a sharper kink - e.g. no throttling
// until below the 750 mark - might give fractionally more frames on a
// P60-ish machine). The easy way to get these coefficients is to use
// Renbase.xls follow the instructions therein using excel solver.
if (q.Proportion>=1000) { m_trThrottle = 0; }
else {
// The DWORD is to make quite sure I get unsigned arithmetic
// as the constant is between 2**31 and 2**32
m_trThrottle = -330000 + (388880000/(q.Proportion+167));
}
return NOERROR;
} // NotifyQuality
// Theory:
// What a supplier wants to know is "is the frame I'm working on NOW
// going to be late?".
// F1 is the frame at the supplier (as above)
// Tf1 is the due time for F1
// T1 is the time at that point (NOW!)
// Tr1 is the time that f1 WILL actually be rendered
// L1 is the latency of the graph for frame F1 = Tr1-T1
// D1 (for delay) is how late F1 will be beyond its due time i.e.
// D1 = (Tr1-Tf1) which is what the supplier really wants to know.
// Unfortunately Tr1 is in the future and is unknown, so is L1
//
// We could estimate L1 by its value for a previous frame,
// L0 = Tr0-T0 and work off
// D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
// Rearranging terms:
// D1' = (T1-T0) + (Tr0-Tf1)
// adding (Tf0-Tf0) and rearranging again:
// = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
// = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
// But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
// Late field in the quality message that we send.
// The other two terms just state what correction should be applied before
// using the lateness of F0 to predict the lateness of F1.
// (T1-T0) says how much time has actually passed (we have lost this much)
// (Tf1-Tf0) says how much time should have passed if we were keeping pace
// (we have gained this much).
//
// Suppliers should therefore work off:
// Quality.Late + (T1-T0) - (Tf1-Tf0)
// and see if this is "acceptably late" or even early (i.e. negative).
// They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
// the time stamps in the frames. They get Quality.Late from us.
//
HRESULT CVideoRendererQuality::GetQuality(REFERENCE_TIME trLate,
REFERENCE_TIME trRealStream,
Quality *pQ)
{
Quality q;
// If we are the main user of time, then report this as Flood/Dry.
// If our suppliers are, then report it as Famine/Glut.
//
// We need to take action, but avoid hunting. Hunting is caused by
// 1. Taking too much action too soon and overshooting
// 2. Taking too long to react (so averaging can CAUSE hunting).
//
// The reason why we use trLate as well as Wait is to reduce hunting;
// if the wait time is coming down and about to go into the red, we do
// NOT want to rely on some average which is only telling is that it used
// to be OK once.
q.TimeStamp = (REFERENCE_TIME)trRealStream;
if (m_trFrameAvg<0) {
q.Type = Famine; // guess
}
// Is the greater part of the time taken bltting or something else
else if (m_trFrameAvg > 2*m_trRenderAvg) {
q.Type = Famine; // mainly other
} else {
q.Type = Flood; // mainly bltting
}
q.Proportion = 1000; // default
if (m_trFrameAvg<0) {
// leave it alone - we don't know enough
}
else if ( trLate> 0 ) {
// try to catch up over the next second
// We could be Really, REALLY late, but rendering all the frames
// anyway, just because it's so cheap.
q.Proportion = 1000 - (int)((trLate)/(UNITS/1000));
if (q.Proportion<500) {
q.Proportion = 500; // don't go daft. (could've been negative!)
} else {
}
} else if ( m_trWaitAvg>20000
&& trLate<-20000
){
// Go cautiously faster - aim at 2mSec wait.
if (m_trWaitAvg>=m_trFrameAvg) {
// This can happen because of some fudges.
// The waitAvg is how long we originally planned to wait
// The frameAvg is more honest.
// It means that we are spending a LOT of time waiting
q.Proportion = 2000; // double.
} else {
if (m_trFrameAvg+20000 > m_trWaitAvg) {
q.Proportion
= 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
} else {
// We're apparently spending more than the whole frame time waiting.
// Assume that the averages are slightly out of kilter, but that we
// are indeed doing a lot of waiting. (This leg probably never
// happens, but the code avoids any potential divide by zero).
q.Proportion = 2000;
}
}
if (q.Proportion>2000) {
q.Proportion = 2000; // don't go crazy.
}
}
// Tell the supplier how late frames are when they get rendered
// That's how late we are now.
// If we are in directdraw mode then the guy upstream can see the drawing
// times and we'll just report on the start time. He can figure out any
// offset to apply. If we are in DIB Section mode then we will apply an
// extra offset which is half of our drawing time. This is usually small
// but can sometimes be the dominant effect. For this we will use the
// average drawing time rather than the last frame. If the last frame took
// a long time to draw and made us late, that's already in the lateness
// figure. We should not add it in again unless we expect the next frame
// to be the same. We don't, we expect the average to be a better shot.
// In direct draw mode the RenderAvg will be zero.
q.Late = trLate + m_trRenderAvg/2;
// log what we're doing
MSR_INTEGER(m_idQualityRate, q.Proportion);
MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );
*pQ = q;
return S_OK;
} // GetQuality
HRESULT CVideoRendererQuality::ComputeLateness(REFERENCE_TIME trStart,
REFERENCE_TIME trRealStream,
REFERENCE_TIME *ptrLate)
{
ASSERT(ptrLate);
if (!ptrLate)
return E_INVALIDARG;
// We lose a bit of time depending on the monitor type waiting for the next
// screen refresh. On average this might be about 8mSec - so it will be
// later than we think when the picture appears. To compensate a bit
// we bias the media samples by -8mSec i.e. 80000 UNITs.
// We don't ever make a stream time negative (call it paranoia)
if (trStart>=80000) {
trStart -= 80000;
}
// We have to wory about two versions of "lateness". The truth, which we
// try to work out here and the one measured against m_trTarget which
// includes long term feedback. We report statistics against the truth
// but for operational decisions we work to the target.
// We use TimeDiff to make sure we get an integer because we
// may actually be late (or more likely early if there is a big time
// gap) by a very long time.
*ptrLate = TimeDiff(trRealStream - trStart);
return S_OK;
}
// We are called with a valid IMediaSample image to decide whether this is to
// be drawn or not. There must be a reference clock in operation.
// Return S_OK if it is to be drawn Now (as soon as possible)
// Return S_FALSE if it is to be drawn when it's due
// Return an error if we want to drop it
// m_nNormal=-1 indicates that we dropped the previous frame and so this
// one should be drawn early. Respect it and update it.
// Use current stream time plus a number of heuristics (detailed below)
// to make the decision
HRESULT CVideoRendererQuality::ShouldDrawSampleNow(IMediaSample *pMediaSample,
REFERENCE_TIME *ptrStart,
REFERENCE_TIME *ptrEnd,
REFERENCE_TIME trRealStream,
BOOL bSupplierHandlingQuality)
{
ASSERT(pMediaSample && ptrStart && ptrEnd);
if (!pMediaSample || !ptrStart || !ptrEnd)
return E_INVALIDARG;
// Don't call us unless there's a clock interface to synchronise with
MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits
MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits
// We lose a bit of time depending on the monitor type waiting for the next
// screen refresh. On average this might be about 8mSec - so it will be
// later than we think when the picture appears. To compensate a bit
// we bias the media samples by -8mSec i.e. 80000 UNITs.
// We don't ever make a stream time negative (call it paranoia)
if (*ptrStart>=80000) {
*ptrStart -= 80000;
*ptrEnd -= 80000; // bias stop to to retain valid frame duration
}
// Cache the time stamp now. We will want to compare what we did with what
// we started with (after making the monitor allowance).
m_trRememberStampForPerf = *ptrStart;
// Get reference times (current and late)
#ifdef PERF
// While the reference clock is expensive:
// Remember the offset from timeGetTime and use that.
// This overflows all over the place, but when we subtract to get
// differences the overflows all cancel out.
m_llTimeOffset = trRealStream-timeGetTime()*10000;
#endif
// We have to wory about two versions of "lateness". The truth, which we
// try to work out here and the one measured against m_trTarget which
// includes long term feedback. We report statistics against the truth
// but for operational decisions we work to the target.
// We use TimeDiff to make sure we get an integer because we
// may actually be late (or more likely early if there is a big time
// gap) by a very long time.
const int trTrueLate = TimeDiff(trRealStream - *ptrStart);
const int trLate = trTrueLate;
MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);
// Note: the filter upstream is allowed to this FAIL meaning "you do it".
m_bSupplierHandlingQuality = bSupplierHandlingQuality;
// Decision time! Do we drop, draw when ready or draw immediately?
const int trDuration = (int)(*ptrEnd - *ptrStart);
{
// We need to see if the frame rate of the file has just changed.
// This would make comparing our previous frame rate with the current
// frame rate silly. Hang on a moment though. I've seen files
// where the frames vary between 33 and 34 mSec so as to average
// 30fps. A minor variation like that won't hurt us.
int t = m_trDuration/32;
if ( trDuration > m_trDuration+t
|| trDuration < m_trDuration-t
) {
// There's a major variation. Reset the average frame rate to
// exactly the current rate to disable decision 9002 for this frame,
// and remember the new rate.
m_trFrameAvg = trDuration;
m_trDuration = trDuration;
}
}
MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
MSR_INTEGER(m_idDuration, trDuration/10000);
#ifdef PERF
if (S_OK==pMediaSample->IsDiscontinuity()) {
MSR_INTEGER(m_idDecision, 9000);
}
#endif
// Control the graceful slide back from slow to fast machine mode.
// After a frame drop accept an early frame and set the earliness to here
// If this frame is already later than the earliness then slide it to here
// otherwise do the standard slide (reduce by about 12% per frame).
// Note: earliness is normally NEGATIVE
BOOL bJustDroppedFrame
= ( m_bSupplierHandlingQuality
// Can't use the pin sample properties because we might
// not be in Receive when we call this
&& (S_OK == pMediaSample->IsDiscontinuity()) // he just dropped one
)
|| (m_nNormal==-1); // we just dropped one
// Set m_trEarliness (slide back from slow to fast machine mode)
if (trLate>0) {
m_trEarliness = 0; // we are no longer in fast machine mode at all!
} else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) {
m_trEarliness = trLate; // Things have slipped of their own accord
} else {
m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide
}
// prepare the new wait average - but don't pollute the old one until
// we have finished with it.
int trWaitAvg;
{
// We never mix in a negative wait. This causes us to believe in fast machines
// slightly more.
int trL = trLate<0 ? -trLate : 0;
trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
}
int trFrame;
{
REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
if (tr>10000000) {
tr = 10000000; // 1 second - arbitrarily.
}
trFrame = int(tr);
}
// We will DRAW this frame IF...
if (
// ...the time we are spending drawing is a small fraction of the total
// observed inter-frame time so that dropping it won't help much.
// 1/10th is a smaller fraction than 1/3rd, which was here before
(10*m_trRenderAvg <= m_trFrameAvg)
// ...or our supplier is NOT handling things and the next frame would
// be less timely than this one or our supplier CLAIMS to be handling
// things, and is now less than a full FOUR frames late.
|| ( m_bSupplierHandlingQuality
? (trLate <= trDuration*4)
: (trLate+trLate < trDuration)
)
// ...or we are on average waiting for over eight milliseconds then
// this may be just a glitch. Draw it and we'll hope to catch up.
|| (m_trWaitAvg > 80000)
// ...or we haven't drawn an image for over a second. We will update
// the display, which stops the video looking hung.
// Do this regardless of how late this media sample is.
|| ((trRealStream - m_trLastDraw) > UNITS)
) {
HRESULT Result;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -