forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			2859 lines
		
	
	
		
			97 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			2859 lines
		
	
	
		
			97 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //------------------------------------------------------------------------------
 | ||
|  | // File: RenBase.cpp
 | ||
|  | //
 | ||
|  | // Desc: DirectShow base classes.
 | ||
|  | //
 | ||
|  | // Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
 | ||
|  | //------------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #include <streams.h>        // DirectShow 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; | ||
|  | } | ||
|  | 
 | ||
|  | // Implements the CBaseRenderer class
 | ||
|  | 
 | ||
|  | CBaseRenderer::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
 | ||
|  | 
 | ||
|  |     CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass), | ||
|  |     m_evComplete(TRUE, phr), | ||
|  |     m_RenderEvent(FALSE, phr), | ||
|  |     m_bAbort(FALSE), | ||
|  |     m_pPosition(NULL), | ||
|  |     m_ThreadSignal(TRUE, phr), | ||
|  |     m_bStreaming(FALSE), | ||
|  |     m_bEOS(FALSE), | ||
|  |     m_bEOSDelivered(FALSE), | ||
|  |     m_pMediaSample(NULL), | ||
|  |     m_dwAdvise(0), | ||
|  |     m_pQSink(NULL), | ||
|  |     m_pInputPin(NULL), | ||
|  |     m_bRepaintStatus(TRUE), | ||
|  |     m_SignalTime(0), | ||
|  |     m_bInReceive(FALSE), | ||
|  |     m_EndOfStreamTimer(0) | ||
|  | { | ||
|  |     if (SUCCEEDED(*phr)) { | ||
|  |         Ready(); | ||
|  | #ifdef PERF
 | ||
|  |         m_idBaseStamp = MSR_REGISTER(TEXT("BaseRenderer: sample time stamp")); | ||
|  |         m_idBaseRenderTime = MSR_REGISTER(TEXT("BaseRenderer: draw time (msec)")); | ||
|  |         m_idBaseAccuracy = MSR_REGISTER(TEXT("BaseRenderer: Accuracy (msec)")); | ||
|  | #endif
 | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
 | ||
|  | // object. The object is created when somebody queries us. These are standard
 | ||
|  | // control interfaces for seeking and setting start/stop positions and rates.
 | ||
|  | // We will probably also have made an input pin based on CRendererInputPin
 | ||
|  | // that has to be deleted, it's created when an enumerator calls our GetPin
 | ||
|  | 
 | ||
|  | CBaseRenderer::~CBaseRenderer() | ||
|  | { | ||
|  |     ASSERT(m_bStreaming == FALSE); | ||
|  |     ASSERT(m_EndOfStreamTimer == 0); | ||
|  |     StopStreaming(); | ||
|  |     ClearPendingSample(); | ||
|  | 
 | ||
|  |     // Delete any IMediaPosition implementation
 | ||
|  | 
 | ||
|  |     if (m_pPosition) { | ||
|  |         delete m_pPosition; | ||
|  |         m_pPosition = NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Delete any input pin created
 | ||
|  | 
 | ||
|  |     if (m_pInputPin) { | ||
|  |         delete m_pInputPin; | ||
|  |         m_pInputPin = NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Release any Quality sink
 | ||
|  | 
 | ||
|  |     ASSERT(m_pQSink == NULL); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This returns the IMediaPosition and IMediaSeeking interfaces
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid, __deref_out void **ppv) | ||
|  | { | ||
|  |     CAutoLock cObjectCreationLock(&m_ObjectCreationLock); | ||
|  |     if (m_pPosition) { | ||
|  |         return m_pPosition->NonDelegatingQueryInterface(riid,ppv); | ||
|  |     } | ||
|  | 
 | ||
|  |     CBasePin *pPin = GetPin(0); | ||
|  |     if (NULL == pPin) { | ||
|  |         return E_OUTOFMEMORY; | ||
|  |     } | ||
|  | 
 | ||
|  |     HRESULT hr = NOERROR; | ||
|  | 
 | ||
|  |     // Create implementation of this dynamically since sometimes we may
 | ||
|  |     // never try and do a seek. The helper object implements a position
 | ||
|  |     // control interface (IMediaPosition) which in fact simply takes the
 | ||
|  |     // calls normally from the filter graph and passes them upstream
 | ||
|  | 
 | ||
|  |     m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"), | ||
|  |                                            CBaseFilter::GetOwner(), | ||
|  |                                            (HRESULT *) &hr, | ||
|  |                                            pPin); | ||
|  |     if (m_pPosition == NULL) { | ||
|  |         return E_OUTOFMEMORY; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (FAILED(hr)) { | ||
|  |         delete m_pPosition; | ||
|  |         m_pPosition = NULL; | ||
|  |         return E_NOINTERFACE; | ||
|  |     } | ||
|  |     return GetMediaPositionInterface(riid,ppv); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Overriden to say what interfaces we support and where
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) | ||
|  | { | ||
|  |     // Do we have this interface
 | ||
|  | 
 | ||
|  |     if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) { | ||
|  |         return GetMediaPositionInterface(riid,ppv); | ||
|  |     } else { | ||
|  |         return CBaseFilter::NonDelegatingQueryInterface(riid,ppv); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This is called whenever we change states, we have a manual reset event that
 | ||
|  | // is signalled whenever we don't won't the source filter thread to wait in us
 | ||
|  | // (such as in a stopped state) and likewise is not signalled whenever it can
 | ||
|  | // wait (during paused and running) this function sets or resets the thread
 | ||
|  | // event. The event is used to stop source filter threads waiting in Receive
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait) | ||
|  | { | ||
|  |     if (bCanWait == TRUE) { | ||
|  |         m_ThreadSignal.Reset(); | ||
|  |     } else { | ||
|  |         m_ThreadSignal.Set(); | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifdef DEBUG
 | ||
|  | // Dump the current renderer state to the debug terminal. The hardest part of
 | ||
|  | // the renderer is the window where we unlock everything to wait for a clock
 | ||
|  | // to signal it is time to draw or for the application to cancel everything
 | ||
|  | // by stopping the filter. If we get things wrong we can leave the thread in
 | ||
|  | // WaitForRenderTime with no way for it to ever get out and we will deadlock
 | ||
|  | 
 | ||
|  | void CBaseRenderer::DisplayRendererState() | ||
|  | { | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime"))); | ||
|  | 
 | ||
|  |     // No way should this be signalled at this point
 | ||
|  | 
 | ||
|  |     BOOL bSignalled = m_ThreadSignal.Check(); | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"),bSignalled)); | ||
|  | 
 | ||
|  |     // Now output the current renderer state variables
 | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"),m_State)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"),m_bAbort)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"),m_bStreaming)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"),m_dwAdvise)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"),m_pMediaSample)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"),m_bEOS)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"),m_bEOSDelivered)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"),m_bRepaintStatus)); | ||
|  | 
 | ||
|  | 
 | ||
|  |     // Output the delayed end of stream timer information
 | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"),m_EndOfStreamTimer)); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)m_SignalTime))); | ||
|  | 
 | ||
|  | 
 | ||
|  |     // Should never timeout during a flushing state
 | ||
|  | 
 | ||
|  |     BOOL bFlushing = m_pInputPin->IsFlushing(); | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"),bFlushing)); | ||
|  | 
 | ||
|  |     // Display the time we were told to start at
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time))); | ||
|  | 
 | ||
|  |     // Have we got a reference clock
 | ||
|  |     if (m_pClock == NULL) return; | ||
|  | 
 | ||
|  |     // Get the current time from the wall clock
 | ||
|  | 
 | ||
|  |     CRefTime CurrentTime,StartTime,EndTime; | ||
|  |     m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime); | ||
|  |     CRefTime Offset = CurrentTime - m_tStart; | ||
|  | 
 | ||
|  |     // Display the current time from the clock
 | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"),CDisp((LONGLONG)CurrentTime.m_time))); | ||
|  | 
 | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"),Offset.Millisecs())); | ||
|  | 
 | ||
|  | 
 | ||
|  |     // Do we have a sample ready to render
 | ||
|  |     if (m_pMediaSample == NULL) return; | ||
|  | 
 | ||
|  |     m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime); | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"), | ||
|  |            StartTime.Millisecs(),EndTime.Millisecs())); | ||
|  | 
 | ||
|  |     // Calculate how long it is until it is due for rendering
 | ||
|  |     CRefTime Wait = (m_tStart + StartTime) - CurrentTime; | ||
|  |     DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"),Wait.Millisecs())); | ||
|  | } | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Wait until the clock sets the timer event or we're otherwise signalled. We
 | ||
|  | // set an arbitrary timeout for this wait and if it fires then we display the
 | ||
|  | // current renderer state on the debugger. It will often fire if the filter's
 | ||
|  | // left paused in an application however it may also fire during stress tests
 | ||
|  | // if the synchronisation with application seeks and state changes is faulty
 | ||
|  | 
 | ||
|  | #define RENDER_TIMEOUT 10000
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::WaitForRenderTime() | ||
|  | { | ||
|  |     HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent }; | ||
|  |     DWORD Result = WAIT_TIMEOUT; | ||
|  | 
 | ||
|  |     // Wait for either the time to arrive or for us to be stopped
 | ||
|  | 
 | ||
|  |     OnWaitStart(); | ||
|  |     while (Result == WAIT_TIMEOUT) { | ||
|  |         Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT); | ||
|  | 
 | ||
|  | #ifdef DEBUG
 | ||
|  |         if (Result == WAIT_TIMEOUT) DisplayRendererState(); | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     } | ||
|  |     OnWaitEnd(); | ||
|  | 
 | ||
|  |     // We may have been awoken without the timer firing
 | ||
|  | 
 | ||
|  |     if (Result == WAIT_OBJECT_0) { | ||
|  |         return VFW_E_STATE_CHANGED; | ||
|  |     } | ||
|  | 
 | ||
|  |     SignalTimerFired(); | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Poll waiting for Receive to complete.  This really matters when
 | ||
|  | // Receive may set the palette and cause window messages
 | ||
|  | // The problem is that if we don't really wait for a renderer to
 | ||
|  | // stop processing we can deadlock waiting for a transform which
 | ||
|  | // is calling the renderer's Receive() method because the transform's
 | ||
|  | // Stop method doesn't know to process window messages to unblock
 | ||
|  | // the renderer's Receive processing
 | ||
|  | void CBaseRenderer::WaitForReceiveToComplete() | ||
|  | { | ||
|  |     for (;;) { | ||
|  |         if (!m_bInReceive) { | ||
|  |             break; | ||
|  |         } | ||
|  | 
 | ||
|  |         MSG msg; | ||
|  |         //  Receive all interthread snedmessages
 | ||
|  |         PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE); | ||
|  | 
 | ||
|  |         Sleep(1); | ||
|  |     } | ||
|  | 
 | ||
|  |     // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
 | ||
|  |     // above just cleared the changebit which will cause some messaging
 | ||
|  |     // calls to block (waitMessage, MsgWaitFor...) now.
 | ||
|  |     // Post a dummy message to set the QS_POSTMESSAGE bit again
 | ||
|  |     if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) { | ||
|  |         //  Send dummy message
 | ||
|  |         PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // A filter can have four discrete states, namely Stopped, Running, Paused,
 | ||
|  | // Intermediate. We are in an intermediate state if we are currently trying
 | ||
|  | // to pause but haven't yet got the first sample (or if we have been flushed
 | ||
|  | // in paused state and therefore still have to wait for a sample to arrive)
 | ||
|  | 
 | ||
|  | // This class contains an event called m_evComplete which is signalled when
 | ||
|  | // the current state is completed and is not signalled when we are waiting to
 | ||
|  | // complete the last state transition. As mentioned above the only time we
 | ||
|  | // use this at the moment is when we wait for a media sample in paused state
 | ||
|  | // If while we are waiting we receive an end of stream notification from the
 | ||
|  | // source filter then we know no data is imminent so we can reset the event
 | ||
|  | // This means that when we transition to paused the source filter must call
 | ||
|  | // end of stream on us or send us an image otherwise we'll hang indefinately
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Simple internal way of getting the real state
 | ||
|  | 
 | ||
|  | FILTER_STATE CBaseRenderer::GetRealState() { | ||
|  |     return m_State; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // The renderer doesn't complete the full transition to paused states until
 | ||
|  | // it has got one media sample to render. If you ask it for its state while
 | ||
|  | // it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State) | ||
|  | { | ||
|  |     CheckPointer(State,E_POINTER); | ||
|  | 
 | ||
|  |     if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) { | ||
|  |         *State = m_State; | ||
|  |         return VFW_S_STATE_INTERMEDIATE; | ||
|  |     } | ||
|  |     *State = m_State; | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // If we're pausing and we have no samples we don't complete the transition
 | ||
|  | // to State_Paused and we return S_FALSE. However if the m_bAbort flag has
 | ||
|  | // been set then all samples are rejected so there is no point waiting for
 | ||
|  | // one. If we do have a sample then return NOERROR. We will only ever return
 | ||
|  | // VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
 | ||
|  | // (calling GetState after either being stopped or Run will NOT return this)
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState) | ||
|  | { | ||
|  |     // Allow us to be paused when disconnected
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         Ready(); | ||
|  |         return S_OK; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Have we run off the end of stream
 | ||
|  | 
 | ||
|  |     if (IsEndOfStream() == TRUE) { | ||
|  |         Ready(); | ||
|  |         return S_OK; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Make sure we get fresh data after being stopped
 | ||
|  | 
 | ||
|  |     if (HaveCurrentSample() == TRUE) { | ||
|  |         if (OldState != State_Stopped) { | ||
|  |             Ready(); | ||
|  |             return S_OK; | ||
|  |         } | ||
|  |     } | ||
|  |     NotReady(); | ||
|  |     return S_FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When we stop the filter the things we do are:-
 | ||
|  | 
 | ||
|  | //      Decommit the allocator being used in the connection
 | ||
|  | //      Release the source filter if it's waiting in Receive
 | ||
|  | //      Cancel any advise link we set up with the clock
 | ||
|  | //      Any end of stream signalled is now obsolete so reset
 | ||
|  | //      Allow us to be stopped when we are not connected
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::Stop() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_InterfaceLock); | ||
|  | 
 | ||
|  |     // Make sure there really is a state change
 | ||
|  | 
 | ||
|  |     if (m_State == State_Stopped) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Is our input pin connected
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         NOTE("Input pin is not connected"); | ||
|  |         m_State = State_Stopped; | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     CBaseFilter::Stop(); | ||
|  | 
 | ||
|  |     // If we are going into a stopped state then we must decommit whatever
 | ||
|  |     // allocator we are using it so that any source filter waiting in the
 | ||
|  |     // GetBuffer can be released and unlock themselves for a state change
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->Allocator()) { | ||
|  |         m_pInputPin->Allocator()->Decommit(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Cancel any scheduled rendering
 | ||
|  | 
 | ||
|  |     SetRepaintStatus(TRUE); | ||
|  |     StopStreaming(); | ||
|  |     SourceThreadCanWait(FALSE); | ||
|  |     ResetEndOfStream(); | ||
|  |     CancelNotification(); | ||
|  | 
 | ||
|  |     // There should be no outstanding clock advise
 | ||
|  |     ASSERT(CancelNotification() == S_FALSE); | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  |     ASSERT(m_EndOfStreamTimer == 0); | ||
|  | 
 | ||
|  |     Ready(); | ||
|  |     WaitForReceiveToComplete(); | ||
|  |     m_bAbort = FALSE; | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When we pause the filter the things we do are:-
 | ||
|  | 
 | ||
|  | //      Commit the allocator being used in the connection
 | ||
|  | //      Allow a source filter thread to wait in Receive
 | ||
|  | //      Cancel any clock advise link (we may be running)
 | ||
|  | //      Possibly complete the state change if we have data
 | ||
|  | //      Allow us to be paused when we are not connected
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::Pause() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_InterfaceLock); | ||
|  |     FILTER_STATE OldState = m_State; | ||
|  |     ASSERT(m_pInputPin->IsFlushing() == FALSE); | ||
|  | 
 | ||
|  |     // Make sure there really is a state change
 | ||
|  | 
 | ||
|  |     if (m_State == State_Paused) { | ||
|  |         return CompleteStateChange(State_Paused); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Has our input pin been connected
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         NOTE("Input pin is not connected"); | ||
|  |         m_State = State_Paused; | ||
|  |         return CompleteStateChange(State_Paused); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Pause the base filter class
 | ||
|  | 
 | ||
|  |     HRESULT hr = CBaseFilter::Pause(); | ||
|  |     if (FAILED(hr)) { | ||
|  |         NOTE("Pause failed"); | ||
|  |         return hr; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Enable EC_REPAINT events again
 | ||
|  | 
 | ||
|  |     SetRepaintStatus(TRUE); | ||
|  |     StopStreaming(); | ||
|  |     SourceThreadCanWait(TRUE); | ||
|  |     CancelNotification(); | ||
|  |     ResetEndOfStreamTimer(); | ||
|  | 
 | ||
|  |     // If we are going into a paused state then we must commit whatever
 | ||
|  |     // allocator we are using it so that any source filter can call the
 | ||
|  |     // GetBuffer and expect to get a buffer without returning an error
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->Allocator()) { | ||
|  |         m_pInputPin->Allocator()->Commit(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // There should be no outstanding advise
 | ||
|  |     ASSERT(CancelNotification() == S_FALSE); | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  |     ASSERT(m_EndOfStreamTimer == 0); | ||
|  |     ASSERT(m_pInputPin->IsFlushing() == FALSE); | ||
|  | 
 | ||
|  |     // When we come out of a stopped state we must clear any image we were
 | ||
|  |     // holding onto for frame refreshing. Since renderers see state changes
 | ||
|  |     // first we can reset ourselves ready to accept the source thread data
 | ||
|  |     // Paused or running after being stopped causes the current position to
 | ||
|  |     // be reset so we're not interested in passing end of stream signals
 | ||
|  | 
 | ||
|  |     if (OldState == State_Stopped) { | ||
|  |         m_bAbort = FALSE; | ||
|  |         ClearPendingSample(); | ||
|  |     } | ||
|  |     return CompleteStateChange(OldState); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When we run the filter the things we do are:-
 | ||
|  | 
 | ||
|  | //      Commit the allocator being used in the connection
 | ||
|  | //      Allow a source filter thread to wait in Receive
 | ||
|  | //      Signal the render event just to get us going
 | ||
|  | //      Start the base class by calling StartStreaming
 | ||
|  | //      Allow us to be run when we are not connected
 | ||
|  | //      Signal EC_COMPLETE if we are not connected
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime) | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_InterfaceLock); | ||
|  |     FILTER_STATE OldState = m_State; | ||
|  | 
 | ||
|  |     // Make sure there really is a state change
 | ||
|  | 
 | ||
|  |     if (m_State == State_Running) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Send EC_COMPLETE if we're not connected
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this); | ||
|  |         m_State = State_Running; | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     Ready(); | ||
|  | 
 | ||
|  |     // Pause the base filter class
 | ||
|  | 
 | ||
|  |     HRESULT hr = CBaseFilter::Run(StartTime); | ||
|  |     if (FAILED(hr)) { | ||
|  |         NOTE("Run failed"); | ||
|  |         return hr; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Allow the source thread to wait
 | ||
|  |     ASSERT(m_pInputPin->IsFlushing() == FALSE); | ||
|  |     SourceThreadCanWait(TRUE); | ||
|  |     SetRepaintStatus(FALSE); | ||
|  | 
 | ||
|  |     // There should be no outstanding advise
 | ||
|  |     ASSERT(CancelNotification() == S_FALSE); | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  |     ASSERT(m_EndOfStreamTimer == 0); | ||
|  |     ASSERT(m_pInputPin->IsFlushing() == FALSE); | ||
|  | 
 | ||
|  |     // If we are going into a running state then we must commit whatever
 | ||
|  |     // allocator we are using it so that any source filter can call the
 | ||
|  |     // GetBuffer and expect to get a buffer without returning an error
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->Allocator()) { | ||
|  |         m_pInputPin->Allocator()->Commit(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // When we come out of a stopped state we must clear any image we were
 | ||
|  |     // holding onto for frame refreshing. Since renderers see state changes
 | ||
|  |     // first we can reset ourselves ready to accept the source thread data
 | ||
|  |     // Paused or running after being stopped causes the current position to
 | ||
|  |     // be reset so we're not interested in passing end of stream signals
 | ||
|  | 
 | ||
|  |     if (OldState == State_Stopped) { | ||
|  |         m_bAbort = FALSE; | ||
|  |         ClearPendingSample(); | ||
|  |     } | ||
|  |     return StartStreaming(); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Return the number of input pins we support
 | ||
|  | 
 | ||
|  | int CBaseRenderer::GetPinCount() | ||
|  | { | ||
|  |     if (m_pInputPin == NULL) { | ||
|  |         //  Try to create it
 | ||
|  |         (void)GetPin(0); | ||
|  |     } | ||
|  |     return m_pInputPin != NULL ? 1 : 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // We only support one input pin and it is numbered zero
 | ||
|  | 
 | ||
|  | CBasePin *CBaseRenderer::GetPin(int n) | ||
|  | { | ||
|  |     CAutoLock cObjectCreationLock(&m_ObjectCreationLock); | ||
|  | 
 | ||
|  |     // Should only ever be called with zero
 | ||
|  |     ASSERT(n == 0); | ||
|  | 
 | ||
|  |     if (n != 0) { | ||
|  |         return NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Create the input pin if not already done so
 | ||
|  | 
 | ||
|  |     if (m_pInputPin == NULL) { | ||
|  | 
 | ||
|  |         // hr must be initialized to NOERROR because
 | ||
|  |         // CRendererInputPin's constructor only changes
 | ||
|  |         // hr's value if an error occurs.
 | ||
|  |         HRESULT hr = NOERROR; | ||
|  | 
 | ||
|  |         m_pInputPin = new CRendererInputPin(this,&hr,L"In"); | ||
|  |         if (NULL == m_pInputPin) { | ||
|  |             return NULL; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (FAILED(hr)) { | ||
|  |             delete m_pInputPin; | ||
|  |             m_pInputPin = NULL; | ||
|  |             return NULL; | ||
|  |         } | ||
|  |     } | ||
|  |     return m_pInputPin; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // If "In" then return the IPin for our input pin, otherwise NULL and error
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, __deref_out IPin **ppPin) | ||
|  | { | ||
|  |     CheckPointer(ppPin,E_POINTER); | ||
|  | 
 | ||
|  |     if (0==lstrcmpW(Id,L"In")) { | ||
|  |         *ppPin = GetPin(0); | ||
|  |         if (*ppPin) { | ||
|  |             (*ppPin)->AddRef(); | ||
|  |         } else { | ||
|  |             return E_OUTOFMEMORY; | ||
|  |         } | ||
|  |     } else { | ||
|  |         *ppPin = NULL; | ||
|  |         return VFW_E_NOT_FOUND; | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when the input pin receives an EndOfStream notification. If we have
 | ||
|  | // not got a sample, then notify EC_COMPLETE now. If we have samples, then set
 | ||
|  | // m_bEOS and check for this on completing samples. If we're waiting to pause
 | ||
|  | // then complete the transition to paused state by setting the state event
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::EndOfStream() | ||
|  | { | ||
|  |     // Ignore these calls if we are stopped
 | ||
|  | 
 | ||
|  |     if (m_State == State_Stopped) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we have a sample then wait for it to be rendered
 | ||
|  | 
 | ||
|  |     m_bEOS = TRUE; | ||
|  |     if (m_pMediaSample) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we are waiting for pause then we are now ready since we cannot now
 | ||
|  |     // carry on waiting for a sample to arrive since we are being told there
 | ||
|  |     // won't be any. This sets an event that the GetState function picks up
 | ||
|  | 
 | ||
|  |     Ready(); | ||
|  | 
 | ||
|  |     // Only signal completion now if we are running otherwise queue it until
 | ||
|  |     // we do run in StartStreaming. This is used when we seek because a seek
 | ||
|  |     // causes a pause where early notification of completion is misleading
 | ||
|  | 
 | ||
|  |     if (m_bStreaming) { | ||
|  |         SendEndOfStream(); | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When we are told to flush we should release the source thread
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::BeginFlush() | ||
|  | { | ||
|  |     // If paused then report state intermediate until we get some data
 | ||
|  | 
 | ||
|  |     if (m_State == State_Paused) { | ||
|  |         NotReady(); | ||
|  |     } | ||
|  | 
 | ||
|  |     SourceThreadCanWait(FALSE); | ||
|  |     CancelNotification(); | ||
|  |     ClearPendingSample(); | ||
|  |     //  Wait for Receive to complete
 | ||
|  |     WaitForReceiveToComplete(); | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // After flushing the source thread can wait in Receive again
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::EndFlush() | ||
|  | { | ||
|  |     // Reset the current sample media time
 | ||
|  |     if (m_pPosition) m_pPosition->ResetMediaTime(); | ||
|  | 
 | ||
|  |     // There should be no outstanding advise
 | ||
|  | 
 | ||
|  |     ASSERT(CancelNotification() == S_FALSE); | ||
|  |     SourceThreadCanWait(TRUE); | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // We can now send EC_REPAINTs if so required
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin) | ||
|  | { | ||
|  |     // The caller should always hold the interface lock because
 | ||
|  |     // the function uses CBaseFilter::m_State.
 | ||
|  |     ASSERT(CritCheckIn(&m_InterfaceLock)); | ||
|  | 
 | ||
|  |     m_bAbort = FALSE; | ||
|  | 
 | ||
|  |     if (State_Running == GetRealState()) { | ||
|  |         HRESULT hr = StartStreaming(); | ||
|  |         if (FAILED(hr)) { | ||
|  |             return hr; | ||
|  |         } | ||
|  | 
 | ||
|  |         SetRepaintStatus(FALSE); | ||
|  |     } else { | ||
|  |         SetRepaintStatus(TRUE); | ||
|  |     } | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when we go paused or running
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::Active() | ||
|  | { | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when we go into a stopped state
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::Inactive() | ||
|  | { | ||
|  |     if (m_pPosition) { | ||
|  |         m_pPosition->ResetMediaTime(); | ||
|  |     } | ||
|  |     //  People who derive from this may want to override this behaviour
 | ||
|  |     //  to keep hold of the sample in some circumstances
 | ||
|  |     ClearPendingSample(); | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Tell derived classes about the media type agreed
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt) | ||
|  | { | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When we break the input pin connection we should reset the EOS flags. When
 | ||
|  | // we are asked for either IMediaPosition or IMediaSeeking we will create a
 | ||
|  | // CPosPassThru object to handles media time pass through. When we're handed
 | ||
|  | // samples we store (by calling CPosPassThru::RegisterMediaTime) their media
 | ||
|  | // times so we can then return a real current position of data being rendered
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::BreakConnect() | ||
|  | { | ||
|  |     // Do we have a quality management sink
 | ||
|  | 
 | ||
|  |     if (m_pQSink) { | ||
|  |         m_pQSink->Release(); | ||
|  |         m_pQSink = NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Check we have a valid connection
 | ||
|  | 
 | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         return S_FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Check we are stopped before disconnecting
 | ||
|  |     if (m_State != State_Stopped && !m_pInputPin->CanReconnectWhenActive()) { | ||
|  |         return VFW_E_NOT_STOPPED; | ||
|  |     } | ||
|  | 
 | ||
|  |     SetRepaintStatus(FALSE); | ||
|  |     ResetEndOfStream(); | ||
|  |     ClearPendingSample(); | ||
|  |     m_bAbort = FALSE; | ||
|  | 
 | ||
|  |     if (State_Running == m_State) { | ||
|  |         StopStreaming(); | ||
|  |     } | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Retrieves the sample times for this samples (note the sample times are
 | ||
|  | // passed in by reference not value). We return S_FALSE to say schedule this
 | ||
|  | // sample according to the times on the sample. We also return S_OK in
 | ||
|  | // which case the object should simply render the sample data immediately
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample, | ||
|  |                                       __out REFERENCE_TIME *pStartTime, | ||
|  |                                       __out REFERENCE_TIME *pEndTime) | ||
|  | { | ||
|  |     ASSERT(m_dwAdvise == 0); | ||
|  |     ASSERT(pMediaSample); | ||
|  | 
 | ||
|  |     // If the stop time for this sample is before or the same as start time,
 | ||
|  |     // then just ignore it (release it) and schedule the next one in line
 | ||
|  |     // Source filters should always fill in the start and end times properly!
 | ||
|  | 
 | ||
|  |     if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) { | ||
|  |         if (*pEndTime < *pStartTime) { | ||
|  |             return VFW_E_START_TIME_AFTER_END; | ||
|  |         } | ||
|  |     } else { | ||
|  |         // no time set in the sample... draw it now?
 | ||
|  |         return S_OK; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Can't synchronise without a clock so we return S_OK which tells the
 | ||
|  |     // caller that the sample should be rendered immediately without going
 | ||
|  |     // through the overhead of setting a timer advise link with the clock
 | ||
|  | 
 | ||
|  |     if (m_pClock == NULL) { | ||
|  |         return S_OK; | ||
|  |     } | ||
|  |     return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // By default all samples are drawn according to their time stamps so we
 | ||
|  | // return S_FALSE. Returning S_OK means draw immediately, this is used
 | ||
|  | // by the derived video renderer class in its quality management.
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, | ||
|  |                                            __out REFERENCE_TIME *ptrStart, | ||
|  |                                            __out REFERENCE_TIME *ptrEnd) | ||
|  | { | ||
|  |     return S_FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // We must always reset the current advise time to zero after a timer fires
 | ||
|  | // because there are several possible ways which lead us not to do any more
 | ||
|  | // scheduling such as the pending image being cleared after state changes
 | ||
|  | 
 | ||
|  | void CBaseRenderer::SignalTimerFired() | ||
|  | { | ||
|  |     m_dwAdvise = 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Cancel any notification currently scheduled. This is called by the owning
 | ||
|  | // window object when it is told to stop streaming. If there is no timer link
 | ||
|  | // outstanding then calling this is benign otherwise we go ahead and cancel
 | ||
|  | // We must always reset the render event as the quality management code can
 | ||
|  | // signal immediate rendering by setting the event without setting an advise
 | ||
|  | // link. If we're subsequently stopped and run the first attempt to setup an
 | ||
|  | // advise link with the reference clock will find the event still signalled
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::CancelNotification() | ||
|  | { | ||
|  |     ASSERT(m_dwAdvise == 0 || m_pClock); | ||
|  |     DWORD_PTR dwAdvise = m_dwAdvise; | ||
|  | 
 | ||
|  |     // Have we a live advise link
 | ||
|  | 
 | ||
|  |     if (m_dwAdvise) { | ||
|  |         m_pClock->Unadvise(m_dwAdvise); | ||
|  |         SignalTimerFired(); | ||
|  |         ASSERT(m_dwAdvise == 0); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Clear the event and return our status
 | ||
|  | 
 | ||
|  |     m_RenderEvent.Reset(); | ||
|  |     return (dwAdvise ? S_OK : S_FALSE); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Responsible for setting up one shot advise links with the clock
 | ||
|  | // Return FALSE if the sample is to be dropped (not drawn at all)
 | ||
|  | // Return TRUE if the sample is to be drawn and in this case also
 | ||
|  | // arrange for m_RenderEvent to be set at the appropriate time
 | ||
|  | 
 | ||
|  | BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample) | ||
|  | { | ||
|  |     REFERENCE_TIME StartSample, EndSample; | ||
|  | 
 | ||
|  |     // Is someone pulling our leg
 | ||
|  | 
 | ||
|  |     if (pMediaSample == NULL) { | ||
|  |         return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Get the next sample due up for rendering.  If there aren't any ready
 | ||
|  |     // then GetNextSampleTimes returns an error.  If there is one to be done
 | ||
|  |     // then it succeeds and yields the sample times. If it is due now then
 | ||
|  |     // it returns S_OK other if it's to be done when due it returns S_FALSE
 | ||
|  | 
 | ||
|  |     HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample); | ||
|  |     if (FAILED(hr)) { | ||
|  |         return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we don't have a reference clock then we cannot set up the advise
 | ||
|  |     // time so we simply set the event indicating an image to render. This
 | ||
|  |     // will cause us to run flat out without any timing or synchronisation
 | ||
|  | 
 | ||
|  |     if (hr == S_OK) { | ||
|  |         EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent)); | ||
|  |         return TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |     ASSERT(m_dwAdvise == 0); | ||
|  |     ASSERT(m_pClock); | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  | 
 | ||
|  |     // We do have a valid reference clock interface so we can ask it to
 | ||
|  |     // set an event when the image comes due for rendering. We pass in
 | ||
|  |     // the reference time we were told to start at and also the current
 | ||
|  |     // stream time which is the offset from the start reference time
 | ||
|  | 
 | ||
|  |     hr = m_pClock->AdviseTime( | ||
|  |             (REFERENCE_TIME) m_tStart,          // Start run time
 | ||
|  |             StartSample,                        // Stream time
 | ||
|  |             (HEVENT)(HANDLE) m_RenderEvent,     // Render notification
 | ||
|  |             &m_dwAdvise);                       // Advise cookie
 | ||
|  | 
 | ||
|  |     if (SUCCEEDED(hr)) { | ||
|  |         return TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // We could not schedule the next sample for rendering despite the fact
 | ||
|  |     // we have a valid sample here. This is a fair indication that either
 | ||
|  |     // the system clock is wrong or the time stamp for the sample is duff
 | ||
|  | 
 | ||
|  |     ASSERT(m_dwAdvise == 0); | ||
|  |     return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This is called when a sample comes due for rendering. We pass the sample
 | ||
|  | // on to the derived class. After rendering we will initialise the timer for
 | ||
|  | // the next sample, NOTE signal that the last one fired first, if we don't
 | ||
|  | // do this it thinks there is still one outstanding that hasn't completed
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample) | ||
|  | { | ||
|  |     // If the media sample is NULL then we will have been notified by the
 | ||
|  |     // clock that another sample is ready but in the mean time someone has
 | ||
|  |     // stopped us streaming which causes the next sample to be released
 | ||
|  | 
 | ||
|  |     if (pMediaSample == NULL) { | ||
|  |         return S_FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we have stopped streaming then don't render any more samples, the
 | ||
|  |     // thread that got in and locked us and then reset this flag does not
 | ||
|  |     // clear the pending sample as we can use it to refresh any output device
 | ||
|  | 
 | ||
|  |     if (m_bStreaming == FALSE) { | ||
|  |         return S_FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Time how long the rendering takes
 | ||
|  | 
 | ||
|  |     OnRenderStart(pMediaSample); | ||
|  |     DoRenderSample(pMediaSample); | ||
|  |     OnRenderEnd(pMediaSample); | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Checks if there is a sample waiting at the renderer
 | ||
|  | 
 | ||
|  | BOOL CBaseRenderer::HaveCurrentSample() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     return (m_pMediaSample == NULL ? FALSE : TRUE); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Returns the current sample waiting at the video renderer. We AddRef the
 | ||
|  | // sample before returning so that should it come due for rendering the
 | ||
|  | // person who called this method will hold the remaining reference count
 | ||
|  | // that will stop the sample being added back onto the allocator free list
 | ||
|  | 
 | ||
|  | IMediaSample *CBaseRenderer::GetCurrentSample() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     if (m_pMediaSample) { | ||
|  |         m_pMediaSample->AddRef(); | ||
|  |     } | ||
|  |     return m_pMediaSample; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when the source delivers us a sample. We go through a few checks to
 | ||
|  | // make sure the sample can be rendered. If we are running (streaming) then we
 | ||
|  | // have the sample scheduled with the reference clock, if we are not streaming
 | ||
|  | // then we have received an sample in paused mode so we can complete any state
 | ||
|  | // transition. On leaving this function everything will be unlocked so an app
 | ||
|  | // thread may get in and change our state to stopped (for example) in which
 | ||
|  | // case it will also signal the thread event so that our wait call is stopped
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample) | ||
|  | { | ||
|  |     CAutoLock cInterfaceLock(&m_InterfaceLock); | ||
|  |     m_bInReceive = TRUE; | ||
|  | 
 | ||
|  |     // Check our flushing and filter state
 | ||
|  | 
 | ||
|  |     // This function must hold the interface lock because it calls 
 | ||
|  |     // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses
 | ||
|  |     // CBasePin::m_bRunTimeError.
 | ||
|  |     HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample); | ||
|  | 
 | ||
|  |     if (hr != NOERROR) { | ||
|  |         m_bInReceive = FALSE; | ||
|  |         return E_FAIL; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Has the type changed on a media sample. We do all rendering
 | ||
|  |     // synchronously on the source thread, which has a side effect
 | ||
|  |     // that only one buffer is ever outstanding. Therefore when we
 | ||
|  |     // have Receive called we can go ahead and change the format
 | ||
|  |     // Since the format change can cause a SendMessage we just don't
 | ||
|  |     // lock
 | ||
|  |     if (m_pInputPin->SampleProps()->pMediaType) { | ||
|  |         hr = m_pInputPin->SetMediaType( | ||
|  |                 (CMediaType *)m_pInputPin->SampleProps()->pMediaType); | ||
|  |         if (FAILED(hr)) { | ||
|  |             m_bInReceive = FALSE; | ||
|  |             return hr; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     CAutoLock cSampleLock(&m_RendererLock); | ||
|  | 
 | ||
|  |     ASSERT(IsActive() == TRUE); | ||
|  |     ASSERT(m_pInputPin->IsFlushing() == FALSE); | ||
|  |     ASSERT(m_pInputPin->IsConnected() == TRUE); | ||
|  |     ASSERT(m_pMediaSample == NULL); | ||
|  | 
 | ||
|  |     // Return an error if we already have a sample waiting for rendering
 | ||
|  |     // source pins must serialise the Receive calls - we also check that
 | ||
|  |     // no data is being sent after the source signalled an end of stream
 | ||
|  | 
 | ||
|  |     if (m_pMediaSample || m_bEOS || m_bAbort) { | ||
|  |         Ready(); | ||
|  |         m_bInReceive = FALSE; | ||
|  |         return E_UNEXPECTED; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Store the media times from this sample
 | ||
|  |     if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample); | ||
|  | 
 | ||
|  |     // Schedule the next sample if we are streaming
 | ||
|  | 
 | ||
|  |     if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) { | ||
|  |         ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  |         ASSERT(CancelNotification() == S_FALSE); | ||
|  |         m_bInReceive = FALSE; | ||
|  |         return VFW_E_SAMPLE_REJECTED; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Store the sample end time for EC_COMPLETE handling
 | ||
|  |     m_SignalTime = m_pInputPin->SampleProps()->tStop; | ||
|  | 
 | ||
|  |     // BEWARE we sometimes keep the sample even after returning the thread to
 | ||
|  |     // the source filter such as when we go into a stopped state (we keep it
 | ||
|  |     // to refresh the device with) so we must AddRef it to keep it safely. If
 | ||
|  |     // we start flushing the source thread is released and any sample waiting
 | ||
|  |     // will be released otherwise GetBuffer may never return (see BeginFlush)
 | ||
|  | 
 | ||
|  |     m_pMediaSample = pMediaSample; | ||
|  |     m_pMediaSample->AddRef(); | ||
|  | 
 | ||
|  |     if (m_bStreaming == FALSE) { | ||
|  |         SetRepaintStatus(TRUE); | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called by the source filter when we have a sample to render. Under normal
 | ||
|  | // circumstances we set an advise link with the clock, wait for the time to
 | ||
|  | // arrive and then render the data using the PURE virtual DoRenderSample that
 | ||
|  | // the derived class will have overriden. After rendering the sample we may
 | ||
|  | // also signal EOS if it was the last one sent before EndOfStream was called
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::Receive(IMediaSample *pSample) | ||
|  | { | ||
|  |     ASSERT(pSample); | ||
|  | 
 | ||
|  |     // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
 | ||
|  | 
 | ||
|  |     HRESULT hr = PrepareReceive(pSample); | ||
|  |     ASSERT(m_bInReceive == SUCCEEDED(hr)); | ||
|  |     if (FAILED(hr)) { | ||
|  |         if (hr == VFW_E_SAMPLE_REJECTED) { | ||
|  |             return NOERROR; | ||
|  |         } | ||
|  |         return hr; | ||
|  |     } | ||
|  | 
 | ||
|  |     // We realize the palette in "PrepareRender()" so we have to give away the
 | ||
|  |     // filter lock here.
 | ||
|  |     if (m_State == State_Paused) { | ||
|  |         PrepareRender(); | ||
|  |         // no need to use InterlockedExchange
 | ||
|  |         m_bInReceive = FALSE; | ||
|  |         { | ||
|  |             // We must hold both these locks
 | ||
|  |             CAutoLock cRendererLock(&m_InterfaceLock); | ||
|  |             if (m_State == State_Stopped) | ||
|  |                 return NOERROR; | ||
|  | 
 | ||
|  |             m_bInReceive = TRUE; | ||
|  |             CAutoLock cSampleLock(&m_RendererLock); | ||
|  |             OnReceiveFirstSample(pSample); | ||
|  |         } | ||
|  |         Ready(); | ||
|  |     } | ||
|  |     // Having set an advise link with the clock we sit and wait. We may be
 | ||
|  |     // awoken by the clock firing or by a state change. The rendering call
 | ||
|  |     // will lock the critical section and check we can still render the data
 | ||
|  | 
 | ||
|  |     hr = WaitForRenderTime(); | ||
|  |     if (FAILED(hr)) { | ||
|  |         m_bInReceive = FALSE; | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     PrepareRender(); | ||
|  | 
 | ||
|  |     //  Set this here and poll it until we work out the locking correctly
 | ||
|  |     //  It can't be right that the streaming stuff grabs the interface
 | ||
|  |     //  lock - after all we want to be able to wait for this stuff
 | ||
|  |     //  to complete
 | ||
|  |     m_bInReceive = FALSE; | ||
|  | 
 | ||
|  |     // We must hold both these locks
 | ||
|  |     CAutoLock cRendererLock(&m_InterfaceLock); | ||
|  | 
 | ||
|  |     // since we gave away the filter wide lock, the sate of the filter could
 | ||
|  |     // have chnaged to Stopped
 | ||
|  |     if (m_State == State_Stopped) | ||
|  |         return NOERROR; | ||
|  | 
 | ||
|  |     CAutoLock cSampleLock(&m_RendererLock); | ||
|  | 
 | ||
|  |     // Deal with this sample
 | ||
|  | 
 | ||
|  |     Render(m_pMediaSample); | ||
|  |     ClearPendingSample(); | ||
|  |     SendEndOfStream(); | ||
|  |     CancelNotification(); | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This is called when we stop or are inactivated to clear the pending sample
 | ||
|  | // We release the media sample interface so that they can be allocated to the
 | ||
|  | // source filter again, unless of course we are changing state to inactive in
 | ||
|  | // which case GetBuffer will return an error. We must also reset the current
 | ||
|  | // media sample to NULL so that we know we do not currently have an image
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::ClearPendingSample() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     if (m_pMediaSample) { | ||
|  |         m_pMediaSample->Release(); | ||
|  |         m_pMediaSample = NULL; | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Used to signal end of stream according to the sample end time
 | ||
|  | 
 | ||
|  | 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
 | ||
|  | { | ||
|  |     CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser; | ||
|  |     NOTE1("EndOfStreamTimer called (%d)",uID); | ||
|  |     pRenderer->TimerCallback(); | ||
|  | } | ||
|  | 
 | ||
|  | //  Do the timer callback work
 | ||
|  | void CBaseRenderer::TimerCallback() | ||
|  | { | ||
|  |     //  Lock for synchronization (but don't hold this lock when calling
 | ||
|  |     //  timeKillEvent)
 | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  | 
 | ||
|  |     // See if we should signal end of stream now
 | ||
|  | 
 | ||
|  |     if (m_EndOfStreamTimer) { | ||
|  |         m_EndOfStreamTimer = 0; | ||
|  |         SendEndOfStream(); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // If we are at the end of the stream signal the filter graph but do not set
 | ||
|  | // the state flag back to FALSE. Once we drop off the end of the stream we
 | ||
|  | // leave the flag set (until a subsequent ResetEndOfStream). Each sample we
 | ||
|  | // get delivered will update m_SignalTime to be the last sample's end time.
 | ||
|  | // We must wait this long before signalling end of stream to the filtergraph
 | ||
|  | 
 | ||
|  | #define TIMEOUT_DELIVERYWAIT 50
 | ||
|  | #define TIMEOUT_RESOLUTION 10
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::SendEndOfStream() | ||
|  | { | ||
|  |     ASSERT(CritCheckIn(&m_RendererLock)); | ||
|  |     if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If there is no clock then signal immediately
 | ||
|  |     if (m_pClock == NULL) { | ||
|  |         return NotifyEndOfStream(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // How long into the future is the delivery time
 | ||
|  | 
 | ||
|  |     REFERENCE_TIME Signal = m_tStart + m_SignalTime; | ||
|  |     REFERENCE_TIME CurrentTime; | ||
|  |     m_pClock->GetTime(&CurrentTime); | ||
|  |     LONG Delay = LONG((Signal - CurrentTime) / 10000); | ||
|  | 
 | ||
|  |     // Dump the timing information to the debugger
 | ||
|  | 
 | ||
|  |     NOTE1("Delay until end of stream delivery %d",Delay); | ||
|  |     NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime)); | ||
|  |     NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal)); | ||
|  | 
 | ||
|  |     // Wait for the delivery time to arrive
 | ||
|  | 
 | ||
|  |     if (Delay < TIMEOUT_DELIVERYWAIT) { | ||
|  |         return NotifyEndOfStream(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Signal a timer callback on another worker thread
 | ||
|  | 
 | ||
|  |     m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer
 | ||
|  |                                       TIMEOUT_RESOLUTION,     // Timer resolution
 | ||
|  |                                       EndOfStreamTimer,       // Callback function
 | ||
|  |                                       DWORD_PTR(this),        // Used information
 | ||
|  |                                       TIME_ONESHOT);          // Type of callback
 | ||
|  |     if (m_EndOfStreamTimer == 0) { | ||
|  |         return NotifyEndOfStream(); | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Signals EC_COMPLETE to the filtergraph manager
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::NotifyEndOfStream() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     ASSERT(m_bEOSDelivered == FALSE); | ||
|  |     ASSERT(m_EndOfStreamTimer == 0); | ||
|  | 
 | ||
|  |     // Has the filter changed state
 | ||
|  | 
 | ||
|  |     if (m_bStreaming == FALSE) { | ||
|  |         ASSERT(m_EndOfStreamTimer == 0); | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reset the end of stream timer
 | ||
|  |     m_EndOfStreamTimer = 0; | ||
|  | 
 | ||
|  |     // If we've been using the IMediaPosition interface, set it's start
 | ||
|  |     // and end media "times" to the stop position by hand.  This ensures
 | ||
|  |     // that we actually get to the end, even if the MPEG guestimate has
 | ||
|  |     // been bad or if the quality management dropped the last few frames
 | ||
|  | 
 | ||
|  |     if (m_pPosition) m_pPosition->EOS(); | ||
|  |     m_bEOSDelivered = TRUE; | ||
|  |     NOTE("Sending EC_COMPLETE..."); | ||
|  |     return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Reset the end of stream flag, this is typically called when we transfer to
 | ||
|  | // stopped states since that resets the current position back to the start so
 | ||
|  | // we will receive more samples or another EndOfStream if there aren't any. We
 | ||
|  | // keep two separate flags one to say we have run off the end of the stream
 | ||
|  | // (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
 | ||
|  | // to the filter graph. We need the latter otherwise we can end up sending an
 | ||
|  | // EC_COMPLETE every time the source changes state and calls our EndOfStream
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::ResetEndOfStream() | ||
|  | { | ||
|  |     ResetEndOfStreamTimer(); | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  | 
 | ||
|  |     m_bEOS = FALSE; | ||
|  |     m_bEOSDelivered = FALSE; | ||
|  |     m_SignalTime = 0; | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Kills any outstanding end of stream timer
 | ||
|  | 
 | ||
|  | void CBaseRenderer::ResetEndOfStreamTimer() | ||
|  | { | ||
|  |     ASSERT(CritCheckOut(&m_RendererLock)); | ||
|  |     if (m_EndOfStreamTimer) { | ||
|  |         timeKillEvent(m_EndOfStreamTimer); | ||
|  |         m_EndOfStreamTimer = 0; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This is called when we start running so that we can schedule any pending
 | ||
|  | // image we have with the clock and display any timing information. If we
 | ||
|  | // don't have any sample but we have queued an EOS flag then we send it. If
 | ||
|  | // we do have a sample then we wait until that has been rendered before we
 | ||
|  | // signal the filter graph otherwise we may change state before it's done
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::StartStreaming() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     if (m_bStreaming == TRUE) { | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reset the streaming times ready for running
 | ||
|  | 
 | ||
|  |     m_bStreaming = TRUE; | ||
|  | 
 | ||
|  |     timeBeginPeriod(1); | ||
|  |     OnStartStreaming(); | ||
|  | 
 | ||
|  |     // There should be no outstanding advise
 | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); | ||
|  |     ASSERT(CancelNotification() == S_FALSE); | ||
|  | 
 | ||
|  |     // If we have an EOS and no data then deliver it now
 | ||
|  | 
 | ||
|  |     if (m_pMediaSample == NULL) { | ||
|  |         return SendEndOfStream(); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Have the data rendered
 | ||
|  | 
 | ||
|  |     ASSERT(m_pMediaSample); | ||
|  |     if (!ScheduleSample(m_pMediaSample)) | ||
|  |         m_RenderEvent.Set(); | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // This is called when we stop streaming so that we can set our internal flag
 | ||
|  | // indicating we are not now to schedule any more samples arriving. The state
 | ||
|  | // change methods in the filter implementation take care of cancelling any
 | ||
|  | // clock advise link we have set up and clearing any pending sample we have
 | ||
|  | 
 | ||
|  | HRESULT CBaseRenderer::StopStreaming() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_RendererLock); | ||
|  |     m_bEOSDelivered = FALSE; | ||
|  | 
 | ||
|  |     if (m_bStreaming == TRUE) { | ||
|  |         m_bStreaming = FALSE; | ||
|  |         OnStopStreaming(); | ||
|  |         timeEndPeriod(1); | ||
|  |     } | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // We have a boolean flag that is reset when we have signalled EC_REPAINT to
 | ||
|  | // the filter graph. We set this when we receive an image so that should any
 | ||
|  | // conditions arise again we can send another one. By having a flag we ensure
 | ||
|  | // we don't flood the filter graph with redundant calls. We do not set the
 | ||
|  | // event when we receive an EndOfStream call since there is no point in us
 | ||
|  | // sending further EC_REPAINTs. In particular the AutoShowWindow method and
 | ||
|  | // the DirectDraw object use this method to control the window repainting
 | ||
|  | 
 | ||
|  | void CBaseRenderer::SetRepaintStatus(BOOL bRepaint) | ||
|  | { | ||
|  |     CAutoLock cSampleLock(&m_RendererLock); | ||
|  |     m_bRepaintStatus = bRepaint; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Pass the window handle to the upstream filter
 | ||
|  | 
 | ||
|  | void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd) | ||
|  | { | ||
|  |     IMediaEventSink *pSink; | ||
|  | 
 | ||
|  |     // Does the pin support IMediaEventSink
 | ||
|  |     HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink); | ||
|  |     if (SUCCEEDED(hr)) { | ||
|  |         pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0); | ||
|  |         pSink->Release(); | ||
|  |     } | ||
|  |     NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Signal an EC_REPAINT to the filter graph. This can be used to have data
 | ||
|  | // sent to us. For example when a video window is first displayed it may
 | ||
|  | // not have an image to display, at which point it signals EC_REPAINT. The
 | ||
|  | // filtergraph will either pause the graph if stopped or if already paused
 | ||
|  | // it will call put_CurrentPosition of the current position. Setting the
 | ||
|  | // current position to itself has the stream flushed and the image resent
 | ||
|  | 
 | ||
|  | #define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
 | ||
|  | 
 | ||
|  | void CBaseRenderer::SendRepaint() | ||
|  | { | ||
|  |     CAutoLock cSampleLock(&m_RendererLock); | ||
|  |     ASSERT(m_pInputPin); | ||
|  | 
 | ||
|  |     // We should not send repaint notifications when...
 | ||
|  |     //    - An end of stream has been notified
 | ||
|  |     //    - Our input pin is being flushed
 | ||
|  |     //    - The input pin is not connected
 | ||
|  |     //    - We have aborted a video playback
 | ||
|  |     //    - There is a repaint already sent
 | ||
|  | 
 | ||
|  |     if (m_bAbort == FALSE) { | ||
|  |         if (m_pInputPin->IsConnected() == TRUE) { | ||
|  |             if (m_pInputPin->IsFlushing() == FALSE) { | ||
|  |                 if (IsEndOfStream() == FALSE) { | ||
|  |                     if (m_bRepaintStatus == TRUE) { | ||
|  |                         IPin *pPin = (IPin *) m_pInputPin; | ||
|  |                         NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0); | ||
|  |                         SetRepaintStatus(FALSE); | ||
|  |                         RLOG("Sending repaint"); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // When a video window detects a display change (WM_DISPLAYCHANGE message) it
 | ||
|  | // can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
 | ||
|  | // filtergraph will stop everyone and reconnect our input pin. As we're then
 | ||
|  | // reconnected we can accept the media type that matches the new display mode
 | ||
|  | // since we may no longer be able to draw the current image type efficiently
 | ||
|  | 
 | ||
|  | BOOL CBaseRenderer::OnDisplayChange() | ||
|  | { | ||
|  |     // Ignore if we are not connected yet
 | ||
|  | 
 | ||
|  |     CAutoLock cSampleLock(&m_RendererLock); | ||
|  |     if (m_pInputPin->IsConnected() == FALSE) { | ||
|  |         return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     RLOG("Notification of EC_DISPLAY_CHANGE"); | ||
|  | 
 | ||
|  |     // Pass our input pin as parameter on the event
 | ||
|  | 
 | ||
|  |     IPin *pPin = (IPin *) m_pInputPin; | ||
|  |     m_pInputPin->AddRef(); | ||
|  |     NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0); | ||
|  |     SetAbortSignal(TRUE); | ||
|  |     ClearPendingSample(); | ||
|  |     m_pInputPin->Release(); | ||
|  | 
 | ||
|  |     return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called just before we start drawing.
 | ||
|  | // Store the current time in m_trRenderStart to allow the rendering time to be
 | ||
|  | // logged.  Log the time stamp of the sample and how late it is (neg is early)
 | ||
|  | 
 | ||
|  | void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample) | ||
|  | { | ||
|  | #ifdef PERF
 | ||
|  |     REFERENCE_TIME trStart, trEnd; | ||
|  |     pMediaSample->GetTime(&trStart, &trEnd); | ||
|  | 
 | ||
|  |     MSR_INTEGER(m_idBaseStamp, (int)trStart);     // dump low order 32 bits
 | ||
|  | 
 | ||
|  |     m_pClock->GetTime(&m_trRenderStart); | ||
|  |     MSR_INTEGER(0, (int)m_trRenderStart); | ||
|  |     REFERENCE_TIME trStream; | ||
|  |     trStream = m_trRenderStart-m_tStart;     // convert reftime to stream time
 | ||
|  |     MSR_INTEGER(0,(int)trStream); | ||
|  | 
 | ||
|  |     const int trLate = (int)(trStream - trStart); | ||
|  |     MSR_INTEGER(m_idBaseAccuracy, trLate/10000);  // dump in mSec
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | } // OnRenderStart
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called directly after drawing an image.
 | ||
|  | // calculate the time spent drawing and log it.
 | ||
|  | 
 | ||
|  | void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample) | ||
|  | { | ||
|  | #ifdef PERF
 | ||
|  |     REFERENCE_TIME trNow; | ||
|  |     m_pClock->GetTime(&trNow); | ||
|  |     MSR_INTEGER(0,(int)trNow); | ||
|  |     int t = (int)((trNow - m_trRenderStart)/10000);   // convert UNITS->msec
 | ||
|  |     MSR_INTEGER(m_idBaseRenderTime, t); | ||
|  | #endif
 | ||
|  | } // OnRenderEnd
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Constructor must be passed the base renderer object
 | ||
|  | 
 | ||
|  | CRendererInputPin::CRendererInputPin(__inout CBaseRenderer *pRenderer, | ||
|  |                                      __inout HRESULT *phr, | ||
|  |                                      __in_opt LPCWSTR pPinName) : | ||
|  |     CBaseInputPin(NAME("Renderer pin"), | ||
|  |                   pRenderer, | ||
|  |                   &pRenderer->m_InterfaceLock, | ||
|  |                   (HRESULT *) phr, | ||
|  |                   pPinName) | ||
|  | { | ||
|  |     m_pRenderer = pRenderer; | ||
|  |     ASSERT(m_pRenderer); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Signals end of data stream on the input pin
 | ||
|  | 
 | ||
|  | STDMETHODIMP CRendererInputPin::EndOfStream() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); | ||
|  |     CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); | ||
|  | 
 | ||
|  |     // Make sure we're streaming ok
 | ||
|  | 
 | ||
|  |     HRESULT hr = CheckStreaming(); | ||
|  |     if (hr != NOERROR) { | ||
|  |         return hr; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Pass it onto the renderer
 | ||
|  | 
 | ||
|  |     hr = m_pRenderer->EndOfStream(); | ||
|  |     if (SUCCEEDED(hr)) { | ||
|  |         hr = CBaseInputPin::EndOfStream(); | ||
|  |     } | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Signals start of flushing on the input pin - we do the final reset end of
 | ||
|  | // stream with the renderer lock unlocked but with the interface lock locked
 | ||
|  | // We must do this because we call timeKillEvent, our timer callback method
 | ||
|  | // has to take the renderer lock to serialise our state. Therefore holding a
 | ||
|  | // renderer lock when calling timeKillEvent could cause a deadlock condition
 | ||
|  | 
 | ||
|  | STDMETHODIMP CRendererInputPin::BeginFlush() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); | ||
|  |     { | ||
|  |         CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); | ||
|  |         CBaseInputPin::BeginFlush(); | ||
|  |         m_pRenderer->BeginFlush(); | ||
|  |     } | ||
|  |     return m_pRenderer->ResetEndOfStream(); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Signals end of flushing on the input pin
 | ||
|  | 
 | ||
|  | STDMETHODIMP CRendererInputPin::EndFlush() | ||
|  | { | ||
|  |     CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); | ||
|  |     CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); | ||
|  | 
 | ||
|  |     HRESULT hr = m_pRenderer->EndFlush(); | ||
|  |     if (SUCCEEDED(hr)) { | ||
|  |         hr = CBaseInputPin::EndFlush(); | ||
|  |     } | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Pass the sample straight through to the renderer object
 | ||
|  | 
 | ||
|  | STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample) | ||
|  | { | ||
|  |     HRESULT hr = m_pRenderer->Receive(pSample); | ||
|  |     if (FAILED(hr)) { | ||
|  | 
 | ||
|  |         // A deadlock could occur if the caller holds the renderer lock and
 | ||
|  |         // attempts to acquire the interface lock.
 | ||
|  |         ASSERT(CritCheckOut(&m_pRenderer->m_RendererLock)); | ||
|  | 
 | ||
|  |         { | ||
|  |             // The interface lock must be held when the filter is calling
 | ||
|  |             // IsStopped() or IsFlushing().  The interface lock must also
 | ||
|  |             // be held because the function uses m_bRunTimeError.
 | ||
|  |             CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); | ||
|  | 
 | ||
|  |             // We do not report errors which occur while the filter is stopping,
 | ||
|  |             // flushing or if the m_bAbort flag is set .  Errors are expected to 
 | ||
|  |             // occur during these operations and the streaming thread correctly 
 | ||
|  |             // handles the errors.  
 | ||
|  |             if (!IsStopped() && !IsFlushing() && !m_pRenderer->m_bAbort && !m_bRunTimeError) { | ||
|  | 
 | ||
|  |                 // EC_ERRORABORT's first parameter is the error which caused
 | ||
|  |                 // the event and its' last parameter is 0.  See the Direct
 | ||
|  |                 // Show SDK documentation for more information.
 | ||
|  |                 m_pRenderer->NotifyEvent(EC_ERRORABORT,hr,0); | ||
|  | 
 | ||
|  |                 { | ||
|  |                     CAutoLock alRendererLock(&m_pRenderer->m_RendererLock); | ||
|  |                     if (m_pRenderer->IsStreaming() && !m_pRenderer->IsEndOfStreamDelivered()) { | ||
|  |                         m_pRenderer->NotifyEndOfStream(); | ||
|  |                     } | ||
|  |                 } | ||
|  |      | ||
|  |                 m_bRunTimeError = TRUE; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when the input pin is disconnected
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::BreakConnect() | ||
|  | { | ||
|  |     HRESULT hr = m_pRenderer->BreakConnect(); | ||
|  |     if (FAILED(hr)) { | ||
|  |         return hr; | ||
|  |     } | ||
|  |     return CBaseInputPin::BreakConnect(); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when the input pin is connected
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin) | ||
|  | { | ||
|  |     HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin); | ||
|  |     if (FAILED(hr)) { | ||
|  |         return hr; | ||
|  |     } | ||
|  |     return CBaseInputPin::CompleteConnect(pReceivePin); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Give the pin id of our one and only pin
 | ||
|  | 
 | ||
|  | STDMETHODIMP CRendererInputPin::QueryId(__deref_out LPWSTR *Id) | ||
|  | { | ||
|  |     CheckPointer(Id,E_POINTER); | ||
|  | 
 | ||
|  |     const WCHAR szIn[] = L"In"; | ||
|  | 
 | ||
|  |     *Id = (LPWSTR)CoTaskMemAlloc(sizeof(szIn)); | ||
|  |     if (*Id == NULL) { | ||
|  |         return E_OUTOFMEMORY; | ||
|  |     } | ||
|  |     CopyMemory(*Id, szIn, sizeof(szIn)); | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Will the filter accept this media type
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt) | ||
|  | { | ||
|  |     return m_pRenderer->CheckMediaType(pmt); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when we go paused or running
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::Active() | ||
|  | { | ||
|  |     return m_pRenderer->Active(); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when we go into a stopped state
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::Inactive() | ||
|  | { | ||
|  |     // The caller must hold the interface lock because 
 | ||
|  |     // this function uses m_bRunTimeError.
 | ||
|  |     ASSERT(CritCheckIn(&m_pRenderer->m_InterfaceLock)); | ||
|  | 
 | ||
|  |     m_bRunTimeError = FALSE; | ||
|  | 
 | ||
|  |     return m_pRenderer->Inactive(); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Tell derived classes about the media type agreed
 | ||
|  | 
 | ||
|  | HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt) | ||
|  | { | ||
|  |     HRESULT hr = CBaseInputPin::SetMediaType(pmt); | ||
|  |     if (FAILED(hr)) { | ||
|  |         return hr; | ||
|  |     } | ||
|  |     return m_pRenderer->SetMediaType(pmt); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // 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"); | ||
|  | 
 | ||
|  | CBaseVideoRenderer::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
 | ||
|  | 
 | ||
|  |     CBaseRenderer(RenderClass,pName,pUnk,phr), | ||
|  |     m_cFramesDropped(0), | ||
|  |     m_cFramesDrawn(0), | ||
|  |     m_bSupplierHandlingQuality(FALSE) | ||
|  | { | ||
|  |     ResetStreamingTimes(); | ||
|  | 
 | ||
|  | #ifdef PERF
 | ||
|  |     m_idTimeStamp       = MSR_REGISTER(TEXT("Frame time stamp")); | ||
|  |     m_idEarliness       = MSR_REGISTER(TEXT("Earliness fudge")); | ||
|  |     m_idTarget          = MSR_REGISTER(TEXT("Target (mSec)")); | ||
|  |     m_idSchLateTime     = MSR_REGISTER(TEXT("mSec late when scheduled")); | ||
|  |     m_idDecision        = MSR_REGISTER(TEXT("Scheduler decision code")); | ||
|  |     m_idQualityRate     = MSR_REGISTER(TEXT("Quality rate sent")); | ||
|  |     m_idQualityTime     = MSR_REGISTER(TEXT("Quality time sent")); | ||
|  |     m_idWaitReal        = MSR_REGISTER(TEXT("Render wait")); | ||
|  |     // m_idWait            = MSR_REGISTER(TEXT("wait time recorded (msec)"));
 | ||
|  |     m_idFrameAccuracy   = MSR_REGISTER(TEXT("Frame accuracy (msecs)")); | ||
|  |     m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE); | ||
|  |     //m_idSendQuality      = MSR_REGISTER(TEXT("Processing Quality message"));
 | ||
|  | 
 | ||
|  |     m_idRenderAvg       = MSR_REGISTER(TEXT("Render draw time Avg")); | ||
|  |     m_idFrameAvg        = MSR_REGISTER(TEXT("FrameAvg")); | ||
|  |     m_idWaitAvg         = MSR_REGISTER(TEXT("WaitAvg")); | ||
|  |     m_idDuration        = MSR_REGISTER(TEXT("Duration")); | ||
|  |     m_idThrottle        = MSR_REGISTER(TEXT("Audio-video throttle wait")); | ||
|  |     // m_idDebug           = MSR_REGISTER(TEXT("Debug stuff"));
 | ||
|  | #endif // PERF
 | ||
|  | } // Constructor
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Destructor is just a placeholder
 | ||
|  | 
 | ||
|  | CBaseVideoRenderer::~CBaseVideoRenderer() | ||
|  | { | ||
|  |     ASSERT(m_dwAdvise == 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // 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 CBaseVideoRenderer::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 - strange value
 | ||
|  |     m_trRenderLast = 0; | ||
|  |     m_trWaitAvg = 0; | ||
|  |     m_tRenderStart = 0; | ||
|  |     m_cFramesDrawn = 0; | ||
|  |     m_cFramesDropped = 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; | ||
|  | 
 | ||
|  | #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 CBaseVideoRenderer::OnStartStreaming() | ||
|  | { | ||
|  |     ResetStreamingTimes(); | ||
|  |     return NOERROR; | ||
|  | } // OnStartStreaming
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called at end of streaming.  Fixes times for property page report
 | ||
|  | 
 | ||
|  | HRESULT CBaseVideoRenderer::OnStopStreaming() | ||
|  | { | ||
|  |     m_tStreamingStart = timeGetTime()-m_tStreamingStart; | ||
|  |     return NOERROR; | ||
|  | } // OnStopStreaming
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Called when we start waiting for a rendering event.
 | ||
|  | // Used to update times spent waiting and not waiting.
 | ||
|  | 
 | ||
|  | void CBaseVideoRenderer::OnWaitStart() | ||
|  | { | ||
|  |     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 CBaseVideoRenderer::OnWaitEnd() | ||
|  | { | ||
|  | #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.
 | ||
|  | #if 0
 | ||
|  |     m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
 | ||
|  | #else
 | ||
|  |     // 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; | ||
|  | #endif
 | ||
|  |     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 invalid 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 CBaseVideoRenderer::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 CBaseVideoRenderer::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 kludge - we can get frames that are very late
 | ||
|  |     // especially (at start-up) and they invalidate 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 invalid time, so don't
 | ||
|  |     // 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 invalid first frame stamp.
 | ||
|  |     if (m_cFramesDrawn>2) { | ||
|  |         int tFrame = trFrame/10000;    // convert to mSec else it overflows
 | ||
|  | 
 | ||
|  |         // This is a kludge.  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 CBaseVideoRenderer::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); | ||
|  |     } else { | ||
|  |         Sleep(0); | ||
|  |     } | ||
|  | } // 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 CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample) | ||
|  | { | ||
|  |     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 CBaseVideoRenderer::OnRenderStart(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 CBaseVideoRenderer::OnRenderEnd(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 CBaseVideoRenderer::SetSink( IQualityControl * piqc) | ||
|  | { | ||
|  | 
 | ||
|  |     m_pQSink = piqc; | ||
|  | 
 | ||
|  |     return NOERROR; | ||
|  | } // SetSink
 | ||
|  | 
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, 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!
 | ||
|  | 
 | ||
|  |     // 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; | ||
|  | } // Notify
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Send a message to indicate what our supplier should do about quality.
 | ||
|  | // 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 CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate, | ||
|  |                                         REFERENCE_TIME trRealStream) | ||
|  | { | ||
|  |     Quality q; | ||
|  |     HRESULT hr; | ||
|  | 
 | ||
|  |     // 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 ); | ||
|  | 
 | ||
|  |     // A specific sink interface may be set through IPin
 | ||
|  | 
 | ||
|  |     if (m_pQSink==NULL) { | ||
|  |         // Get our input pin's peer.  We send quality management messages
 | ||
|  |         // to any nominated receiver of these things (set in the IPin
 | ||
|  |         // interface), or else to our source filter.
 | ||
|  | 
 | ||
|  |         IQualityControl *pQC = NULL; | ||
|  |         IPin *pOutputPin = m_pInputPin->GetConnected(); | ||
|  |         ASSERT(pOutputPin != NULL); | ||
|  | 
 | ||
|  |         // And get an AddRef'd quality control interface
 | ||
|  | 
 | ||
|  |         hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC); | ||
|  |         if (SUCCEEDED(hr)) { | ||
|  |             m_pQSink = pQC; | ||
|  |         } | ||
|  |     } | ||
|  |     if (m_pQSink) { | ||
|  |         return m_pQSink->Notify(this,q); | ||
|  |     } | ||
|  | 
 | ||
|  |     return S_FALSE; | ||
|  | 
 | ||
|  | } // SendQuality
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // 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 CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, | ||
|  |                                                 __inout REFERENCE_TIME *ptrStart, | ||
|  |                                                 __inout REFERENCE_TIME *ptrEnd) | ||
|  | { | ||
|  | 
 | ||
|  |     // Don't call us unless there's a clock interface to synchronise with
 | ||
|  |     ASSERT(m_pClock); | ||
|  | 
 | ||
|  |     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)
 | ||
|  |     REFERENCE_TIME trRealStream;     // the real time now expressed as stream time.
 | ||
|  |     m_pClock->GetTime(&trRealStream); | ||
|  | #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
 | ||
|  |     trRealStream -= m_tStart;     // convert to stream time (this is a reftime)
 | ||
|  | 
 | ||
|  |     // 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); | ||
|  | 
 | ||
|  |     // Send quality control messages upstream, measured against target
 | ||
|  |     HRESULT hr = SendQuality(trLate, trRealStream); | ||
|  |     // Note: the filter upstream is allowed to this FAIL meaning "you do it".
 | ||
|  |     m_bSupplierHandlingQuality = (hr==S_OK); | ||
|  | 
 | ||
|  |     // 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 inefficent.  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.
 | ||
|  |           (3*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; | ||
|  | 
 | ||
|  |         // 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 manipulated 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
 | ||
|  | 
 | ||
|  | BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample) | ||
|  | { | ||
|  |     // We override ShouldDrawSampleNow to add quality management
 | ||
|  | 
 | ||
|  |     BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample); | ||
|  |     if (bDrawImage == FALSE) { | ||
|  | 	++m_cFramesDropped; | ||
|  | 	return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     // m_cFramesDrawn must NOT be updated here.  It has to be updated
 | ||
|  |     // in RecordFrameLateness at the same time as the other statistics.
 | ||
|  |     return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // 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 CBaseVideoRenderer::get_FramesDroppedInRenderer(__out int *pcFramesDropped) | ||
|  | { | ||
|  |     CheckPointer(pcFramesDropped,E_POINTER); | ||
|  |     CAutoLock cVideoLock(&m_InterfaceLock); | ||
|  |     *pcFramesDropped = m_cFramesDropped; | ||
|  |     return NOERROR; | ||
|  | } // get_FramesDroppedInRenderer
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Set *pcFramesDrawn to the number of frames drawn since
 | ||
|  | // streaming started.
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn) | ||
|  | { | ||
|  |     CheckPointer(pcFramesDrawn,E_POINTER); | ||
|  |     CAutoLock cVideoLock(&m_InterfaceLock); | ||
|  |     *pcFramesDrawn = m_cFramesDrawn; | ||
|  |     return NOERROR; | ||
|  | } // get_FramesDrawn
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // Set iAvgFrameRate to the frames per hundred secs since
 | ||
|  | // streaming started.  0 otherwise.
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate) | ||
|  | { | ||
|  |     CheckPointer(piAvgFrameRate,E_POINTER); | ||
|  |     CAutoLock cVideoLock(&m_InterfaceLock); | ||
|  | 
 | ||
|  |     int t; | ||
|  |     if (m_bStreaming) { | ||
|  |         t = timeGetTime()-m_tStreamingStart; | ||
|  |     } else { | ||
|  |         t = m_tStreamingStart; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (t<=0) { | ||
|  |         *piAvgFrameRate = 0; | ||
|  |         ASSERT(m_cFramesDrawn == 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 CBaseVideoRenderer::get_AvgSyncOffset(__out int *piAvg) | ||
|  | { | ||
|  |     CheckPointer(piAvg,E_POINTER); | ||
|  |     CAutoLock cVideoLock(&m_InterfaceLock); | ||
|  | 
 | ||
|  |     if (NULL==m_pClock) { | ||
|  |         *piAvg = 0; | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // 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 CBaseVideoRenderer::GetStdDev( | ||
|  |     int nSamples, | ||
|  |     __out int *piResult, | ||
|  |     LONGLONG llSumSq, | ||
|  |     LONGLONG iTot | ||
|  | ) | ||
|  | { | ||
|  |     CheckPointer(piResult,E_POINTER); | ||
|  |     CAutoLock cVideoLock(&m_InterfaceLock); | ||
|  | 
 | ||
|  |     if (NULL==m_pClock) { | ||
|  |         *piResult = 0; | ||
|  |         return NOERROR; | ||
|  |     } | ||
|  | 
 | ||
|  |     // 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 invalid 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 CBaseVideoRenderer::get_DevSyncOffset(__out int *piDev) | ||
|  | { | ||
|  |     // First frames have invalid 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 CBaseVideoRenderer::get_Jitter(__out int *piJitter) | ||
|  | { | ||
|  |     // First frames have invalid stamps, so we get no stats for them
 | ||
|  |     // So second frame gives invalid 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 | ||
|  | CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv) | ||
|  | { | ||
|  |     // We return IQualProp and delegate everything else
 | ||
|  | 
 | ||
|  |     if (riid == IID_IQualProp) { | ||
|  |         return GetInterface( (IQualProp *)this, ppv); | ||
|  |     } else if (riid == IID_IQualityControl) { | ||
|  |         return GetInterface( (IQualityControl *)this, ppv); | ||
|  |     } | ||
|  |     return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Override JoinFilterGraph so that, just before leaving
 | ||
|  | // the graph we can send an EC_WINDOW_DESTROYED event
 | ||
|  | 
 | ||
|  | STDMETHODIMP | ||
|  | CBaseVideoRenderer::JoinFilterGraph(__inout_opt IFilterGraph *pGraph, __in_opt LPCWSTR pName) | ||
|  | { | ||
|  |     // Since we send EC_ACTIVATE, we also need to ensure
 | ||
|  |     // we send EC_WINDOW_DESTROYED or the resource manager may be
 | ||
|  |     // holding us as a focus object
 | ||
|  |     if (!pGraph && m_pGraph) { | ||
|  | 
 | ||
|  |         // We were in a graph and now we're not
 | ||
|  |         // Do this properly in case we are aggregated
 | ||
|  |         IBaseFilter* pFilter = this; | ||
|  |         NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0); | ||
|  |     } | ||
|  |     return CBaseFilter::JoinFilterGraph(pGraph, pName); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // 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)
 | ||
|  | 
 |