| //------------------------------------------------------------------------------ | |
| // 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) | |