Support unrar64.dll
[xy_vsfilter.git] / src / filters / BaseClasses / renbase.cpp
blob712fb84283c7beb7116c6d6feae7cad861947365
1 //------------------------------------------------------------------------------
2 // File: RenBase.cpp
3 //
4 // Desc: DirectShow base classes.
5 //
6 // Copyright (c) 1992-2002 Microsoft Corporation. All rights reserved.
7 //------------------------------------------------------------------------------
10 #include <streams.h> // DirectShow base class definitions
11 #include <mmsystem.h> // Needed for definition of timeGetTime
12 #include <limits.h> // Standard data type limit definitions
13 #include <measure.h> // Used for time critical log functions
15 #pragma warning(disable:4355)
17 // Helper function for clamping time differences
18 int inline TimeDiff(REFERENCE_TIME rt)
20 if (rt < - (50 * UNITS)) {
21 return -(50 * UNITS);
22 } else
23 if (rt > 50 * UNITS) {
24 return 50 * UNITS;
25 } else return (int)rt;
28 // Implements the CBaseRenderer class
30 CBaseRenderer::CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
31 TCHAR *pName, // Debug ONLY description
32 LPUNKNOWN pUnk, // Aggregated owner object
33 HRESULT *phr) : // General OLE return code
35 CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass),
36 m_evComplete(TRUE),
37 m_bAbort(FALSE),
38 m_pPosition(NULL),
39 m_ThreadSignal(TRUE),
40 m_bStreaming(FALSE),
41 m_bEOS(FALSE),
42 m_bEOSDelivered(FALSE),
43 m_pMediaSample(NULL),
44 m_dwAdvise(0),
45 m_pQSink(NULL),
46 m_pInputPin(NULL),
47 m_bRepaintStatus(TRUE),
48 m_SignalTime(0),
49 m_bInReceive(FALSE),
50 m_EndOfStreamTimer(0)
52 Ready();
53 #ifdef PERF
54 m_idBaseStamp = MSR_REGISTER(TEXT("BaseRenderer: sample time stamp"));
55 m_idBaseRenderTime = MSR_REGISTER(TEXT("BaseRenderer: draw time (msec)"));
56 m_idBaseAccuracy = MSR_REGISTER(TEXT("BaseRenderer: Accuracy (msec)"));
57 #endif
61 // Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
62 // object. The object is created when somebody queries us. These are standard
63 // control interfaces for seeking and setting start/stop positions and rates.
64 // We will probably also have made an input pin based on CRendererInputPin
65 // that has to be deleted, it's created when an enumerator calls our GetPin
67 CBaseRenderer::~CBaseRenderer()
69 ASSERT(m_bStreaming == FALSE);
70 ASSERT(m_EndOfStreamTimer == 0);
71 StopStreaming();
72 ClearPendingSample();
74 // Delete any IMediaPosition implementation
76 if (m_pPosition) {
77 delete m_pPosition;
78 m_pPosition = NULL;
81 // Delete any input pin created
83 if (m_pInputPin) {
84 delete m_pInputPin;
85 m_pInputPin = NULL;
88 // Release any Quality sink
90 ASSERT(m_pQSink == NULL);
94 // This returns the IMediaPosition and IMediaSeeking interfaces
96 HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid,void **ppv)
98 CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
99 if (m_pPosition) {
100 return m_pPosition->NonDelegatingQueryInterface(riid,ppv);
103 HRESULT hr = NOERROR;
105 // Create implementation of this dynamically since sometimes we may
106 // never try and do a seek. The helper object implements a position
107 // control interface (IMediaPosition) which in fact simply takes the
108 // calls normally from the filter graph and passes them upstream
110 m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
111 CBaseFilter::GetOwner(),
112 (HRESULT *) &hr,
113 GetPin(0));
114 if (m_pPosition == NULL) {
115 return E_OUTOFMEMORY;
118 if (FAILED(hr)) {
119 delete m_pPosition;
120 m_pPosition = NULL;
121 return E_NOINTERFACE;
123 return GetMediaPositionInterface(riid,ppv);
127 // Overriden to say what interfaces we support and where
129 STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid,void **ppv)
131 // Do we have this interface
133 if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
134 return GetMediaPositionInterface(riid,ppv);
135 } else {
136 return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
141 // This is called whenever we change states, we have a manual reset event that
142 // is signalled whenever we don't won't the source filter thread to wait in us
143 // (such as in a stopped state) and likewise is not signalled whenever it can
144 // wait (during paused and running) this function sets or resets the thread
145 // event. The event is used to stop source filter threads waiting in Receive
147 HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait)
149 if (bCanWait == TRUE) {
150 m_ThreadSignal.Reset();
151 } else {
152 m_ThreadSignal.Set();
154 return NOERROR;
158 #ifdef DEBUG
159 // Dump the current renderer state to the debug terminal. The hardest part of
160 // the renderer is the window where we unlock everything to wait for a clock
161 // to signal it is time to draw or for the application to cancel everything
162 // by stopping the filter. If we get things wrong we can leave the thread in
163 // WaitForRenderTime with no way for it to ever get out and we will deadlock
165 void CBaseRenderer::DisplayRendererState()
167 DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime")));
169 // No way should this be signalled at this point
171 BOOL bSignalled = m_ThreadSignal.Check();
172 DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"),bSignalled));
174 // Now output the current renderer state variables
176 DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"),m_State));
178 DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"),m_bAbort));
180 DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"),m_bStreaming));
182 DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"),m_dwAdvise));
184 DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"),m_pMediaSample));
186 DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"),m_bEOS));
188 DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"),m_bEOSDelivered));
190 DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"),m_bRepaintStatus));
193 // Output the delayed end of stream timer information
195 DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"),m_EndOfStreamTimer));
197 DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)m_SignalTime)));
200 // Should never timeout during a flushing state
202 BOOL bFlushing = m_pInputPin->IsFlushing();
203 DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"),bFlushing));
205 // Display the time we were told to start at
206 DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time)));
208 // Have we got a reference clock
209 if (m_pClock == NULL) return;
211 // Get the current time from the wall clock
213 CRefTime CurrentTime,StartTime,EndTime;
214 m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime);
215 CRefTime Offset = CurrentTime - m_tStart;
217 // Display the current time from the clock
219 DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"),CDisp((LONGLONG)CurrentTime.m_time)));
221 DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"),Offset.Millisecs()));
224 // Do we have a sample ready to render
225 if (m_pMediaSample == NULL) return;
227 m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
228 DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"),
229 StartTime.Millisecs(),EndTime.Millisecs()));
231 // Calculate how long it is until it is due for rendering
232 CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
233 DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"),Wait.Millisecs()));
235 #endif
238 // Wait until the clock sets the timer event or we're otherwise signalled. We
239 // set an arbitrary timeout for this wait and if it fires then we display the
240 // current renderer state on the debugger. It will often fire if the filter's
241 // left paused in an application however it may also fire during stress tests
242 // if the synchronisation with application seeks and state changes is faulty
244 #define RENDER_TIMEOUT 10000
246 HRESULT CBaseRenderer::WaitForRenderTime()
248 HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent };
249 DWORD Result = WAIT_TIMEOUT;
251 // Wait for either the time to arrive or for us to be stopped
253 OnWaitStart();
254 while (Result == WAIT_TIMEOUT) {
255 Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT);
257 #ifdef DEBUG
258 if (Result == WAIT_TIMEOUT) DisplayRendererState();
259 #endif
262 OnWaitEnd();
264 // We may have been awoken without the timer firing
266 if (Result == WAIT_OBJECT_0) {
267 return VFW_E_STATE_CHANGED;
270 SignalTimerFired();
271 return NOERROR;
275 // Poll waiting for Receive to complete. This really matters when
276 // Receive may set the palette and cause window messages
277 // The problem is that if we don't really wait for a renderer to
278 // stop processing we can deadlock waiting for a transform which
279 // is calling the renderer's Receive() method because the transform's
280 // Stop method doesn't know to process window messages to unblock
281 // the renderer's Receive processing
282 void CBaseRenderer::WaitForReceiveToComplete()
284 for (;;) {
285 if (!m_bInReceive) {
286 break;
289 MSG msg;
290 // Receive all interthread sendmessages
291 PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE);
293 Sleep(1);
296 // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
297 // above just cleared the changebit which will cause some messaging
298 // calls to block (waitMessage, MsgWaitFor...) now.
299 // Post a dummy message to set the QS_POSTMESSAGE bit again
300 if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
301 // Send dummy message
302 PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
306 // A filter can have four discrete states, namely Stopped, Running, Paused,
307 // Intermediate. We are in an intermediate state if we are currently trying
308 // to pause but haven't yet got the first sample (or if we have been flushed
309 // in paused state and therefore still have to wait for a sample to arrive)
311 // This class contains an event called m_evComplete which is signalled when
312 // the current state is completed and is not signalled when we are waiting to
313 // complete the last state transition. As mentioned above the only time we
314 // use this at the moment is when we wait for a media sample in paused state
315 // If while we are waiting we receive an end of stream notification from the
316 // source filter then we know no data is imminent so we can reset the event
317 // This means that when we transition to paused the source filter must call
318 // end of stream on us or send us an image otherwise we'll hang indefinately
321 // Simple internal way of getting the real state
323 FILTER_STATE CBaseRenderer::GetRealState() {
324 return m_State;
328 // The renderer doesn't complete the full transition to paused states until
329 // it has got one media sample to render. If you ask it for its state while
330 // it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
332 STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State)
334 CheckPointer(State,E_POINTER);
336 if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) {
337 *State = m_State;
338 return VFW_S_STATE_INTERMEDIATE;
340 *State = m_State;
341 return NOERROR;
345 // If we're pausing and we have no samples we don't complete the transition
346 // to State_Paused and we return S_FALSE. However if the m_bAbort flag has
347 // been set then all samples are rejected so there is no point waiting for
348 // one. If we do have a sample then return NOERROR. We will only ever return
349 // VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
350 // (calling GetState after either being stopped or Run will NOT return this)
352 HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState)
354 // Allow us to be paused when disconnected
356 if (m_pInputPin->IsConnected() == FALSE) {
357 Ready();
358 return S_OK;
361 // Have we run off the end of stream
363 if (IsEndOfStream() == TRUE) {
364 Ready();
365 return S_OK;
368 // Make sure we get fresh data after being stopped
370 if (HaveCurrentSample() == TRUE) {
371 if (OldState != State_Stopped) {
372 Ready();
373 return S_OK;
376 NotReady();
377 return S_FALSE;
381 // When we stop the filter the things we do are:-
383 // Decommit the allocator being used in the connection
384 // Release the source filter if it's waiting in Receive
385 // Cancel any advise link we set up with the clock
386 // Any end of stream signalled is now obsolete so reset
387 // Allow us to be stopped when we are not connected
389 STDMETHODIMP CBaseRenderer::Stop()
391 CAutoLock cRendererLock(&m_InterfaceLock);
393 // Make sure there really is a state change
395 if (m_State == State_Stopped) {
396 return NOERROR;
399 // Is our input pin connected
401 if (m_pInputPin->IsConnected() == FALSE) {
402 NOTE("Input pin is not connected");
403 m_State = State_Stopped;
404 return NOERROR;
407 CBaseFilter::Stop();
409 // If we are going into a stopped state then we must decommit whatever
410 // allocator we are using it so that any source filter waiting in the
411 // GetBuffer can be released and unlock themselves for a state change
413 if (m_pInputPin->Allocator()) {
414 m_pInputPin->Allocator()->Decommit();
417 // Cancel any scheduled rendering
419 SetRepaintStatus(TRUE);
420 StopStreaming();
421 SourceThreadCanWait(FALSE);
422 ResetEndOfStream();
423 CancelNotification();
425 // There should be no outstanding clock advise
426 ASSERT(CancelNotification() == S_FALSE);
427 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
428 ASSERT(m_EndOfStreamTimer == 0);
430 Ready();
431 WaitForReceiveToComplete();
432 m_bAbort = FALSE;
434 return NOERROR;
438 // When we pause the filter the things we do are:-
440 // Commit the allocator being used in the connection
441 // Allow a source filter thread to wait in Receive
442 // Cancel any clock advise link (we may be running)
443 // Possibly complete the state change if we have data
444 // Allow us to be paused when we are not connected
446 STDMETHODIMP CBaseRenderer::Pause()
448 CAutoLock cRendererLock(&m_InterfaceLock);
449 FILTER_STATE OldState = m_State;
450 ASSERT(m_pInputPin->IsFlushing() == FALSE);
452 // Make sure there really is a state change
454 if (m_State == State_Paused) {
455 return CompleteStateChange(State_Paused);
458 // Has our input pin been connected
460 if (m_pInputPin->IsConnected() == FALSE) {
461 NOTE("Input pin is not connected");
462 m_State = State_Paused;
463 return CompleteStateChange(State_Paused);
466 // Pause the base filter class
468 HRESULT hr = CBaseFilter::Pause();
469 if (FAILED(hr)) {
470 NOTE("Pause failed");
471 return hr;
474 // Enable EC_REPAINT events again
476 SetRepaintStatus(TRUE);
477 StopStreaming();
478 SourceThreadCanWait(TRUE);
479 CancelNotification();
480 ResetEndOfStreamTimer();
482 // If we are going into a paused state then we must commit whatever
483 // allocator we are using it so that any source filter can call the
484 // GetBuffer and expect to get a buffer without returning an error
486 if (m_pInputPin->Allocator()) {
487 m_pInputPin->Allocator()->Commit();
490 // There should be no outstanding advise
491 ASSERT(CancelNotification() == S_FALSE);
492 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
493 ASSERT(m_EndOfStreamTimer == 0);
494 ASSERT(m_pInputPin->IsFlushing() == FALSE);
496 // When we come out of a stopped state we must clear any image we were
497 // holding onto for frame refreshing. Since renderers see state changes
498 // first we can reset ourselves ready to accept the source thread data
499 // Paused or running after being stopped causes the current position to
500 // be reset so we're not interested in passing end of stream signals
502 if (OldState == State_Stopped) {
503 m_bAbort = FALSE;
504 ClearPendingSample();
506 return CompleteStateChange(OldState);
510 // When we run the filter the things we do are:-
512 // Commit the allocator being used in the connection
513 // Allow a source filter thread to wait in Receive
514 // Signal the render event just to get us going
515 // Start the base class by calling StartStreaming
516 // Allow us to be run when we are not connected
517 // Signal EC_COMPLETE if we are not connected
519 STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime)
521 CAutoLock cRendererLock(&m_InterfaceLock);
522 FILTER_STATE OldState = m_State;
524 // Make sure there really is a state change
526 if (m_State == State_Running) {
527 return NOERROR;
530 // Send EC_COMPLETE if we're not connected
532 if (m_pInputPin->IsConnected() == FALSE) {
533 NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
534 m_State = State_Running;
535 return NOERROR;
538 Ready();
540 // Pause the base filter class
542 HRESULT hr = CBaseFilter::Run(StartTime);
543 if (FAILED(hr)) {
544 NOTE("Run failed");
545 return hr;
548 // Allow the source thread to wait
549 ASSERT(m_pInputPin->IsFlushing() == FALSE);
550 SourceThreadCanWait(TRUE);
551 SetRepaintStatus(FALSE);
553 // There should be no outstanding advise
554 ASSERT(CancelNotification() == S_FALSE);
555 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
556 ASSERT(m_EndOfStreamTimer == 0);
557 ASSERT(m_pInputPin->IsFlushing() == FALSE);
559 // If we are going into a running state then we must commit whatever
560 // allocator we are using it so that any source filter can call the
561 // GetBuffer and expect to get a buffer without returning an error
563 if (m_pInputPin->Allocator()) {
564 m_pInputPin->Allocator()->Commit();
567 // When we come out of a stopped state we must clear any image we were
568 // holding onto for frame refreshing. Since renderers see state changes
569 // first we can reset ourselves ready to accept the source thread data
570 // Paused or running after being stopped causes the current position to
571 // be reset so we're not interested in passing end of stream signals
573 if (OldState == State_Stopped) {
574 m_bAbort = FALSE;
575 ClearPendingSample();
577 return StartStreaming();
581 // Return the number of input pins we support
583 int CBaseRenderer::GetPinCount()
585 return 1;
589 // We only support one input pin and it is numbered zero
591 CBasePin *CBaseRenderer::GetPin(int n)
593 CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
595 // Should only ever be called with zero
596 ASSERT(n == 0);
598 if (n != 0) {
599 return NULL;
602 // Create the input pin if not already done so
604 if (m_pInputPin == NULL) {
606 // hr must be initialized to NOERROR because
607 // CRendererInputPin's constructor only changes
608 // hr's value if an error occurs.
609 HRESULT hr = NOERROR;
611 m_pInputPin = new CRendererInputPin(this,&hr,L"In");
612 if (NULL == m_pInputPin) {
613 return NULL;
616 if (FAILED(hr)) {
617 delete m_pInputPin;
618 m_pInputPin = NULL;
619 return NULL;
622 return m_pInputPin;
626 // If "In" then return the IPin for our input pin, otherwise NULL and error
628 STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, IPin **ppPin)
630 CheckPointer(ppPin,E_POINTER);
632 if (0==lstrcmpW(Id,L"In")) {
633 *ppPin = GetPin(0);
634 ASSERT(*ppPin);
635 (*ppPin)->AddRef();
636 } else {
637 *ppPin = NULL;
638 return VFW_E_NOT_FOUND;
640 return NOERROR;
644 // Called when the input pin receives an EndOfStream notification. If we have
645 // not got a sample, then notify EC_COMPLETE now. If we have samples, then set
646 // m_bEOS and check for this on completing samples. If we're waiting to pause
647 // then complete the transition to paused state by setting the state event
649 HRESULT CBaseRenderer::EndOfStream()
651 // Ignore these calls if we are stopped
653 if (m_State == State_Stopped) {
654 return NOERROR;
657 // If we have a sample then wait for it to be rendered
659 m_bEOS = TRUE;
660 if (m_pMediaSample) {
661 return NOERROR;
664 // If we are waiting for pause then we are now ready since we cannot now
665 // carry on waiting for a sample to arrive since we are being told there
666 // won't be any. This sets an event that the GetState function picks up
668 Ready();
670 // Only signal completion now if we are running otherwise queue it until
671 // we do run in StartStreaming. This is used when we seek because a seek
672 // causes a pause where early notification of completion is misleading
674 if (m_bStreaming) {
675 SendEndOfStream();
677 return NOERROR;
681 // When we are told to flush we should release the source thread
683 HRESULT CBaseRenderer::BeginFlush()
685 // If paused then report state intermediate until we get some data
687 if (m_State == State_Paused) {
688 NotReady();
691 SourceThreadCanWait(FALSE);
692 CancelNotification();
693 ClearPendingSample();
694 // Wait for Receive to complete
695 WaitForReceiveToComplete();
697 return NOERROR;
701 // After flushing the source thread can wait in Receive again
703 HRESULT CBaseRenderer::EndFlush()
705 // Reset the current sample media time
706 if (m_pPosition) m_pPosition->ResetMediaTime();
708 // There should be no outstanding advise
710 ASSERT(CancelNotification() == S_FALSE);
711 SourceThreadCanWait(TRUE);
712 return NOERROR;
716 // We can now send EC_REPAINTs if so required
718 HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin)
720 // The caller should always hold the interface lock because
721 // the function uses CBaseFilter::m_State.
722 ASSERT(CritCheckIn(&m_InterfaceLock));
724 m_bAbort = FALSE;
726 if (State_Running == GetRealState()) {
727 HRESULT hr = StartStreaming();
728 if (FAILED(hr)) {
729 return hr;
732 SetRepaintStatus(FALSE);
733 } else {
734 SetRepaintStatus(TRUE);
737 return NOERROR;
741 // Called when we go paused or running
743 HRESULT CBaseRenderer::Active()
745 return NOERROR;
749 // Called when we go into a stopped state
751 HRESULT CBaseRenderer::Inactive()
753 if (m_pPosition) {
754 m_pPosition->ResetMediaTime();
756 // People who derive from this may want to override this behaviour
757 // to keep hold of the sample in some circumstances
758 ClearPendingSample();
760 return NOERROR;
764 // Tell derived classes about the media type agreed
766 HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt)
768 return NOERROR;
772 // When we break the input pin connection we should reset the EOS flags. When
773 // we are asked for either IMediaPosition or IMediaSeeking we will create a
774 // CPosPassThru object to handles media time pass through. When we're handed
775 // samples we store (by calling CPosPassThru::RegisterMediaTime) their media
776 // times so we can then return a real current position of data being rendered
778 HRESULT CBaseRenderer::BreakConnect()
780 // Do we have a quality management sink
782 if (m_pQSink) {
783 m_pQSink->Release();
784 m_pQSink = NULL;
787 // Check we have a valid connection
789 if (m_pInputPin->IsConnected() == FALSE) {
790 return S_FALSE;
793 // Check we are stopped before disconnecting
794 if (m_State != State_Stopped && !m_pInputPin->CanReconnectWhenActive()) {
795 return VFW_E_NOT_STOPPED;
798 SetRepaintStatus(FALSE);
799 ResetEndOfStream();
800 ClearPendingSample();
801 m_bAbort = FALSE;
803 if (State_Running == m_State) {
804 StopStreaming();
807 return NOERROR;
811 // Retrieves the sample times for this samples (note the sample times are
812 // passed in by reference not value). We return S_FALSE to say schedule this
813 // sample according to the times on the sample. We also return S_OK in
814 // which case the object should simply render the sample data immediately
816 HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample,
817 REFERENCE_TIME *pStartTime,
818 REFERENCE_TIME *pEndTime)
820 ASSERT(m_dwAdvise == 0);
821 ASSERT(pMediaSample);
823 // If the stop time for this sample is before or the same as start time,
824 // then just ignore it (release it) and schedule the next one in line
825 // Source filters should always fill in the start and end times properly!
827 if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) {
828 if (*pEndTime < *pStartTime) {
829 return VFW_E_START_TIME_AFTER_END;
831 } else {
832 // no time set in the sample... draw it now?
833 return S_OK;
836 // Can't synchronise without a clock so we return S_OK which tells the
837 // caller that the sample should be rendered immediately without going
838 // through the overhead of setting a timer advise link with the clock
840 if (m_pClock == NULL) {
841 return S_OK;
843 return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime);
847 // By default all samples are drawn according to their time stamps so we
848 // return S_FALSE. Returning S_OK means draw immediately, this is used
849 // by the derived video renderer class in its quality management.
851 HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
852 REFERENCE_TIME *ptrStart,
853 REFERENCE_TIME *ptrEnd)
855 return S_FALSE;
859 // We must always reset the current advise time to zero after a timer fires
860 // because there are several possible ways which lead us not to do any more
861 // scheduling such as the pending image being cleared after state changes
863 void CBaseRenderer::SignalTimerFired()
865 m_dwAdvise = 0;
869 // Cancel any notification currently scheduled. This is called by the owning
870 // window object when it is told to stop streaming. If there is no timer link
871 // outstanding then calling this is benign otherwise we go ahead and cancel
872 // We must always reset the render event as the quality management code can
873 // signal immediate rendering by setting the event without setting an advise
874 // link. If we're subsequently stopped and run the first attempt to setup an
875 // advise link with the reference clock will find the event still signalled
877 HRESULT CBaseRenderer::CancelNotification()
879 ASSERT(m_dwAdvise == 0 || m_pClock);
880 DWORD_PTR dwAdvise = m_dwAdvise;
882 // Have we a live advise link
884 if (m_dwAdvise) {
885 m_pClock->Unadvise(m_dwAdvise);
886 SignalTimerFired();
887 ASSERT(m_dwAdvise == 0);
890 // Clear the event and return our status
892 m_RenderEvent.Reset();
893 return (dwAdvise ? S_OK : S_FALSE);
897 // Responsible for setting up one shot advise links with the clock
898 // Return FALSE if the sample is to be dropped (not drawn at all)
899 // Return TRUE if the sample is to be drawn and in this case also
900 // arrange for m_RenderEvent to be set at the appropriate time
902 BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample)
904 REFERENCE_TIME StartSample, EndSample;
906 // Is someone pulling our leg
908 if (pMediaSample == NULL) {
909 return FALSE;
912 // Get the next sample due up for rendering. If there aren't any ready
913 // then GetNextSampleTimes returns an error. If there is one to be done
914 // then it succeeds and yields the sample times. If it is due now then
915 // it returns S_OK other if it's to be done when due it returns S_FALSE
917 HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
918 if (FAILED(hr)) {
919 return FALSE;
922 // If we don't have a reference clock then we cannot set up the advise
923 // time so we simply set the event indicating an image to render. This
924 // will cause us to run flat out without any timing or synchronisation
926 if (hr == S_OK) {
927 EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
928 return TRUE;
931 ASSERT(m_dwAdvise == 0);
932 ASSERT(m_pClock);
933 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
935 // We do have a valid reference clock interface so we can ask it to
936 // set an event when the image comes due for rendering. We pass in
937 // the reference time we were told to start at and also the current
938 // stream time which is the offset from the start reference time
940 hr = m_pClock->AdviseTime(
941 (REFERENCE_TIME) m_tStart, // Start run time
942 StartSample, // Stream time
943 (HEVENT)(HANDLE) m_RenderEvent, // Render notification
944 &m_dwAdvise); // Advise cookie
946 if (SUCCEEDED(hr)) {
947 return TRUE;
950 // We could not schedule the next sample for rendering despite the fact
951 // we have a valid sample here. This is a fair indication that either
952 // the system clock is wrong or the time stamp for the sample is duff
954 ASSERT(m_dwAdvise == 0);
955 return FALSE;
959 // This is called when a sample comes due for rendering. We pass the sample
960 // on to the derived class. After rendering we will initialise the timer for
961 // the next sample, NOTE signal that the last one fired first, if we don't
962 // do this it thinks there is still one outstanding that hasn't completed
964 HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample)
966 // If the media sample is NULL then we will have been notified by the
967 // clock that another sample is ready but in the mean time someone has
968 // stopped us streaming which causes the next sample to be released
970 if (pMediaSample == NULL) {
971 return S_FALSE;
974 // If we have stopped streaming then don't render any more samples, the
975 // thread that got in and locked us and then reset this flag does not
976 // clear the pending sample as we can use it to refresh any output device
978 if (m_bStreaming == FALSE) {
979 return S_FALSE;
982 // Time how long the rendering takes
984 OnRenderStart(pMediaSample);
985 DoRenderSample(pMediaSample);
986 OnRenderEnd(pMediaSample);
988 return NOERROR;
992 // Checks if there is a sample waiting at the renderer
994 BOOL CBaseRenderer::HaveCurrentSample()
996 CAutoLock cRendererLock(&m_RendererLock);
997 return (m_pMediaSample == NULL ? FALSE : TRUE);
1001 // Returns the current sample waiting at the video renderer. We AddRef the
1002 // sample before returning so that should it come due for rendering the
1003 // person who called this method will hold the remaining reference count
1004 // that will stop the sample being added back onto the allocator free list
1006 IMediaSample *CBaseRenderer::GetCurrentSample()
1008 CAutoLock cRendererLock(&m_RendererLock);
1009 if (m_pMediaSample) {
1010 m_pMediaSample->AddRef();
1012 return m_pMediaSample;
1016 // Called when the source delivers us a sample. We go through a few checks to
1017 // make sure the sample can be rendered. If we are running (streaming) then we
1018 // have the sample scheduled with the reference clock, if we are not streaming
1019 // then we have received an sample in paused mode so we can complete any state
1020 // transition. On leaving this function everything will be unlocked so an app
1021 // thread may get in and change our state to stopped (for example) in which
1022 // case it will also signal the thread event so that our wait call is stopped
1024 HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample)
1026 CAutoLock cInterfaceLock(&m_InterfaceLock);
1027 m_bInReceive = TRUE;
1029 // Check our flushing and filter state
1031 // This function must hold the interface lock because it calls
1032 // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses
1033 // CBasePin::m_bRunTimeError.
1034 HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample);
1036 if (hr != NOERROR) {
1037 m_bInReceive = FALSE;
1038 return E_FAIL;
1041 // Has the type changed on a media sample. We do all rendering
1042 // synchronously on the source thread, which has a side effect
1043 // that only one buffer is ever outstanding. Therefore when we
1044 // have Receive called we can go ahead and change the format
1045 // Since the format change can cause a SendMessage we just don't
1046 // lock
1047 if (m_pInputPin->SampleProps()->pMediaType) {
1048 hr = m_pInputPin->SetMediaType(
1049 (CMediaType *)m_pInputPin->SampleProps()->pMediaType);
1050 if (FAILED(hr)) {
1051 m_bInReceive = FALSE;
1052 return hr;
1057 CAutoLock cSampleLock(&m_RendererLock);
1059 ASSERT(IsActive() == TRUE);
1060 ASSERT(m_pInputPin->IsFlushing() == FALSE);
1061 ASSERT(m_pInputPin->IsConnected() == TRUE);
1062 ASSERT(m_pMediaSample == NULL);
1064 // Return an error if we already have a sample waiting for rendering
1065 // source pins must serialise the Receive calls - we also check that
1066 // no data is being sent after the source signalled an end of stream
1068 if (m_pMediaSample || m_bEOS || m_bAbort) {
1069 Ready();
1070 m_bInReceive = FALSE;
1071 return E_UNEXPECTED;
1074 // Store the media times from this sample
1075 if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);
1077 // Schedule the next sample if we are streaming
1079 if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) {
1080 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
1081 ASSERT(CancelNotification() == S_FALSE);
1082 m_bInReceive = FALSE;
1083 return VFW_E_SAMPLE_REJECTED;
1086 // Store the sample end time for EC_COMPLETE handling
1087 m_SignalTime = m_pInputPin->SampleProps()->tStop;
1089 // BEWARE we sometimes keep the sample even after returning the thread to
1090 // the source filter such as when we go into a stopped state (we keep it
1091 // to refresh the device with) so we must AddRef it to keep it safely. If
1092 // we start flushing the source thread is released and any sample waiting
1093 // will be released otherwise GetBuffer may never return (see BeginFlush)
1095 m_pMediaSample = pMediaSample;
1096 m_pMediaSample->AddRef();
1098 if (m_bStreaming == FALSE) {
1099 SetRepaintStatus(TRUE);
1101 return NOERROR;
1105 // Called by the source filter when we have a sample to render. Under normal
1106 // circumstances we set an advise link with the clock, wait for the time to
1107 // arrive and then render the data using the PURE virtual DoRenderSample that
1108 // the derived class will have overriden. After rendering the sample we may
1109 // also signal EOS if it was the last one sent before EndOfStream was called
1111 HRESULT CBaseRenderer::Receive(IMediaSample *pSample)
1113 ASSERT(pSample);
1115 // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
1117 HRESULT hr = PrepareReceive(pSample);
1118 ASSERT(m_bInReceive == SUCCEEDED(hr));
1119 if (FAILED(hr)) {
1120 if (hr == VFW_E_SAMPLE_REJECTED) {
1121 return NOERROR;
1123 return hr;
1126 // We realize the palette in "PrepareRender()" so we have to give away the
1127 // filter lock here.
1128 if (m_State == State_Paused) {
1129 PrepareRender();
1130 // no need to use InterlockedExchange
1131 m_bInReceive = FALSE;
1133 // We must hold both these locks
1134 CAutoLock cRendererLock(&m_InterfaceLock);
1135 if (m_State == State_Stopped)
1136 return NOERROR;
1138 m_bInReceive = TRUE;
1139 CAutoLock cSampleLock(&m_RendererLock);
1140 OnReceiveFirstSample(pSample);
1142 Ready();
1144 // Having set an advise link with the clock we sit and wait. We may be
1145 // awoken by the clock firing or by a state change. The rendering call
1146 // will lock the critical section and check we can still render the data
1148 hr = WaitForRenderTime();
1149 if (FAILED(hr)) {
1150 m_bInReceive = FALSE;
1151 return NOERROR;
1154 PrepareRender();
1156 // Set this here and poll it until we work out the locking correctly
1157 // It can't be right that the streaming stuff grabs the interface
1158 // lock - after all we want to be able to wait for this stuff
1159 // to complete
1160 m_bInReceive = FALSE;
1162 // We must hold both these locks
1163 CAutoLock cRendererLock(&m_InterfaceLock);
1165 // since we gave away the filter wide lock, the sate of the filter could
1166 // have chnaged to Stopped
1167 if (m_State == State_Stopped)
1168 return NOERROR;
1170 CAutoLock cSampleLock(&m_RendererLock);
1172 // Deal with this sample
1174 Render(m_pMediaSample);
1175 ClearPendingSample();
1176 SendEndOfStream();
1177 CancelNotification();
1178 return NOERROR;
1182 // This is called when we stop or are inactivated to clear the pending sample
1183 // We release the media sample interface so that they can be allocated to the
1184 // source filter again, unless of course we are changing state to inactive in
1185 // which case GetBuffer will return an error. We must also reset the current
1186 // media sample to NULL so that we know we do not currently have an image
1188 HRESULT CBaseRenderer::ClearPendingSample()
1190 CAutoLock cRendererLock(&m_RendererLock);
1191 if (m_pMediaSample) {
1192 m_pMediaSample->Release();
1193 m_pMediaSample = NULL;
1195 return NOERROR;
1199 // Used to signal end of stream according to the sample end time
1201 void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier
1202 UINT uMsg, // Not currently used
1203 DWORD_PTR dwUser,// User information
1204 DWORD_PTR dw1, // Windows reserved
1205 DWORD_PTR dw2) // is also reserved
1207 CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser;
1208 NOTE1("EndOfStreamTimer called (%d)",uID);
1209 pRenderer->TimerCallback();
1212 // Do the timer callback work
1213 void CBaseRenderer::TimerCallback()
1215 // Lock for synchronization (but don't hold this lock when calling
1216 // timeKillEvent)
1217 CAutoLock cRendererLock(&m_RendererLock);
1219 // See if we should signal end of stream now
1221 if (m_EndOfStreamTimer) {
1222 m_EndOfStreamTimer = 0;
1223 SendEndOfStream();
1228 // If we are at the end of the stream signal the filter graph but do not set
1229 // the state flag back to FALSE. Once we drop off the end of the stream we
1230 // leave the flag set (until a subsequent ResetEndOfStream). Each sample we
1231 // get delivered will update m_SignalTime to be the last sample's end time.
1232 // We must wait this long before signalling end of stream to the filtergraph
1234 #define TIMEOUT_DELIVERYWAIT 50
1235 #define TIMEOUT_RESOLUTION 10
1237 HRESULT CBaseRenderer::SendEndOfStream()
1239 ASSERT(CritCheckIn(&m_RendererLock));
1240 if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) {
1241 return NOERROR;
1244 // If there is no clock then signal immediately
1245 if (m_pClock == NULL) {
1246 return NotifyEndOfStream();
1249 // How long into the future is the delivery time
1251 REFERENCE_TIME Signal = m_tStart + m_SignalTime;
1252 REFERENCE_TIME CurrentTime;
1253 m_pClock->GetTime(&CurrentTime);
1254 LONG Delay = LONG((Signal - CurrentTime) / 10000);
1256 // Dump the timing information to the debugger
1258 NOTE1("Delay until end of stream delivery %d",Delay);
1259 NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
1260 NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));
1262 // Wait for the delivery time to arrive
1264 if (Delay < TIMEOUT_DELIVERYWAIT) {
1265 return NotifyEndOfStream();
1268 // Signal a timer callback on another worker thread
1270 m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer
1271 TIMEOUT_RESOLUTION, // Timer resolution
1272 EndOfStreamTimer, // Callback function
1273 DWORD_PTR(this), // Used information
1274 TIME_ONESHOT); // Type of callback
1275 if (m_EndOfStreamTimer == 0) {
1276 return NotifyEndOfStream();
1278 return NOERROR;
1282 // Signals EC_COMPLETE to the filtergraph manager
1284 HRESULT CBaseRenderer::NotifyEndOfStream()
1286 CAutoLock cRendererLock(&m_RendererLock);
1287 ASSERT(m_bEOSDelivered == FALSE);
1288 ASSERT(m_EndOfStreamTimer == 0);
1290 // Has the filter changed state
1292 if (m_bStreaming == FALSE) {
1293 ASSERT(m_EndOfStreamTimer == 0);
1294 return NOERROR;
1297 // Reset the end of stream timer
1298 m_EndOfStreamTimer = 0;
1300 // If we've been using the IMediaPosition interface, set it's start
1301 // and end media "times" to the stop position by hand. This ensures
1302 // that we actually get to the end, even if the MPEG guestimate has
1303 // been bad or if the quality management dropped the last few frames
1305 if (m_pPosition) m_pPosition->EOS();
1306 m_bEOSDelivered = TRUE;
1307 NOTE("Sending EC_COMPLETE...");
1308 return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
1312 // Reset the end of stream flag, this is typically called when we transfer to
1313 // stopped states since that resets the current position back to the start so
1314 // we will receive more samples or another EndOfStream if there aren't any. We
1315 // keep two separate flags one to say we have run off the end of the stream
1316 // (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
1317 // to the filter graph. We need the latter otherwise we can end up sending an
1318 // EC_COMPLETE every time the source changes state and calls our EndOfStream
1320 HRESULT CBaseRenderer::ResetEndOfStream()
1322 ResetEndOfStreamTimer();
1323 CAutoLock cRendererLock(&m_RendererLock);
1325 m_bEOS = FALSE;
1326 m_bEOSDelivered = FALSE;
1327 m_SignalTime = 0;
1329 return NOERROR;
1333 // Kills any outstanding end of stream timer
1335 void CBaseRenderer::ResetEndOfStreamTimer()
1337 ASSERT(CritCheckOut(&m_RendererLock));
1338 if (m_EndOfStreamTimer) {
1339 timeKillEvent(m_EndOfStreamTimer);
1340 m_EndOfStreamTimer = 0;
1345 // This is called when we start running so that we can schedule any pending
1346 // image we have with the clock and display any timing information. If we
1347 // don't have any sample but we have queued an EOS flag then we send it. If
1348 // we do have a sample then we wait until that has been rendered before we
1349 // signal the filter graph otherwise we may change state before it's done
1351 HRESULT CBaseRenderer::StartStreaming()
1353 CAutoLock cRendererLock(&m_RendererLock);
1354 if (m_bStreaming == TRUE) {
1355 return NOERROR;
1358 // Reset the streaming times ready for running
1360 m_bStreaming = TRUE;
1362 timeBeginPeriod(1);
1363 OnStartStreaming();
1365 // There should be no outstanding advise
1366 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
1367 ASSERT(CancelNotification() == S_FALSE);
1369 // If we have an EOS and no data then deliver it now
1371 if (m_pMediaSample == NULL) {
1372 return SendEndOfStream();
1375 // Have the data rendered
1377 ASSERT(m_pMediaSample);
1378 if (!ScheduleSample(m_pMediaSample))
1379 m_RenderEvent.Set();
1381 return NOERROR;
1385 // This is called when we stop streaming so that we can set our internal flag
1386 // indicating we are not now to schedule any more samples arriving. The state
1387 // change methods in the filter implementation take care of cancelling any
1388 // clock advise link we have set up and clearing any pending sample we have
1390 HRESULT CBaseRenderer::StopStreaming()
1392 CAutoLock cRendererLock(&m_RendererLock);
1393 m_bEOSDelivered = FALSE;
1395 if (m_bStreaming == TRUE) {
1396 m_bStreaming = FALSE;
1397 OnStopStreaming();
1398 timeEndPeriod(1);
1400 return NOERROR;
1404 // We have a boolean flag that is reset when we have signalled EC_REPAINT to
1405 // the filter graph. We set this when we receive an image so that should any
1406 // conditions arise again we can send another one. By having a flag we ensure
1407 // we don't flood the filter graph with redundant calls. We do not set the
1408 // event when we receive an EndOfStream call since there is no point in us
1409 // sending further EC_REPAINTs. In particular the AutoShowWindow method and
1410 // the DirectDraw object use this method to control the window repainting
1412 void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
1414 CAutoLock cSampleLock(&m_RendererLock);
1415 m_bRepaintStatus = bRepaint;
1419 // Pass the window handle to the upstream filter
1421 void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd)
1423 IMediaEventSink *pSink;
1425 // Does the pin support IMediaEventSink
1426 HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink);
1427 if (SUCCEEDED(hr)) {
1428 pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
1429 pSink->Release();
1431 NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
1435 // Signal an EC_REPAINT to the filter graph. This can be used to have data
1436 // sent to us. For example when a video window is first displayed it may
1437 // not have an image to display, at which point it signals EC_REPAINT. The
1438 // filtergraph will either pause the graph if stopped or if already paused
1439 // it will call put_CurrentPosition of the current position. Setting the
1440 // current position to itself has the stream flushed and the image resent
1442 #define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
1444 void CBaseRenderer::SendRepaint()
1446 CAutoLock cSampleLock(&m_RendererLock);
1447 ASSERT(m_pInputPin);
1449 // We should not send repaint notifications when...
1450 // - An end of stream has been notified
1451 // - Our input pin is being flushed
1452 // - The input pin is not connected
1453 // - We have aborted a video playback
1454 // - There is a repaint already sent
1456 if (m_bAbort == FALSE) {
1457 if (m_pInputPin->IsConnected() == TRUE) {
1458 if (m_pInputPin->IsFlushing() == FALSE) {
1459 if (IsEndOfStream() == FALSE) {
1460 if (m_bRepaintStatus == TRUE) {
1461 IPin *pPin = (IPin *) m_pInputPin;
1462 NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0);
1463 SetRepaintStatus(FALSE);
1464 RLOG("Sending repaint");
1473 // When a video window detects a display change (WM_DISPLAYCHANGE message) it
1474 // can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
1475 // filtergraph will stop everyone and reconnect our input pin. As we're then
1476 // reconnected we can accept the media type that matches the new display mode
1477 // since we may no longer be able to draw the current image type efficiently
1479 BOOL CBaseRenderer::OnDisplayChange()
1481 // Ignore if we are not connected yet
1483 CAutoLock cSampleLock(&m_RendererLock);
1484 if (m_pInputPin->IsConnected() == FALSE) {
1485 return FALSE;
1488 RLOG("Notification of EC_DISPLAY_CHANGE");
1490 // Pass our input pin as parameter on the event
1492 IPin *pPin = (IPin *) m_pInputPin;
1493 m_pInputPin->AddRef();
1494 NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0);
1495 SetAbortSignal(TRUE);
1496 ClearPendingSample();
1497 m_pInputPin->Release();
1499 return TRUE;
1503 // Called just before we start drawing.
1504 // Store the current time in m_trRenderStart to allow the rendering time to be
1505 // logged. Log the time stamp of the sample and how late it is (neg is early)
1507 void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
1509 #ifdef PERF
1510 REFERENCE_TIME trStart, trEnd;
1511 pMediaSample->GetTime(&trStart, &trEnd);
1513 MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
1515 m_pClock->GetTime(&m_trRenderStart);
1516 MSR_INTEGER(0, (int)m_trRenderStart);
1517 REFERENCE_TIME trStream;
1518 trStream = m_trRenderStart-m_tStart; // convert reftime to stream time
1519 MSR_INTEGER(0,(int)trStream);
1521 const int trLate = (int)(trStream - trStart);
1522 MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec
1523 #endif
1525 } // OnRenderStart
1528 // Called directly after drawing an image.
1529 // calculate the time spent drawing and log it.
1531 void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
1533 #ifdef PERF
1534 REFERENCE_TIME trNow;
1535 m_pClock->GetTime(&trNow);
1536 MSR_INTEGER(0,(int)trNow);
1537 int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec
1538 MSR_INTEGER(m_idBaseRenderTime, t);
1539 #endif
1540 } // OnRenderEnd
1545 // Constructor must be passed the base renderer object
1547 CRendererInputPin::CRendererInputPin(CBaseRenderer *pRenderer,
1548 HRESULT *phr,
1549 LPCWSTR pPinName) :
1550 CBaseInputPin(NAME("Renderer pin"),
1551 pRenderer,
1552 &pRenderer->m_InterfaceLock,
1553 (HRESULT *) phr,
1554 pPinName)
1556 m_pRenderer = pRenderer;
1557 ASSERT(m_pRenderer);
1561 // Signals end of data stream on the input pin
1563 STDMETHODIMP CRendererInputPin::EndOfStream()
1565 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1566 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1568 // Make sure we're streaming ok
1570 HRESULT hr = CheckStreaming();
1571 if (hr != NOERROR) {
1572 return hr;
1575 // Pass it onto the renderer
1577 hr = m_pRenderer->EndOfStream();
1578 if (SUCCEEDED(hr)) {
1579 hr = CBaseInputPin::EndOfStream();
1581 return hr;
1585 // Signals start of flushing on the input pin - we do the final reset end of
1586 // stream with the renderer lock unlocked but with the interface lock locked
1587 // We must do this because we call timeKillEvent, our timer callback method
1588 // has to take the renderer lock to serialise our state. Therefore holding a
1589 // renderer lock when calling timeKillEvent could cause a deadlock condition
1591 STDMETHODIMP CRendererInputPin::BeginFlush()
1593 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1595 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1596 CBaseInputPin::BeginFlush();
1597 m_pRenderer->BeginFlush();
1599 return m_pRenderer->ResetEndOfStream();
1603 // Signals end of flushing on the input pin
1605 STDMETHODIMP CRendererInputPin::EndFlush()
1607 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1608 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1610 HRESULT hr = m_pRenderer->EndFlush();
1611 if (SUCCEEDED(hr)) {
1612 hr = CBaseInputPin::EndFlush();
1614 return hr;
1618 // Pass the sample straight through to the renderer object
1620 STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample)
1622 HRESULT hr = m_pRenderer->Receive(pSample);
1623 if (FAILED(hr)) {
1625 // A deadlock could occur if the caller holds the renderer lock and
1626 // attempts to acquire the interface lock.
1627 ASSERT(CritCheckOut(&m_pRenderer->m_RendererLock));
1630 // The interface lock must be held when the filter is calling
1631 // IsStopped() or IsFlushing(). The interface lock must also
1632 // be held because the function uses m_bRunTimeError.
1633 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1635 // We do not report errors which occur while the filter is stopping,
1636 // flushing or if the m_bAbort flag is set . Errors are expected to
1637 // occur during these operations and the streaming thread correctly
1638 // handles the errors.
1639 if (!IsStopped() && !IsFlushing() && !m_pRenderer->m_bAbort && !m_bRunTimeError) {
1641 // EC_ERRORABORT's first parameter is the error which caused
1642 // the event and its' last parameter is 0. See the Direct
1643 // Show SDK documentation for more information.
1644 m_pRenderer->NotifyEvent(EC_ERRORABORT,hr,0);
1647 CAutoLock alRendererLock(&m_pRenderer->m_RendererLock);
1648 if (m_pRenderer->IsStreaming() && !m_pRenderer->IsEndOfStreamDelivered()) {
1649 m_pRenderer->NotifyEndOfStream();
1653 m_bRunTimeError = TRUE;
1658 return hr;
1662 // Called when the input pin is disconnected
1664 HRESULT CRendererInputPin::BreakConnect()
1666 HRESULT hr = m_pRenderer->BreakConnect();
1667 if (FAILED(hr)) {
1668 return hr;
1670 return CBaseInputPin::BreakConnect();
1674 // Called when the input pin is connected
1676 HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin)
1678 HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
1679 if (FAILED(hr)) {
1680 return hr;
1682 return CBaseInputPin::CompleteConnect(pReceivePin);
1686 // Give the pin id of our one and only pin
1688 STDMETHODIMP CRendererInputPin::QueryId(LPWSTR *Id)
1690 CheckPointer(Id,E_POINTER);
1692 *Id = (LPWSTR)CoTaskMemAlloc(8);
1693 if (*Id == NULL) {
1694 return E_OUTOFMEMORY;
1696 lstrcpyW(*Id, L"In");
1697 return NOERROR;
1701 // Will the filter accept this media type
1703 HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
1705 return m_pRenderer->CheckMediaType(pmt);
1709 // Called when we go paused or running
1711 HRESULT CRendererInputPin::Active()
1713 return m_pRenderer->Active();
1717 // Called when we go into a stopped state
1719 HRESULT CRendererInputPin::Inactive()
1721 // The caller must hold the interface lock because
1722 // this function uses m_bRunTimeError.
1723 ASSERT(CritCheckIn(&m_pRenderer->m_InterfaceLock));
1725 m_bRunTimeError = FALSE;
1727 return m_pRenderer->Inactive();
1731 // Tell derived classes about the media type agreed
1733 HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt)
1735 HRESULT hr = CBaseInputPin::SetMediaType(pmt);
1736 if (FAILED(hr)) {
1737 return hr;
1739 return m_pRenderer->SetMediaType(pmt);
1743 // We do not keep an event object to use when setting up a timer link with
1744 // the clock but are given a pointer to one by the owning object through the
1745 // SetNotificationObject method - this must be initialised before starting
1746 // We can override the default quality management process to have it always
1747 // draw late frames, this is currently done by having the following registry
1748 // key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
1750 const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
1751 const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");
1753 CBaseVideoRenderer::CBaseVideoRenderer(
1754 REFCLSID RenderClass, // CLSID for this renderer
1755 TCHAR *pName, // Debug ONLY description
1756 LPUNKNOWN pUnk, // Aggregated owner object
1757 HRESULT *phr) : // General OLE return code
1759 CBaseRenderer(RenderClass,pName,pUnk,phr),
1760 m_cFramesDropped(0),
1761 m_cFramesDrawn(0),
1762 m_bSupplierHandlingQuality(FALSE)
1764 ResetStreamingTimes();
1766 #ifdef PERF
1767 m_idTimeStamp = MSR_REGISTER(TEXT("Frame time stamp"));
1768 m_idEarliness = MSR_REGISTER(TEXT("Earliness fudge"));
1769 m_idTarget = MSR_REGISTER(TEXT("Target (mSec)"));
1770 m_idSchLateTime = MSR_REGISTER(TEXT("mSec late when scheduled"));
1771 m_idDecision = MSR_REGISTER(TEXT("Scheduler decision code"));
1772 m_idQualityRate = MSR_REGISTER(TEXT("Quality rate sent"));
1773 m_idQualityTime = MSR_REGISTER(TEXT("Quality time sent"));
1774 m_idWaitReal = MSR_REGISTER(TEXT("Render wait"));
1775 // m_idWait = MSR_REGISTER(TEXT("wait time recorded (msec)"));
1776 m_idFrameAccuracy = MSR_REGISTER(TEXT("Frame accuracy (msecs)"));
1777 m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
1778 //m_idSendQuality = MSR_REGISTER(TEXT("Processing Quality message"));
1780 m_idRenderAvg = MSR_REGISTER(TEXT("Render draw time Avg"));
1781 m_idFrameAvg = MSR_REGISTER(TEXT("FrameAvg"));
1782 m_idWaitAvg = MSR_REGISTER(TEXT("WaitAvg"));
1783 m_idDuration = MSR_REGISTER(TEXT("Duration"));
1784 m_idThrottle = MSR_REGISTER(TEXT("Audio-video throttle wait"));
1785 // m_idDebug = MSR_REGISTER(TEXT("Debug stuff"));
1786 #endif // PERF
1787 } // Constructor
1790 // Destructor is just a placeholder
1792 CBaseVideoRenderer::~CBaseVideoRenderer()
1794 ASSERT(m_dwAdvise == 0);
1798 // The timing functions in this class are called by the window object and by
1799 // the renderer's allocator.
1800 // The windows object calls timing functions as it receives media sample
1801 // images for drawing using GDI.
1802 // The allocator calls timing functions when it starts passing DCI/DirectDraw
1803 // surfaces which are not rendered in the same way; The decompressor writes
1804 // directly to the surface with no separate rendering, so those code paths
1805 // call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
1806 // when we have allocated one and only one image we know there cannot be any
1807 // conflict between the two.
1809 // We use timeGetTime to return the timing counts we use (since it's relative
1810 // performance we are interested in rather than absolute compared to a clock)
1811 // The window object sets the accuracy of the system clock (normally 1ms) by
1812 // calling timeBeginPeriod/timeEndPeriod when it changes streaming states
1815 // Reset all times controlling streaming.
1816 // Set them so that
1817 // 1. Frames will not initially be dropped
1818 // 2. The first frame will definitely be drawn (achieved by saying that there
1819 // has not ben a frame drawn for a long time).
1821 HRESULT CBaseVideoRenderer::ResetStreamingTimes()
1823 m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago
1824 m_tStreamingStart = timeGetTime();
1825 m_trRenderAvg = 0;
1826 m_trFrameAvg = -1; // -1000 fps == "unset"
1827 m_trDuration = 0; // 0 - strange value
1828 m_trRenderLast = 0;
1829 m_trWaitAvg = 0;
1830 m_tRenderStart = 0;
1831 m_cFramesDrawn = 0;
1832 m_cFramesDropped = 0;
1833 m_iTotAcc = 0;
1834 m_iSumSqAcc = 0;
1835 m_iSumSqFrameTime = 0;
1836 m_trFrame = 0; // hygiene - not really needed
1837 m_trLate = 0; // hygiene - not really needed
1838 m_iSumFrameTime = 0;
1839 m_nNormal = 0;
1840 m_trEarliness = 0;
1841 m_trTarget = -300000; // 30mSec early
1842 m_trThrottle = 0;
1843 m_trRememberStampForPerf = 0;
1845 #ifdef PERF
1846 m_trRememberFrameForPerf = 0;
1847 #endif
1849 return NOERROR;
1850 } // ResetStreamingTimes
1853 // Reset all times controlling streaming. Note that we're now streaming. We
1854 // don't need to set the rendering event to have the source filter released
1855 // as it is done during the Run processing. When we are run we immediately
1856 // release the source filter thread and draw any image waiting (that image
1857 // may already have been drawn once as a poster frame while we were paused)
1859 HRESULT CBaseVideoRenderer::OnStartStreaming()
1861 ResetStreamingTimes();
1862 return NOERROR;
1863 } // OnStartStreaming
1866 // Called at end of streaming. Fixes times for property page report
1868 HRESULT CBaseVideoRenderer::OnStopStreaming()
1870 m_tStreamingStart = timeGetTime()-m_tStreamingStart;
1871 return NOERROR;
1872 } // OnStopStreaming
1875 // Called when we start waiting for a rendering event.
1876 // Used to update times spent waiting and not waiting.
1878 void CBaseVideoRenderer::OnWaitStart()
1880 MSR_START(m_idWaitReal);
1881 } // OnWaitStart
1884 // Called when we are awoken from the wait in the window OR by our allocator
1885 // when it is hanging around until the next sample is due for rendering on a
1886 // DCI/DirectDraw surface. We add the wait time into our rolling average.
1887 // We grab the interface lock so that we're serialised with the application
1888 // thread going through the run code - which in due course ends up calling
1889 // ResetStreaming times - possibly as we run through this section of code
1891 void CBaseVideoRenderer::OnWaitEnd()
1893 #ifdef PERF
1894 MSR_STOP(m_idWaitReal);
1895 // for a perf build we want to know just exactly how late we REALLY are.
1896 // even if this means that we have to look at the clock again.
1898 REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
1899 #if 0
1900 m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
1901 #else
1902 // We will be discarding overflows like mad here!
1903 // This is wrong really because timeGetTime() can wrap but it's
1904 // only for PERF
1905 REFERENCE_TIME tr = timeGetTime()*10000;
1906 trRealStream = tr + m_llTimeOffset;
1907 #endif
1908 trRealStream -= m_tStart; // convert to stream time (this is a reftime)
1910 if (m_trRememberStampForPerf==0) {
1911 // This is probably the poster frame at the start, and it is not scheduled
1912 // in the usual way at all. Just count it. The rememberstamp gets set
1913 // in ShouldDrawSampleNow, so this does invalid frame recording until we
1914 // actually start playing.
1915 PreparePerformanceData(0, 0);
1916 } else {
1917 int trLate = (int)(trRealStream - m_trRememberStampForPerf);
1918 int trFrame = (int)(tr - m_trRememberFrameForPerf);
1919 PreparePerformanceData(trLate, trFrame);
1921 m_trRememberFrameForPerf = tr;
1922 #endif //PERF
1923 } // OnWaitEnd
1926 // Put data on one side that describes the lateness of the current frame.
1927 // We don't yet know whether it will actually be drawn. In direct draw mode,
1928 // this decision is up to the filter upstream, and it could change its mind.
1929 // The rules say that if it did draw it must call Receive(). One way or
1930 // another we eventually get into either OnRenderStart or OnDirectRender and
1931 // these both call RecordFrameLateness to update the statistics.
1933 void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame)
1935 m_trLate = trLate;
1936 m_trFrame = trFrame;
1937 } // PreparePerformanceData
1940 // update the statistics:
1941 // m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
1942 // Note that because the properties page reports using these variables,
1943 // 1. We need to be inside a critical section
1944 // 2. They must all be updated together. Updating the sums here and the count
1945 // elsewhere can result in imaginary jitter (i.e. attempts to find square roots
1946 // of negative numbers) in the property page code.
1948 void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame)
1950 // Record how timely we are.
1951 int tLate = trLate/10000;
1953 // Best estimate of moment of appearing on the screen is average of
1954 // start and end draw times. Here we have only the end time. This may
1955 // tend to show us as spuriously late by up to 1/2 frame rate achieved.
1956 // Decoder probably monitors draw time. We don't bother.
1957 MSR_INTEGER( m_idFrameAccuracy, tLate );
1959 // This is a kludge - we can get frames that are very late
1960 // especially (at start-up) and they invalidate the statistics.
1961 // So ignore things that are more than 1 sec off.
1962 if (tLate>1000 || tLate<-1000) {
1963 if (m_cFramesDrawn<=1) {
1964 tLate = 0;
1965 } else if (tLate>0) {
1966 tLate = 1000;
1967 } else {
1968 tLate = -1000;
1971 // The very first frame often has a invalid time, so don't
1972 // count it into the statistics. (???)
1973 if (m_cFramesDrawn>1) {
1974 m_iTotAcc += tLate;
1975 m_iSumSqAcc += (tLate*tLate);
1978 // calculate inter-frame time. Doesn't make sense for first frame
1979 // second frame suffers from invalid first frame stamp.
1980 if (m_cFramesDrawn>2) {
1981 int tFrame = trFrame/10000; // convert to mSec else it overflows
1983 // This is a kludge. It can overflow anyway (a pause can cause
1984 // a very long inter-frame time) and it overflows at 2**31/10**7
1985 // or about 215 seconds i.e. 3min 35sec
1986 if (tFrame>1000||tFrame<0) tFrame = 1000;
1987 m_iSumSqFrameTime += tFrame*tFrame;
1988 ASSERT(m_iSumSqFrameTime>=0);
1989 m_iSumFrameTime += tFrame;
1991 ++m_cFramesDrawn;
1993 } // RecordFrameLateness
1996 void CBaseVideoRenderer::ThrottleWait()
1998 if (m_trThrottle>0) {
1999 int iThrottle = m_trThrottle/10000; // convert to mSec
2000 MSR_INTEGER( m_idThrottle, iThrottle);
2001 DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle));
2002 Sleep(iThrottle);
2003 } else {
2004 Sleep(0);
2006 } // ThrottleWait
2009 // Whenever a frame is rendered it goes though either OnRenderStart
2010 // or OnDirectRender. Data that are generated during ShouldDrawSample
2011 // are added to the statistics by calling RecordFrameLateness from both
2012 // these two places.
2014 // Called in place of OnRenderStart..OnRenderEnd
2015 // When a DirectDraw image is drawn
2016 void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample)
2018 m_trRenderAvg = 0;
2019 m_trRenderLast = 5000000; // If we mode switch, we do NOT want this
2020 // to inhibit the new average getting going!
2021 // so we set it to half a second
2022 // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
2023 RecordFrameLateness(m_trLate, m_trFrame);
2024 ThrottleWait();
2025 } // OnDirectRender
2028 // Called just before we start drawing. All we do is to get the current clock
2029 // time (from the system) and return. We have to store the start render time
2030 // in a member variable because it isn't used until we complete the drawing
2031 // The rest is just performance logging.
2033 void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample)
2035 RecordFrameLateness(m_trLate, m_trFrame);
2036 m_tRenderStart = timeGetTime();
2037 } // OnRenderStart
2040 // Called directly after drawing an image. We calculate the time spent in the
2041 // drawing code and if this doesn't appear to have any odd looking spikes in
2042 // it then we add it to the current average draw time. Measurement spikes may
2043 // occur if the drawing thread is interrupted and switched to somewhere else.
2045 void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample)
2047 // The renderer time can vary erratically if we are interrupted so we do
2048 // some smoothing to help get more sensible figures out but even that is
2049 // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
2051 int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS
2052 if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
2053 // DO_MOVING_AVG(m_trRenderAvg, tr);
2054 m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
2056 m_trRenderLast = tr;
2057 ThrottleWait();
2058 } // OnRenderEnd
2061 STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc)
2064 m_pQSink = piqc;
2066 return NOERROR;
2067 } // SetSink
2070 STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, Quality q)
2072 // NOTE: We are NOT getting any locks here. We could be called
2073 // asynchronously and possibly even on a time critical thread of
2074 // someone else's - so we do the minumum. We only set one state
2075 // variable (an integer) and if that happens to be in the middle
2076 // of another thread reading it they will just get either the new
2077 // or the old value. Locking would achieve no more than this.
2079 // It might be nice to check that we are being called from m_pGraph, but
2080 // it turns out to be a millisecond or so per throw!
2082 // This is heuristics, these numbers are aimed at being "what works"
2083 // rather than anything based on some theory.
2084 // We use a hyperbola because it's easy to calculate and it includes
2085 // a panic button asymptote (which we push off just to the left)
2086 // The throttling fits the following table (roughly)
2087 // Proportion Throttle (msec)
2088 // >=1000 0
2089 // 900 3
2090 // 800 7
2091 // 700 11
2092 // 600 17
2093 // 500 25
2094 // 400 35
2095 // 300 50
2096 // 200 72
2097 // 125 100
2098 // 100 112
2099 // 50 146
2100 // 0 200
2102 // (some evidence that we could go for a sharper kink - e.g. no throttling
2103 // until below the 750 mark - might give fractionally more frames on a
2104 // P60-ish machine). The easy way to get these coefficients is to use
2105 // Renbase.xls follow the instructions therein using excel solver.
2107 if (q.Proportion>=1000) { m_trThrottle = 0; }
2108 else {
2109 // The DWORD is to make quite sure I get unsigned arithmetic
2110 // as the constant is between 2**31 and 2**32
2111 m_trThrottle = -330000 + (388880000/(q.Proportion+167));
2113 return NOERROR;
2114 } // Notify
2117 // Send a message to indicate what our supplier should do about quality.
2118 // Theory:
2119 // What a supplier wants to know is "is the frame I'm working on NOW
2120 // going to be late?".
2121 // F1 is the frame at the supplier (as above)
2122 // Tf1 is the due time for F1
2123 // T1 is the time at that point (NOW!)
2124 // Tr1 is the time that f1 WILL actually be rendered
2125 // L1 is the latency of the graph for frame F1 = Tr1-T1
2126 // D1 (for delay) is how late F1 will be beyond its due time i.e.
2127 // D1 = (Tr1-Tf1) which is what the supplier really wants to know.
2128 // Unfortunately Tr1 is in the future and is unknown, so is L1
2130 // We could estimate L1 by its value for a previous frame,
2131 // L0 = Tr0-T0 and work off
2132 // D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
2133 // Rearranging terms:
2134 // D1' = (T1-T0) + (Tr0-Tf1)
2135 // adding (Tf0-Tf0) and rearranging again:
2136 // = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
2137 // = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
2138 // But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
2139 // Late field in the quality message that we send.
2140 // The other two terms just state what correction should be applied before
2141 // using the lateness of F0 to predict the lateness of F1.
2142 // (T1-T0) says how much time has actually passed (we have lost this much)
2143 // (Tf1-Tf0) says how much time should have passed if we were keeping pace
2144 // (we have gained this much).
2146 // Suppliers should therefore work off:
2147 // Quality.Late + (T1-T0) - (Tf1-Tf0)
2148 // and see if this is "acceptably late" or even early (i.e. negative).
2149 // They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
2150 // the time stamps in the frames. They get Quality.Late from us.
2153 HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate,
2154 REFERENCE_TIME trRealStream)
2156 Quality q;
2157 HRESULT hr;
2159 // If we are the main user of time, then report this as Flood/Dry.
2160 // If our suppliers are, then report it as Famine/Glut.
2162 // We need to take action, but avoid hunting. Hunting is caused by
2163 // 1. Taking too much action too soon and overshooting
2164 // 2. Taking too long to react (so averaging can CAUSE hunting).
2166 // The reason why we use trLate as well as Wait is to reduce hunting;
2167 // if the wait time is coming down and about to go into the red, we do
2168 // NOT want to rely on some average which is only telling is that it used
2169 // to be OK once.
2171 q.TimeStamp = (REFERENCE_TIME)trRealStream;
2173 if (m_trFrameAvg<0) {
2174 q.Type = Famine; // guess
2176 // Is the greater part of the time taken bltting or something else
2177 else if (m_trFrameAvg > 2*m_trRenderAvg) {
2178 q.Type = Famine; // mainly other
2179 } else {
2180 q.Type = Flood; // mainly bltting
2183 q.Proportion = 1000; // default
2185 if (m_trFrameAvg<0) {
2186 // leave it alone - we don't know enough
2188 else if ( trLate> 0 ) {
2189 // try to catch up over the next second
2190 // We could be Really, REALLY late, but rendering all the frames
2191 // anyway, just because it's so cheap.
2193 q.Proportion = 1000 - (int)((trLate)/(UNITS/1000));
2194 if (q.Proportion<500) {
2195 q.Proportion = 500; // don't go daft. (could've been negative!)
2196 } else {
2199 } else if ( m_trWaitAvg>20000
2200 && trLate<-20000
2202 // Go cautiously faster - aim at 2mSec wait.
2203 if (m_trWaitAvg>=m_trFrameAvg) {
2204 // This can happen because of some fudges.
2205 // The waitAvg is how long we originally planned to wait
2206 // The frameAvg is more honest.
2207 // It means that we are spending a LOT of time waiting
2208 q.Proportion = 2000; // double.
2209 } else {
2210 if (m_trFrameAvg+20000 > m_trWaitAvg) {
2211 q.Proportion
2212 = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
2213 } else {
2214 // We're apparently spending more than the whole frame time waiting.
2215 // Assume that the averages are slightly out of kilter, but that we
2216 // are indeed doing a lot of waiting. (This leg probably never
2217 // happens, but the code avoids any potential divide by zero).
2218 q.Proportion = 2000;
2222 if (q.Proportion>2000) {
2223 q.Proportion = 2000; // don't go crazy.
2227 // Tell the supplier how late frames are when they get rendered
2228 // That's how late we are now.
2229 // If we are in directdraw mode then the guy upstream can see the drawing
2230 // times and we'll just report on the start time. He can figure out any
2231 // offset to apply. If we are in DIB Section mode then we will apply an
2232 // extra offset which is half of our drawing time. This is usually small
2233 // but can sometimes be the dominant effect. For this we will use the
2234 // average drawing time rather than the last frame. If the last frame took
2235 // a long time to draw and made us late, that's already in the lateness
2236 // figure. We should not add it in again unless we expect the next frame
2237 // to be the same. We don't, we expect the average to be a better shot.
2238 // In direct draw mode the RenderAvg will be zero.
2240 q.Late = trLate + m_trRenderAvg/2;
2242 // log what we're doing
2243 MSR_INTEGER(m_idQualityRate, q.Proportion);
2244 MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );
2246 // A specific sink interface may be set through IPin
2248 if (m_pQSink==NULL) {
2249 // Get our input pin's peer. We send quality management messages
2250 // to any nominated receiver of these things (set in the IPin
2251 // interface), or else to our source filter.
2253 IQualityControl *pQC = NULL;
2254 IPin *pOutputPin = m_pInputPin->GetConnected();
2255 ASSERT(pOutputPin != NULL);
2257 // And get an AddRef'd quality control interface
2259 hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC);
2260 if (SUCCEEDED(hr)) {
2261 m_pQSink = pQC;
2264 if (m_pQSink) {
2265 return m_pQSink->Notify(this,q);
2268 return S_FALSE;
2270 } // SendQuality
2273 // We are called with a valid IMediaSample image to decide whether this is to
2274 // be drawn or not. There must be a reference clock in operation.
2275 // Return S_OK if it is to be drawn Now (as soon as possible)
2276 // Return S_FALSE if it is to be drawn when it's due
2277 // Return an error if we want to drop it
2278 // m_nNormal=-1 indicates that we dropped the previous frame and so this
2279 // one should be drawn early. Respect it and update it.
2280 // Use current stream time plus a number of heuristics (detailed below)
2281 // to make the decision
2283 HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
2284 REFERENCE_TIME *ptrStart,
2285 REFERENCE_TIME *ptrEnd)
2288 // Don't call us unless there's a clock interface to synchronise with
2289 ASSERT(m_pClock);
2291 MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits
2292 MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits
2294 // We lose a bit of time depending on the monitor type waiting for the next
2295 // screen refresh. On average this might be about 8mSec - so it will be
2296 // later than we think when the picture appears. To compensate a bit
2297 // we bias the media samples by -8mSec i.e. 80000 UNITs.
2298 // We don't ever make a stream time negative (call it paranoia)
2299 if (*ptrStart>=80000) {
2300 *ptrStart -= 80000;
2301 *ptrEnd -= 80000; // bias stop to to retain valid frame duration
2304 // Cache the time stamp now. We will want to compare what we did with what
2305 // we started with (after making the monitor allowance).
2306 m_trRememberStampForPerf = *ptrStart;
2308 // Get reference times (current and late)
2309 REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
2310 m_pClock->GetTime(&trRealStream);
2311 #ifdef PERF
2312 // While the reference clock is expensive:
2313 // Remember the offset from timeGetTime and use that.
2314 // This overflows all over the place, but when we subtract to get
2315 // differences the overflows all cancel out.
2316 m_llTimeOffset = trRealStream-timeGetTime()*10000;
2317 #endif
2318 trRealStream -= m_tStart; // convert to stream time (this is a reftime)
2320 // We have to wory about two versions of "lateness". The truth, which we
2321 // try to work out here and the one measured against m_trTarget which
2322 // includes long term feedback. We report statistics against the truth
2323 // but for operational decisions we work to the target.
2324 // We use TimeDiff to make sure we get an integer because we
2325 // may actually be late (or more likely early if there is a big time
2326 // gap) by a very long time.
2327 const int trTrueLate = TimeDiff(trRealStream - *ptrStart);
2328 const int trLate = trTrueLate;
2330 MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);
2332 // Send quality control messages upstream, measured against target
2333 HRESULT hr = SendQuality(trLate, trRealStream);
2334 // Note: the filter upstream is allowed to this FAIL meaning "you do it".
2335 m_bSupplierHandlingQuality = (hr==S_OK);
2337 // Decision time! Do we drop, draw when ready or draw immediately?
2339 const int trDuration = (int)(*ptrEnd - *ptrStart);
2341 // We need to see if the frame rate of the file has just changed.
2342 // This would make comparing our previous frame rate with the current
2343 // frame rate inefficent. Hang on a moment though. I've seen files
2344 // where the frames vary between 33 and 34 mSec so as to average
2345 // 30fps. A minor variation like that won't hurt us.
2346 int t = m_trDuration/32;
2347 if ( trDuration > m_trDuration+t
2348 || trDuration < m_trDuration-t
2350 // There's a major variation. Reset the average frame rate to
2351 // exactly the current rate to disable decision 9002 for this frame,
2352 // and remember the new rate.
2353 m_trFrameAvg = trDuration;
2354 m_trDuration = trDuration;
2358 MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
2359 MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
2360 MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
2361 MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
2362 MSR_INTEGER(m_idDuration, trDuration/10000);
2364 #ifdef PERF
2365 if (S_OK==pMediaSample->IsDiscontinuity()) {
2366 MSR_INTEGER(m_idDecision, 9000);
2368 #endif
2370 // Control the graceful slide back from slow to fast machine mode.
2371 // After a frame drop accept an early frame and set the earliness to here
2372 // If this frame is already later than the earliness then slide it to here
2373 // otherwise do the standard slide (reduce by about 12% per frame).
2374 // Note: earliness is normally NEGATIVE
2375 BOOL bJustDroppedFrame
2376 = ( m_bSupplierHandlingQuality
2377 // Can't use the pin sample properties because we might
2378 // not be in Receive when we call this
2379 && (S_OK == pMediaSample->IsDiscontinuity()) // he just dropped one
2381 || (m_nNormal==-1); // we just dropped one
2384 // Set m_trEarliness (slide back from slow to fast machine mode)
2385 if (trLate>0) {
2386 m_trEarliness = 0; // we are no longer in fast machine mode at all!
2387 } else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) {
2388 m_trEarliness = trLate; // Things have slipped of their own accord
2389 } else {
2390 m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide
2393 // prepare the new wait average - but don't pollute the old one until
2394 // we have finished with it.
2395 int trWaitAvg;
2397 // We never mix in a negative wait. This causes us to believe in fast machines
2398 // slightly more.
2399 int trL = trLate<0 ? -trLate : 0;
2400 trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
2404 int trFrame;
2406 REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
2407 if (tr>10000000) {
2408 tr = 10000000; // 1 second - arbitrarily.
2410 trFrame = int(tr);
2413 // We will DRAW this frame IF...
2414 if (
2415 // ...the time we are spending drawing is a small fraction of the total
2416 // observed inter-frame time so that dropping it won't help much.
2417 (3*m_trRenderAvg <= m_trFrameAvg)
2419 // ...or our supplier is NOT handling things and the next frame would
2420 // be less timely than this one or our supplier CLAIMS to be handling
2421 // things, and is now less than a full FOUR frames late.
2422 || ( m_bSupplierHandlingQuality
2423 ? (trLate <= trDuration*4)
2424 : (trLate+trLate < trDuration)
2427 // ...or we are on average waiting for over eight milliseconds then
2428 // this may be just a glitch. Draw it and we'll hope to catch up.
2429 || (m_trWaitAvg > 80000)
2431 // ...or we haven't drawn an image for over a second. We will update
2432 // the display, which stops the video looking hung.
2433 // Do this regardless of how late this media sample is.
2434 || ((trRealStream - m_trLastDraw) > UNITS)
2437 HRESULT Result;
2439 // We are going to play this frame. We may want to play it early.
2440 // We will play it early if we think we are in slow machine mode.
2441 // If we think we are NOT in slow machine mode, we will still play
2442 // it early by m_trEarliness as this controls the graceful slide back.
2443 // and in addition we aim at being m_trTarget late rather than "on time".
2445 BOOL bPlayASAP = FALSE;
2447 // we will play it AT ONCE (slow machine mode) if...
2449 // ...we are playing catch-up
2450 if ( bJustDroppedFrame) {
2451 bPlayASAP = TRUE;
2452 MSR_INTEGER(m_idDecision, 9001);
2455 // ...or if we are running below the true frame rate
2456 // exact comparisons are glitchy, for these measurements,
2457 // so add an extra 5% or so
2458 else if ( (m_trFrameAvg > trDuration + trDuration/16)
2460 // It's possible to get into a state where we are losing ground, but
2461 // are a very long way ahead. To avoid this or recover from it
2462 // we refuse to play early by more than 10 frames.
2463 && (trLate > - trDuration*10)
2465 bPlayASAP = TRUE;
2466 MSR_INTEGER(m_idDecision, 9002);
2468 #if 0
2469 // ...or if we have been late and are less than one frame early
2470 else if ( (trLate + trDuration > 0)
2471 && (m_trWaitAvg<=20000)
2473 bPlayASAP = TRUE;
2474 MSR_INTEGER(m_idDecision, 9003);
2476 #endif
2477 // We will NOT play it at once if we are grossly early. On very slow frame
2478 // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
2479 // because we got starved (for instance by the net) and dropped one frame
2480 // some time or other. If we are more than 900mSec early, then wait.
2481 if (trLate<-9000000) {
2482 bPlayASAP = FALSE;
2485 if (bPlayASAP) {
2487 m_nNormal = 0;
2488 MSR_INTEGER(m_idDecision, 0);
2489 // When we are here, we are in slow-machine mode. trLate may well
2490 // oscillate between negative and positive when the supplier is
2491 // dropping frames to keep sync. We should not let that mislead
2492 // us into thinking that we have as much as zero spare time!
2493 // We just update with a zero wait.
2494 m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
2496 // Assume that we draw it immediately. Update inter-frame stats
2497 m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
2498 #ifndef PERF
2499 // If this is NOT a perf build, then report what we know so far
2500 // without looking at the clock any more. This assumes that we
2501 // actually wait for exactly the time we hope to. It also reports
2502 // how close we get to the manipulated time stamps that we now have
2503 // rather than the ones we originally started with. It will
2504 // therefore be a little optimistic. However it's fast.
2505 PreparePerformanceData(trTrueLate, trFrame);
2506 #endif
2507 m_trLastDraw = trRealStream;
2508 if (m_trEarliness > trLate) {
2509 m_trEarliness = trLate; // if we are actually early, this is neg
2511 Result = S_OK; // Draw it now
2513 } else {
2514 ++m_nNormal;
2515 // Set the average frame rate to EXACTLY the ideal rate.
2516 // If we are exiting slow-machine mode then we will have caught up
2517 // and be running ahead, so as we slide back to exact timing we will
2518 // have a longer than usual gap at this point. If we record this
2519 // real gap then we'll think that we're running slow and go back
2520 // into slow-machine mode and vever get it straight.
2521 m_trFrameAvg = trDuration;
2522 MSR_INTEGER(m_idDecision, 1);
2524 // Play it early by m_trEarliness and by m_trTarget
2527 int trE = m_trEarliness;
2528 if (trE < -m_trFrameAvg) {
2529 trE = -m_trFrameAvg;
2531 *ptrStart += trE; // N.B. earliness is negative
2534 int Delay = -trTrueLate;
2535 Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait
2537 m_trWaitAvg = trWaitAvg;
2539 // Predict when it will actually be drawn and update frame stats
2541 if (Result==S_FALSE) { // We are going to wait
2542 trFrame = TimeDiff(*ptrStart-m_trLastDraw);
2543 m_trLastDraw = *ptrStart;
2544 } else {
2545 // trFrame is already = trRealStream-m_trLastDraw;
2546 m_trLastDraw = trRealStream;
2548 #ifndef PERF
2549 int iAccuracy;
2550 if (Delay>0) {
2551 // Report lateness based on when we intend to play it
2552 iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf);
2553 } else {
2554 // Report lateness based on playing it *now*.
2555 iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf;
2557 PreparePerformanceData(iAccuracy, trFrame);
2558 #endif
2560 return Result;
2563 // We are going to drop this frame!
2564 // Of course in DirectDraw mode the guy upstream may draw it anyway.
2566 // This will probably give a large negative wack to the wait avg.
2567 m_trWaitAvg = trWaitAvg;
2569 #ifdef PERF
2570 // Respect registry setting - debug only!
2571 if (m_bDrawLateFrames) {
2572 return S_OK; // draw it when it's ready
2573 } // even though it's late.
2574 #endif
2576 // We are going to drop this frame so draw the next one early
2577 // n.b. if the supplier is doing direct draw then he may draw it anyway
2578 // but he's doing something funny to arrive here in that case.
2580 MSR_INTEGER(m_idDecision, 2);
2581 m_nNormal = -1;
2582 return E_FAIL; // drop it
2584 } // ShouldDrawSampleNow
2587 // NOTE we're called by both the window thread and the source filter thread
2588 // so we have to be protected by a critical section (locked before called)
2589 // Also, when the window thread gets signalled to render an image, it always
2590 // does so regardless of how late it is. All the degradation is done when we
2591 // are scheduling the next sample to be drawn. Hence when we start an advise
2592 // link to draw a sample, that sample's time will always become the last one
2593 // drawn - unless of course we stop streaming in which case we cancel links
2595 BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample)
2597 // We override ShouldDrawSampleNow to add quality management
2599 BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample);
2600 if (bDrawImage == FALSE) {
2601 ++m_cFramesDropped;
2602 return FALSE;
2605 // m_cFramesDrawn must NOT be updated here. It has to be updated
2606 // in RecordFrameLateness at the same time as the other statistics.
2607 return TRUE;
2611 // Implementation of IQualProp interface needed to support the property page
2612 // This is how the property page gets the data out of the scheduler. We are
2613 // passed into the constructor the owning object in the COM sense, this will
2614 // either be the video renderer or an external IUnknown if we're aggregated.
2615 // We initialise our CUnknown base class with this interface pointer. Then
2616 // all we have to do is to override NonDelegatingQueryInterface to expose
2617 // our IQualProp interface. The AddRef and Release are handled automatically
2618 // by the base class and will be passed on to the appropriate outer object
2620 STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(int *pcFramesDropped)
2622 CheckPointer(pcFramesDropped,E_POINTER);
2623 CAutoLock cVideoLock(&m_InterfaceLock);
2624 *pcFramesDropped = m_cFramesDropped;
2625 return NOERROR;
2626 } // get_FramesDroppedInRenderer
2629 // Set *pcFramesDrawn to the number of frames drawn since
2630 // streaming started.
2632 STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn)
2634 CheckPointer(pcFramesDrawn,E_POINTER);
2635 CAutoLock cVideoLock(&m_InterfaceLock);
2636 *pcFramesDrawn = m_cFramesDrawn;
2637 return NOERROR;
2638 } // get_FramesDrawn
2641 // Set iAvgFrameRate to the frames per hundred secs since
2642 // streaming started. 0 otherwise.
2644 STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate)
2646 CheckPointer(piAvgFrameRate,E_POINTER);
2647 CAutoLock cVideoLock(&m_InterfaceLock);
2649 int t;
2650 if (m_bStreaming) {
2651 t = timeGetTime()-m_tStreamingStart;
2652 } else {
2653 t = m_tStreamingStart;
2656 if (t<=0) {
2657 *piAvgFrameRate = 0;
2658 ASSERT(m_cFramesDrawn == 0);
2659 } else {
2660 // i is frames per hundred seconds
2661 *piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
2663 return NOERROR;
2664 } // get_AvgFrameRate
2667 // Set *piAvg to the average sync offset since streaming started
2668 // in mSec. The sync offset is the time in mSec between when the frame
2669 // should have been drawn and when the frame was actually drawn.
2671 STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset( int *piAvg)
2673 CheckPointer(piAvg,E_POINTER);
2674 CAutoLock cVideoLock(&m_InterfaceLock);
2676 if (NULL==m_pClock) {
2677 *piAvg = 0;
2678 return NOERROR;
2681 // Note that we didn't gather the stats on the first frame
2682 // so we use m_cFramesDrawn-1 here
2683 if (m_cFramesDrawn<=1) {
2684 *piAvg = 0;
2685 } else {
2686 *piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1));
2688 return NOERROR;
2689 } // get_AvgSyncOffset
2692 // To avoid dragging in the maths library - a cheap
2693 // approximate integer square root.
2694 // We do this by getting a starting guess which is between 1
2695 // and 2 times too large, followed by THREE iterations of
2696 // Newton Raphson. (That will give accuracy to the nearest mSec
2697 // for the range in question - roughly 0..1000)
2699 // It would be faster to use a linear interpolation and ONE NR, but
2700 // who cares. If anyone does - the best linear interpolation is
2701 // to approximates sqrt(x) by
2702 // y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
2703 // 0r y = x*0.41421 + 0.59467
2704 // This minimises the maximal error in the range in question.
2705 // (error is about +0.008883 and then one NR will give error .0000something
2706 // (Of course these are integers, so you can't just multiply by 0.41421
2707 // you'd have to do some sort of MulDiv).
2708 // Anyone wanna check my maths? (This is only for a property display!)
2710 int isqrt(int x)
2712 int s = 1;
2713 // Make s an initial guess for sqrt(x)
2714 if (x > 0x40000000) {
2715 s = 0x8000; // prevent any conceivable closed loop
2716 } else {
2717 while (s*s<x) { // loop cannot possible go more than 31 times
2718 s = 2*s; // normally it goes about 6 times
2720 // Three NR iterations.
2721 if (x==0) {
2722 s= 0; // Wouldn't it be tragic to divide by zero whenever our
2723 // accuracy was perfect!
2724 } else {
2725 s = (s*s+x)/(2*s);
2726 if (s>=0) s = (s*s+x)/(2*s);
2727 if (s>=0) s = (s*s+x)/(2*s);
2730 return s;
2734 // Do estimates for standard deviations for per-frame
2735 // statistics
2737 HRESULT CBaseVideoRenderer::GetStdDev(
2738 int nSamples,
2739 int *piResult,
2740 LONGLONG llSumSq,
2741 LONGLONG iTot
2744 CheckPointer(piResult,E_POINTER);
2745 CAutoLock cVideoLock(&m_InterfaceLock);
2747 if (NULL==m_pClock) {
2748 *piResult = 0;
2749 return NOERROR;
2752 // If S is the Sum of the Squares of observations and
2753 // T the Total (i.e. sum) of the observations and there were
2754 // N observations, then an estimate of the standard deviation is
2755 // sqrt( (S - T**2/N) / (N-1) )
2757 if (nSamples<=1) {
2758 *piResult = 0;
2759 } else {
2760 LONGLONG x;
2761 // First frames have invalid stamps, so we get no stats for them
2762 // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
2764 // so we use m_cFramesDrawn-1 here
2765 x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0);
2766 x = x / (nSamples-1);
2767 ASSERT(x>=0);
2768 *piResult = isqrt((LONG)x);
2770 return NOERROR;
2773 // Set *piDev to the standard deviation in mSec of the sync offset
2774 // of each frame since streaming started.
2776 STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset( int *piDev)
2778 // First frames have invalid stamps, so we get no stats for them
2779 // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
2780 return GetStdDev(m_cFramesDrawn - 1,
2781 piDev,
2782 m_iSumSqAcc,
2783 m_iTotAcc);
2784 } // get_DevSyncOffset
2787 // Set *piJitter to the standard deviation in mSec of the inter-frame time
2788 // of frames since streaming started.
2790 STDMETHODIMP CBaseVideoRenderer::get_Jitter( int *piJitter)
2792 // First frames have invalid stamps, so we get no stats for them
2793 // So second frame gives invalid inter-frame time
2794 // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
2795 return GetStdDev(m_cFramesDrawn - 2,
2796 piJitter,
2797 m_iSumSqFrameTime,
2798 m_iSumFrameTime);
2799 } // get_Jitter
2802 // Overidden to return our IQualProp interface
2804 STDMETHODIMP
2805 CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,VOID **ppv)
2807 // We return IQualProp and delegate everything else
2809 if (riid == IID_IQualProp) {
2810 return GetInterface( (IQualProp *)this, ppv);
2811 } else if (riid == IID_IQualityControl) {
2812 return GetInterface( (IQualityControl *)this, ppv);
2814 return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
2818 // Override JoinFilterGraph so that, just before leaving
2819 // the graph we can send an EC_WINDOW_DESTROYED event
2821 STDMETHODIMP
2822 CBaseVideoRenderer::JoinFilterGraph(IFilterGraph *pGraph,LPCWSTR pName)
2824 // Since we send EC_ACTIVATE, we also need to ensure
2825 // we send EC_WINDOW_DESTROYED or the resource manager may be
2826 // holding us as a focus object
2827 if (!pGraph && m_pGraph) {
2829 // We were in a graph and now we're not
2830 // Do this properly in case we are aggregated
2831 IBaseFilter* pFilter;
2832 QueryInterface(IID_IBaseFilter,(void **) &pFilter);
2833 NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0);
2834 pFilter->Release();
2836 return CBaseFilter::JoinFilterGraph(pGraph, pName);
2840 // This removes a large number of level 4 warnings from the
2841 // Microsoft compiler which in this case are not very useful
2842 #pragma warning(disable: 4514)