//------------------------------------------------------------------------------ // File: RenBase.h // // Desc: DirectShow base classes - defines a generic ActiveX base renderer // class. // // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ #ifndef __RENBASE__ #define __RENBASE__ // Forward class declarations class CBaseRenderer; class CBaseVideoRenderer; class CRendererInputPin; // This is our input pin class that channels calls to the renderer class CRendererInputPin : public CBaseInputPin { protected: CBaseRenderer *m_pRenderer; public: CRendererInputPin(__inout CBaseRenderer *pRenderer, __inout HRESULT *phr, __in_opt LPCWSTR Name); // Overriden from the base pin classes HRESULT BreakConnect(); HRESULT CompleteConnect(IPin *pReceivePin); HRESULT SetMediaType(const CMediaType *pmt); HRESULT CheckMediaType(const CMediaType *pmt); HRESULT Active(); HRESULT Inactive(); // Add rendering behaviour to interface functions STDMETHODIMP QueryId(__deref_out LPWSTR *Id); STDMETHODIMP EndOfStream(); STDMETHODIMP BeginFlush(); STDMETHODIMP EndFlush(); STDMETHODIMP Receive(IMediaSample *pMediaSample); // Helper IMemAllocator inline *Allocator() const { return m_pAllocator; } }; // Main renderer class that handles synchronisation and state changes class CBaseRenderer : public CBaseFilter { protected: friend class CRendererInputPin; friend void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier UINT uMsg, // Not currently used DWORD_PTR dwUser, // User information DWORD_PTR dw1, // Windows reserved DWORD_PTR dw2); // Is also reserved CRendererPosPassThru *m_pPosition; // Media seeking pass by object CAMEvent m_RenderEvent; // Used to signal timer events CAMEvent m_ThreadSignal; // Signalled to release worker thread CAMEvent m_evComplete; // Signalled when state complete BOOL m_bAbort; // Stop us from rendering more data BOOL m_bStreaming; // Are we currently streaming DWORD_PTR m_dwAdvise; // Timer advise cookie IMediaSample *m_pMediaSample; // Current image media sample BOOL m_bEOS; // Any more samples in the stream BOOL m_bEOSDelivered; // Have we delivered an EC_COMPLETE CRendererInputPin *m_pInputPin; // Our renderer input pin object CCritSec m_InterfaceLock; // Critical section for interfaces CCritSec m_RendererLock; // Controls access to internals IQualityControl * m_pQSink; // QualityControl sink BOOL m_bRepaintStatus; // Can we signal an EC_REPAINT // Avoid some deadlocks by tracking filter during stop volatile BOOL m_bInReceive; // Inside Receive between PrepareReceive // And actually processing the sample REFERENCE_TIME m_SignalTime; // Time when we signal EC_COMPLETE UINT m_EndOfStreamTimer; // Used to signal end of stream CCritSec m_ObjectCreationLock; // This lock protects the creation and // of m_pPosition and m_pInputPin. It // ensures that two threads cannot create // either object simultaneously. public: CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer __in_opt LPCTSTR pName, // Debug ONLY description __inout_opt LPUNKNOWN pUnk, // Aggregated owner object __inout HRESULT *phr); // General OLE return code ~CBaseRenderer(); // Overriden to say what interfaces we support and where virtual HRESULT GetMediaPositionInterface(REFIID riid, __deref_out void **ppv); STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **); virtual HRESULT SourceThreadCanWait(BOOL bCanWait); #ifdef DEBUG // Debug only dump of the renderer state void DisplayRendererState(); #endif virtual HRESULT WaitForRenderTime(); virtual HRESULT CompleteStateChange(FILTER_STATE OldState); // Return internal information about this filter BOOL IsEndOfStream() { return m_bEOS; }; BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; }; BOOL IsStreaming() { return m_bStreaming; }; void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; }; virtual void OnReceiveFirstSample(IMediaSample *pMediaSample) { }; CAMEvent *GetRenderEvent() { return &m_RenderEvent; }; // Permit access to the transition state void Ready() { m_evComplete.Set(); }; void NotReady() { m_evComplete.Reset(); }; BOOL CheckReady() { return m_evComplete.Check(); }; virtual int GetPinCount(); virtual CBasePin *GetPin(int n); FILTER_STATE GetRealState(); void SendRepaint(); void SendNotifyWindow(IPin *pPin,HWND hwnd); BOOL OnDisplayChange(); void SetRepaintStatus(BOOL bRepaint); // Override the filter and pin interface functions STDMETHODIMP Stop(); STDMETHODIMP Pause(); STDMETHODIMP Run(REFERENCE_TIME StartTime); STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); STDMETHODIMP FindPin(LPCWSTR Id, __deref_out IPin **ppPin); // These are available for a quality management implementation virtual void OnRenderStart(IMediaSample *pMediaSample); virtual void OnRenderEnd(IMediaSample *pMediaSample); virtual HRESULT OnStartStreaming() { return NOERROR; }; virtual HRESULT OnStopStreaming() { return NOERROR; }; virtual void OnWaitStart() { }; virtual void OnWaitEnd() { }; virtual void PrepareRender() { }; #ifdef PERF REFERENCE_TIME m_trRenderStart; // Just before we started drawing // Set in OnRenderStart, Used in OnRenderEnd int m_idBaseStamp; // MSR_id for frame time stamp int m_idBaseRenderTime; // MSR_id for true wait time int m_idBaseAccuracy; // MSR_id for time frame is late (int) #endif // Quality management implementation for scheduling rendering virtual BOOL ScheduleSample(IMediaSample *pMediaSample); virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample, __out REFERENCE_TIME *pStartTime, __out REFERENCE_TIME *pEndTime); virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, __out REFERENCE_TIME *ptrStart, __out REFERENCE_TIME *ptrEnd); // Lots of end of stream complexities void TimerCallback(); void ResetEndOfStreamTimer(); HRESULT NotifyEndOfStream(); virtual HRESULT SendEndOfStream(); virtual HRESULT ResetEndOfStream(); virtual HRESULT EndOfStream(); // Rendering is based around the clock void SignalTimerFired(); virtual HRESULT CancelNotification(); virtual HRESULT ClearPendingSample(); // Called when the filter changes state virtual HRESULT Active(); virtual HRESULT Inactive(); virtual HRESULT StartStreaming(); virtual HRESULT StopStreaming(); virtual HRESULT BeginFlush(); virtual HRESULT EndFlush(); // Deal with connections and type changes virtual HRESULT BreakConnect(); virtual HRESULT SetMediaType(const CMediaType *pmt); virtual HRESULT CompleteConnect(IPin *pReceivePin); // These look after the handling of data samples virtual HRESULT PrepareReceive(IMediaSample *pMediaSample); virtual HRESULT Receive(IMediaSample *pMediaSample); virtual BOOL HaveCurrentSample(); virtual IMediaSample *GetCurrentSample(); virtual HRESULT Render(IMediaSample *pMediaSample); // Derived classes MUST override these virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE; virtual HRESULT CheckMediaType(const CMediaType *) PURE; // Helper void WaitForReceiveToComplete(); }; // CBaseVideoRenderer is a renderer class (see its ancestor class) and // it handles scheduling of media samples so that they are drawn at the // correct time by the reference clock. It implements a degradation // strategy. Possible degradation modes are: // Drop frames here (only useful if the drawing takes significant time) // Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip. // Signal supplier to change the frame rate - i.e. ongoing skipping. // Or any combination of the above. // In order to determine what's useful to try we need to know what's going // on. This is done by timing various operations (including the supplier). // This timing is done by using timeGetTime as it is accurate enough and // usually cheaper than calling the reference clock. It also tells the // truth if there is an audio break and the reference clock stops. // We provide a number of public entry points (named OnXxxStart, OnXxxEnd) // which the rest of the renderer calls at significant moments. These do // the timing. // the number of frames that the sliding averages are averaged over. // the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD #define AVGPERIOD 4 #define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD) // Spot the bug in this macro - I can't. but it doesn't work! class CBaseVideoRenderer : public CBaseRenderer, // Base renderer class public IQualProp, // Property page guff public IQualityControl // Allow throttling { protected: // Hungarian: // tFoo is the time Foo in mSec (beware m_tStart from filter.h) // trBar is the time Bar by the reference clock //****************************************************************** // State variables to control synchronisation //****************************************************************** // Control of sending Quality messages. We need to know whether // we are in trouble (e.g. frames being dropped) and where the time // is being spent. // When we drop a frame we play the next one early. // The frame after that is likely to wait before drawing and counting this // wait as spare time is unfair, so we count it as a zero wait. // We therefore need to know whether we are playing frames early or not. int m_nNormal; // The number of consecutive frames // drawn at their normal time (not early) // -1 means we just dropped a frame. #ifdef PERF BOOL m_bDrawLateFrames; // Don't drop any frames (debug and I'm // not keen on people using it!) #endif BOOL m_bSupplierHandlingQuality;// The response to Quality messages says // our supplier is handling things. // We will allow things to go extra late // before dropping frames. We will play // very early after he has dropped one. // Control of scheduling, frame dropping etc. // We need to know where the time is being spent so as to tell whether // we should be taking action here, signalling supplier or what. // The variables are initialised to a mode of NOT dropping frames. // They will tell the truth after a few frames. // We typically record a start time for an event, later we get the time // again and subtract to get the elapsed time, and we average this over // a few frames. The average is used to tell what mode we are in. // Although these are reference times (64 bit) they are all DIFFERENCES // between times which are small. An int will go up to 214 secs before // overflow. Avoiding 64 bit multiplications and divisions seems // worth while. // Audio-video throttling. If the user has turned up audio quality // very high (in principle it could be any other stream, not just audio) // then we can receive cries for help via the graph manager. In this case // we put in a wait for some time after rendering each frame. int m_trThrottle; // The time taken to render (i.e. BitBlt) frames controls which component // needs to degrade. If the blt is expensive, the renderer degrades. // If the blt is cheap it's done anyway and the supplier degrades. int m_trRenderAvg; // Time frames are taking to blt int m_trRenderLast; // Time for last frame blt int m_tRenderStart; // Just before we started drawing (mSec) // derived from timeGetTime. // When frames are dropped we will play the next frame as early as we can. // If it was a false alarm and the machine is fast we slide gently back to // normal timing. To do this, we record the offset showing just how early // we really are. This will normally be negative meaning early or zero. int m_trEarliness; // Target provides slow long-term feedback to try to reduce the // average sync offset to zero. Whenever a frame is actually rendered // early we add a msec or two, whenever late we take off a few. // We add or take off 1/32 of the error time. // Eventually we should be hovering around zero. For a really bad case // where we were (say) 300mSec off, it might take 100 odd frames to // settle down. The rate of change of this is intended to be slower // than any other mechanism in Quartz, thereby avoiding hunting. int m_trTarget; // The proportion of time spent waiting for the right moment to blt // controls whether we bother to drop a frame or whether we reckon that // we're doing well enough that we can stand a one-frame glitch. int m_trWaitAvg; // Average of last few wait times // (actually we just average how early // we were). Negative here means LATE. // The average inter-frame time. // This is used to calculate the proportion of the time used by the // three operations (supplying us, waiting, rendering) int m_trFrameAvg; // Average inter-frame time int m_trDuration; // duration of last frame. #ifdef PERF // Performance logging identifiers int m_idTimeStamp; // MSR_id for frame time stamp int m_idEarliness; // MSR_id for earliness fudge int m_idTarget; // MSR_id for Target fudge int m_idWaitReal; // MSR_id for true wait time int m_idWait; // MSR_id for wait time recorded int m_idFrameAccuracy; // MSR_id for time frame is late (int) int m_idRenderAvg; // MSR_id for Render time recorded (int) int m_idSchLateTime; // MSR_id for lateness at scheduler int m_idQualityRate; // MSR_id for Quality rate requested int m_idQualityTime; // MSR_id for Quality time requested int m_idDecision; // MSR_id for decision code int m_idDuration; // MSR_id for duration of a frame int m_idThrottle; // MSR_id for audio-video throttling //int m_idDebug; // MSR_id for trace style debugging //int m_idSendQuality; // MSR_id for timing the notifications per se #endif // PERF REFERENCE_TIME m_trRememberStampForPerf; // original time stamp of frame // with no earliness fudges etc. #ifdef PERF REFERENCE_TIME m_trRememberFrameForPerf; // time when previous frame rendered // debug... int m_idFrameAvg; int m_idWaitAvg; #endif // PROPERTY PAGE // This has edit fields that show the user what's happening // These member variables hold these counts. int m_cFramesDropped; // cumulative frames dropped IN THE RENDERER int m_cFramesDrawn; // Frames since streaming started seen BY THE // RENDERER (some may be dropped upstream) // Next two support average sync offset and standard deviation of sync offset. LONGLONG m_iTotAcc; // Sum of accuracies in mSec LONGLONG m_iSumSqAcc; // Sum of squares of (accuracies in mSec) // Next two allow jitter calculation. Jitter is std deviation of frame time. REFERENCE_TIME m_trLastDraw; // Time of prev frame (for inter-frame times) LONGLONG m_iSumSqFrameTime; // Sum of squares of (inter-frame time in mSec) LONGLONG m_iSumFrameTime; // Sum of inter-frame times in mSec // To get performance statistics on frame rate, jitter etc, we need // to record the lateness and inter-frame time. What we actually need are the // data above (sum, sum of squares and number of entries for each) but the data // is generated just ahead of time and only later do we discover whether the // frame was actually drawn or not. So we have to hang on to the data int m_trLate; // hold onto frame lateness int m_trFrame; // hold onto inter-frame time int m_tStreamingStart; // if streaming then time streaming started // else time of last streaming session // used for property page statistics #ifdef PERF LONGLONG m_llTimeOffset; // timeGetTime()*10000+m_llTimeOffset==ref time #endif public: CBaseVideoRenderer(REFCLSID RenderClass, // CLSID for this renderer __in_opt LPCTSTR pName, // Debug ONLY description __inout_opt LPUNKNOWN pUnk, // Aggregated owner object __inout HRESULT *phr); // General OLE return code ~CBaseVideoRenderer(); // IQualityControl methods - Notify allows audio-video throttling STDMETHODIMP SetSink( IQualityControl * piqc); STDMETHODIMP Notify( IBaseFilter * pSelf, Quality q); // These provide a full video quality management implementation void OnRenderStart(IMediaSample *pMediaSample); void OnRenderEnd(IMediaSample *pMediaSample); void OnWaitStart(); void OnWaitEnd(); HRESULT OnStartStreaming(); HRESULT OnStopStreaming(); void ThrottleWait(); // Handle the statistics gathering for our quality management void PreparePerformanceData(int trLate, int trFrame); virtual void RecordFrameLateness(int trLate, int trFrame); virtual void OnDirectRender(IMediaSample *pMediaSample); virtual HRESULT ResetStreamingTimes(); BOOL ScheduleSample(IMediaSample *pMediaSample); HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, __inout REFERENCE_TIME *ptrStart, __inout REFERENCE_TIME *ptrEnd); virtual HRESULT SendQuality(REFERENCE_TIME trLate, REFERENCE_TIME trRealStream); STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName); // // Do estimates for standard deviations for per-frame // statistics // // *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) / // (m_cFramesDrawn - 2) // or 0 if m_cFramesDrawn <= 3 // HRESULT GetStdDev( int nSamples, __out int *piResult, LONGLONG llSumSq, LONGLONG iTot ); public: // IQualProp property page support STDMETHODIMP get_FramesDroppedInRenderer(__out int *cFramesDropped); STDMETHODIMP get_FramesDrawn(__out int *pcFramesDrawn); STDMETHODIMP get_AvgFrameRate(__out int *piAvgFrameRate); STDMETHODIMP get_Jitter(__out int *piJitter); STDMETHODIMP get_AvgSyncOffset(__out int *piAvg); STDMETHODIMP get_DevSyncOffset(__out int *piDev); // Implement an IUnknown interface and expose IQualProp DECLARE_IUNKNOWN STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv); }; #endif // __RENBASE__