home *** CD-ROM | disk | FTP | other *** search
- //==========================================================================;
- //
- // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
- // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
- // PURPOSE.
- //
- // Copyright (c) 1992 - 1996 Microsoft Corporation. All Rights Reserved.
- //
- //--------------------------------------------------------------------------;
-
-
- #include <streams.h> // ActiveMovie base class definitions
- #include <mmsystem.h> // Needed for definition of timeGetTime
- #include <limits.h> // Standard data type limit definitions
- #include <measure.h> // Used for time critical log functions
-
- #pragma warning(disable:4355)
-
- // Implements CRenderedInputPin class
- CRenderedInputPin::CRenderedInputPin(
- TCHAR *pObjectName,
- CBaseFilter *pFilter,
- CCritSec *pLock,
- HRESULT *phr,
- LPCWSTR pName) :
- CBaseInputPin(pObjectName, pFilter, pLock, phr, pName),
- m_bAtEndOfStream(FALSE),
- m_bCompleteNotified(FALSE)
- {
- }
-
- // Flush end of stream condition
- // Caller should do any necessary stream level locking before calling
- // this.
-
- STDMETHODIMP
- CRenderedInputPin::EndOfStream()
- {
- HRESULT hr = CheckStreaming();
-
- // Do EC_COMPLETE handling for rendered pins
- if (S_OK == hr && !m_bAtEndOfStream) {
- m_bAtEndOfStream = TRUE;
- FILTER_STATE fs;
- EXECUTE_ASSERT(SUCCEEDED(m_pFilter->GetState(0, &fs)));
- if (fs == State_Running) {
- DoCompleteHandling();
- }
- }
- return hr;
- }
-
- STDMETHODIMP
- CRenderedInputPin::EndFlush()
- {
- CAutoLock lck(m_pLock);
-
- // Clean up renderer state
- m_bAtEndOfStream = FALSE;
- m_bCompleteNotified = FALSE;
-
- return CBaseInputPin::EndFlush();
- }
-
- // Notify of Run() from filter
- HRESULT
- CRenderedInputPin::Run(REFERENCE_TIME tStart)
- {
- UNREFERENCED_PARAMETER(tStart);
- m_bCompleteNotified = FALSE;
- if (m_bAtEndOfStream) {
- DoCompleteHandling();
- }
- return S_OK;
- }
-
- // Clear status on going into paused state
- HRESULT
- CRenderedInputPin::Active()
- {
- m_bAtEndOfStream = FALSE;
- m_bCompleteNotified = FALSE;
- return CBaseInputPin::Active();
- }
-
- // Do stuff to deliver end of stream
- void
- CRenderedInputPin::DoCompleteHandling()
- {
- ASSERT(m_bAtEndOfStream);
- if (!m_bCompleteNotified) {
- m_bCompleteNotified = TRUE;
- m_pFilter->NotifyEvent(EC_COMPLETE, 0, 0);
- }
- }
-
-
- // Implements the CBaseRenderer class
-
- // Constructor
-
- CBaseRenderer::CBaseRenderer(CLSID RenderClass, // CLSID for this renderer
- TCHAR *pName, // Debug ONLY description
- LPUNKNOWN pUnk, // Aggregated owner object
- HRESULT *phr) : // General OLE return code
-
- CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass,phr),
- m_evComplete(TRUE),
- m_bAbort(FALSE),
- m_pPosition(NULL),
- m_ThreadSignal(TRUE),
- m_bStreaming(FALSE),
- m_bEOS(FALSE),
- m_bEOSDelivered(FALSE),
- m_pMediaSample(NULL),
- m_dwAdvise(0),
- m_pQSink(NULL),
- m_pInputPin(NULL),
- m_bRepaintStatus(TRUE)
- {
- Ready();
- #ifdef PERF
- m_idBaseStamp = MSR_REGISTER("BaseRenderer: sample time stamp");
- m_idBaseRenderTime = MSR_REGISTER("BaseRenderer: draw time (msec)");
- m_idBaseAccuracy = MSR_REGISTER("BaseRenderer: Accuracy (msec)");
- #endif
- }
-
-
- // Delete the dynamically allocated IMediaPosition and IMediaSelection helper
- // object. The object is created when somebody queries us. These are standard
- // control interfaces for seeking and setting start/stop positions and rates.
- // We will probably also have made an input pin based on CRendererInputPin
- // that has to be deleted, it's created when an enumerator calls our GetPin
-
- CBaseRenderer::~CBaseRenderer()
- {
- ASSERT(m_bStreaming == FALSE);
- StopStreaming();
- ClearPendingSample();
-
- // Delete any IMediaPosition implementation
-
- if (m_pPosition) {
- delete m_pPosition;
- m_pPosition = NULL;
- }
-
- // Delete any input pin created
-
- if (m_pInputPin) {
- delete m_pInputPin;
- m_pInputPin = NULL;
- }
-
- // Release any Quality sink
-
- ASSERT(m_pQSink == NULL);
- }
-
-
- // This returns the IMediaPosition and IMediaSelection interfaces
-
- HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid,void **ppv)
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- if (m_pPosition) {
- return m_pPosition->NonDelegatingQueryInterface(riid,ppv);
- }
-
- HRESULT hr = NOERROR;
-
- // Create implementation of this dynamically since sometimes we may
- // never try and do a seek. The helper object implements a position
- // control interface (IMediaPosition) which in fact simply takes the
- // calls normally from the filter graph and passes them upstream
-
- m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
- CBaseFilter::GetOwner(),
- (HRESULT *) &hr,
- (IPin *) GetPin(0));
- if (m_pPosition == NULL) {
- return E_OUTOFMEMORY;
- }
-
- if (FAILED(hr)) {
- delete m_pPosition;
- m_pPosition = NULL;
- return E_NOINTERFACE;
- }
- return GetMediaPositionInterface(riid,ppv);
- }
-
-
- // Overriden to say what interfaces we support and where
-
- STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid,void **ppv)
- {
- // Do we have this interface
-
- if (riid == IID_IMediaPosition || riid == IID_IMediaSelection) {
- return GetMediaPositionInterface(riid,ppv);
- } else {
- return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
- }
- }
-
-
- // This is called whenever we change states, we have a manual reset event that
- // is signalled whenever we don't won't the source filter thread to wait in us
- // (such as in a stopped state) and likewise is not signalled whenever it can
- // wait (during paused and running) this function sets or resets the thread
- // event. The event is used to stop source filter threads waiting in Receive
-
- HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait)
- {
- if (bCanWait == TRUE) {
- m_ThreadSignal.Reset();
- } else {
- m_ThreadSignal.Set();
- }
- return NOERROR;
- }
-
-
- // Wait until the clock sets the timer event or we are otherwise signalled
-
- HRESULT CBaseRenderer::WaitForRenderTime()
- {
- HANDLE WaitObjects[] = {m_ThreadSignal,m_RenderEvent};
-
- // Wait for either the time to arrive or for us to be stopped
-
- OnWaitStart();
- DWORD Result = WaitForMultipleObjects(2,WaitObjects,FALSE,INFINITE);
- OnWaitEnd();
-
- // We may have been awoken without the timer firing
-
- if (Result == WAIT_OBJECT_0) {
- return VFW_E_STATE_CHANGED;
- }
-
- SignalTimerFired();
- return NOERROR;
- }
-
-
- // A filter can have four discrete states, namely Stopped, Running, Paused,
- // Intermediate. We are in an intermediate state if we are currently trying
- // to pause but haven't yet got the first sample (or if we have been flushed
- // in paused state and therefore still have to wait for a sample to arrive)
-
- // This class contains an event called m_evComplete which is signalled when
- // the current state is completed and is not signalled when we are waiting to
- // complete the last state transition. As mentioned above the only time we
- // use this at the moment is when we wait for a media sample in paused state
- // If while we are waiting we receive an end of stream notification from the
- // source filter then we know no data is imminent so we can reset the event
- // This means that when we transition to paused the source filter must call
- // end of stream on us or send us an image otherwise we'll hang indefinately
-
-
- // Simple internal way of getting the real state, calling through the media
- // filter interface to get the state could return State_Intermediate while
- // we're waiting for an image before completing a paused state transition
- // We must be called with the main renderer critical section already locked
-
- FILTER_STATE CBaseRenderer::GetRealState() {
- return m_State;
- }
-
-
- // The renderer doesn't complete the full transition to paused states until
- // it has got one media sample to render. If you ask it for its state while
- // it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
-
- STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State)
- {
- CheckPointer(State,E_POINTER);
-
- if (m_evComplete.Wait(dwMSecs) == FALSE) {
- *State = m_State;
- return VFW_S_STATE_INTERMEDIATE;
- }
- *State = m_State;
- return NOERROR;
- }
-
-
- // If we are pausing and there's no sample waiting then don't complete
- // the transition and return S_FALSE until the first one arrives. However if
- // the m_bAbort flag has been set then all samples are rejected so there is
- // no point waiting for one. If we do have a sample then return NOERROR. At
- // the moment we'll only return State_Intermediate from Paused being called
-
- HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState)
- {
- // Allow us to be paused when disconnected
-
- if (m_pInputPin->IsConnected() == FALSE) {
- Ready();
- return S_OK;
- }
-
- // Have we run off the end of stream
-
- if (IsEndOfStream() == TRUE) {
- Ready();
- return S_OK;
- }
-
- // Make sure we get fresh data after being stopped
-
- if (HaveCurrentSample() == TRUE) {
- if (OldState != State_Stopped) {
- Ready();
- return S_OK;
- }
- }
- NotReady();
- return S_FALSE;
- }
-
-
- // When we stop the filter the things we do are:-
-
- // Decommit the allocator being used in the connection
- // Release the source filter if it's waiting in Receive
- // Cancel any advise link we set up with the clock
- // Any end of stream signalled is now obsolete so reset
- // Allow us to be stopped when we are not connected
-
- STDMETHODIMP CBaseRenderer::Stop()
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- IMemAllocator *pMemAllocator = NULL;
-
- // Make sure there really is a state change
-
- if (m_State == State_Stopped) {
- return NOERROR;
- }
-
- // Is our input pin connected
-
- if (m_pInputPin->IsConnected() == FALSE) {
- NOTE("Input pin is not connected");
- m_State = State_Stopped;
- return NOERROR;
- }
-
- CBaseFilter::Stop();
-
- // If we are going into a stopped state then we must decommit whatever
- // allocator we are using it so that any source filter waiting in the
- // GetBuffer can be released and unlock themselves for a state change
-
- m_pInputPin->GetAllocator(&pMemAllocator);
- if (pMemAllocator) {
- pMemAllocator->Decommit();
- pMemAllocator->Release();
- }
-
- // Cancel any scheduled rendering
-
- SetRepaintStatus(TRUE);
- StopStreaming();
- SourceThreadCanWait(FALSE);
- ResetEndOfStream();
- CancelNotification();
-
- Ready();
- m_bAbort = FALSE;
- return NOERROR;
- }
-
-
- // When we pause the filter the things we do are:-
-
- // Commit the allocator being used in the connection
- // Allow a source filter thread to wait in Receive
- // Cancel any clock advise link (we may be running)
- // Possibly complete the state change if we have data
- // Allow us to be paused when we are not connected
-
- STDMETHODIMP CBaseRenderer::Pause()
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- IMemAllocator *pMemAllocator = NULL;
- FILTER_STATE OldState = m_State;
-
- // Make sure there really is a state change
-
- if (m_State == State_Paused) {
- return CompleteStateChange(State_Paused);
- }
-
- // Has our input pin been connected
-
- if (m_pInputPin->IsConnected() == FALSE) {
- NOTE("Input pin is not connected");
- m_State = State_Paused;
- return CompleteStateChange(State_Paused);
- }
-
- // Pause the base filter class
-
- HRESULT hr = CBaseFilter::Pause();
- if (FAILED(hr)) {
- NOTE("Pause failed");
- return hr;
- }
-
- // Enable EC_REPAINT events again
-
- SetRepaintStatus(TRUE);
- StopStreaming();
- SourceThreadCanWait(TRUE);
- CancelNotification();
-
- // If we are going into a paused state then we must commit whatever
- // allocator we are using it so that any source filter can call the
- // GetBuffer and expect to get a buffer without returning an error
-
- m_pInputPin->GetAllocator(&pMemAllocator);
- if (pMemAllocator) {
- pMemAllocator->Commit();
- pMemAllocator->Release();
- }
-
- // When we come out of a stopped state we must clear any image we were
- // holding onto for frame refreshing. Since renderers see state changes
- // first we can reset ourselves ready to accept the source thread data
- // Paused or running after being stopped causes the current position to
- // be reset so we're not interested in passing end of stream signals
-
- if (OldState == State_Stopped) {
- m_bAbort = FALSE;
- ClearPendingSample();
- }
- return CompleteStateChange(OldState);
- }
-
-
- // When we run the filter the things we do are:-
-
- // Commit the allocator being used in the connection
- // Allow a source filter thread to wait in Receive
- // Signal the render event just to get us going
- // Start the base class by calling StartStreaming
- // Allow us to be run when we are not connected
- // Signal EC_COMPLETE if we are not connected
-
- STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime)
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- IMemAllocator *pMemAllocator = NULL;
- FILTER_STATE OldState = m_State;
-
- // Make sure there really is a state change
-
- if (m_State == State_Running) {
- return NOERROR;
- }
-
- // Send EC_COMPLETE if we're not connected
-
- if (m_pInputPin->IsConnected() == FALSE) {
- NotifyEvent(EC_COMPLETE,S_OK,0);
- m_State = State_Running;
- return NOERROR;
- }
-
- Ready();
-
- // Pause the base filter class
-
- HRESULT hr = CBaseFilter::Run(StartTime);
- if (FAILED(hr)) {
- NOTE("Run failed");
- return hr;
- }
-
- // Allow the source thread wait
-
- SourceThreadCanWait(TRUE);
- SetRepaintStatus(FALSE);
-
- // If we are going into a running state then we must commit whatever
- // allocator we are using it so that any source filter can call the
- // GetBuffer and expect to get a buffer without returning an error
-
- m_pInputPin->GetAllocator(&pMemAllocator);
- if (pMemAllocator) {
- pMemAllocator->Commit();
- pMemAllocator->Release();
- }
-
- // When we come out of a stopped state we must clear any image we were
- // holding onto for frame refreshing. Since renderers see state changes
- // first we can reset ourselves ready to accept the source thread data
- // Paused or running after being stopped causes the current position to
- // be reset so we're not interested in passing end of stream signals
-
- if (OldState == State_Stopped) {
- m_bAbort = FALSE;
- ClearPendingSample();
- }
- return StartStreaming();
- }
-
-
- // Return the number of input pins we support
-
- int CBaseRenderer::GetPinCount()
- {
- return 1;
- }
-
-
- // We only support one input pin and it is numbered zero
-
- CBasePin *CBaseRenderer::GetPin(int n)
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- HRESULT hr = NOERROR;
- ASSERT(n == 0);
-
- // Should only ever be called with zero
-
- if (n != 0) {
- return NULL;
- }
-
- // Create the input pin if not already done so
-
- if (m_pInputPin == NULL) {
- m_pInputPin = new CRendererInputPin(this,&hr,L"In");
- }
- return m_pInputPin;
- }
-
-
- // If "In" then return the IPin for our input pin, otherwise NULL and error
-
- STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, IPin **ppPin)
- {
- CheckPointer(ppPin,E_POINTER);
-
- if (0==lstrcmpW(Id,L"In")) {
- *ppPin = GetPin(0);
- ASSERT(*ppPin);
- (*ppPin)->AddRef();
- } else {
- *ppPin = NULL;
- return VFW_E_NOT_FOUND;
- }
- return NOERROR;
- }
-
-
- // Called when the input pin receives an EndOfStream notification. If we have
- // not got a sample, then notify EC_COMPLETE now. If we have samples, then set
- // m_bEOS and check for this on completing samples. If we're waiting to pause
- // then complete the transition to paused state by setting the state event
-
- HRESULT CBaseRenderer::EndOfStream()
- {
- // Ignore these calls if we are stopped
-
- if (m_State == State_Stopped) {
- return NOERROR;
- }
-
- // If we have a sample then wait for it to be rendered
-
- m_bEOS = TRUE;
- if (m_pMediaSample) {
- return NOERROR;
- }
-
- // If we are waiting for pause then we are now ready since we cannot now
- // carry on waiting for a sample to arrive since we are being told there
- // won't be any. This sets an event that the GetState function picks up
-
- Ready();
-
- // Only signal completion now if we are running otherwise queue it until
- // we do run in StartStreaming. This is used when we seek because a seek
- // causes a pause where early notification of completion is misleading
-
- if (m_bStreaming) {
- SendEndOfStream();
- }
- return NOERROR;
- }
-
-
- // When we are told to flush we should release the source thread
-
- HRESULT CBaseRenderer::BeginFlush()
- {
- // If paused then report state intermediate until we get some data
-
- if (m_State == State_Paused) {
- NotReady();
- }
-
- SourceThreadCanWait(FALSE);
- CancelNotification();
- ClearPendingSample();
- ResetEndOfStream();
- return NOERROR;
- }
-
-
- // After flushing the source thread can wait in Receive again
-
- HRESULT CBaseRenderer::EndFlush()
- {
- // Reset sample times
- if (m_pPosition) {
- m_pPosition->ResetMediaTime();
- }
- SourceThreadCanWait(TRUE);
- return NOERROR;
- }
-
-
- // We can now send EC_REPAINTs if so required
-
- HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin)
- {
- SetRepaintStatus(TRUE);
- m_bAbort = FALSE;
- return NOERROR;
- }
-
-
- // Called when we go paused or running
-
- HRESULT CBaseRenderer::Active()
- {
- return NOERROR;
- }
-
-
- // Called when we go into a stopped state
-
- HRESULT CBaseRenderer::Inactive()
- {
- if (m_pPosition) {
- m_pPosition->ResetMediaTime();
- }
- return NOERROR;
- }
-
-
- // Tell derived classes about the media type agreed
-
- HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt)
- {
- return NOERROR;
- }
-
-
- // When we break the input pin connection we should reset the EOS flags. When
- // we are asked for either IMediaPosition or IMediaSelection we will create a
- // CPosPassThru object to handles media time pass through. When we're handed
- // samples we store (by calling CPosPassThru::RegisterMediaTime) their media
- // times so we can then return a real current position of data being rendered
-
- HRESULT CBaseRenderer::BreakConnect()
- {
- // Do we have a quality management sink
-
- if (m_pQSink) {
- m_pQSink->Release();
- m_pQSink = NULL;
- }
-
- // Check we have a valid connection
-
- if (m_pInputPin->IsConnected() == FALSE) {
- return S_FALSE;
- }
-
- // Check we are stopped before disconnecting
-
- if (m_State != State_Stopped) {
- return VFW_E_NOT_STOPPED;
- }
-
- SetRepaintStatus(FALSE);
- ResetEndOfStream();
- ClearPendingSample();
- m_bAbort = FALSE;
- return NOERROR;
- }
-
-
- // Retrieves the sample times for this samples (note the sample times are
- // passed in by reference not value). We return S_FALSE to say schedule this
- // sample according to the times on the sample. We also return S_OK in
- // which case the object should simply render the sample data immediately
-
- HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample,
- REFERENCE_TIME *pStartTime,
- REFERENCE_TIME *pEndTime)
- {
- ASSERT(m_dwAdvise == 0);
- ASSERT(pMediaSample);
-
- // If the stop time for this sample is before or the same as start time,
- // then just ignore it (release it) and schedule the next one in line
- // Source filters should always fill in the start and end times properly!
-
- if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) {
- if (*pEndTime < *pStartTime) {
- return VFW_E_START_TIME_AFTER_END;
- }
- } else {
- // no time set in the sample... draw it now?
- return S_OK;
- }
-
- // Can't synchronise without a clock so we return S_OK which tells the
- // caller that the sample should be rendered immediately without going
- // through the overhead of setting a timer advise link with the clock
-
- if (m_pClock == NULL) {
- return S_OK;
- }
- return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime);
- }
-
-
- // By default all samples are drawn according to their time stamps so we
- // return S_FALSE. Returning S_OK means draw immediately, this is used
- // by the derived video renderer class in its quality management.
-
- HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
- REFERENCE_TIME *ptrStart,
- REFERENCE_TIME *ptrEnd)
- {
- return S_FALSE;
- }
-
-
- // We must always reset the current advise time to zero after a timer fires
- // because there are several possible ways which lead us not to do any more
- // scheduling such as the pending image being cleared after state changes
-
- void CBaseRenderer::SignalTimerFired()
- {
- m_dwAdvise = 0;
- }
-
-
- // Cancel any notification currently scheduled. This is called by the owning
- // window object when it is told to stop streaming. If there is no timer link
- // outstanding then calling this is benign otherwise we go ahead and cancel
-
- HRESULT CBaseRenderer::CancelNotification()
- {
- if (m_dwAdvise == 0) {
- return S_FALSE;
- }
-
- ASSERT(m_pClock);
-
- // Stop the advise link and reset the render event
-
- m_pClock->Unadvise(m_dwAdvise);
- m_RenderEvent.Reset();
- SignalTimerFired();
- return NOERROR;
- }
-
-
- // Responsible for setting up one shot advise links with the clock
- // Return FALSE if the sample is to be dropped (not drawn at all)
- // Return TRUE if the sample is to be drawn and in this case also
- // arrange for m_RenderEvent to be set at the appropriate time
-
- BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample)
- {
- REFERENCE_TIME StartSample, EndSample;
-
- // Is someone pulling our leg
-
- if (pMediaSample == NULL) {
- return FALSE;
- }
-
- // Get the next sample due up for rendering. If there aren't any ready
- // then GetNextSampleTimes returns an error. If there is one to be done
- // then it succeeds and yields the sample times. If it is due now then
- // it returns S_OK other if it's to be done when due it returns S_FALSE
-
- HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
- if (FAILED(hr)) {
- return FALSE;
- }
-
- // If we don't have a reference clock then we cannot set up the advise
- // time so we simply set the event indicating an image to render. This
- // will cause us to run flat out without any timing or synchronisation
-
- if (hr == S_OK) {
- EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
- return TRUE;
- }
-
- ASSERT(m_pClock);
-
- // We do have a valid reference clock interface so we can ask it to
- // set an event when the image comes due for rendering. We pass in
- // the reference time we were told to start at and also the current
- // stream time which is the offset from the start reference time
-
- hr = m_pClock->AdviseTime(
- (REFERENCE_TIME) m_tStart, // Start run time
- StartSample, // Stream time
- (HEVENT)(HANDLE) m_RenderEvent, // Render notification
- &m_dwAdvise); // Advise cookie
-
- if (SUCCEEDED(hr)) {
- return TRUE;
- }
-
- // We could not schedule the next sample for rendering despite the fact
- // we have a valid sample here. This is a fair indication that either
- // the system clock is wrong or the time stamp for the sample is duff
-
- ASSERT(m_dwAdvise == 0);
- return FALSE;
- }
-
-
- // The timing information we receive from the filter graph is critical to the
- // base renderer. We are given a time in the run command which we use as a
- // base time, to this time we add the next sample stream time to get a whole
- // reference time to wait for. If the run time is corrupt in some way we can
- // start waiting for stupid times way in the future which may never arrive
-
- HRESULT CBaseRenderer::DisplayTimingInfo()
- {
- // Display the time we were told to start at
-
- DbgLog((LOG_TIMING,1,TEXT("Base Renderer Timing Information")));
- DbgLog((LOG_TIMING,1,TEXT("Time from RUN command %d ms"),
- m_tStart.Millisecs()));
-
- // Have we got a reference clock to synchronise with
-
- if (m_pClock == NULL) {
- DbgLog((LOG_TIMING,1,TEXT("No clock supplied")));
- return S_FALSE;
- }
-
- #ifdef DEBUG
- // even on a debug build we only make the clock and other calls if
- // we would generate output.
- if (DbgCheckModuleLevel(LOG_TIMING, 1))
- {
- CRefTime CurrentTime,StartTime,EndTime;
-
- // Display the current time from the clock
-
- m_pClock->GetTime((REFERENCE_TIME*)&CurrentTime);
- CRefTime Offset = CurrentTime -m_tStart;
- DbgLog((LOG_TIMING,0,TEXT("Current clock time %d ms"),CurrentTime.Millisecs()));
- DbgLog((LOG_TIMING,0,TEXT("Time difference %d ms"),Offset.Millisecs()));
- #endif
-
- // Display the start time for the next available sample
- if (m_pMediaSample == NULL) {
- DbgLog((LOG_TIMING,1,TEXT("No media sample pending")));
- return S_FALSE;
- }
-
- #ifdef DEBUG
- m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
- DbgLog((LOG_TIMING,1,TEXT("Next sample stream times (Start %d End %d ms)"),
- StartTime.Millisecs(),EndTime.Millisecs()));
-
- // Calculate how long it is until it is due for rendering
-
- CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
- DbgLog((LOG_TIMING,1,TEXT("Wait required for first sample %d ms"),
- Wait.Millisecs()));
- }
- #endif
-
- return NOERROR;
- }
-
-
- // This is called when a sample comes due for rendering. We pass the sample
- // on to the derived class. After rendering we will initialise the timer for
- // the next sample, NOTE signal that the last one fired first, if we don't
- // do this it thinks there is still one outstanding that hasn't completed
-
- HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample)
- {
- // If the media sample is NULL then we will have been notified by the
- // clock that another sample is ready but in the mean time someone has
- // stopped us streaming which causes the next sample to be released
-
- if (pMediaSample == NULL) {
- return S_FALSE;
- }
-
- // If we have stopped streaming then don't render any more samples, the
- // thread that got in and locked us and then reset this flag does not
- // clear the pending sample as we can use it to refresh any output device
-
- if (m_bStreaming == FALSE) {
- return S_FALSE;
- }
-
- // Time how long the rendering takes
-
- OnRenderStart(pMediaSample);
- DoRenderSample(pMediaSample);
- OnRenderEnd(pMediaSample);
-
- return NOERROR;
- }
-
-
- // Checks if there is a sample waiting at the renderer
-
- BOOL CBaseRenderer::HaveCurrentSample()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- return (m_pMediaSample == NULL ? FALSE : TRUE);
- }
-
-
- // Returns the current sample waiting at the video renderer. We AddRef the
- // sample before returning so that should it come due for rendering the
- // person who called this method will hold the remaining reference count
- // that will stop the sample being added back onto the allocator free list
-
- IMediaSample *CBaseRenderer::GetCurrentSample()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- if (m_pMediaSample) {
- m_pMediaSample->AddRef();
- }
- return m_pMediaSample;
- }
-
-
- // Called when the source delivers us a sample. We go through a few checks to
- // make sure the sample can be rendered. If we are running (streaming) then we
- // have the sample scheduled with the reference clock, if we are not streaming
- // then we have received an sample in paused mode so we can complete any state
- // transition. On leaving this function everything will be unlocked so an app
- // thread may get in and change our state to stopped (for example) in which
- // case it will also signal the thread event so that our wait call is stopped
-
- HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample)
- {
- CAutoLock cRendererLock(&m_InterfaceLock);
- CAutoLock cSampleLock(&m_RendererLock);
-
- // Check our flushing and filter state
-
- HRESULT hr = m_pInputPin->CheckStreaming();
- if (hr != NOERROR) {
- return E_UNEXPECTED;
- }
-
- ASSERT(IsActive() == TRUE);
- ASSERT(m_pInputPin->IsFlushing() == FALSE);
- ASSERT(m_pInputPin->IsConnected() == TRUE);
- ASSERT(m_pMediaSample == NULL);
-
- // Return an error if we already have a sample waiting for rendering
- // source pins must serialise the Receive calls - we also check that
- // no data is being sent after the source signalled an end of stream
-
- if (m_pMediaSample || m_bEOS || m_bAbort) {
- Ready();
- return E_UNEXPECTED;
- }
-
- // Store the media times from this sample
- if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);
-
- // Schedule the next sample if we are streaming
-
- if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) {
- ASSERT(CancelNotification() == S_FALSE);
- return VFW_E_SAMPLE_REJECTED;
- }
-
- // BEWARE we sometimes keep the sample even after returning the thread to
- // the source filter such as when we go into a stopped state (we keep it
- // to refresh the device with) so we must AddRef it to keep it safely. If
- // we start flushing the source thread is released and any sample waiting
- // will be released otherwise GetBuffer may never return (see BeginFlush)
-
- m_pMediaSample = pMediaSample;
- m_pMediaSample->AddRef();
-
- if (m_bStreaming == FALSE) {
- SetRepaintStatus(TRUE);
- OnReceiveFirstSample(pMediaSample);
- Ready();
- }
- return NOERROR;
- }
-
-
- // Called by the source filter when we have a sample to render. Under normal
- // circumstances we set an advise link with the clock, wait for the time to
- // arrive and then render the data using the PURE virtual DoRenderSample that
- // the derived class will have overriden. After rendering the sample we may
- // also signal EOS if it was the last one sent before EndOfStream was called
-
- HRESULT CBaseRenderer::Receive(IMediaSample *pSample)
- {
- CheckPointer(pSample,E_POINTER);
- ASSERT(pSample);
-
- // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
-
- HRESULT hr = PrepareReceive(pSample);
- if (FAILED(hr)) {
- if (hr == VFW_E_SAMPLE_REJECTED) {
- return NOERROR;
- }
- return hr;
- }
-
- // Having set an advise link with the clock we sit and wait. We may be
- // awoken by the clock firing or by a state change. The rendering call
- // will lock the critical section and check we can still render the data
-
- hr = WaitForRenderTime();
- if (FAILED(hr)) {
- return NOERROR;
- }
-
- PrepareRender();
-
- // Lock the main renderer and also the sample critical section
-
- CAutoLock cRendererLock(&m_InterfaceLock);
- CAutoLock cSampleLock(&m_RendererLock);
- Render(m_pMediaSample);
- ClearPendingSample();
- SendEndOfStream();
- return NOERROR;
- }
-
-
- // This is called when we stop or are inactivated to clear the pending sample
- // We release the media sample interface so that they can be allocated to the
- // source filter again, unless of course we are changing state to inactive in
- // which case GetBuffer will return an error. We must also reset the current
- // media sample to NULL so that we know we do not currently have an image
-
- HRESULT CBaseRenderer::ClearPendingSample()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- if (m_pMediaSample) {
- m_pMediaSample->Release();
- m_pMediaSample = NULL;
- }
- return NOERROR;
- }
-
-
- // If we are at the end of the stream signal the filter graph but do not set
- // the state flag back to FALSE. Once we drop off the end of the stream we
- // leave the flag set (until a subsequent ResetEndOfStream) so that we know
- // when refreshing the window whether we will have an image to cover with
-
- HRESULT CBaseRenderer::SendEndOfStream()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- if (m_bEOS == FALSE || m_bEOSDelivered == TRUE) {
- return NOERROR;
- }
-
- // If we've been using the IMediaPosition interface, set it's start
- // and end media "times" to the stop position by hand. This ensures
- // that we actually get to the end, even if the MPEG guestimate has
- // been bad or if we managed to drop the last few frames.
- if (m_pPosition) m_pPosition->EOS();
-
- m_bEOSDelivered = TRUE;
- return NotifyEvent(EC_COMPLETE,S_OK,0);
- }
-
-
- // Reset the end of stream flag, this is typically called when we transfer to
- // stopped states since that resets the current position back to the start so
- // we will receive more samples or another EndOfStream if there aren't any. We
- // keep two separate flags one to say we have run off the end of the stream
- // (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
- // to the filter graph. We need the latter otherwise we can end up sending an
- // EC_COMPLETE every time the source changes state and calls our EndOfStream
-
- HRESULT CBaseRenderer::ResetEndOfStream()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- m_bEOS = FALSE;
- m_bEOSDelivered = FALSE;
- return NOERROR;
- }
-
-
- // This is called when we start running so that we can schedule any pending
- // image we have with the clock and display any timing information. If we
- // don't have any sample but we have queued an EOS flag then we send it. If
- // we do have a sample then we wait until that has been rendered before we
- // signal the filter graph otherwise we may change state before it's done
-
- HRESULT CBaseRenderer::StartStreaming()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- if (m_bStreaming == TRUE) {
- return NOERROR;
- }
-
- // Reset the streaming times ready for running
-
- m_bStreaming = TRUE;
- DisplayTimingInfo();
- timeBeginPeriod(1);
- OnStartStreaming();
-
- // If we have an EOS and no data then deliver it now
-
- if (m_pMediaSample == NULL) {
- return SendEndOfStream();
- }
-
- // Have the data rendered
-
- ASSERT(m_pMediaSample);
- m_RenderEvent.Set();
- return NOERROR;
- }
-
-
- // This is called when we stop streaming so that we can set our internal flag
- // indicating we are not now to schedule any more samples arriving. The state
- // change methods in the filter implementation take care of cancelling any
- // clock advise link we have set up and clearing any pending sample we have
-
- HRESULT CBaseRenderer::StopStreaming()
- {
- CAutoLock cRendererLock(&m_RendererLock);
- m_bEOSDelivered = FALSE;
-
- if (m_bStreaming == TRUE) {
- m_bStreaming = FALSE;
- OnStopStreaming();
- timeEndPeriod(1);
- }
- return NOERROR;
- }
-
-
- // We have a boolean flag that is reset when we have signalled EC_REPAINT to
- // the filter graph. We set this when we receive an image so that should any
- // conditions arise again we can send another one. By having a flag we ensure
- // we don't flood the filter graph with redundant calls. We do not set the
- // event when we receive an EndOfStream call since there is no point in us
- // sending further EC_REPAINTs. In particular the AutoShowWindow method and
- // the DirectDraw object use this method to control the window repainting
-
- void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
- {
- CAutoLock cSampleLock(&m_RendererLock);
- m_bRepaintStatus = bRepaint;
- }
-
-
- // Signal an EC_REPAINT to the filter graph. This can be used to have data
- // sent to us. For example when a video window is first displayed it may
- // not have an image to display, at which point it signals EC_REPAINT. The
- // filtergraph will either pause the graph if stopped or if already paused
- // it will call put_CurrentPosition of the current position. Setting the
- // current position to itself has the stream flushed and the image resent
-
- #define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
-
- void CBaseRenderer::SendRepaint()
- {
- CAutoLock cSampleLock(&m_RendererLock);
- ASSERT(m_pInputPin);
-
- // We should not send repaint notifications when...
- // - An end of stream has been notified
- // - Our input pin is being flushed
- // - The input pin is not connected
- // - We have aborted a video playback
- // - There is a repaint already sent
-
- if (m_bAbort == FALSE) {
- if (m_pInputPin->IsConnected() == TRUE) {
- if (m_pInputPin->IsFlushing() == FALSE) {
- if (IsEndOfStream() == FALSE) {
- if (m_bRepaintStatus == TRUE) {
- IPin *pPin = (IPin *) m_pInputPin;
- NotifyEvent(EC_REPAINT,(long) pPin,0);
- SetRepaintStatus(FALSE);
- ClearPendingSample();
- RLOG("Sending repaint");
- }
- }
- }
- }
- }
- }
-
-
- // When a video window detects a display change (WM_DISPLAYCHANGE message) it
- // can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
- // filtergraph will stop everyone and reconnect our input pin. As we're then
- // reconnected we can accept the media type that matches the new display mode
- // since we may no longer be able to draw the current image type efficiently
-
- BOOL CBaseRenderer::OnDisplayChange()
- {
- // Ignore if we are not connected yet
-
- CAutoLock cSampleLock(&m_RendererLock);
- if (m_pInputPin->IsConnected() == FALSE) {
- return FALSE;
- }
-
- RLOG("Notification of EC_DISPLAY_CHANGE");
-
- // Pass our input pin as parameter on the event
-
- IPin *pPin = (IPin *) m_pInputPin;
- m_pInputPin->AddRef();
- NotifyEvent(EC_DISPLAY_CHANGED,(long) pPin,0);
- SetAbortSignal(TRUE);
- ClearPendingSample();
- m_pInputPin->Release();
-
- return TRUE;
- }
-
- // Called just before we start drawing.
- // Store the current time in m_trRenderStart to allow the rendering time to be
- // logged. Log the time stamp of the sample and how late it is (neg is early)
-
- void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
- {
- #ifdef PERF
- REFERENCE_TIME trStart, trEnd;
- pMediaSample->GetTime(&trStart, &trEnd);
-
- MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
-
- m_pClock->GetTime(&m_trRenderStart);
- MSR_INTEGER(0, (int)m_trRenderStart);
- REFERENCE_TIME trStream;
- trStream = m_trRenderStart-m_tStart; // convert reftime to stream time
- MSR_INTEGER(0,(int)trStream);
-
- const int trLate = (int)(trStream - trStart);
- MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec
- #endif
-
- } // OnRenderStart
-
-
- // Called directly after drawing an image.
- // calculate the time spent drawing and log it.
-
- void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
- {
- #ifdef PERF
- REFERENCE_TIME trNow;
- m_pClock->GetTime(&trNow);
- MSR_INTEGER(0,(int)trNow);
- int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec
- MSR_INTEGER(m_idBaseRenderTime, t);
- #endif
- } // OnRenderEnd
-
-
-
-
- // Constructor must be passed the base renderer object
-
- CRendererInputPin::CRendererInputPin(CBaseRenderer *pRenderer,
- HRESULT *phr,
- LPCWSTR pPinName) :
- CBaseInputPin(NAME("Renderer pin"),
- pRenderer,
- &pRenderer->m_InterfaceLock,
- (HRESULT *) phr,
- pPinName)
- {
- m_pRenderer = pRenderer;
- ASSERT(m_pRenderer);
- }
-
-
- // Signals end of data stream on the input pin
-
- STDMETHODIMP CRendererInputPin::EndOfStream()
- {
- CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
- CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
-
- // Make sure we're streaming ok
-
- HRESULT hr = CheckStreaming();
- if (hr != NOERROR) {
- return hr;
- }
-
- // Pass it onto the renderer
-
- hr = m_pRenderer->EndOfStream();
- if (SUCCEEDED(hr)) {
- hr = CBaseInputPin::EndOfStream();
- }
- return hr;
- }
-
-
- // Signals start of flushing on the input pin
-
- STDMETHODIMP CRendererInputPin::BeginFlush()
- {
- CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
- CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
-
- HRESULT hr = m_pRenderer->BeginFlush();
- if (SUCCEEDED(hr)) {
- hr = CBaseInputPin::BeginFlush();
- }
- return hr;
- }
-
-
- // Signals end of flushing on the input pin
-
- STDMETHODIMP CRendererInputPin::EndFlush()
- {
- CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
- CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
-
- HRESULT hr = m_pRenderer->EndFlush();
- if (SUCCEEDED(hr)) {
- hr = CBaseInputPin::EndFlush();
- }
- return hr;
- }
-
-
- // Pass the sample straight through to the renderer object
-
- STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample)
- {
- CheckPointer(pSample,E_POINTER);
- AM_MEDIA_TYPE *pMediaType;
- HRESULT hr = NOERROR;
-
- // Let the base class check we are ok
-
- hr = CBaseInputPin::Receive(pSample);
- if (FAILED(hr)) {
- return hr;
- }
-
- // Has the type changed on a media sample. We do all rendering
- // synchronously on the source thread, which has a side effect
- // that only one buffer is ever outstanding. Therefore when we
- // have Receive called we can go ahead and change the format
-
- pSample->GetMediaType(&pMediaType);
- if (pMediaType) {
- CMediaType cmt(*pMediaType);
- SetMediaType(&cmt);
- DeleteMediaType(pMediaType);
- }
- return m_pRenderer->Receive(pSample);
- }
-
-
- // Called when the input pin is disconnected
-
- HRESULT CRendererInputPin::BreakConnect()
- {
- HRESULT hr = m_pRenderer->BreakConnect();
- if (FAILED(hr)) {
- return hr;
- }
- return CBaseInputPin::BreakConnect();
- }
-
-
- // Called when the input pin is connected
-
- HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin)
- {
- HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
- if (FAILED(hr)) {
- return hr;
- }
- return CBaseInputPin::CompleteConnect(pReceivePin);
- }
-
-
- // Give the pin id of our one and only pin
-
- STDMETHODIMP CRendererInputPin::QueryId(LPWSTR *Id)
- {
- CheckPointer(Id,E_POINTER);
-
- *Id = (LPWSTR)CoTaskMemAlloc(8);
- if (*Id == NULL) {
- return E_OUTOFMEMORY;
- }
- lstrcpyW(*Id, L"In");
- return NOERROR;
- }
-
-
- // Will the filter accept this media type
-
- HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
- {
- return m_pRenderer->CheckMediaType(pmt);
- }
-
-
- // Called when we go paused or running
-
- HRESULT CRendererInputPin::Active()
- {
- return m_pRenderer->Active();
- }
-
-
- // Called when we go into a stopped state
-
- HRESULT CRendererInputPin::Inactive()
- {
- return m_pRenderer->Inactive();
- }
-
-
- // Tell derived classes about the media type agreed
-
- HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt)
- {
- HRESULT hr = CBaseInputPin::SetMediaType(pmt);
- if (FAILED(hr)) {
- return hr;
- }
- return m_pRenderer->SetMediaType(pmt);
- }
-
-
- // We do not keep an event object to use when setting up a timer link with
- // the clock but are given a pointer to one by the owning object through the
- // SetNotificationObject method - this must be initialised before starting
- // We can override the default quality management process to have it always
- // draw late frames, this is currently done by having the following registry
- // key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
-
- const LPTSTR AMQUALITY = TEXT("ActiveMovie");
- const LPTSTR DRAWLATEFRAMES = TEXT("DrawLateFrames");
-
- CBaseVideoRenderer::CBaseVideoRenderer(
- CLSID RenderClass, // CLSID for this renderer
- TCHAR *pName, // Debug ONLY description
- LPUNKNOWN pUnk, // Aggregated owner object
- HRESULT *phr) : // General OLE return code
-
- CBaseRenderer(RenderClass,pName,pUnk,phr),
- m_cFramesDropped(0),
- m_cFramesDrawn(0),
- m_bSupplierHandlingQuality(FALSE)
- {
- ResetStreamingTimes();
-
- m_idTimeStamp = MSR_REGISTER("Frame time stamp");
- m_idEarliness = MSR_REGISTER("Earliness fudge");
- m_idTarget = MSR_REGISTER("Target (mSec)");
- m_idSchLateTime = MSR_REGISTER("mSec late when scheduled");
- m_idDecision = MSR_REGISTER("Scheduler decision code");
- m_idQualityRate = MSR_REGISTER("Quality rate sent");
- m_idQualityTime = MSR_REGISTER("Quality time sent");
- m_idWaitReal = MSR_REGISTER("Render wait");
- // m_idWait = MSR_REGISTER("wait time recorded (msec)");
- m_idFrameAccuracy = MSR_REGISTER("Frame accuracy (msecs)");
- m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
- //m_idSendQuality = MSR_REGISTER("Processing Quality message");
-
- m_idRenderAvg = MSR_REGISTER("Render draw time Avg");
- m_idFrameAvg = MSR_REGISTER("FrameAvg");
- m_idWaitAvg = MSR_REGISTER("WaitAvg");
- m_idDuration = MSR_REGISTER("Duration");
- m_idThrottle = MSR_REGISTER("Audio-video throttle wait");
- // m_idDebug = MSR_REGISTER("Debug stuff");
- } // Constructor
-
-
- // Destructor is just a placeholder
-
- CBaseVideoRenderer::~CBaseVideoRenderer()
- {
- ASSERT(m_dwAdvise == 0);
- }
-
-
- // The timing functions in this class are called by the window object and by
- // the renderer's allocator.
- // The windows object calls timing functions as it receives media sample
- // images for drawing using GDI.
- // The allocator calls timing functions when it starts passing DCI/DirectDraw
- // surfaces which are not rendered in the same way; The decompressor writes
- // directly to the surface with no separate rendering, so those code paths
- // call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
- // when we have allocated one and only one image we know there cannot be any
- // conflict between the two.
- //
- // We use timeGetTime to return the timing counts we use (since it's relative
- // performance we are interested in rather than absolute compared to a clock)
- // The window object sets the accuracy of the system clock (normally 1ms) by
- // calling timeBeginPeriod/timeEndPeriod when it changes streaming states
-
-
- // Reset all times controlling streaming.
- // Set them so that
- // 1. Frames will not initially be dropped
- // 2. The first frame will definitely be drawn (achieved by saying that there
- // has not ben a frame drawn for a long time).
-
- HRESULT CBaseVideoRenderer::ResetStreamingTimes()
- {
- m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago
- m_tStreamingStart = timeGetTime();
- m_trRenderAvg = 0;
- m_trFrameAvg = -1; // -1000 fps == "unset"
- m_trDuration = 0; // 0 - silly value
- m_trRenderLast = 0;
- m_trWaitAvg = 0;
- m_tRenderStart = 0;
- m_cFramesDrawn = 0;
- m_cFramesDropped = 0;
- m_iTotAcc = 0;
- m_iSumSqAcc = 0;
- m_iSumSqFrameTime = 0;
- m_trFrame = 0; // hygeine - not really needed
- m_trLate = 0; // hygeine - not really needed
- m_iSumFrameTime = 0;
- m_nNormal = 0;
- m_trEarliness = 0;
- m_trTarget = -300000; // 30mSec early
- m_trThrottle = 0;
- m_trRememberStampForPerf = 0;
-
- #ifdef PERF
- m_trRememberFrameForPerf = 0;
- #endif
-
- return NOERROR;
- } // ResetStreamingTimes
-
-
- // Reset all times controlling streaming. Note that we're now streaming. We
- // don't need to set the rendering event to have the source filter released
- // as it is done during the Run processing. When we are run we immediately
- // release the source filter thread and draw any image waiting (that image
- // may already have been drawn once as a poster frame while we were paused)
-
- HRESULT CBaseVideoRenderer::OnStartStreaming()
- {
- ResetStreamingTimes();
- return NOERROR;
- } // OnStartStreaming
-
-
- // Called at end of streaming. Fixes times for property page report
-
- HRESULT CBaseVideoRenderer::OnStopStreaming()
- {
- m_tStreamingStart = timeGetTime()-m_tStreamingStart;
- return NOERROR;
- } // OnStopStreaming
-
-
- // Called when we start waiting for a rendering event.
- // Used to update times spent waiting and not waiting.
-
- void CBaseVideoRenderer::OnWaitStart()
- {
- MSR_START(m_idWaitReal);
- } // OnWaitStart
-
-
- // Called when we are awoken from the wait in the window OR by our allocator
- // when it is hanging around until the next sample is due for rendering on a
- // DCI/DirectDraw surface. We add the wait time into our rolling average.
- // We grab the interface lock so that we're serialised with the application
- // thread going through the run code - which in due course ends up calling
- // ResetStreaming times - possibly as we run through this section of code
-
- void CBaseVideoRenderer::OnWaitEnd()
- {
- #ifdef PERF
- CAutoLock cVideoLock(&m_InterfaceLock);
- MSR_STOP(m_idWaitReal);
- // for a perf build we want to know just exactly how late we REALLY are.
- // even if this means that we have to look at the clock again.
-
- REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
- #if 0
- m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
- #else
- // We will be discarding overflows like mad here!
- REFERENCE_TIME tr = timeGetTime()*10000;
- trRealStream = tr + m_llTimeOffset;
- #endif
- trRealStream -= m_tStart; // convert to stream time (this is a reftime)
-
- if (m_trRememberStampForPerf==0) {
- // This is probably the poster frame at the start, and it is not scheduled
- // in the usual way at all. Just count it. The rememberstamp gets set
- // in ShouldDrawSampleNow, so this does bogus frame recording until we
- // actually start playing.
- PreparePerformanceData(0, 0);
- } else {
- int trLate = (int)(trRealStream - m_trRememberStampForPerf);
- int trFrame = (int)(tr - m_trRememberFrameForPerf);
- PreparePerformanceData(trLate, trFrame);
- }
- m_trRememberFrameForPerf = tr;
- #endif //PERF
- } // OnWaitEnd
-
-
- // Put data on one side that describes the lateness of the current frame.
- // We don't yet know whether it will actually be drawn. In direct draw mode,
- // this decision is up to the filter upstream, and it could change its mind.
- // The rules say that if it did draw it must call Receive(). One way or
- // another we eventually get into either OnRenderStart or OnDirectRender and
- // these both call RecordFrameLateness to update the statistics.
-
- void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame)
- {
- m_trLate = trLate;
- m_trFrame = trFrame;
- MSR_INTEGER(m_idTarget, m_trTarget/10000);
- m_trTarget -= trLate/32;
- } // PreparePerformanceData
-
-
-
- // update the statistics:
- // m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
- // Note that because the properties page reports using these variables,
- // 1. We need to be inside a critical section
- // 2. They must all be updated together. Updating the sums here and the count
- // elsewhere can result in imaginary jitter (i.e. attempts to find square roots
- // of negative numbers) in the property page code.
-
- void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame)
- {
- // Record how timely we are.
- int tLate = trLate/10000;
-
- // Best estimate of moment of appearing on the screen is average of
- // start and end draw times. Here we have only the end time. This may
- // tend to show us as spuriously late by up to 1/2 frame rate achieved.
- // Decoder probably monitors draw time. We don't bother.
- MSR_INTEGER( m_idFrameAccuracy, tLate );
-
- // This is a hack - we can get frames that are ridiculously late
- // especially (at start-up) and they sod up the statistics.
- // So ignore things that are more than 1 sec off.
- if (tLate>1000 || tLate<-1000) {
- if (m_cFramesDrawn<=1) {
- tLate = 0;
- } else if (tLate>0) {
- tLate = 1000;
- } else {
- tLate = -1000;
- }
- }
- // The very first frame often has a bogus time, so I'm just
- // not going to count it into the statistics. ???
- if (m_cFramesDrawn>1) {
- m_iTotAcc += tLate;
- m_iSumSqAcc += (tLate*tLate);
- }
-
- // calculate inter-frame time. Doesn't make sense for first frame
- // second frame suffers from bogus first frame stamp.
- if (m_cFramesDrawn>2) {
- int tFrame = trFrame/10000; // convert to mSec else it overflows
- // This is a hack. It can overflow anyway (a pause can cause
- // a very long inter-frame time) and it overflows at 2**31/10**7
- // or about 215 seconds i.e. 3min 35sec
- if (tFrame>1000||tFrame<0) tFrame = 1000;
- m_iSumSqFrameTime += tFrame*tFrame;
- ASSERT(m_iSumSqFrameTime>=0);
- m_iSumFrameTime += tFrame;
- }
- ++m_cFramesDrawn;
-
- } // RecordFrameLateness
-
-
- void CBaseVideoRenderer::ThrottleWait()
- {
- if (m_trThrottle>0) {
- int iThrottle = m_trThrottle/10000; // convert to mSec
- MSR_INTEGER( m_idThrottle, iThrottle);
- Sleep(iThrottle);
- }
- } // ThrottleWait
-
-
- // Whenever a frame is rendered it goes though either OnRenderStart
- // or OnDirectRender. Data that are generated during ShouldDrawSample
- // are added to the statistics by calling RecordFrameLateness from both
- // these two places.
-
- // Called in place of OnRenderStart..OnRenderEnd
- // When a DirectDraw image is drawn
- void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample)
- {
- int time = 0;
- m_trRenderAvg = 0;
- m_trRenderLast = 5000000; // If we mode switch, we do NOT want this
- // to inhibit the new average getting going!
- // so we set it to half a second
- // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
- RecordFrameLateness(m_trLate, m_trFrame);
- ThrottleWait();
- } // OnDirectRender
-
-
- // Called just before we start drawing. All we do is to get the current clock
- // time (from the system) and return. We have to store the start render time
- // in a member variable because it isn't used until we complete the drawing
- // The rest is just performance logging.
-
- void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample)
- {
- RecordFrameLateness(m_trLate, m_trFrame);
- m_tRenderStart = timeGetTime();
- } // OnRenderStart
-
-
- // Called directly after drawing an image. We calculate the time spent in the
- // drawing code and if this doesn't appear to have any odd looking spikes in
- // it then we add it to the current average draw time. Measurement spikes may
- // occur if the drawing thread is interrupted and switched to somewhere else.
-
- void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample)
- {
- // The renderer time can vary erratically if we are interrupted so we do
- // some smoothing to help get more sensible figures out but even that is
- // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
-
- int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS
- if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
- // DO_MOVING_AVG(m_trRenderAvg, tr);
- m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
- }
- m_trRenderLast = tr;
- ThrottleWait();
- } // OnRenderEnd
-
-
- STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc)
- {
-
- m_pQSink = piqc;
-
- return NOERROR;
- } // SetSink
-
- STDMETHODIMP CBaseVideoRenderer::Notify( IFilter * pSelf, Quality q)
- {
- // NOTE: We are NOT getting any locks here. We could be called
- // asynchronously and possibly even on a time critical thread of
- // someone else's - so we do the minumum. We only set one state
- // variable (an integer) and if that happens to be in the middle
- // of another thread reading it they will just get either the new
- // or the old value. Locking would achieve no more than this.
-
- // It might be nice to check that we are being called from m_pGraph, but
- // it turns out to be a millisecond or so per throw!
-
- // This is heuristics, these numbers are aimed at being "what works"
- // rather than anything based on some theory.
- // We use a hyperbola because it's easy to calculate and it includes
- // a panic button asymptote (which we push off just to the left)
- // The throttling fits the following table (roughly)
- // Proportion Throttle (msec)
- // >=1000 0
- // 900 3
- // 800 7
- // 700 11
- // 600 17
- // 500 25
- // 400 35
- // 300 50
- // 200 72
- // 125 100
- // 100 112
- // 50 146
- // 0 200
-
- // (some evidence that we could go for a sharper kink - e.g. no throttling
- // until below the 750 mark - might give fractionally more frames on a
- // P60-ish machine). The easy way to get these coefficients is to use
- // Renbase.xls follow the instructions therein using excel solver.
-
- if (q.Proportion>=1000) { m_trThrottle = 0; }
- else {
- // The DWORD is to make quite sure I get unsigned arithmetic
- // as the constant is between 2**31 and 2**32
- m_trThrottle = -330000 + (388880000/(q.Proportion+167));
- }
- return NOERROR;
- } // Notify
-
-
- // Send a message to indicate what our supplier should do about quality.
- // Theory:
- // What a supplier wants to know is "is the frame I'm working on NOW
- // going to be late?".
- // F1 is the frame at the supplier (as above)
- // Tf1 is the due time for F1
- // T1 is the time at that point (NOW!)
- // Tr1 is the time that f1 WILL actually be rendered
- // L1 is the latency of the graph for frame F1 = Tr1-T1
- // D1 (for delay) is how late F1 will be beyond its due time i.e.
- // D1 = (Tr1-Tf1) which is what the supplier really wants to know.
- // Unfortunately Tr1 is in the future and is unknown, so is L1
- //
- // We could estimate L1 by its value for a previous frame,
- // L0 = Tr0-T0 and work off
- // D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
- // Rearranging terms:
- // D1' = (T1-T0) + (Tr0-Tf1)
- // adding (Tf0-Tf0) and rearranging again:
- // = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
- // = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
- // But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
- // Late field in the quality message that we send.
- // The other two terms just state what correction should be applied before
- // using the lateness of F0 to predict the lateness of F1.
- // (T1-T0) says how much time has actually passed (we have lost this much)
- // (Tf1-Tf0) says how much time should have passed if we were keeping pace
- // (we have gained this much).
- //
- // Suppliers should therefore work off:
- // Quality.Late + (T1-T0) - (Tf1-Tf0)
- // and see if this is "acceptably late" or even early (i.e. negative).
- // They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
- // the time stamps in the frames. They get Quality.Late from us.
- //
-
- HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate,
- REFERENCE_TIME trRealStream)
- {
- Quality q;
- HRESULT hr;
-
- // If we are the main user of time, then report this as Flood/Dry.
- // If our suppliers are, then report it as Famine/Glut.
- //
- // We need to take action, but avoid hunting. Hunting is caused by
- // 1. Taking too much action too soon and overshooting
- // 2. Taking too long to react (so averaging can CAUSE hunting).
- //
- // The reason why we use trLate as well as Wait is to reduce hunting;
- // if the wait time is coming down and about to go into the red, we do
- // NOT want to rely on some average which is only telling is that it used
- // to be OK once.
-
- q.TimeStamp = (REFERENCE_TIME)trRealStream;
-
- if (m_trFrameAvg<0) {
- q.Type = Famine; // guess
- }
- // Is the greater part of the time taken bltting or something else
- else if (m_trFrameAvg > 2*m_trRenderAvg) {
- q.Type = Famine; // mainly other
- } else {
- q.Type = Flood; // mainly bltting
- }
-
- q.Proportion = 1000; // default
-
- if (m_trFrameAvg<0) {
- // leave it alone - we don't know enough
- }
- else if ( trLate> 0 ) {
- // try to catch up over the next second
- // We could be Really, REALLY late, but rendering all the frames
- // anyway, just because it's so cheap.
-
- q.Proportion = 1000 - (int)((1000*trLate)/UNITS);
- if (q.Proportion<500) {
- q.Proportion = 500; // don't go daft. (could've been negative!)
- } else {
- }
-
- } else if ( m_trWaitAvg>20000
- && trLate<-20000
- ){
- // Go cautiously faster - aim at 2mSec wait.
- if (m_trWaitAvg>=m_trFrameAvg) {
- // This can happen because of some fudges.
- // The waitAvg is how long we originally planned to wait
- // The frameAvg is more honest.
- // It means that we are spending a LOT of time waiting
- q.Proportion = 2000; // double.
- } else {
- if (m_trFrameAvg+20000 > m_trWaitAvg) {
- q.Proportion
- = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
- } else {
- // We're apparently spending more than the whole frame time waiting.
- // Assume that the averages are slightly out of kilter, but that we
- // are indeed doing a lot of waiting. (This leg probably never
- // happens, but the code avoids any potential divide by zero).
- q.Proportion = 2000;
- }
- }
-
- if (q.Proportion>2000) {
- q.Proportion = 2000; // don't go crazy.
- }
- }
-
-
- // Tell the supplier how late frames are when they get rendered
- // That's how late we are now.
- // If we are in directdraw mode then the guy upstream can see the drawing
- // times and we'll just report on the start time. He can figure out any
- // offset to apply. If we are in DIB Section mode then we will apply an
- // extra offset which is half of our drawing time. This is usually small
- // but can sometimes be the dominant effect. For this we will use the
- // average drawing time rather than the last frame. If the last frame took
- // a long time to draw and made us late, that's already in the lateness
- // figure. We should not add it in again unless we expect the next frame
- // to be the same. We don't, we expect the average to be a better shot.
- // In direct draw mode the RenderAvg will be zero.
-
- q.Late = trLate + m_trRenderAvg/2;
-
- // log what we're doing
- MSR_INTEGER(m_idQualityRate, q.Proportion);
- MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );
-
- // A specific sink interface may be set through IPin
-
- if (m_pQSink==NULL) {
- // Get our input pin's peer. We send quality management messages
- // to any nominated receiver of these things (set in the IPin
- // interface), or else to our source filter.
-
- IQualityControl *pQC = NULL;
- IPin *pOutputPin = m_pInputPin->GetConnected();
- ASSERT(pOutputPin != NULL);
-
- // And get an AddRef'd quality control interface
-
- hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC);
- if (SUCCEEDED(hr)) {
- m_pQSink = pQC;
- }
- }
- if (m_pQSink) {
- return m_pQSink->Notify(this,q);
- }
-
- return S_FALSE;
-
- } // SendQuality
-
-
- // We are called with a valid IMediaSample image to decide whether this is to
- // be drawn or not. There must be a reference clock in operation.
- // Return S_OK if it is to be drawn Now (as soon as possible)
- // Return S_FALSE if it is to be drawn when it's due
- // Return an error if we want to drop it
- // m_nNormal=-1 indicates that we dropped the previous frame and so this
- // one should be drawn early. Respect it and update it.
- // Use current stream time plus a number of heuristics (detailed below)
- // to make the decision
-
- HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
- REFERENCE_TIME *ptrStart,
- REFERENCE_TIME *ptrEnd)
- {
-
- // Don't call us unless there's a clock interface to synchronise with
- ASSERT(m_pClock);
-
- MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits
- MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits
-
- // We lose a bit of time depending on the monitor type waiting for the next
- // screen refresh. On average this might be about 8mSec - so it will be
- // later than we think when the picture appears. To compensate a bit
- // we bias the media samples by -8mSec i.e. 80000 UNITs.
- // We don't ever make a stream time negative (call it paranoia)
- if (*ptrStart>=80000) {
- *ptrStart -= 80000;
- *ptrEnd -= 80000; // bias stop to to retain valid frame duration
- }
-
- // Cache the time stamp now. We will want to compare what we did with what
- // we started with (after making the monitor allowance).
- m_trRememberStampForPerf = *ptrStart;
-
- // Get reference times (current and late)
- REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
- m_pClock->GetTime(&trRealStream);
- #ifdef PERF
- // While the reference clock is expensive:
- // Remember the offset from timeGetTime and use that.
- // This overflows all over the place, but when we subtract to get
- // differences the overflows all cancel out.
- m_llTimeOffset = trRealStream-timeGetTime()*10000;
- #endif
- trRealStream -= m_tStart; // convert to stream time (this is a reftime)
-
- // We have to wory about two versions of "lateness". The truth, which we
- // try to work out here and the one measured against m_trTarget which
- // includes long term feedback. We report statistics against the truth
- // but for operational decisions we work to the target.
- const int trTrueLate = (int)(trRealStream - *ptrStart);
- const int trLate = trTrueLate-m_trTarget;
-
- MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);
-
- // Send quality control messages upstream, measured against target
- HRESULT hr = SendQuality(trLate, trRealStream);
- // Note: the filter upstream is allowed to this FAIL meaning "you do it".
- m_bSupplierHandlingQuality = (hr==S_OK);
-
- // Decision time! Do we drop, draw when ready or draw immediately?
-
- const int trDuration = (int)(*ptrEnd - *ptrStart);
- {
- // We need to see if the frame rate of the file has just changed.
- // This would make comparing our previous frame rate with the current
- // frame rate silly. Hang on a moment though. I've seen files
- // where the frames vary between 33 and 34 mSec so as to average
- // 30fps. A minor variation like that won't hurt us.
- int t = m_trDuration/32;
- if ( trDuration > m_trDuration+t
- || trDuration < m_trDuration-t
- ) {
- // There's a major variation. Reset the average frame rate to
- // exactly the current rate to disable decision 9002 for this frame,
- // and remember the new rate.
- m_trFrameAvg = trDuration;
- m_trDuration = trDuration;
- }
- }
-
- MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
- MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
- MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
- MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
- MSR_INTEGER(m_idDuration, trDuration/10000);
-
- #ifdef PERF
- if (S_OK==pMediaSample->IsDiscontinuity()) {
- MSR_INTEGER(m_idDecision, 9000);
- }
- #endif
-
- // Control the graceful slide back from slow to fast machine mode.
- // After a frame drop accept an early frame and set the earliness to here
- // If this frame is already later than the earliness then slide it to here
- // otherwise do the standard slide (reduce by about 12% per frame).
- // Note: earliness is normally NEGATIVE
- BOOL bJustDroppedFrame
- = ( m_bSupplierHandlingQuality
- && S_OK==pMediaSample->IsDiscontinuity() // he just dropped one
- )
- || (m_nNormal==-1); // we just dropped one
-
-
- // Set m_trEarliness (slide back from slow to fast machine mode)
- if (trLate>0) {
- m_trEarliness = 0; // we are no longer in fast machine mode at all!
- } else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) {
- m_trEarliness = trLate; // Things have slipped of their own accord
- } else {
- m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide
- }
-
- // prepare the new wait average - but don't pollute the old one until
- // we have finished with it.
- int trWaitAvg;
- {
- // We never mix in a negative wait. This causes us to believe in fast machines
- // slightly more.
- int trL = trLate<0 ? -trLate : 0;
- trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
- }
-
-
- int trFrame;
- {
- REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
- if (tr>10000000) {
- tr = 10000000; // 1 second - arbitrarily.
- }
- trFrame = int(tr);
- }
-
- // We will DRAW this frame IF...
- if (
- // ...the time we are spending drawing is a small fraction of the total
- // observed inter-frame time so that dropping it won't help much.
- (3*m_trRenderAvg <= m_trFrameAvg)
-
- // ...or our supplier is NOT handling things and the next frame would
- // be less timely than this one or our supplier CLAIMS to be handling
- // things, and is now less than a full FOUR frames late.
- || ( m_bSupplierHandlingQuality
- ? (trLate <= trDuration*4)
- : (trLate+trLate < trDuration)
- )
-
- // ...or we are on average waiting for over eight milliseconds then
- // this may be just a glitch. Draw it and we'll hope to catch up.
- || (m_trWaitAvg > 80000)
-
- // ...or we haven't drawn an image for over a second. We will update
- // the display, which stops the video looking hung.
- // Do this regardless of how late this media sample is.
- || ((trRealStream - m_trLastDraw) > UNITS)
-
- ) {
- HRESULT Result;
-
- // We are going to play this frame. We may want to play it early.
- // We will play it early if we think we are in slow machine mode.
- // If we think we are NOT in slow machine mode, we will still play
- // it early by m_trEarliness as this controls the graceful slide back.
- // and in addition we aim at being m_trTarget late rather than "on time".
-
- BOOL bPlayASAP = FALSE;
-
- // we will play it AT ONCE (slow machine mode) if...
-
- // ...we are playing catch-up
- if ( bJustDroppedFrame) {
- bPlayASAP = TRUE;
- MSR_INTEGER(m_idDecision, 9001);
- }
-
- // ...or if we are running below the true frame rate
- // exact comparisons are glitchy, for these measurements,
- // so add an extra 5% or so
- else if ( (m_trFrameAvg > trDuration + trDuration/16)
-
- // It's possible to get into a state where we are losing ground, but
- // are a very long way ahead. To avoid this or recover from it
- // we refuse to play early by more than 10 frames.
- && (trLate > - trDuration*10)
- ){
- bPlayASAP = TRUE;
- MSR_INTEGER(m_idDecision, 9002);
- }
- #if 0
- // ...or if we have been late and are less than one frame early
- else if ( (trLate + trDuration > 0)
- && (m_trWaitAvg<=20000)
- ) {
- bPlayASAP = TRUE;
- MSR_INTEGER(m_idDecision, 9003);
- }
- #endif
- // We will NOT play it at once if we are grossly early. On very slow frame
- // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
- // because we got starved (for instance by the net) and dropped one frame
- // some time or other. If we are more than 900mSec early, then wait.
- if (trLate<-9000000) {
- bPlayASAP = FALSE;
- }
-
-
- if (bPlayASAP) {
-
- m_nNormal = 0;
- MSR_INTEGER(m_idDecision, 0);
- // When we are here, we are in slow-machine mode. trLate may well
- // oscillate between negative and positive when the supplier is
- // dropping frames to keep sync. We should not let that mislead
- // us into thinking that we have as much as zero spare time!
- // We just update with a zero wait.
- m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
-
- // Assume that we draw it immediately. Update inter-frame stats
- m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
- #ifndef PERF
- // if this is NOT a perf build, then report what we know so far
- // without looking at the clock any more. This assumes that we
- // actually wait for exactly the time we hope to. it also reports
- // how close we get to the hacked up time stamps that we now have
- // rather than the ones we originally started with. It will
- // therefore be a little optimistic. However it's fast.
- PreparePerformanceData(trTrueLate, trFrame);
- #endif
- m_trLastDraw = trRealStream;
- if (m_trEarliness > trLate) {
- m_trEarliness = trLate; // if we are actually early, this is neg
- }
- Result = S_OK; // Draw it now
-
- } else {
- ++m_nNormal;
- // Set the average frame rate to EXACTLY the ideal rate.
- // If we are exiting slow-machine mode then we will have caught up
- // and be running ahead, so as we slide back to exact timing we will
- // have a longer than usual gap at this point. If we record this
- // real gap then we'll think that we're running slow and go back
- // into slow-machine mode and vever get it straight.
- m_trFrameAvg = trDuration;
- MSR_INTEGER(m_idDecision, 1);
-
- // Play it early by m_trEarliness and by m_trTarget
-
- {
- int trE = m_trEarliness + m_trTarget;
- *ptrStart += trE; // N.B. earliness is negative
- }
-
- int Delay = (int)(*ptrStart-trRealStream);
- Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait
-
- m_trWaitAvg = trWaitAvg;
-
- // Predict when it will actually be drawn and update frame stats
-
- if (Result==S_FALSE) { // We are going to wait
- trFrame = (int)(*ptrStart-m_trLastDraw);
- m_trLastDraw = *ptrStart;
- } else {
- // trFrame is already = trRealStream-m_trLastDraw;
- m_trLastDraw = trRealStream;
- }
- #ifndef PERF
- int iAccuracy;
- if (Delay>0) {
- // Report lateness based on when we intend to play it
- iAccuracy = (int)(*ptrStart-m_trRememberStampForPerf);
- } else {
- // Report lateness based on playing it *now*.
- iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf;
- }
- PreparePerformanceData(iAccuracy, trFrame);
- #endif
- }
- return Result;
- }
- // We are going to drop this frame!
- // Of course in DirectDraw mode the guy upstream may draw it anyway.
-
- // This will probably give a large negative wack to the wait avg.
- m_trWaitAvg = trWaitAvg;
-
- // Respect registry setting - debug only!
- if (m_bDrawLateFrames) {
- return S_OK; // draw it when it's ready
- } // even though it's late.
-
- // We are going to drop this frame so draw the next one early
- // n.b. if the supplier is doing direct draw then he may draw it anyway
- // but he's doing something funny to arrive here in that case.
-
- MSR_INTEGER(m_idDecision, 2);
- m_nNormal = -1;
- return E_FAIL; // drop it
-
- } // ShouldDrawSampleNow
-
-
- // NOTE we're called by both the window thread and the source filter thread
- // so we have to be protected by a critical section (locked before called)
- // Also, when the window thread gets signalled to render an image, it always
- // does so regardless of how late it is. All the degradation is done when we
- // are scheduling the next sample to be drawn. Hence when we start an advise
- // link to draw a sample, that sample's time will always become the last one
- // drawn - unless of course we stop streaming in which case we cancel links
-
- BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample)
- {
- // We override ShouldDrawSampleNow to add quality management
-
- BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample);
- if (bDrawImage == FALSE) {
- ++m_cFramesDropped;
- return FALSE;
- }
-
- // m_cFramesDrawn must NOT be updated here. It has to be updated
- // in RecordFrameLateness at the same time as the other statistics.
- return TRUE;
- }
-
-
- // Implementation of IQualProp interface needed to support the property page
- // This is how the property page gets the data out of the scheduler. We are
- // passed into the constructor the owning object in the COM sense, this will
- // either be the video renderer or an external IUnknown if we're aggregated.
- // We initialise our CUnknown base class with this interface pointer. Then
- // all we have to do is to override NonDelegatingQueryInterface to expose
- // our IQualProp interface. The AddRef and Release are handled automatically
- // by the base class and will be passed on to the appropriate outer object
-
- STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(int *pcFramesDropped)
- {
- CheckPointer(pcFramesDropped,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
- *pcFramesDropped = m_cFramesDropped;
- return NOERROR;
- } // get_FramesDroppedInRenderer
-
-
- // Set *pcFramesDrawn to the number of frames drawn since
- // streaming started.
-
- STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn)
- {
- CheckPointer(pcFramesDrawn,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
- *pcFramesDrawn = m_cFramesDrawn;
- return NOERROR;
- } // get_FramesDrawn
-
-
- // Set iAvgFrameRate to the frames per hundred secs since
- // streaming started. 0 otherwise.
-
- STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate)
- {
- CheckPointer(piAvgFrameRate,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
-
- int t;
- if (m_bStreaming) {
- t = timeGetTime()-m_tStreamingStart;
- } else {
- t = m_tStreamingStart;
- }
-
- if (t<=0) {
- *piAvgFrameRate = 0;
- ASSERT(m_cFramesDrawn == 0);
- } else {
- // i is frames per hundred seconds
- *piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
- }
- return NOERROR;
- } // get_AvgFrameRate
-
-
- // Set *piAvg to the average sync offset since streaming started
- // in mSec. The sync offset is the time in mSec between when the frame
- // should have been drawn and when the frame was actually drawn.
-
- STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset( int *piAvg)
- {
- CheckPointer(piAvg,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
-
- if (NULL==m_pClock) {
- *piAvg = 0;
- return NOERROR;
- }
-
- // Note that we didn't gather the stats on the first frame
- // so we use m_cFramesDrawn-1 here
- if (m_cFramesDrawn<=1) {
- *piAvg = 0;
- } else {
- *piAvg = m_iTotAcc / (m_cFramesDrawn-1);
- }
- return NOERROR;
- } // get_AvgSyncOffset
-
-
- // To avoid dragging in the maths library - a cheap
- // approximate integer square root.
- // We do this by getting a starting guess which is between 1
- // and 2 times too large, followed by THREE iterations of
- // Newton Raphson. (That will give accuracy to the nearest mSec
- // for the range in question - roughly 0..1000)
- //
- // It would be faster to use a linear interpolation and ONE NR, but
- // who cares. If anyone does - the best linear interpolation is
- // to approximates sqrt(x) by
- // y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
- // 0r y = x*0.41421 + 0.59467
- // This minimises the maximal error in the range in question.
- // (error is about +0.008883 and then one NR will give error .0000something
- // (Of course these are integers, so you can't just multiply by 0.41421
- // you'd have to do some sort of MulDiv).
- // Anyone wanna check my maths? (This is only for a property display!)
-
- int isqrt(int x)
- {
- int s = 1;
- // Make s an initial guess for sqrt(x)
- if (x > 0x40000000) {
- s = 0x8000; // prevent any conceivable closed loop
- } else {
- while (s*s<x) { // loop cannot possible go more than 31 times
- s = 2*s; // normally it goes about 6 times
- }
- // Three NR iterations.
- if (x==0) {
- s= 0; // Wouldn't it be tragic to divide by zero whenever our
- // accuracy was perfect!
- } else {
- s = (s*s+x)/(2*s);
- if (s>=0) s = (s*s+x)/(2*s);
- if (s>=0) s = (s*s+x)/(2*s);
- }
- }
- return s;
- }
-
-
-
- // Set *piDev to the standard deviation in mSec of the sync offset
- // of each frame since streaming started.
-
- STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset( int *piDev)
- {
- CheckPointer(piDev,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
-
- if (NULL==m_pClock) {
- *piDev = 0;
- return NOERROR;
- }
-
- // If S is the Sum of the Squares of observations and
- // T the Total (i.e. sum) of the observations and there were
- // N observations, then an estimate of the standard deviation is
- // sqrt( (S - T**2/N) / (N-1) )
-
- if (m_cFramesDrawn<=3) {
- *piDev = 0;
- } else {
- int x;
- // First frames have bogus stamps, so we get no stats for them
- // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
-
- // so we use m_cFramesDrawn-1 here
- x = m_iSumSqAcc - (m_iTotAcc*m_iTotAcc/(m_cFramesDrawn-1));
- x = x / (m_cFramesDrawn-2);
- ASSERT(x>=0);
- *piDev = isqrt(x);
- }
- return NOERROR;
- } // get_DevSyncOffset
-
-
- // Set *piJitter to the standard deviation in mSec of the inter-frame time
- // of frames since streaming started.
-
- STDMETHODIMP CBaseVideoRenderer::get_Jitter( int *piJitter)
- {
- CheckPointer(piJitter,E_POINTER);
- CAutoLock cVideoLock(&m_InterfaceLock);
-
- if (NULL==m_pClock) {
- *piJitter = 0;
- return NOERROR;
- }
-
- // See get_DevSyncOffset for explanation
-
- if (m_cFramesDrawn<=3) {
- *piJitter = 0;
- } else {
- int x;
- // First frames have bogus stamps, so we get no stats for them
- // So second frame gives bogus inter-frame time
- // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
- x = m_iSumSqFrameTime - (m_iSumFrameTime*m_iSumFrameTime/(m_cFramesDrawn-2));
- x = x / (m_cFramesDrawn-3);
- ASSERT(x>=0);
- *piJitter = isqrt(x);
- }
- return NOERROR;
- } // get_Jitter
-
-
- // Overidden to return our IQualProp interface
-
- STDMETHODIMP CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,VOID **ppv)
- {
- // We return IQualProp and delegate everything else
-
- if (riid == IID_IQualProp) {
- return GetInterface( (IQualProp *)this, ppv);
- } else if (riid == IID_IQualityControl) {
- return GetInterface( (IQualityControl *)this, ppv);
- }
- return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
- } // NonDelegatingQueryInterface
-
-
- // Override JoinFilterGraph so that, just before leaving
- // the graph we can send an EC_WINDOW_DESTROYED event
-
- STDMETHODIMP
- CBaseVideoRenderer::JoinFilterGraph(IFilterGraph *pGraph,LPCWSTR pName)
- {
- // Since we send EC_ACTIVATE, we also need to ensure
- // we send EC_WINDOW_DESTROYED or the resource manager may be
- // holding us as a focus object
- if (!pGraph && m_pGraph) {
-
- // We were in a graph and now we're not
-
- // Do this properly in case we are aggregated.
- IFilter* pFilter;
- QueryInterface(IID_IFilter,(void **) &pFilter);
- NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0);
- pFilter->Release();
- }
- return CBaseFilter::JoinFilterGraph(pGraph, pName);
- }
-
-
- // This removes a large number of level 4 warnings from the
- // Microsoft compiler which in this case are not very useful
- #pragma warning(disable: 4514)
-
-