blob: b354b5fb6d5fb90e9a8309b8e9d43cb09e2070bf [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001//------------------------------------------------------------------------------
2// File: RenBase.cpp
3//
4// Desc: DirectShow base classes.
5//
6// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved.
7//------------------------------------------------------------------------------
8
9#include <pjmedia-videodev/config.h>
10
11#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
12
13#include <streams.h> // DirectShow base class definitions
14#include <mmsystem.h> // Needed for definition of timeGetTime
15#include <limits.h> // Standard data type limit definitions
16#include <measure.h> // Used for time critical log functions
17
18#pragma warning(disable:4355)
19
20// Helper function for clamping time differences
21int inline TimeDiff(REFERENCE_TIME rt)
22{
23 if (rt < - (50 * UNITS)) {
24 return -(50 * UNITS);
25 } else
26 if (rt > 50 * UNITS) {
27 return 50 * UNITS;
28 } else return (int)rt;
29}
30
31// Implements the CBaseRenderer class
32
33CBaseRenderer::CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
34 __in_opt LPCTSTR pName, // Debug ONLY description
35 __inout_opt LPUNKNOWN pUnk, // Aggregated owner object
36 __inout HRESULT *phr) : // General OLE return code
37
38 CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass),
39 m_evComplete(TRUE, phr),
40 m_RenderEvent(FALSE, phr),
41 m_bAbort(FALSE),
42 m_pPosition(NULL),
43 m_ThreadSignal(TRUE, phr),
44 m_bStreaming(FALSE),
45 m_bEOS(FALSE),
46 m_bEOSDelivered(FALSE),
47 m_pMediaSample(NULL),
48 m_dwAdvise(0),
49 m_pQSink(NULL),
50 m_pInputPin(NULL),
51 m_bRepaintStatus(TRUE),
52 m_SignalTime(0),
53 m_bInReceive(FALSE),
54 m_EndOfStreamTimer(0)
55{
56 if (SUCCEEDED(*phr)) {
57 Ready();
58#ifdef PERF
59 m_idBaseStamp = MSR_REGISTER(TEXT("BaseRenderer: sample time stamp"));
60 m_idBaseRenderTime = MSR_REGISTER(TEXT("BaseRenderer: draw time (msec)"));
61 m_idBaseAccuracy = MSR_REGISTER(TEXT("BaseRenderer: Accuracy (msec)"));
62#endif
63 }
64}
65
66
67// Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
68// object. The object is created when somebody queries us. These are standard
69// control interfaces for seeking and setting start/stop positions and rates.
70// We will probably also have made an input pin based on CRendererInputPin
71// that has to be deleted, it's created when an enumerator calls our GetPin
72
73CBaseRenderer::~CBaseRenderer()
74{
75 ASSERT(m_bStreaming == FALSE);
76 ASSERT(m_EndOfStreamTimer == 0);
77 StopStreaming();
78 ClearPendingSample();
79
80 // Delete any IMediaPosition implementation
81
82 if (m_pPosition) {
83 delete m_pPosition;
84 m_pPosition = NULL;
85 }
86
87 // Delete any input pin created
88
89 if (m_pInputPin) {
90 delete m_pInputPin;
91 m_pInputPin = NULL;
92 }
93
94 // Release any Quality sink
95
96 ASSERT(m_pQSink == NULL);
97}
98
99
100// This returns the IMediaPosition and IMediaSeeking interfaces
101
102HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid, __deref_out void **ppv)
103{
104 CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
105 if (m_pPosition) {
106 return m_pPosition->NonDelegatingQueryInterface(riid,ppv);
107 }
108
109 CBasePin *pPin = GetPin(0);
110 if (NULL == pPin) {
111 return E_OUTOFMEMORY;
112 }
113
114 HRESULT hr = NOERROR;
115
116 // Create implementation of this dynamically since sometimes we may
117 // never try and do a seek. The helper object implements a position
118 // control interface (IMediaPosition) which in fact simply takes the
119 // calls normally from the filter graph and passes them upstream
120
121 m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
122 CBaseFilter::GetOwner(),
123 (HRESULT *) &hr,
124 pPin);
125 if (m_pPosition == NULL) {
126 return E_OUTOFMEMORY;
127 }
128
129 if (FAILED(hr)) {
130 delete m_pPosition;
131 m_pPosition = NULL;
132 return E_NOINTERFACE;
133 }
134 return GetMediaPositionInterface(riid,ppv);
135}
136
137
138// Overriden to say what interfaces we support and where
139
140STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)
141{
142 // Do we have this interface
143
144 if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
145 return GetMediaPositionInterface(riid,ppv);
146 } else {
147 return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
148 }
149}
150
151
152// This is called whenever we change states, we have a manual reset event that
153// is signalled whenever we don't won't the source filter thread to wait in us
154// (such as in a stopped state) and likewise is not signalled whenever it can
155// wait (during paused and running) this function sets or resets the thread
156// event. The event is used to stop source filter threads waiting in Receive
157
158HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait)
159{
160 if (bCanWait == TRUE) {
161 m_ThreadSignal.Reset();
162 } else {
163 m_ThreadSignal.Set();
164 }
165 return NOERROR;
166}
167
168
169#ifdef DEBUG
170// Dump the current renderer state to the debug terminal. The hardest part of
171// the renderer is the window where we unlock everything to wait for a clock
172// to signal it is time to draw or for the application to cancel everything
173// by stopping the filter. If we get things wrong we can leave the thread in
174// WaitForRenderTime with no way for it to ever get out and we will deadlock
175
176void CBaseRenderer::DisplayRendererState()
177{
178 DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime")));
179
180 // No way should this be signalled at this point
181
182 BOOL bSignalled = m_ThreadSignal.Check();
183 DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"),bSignalled));
184
185 // Now output the current renderer state variables
186
187 DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"),m_State));
188
189 DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"),m_bAbort));
190
191 DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"),m_bStreaming));
192
193 DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"),m_dwAdvise));
194
195 DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"),m_pMediaSample));
196
197 DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"),m_bEOS));
198
199 DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"),m_bEOSDelivered));
200
201 DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"),m_bRepaintStatus));
202
203
204 // Output the delayed end of stream timer information
205
206 DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"),m_EndOfStreamTimer));
207
208 DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)m_SignalTime)));
209
210
211 // Should never timeout during a flushing state
212
213 BOOL bFlushing = m_pInputPin->IsFlushing();
214 DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"),bFlushing));
215
216 // Display the time we were told to start at
217 DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time)));
218
219 // Have we got a reference clock
220 if (m_pClock == NULL) return;
221
222 // Get the current time from the wall clock
223
224 CRefTime CurrentTime,StartTime,EndTime;
225 m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime);
226 CRefTime Offset = CurrentTime - m_tStart;
227
228 // Display the current time from the clock
229
230 DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"),CDisp((LONGLONG)CurrentTime.m_time)));
231
232 DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"),Offset.Millisecs()));
233
234
235 // Do we have a sample ready to render
236 if (m_pMediaSample == NULL) return;
237
238 m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
239 DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"),
240 StartTime.Millisecs(),EndTime.Millisecs()));
241
242 // Calculate how long it is until it is due for rendering
243 CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
244 DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"),Wait.Millisecs()));
245}
246#endif
247
248
249// Wait until the clock sets the timer event or we're otherwise signalled. We
250// set an arbitrary timeout for this wait and if it fires then we display the
251// current renderer state on the debugger. It will often fire if the filter's
252// left paused in an application however it may also fire during stress tests
253// if the synchronisation with application seeks and state changes is faulty
254
255#define RENDER_TIMEOUT 10000
256
257HRESULT CBaseRenderer::WaitForRenderTime()
258{
259 HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent };
260 DWORD Result = WAIT_TIMEOUT;
261
262 // Wait for either the time to arrive or for us to be stopped
263
264 OnWaitStart();
265 while (Result == WAIT_TIMEOUT) {
266 Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT);
267
268#ifdef DEBUG
269 if (Result == WAIT_TIMEOUT) DisplayRendererState();
270#endif
271
272 }
273 OnWaitEnd();
274
275 // We may have been awoken without the timer firing
276
277 if (Result == WAIT_OBJECT_0) {
278 return VFW_E_STATE_CHANGED;
279 }
280
281 SignalTimerFired();
282 return NOERROR;
283}
284
285
286// Poll waiting for Receive to complete. This really matters when
287// Receive may set the palette and cause window messages
288// The problem is that if we don't really wait for a renderer to
289// stop processing we can deadlock waiting for a transform which
290// is calling the renderer's Receive() method because the transform's
291// Stop method doesn't know to process window messages to unblock
292// the renderer's Receive processing
293void CBaseRenderer::WaitForReceiveToComplete()
294{
295 for (;;) {
296 if (!m_bInReceive) {
297 break;
298 }
299
300 MSG msg;
301 // Receive all interthread snedmessages
302 PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE);
303
304 Sleep(1);
305 }
306
307 // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
308 // above just cleared the changebit which will cause some messaging
309 // calls to block (waitMessage, MsgWaitFor...) now.
310 // Post a dummy message to set the QS_POSTMESSAGE bit again
311 if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
312 // Send dummy message
313 PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
314 }
315}
316
317// A filter can have four discrete states, namely Stopped, Running, Paused,
318// Intermediate. We are in an intermediate state if we are currently trying
319// to pause but haven't yet got the first sample (or if we have been flushed
320// in paused state and therefore still have to wait for a sample to arrive)
321
322// This class contains an event called m_evComplete which is signalled when
323// the current state is completed and is not signalled when we are waiting to
324// complete the last state transition. As mentioned above the only time we
325// use this at the moment is when we wait for a media sample in paused state
326// If while we are waiting we receive an end of stream notification from the
327// source filter then we know no data is imminent so we can reset the event
328// This means that when we transition to paused the source filter must call
329// end of stream on us or send us an image otherwise we'll hang indefinately
330
331
332// Simple internal way of getting the real state
333
334FILTER_STATE CBaseRenderer::GetRealState() {
335 return m_State;
336}
337
338
339// The renderer doesn't complete the full transition to paused states until
340// it has got one media sample to render. If you ask it for its state while
341// it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
342
343STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State)
344{
345 CheckPointer(State,E_POINTER);
346
347 if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) {
348 *State = m_State;
349 return VFW_S_STATE_INTERMEDIATE;
350 }
351 *State = m_State;
352 return NOERROR;
353}
354
355
356// If we're pausing and we have no samples we don't complete the transition
357// to State_Paused and we return S_FALSE. However if the m_bAbort flag has
358// been set then all samples are rejected so there is no point waiting for
359// one. If we do have a sample then return NOERROR. We will only ever return
360// VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
361// (calling GetState after either being stopped or Run will NOT return this)
362
363HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState)
364{
365 // Allow us to be paused when disconnected
366
367 if (m_pInputPin->IsConnected() == FALSE) {
368 Ready();
369 return S_OK;
370 }
371
372 // Have we run off the end of stream
373
374 if (IsEndOfStream() == TRUE) {
375 Ready();
376 return S_OK;
377 }
378
379 // Make sure we get fresh data after being stopped
380
381 if (HaveCurrentSample() == TRUE) {
382 if (OldState != State_Stopped) {
383 Ready();
384 return S_OK;
385 }
386 }
387 NotReady();
388 return S_FALSE;
389}
390
391
392// When we stop the filter the things we do are:-
393
394// Decommit the allocator being used in the connection
395// Release the source filter if it's waiting in Receive
396// Cancel any advise link we set up with the clock
397// Any end of stream signalled is now obsolete so reset
398// Allow us to be stopped when we are not connected
399
400STDMETHODIMP CBaseRenderer::Stop()
401{
402 CAutoLock cRendererLock(&m_InterfaceLock);
403
404 // Make sure there really is a state change
405
406 if (m_State == State_Stopped) {
407 return NOERROR;
408 }
409
410 // Is our input pin connected
411
412 if (m_pInputPin->IsConnected() == FALSE) {
413 NOTE("Input pin is not connected");
414 m_State = State_Stopped;
415 return NOERROR;
416 }
417
418 CBaseFilter::Stop();
419
420 // If we are going into a stopped state then we must decommit whatever
421 // allocator we are using it so that any source filter waiting in the
422 // GetBuffer can be released and unlock themselves for a state change
423
424 if (m_pInputPin->Allocator()) {
425 m_pInputPin->Allocator()->Decommit();
426 }
427
428 // Cancel any scheduled rendering
429
430 SetRepaintStatus(TRUE);
431 StopStreaming();
432 SourceThreadCanWait(FALSE);
433 ResetEndOfStream();
434 CancelNotification();
435
436 // There should be no outstanding clock advise
437 ASSERT(CancelNotification() == S_FALSE);
438 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
439 ASSERT(m_EndOfStreamTimer == 0);
440
441 Ready();
442 WaitForReceiveToComplete();
443 m_bAbort = FALSE;
444
445 return NOERROR;
446}
447
448
449// When we pause the filter the things we do are:-
450
451// Commit the allocator being used in the connection
452// Allow a source filter thread to wait in Receive
453// Cancel any clock advise link (we may be running)
454// Possibly complete the state change if we have data
455// Allow us to be paused when we are not connected
456
457STDMETHODIMP CBaseRenderer::Pause()
458{
459 CAutoLock cRendererLock(&m_InterfaceLock);
460 FILTER_STATE OldState = m_State;
461 ASSERT(m_pInputPin->IsFlushing() == FALSE);
462
463 // Make sure there really is a state change
464
465 if (m_State == State_Paused) {
466 return CompleteStateChange(State_Paused);
467 }
468
469 // Has our input pin been connected
470
471 if (m_pInputPin->IsConnected() == FALSE) {
472 NOTE("Input pin is not connected");
473 m_State = State_Paused;
474 return CompleteStateChange(State_Paused);
475 }
476
477 // Pause the base filter class
478
479 HRESULT hr = CBaseFilter::Pause();
480 if (FAILED(hr)) {
481 NOTE("Pause failed");
482 return hr;
483 }
484
485 // Enable EC_REPAINT events again
486
487 SetRepaintStatus(TRUE);
488 StopStreaming();
489 SourceThreadCanWait(TRUE);
490 CancelNotification();
491 ResetEndOfStreamTimer();
492
493 // If we are going into a paused state then we must commit whatever
494 // allocator we are using it so that any source filter can call the
495 // GetBuffer and expect to get a buffer without returning an error
496
497 if (m_pInputPin->Allocator()) {
498 m_pInputPin->Allocator()->Commit();
499 }
500
501 // There should be no outstanding advise
502 ASSERT(CancelNotification() == S_FALSE);
503 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
504 ASSERT(m_EndOfStreamTimer == 0);
505 ASSERT(m_pInputPin->IsFlushing() == FALSE);
506
507 // When we come out of a stopped state we must clear any image we were
508 // holding onto for frame refreshing. Since renderers see state changes
509 // first we can reset ourselves ready to accept the source thread data
510 // Paused or running after being stopped causes the current position to
511 // be reset so we're not interested in passing end of stream signals
512
513 if (OldState == State_Stopped) {
514 m_bAbort = FALSE;
515 ClearPendingSample();
516 }
517 return CompleteStateChange(OldState);
518}
519
520
521// When we run the filter the things we do are:-
522
523// Commit the allocator being used in the connection
524// Allow a source filter thread to wait in Receive
525// Signal the render event just to get us going
526// Start the base class by calling StartStreaming
527// Allow us to be run when we are not connected
528// Signal EC_COMPLETE if we are not connected
529
530STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime)
531{
532 CAutoLock cRendererLock(&m_InterfaceLock);
533 FILTER_STATE OldState = m_State;
534
535 // Make sure there really is a state change
536
537 if (m_State == State_Running) {
538 return NOERROR;
539 }
540
541 // Send EC_COMPLETE if we're not connected
542
543 if (m_pInputPin->IsConnected() == FALSE) {
544 NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
545 m_State = State_Running;
546 return NOERROR;
547 }
548
549 Ready();
550
551 // Pause the base filter class
552
553 HRESULT hr = CBaseFilter::Run(StartTime);
554 if (FAILED(hr)) {
555 NOTE("Run failed");
556 return hr;
557 }
558
559 // Allow the source thread to wait
560 ASSERT(m_pInputPin->IsFlushing() == FALSE);
561 SourceThreadCanWait(TRUE);
562 SetRepaintStatus(FALSE);
563
564 // There should be no outstanding advise
565 ASSERT(CancelNotification() == S_FALSE);
566 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
567 ASSERT(m_EndOfStreamTimer == 0);
568 ASSERT(m_pInputPin->IsFlushing() == FALSE);
569
570 // If we are going into a running state then we must commit whatever
571 // allocator we are using it so that any source filter can call the
572 // GetBuffer and expect to get a buffer without returning an error
573
574 if (m_pInputPin->Allocator()) {
575 m_pInputPin->Allocator()->Commit();
576 }
577
578 // When we come out of a stopped state we must clear any image we were
579 // holding onto for frame refreshing. Since renderers see state changes
580 // first we can reset ourselves ready to accept the source thread data
581 // Paused or running after being stopped causes the current position to
582 // be reset so we're not interested in passing end of stream signals
583
584 if (OldState == State_Stopped) {
585 m_bAbort = FALSE;
586 ClearPendingSample();
587 }
588 return StartStreaming();
589}
590
591
592// Return the number of input pins we support
593
594int CBaseRenderer::GetPinCount()
595{
596 if (m_pInputPin == NULL) {
597 // Try to create it
598 (void)GetPin(0);
599 }
600 return m_pInputPin != NULL ? 1 : 0;
601}
602
603
604// We only support one input pin and it is numbered zero
605
606CBasePin *CBaseRenderer::GetPin(int n)
607{
608 CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
609
610 // Should only ever be called with zero
611 ASSERT(n == 0);
612
613 if (n != 0) {
614 return NULL;
615 }
616
617 // Create the input pin if not already done so
618
619 if (m_pInputPin == NULL) {
620
621 // hr must be initialized to NOERROR because
622 // CRendererInputPin's constructor only changes
623 // hr's value if an error occurs.
624 HRESULT hr = NOERROR;
625
626 m_pInputPin = new CRendererInputPin(this,&hr,L"In");
627 if (NULL == m_pInputPin) {
628 return NULL;
629 }
630
631 if (FAILED(hr)) {
632 delete m_pInputPin;
633 m_pInputPin = NULL;
634 return NULL;
635 }
636 }
637 return m_pInputPin;
638}
639
640
641// If "In" then return the IPin for our input pin, otherwise NULL and error
642
643STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, __deref_out IPin **ppPin)
644{
645 CheckPointer(ppPin,E_POINTER);
646
647 if (0==lstrcmpW(Id,L"In")) {
648 *ppPin = GetPin(0);
649 if (*ppPin) {
650 (*ppPin)->AddRef();
651 } else {
652 return E_OUTOFMEMORY;
653 }
654 } else {
655 *ppPin = NULL;
656 return VFW_E_NOT_FOUND;
657 }
658 return NOERROR;
659}
660
661
662// Called when the input pin receives an EndOfStream notification. If we have
663// not got a sample, then notify EC_COMPLETE now. If we have samples, then set
664// m_bEOS and check for this on completing samples. If we're waiting to pause
665// then complete the transition to paused state by setting the state event
666
667HRESULT CBaseRenderer::EndOfStream()
668{
669 // Ignore these calls if we are stopped
670
671 if (m_State == State_Stopped) {
672 return NOERROR;
673 }
674
675 // If we have a sample then wait for it to be rendered
676
677 m_bEOS = TRUE;
678 if (m_pMediaSample) {
679 return NOERROR;
680 }
681
682 // If we are waiting for pause then we are now ready since we cannot now
683 // carry on waiting for a sample to arrive since we are being told there
684 // won't be any. This sets an event that the GetState function picks up
685
686 Ready();
687
688 // Only signal completion now if we are running otherwise queue it until
689 // we do run in StartStreaming. This is used when we seek because a seek
690 // causes a pause where early notification of completion is misleading
691
692 if (m_bStreaming) {
693 SendEndOfStream();
694 }
695 return NOERROR;
696}
697
698
699// When we are told to flush we should release the source thread
700
701HRESULT CBaseRenderer::BeginFlush()
702{
703 // If paused then report state intermediate until we get some data
704
705 if (m_State == State_Paused) {
706 NotReady();
707 }
708
709 SourceThreadCanWait(FALSE);
710 CancelNotification();
711 ClearPendingSample();
712 // Wait for Receive to complete
713 WaitForReceiveToComplete();
714
715 return NOERROR;
716}
717
718
719// After flushing the source thread can wait in Receive again
720
721HRESULT CBaseRenderer::EndFlush()
722{
723 // Reset the current sample media time
724 if (m_pPosition) m_pPosition->ResetMediaTime();
725
726 // There should be no outstanding advise
727
728 ASSERT(CancelNotification() == S_FALSE);
729 SourceThreadCanWait(TRUE);
730 return NOERROR;
731}
732
733
734// We can now send EC_REPAINTs if so required
735
736HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin)
737{
738 // The caller should always hold the interface lock because
739 // the function uses CBaseFilter::m_State.
740 ASSERT(CritCheckIn(&m_InterfaceLock));
741
742 m_bAbort = FALSE;
743
744 if (State_Running == GetRealState()) {
745 HRESULT hr = StartStreaming();
746 if (FAILED(hr)) {
747 return hr;
748 }
749
750 SetRepaintStatus(FALSE);
751 } else {
752 SetRepaintStatus(TRUE);
753 }
754
755 return NOERROR;
756}
757
758
759// Called when we go paused or running
760
761HRESULT CBaseRenderer::Active()
762{
763 return NOERROR;
764}
765
766
767// Called when we go into a stopped state
768
769HRESULT CBaseRenderer::Inactive()
770{
771 if (m_pPosition) {
772 m_pPosition->ResetMediaTime();
773 }
774 // People who derive from this may want to override this behaviour
775 // to keep hold of the sample in some circumstances
776 ClearPendingSample();
777
778 return NOERROR;
779}
780
781
782// Tell derived classes about the media type agreed
783
784HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt)
785{
786 return NOERROR;
787}
788
789
790// When we break the input pin connection we should reset the EOS flags. When
791// we are asked for either IMediaPosition or IMediaSeeking we will create a
792// CPosPassThru object to handles media time pass through. When we're handed
793// samples we store (by calling CPosPassThru::RegisterMediaTime) their media
794// times so we can then return a real current position of data being rendered
795
796HRESULT CBaseRenderer::BreakConnect()
797{
798 // Do we have a quality management sink
799
800 if (m_pQSink) {
801 m_pQSink->Release();
802 m_pQSink = NULL;
803 }
804
805 // Check we have a valid connection
806
807 if (m_pInputPin->IsConnected() == FALSE) {
808 return S_FALSE;
809 }
810
811 // Check we are stopped before disconnecting
812 if (m_State != State_Stopped && !m_pInputPin->CanReconnectWhenActive()) {
813 return VFW_E_NOT_STOPPED;
814 }
815
816 SetRepaintStatus(FALSE);
817 ResetEndOfStream();
818 ClearPendingSample();
819 m_bAbort = FALSE;
820
821 if (State_Running == m_State) {
822 StopStreaming();
823 }
824
825 return NOERROR;
826}
827
828
829// Retrieves the sample times for this samples (note the sample times are
830// passed in by reference not value). We return S_FALSE to say schedule this
831// sample according to the times on the sample. We also return S_OK in
832// which case the object should simply render the sample data immediately
833
834HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample,
835 __out REFERENCE_TIME *pStartTime,
836 __out REFERENCE_TIME *pEndTime)
837{
838 ASSERT(m_dwAdvise == 0);
839 ASSERT(pMediaSample);
840
841 // If the stop time for this sample is before or the same as start time,
842 // then just ignore it (release it) and schedule the next one in line
843 // Source filters should always fill in the start and end times properly!
844
845 if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) {
846 if (*pEndTime < *pStartTime) {
847 return VFW_E_START_TIME_AFTER_END;
848 }
849 } else {
850 // no time set in the sample... draw it now?
851 return S_OK;
852 }
853
854 // Can't synchronise without a clock so we return S_OK which tells the
855 // caller that the sample should be rendered immediately without going
856 // through the overhead of setting a timer advise link with the clock
857
858 if (m_pClock == NULL) {
859 return S_OK;
860 }
861 return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime);
862}
863
864
865// By default all samples are drawn according to their time stamps so we
866// return S_FALSE. Returning S_OK means draw immediately, this is used
867// by the derived video renderer class in its quality management.
868
869HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
870 __out REFERENCE_TIME *ptrStart,
871 __out REFERENCE_TIME *ptrEnd)
872{
873 return S_FALSE;
874}
875
876
877// We must always reset the current advise time to zero after a timer fires
878// because there are several possible ways which lead us not to do any more
879// scheduling such as the pending image being cleared after state changes
880
881void CBaseRenderer::SignalTimerFired()
882{
883 m_dwAdvise = 0;
884}
885
886
887// Cancel any notification currently scheduled. This is called by the owning
888// window object when it is told to stop streaming. If there is no timer link
889// outstanding then calling this is benign otherwise we go ahead and cancel
890// We must always reset the render event as the quality management code can
891// signal immediate rendering by setting the event without setting an advise
892// link. If we're subsequently stopped and run the first attempt to setup an
893// advise link with the reference clock will find the event still signalled
894
895HRESULT CBaseRenderer::CancelNotification()
896{
897 ASSERT(m_dwAdvise == 0 || m_pClock);
898 DWORD_PTR dwAdvise = m_dwAdvise;
899
900 // Have we a live advise link
901
902 if (m_dwAdvise) {
903 m_pClock->Unadvise(m_dwAdvise);
904 SignalTimerFired();
905 ASSERT(m_dwAdvise == 0);
906 }
907
908 // Clear the event and return our status
909
910 m_RenderEvent.Reset();
911 return (dwAdvise ? S_OK : S_FALSE);
912}
913
914
915// Responsible for setting up one shot advise links with the clock
916// Return FALSE if the sample is to be dropped (not drawn at all)
917// Return TRUE if the sample is to be drawn and in this case also
918// arrange for m_RenderEvent to be set at the appropriate time
919
920BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample)
921{
922 REFERENCE_TIME StartSample, EndSample;
923
924 // Is someone pulling our leg
925
926 if (pMediaSample == NULL) {
927 return FALSE;
928 }
929
930 // Get the next sample due up for rendering. If there aren't any ready
931 // then GetNextSampleTimes returns an error. If there is one to be done
932 // then it succeeds and yields the sample times. If it is due now then
933 // it returns S_OK other if it's to be done when due it returns S_FALSE
934
935 HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
936 if (FAILED(hr)) {
937 return FALSE;
938 }
939
940 // If we don't have a reference clock then we cannot set up the advise
941 // time so we simply set the event indicating an image to render. This
942 // will cause us to run flat out without any timing or synchronisation
943
944 if (hr == S_OK) {
945 EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
946 return TRUE;
947 }
948
949 ASSERT(m_dwAdvise == 0);
950 ASSERT(m_pClock);
951 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
952
953 // We do have a valid reference clock interface so we can ask it to
954 // set an event when the image comes due for rendering. We pass in
955 // the reference time we were told to start at and also the current
956 // stream time which is the offset from the start reference time
957
958 hr = m_pClock->AdviseTime(
959 (REFERENCE_TIME) m_tStart, // Start run time
960 StartSample, // Stream time
961 (HEVENT)(HANDLE) m_RenderEvent, // Render notification
962 &m_dwAdvise); // Advise cookie
963
964 if (SUCCEEDED(hr)) {
965 return TRUE;
966 }
967
968 // We could not schedule the next sample for rendering despite the fact
969 // we have a valid sample here. This is a fair indication that either
970 // the system clock is wrong or the time stamp for the sample is duff
971
972 ASSERT(m_dwAdvise == 0);
973 return FALSE;
974}
975
976
977// This is called when a sample comes due for rendering. We pass the sample
978// on to the derived class. After rendering we will initialise the timer for
979// the next sample, NOTE signal that the last one fired first, if we don't
980// do this it thinks there is still one outstanding that hasn't completed
981
982HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample)
983{
984 // If the media sample is NULL then we will have been notified by the
985 // clock that another sample is ready but in the mean time someone has
986 // stopped us streaming which causes the next sample to be released
987
988 if (pMediaSample == NULL) {
989 return S_FALSE;
990 }
991
992 // If we have stopped streaming then don't render any more samples, the
993 // thread that got in and locked us and then reset this flag does not
994 // clear the pending sample as we can use it to refresh any output device
995
996 if (m_bStreaming == FALSE) {
997 return S_FALSE;
998 }
999
1000 // Time how long the rendering takes
1001
1002 OnRenderStart(pMediaSample);
1003 DoRenderSample(pMediaSample);
1004 OnRenderEnd(pMediaSample);
1005
1006 return NOERROR;
1007}
1008
1009
1010// Checks if there is a sample waiting at the renderer
1011
1012BOOL CBaseRenderer::HaveCurrentSample()
1013{
1014 CAutoLock cRendererLock(&m_RendererLock);
1015 return (m_pMediaSample == NULL ? FALSE : TRUE);
1016}
1017
1018
1019// Returns the current sample waiting at the video renderer. We AddRef the
1020// sample before returning so that should it come due for rendering the
1021// person who called this method will hold the remaining reference count
1022// that will stop the sample being added back onto the allocator free list
1023
1024IMediaSample *CBaseRenderer::GetCurrentSample()
1025{
1026 CAutoLock cRendererLock(&m_RendererLock);
1027 if (m_pMediaSample) {
1028 m_pMediaSample->AddRef();
1029 }
1030 return m_pMediaSample;
1031}
1032
1033
1034// Called when the source delivers us a sample. We go through a few checks to
1035// make sure the sample can be rendered. If we are running (streaming) then we
1036// have the sample scheduled with the reference clock, if we are not streaming
1037// then we have received an sample in paused mode so we can complete any state
1038// transition. On leaving this function everything will be unlocked so an app
1039// thread may get in and change our state to stopped (for example) in which
1040// case it will also signal the thread event so that our wait call is stopped
1041
1042HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample)
1043{
1044 CAutoLock cInterfaceLock(&m_InterfaceLock);
1045 m_bInReceive = TRUE;
1046
1047 // Check our flushing and filter state
1048
1049 // This function must hold the interface lock because it calls
1050 // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses
1051 // CBasePin::m_bRunTimeError.
1052 HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample);
1053
1054 if (hr != NOERROR) {
1055 m_bInReceive = FALSE;
1056 return E_FAIL;
1057 }
1058
1059 // Has the type changed on a media sample. We do all rendering
1060 // synchronously on the source thread, which has a side effect
1061 // that only one buffer is ever outstanding. Therefore when we
1062 // have Receive called we can go ahead and change the format
1063 // Since the format change can cause a SendMessage we just don't
1064 // lock
1065 if (m_pInputPin->SampleProps()->pMediaType) {
1066 hr = m_pInputPin->SetMediaType(
1067 (CMediaType *)m_pInputPin->SampleProps()->pMediaType);
1068 if (FAILED(hr)) {
1069 m_bInReceive = FALSE;
1070 return hr;
1071 }
1072 }
1073
1074
1075 CAutoLock cSampleLock(&m_RendererLock);
1076
1077 ASSERT(IsActive() == TRUE);
1078 ASSERT(m_pInputPin->IsFlushing() == FALSE);
1079 ASSERT(m_pInputPin->IsConnected() == TRUE);
1080 ASSERT(m_pMediaSample == NULL);
1081
1082 // Return an error if we already have a sample waiting for rendering
1083 // source pins must serialise the Receive calls - we also check that
1084 // no data is being sent after the source signalled an end of stream
1085
1086 if (m_pMediaSample || m_bEOS || m_bAbort) {
1087 Ready();
1088 m_bInReceive = FALSE;
1089 return E_UNEXPECTED;
1090 }
1091
1092 // Store the media times from this sample
1093 if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);
1094
1095 // Schedule the next sample if we are streaming
1096
1097 if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) {
1098 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
1099 ASSERT(CancelNotification() == S_FALSE);
1100 m_bInReceive = FALSE;
1101 return VFW_E_SAMPLE_REJECTED;
1102 }
1103
1104 // Store the sample end time for EC_COMPLETE handling
1105 m_SignalTime = m_pInputPin->SampleProps()->tStop;
1106
1107 // BEWARE we sometimes keep the sample even after returning the thread to
1108 // the source filter such as when we go into a stopped state (we keep it
1109 // to refresh the device with) so we must AddRef it to keep it safely. If
1110 // we start flushing the source thread is released and any sample waiting
1111 // will be released otherwise GetBuffer may never return (see BeginFlush)
1112
1113 m_pMediaSample = pMediaSample;
1114 m_pMediaSample->AddRef();
1115
1116 if (m_bStreaming == FALSE) {
1117 SetRepaintStatus(TRUE);
1118 }
1119 return NOERROR;
1120}
1121
1122
1123// Called by the source filter when we have a sample to render. Under normal
1124// circumstances we set an advise link with the clock, wait for the time to
1125// arrive and then render the data using the PURE virtual DoRenderSample that
1126// the derived class will have overriden. After rendering the sample we may
1127// also signal EOS if it was the last one sent before EndOfStream was called
1128
1129HRESULT CBaseRenderer::Receive(IMediaSample *pSample)
1130{
1131 ASSERT(pSample);
1132
1133 // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
1134
1135 HRESULT hr = PrepareReceive(pSample);
1136 ASSERT(m_bInReceive == SUCCEEDED(hr));
1137 if (FAILED(hr)) {
1138 if (hr == VFW_E_SAMPLE_REJECTED) {
1139 return NOERROR;
1140 }
1141 return hr;
1142 }
1143
1144 // We realize the palette in "PrepareRender()" so we have to give away the
1145 // filter lock here.
1146 if (m_State == State_Paused) {
1147 PrepareRender();
1148 // no need to use InterlockedExchange
1149 m_bInReceive = FALSE;
1150 {
1151 // We must hold both these locks
1152 CAutoLock cRendererLock(&m_InterfaceLock);
1153 if (m_State == State_Stopped)
1154 return NOERROR;
1155
1156 m_bInReceive = TRUE;
1157 CAutoLock cSampleLock(&m_RendererLock);
1158 OnReceiveFirstSample(pSample);
1159 }
1160 Ready();
1161 }
1162 // Having set an advise link with the clock we sit and wait. We may be
1163 // awoken by the clock firing or by a state change. The rendering call
1164 // will lock the critical section and check we can still render the data
1165
1166 hr = WaitForRenderTime();
1167 if (FAILED(hr)) {
1168 m_bInReceive = FALSE;
1169 return NOERROR;
1170 }
1171
1172 PrepareRender();
1173
1174 // Set this here and poll it until we work out the locking correctly
1175 // It can't be right that the streaming stuff grabs the interface
1176 // lock - after all we want to be able to wait for this stuff
1177 // to complete
1178 m_bInReceive = FALSE;
1179
1180 // We must hold both these locks
1181 CAutoLock cRendererLock(&m_InterfaceLock);
1182
1183 // since we gave away the filter wide lock, the sate of the filter could
1184 // have chnaged to Stopped
1185 if (m_State == State_Stopped)
1186 return NOERROR;
1187
1188 CAutoLock cSampleLock(&m_RendererLock);
1189
1190 // Deal with this sample
1191
1192 Render(m_pMediaSample);
1193 ClearPendingSample();
1194 SendEndOfStream();
1195 CancelNotification();
1196 return NOERROR;
1197}
1198
1199
1200// This is called when we stop or are inactivated to clear the pending sample
1201// We release the media sample interface so that they can be allocated to the
1202// source filter again, unless of course we are changing state to inactive in
1203// which case GetBuffer will return an error. We must also reset the current
1204// media sample to NULL so that we know we do not currently have an image
1205
1206HRESULT CBaseRenderer::ClearPendingSample()
1207{
1208 CAutoLock cRendererLock(&m_RendererLock);
1209 if (m_pMediaSample) {
1210 m_pMediaSample->Release();
1211 m_pMediaSample = NULL;
1212 }
1213 return NOERROR;
1214}
1215
1216
1217// Used to signal end of stream according to the sample end time
1218
1219void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier
1220 UINT uMsg, // Not currently used
1221 DWORD_PTR dwUser,// User information
1222 DWORD_PTR dw1, // Windows reserved
1223 DWORD_PTR dw2) // is also reserved
1224{
1225 CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser;
1226 NOTE1("EndOfStreamTimer called (%d)",uID);
1227 pRenderer->TimerCallback();
1228}
1229
1230// Do the timer callback work
1231void CBaseRenderer::TimerCallback()
1232{
1233 // Lock for synchronization (but don't hold this lock when calling
1234 // timeKillEvent)
1235 CAutoLock cRendererLock(&m_RendererLock);
1236
1237 // See if we should signal end of stream now
1238
1239 if (m_EndOfStreamTimer) {
1240 m_EndOfStreamTimer = 0;
1241 SendEndOfStream();
1242 }
1243}
1244
1245
1246// If we are at the end of the stream signal the filter graph but do not set
1247// the state flag back to FALSE. Once we drop off the end of the stream we
1248// leave the flag set (until a subsequent ResetEndOfStream). Each sample we
1249// get delivered will update m_SignalTime to be the last sample's end time.
1250// We must wait this long before signalling end of stream to the filtergraph
1251
1252#define TIMEOUT_DELIVERYWAIT 50
1253#define TIMEOUT_RESOLUTION 10
1254
1255HRESULT CBaseRenderer::SendEndOfStream()
1256{
1257 ASSERT(CritCheckIn(&m_RendererLock));
1258 if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) {
1259 return NOERROR;
1260 }
1261
1262 // If there is no clock then signal immediately
1263 if (m_pClock == NULL) {
1264 return NotifyEndOfStream();
1265 }
1266
1267 // How long into the future is the delivery time
1268
1269 REFERENCE_TIME Signal = m_tStart + m_SignalTime;
1270 REFERENCE_TIME CurrentTime;
1271 m_pClock->GetTime(&CurrentTime);
1272 LONG Delay = LONG((Signal - CurrentTime) / 10000);
1273
1274 // Dump the timing information to the debugger
1275
1276 NOTE1("Delay until end of stream delivery %d",Delay);
1277 NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
1278 NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));
1279
1280 // Wait for the delivery time to arrive
1281
1282 if (Delay < TIMEOUT_DELIVERYWAIT) {
1283 return NotifyEndOfStream();
1284 }
1285
1286 // Signal a timer callback on another worker thread
1287
1288 m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer
1289 TIMEOUT_RESOLUTION, // Timer resolution
1290 EndOfStreamTimer, // Callback function
1291 DWORD_PTR(this), // Used information
1292 TIME_ONESHOT); // Type of callback
1293 if (m_EndOfStreamTimer == 0) {
1294 return NotifyEndOfStream();
1295 }
1296 return NOERROR;
1297}
1298
1299
1300// Signals EC_COMPLETE to the filtergraph manager
1301
1302HRESULT CBaseRenderer::NotifyEndOfStream()
1303{
1304 CAutoLock cRendererLock(&m_RendererLock);
1305 ASSERT(m_bEOSDelivered == FALSE);
1306 ASSERT(m_EndOfStreamTimer == 0);
1307
1308 // Has the filter changed state
1309
1310 if (m_bStreaming == FALSE) {
1311 ASSERT(m_EndOfStreamTimer == 0);
1312 return NOERROR;
1313 }
1314
1315 // Reset the end of stream timer
1316 m_EndOfStreamTimer = 0;
1317
1318 // If we've been using the IMediaPosition interface, set it's start
1319 // and end media "times" to the stop position by hand. This ensures
1320 // that we actually get to the end, even if the MPEG guestimate has
1321 // been bad or if the quality management dropped the last few frames
1322
1323 if (m_pPosition) m_pPosition->EOS();
1324 m_bEOSDelivered = TRUE;
1325 NOTE("Sending EC_COMPLETE...");
1326 return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
1327}
1328
1329
1330// Reset the end of stream flag, this is typically called when we transfer to
1331// stopped states since that resets the current position back to the start so
1332// we will receive more samples or another EndOfStream if there aren't any. We
1333// keep two separate flags one to say we have run off the end of the stream
1334// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
1335// to the filter graph. We need the latter otherwise we can end up sending an
1336// EC_COMPLETE every time the source changes state and calls our EndOfStream
1337
1338HRESULT CBaseRenderer::ResetEndOfStream()
1339{
1340 ResetEndOfStreamTimer();
1341 CAutoLock cRendererLock(&m_RendererLock);
1342
1343 m_bEOS = FALSE;
1344 m_bEOSDelivered = FALSE;
1345 m_SignalTime = 0;
1346
1347 return NOERROR;
1348}
1349
1350
1351// Kills any outstanding end of stream timer
1352
1353void CBaseRenderer::ResetEndOfStreamTimer()
1354{
1355 ASSERT(CritCheckOut(&m_RendererLock));
1356 if (m_EndOfStreamTimer) {
1357 timeKillEvent(m_EndOfStreamTimer);
1358 m_EndOfStreamTimer = 0;
1359 }
1360}
1361
1362
1363// This is called when we start running so that we can schedule any pending
1364// image we have with the clock and display any timing information. If we
1365// don't have any sample but we have queued an EOS flag then we send it. If
1366// we do have a sample then we wait until that has been rendered before we
1367// signal the filter graph otherwise we may change state before it's done
1368
1369HRESULT CBaseRenderer::StartStreaming()
1370{
1371 CAutoLock cRendererLock(&m_RendererLock);
1372 if (m_bStreaming == TRUE) {
1373 return NOERROR;
1374 }
1375
1376 // Reset the streaming times ready for running
1377
1378 m_bStreaming = TRUE;
1379
1380 timeBeginPeriod(1);
1381 OnStartStreaming();
1382
1383 // There should be no outstanding advise
1384 ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
1385 ASSERT(CancelNotification() == S_FALSE);
1386
1387 // If we have an EOS and no data then deliver it now
1388
1389 if (m_pMediaSample == NULL) {
1390 return SendEndOfStream();
1391 }
1392
1393 // Have the data rendered
1394
1395 ASSERT(m_pMediaSample);
1396 if (!ScheduleSample(m_pMediaSample))
1397 m_RenderEvent.Set();
1398
1399 return NOERROR;
1400}
1401
1402
1403// This is called when we stop streaming so that we can set our internal flag
1404// indicating we are not now to schedule any more samples arriving. The state
1405// change methods in the filter implementation take care of cancelling any
1406// clock advise link we have set up and clearing any pending sample we have
1407
1408HRESULT CBaseRenderer::StopStreaming()
1409{
1410 CAutoLock cRendererLock(&m_RendererLock);
1411 m_bEOSDelivered = FALSE;
1412
1413 if (m_bStreaming == TRUE) {
1414 m_bStreaming = FALSE;
1415 OnStopStreaming();
1416 timeEndPeriod(1);
1417 }
1418 return NOERROR;
1419}
1420
1421
1422// We have a boolean flag that is reset when we have signalled EC_REPAINT to
1423// the filter graph. We set this when we receive an image so that should any
1424// conditions arise again we can send another one. By having a flag we ensure
1425// we don't flood the filter graph with redundant calls. We do not set the
1426// event when we receive an EndOfStream call since there is no point in us
1427// sending further EC_REPAINTs. In particular the AutoShowWindow method and
1428// the DirectDraw object use this method to control the window repainting
1429
1430void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
1431{
1432 CAutoLock cSampleLock(&m_RendererLock);
1433 m_bRepaintStatus = bRepaint;
1434}
1435
1436
1437// Pass the window handle to the upstream filter
1438
1439void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd)
1440{
1441 IMediaEventSink *pSink;
1442
1443 // Does the pin support IMediaEventSink
1444 HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink);
1445 if (SUCCEEDED(hr)) {
1446 pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
1447 pSink->Release();
1448 }
1449 NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
1450}
1451
1452
1453// Signal an EC_REPAINT to the filter graph. This can be used to have data
1454// sent to us. For example when a video window is first displayed it may
1455// not have an image to display, at which point it signals EC_REPAINT. The
1456// filtergraph will either pause the graph if stopped or if already paused
1457// it will call put_CurrentPosition of the current position. Setting the
1458// current position to itself has the stream flushed and the image resent
1459
1460#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
1461
1462void CBaseRenderer::SendRepaint()
1463{
1464 CAutoLock cSampleLock(&m_RendererLock);
1465 ASSERT(m_pInputPin);
1466
1467 // We should not send repaint notifications when...
1468 // - An end of stream has been notified
1469 // - Our input pin is being flushed
1470 // - The input pin is not connected
1471 // - We have aborted a video playback
1472 // - There is a repaint already sent
1473
1474 if (m_bAbort == FALSE) {
1475 if (m_pInputPin->IsConnected() == TRUE) {
1476 if (m_pInputPin->IsFlushing() == FALSE) {
1477 if (IsEndOfStream() == FALSE) {
1478 if (m_bRepaintStatus == TRUE) {
1479 IPin *pPin = (IPin *) m_pInputPin;
1480 NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0);
1481 SetRepaintStatus(FALSE);
1482 RLOG("Sending repaint");
1483 }
1484 }
1485 }
1486 }
1487 }
1488}
1489
1490
1491// When a video window detects a display change (WM_DISPLAYCHANGE message) it
1492// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
1493// filtergraph will stop everyone and reconnect our input pin. As we're then
1494// reconnected we can accept the media type that matches the new display mode
1495// since we may no longer be able to draw the current image type efficiently
1496
1497BOOL CBaseRenderer::OnDisplayChange()
1498{
1499 // Ignore if we are not connected yet
1500
1501 CAutoLock cSampleLock(&m_RendererLock);
1502 if (m_pInputPin->IsConnected() == FALSE) {
1503 return FALSE;
1504 }
1505
1506 RLOG("Notification of EC_DISPLAY_CHANGE");
1507
1508 // Pass our input pin as parameter on the event
1509
1510 IPin *pPin = (IPin *) m_pInputPin;
1511 m_pInputPin->AddRef();
1512 NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0);
1513 SetAbortSignal(TRUE);
1514 ClearPendingSample();
1515 m_pInputPin->Release();
1516
1517 return TRUE;
1518}
1519
1520
1521// Called just before we start drawing.
1522// Store the current time in m_trRenderStart to allow the rendering time to be
1523// logged. Log the time stamp of the sample and how late it is (neg is early)
1524
1525void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
1526{
1527#ifdef PERF
1528 REFERENCE_TIME trStart, trEnd;
1529 pMediaSample->GetTime(&trStart, &trEnd);
1530
1531 MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
1532
1533 m_pClock->GetTime(&m_trRenderStart);
1534 MSR_INTEGER(0, (int)m_trRenderStart);
1535 REFERENCE_TIME trStream;
1536 trStream = m_trRenderStart-m_tStart; // convert reftime to stream time
1537 MSR_INTEGER(0,(int)trStream);
1538
1539 const int trLate = (int)(trStream - trStart);
1540 MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec
1541#endif
1542
1543} // OnRenderStart
1544
1545
1546// Called directly after drawing an image.
1547// calculate the time spent drawing and log it.
1548
1549void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
1550{
1551#ifdef PERF
1552 REFERENCE_TIME trNow;
1553 m_pClock->GetTime(&trNow);
1554 MSR_INTEGER(0,(int)trNow);
1555 int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec
1556 MSR_INTEGER(m_idBaseRenderTime, t);
1557#endif
1558} // OnRenderEnd
1559
1560
1561
1562
1563// Constructor must be passed the base renderer object
1564
1565CRendererInputPin::CRendererInputPin(__inout CBaseRenderer *pRenderer,
1566 __inout HRESULT *phr,
1567 __in_opt LPCWSTR pPinName) :
1568 CBaseInputPin(NAME("Renderer pin"),
1569 pRenderer,
1570 &pRenderer->m_InterfaceLock,
1571 (HRESULT *) phr,
1572 pPinName)
1573{
1574 m_pRenderer = pRenderer;
1575 ASSERT(m_pRenderer);
1576}
1577
1578
1579// Signals end of data stream on the input pin
1580
1581STDMETHODIMP CRendererInputPin::EndOfStream()
1582{
1583 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1584 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1585
1586 // Make sure we're streaming ok
1587
1588 HRESULT hr = CheckStreaming();
1589 if (hr != NOERROR) {
1590 return hr;
1591 }
1592
1593 // Pass it onto the renderer
1594
1595 hr = m_pRenderer->EndOfStream();
1596 if (SUCCEEDED(hr)) {
1597 hr = CBaseInputPin::EndOfStream();
1598 }
1599 return hr;
1600}
1601
1602
1603// Signals start of flushing on the input pin - we do the final reset end of
1604// stream with the renderer lock unlocked but with the interface lock locked
1605// We must do this because we call timeKillEvent, our timer callback method
1606// has to take the renderer lock to serialise our state. Therefore holding a
1607// renderer lock when calling timeKillEvent could cause a deadlock condition
1608
1609STDMETHODIMP CRendererInputPin::BeginFlush()
1610{
1611 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1612 {
1613 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1614 CBaseInputPin::BeginFlush();
1615 m_pRenderer->BeginFlush();
1616 }
1617 return m_pRenderer->ResetEndOfStream();
1618}
1619
1620
1621// Signals end of flushing on the input pin
1622
1623STDMETHODIMP CRendererInputPin::EndFlush()
1624{
1625 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1626 CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
1627
1628 HRESULT hr = m_pRenderer->EndFlush();
1629 if (SUCCEEDED(hr)) {
1630 hr = CBaseInputPin::EndFlush();
1631 }
1632 return hr;
1633}
1634
1635
1636// Pass the sample straight through to the renderer object
1637
1638STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample)
1639{
1640 HRESULT hr = m_pRenderer->Receive(pSample);
1641 if (FAILED(hr)) {
1642
1643 // A deadlock could occur if the caller holds the renderer lock and
1644 // attempts to acquire the interface lock.
1645 ASSERT(CritCheckOut(&m_pRenderer->m_RendererLock));
1646
1647 {
1648 // The interface lock must be held when the filter is calling
1649 // IsStopped() or IsFlushing(). The interface lock must also
1650 // be held because the function uses m_bRunTimeError.
1651 CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
1652
1653 // We do not report errors which occur while the filter is stopping,
1654 // flushing or if the m_bAbort flag is set . Errors are expected to
1655 // occur during these operations and the streaming thread correctly
1656 // handles the errors.
1657 if (!IsStopped() && !IsFlushing() && !m_pRenderer->m_bAbort && !m_bRunTimeError) {
1658
1659 // EC_ERRORABORT's first parameter is the error which caused
1660 // the event and its' last parameter is 0. See the Direct
1661 // Show SDK documentation for more information.
1662 m_pRenderer->NotifyEvent(EC_ERRORABORT,hr,0);
1663
1664 {
1665 CAutoLock alRendererLock(&m_pRenderer->m_RendererLock);
1666 if (m_pRenderer->IsStreaming() && !m_pRenderer->IsEndOfStreamDelivered()) {
1667 m_pRenderer->NotifyEndOfStream();
1668 }
1669 }
1670
1671 m_bRunTimeError = TRUE;
1672 }
1673 }
1674 }
1675
1676 return hr;
1677}
1678
1679
1680// Called when the input pin is disconnected
1681
1682HRESULT CRendererInputPin::BreakConnect()
1683{
1684 HRESULT hr = m_pRenderer->BreakConnect();
1685 if (FAILED(hr)) {
1686 return hr;
1687 }
1688 return CBaseInputPin::BreakConnect();
1689}
1690
1691
1692// Called when the input pin is connected
1693
1694HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin)
1695{
1696 HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
1697 if (FAILED(hr)) {
1698 return hr;
1699 }
1700 return CBaseInputPin::CompleteConnect(pReceivePin);
1701}
1702
1703
1704// Give the pin id of our one and only pin
1705
1706STDMETHODIMP CRendererInputPin::QueryId(__deref_out LPWSTR *Id)
1707{
1708 CheckPointer(Id,E_POINTER);
1709
1710 const WCHAR szIn[] = L"In";
1711
1712 *Id = (LPWSTR)CoTaskMemAlloc(sizeof(szIn));
1713 if (*Id == NULL) {
1714 return E_OUTOFMEMORY;
1715 }
1716 CopyMemory(*Id, szIn, sizeof(szIn));
1717 return NOERROR;
1718}
1719
1720
1721// Will the filter accept this media type
1722
1723HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
1724{
1725 return m_pRenderer->CheckMediaType(pmt);
1726}
1727
1728
1729// Called when we go paused or running
1730
1731HRESULT CRendererInputPin::Active()
1732{
1733 return m_pRenderer->Active();
1734}
1735
1736
1737// Called when we go into a stopped state
1738
1739HRESULT CRendererInputPin::Inactive()
1740{
1741 // The caller must hold the interface lock because
1742 // this function uses m_bRunTimeError.
1743 ASSERT(CritCheckIn(&m_pRenderer->m_InterfaceLock));
1744
1745 m_bRunTimeError = FALSE;
1746
1747 return m_pRenderer->Inactive();
1748}
1749
1750
1751// Tell derived classes about the media type agreed
1752
1753HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt)
1754{
1755 HRESULT hr = CBaseInputPin::SetMediaType(pmt);
1756 if (FAILED(hr)) {
1757 return hr;
1758 }
1759 return m_pRenderer->SetMediaType(pmt);
1760}
1761
1762
1763// We do not keep an event object to use when setting up a timer link with
1764// the clock but are given a pointer to one by the owning object through the
1765// SetNotificationObject method - this must be initialised before starting
1766// We can override the default quality management process to have it always
1767// draw late frames, this is currently done by having the following registry
1768// key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
1769
1770const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
1771const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");
1772
1773CBaseVideoRenderer::CBaseVideoRenderer(
1774 REFCLSID RenderClass, // CLSID for this renderer
1775 __in_opt LPCTSTR pName, // Debug ONLY description
1776 __inout_opt LPUNKNOWN pUnk, // Aggregated owner object
1777 __inout HRESULT *phr) : // General OLE return code
1778
1779 CBaseRenderer(RenderClass,pName,pUnk,phr),
1780 m_cFramesDropped(0),
1781 m_cFramesDrawn(0),
1782 m_bSupplierHandlingQuality(FALSE)
1783{
1784 ResetStreamingTimes();
1785
1786#ifdef PERF
1787 m_idTimeStamp = MSR_REGISTER(TEXT("Frame time stamp"));
1788 m_idEarliness = MSR_REGISTER(TEXT("Earliness fudge"));
1789 m_idTarget = MSR_REGISTER(TEXT("Target (mSec)"));
1790 m_idSchLateTime = MSR_REGISTER(TEXT("mSec late when scheduled"));
1791 m_idDecision = MSR_REGISTER(TEXT("Scheduler decision code"));
1792 m_idQualityRate = MSR_REGISTER(TEXT("Quality rate sent"));
1793 m_idQualityTime = MSR_REGISTER(TEXT("Quality time sent"));
1794 m_idWaitReal = MSR_REGISTER(TEXT("Render wait"));
1795 // m_idWait = MSR_REGISTER(TEXT("wait time recorded (msec)"));
1796 m_idFrameAccuracy = MSR_REGISTER(TEXT("Frame accuracy (msecs)"));
1797 m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
1798 //m_idSendQuality = MSR_REGISTER(TEXT("Processing Quality message"));
1799
1800 m_idRenderAvg = MSR_REGISTER(TEXT("Render draw time Avg"));
1801 m_idFrameAvg = MSR_REGISTER(TEXT("FrameAvg"));
1802 m_idWaitAvg = MSR_REGISTER(TEXT("WaitAvg"));
1803 m_idDuration = MSR_REGISTER(TEXT("Duration"));
1804 m_idThrottle = MSR_REGISTER(TEXT("Audio-video throttle wait"));
1805 // m_idDebug = MSR_REGISTER(TEXT("Debug stuff"));
1806#endif // PERF
1807} // Constructor
1808
1809
1810// Destructor is just a placeholder
1811
1812CBaseVideoRenderer::~CBaseVideoRenderer()
1813{
1814 ASSERT(m_dwAdvise == 0);
1815}
1816
1817
1818// The timing functions in this class are called by the window object and by
1819// the renderer's allocator.
1820// The windows object calls timing functions as it receives media sample
1821// images for drawing using GDI.
1822// The allocator calls timing functions when it starts passing DCI/DirectDraw
1823// surfaces which are not rendered in the same way; The decompressor writes
1824// directly to the surface with no separate rendering, so those code paths
1825// call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
1826// when we have allocated one and only one image we know there cannot be any
1827// conflict between the two.
1828//
1829// We use timeGetTime to return the timing counts we use (since it's relative
1830// performance we are interested in rather than absolute compared to a clock)
1831// The window object sets the accuracy of the system clock (normally 1ms) by
1832// calling timeBeginPeriod/timeEndPeriod when it changes streaming states
1833
1834
1835// Reset all times controlling streaming.
1836// Set them so that
1837// 1. Frames will not initially be dropped
1838// 2. The first frame will definitely be drawn (achieved by saying that there
1839// has not ben a frame drawn for a long time).
1840
1841HRESULT CBaseVideoRenderer::ResetStreamingTimes()
1842{
1843 m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago
1844 m_tStreamingStart = timeGetTime();
1845 m_trRenderAvg = 0;
1846 m_trFrameAvg = -1; // -1000 fps == "unset"
1847 m_trDuration = 0; // 0 - strange value
1848 m_trRenderLast = 0;
1849 m_trWaitAvg = 0;
1850 m_tRenderStart = 0;
1851 m_cFramesDrawn = 0;
1852 m_cFramesDropped = 0;
1853 m_iTotAcc = 0;
1854 m_iSumSqAcc = 0;
1855 m_iSumSqFrameTime = 0;
1856 m_trFrame = 0; // hygeine - not really needed
1857 m_trLate = 0; // hygeine - not really needed
1858 m_iSumFrameTime = 0;
1859 m_nNormal = 0;
1860 m_trEarliness = 0;
1861 m_trTarget = -300000; // 30mSec early
1862 m_trThrottle = 0;
1863 m_trRememberStampForPerf = 0;
1864
1865#ifdef PERF
1866 m_trRememberFrameForPerf = 0;
1867#endif
1868
1869 return NOERROR;
1870} // ResetStreamingTimes
1871
1872
1873// Reset all times controlling streaming. Note that we're now streaming. We
1874// don't need to set the rendering event to have the source filter released
1875// as it is done during the Run processing. When we are run we immediately
1876// release the source filter thread and draw any image waiting (that image
1877// may already have been drawn once as a poster frame while we were paused)
1878
1879HRESULT CBaseVideoRenderer::OnStartStreaming()
1880{
1881 ResetStreamingTimes();
1882 return NOERROR;
1883} // OnStartStreaming
1884
1885
1886// Called at end of streaming. Fixes times for property page report
1887
1888HRESULT CBaseVideoRenderer::OnStopStreaming()
1889{
1890 m_tStreamingStart = timeGetTime()-m_tStreamingStart;
1891 return NOERROR;
1892} // OnStopStreaming
1893
1894
1895// Called when we start waiting for a rendering event.
1896// Used to update times spent waiting and not waiting.
1897
1898void CBaseVideoRenderer::OnWaitStart()
1899{
1900 MSR_START(m_idWaitReal);
1901} // OnWaitStart
1902
1903
1904// Called when we are awoken from the wait in the window OR by our allocator
1905// when it is hanging around until the next sample is due for rendering on a
1906// DCI/DirectDraw surface. We add the wait time into our rolling average.
1907// We grab the interface lock so that we're serialised with the application
1908// thread going through the run code - which in due course ends up calling
1909// ResetStreaming times - possibly as we run through this section of code
1910
1911void CBaseVideoRenderer::OnWaitEnd()
1912{
1913#ifdef PERF
1914 MSR_STOP(m_idWaitReal);
1915 // for a perf build we want to know just exactly how late we REALLY are.
1916 // even if this means that we have to look at the clock again.
1917
1918 REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
1919#if 0
1920 m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
1921#else
1922 // We will be discarding overflows like mad here!
1923 // This is wrong really because timeGetTime() can wrap but it's
1924 // only for PERF
1925 REFERENCE_TIME tr = timeGetTime()*10000;
1926 trRealStream = tr + m_llTimeOffset;
1927#endif
1928 trRealStream -= m_tStart; // convert to stream time (this is a reftime)
1929
1930 if (m_trRememberStampForPerf==0) {
1931 // This is probably the poster frame at the start, and it is not scheduled
1932 // in the usual way at all. Just count it. The rememberstamp gets set
1933 // in ShouldDrawSampleNow, so this does invalid frame recording until we
1934 // actually start playing.
1935 PreparePerformanceData(0, 0);
1936 } else {
1937 int trLate = (int)(trRealStream - m_trRememberStampForPerf);
1938 int trFrame = (int)(tr - m_trRememberFrameForPerf);
1939 PreparePerformanceData(trLate, trFrame);
1940 }
1941 m_trRememberFrameForPerf = tr;
1942#endif //PERF
1943} // OnWaitEnd
1944
1945
1946// Put data on one side that describes the lateness of the current frame.
1947// We don't yet know whether it will actually be drawn. In direct draw mode,
1948// this decision is up to the filter upstream, and it could change its mind.
1949// The rules say that if it did draw it must call Receive(). One way or
1950// another we eventually get into either OnRenderStart or OnDirectRender and
1951// these both call RecordFrameLateness to update the statistics.
1952
1953void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame)
1954{
1955 m_trLate = trLate;
1956 m_trFrame = trFrame;
1957} // PreparePerformanceData
1958
1959
1960// update the statistics:
1961// m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
1962// Note that because the properties page reports using these variables,
1963// 1. We need to be inside a critical section
1964// 2. They must all be updated together. Updating the sums here and the count
1965// elsewhere can result in imaginary jitter (i.e. attempts to find square roots
1966// of negative numbers) in the property page code.
1967
1968void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame)
1969{
1970 // Record how timely we are.
1971 int tLate = trLate/10000;
1972
1973 // Best estimate of moment of appearing on the screen is average of
1974 // start and end draw times. Here we have only the end time. This may
1975 // tend to show us as spuriously late by up to 1/2 frame rate achieved.
1976 // Decoder probably monitors draw time. We don't bother.
1977 MSR_INTEGER( m_idFrameAccuracy, tLate );
1978
1979 // This is a kludge - we can get frames that are very late
1980 // especially (at start-up) and they invalidate the statistics.
1981 // So ignore things that are more than 1 sec off.
1982 if (tLate>1000 || tLate<-1000) {
1983 if (m_cFramesDrawn<=1) {
1984 tLate = 0;
1985 } else if (tLate>0) {
1986 tLate = 1000;
1987 } else {
1988 tLate = -1000;
1989 }
1990 }
1991 // The very first frame often has a invalid time, so don't
1992 // count it into the statistics. (???)
1993 if (m_cFramesDrawn>1) {
1994 m_iTotAcc += tLate;
1995 m_iSumSqAcc += (tLate*tLate);
1996 }
1997
1998 // calculate inter-frame time. Doesn't make sense for first frame
1999 // second frame suffers from invalid first frame stamp.
2000 if (m_cFramesDrawn>2) {
2001 int tFrame = trFrame/10000; // convert to mSec else it overflows
2002
2003 // This is a kludge. It can overflow anyway (a pause can cause
2004 // a very long inter-frame time) and it overflows at 2**31/10**7
2005 // or about 215 seconds i.e. 3min 35sec
2006 if (tFrame>1000||tFrame<0) tFrame = 1000;
2007 m_iSumSqFrameTime += tFrame*tFrame;
2008 ASSERT(m_iSumSqFrameTime>=0);
2009 m_iSumFrameTime += tFrame;
2010 }
2011 ++m_cFramesDrawn;
2012
2013} // RecordFrameLateness
2014
2015
2016void CBaseVideoRenderer::ThrottleWait()
2017{
2018 if (m_trThrottle>0) {
2019 int iThrottle = m_trThrottle/10000; // convert to mSec
2020 MSR_INTEGER( m_idThrottle, iThrottle);
2021 DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle));
2022 Sleep(iThrottle);
2023 } else {
2024 Sleep(0);
2025 }
2026} // ThrottleWait
2027
2028
2029// Whenever a frame is rendered it goes though either OnRenderStart
2030// or OnDirectRender. Data that are generated during ShouldDrawSample
2031// are added to the statistics by calling RecordFrameLateness from both
2032// these two places.
2033
2034// Called in place of OnRenderStart..OnRenderEnd
2035// When a DirectDraw image is drawn
2036void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample)
2037{
2038 m_trRenderAvg = 0;
2039 m_trRenderLast = 5000000; // If we mode switch, we do NOT want this
2040 // to inhibit the new average getting going!
2041 // so we set it to half a second
2042 // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
2043 RecordFrameLateness(m_trLate, m_trFrame);
2044 ThrottleWait();
2045} // OnDirectRender
2046
2047
2048// Called just before we start drawing. All we do is to get the current clock
2049// time (from the system) and return. We have to store the start render time
2050// in a member variable because it isn't used until we complete the drawing
2051// The rest is just performance logging.
2052
2053void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample)
2054{
2055 RecordFrameLateness(m_trLate, m_trFrame);
2056 m_tRenderStart = timeGetTime();
2057} // OnRenderStart
2058
2059
2060// Called directly after drawing an image. We calculate the time spent in the
2061// drawing code and if this doesn't appear to have any odd looking spikes in
2062// it then we add it to the current average draw time. Measurement spikes may
2063// occur if the drawing thread is interrupted and switched to somewhere else.
2064
2065void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample)
2066{
2067 // The renderer time can vary erratically if we are interrupted so we do
2068 // some smoothing to help get more sensible figures out but even that is
2069 // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
2070
2071 int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS
2072 if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
2073 // DO_MOVING_AVG(m_trRenderAvg, tr);
2074 m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
2075 }
2076 m_trRenderLast = tr;
2077 ThrottleWait();
2078} // OnRenderEnd
2079
2080
2081STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc)
2082{
2083
2084 m_pQSink = piqc;
2085
2086 return NOERROR;
2087} // SetSink
2088
2089
2090STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, Quality q)
2091{
2092 // NOTE: We are NOT getting any locks here. We could be called
2093 // asynchronously and possibly even on a time critical thread of
2094 // someone else's - so we do the minumum. We only set one state
2095 // variable (an integer) and if that happens to be in the middle
2096 // of another thread reading it they will just get either the new
2097 // or the old value. Locking would achieve no more than this.
2098
2099 // It might be nice to check that we are being called from m_pGraph, but
2100 // it turns out to be a millisecond or so per throw!
2101
2102 // This is heuristics, these numbers are aimed at being "what works"
2103 // rather than anything based on some theory.
2104 // We use a hyperbola because it's easy to calculate and it includes
2105 // a panic button asymptote (which we push off just to the left)
2106 // The throttling fits the following table (roughly)
2107 // Proportion Throttle (msec)
2108 // >=1000 0
2109 // 900 3
2110 // 800 7
2111 // 700 11
2112 // 600 17
2113 // 500 25
2114 // 400 35
2115 // 300 50
2116 // 200 72
2117 // 125 100
2118 // 100 112
2119 // 50 146
2120 // 0 200
2121
2122 // (some evidence that we could go for a sharper kink - e.g. no throttling
2123 // until below the 750 mark - might give fractionally more frames on a
2124 // P60-ish machine). The easy way to get these coefficients is to use
2125 // Renbase.xls follow the instructions therein using excel solver.
2126
2127 if (q.Proportion>=1000) { m_trThrottle = 0; }
2128 else {
2129 // The DWORD is to make quite sure I get unsigned arithmetic
2130 // as the constant is between 2**31 and 2**32
2131 m_trThrottle = -330000 + (388880000/(q.Proportion+167));
2132 }
2133 return NOERROR;
2134} // Notify
2135
2136
2137// Send a message to indicate what our supplier should do about quality.
2138// Theory:
2139// What a supplier wants to know is "is the frame I'm working on NOW
2140// going to be late?".
2141// F1 is the frame at the supplier (as above)
2142// Tf1 is the due time for F1
2143// T1 is the time at that point (NOW!)
2144// Tr1 is the time that f1 WILL actually be rendered
2145// L1 is the latency of the graph for frame F1 = Tr1-T1
2146// D1 (for delay) is how late F1 will be beyond its due time i.e.
2147// D1 = (Tr1-Tf1) which is what the supplier really wants to know.
2148// Unfortunately Tr1 is in the future and is unknown, so is L1
2149//
2150// We could estimate L1 by its value for a previous frame,
2151// L0 = Tr0-T0 and work off
2152// D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
2153// Rearranging terms:
2154// D1' = (T1-T0) + (Tr0-Tf1)
2155// adding (Tf0-Tf0) and rearranging again:
2156// = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
2157// = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
2158// But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
2159// Late field in the quality message that we send.
2160// The other two terms just state what correction should be applied before
2161// using the lateness of F0 to predict the lateness of F1.
2162// (T1-T0) says how much time has actually passed (we have lost this much)
2163// (Tf1-Tf0) says how much time should have passed if we were keeping pace
2164// (we have gained this much).
2165//
2166// Suppliers should therefore work off:
2167// Quality.Late + (T1-T0) - (Tf1-Tf0)
2168// and see if this is "acceptably late" or even early (i.e. negative).
2169// They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
2170// the time stamps in the frames. They get Quality.Late from us.
2171//
2172
2173HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate,
2174 REFERENCE_TIME trRealStream)
2175{
2176 Quality q;
2177 HRESULT hr;
2178
2179 // If we are the main user of time, then report this as Flood/Dry.
2180 // If our suppliers are, then report it as Famine/Glut.
2181 //
2182 // We need to take action, but avoid hunting. Hunting is caused by
2183 // 1. Taking too much action too soon and overshooting
2184 // 2. Taking too long to react (so averaging can CAUSE hunting).
2185 //
2186 // The reason why we use trLate as well as Wait is to reduce hunting;
2187 // if the wait time is coming down and about to go into the red, we do
2188 // NOT want to rely on some average which is only telling is that it used
2189 // to be OK once.
2190
2191 q.TimeStamp = (REFERENCE_TIME)trRealStream;
2192
2193 if (m_trFrameAvg<0) {
2194 q.Type = Famine; // guess
2195 }
2196 // Is the greater part of the time taken bltting or something else
2197 else if (m_trFrameAvg > 2*m_trRenderAvg) {
2198 q.Type = Famine; // mainly other
2199 } else {
2200 q.Type = Flood; // mainly bltting
2201 }
2202
2203 q.Proportion = 1000; // default
2204
2205 if (m_trFrameAvg<0) {
2206 // leave it alone - we don't know enough
2207 }
2208 else if ( trLate> 0 ) {
2209 // try to catch up over the next second
2210 // We could be Really, REALLY late, but rendering all the frames
2211 // anyway, just because it's so cheap.
2212
2213 q.Proportion = 1000 - (int)((trLate)/(UNITS/1000));
2214 if (q.Proportion<500) {
2215 q.Proportion = 500; // don't go daft. (could've been negative!)
2216 } else {
2217 }
2218
2219 } else if ( m_trWaitAvg>20000
2220 && trLate<-20000
2221 ){
2222 // Go cautiously faster - aim at 2mSec wait.
2223 if (m_trWaitAvg>=m_trFrameAvg) {
2224 // This can happen because of some fudges.
2225 // The waitAvg is how long we originally planned to wait
2226 // The frameAvg is more honest.
2227 // It means that we are spending a LOT of time waiting
2228 q.Proportion = 2000; // double.
2229 } else {
2230 if (m_trFrameAvg+20000 > m_trWaitAvg) {
2231 q.Proportion
2232 = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
2233 } else {
2234 // We're apparently spending more than the whole frame time waiting.
2235 // Assume that the averages are slightly out of kilter, but that we
2236 // are indeed doing a lot of waiting. (This leg probably never
2237 // happens, but the code avoids any potential divide by zero).
2238 q.Proportion = 2000;
2239 }
2240 }
2241
2242 if (q.Proportion>2000) {
2243 q.Proportion = 2000; // don't go crazy.
2244 }
2245 }
2246
2247 // Tell the supplier how late frames are when they get rendered
2248 // That's how late we are now.
2249 // If we are in directdraw mode then the guy upstream can see the drawing
2250 // times and we'll just report on the start time. He can figure out any
2251 // offset to apply. If we are in DIB Section mode then we will apply an
2252 // extra offset which is half of our drawing time. This is usually small
2253 // but can sometimes be the dominant effect. For this we will use the
2254 // average drawing time rather than the last frame. If the last frame took
2255 // a long time to draw and made us late, that's already in the lateness
2256 // figure. We should not add it in again unless we expect the next frame
2257 // to be the same. We don't, we expect the average to be a better shot.
2258 // In direct draw mode the RenderAvg will be zero.
2259
2260 q.Late = trLate + m_trRenderAvg/2;
2261
2262 // log what we're doing
2263 MSR_INTEGER(m_idQualityRate, q.Proportion);
2264 MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );
2265
2266 // A specific sink interface may be set through IPin
2267
2268 if (m_pQSink==NULL) {
2269 // Get our input pin's peer. We send quality management messages
2270 // to any nominated receiver of these things (set in the IPin
2271 // interface), or else to our source filter.
2272
2273 IQualityControl *pQC = NULL;
2274 IPin *pOutputPin = m_pInputPin->GetConnected();
2275 ASSERT(pOutputPin != NULL);
2276
2277 // And get an AddRef'd quality control interface
2278
2279 hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC);
2280 if (SUCCEEDED(hr)) {
2281 m_pQSink = pQC;
2282 }
2283 }
2284 if (m_pQSink) {
2285 return m_pQSink->Notify(this,q);
2286 }
2287
2288 return S_FALSE;
2289
2290} // SendQuality
2291
2292
2293// We are called with a valid IMediaSample image to decide whether this is to
2294// be drawn or not. There must be a reference clock in operation.
2295// Return S_OK if it is to be drawn Now (as soon as possible)
2296// Return S_FALSE if it is to be drawn when it's due
2297// Return an error if we want to drop it
2298// m_nNormal=-1 indicates that we dropped the previous frame and so this
2299// one should be drawn early. Respect it and update it.
2300// Use current stream time plus a number of heuristics (detailed below)
2301// to make the decision
2302
2303HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
2304 __inout REFERENCE_TIME *ptrStart,
2305 __inout REFERENCE_TIME *ptrEnd)
2306{
2307
2308 // Don't call us unless there's a clock interface to synchronise with
2309 ASSERT(m_pClock);
2310
2311 MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits
2312 MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits
2313
2314 // We lose a bit of time depending on the monitor type waiting for the next
2315 // screen refresh. On average this might be about 8mSec - so it will be
2316 // later than we think when the picture appears. To compensate a bit
2317 // we bias the media samples by -8mSec i.e. 80000 UNITs.
2318 // We don't ever make a stream time negative (call it paranoia)
2319 if (*ptrStart>=80000) {
2320 *ptrStart -= 80000;
2321 *ptrEnd -= 80000; // bias stop to to retain valid frame duration
2322 }
2323
2324 // Cache the time stamp now. We will want to compare what we did with what
2325 // we started with (after making the monitor allowance).
2326 m_trRememberStampForPerf = *ptrStart;
2327
2328 // Get reference times (current and late)
2329 REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
2330 m_pClock->GetTime(&trRealStream);
2331#ifdef PERF
2332 // While the reference clock is expensive:
2333 // Remember the offset from timeGetTime and use that.
2334 // This overflows all over the place, but when we subtract to get
2335 // differences the overflows all cancel out.
2336 m_llTimeOffset = trRealStream-timeGetTime()*10000;
2337#endif
2338 trRealStream -= m_tStart; // convert to stream time (this is a reftime)
2339
2340 // We have to wory about two versions of "lateness". The truth, which we
2341 // try to work out here and the one measured against m_trTarget which
2342 // includes long term feedback. We report statistics against the truth
2343 // but for operational decisions we work to the target.
2344 // We use TimeDiff to make sure we get an integer because we
2345 // may actually be late (or more likely early if there is a big time
2346 // gap) by a very long time.
2347 const int trTrueLate = TimeDiff(trRealStream - *ptrStart);
2348 const int trLate = trTrueLate;
2349
2350 MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);
2351
2352 // Send quality control messages upstream, measured against target
2353 HRESULT hr = SendQuality(trLate, trRealStream);
2354 // Note: the filter upstream is allowed to this FAIL meaning "you do it".
2355 m_bSupplierHandlingQuality = (hr==S_OK);
2356
2357 // Decision time! Do we drop, draw when ready or draw immediately?
2358
2359 const int trDuration = (int)(*ptrEnd - *ptrStart);
2360 {
2361 // We need to see if the frame rate of the file has just changed.
2362 // This would make comparing our previous frame rate with the current
2363 // frame rate inefficent. Hang on a moment though. I've seen files
2364 // where the frames vary between 33 and 34 mSec so as to average
2365 // 30fps. A minor variation like that won't hurt us.
2366 int t = m_trDuration/32;
2367 if ( trDuration > m_trDuration+t
2368 || trDuration < m_trDuration-t
2369 ) {
2370 // There's a major variation. Reset the average frame rate to
2371 // exactly the current rate to disable decision 9002 for this frame,
2372 // and remember the new rate.
2373 m_trFrameAvg = trDuration;
2374 m_trDuration = trDuration;
2375 }
2376 }
2377
2378 MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
2379 MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
2380 MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
2381 MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
2382 MSR_INTEGER(m_idDuration, trDuration/10000);
2383
2384#ifdef PERF
2385 if (S_OK==pMediaSample->IsDiscontinuity()) {
2386 MSR_INTEGER(m_idDecision, 9000);
2387 }
2388#endif
2389
2390 // Control the graceful slide back from slow to fast machine mode.
2391 // After a frame drop accept an early frame and set the earliness to here
2392 // If this frame is already later than the earliness then slide it to here
2393 // otherwise do the standard slide (reduce by about 12% per frame).
2394 // Note: earliness is normally NEGATIVE
2395 BOOL bJustDroppedFrame
2396 = ( m_bSupplierHandlingQuality
2397 // Can't use the pin sample properties because we might
2398 // not be in Receive when we call this
2399 && (S_OK == pMediaSample->IsDiscontinuity()) // he just dropped one
2400 )
2401 || (m_nNormal==-1); // we just dropped one
2402
2403
2404 // Set m_trEarliness (slide back from slow to fast machine mode)
2405 if (trLate>0) {
2406 m_trEarliness = 0; // we are no longer in fast machine mode at all!
2407 } else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) {
2408 m_trEarliness = trLate; // Things have slipped of their own accord
2409 } else {
2410 m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide
2411 }
2412
2413 // prepare the new wait average - but don't pollute the old one until
2414 // we have finished with it.
2415 int trWaitAvg;
2416 {
2417 // We never mix in a negative wait. This causes us to believe in fast machines
2418 // slightly more.
2419 int trL = trLate<0 ? -trLate : 0;
2420 trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
2421 }
2422
2423
2424 int trFrame;
2425 {
2426 REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
2427 if (tr>10000000) {
2428 tr = 10000000; // 1 second - arbitrarily.
2429 }
2430 trFrame = int(tr);
2431 }
2432
2433 // We will DRAW this frame IF...
2434 if (
2435 // ...the time we are spending drawing is a small fraction of the total
2436 // observed inter-frame time so that dropping it won't help much.
2437 (3*m_trRenderAvg <= m_trFrameAvg)
2438
2439 // ...or our supplier is NOT handling things and the next frame would
2440 // be less timely than this one or our supplier CLAIMS to be handling
2441 // things, and is now less than a full FOUR frames late.
2442 || ( m_bSupplierHandlingQuality
2443 ? (trLate <= trDuration*4)
2444 : (trLate+trLate < trDuration)
2445 )
2446
2447 // ...or we are on average waiting for over eight milliseconds then
2448 // this may be just a glitch. Draw it and we'll hope to catch up.
2449 || (m_trWaitAvg > 80000)
2450
2451 // ...or we haven't drawn an image for over a second. We will update
2452 // the display, which stops the video looking hung.
2453 // Do this regardless of how late this media sample is.
2454 || ((trRealStream - m_trLastDraw) > UNITS)
2455
2456 ) {
2457 HRESULT Result;
2458
2459 // We are going to play this frame. We may want to play it early.
2460 // We will play it early if we think we are in slow machine mode.
2461 // If we think we are NOT in slow machine mode, we will still play
2462 // it early by m_trEarliness as this controls the graceful slide back.
2463 // and in addition we aim at being m_trTarget late rather than "on time".
2464
2465 BOOL bPlayASAP = FALSE;
2466
2467 // we will play it AT ONCE (slow machine mode) if...
2468
2469 // ...we are playing catch-up
2470 if ( bJustDroppedFrame) {
2471 bPlayASAP = TRUE;
2472 MSR_INTEGER(m_idDecision, 9001);
2473 }
2474
2475 // ...or if we are running below the true frame rate
2476 // exact comparisons are glitchy, for these measurements,
2477 // so add an extra 5% or so
2478 else if ( (m_trFrameAvg > trDuration + trDuration/16)
2479
2480 // It's possible to get into a state where we are losing ground, but
2481 // are a very long way ahead. To avoid this or recover from it
2482 // we refuse to play early by more than 10 frames.
2483 && (trLate > - trDuration*10)
2484 ){
2485 bPlayASAP = TRUE;
2486 MSR_INTEGER(m_idDecision, 9002);
2487 }
2488#if 0
2489 // ...or if we have been late and are less than one frame early
2490 else if ( (trLate + trDuration > 0)
2491 && (m_trWaitAvg<=20000)
2492 ) {
2493 bPlayASAP = TRUE;
2494 MSR_INTEGER(m_idDecision, 9003);
2495 }
2496#endif
2497 // We will NOT play it at once if we are grossly early. On very slow frame
2498 // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
2499 // because we got starved (for instance by the net) and dropped one frame
2500 // some time or other. If we are more than 900mSec early, then wait.
2501 if (trLate<-9000000) {
2502 bPlayASAP = FALSE;
2503 }
2504
2505 if (bPlayASAP) {
2506
2507 m_nNormal = 0;
2508 MSR_INTEGER(m_idDecision, 0);
2509 // When we are here, we are in slow-machine mode. trLate may well
2510 // oscillate between negative and positive when the supplier is
2511 // dropping frames to keep sync. We should not let that mislead
2512 // us into thinking that we have as much as zero spare time!
2513 // We just update with a zero wait.
2514 m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
2515
2516 // Assume that we draw it immediately. Update inter-frame stats
2517 m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
2518#ifndef PERF
2519 // If this is NOT a perf build, then report what we know so far
2520 // without looking at the clock any more. This assumes that we
2521 // actually wait for exactly the time we hope to. It also reports
2522 // how close we get to the manipulated time stamps that we now have
2523 // rather than the ones we originally started with. It will
2524 // therefore be a little optimistic. However it's fast.
2525 PreparePerformanceData(trTrueLate, trFrame);
2526#endif
2527 m_trLastDraw = trRealStream;
2528 if (m_trEarliness > trLate) {
2529 m_trEarliness = trLate; // if we are actually early, this is neg
2530 }
2531 Result = S_OK; // Draw it now
2532
2533 } else {
2534 ++m_nNormal;
2535 // Set the average frame rate to EXACTLY the ideal rate.
2536 // If we are exiting slow-machine mode then we will have caught up
2537 // and be running ahead, so as we slide back to exact timing we will
2538 // have a longer than usual gap at this point. If we record this
2539 // real gap then we'll think that we're running slow and go back
2540 // into slow-machine mode and vever get it straight.
2541 m_trFrameAvg = trDuration;
2542 MSR_INTEGER(m_idDecision, 1);
2543
2544 // Play it early by m_trEarliness and by m_trTarget
2545
2546 {
2547 int trE = m_trEarliness;
2548 if (trE < -m_trFrameAvg) {
2549 trE = -m_trFrameAvg;
2550 }
2551 *ptrStart += trE; // N.B. earliness is negative
2552 }
2553
2554 int Delay = -trTrueLate;
2555 Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait
2556
2557 m_trWaitAvg = trWaitAvg;
2558
2559 // Predict when it will actually be drawn and update frame stats
2560
2561 if (Result==S_FALSE) { // We are going to wait
2562 trFrame = TimeDiff(*ptrStart-m_trLastDraw);
2563 m_trLastDraw = *ptrStart;
2564 } else {
2565 // trFrame is already = trRealStream-m_trLastDraw;
2566 m_trLastDraw = trRealStream;
2567 }
2568#ifndef PERF
2569 int iAccuracy;
2570 if (Delay>0) {
2571 // Report lateness based on when we intend to play it
2572 iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf);
2573 } else {
2574 // Report lateness based on playing it *now*.
2575 iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf;
2576 }
2577 PreparePerformanceData(iAccuracy, trFrame);
2578#endif
2579 }
2580 return Result;
2581 }
2582
2583 // We are going to drop this frame!
2584 // Of course in DirectDraw mode the guy upstream may draw it anyway.
2585
2586 // This will probably give a large negative wack to the wait avg.
2587 m_trWaitAvg = trWaitAvg;
2588
2589#ifdef PERF
2590 // Respect registry setting - debug only!
2591 if (m_bDrawLateFrames) {
2592 return S_OK; // draw it when it's ready
2593 } // even though it's late.
2594#endif
2595
2596 // We are going to drop this frame so draw the next one early
2597 // n.b. if the supplier is doing direct draw then he may draw it anyway
2598 // but he's doing something funny to arrive here in that case.
2599
2600 MSR_INTEGER(m_idDecision, 2);
2601 m_nNormal = -1;
2602 return E_FAIL; // drop it
2603
2604} // ShouldDrawSampleNow
2605
2606
2607// NOTE we're called by both the window thread and the source filter thread
2608// so we have to be protected by a critical section (locked before called)
2609// Also, when the window thread gets signalled to render an image, it always
2610// does so regardless of how late it is. All the degradation is done when we
2611// are scheduling the next sample to be drawn. Hence when we start an advise
2612// link to draw a sample, that sample's time will always become the last one
2613// drawn - unless of course we stop streaming in which case we cancel links
2614
2615BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample)
2616{
2617 // We override ShouldDrawSampleNow to add quality management
2618
2619 BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample);
2620 if (bDrawImage == FALSE) {
2621 ++m_cFramesDropped;
2622 return FALSE;
2623 }
2624
2625 // m_cFramesDrawn must NOT be updated here. It has to be updated
2626 // in RecordFrameLateness at the same time as the other statistics.
2627 return TRUE;
2628}
2629
2630
2631// Implementation of IQualProp interface needed to support the property page
2632// This is how the property page gets the data out of the scheduler. We are
2633// passed into the constructor the owning object in the COM sense, this will
2634// either be the video renderer or an external IUnknown if we're aggregated.
2635// We initialise our CUnknown base class with this interface pointer. Then
2636// all we have to do is to override NonDelegatingQueryInterface to expose
2637// our IQualProp interface. The AddRef and Release are handled automatically
2638// by the base class and will be passed on to the appropriate outer object
2639
2640STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(__out int *pcFramesDropped)
2641{
2642 CheckPointer(pcFramesDropped,E_POINTER);
2643 CAutoLock cVideoLock(&m_InterfaceLock);
2644 *pcFramesDropped = m_cFramesDropped;
2645 return NOERROR;
2646} // get_FramesDroppedInRenderer
2647
2648
2649// Set *pcFramesDrawn to the number of frames drawn since
2650// streaming started.
2651
2652STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn)
2653{
2654 CheckPointer(pcFramesDrawn,E_POINTER);
2655 CAutoLock cVideoLock(&m_InterfaceLock);
2656 *pcFramesDrawn = m_cFramesDrawn;
2657 return NOERROR;
2658} // get_FramesDrawn
2659
2660
2661// Set iAvgFrameRate to the frames per hundred secs since
2662// streaming started. 0 otherwise.
2663
2664STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate)
2665{
2666 CheckPointer(piAvgFrameRate,E_POINTER);
2667 CAutoLock cVideoLock(&m_InterfaceLock);
2668
2669 int t;
2670 if (m_bStreaming) {
2671 t = timeGetTime()-m_tStreamingStart;
2672 } else {
2673 t = m_tStreamingStart;
2674 }
2675
2676 if (t<=0) {
2677 *piAvgFrameRate = 0;
2678 ASSERT(m_cFramesDrawn == 0);
2679 } else {
2680 // i is frames per hundred seconds
2681 *piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
2682 }
2683 return NOERROR;
2684} // get_AvgFrameRate
2685
2686
2687// Set *piAvg to the average sync offset since streaming started
2688// in mSec. The sync offset is the time in mSec between when the frame
2689// should have been drawn and when the frame was actually drawn.
2690
2691STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset(__out int *piAvg)
2692{
2693 CheckPointer(piAvg,E_POINTER);
2694 CAutoLock cVideoLock(&m_InterfaceLock);
2695
2696 if (NULL==m_pClock) {
2697 *piAvg = 0;
2698 return NOERROR;
2699 }
2700
2701 // Note that we didn't gather the stats on the first frame
2702 // so we use m_cFramesDrawn-1 here
2703 if (m_cFramesDrawn<=1) {
2704 *piAvg = 0;
2705 } else {
2706 *piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1));
2707 }
2708 return NOERROR;
2709} // get_AvgSyncOffset
2710
2711
2712// To avoid dragging in the maths library - a cheap
2713// approximate integer square root.
2714// We do this by getting a starting guess which is between 1
2715// and 2 times too large, followed by THREE iterations of
2716// Newton Raphson. (That will give accuracy to the nearest mSec
2717// for the range in question - roughly 0..1000)
2718//
2719// It would be faster to use a linear interpolation and ONE NR, but
2720// who cares. If anyone does - the best linear interpolation is
2721// to approximates sqrt(x) by
2722// y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
2723// 0r y = x*0.41421 + 0.59467
2724// This minimises the maximal error in the range in question.
2725// (error is about +0.008883 and then one NR will give error .0000something
2726// (Of course these are integers, so you can't just multiply by 0.41421
2727// you'd have to do some sort of MulDiv).
2728// Anyone wanna check my maths? (This is only for a property display!)
2729
2730int isqrt(int x)
2731{
2732 int s = 1;
2733 // Make s an initial guess for sqrt(x)
2734 if (x > 0x40000000) {
2735 s = 0x8000; // prevent any conceivable closed loop
2736 } else {
2737 while (s*s<x) { // loop cannot possible go more than 31 times
2738 s = 2*s; // normally it goes about 6 times
2739 }
2740 // Three NR iterations.
2741 if (x==0) {
2742 s= 0; // Wouldn't it be tragic to divide by zero whenever our
2743 // accuracy was perfect!
2744 } else {
2745 s = (s*s+x)/(2*s);
2746 if (s>=0) s = (s*s+x)/(2*s);
2747 if (s>=0) s = (s*s+x)/(2*s);
2748 }
2749 }
2750 return s;
2751}
2752
2753//
2754// Do estimates for standard deviations for per-frame
2755// statistics
2756//
2757HRESULT CBaseVideoRenderer::GetStdDev(
2758 int nSamples,
2759 __out int *piResult,
2760 LONGLONG llSumSq,
2761 LONGLONG iTot
2762)
2763{
2764 CheckPointer(piResult,E_POINTER);
2765 CAutoLock cVideoLock(&m_InterfaceLock);
2766
2767 if (NULL==m_pClock) {
2768 *piResult = 0;
2769 return NOERROR;
2770 }
2771
2772 // If S is the Sum of the Squares of observations and
2773 // T the Total (i.e. sum) of the observations and there were
2774 // N observations, then an estimate of the standard deviation is
2775 // sqrt( (S - T**2/N) / (N-1) )
2776
2777 if (nSamples<=1) {
2778 *piResult = 0;
2779 } else {
2780 LONGLONG x;
2781 // First frames have invalid stamps, so we get no stats for them
2782 // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
2783
2784 // so we use m_cFramesDrawn-1 here
2785 x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0);
2786 x = x / (nSamples-1);
2787 ASSERT(x>=0);
2788 *piResult = isqrt((LONG)x);
2789 }
2790 return NOERROR;
2791}
2792
2793// Set *piDev to the standard deviation in mSec of the sync offset
2794// of each frame since streaming started.
2795
2796STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset(__out int *piDev)
2797{
2798 // First frames have invalid stamps, so we get no stats for them
2799 // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
2800 return GetStdDev(m_cFramesDrawn - 1,
2801 piDev,
2802 m_iSumSqAcc,
2803 m_iTotAcc);
2804} // get_DevSyncOffset
2805
2806
2807// Set *piJitter to the standard deviation in mSec of the inter-frame time
2808// of frames since streaming started.
2809
2810STDMETHODIMP CBaseVideoRenderer::get_Jitter(__out int *piJitter)
2811{
2812 // First frames have invalid stamps, so we get no stats for them
2813 // So second frame gives invalid inter-frame time
2814 // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
2815 return GetStdDev(m_cFramesDrawn - 2,
2816 piJitter,
2817 m_iSumSqFrameTime,
2818 m_iSumFrameTime);
2819} // get_Jitter
2820
2821
2822// Overidden to return our IQualProp interface
2823
2824STDMETHODIMP
2825CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv)
2826{
2827 // We return IQualProp and delegate everything else
2828
2829 if (riid == IID_IQualProp) {
2830 return GetInterface( (IQualProp *)this, ppv);
2831 } else if (riid == IID_IQualityControl) {
2832 return GetInterface( (IQualityControl *)this, ppv);
2833 }
2834 return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
2835}
2836
2837
2838// Override JoinFilterGraph so that, just before leaving
2839// the graph we can send an EC_WINDOW_DESTROYED event
2840
2841STDMETHODIMP
2842CBaseVideoRenderer::JoinFilterGraph(__inout_opt IFilterGraph *pGraph, __in_opt LPCWSTR pName)
2843{
2844 // Since we send EC_ACTIVATE, we also need to ensure
2845 // we send EC_WINDOW_DESTROYED or the resource manager may be
2846 // holding us as a focus object
2847 if (!pGraph && m_pGraph) {
2848
2849 // We were in a graph and now we're not
2850 // Do this properly in case we are aggregated
2851 IBaseFilter* pFilter = this;
2852 NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0);
2853 }
2854 return CBaseFilter::JoinFilterGraph(pGraph, pName);
2855}
2856
2857
2858// This removes a large number of level 4 warnings from the
2859// Microsoft compiler which in this case are not very useful
2860#pragma warning(disable: 4514)
2861
2862#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */