📄 renqual.cpp
字号:
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Use of this source code is subject to the terms of your Microsoft Windows CE
// Source Alliance Program license form. If you did not accept the terms of
// such a license, you are not authorized to use this source code.
//
//==========================================================================;
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
//
//--------------------------------------------------------------------------;
#include <streams.h> // ActiveMovie base class definitions
#include <mmsystem.h> // Needed for definition of timeGetTime
#include <limits.h> // Standard data type limit definitions
#include <measure.h> // Used for time critical log functions
#pragma warning(disable:4355)
// Helper function for clamping time differences
int inline TimeDiff(REFERENCE_TIME rt)
{
if (rt < - (50 * UNITS)) {
return -(50 * UNITS);
} else
if (rt > 50 * UNITS) {
return 50 * UNITS;
} else return (int)rt;
}
// We do not keep an event object to use when setting up a timer link with
// the clock but are given a pointer to one by the owning object through the
// SetNotificationObject method - this must be initialised before starting
// We can override the default quality management process to have it always
// draw late frames, this is currently done by having the following registry
// key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");
CVideoRendererQuality::CVideoRendererQuality() :
m_cFramesDropped(0),
m_cFramesDrawn(0),
m_bSupplierHandlingQuality(FALSE)
{
m_prevState = State_Stopped;
ResetStreamingTimes();
#ifdef PERF
m_idTimeStamp = MSR_REGISTER("Frame time stamp");
m_idEarliness = MSR_REGISTER("Earliness fudge");
m_idTarget = MSR_REGISTER("Target (mSec)");
m_idSchLateTime = MSR_REGISTER("mSec late when scheduled");
m_idDecision = MSR_REGISTER("Scheduler decision code");
m_idQualityRate = MSR_REGISTER("Quality rate sent");
m_idQualityTime = MSR_REGISTER("Quality time sent");
m_idWaitReal = MSR_REGISTER("Render wait");
// m_idWait = MSR_REGISTER("wait time recorded (msec)");
m_idFrameAccuracy = MSR_REGISTER("Frame accuracy (msecs)");
m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
//m_idSendQuality = MSR_REGISTER("Processing Quality message");
m_idRenderAvg = MSR_REGISTER("Render draw time Avg");
m_idFrameAvg = MSR_REGISTER("FrameAvg");
m_idWaitAvg = MSR_REGISTER("WaitAvg");
m_idDuration = MSR_REGISTER("Duration");
m_idThrottle = MSR_REGISTER("Audio-video throttle wait");
// m_idDebug = MSR_REGISTER("Debug stuff");
#endif // PERF
} // Constructor
// Destructor is just a placeholder
CVideoRendererQuality::~CVideoRendererQuality()
{
}
// The timing functions in this class are called by the window object and by
// the renderer's allocator.
// The windows object calls timing functions as it receives media sample
// images for drawing using GDI.
// The allocator calls timing functions when it starts passing DCI/DirectDraw
// surfaces which are not rendered in the same way; The decompressor writes
// directly to the surface with no separate rendering, so those code paths
// call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
// when we have allocated one and only one image we know there cannot be any
// conflict between the two.
//
// We use timeGetTime to return the timing counts we use (since it's relative
// performance we are interested in rather than absolute compared to a clock)
// The window object sets the accuracy of the system clock (normally 1ms) by
// calling timeBeginPeriod/timeEndPeriod when it changes streaming states
// Reset all times controlling streaming.
// Set them so that
// 1. Frames will not initially be dropped
// 2. The first frame will definitely be drawn (achieved by saying that there
// has not ben a frame drawn for a long time).
HRESULT CVideoRendererQuality::ResetStreamingTimes()
{
m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago
m_tStreamingStart = timeGetTime();
m_trRenderAvg = 0;
m_trFrameAvg = -1; // -1000 fps == "unset"
m_trDuration = 0; // 0 - silly value
m_trRenderLast = 0;
m_trWaitAvg = 0;
m_tRenderStart = 0;
m_iTotAcc = 0;
m_iSumSqAcc = 0;
m_iSumSqFrameTime = 0;
m_trFrame = 0; // hygeine - not really needed
m_trLate = 0; // hygeine - not really needed
m_iSumFrameTime = 0;
m_nNormal = 0;
m_trEarliness = 0;
m_trTarget = -300000; // 30mSec early
m_trThrottle = 0;
m_trRememberStampForPerf = 0;
//
// Just reset the frames drawn/dropped count if the previous state when we stopped streaming
// was stopped. This prevents resetting them when we go to Run from Paused state.
//
if (m_prevState == State_Stopped)
{
m_cFramesDrawn = 0;
m_cFramesDropped = 0;
}
#ifdef PERF
m_trRememberFrameForPerf = 0;
#endif
return NOERROR;
} // ResetStreamingTimes
// Reset all times controlling streaming. Note that we're now streaming. We
// don't need to set the rendering event to have the source filter released
// as it is done during the Run processing. When we are run we immediately
// release the source filter thread and draw any image waiting (that image
// may already have been drawn once as a poster frame while we were paused)
HRESULT CVideoRendererQuality::OnStartStreamingQual(FILTER_STATE curState)
{
ResetStreamingTimes();
return NOERROR;
} // OnStartStreaming
// Called at end of streaming. Fixes times for property page report
HRESULT CVideoRendererQuality::OnStopStreamingQual(FILTER_STATE curState)
{
m_tStreamingStart = timeGetTime()-m_tStreamingStart;
m_prevState = curState; // save the state when we were issued a stop streaming (may be either Paused/Stopped)
return NOERROR;
} // OnStopStreaming
// Called when we start waiting for a rendering event.
// Used to update times spent waiting and not waiting.
void CVideoRendererQuality::OnWaitStartQual()
{
MSR_START(m_idWaitReal);
} // OnWaitStart
// Called when we are awoken from the wait in the window OR by our allocator
// when it is hanging around until the next sample is due for rendering on a
// DCI/DirectDraw surface. We add the wait time into our rolling average.
// We grab the interface lock so that we're serialised with the application
// thread going through the run code - which in due course ends up calling
// ResetStreaming times - possibly as we run through this section of code
void CVideoRendererQuality::OnWaitEndQual()
{
#ifdef PERF
MSR_STOP(m_idWaitReal);
// for a perf build we want to know just exactly how late we REALLY are.
// even if this means that we have to look at the clock again.
REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
// We will be discarding overflows like mad here!
// This is wrong really because timeGetTime() can wrap but it's
// only for PERF
REFERENCE_TIME tr = timeGetTime()*10000;
trRealStream = tr + m_llTimeOffset;
trRealStream -= m_tStart; // convert to stream time (this is a reftime)
if (m_trRememberStampForPerf==0) {
// This is probably the poster frame at the start, and it is not scheduled
// in the usual way at all. Just count it. The rememberstamp gets set
// in ShouldDrawSampleNow, so this does bogus frame recording until we
// actually start playing.
PreparePerformanceData(0, 0);
} else {
int trLate = (int)(trRealStream - m_trRememberStampForPerf);
int trFrame = (int)(tr - m_trRememberFrameForPerf);
PreparePerformanceData(trLate, trFrame);
}
m_trRememberFrameForPerf = tr;
#endif //PERF
} // OnWaitEnd
// Put data on one side that describes the lateness of the current frame.
// We don't yet know whether it will actually be drawn. In direct draw mode,
// this decision is up to the filter upstream, and it could change its mind.
// The rules say that if it did draw it must call Receive(). One way or
// another we eventually get into either OnRenderStart or OnDirectRender and
// these both call RecordFrameLateness to update the statistics.
void CVideoRendererQuality::PreparePerformanceData(int trLate, int trFrame)
{
m_trLate = trLate;
m_trFrame = trFrame;
} // PreparePerformanceData
// update the statistics:
// m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
// Note that because the properties page reports using these variables,
// 1. We need to be inside a critical section
// 2. They must all be updated together. Updating the sums here and the count
// elsewhere can result in imaginary jitter (i.e. attempts to find square roots
// of negative numbers) in the property page code.
void CVideoRendererQuality::RecordFrameLateness(int trLate, int trFrame)
{
// Record how timely we are.
int tLate = trLate/10000;
// Best estimate of moment of appearing on the screen is average of
// start and end draw times. Here we have only the end time. This may
// tend to show us as spuriously late by up to 1/2 frame rate achieved.
// Decoder probably monitors draw time. We don't bother.
MSR_INTEGER( m_idFrameAccuracy, tLate );
// This is a hack - we can get frames that are ridiculously late
// especially (at start-up) and they sod up the statistics.
// So ignore things that are more than 1 sec off.
if (tLate>1000 || tLate<-1000) {
if (m_cFramesDrawn<=1) {
tLate = 0;
} else if (tLate>0) {
tLate = 1000;
} else {
tLate = -1000;
}
}
// The very first frame often has a bogus time, so I'm just
// not going to count it into the statistics. ???
if (m_cFramesDrawn>1) {
m_iTotAcc += tLate;
m_iSumSqAcc += (tLate*tLate);
}
// calculate inter-frame time. Doesn't make sense for first frame
// second frame suffers from bogus first frame stamp.
if (m_cFramesDrawn>2) {
int tFrame = trFrame/10000; // convert to mSec else it overflows
// This is a hack. It can overflow anyway (a pause can cause
// a very long inter-frame time) and it overflows at 2**31/10**7
// or about 215 seconds i.e. 3min 35sec
if (tFrame>1000||tFrame<0) tFrame = 1000;
m_iSumSqFrameTime += tFrame*tFrame;
ASSERT(m_iSumSqFrameTime>=0);
m_iSumFrameTime += tFrame;
}
++m_cFramesDrawn;
} // RecordFrameLateness
void CVideoRendererQuality::ThrottleWait()
{
if (m_trThrottle>0) {
int iThrottle = m_trThrottle/10000; // convert to mSec
MSR_INTEGER( m_idThrottle, iThrottle);
DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle));
Sleep(iThrottle);
}
} // ThrottleWait
// Whenever a frame is rendered it goes though either OnRenderStart
// or OnDirectRender. Data that are generated during ShouldDrawSample
// are added to the statistics by calling RecordFrameLateness from both
// these two places.
// Called in place of OnRenderStart..OnRenderEnd
// When a DirectDraw image is drawn
void CVideoRendererQuality::OnDirectRender(IMediaSample *pMediaSample)
{
int time = 0;
m_trRenderAvg = 0;
m_trRenderLast = 5000000; // If we mode switch, we do NOT want this
// to inhibit the new average getting going!
// so we set it to half a second
// MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
RecordFrameLateness(m_trLate, m_trFrame);
ThrottleWait();
} // OnDirectRender
// Called just before we start drawing. All we do is to get the current clock
// time (from the system) and return. We have to store the start render time
// in a member variable because it isn't used until we complete the drawing
// The rest is just performance logging.
void CVideoRendererQuality::OnRenderStartQual(IMediaSample *pMediaSample)
{
RecordFrameLateness(m_trLate, m_trFrame);
m_tRenderStart = timeGetTime();
} // OnRenderStart
// Called directly after drawing an image. We calculate the time spent in the
// drawing code and if this doesn't appear to have any odd looking spikes in
// it then we add it to the current average draw time. Measurement spikes may
// occur if the drawing thread is interrupted and switched to somewhere else.
void CVideoRendererQuality::OnRenderEndQual(IMediaSample *pMediaSample)
{
// The renderer time can vary erratically if we are interrupted so we do
// some smoothing to help get more sensible figures out but even that is
// not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS
if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
// DO_MOVING_AVG(m_trRenderAvg, tr);
m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
}
m_trRenderLast = tr;
ThrottleWait();
} // OnRenderEnd
STDMETHODIMP CVideoRendererQuality::NotifyQuality(Quality q)
{
// NOTE: We are NOT getting any locks here. We could be called
// asynchronously and possibly even on a time critical thread of
// someone else's - so we do the minumum. We only set one state
// variable (an integer) and if that happens to be in the middle
// of another thread reading it they will just get either the new
// or the old value. Locking would achieve no more than this.
// It might be nice to check that we are being called from m_pGraph, but
// it turns out to be a millisecond or so per throw!
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -