403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //------------------------------------------------------------------------------
 | |
| // File: StrmCtl.cpp
 | |
| //
 | |
| // Desc: DirectShow base classes.
 | |
| //
 | |
| // Copyright (c) 1996-2001 Microsoft Corporation.  All rights reserved.
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| #include <streams.h>
 | |
| #include <strmctl.h>
 | |
| 
 | |
| CBaseStreamControl::CBaseStreamControl(__inout HRESULT *phr)
 | |
| : m_StreamState(STREAM_FLOWING)
 | |
| , m_StreamStateOnStop(STREAM_FLOWING) // means no pending stop
 | |
| , m_tStartTime(MAX_TIME)
 | |
| , m_tStopTime(MAX_TIME)
 | |
| , m_StreamEvent(FALSE, phr)
 | |
| , m_dwStartCookie(0)
 | |
| , m_dwStopCookie(0)
 | |
| , m_pRefClock(NULL)
 | |
| , m_FilterState(State_Stopped)
 | |
| , m_bIsFlushing(FALSE)
 | |
| , m_bStopSendExtra(FALSE)
 | |
| {}
 | |
| 
 | |
| CBaseStreamControl::~CBaseStreamControl()
 | |
| {
 | |
|     // Make sure we release the clock.
 | |
|     SetSyncSource(NULL);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| STDMETHODIMP CBaseStreamControl::StopAt(const REFERENCE_TIME * ptStop, BOOL bSendExtra, DWORD dwCookie)
 | |
| {
 | |
|     CAutoLock lck(&m_CritSec);
 | |
|     m_bStopSendExtra = FALSE;	// reset
 | |
|     m_bStopExtraSent = FALSE;
 | |
|     if (ptStop)
 | |
|     {
 | |
|         if (*ptStop == MAX_TIME)
 | |
|         {
 | |
|             DbgLog((LOG_TRACE,2,TEXT("StopAt: Cancel stop")));
 | |
|             CancelStop();
 | |
| 	    // If there's now a command to start in the future, we assume
 | |
| 	    // they want to be stopped when the graph is first run
 | |
| 	    if (m_FilterState == State_Stopped && m_tStartTime < MAX_TIME) {
 | |
| 	        m_StreamState = STREAM_DISCARDING;
 | |
|                 DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
 | |
| 	    }
 | |
|             return NOERROR;
 | |
|         }
 | |
|         DbgLog((LOG_TRACE,2,TEXT("StopAt: %dms extra=%d"),
 | |
| 				(int)(*ptStop/10000), bSendExtra));
 | |
| 	// if the first command is to stop in the future, then we assume they
 | |
|         // want to be started when the graph is first run
 | |
| 	if (m_FilterState == State_Stopped && m_tStartTime > *ptStop) {
 | |
| 	    m_StreamState = STREAM_FLOWING;
 | |
|             DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
 | |
| 	}
 | |
|         m_bStopSendExtra = bSendExtra;
 | |
|         m_tStopTime = *ptStop;
 | |
|         m_dwStopCookie = dwCookie;
 | |
|         m_StreamStateOnStop = STREAM_DISCARDING;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         DbgLog((LOG_TRACE,2,TEXT("StopAt: now")));
 | |
| 	// sending an extra frame when told to stop now would mess people up
 | |
|         m_bStopSendExtra = FALSE;
 | |
|         m_tStopTime = MAX_TIME;
 | |
|         m_dwStopCookie = 0;
 | |
|         m_StreamState = STREAM_DISCARDING;
 | |
|         m_StreamStateOnStop = STREAM_FLOWING;	// no pending stop
 | |
|     }
 | |
|     // we might change our mind what to do with a sample we're blocking
 | |
|     m_StreamEvent.Set();
 | |
|     return NOERROR;
 | |
| }
 | |
| 
 | |
| STDMETHODIMP CBaseStreamControl::StartAt
 | |
| ( const REFERENCE_TIME *ptStart, DWORD dwCookie )
 | |
| {
 | |
|     CAutoLock lck(&m_CritSec);
 | |
|     if (ptStart)
 | |
|     {
 | |
|         if (*ptStart == MAX_TIME)
 | |
|         {
 | |
|             DbgLog((LOG_TRACE,2,TEXT("StartAt: Cancel start")));
 | |
|             CancelStart();
 | |
| 	    // If there's now a command to stop in the future, we assume
 | |
| 	    // they want to be started when the graph is first run
 | |
| 	    if (m_FilterState == State_Stopped && m_tStopTime < MAX_TIME) {
 | |
|                 DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
 | |
| 	        m_StreamState = STREAM_FLOWING;
 | |
| 	    }
 | |
|             return NOERROR;
 | |
|         }
 | |
|         DbgLog((LOG_TRACE,2,TEXT("StartAt: %dms"), (int)(*ptStart/10000)));
 | |
| 	// if the first command is to start in the future, then we assume they
 | |
|         // want to be stopped when the graph is first run
 | |
| 	if (m_FilterState == State_Stopped && m_tStopTime >= *ptStart) {
 | |
|             DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
 | |
| 	    m_StreamState = STREAM_DISCARDING;
 | |
| 	}
 | |
|         m_tStartTime = *ptStart;
 | |
|         m_dwStartCookie = dwCookie;
 | |
|         // if (m_tStopTime == m_tStartTime) CancelStop();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         DbgLog((LOG_TRACE,2,TEXT("StartAt: now")));
 | |
|         m_tStartTime = MAX_TIME;
 | |
|         m_dwStartCookie = 0;
 | |
|         m_StreamState = STREAM_FLOWING;
 | |
|     }
 | |
|     // we might change our mind what to do with a sample we're blocking
 | |
|     m_StreamEvent.Set();
 | |
|     return NOERROR;
 | |
| }
 | |
| 
 | |
| //  Retrieve information about current settings
 | |
| STDMETHODIMP CBaseStreamControl::GetInfo(__out AM_STREAM_INFO *pInfo)
 | |
| {
 | |
|     if (pInfo == NULL)
 | |
| 	return E_POINTER;
 | |
| 
 | |
|     pInfo->tStart = m_tStartTime;
 | |
|     pInfo->tStop  = m_tStopTime;
 | |
|     pInfo->dwStartCookie = m_dwStartCookie;
 | |
|     pInfo->dwStopCookie  = m_dwStopCookie;
 | |
|     pInfo->dwFlags = m_bStopSendExtra ? AM_STREAM_INFO_STOP_SEND_EXTRA : 0;
 | |
|     pInfo->dwFlags |= m_tStartTime == MAX_TIME ? 0 : AM_STREAM_INFO_START_DEFINED;
 | |
|     pInfo->dwFlags |= m_tStopTime == MAX_TIME ? 0 : AM_STREAM_INFO_STOP_DEFINED;
 | |
|     switch (m_StreamState) {
 | |
|     default:
 | |
|         DbgBreak("Invalid stream state");
 | |
|     case STREAM_FLOWING:
 | |
|         break;
 | |
|     case STREAM_DISCARDING:
 | |
|         pInfo->dwFlags |= AM_STREAM_INFO_DISCARDING;
 | |
|         break;
 | |
|     }
 | |
|     return S_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CBaseStreamControl::ExecuteStop()
 | |
| {
 | |
|     ASSERT(CritCheckIn(&m_CritSec));
 | |
|     m_StreamState = m_StreamStateOnStop;
 | |
|     if (m_dwStopCookie && m_pSink) {
 | |
| 	DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STOPPED (%d)"),
 | |
| 							m_dwStopCookie));
 | |
|         m_pSink->Notify(EC_STREAM_CONTROL_STOPPED, (LONG_PTR)this, m_dwStopCookie);
 | |
|     }
 | |
|     CancelStop(); // This will do the tidy up
 | |
| }
 | |
| 
 | |
| void CBaseStreamControl::ExecuteStart()
 | |
| {
 | |
|     ASSERT(CritCheckIn(&m_CritSec));
 | |
|     m_StreamState = STREAM_FLOWING;
 | |
|     if (m_dwStartCookie) {
 | |
| 	DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STARTED (%d)"),
 | |
| 							m_dwStartCookie));
 | |
|         m_pSink->Notify(EC_STREAM_CONTROL_STARTED, (LONG_PTR)this, m_dwStartCookie);
 | |
|     }
 | |
|     CancelStart(); // This will do the tidy up
 | |
| }
 | |
| 
 | |
| void CBaseStreamControl::CancelStop()
 | |
| {
 | |
|     ASSERT(CritCheckIn(&m_CritSec));
 | |
|     m_tStopTime = MAX_TIME;
 | |
|     m_dwStopCookie = 0;
 | |
|     m_StreamStateOnStop = STREAM_FLOWING;
 | |
| }
 | |
| 
 | |
| void CBaseStreamControl::CancelStart()
 | |
| {
 | |
|     ASSERT(CritCheckIn(&m_CritSec));
 | |
|     m_tStartTime = MAX_TIME;
 | |
|     m_dwStartCookie = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| // This guy will return one of the three StreamControlState's.  Here's what the caller
 | |
| // should do for each one:
 | |
| //
 | |
| // STREAM_FLOWING:      Proceed as usual (render or pass the sample on)
 | |
| // STREAM_DISCARDING:   Calculate the time 'til *pSampleStart and wait that long
 | |
| //                      for the event handle (GetStreamEventHandle()).  If the
 | |
| //                      wait expires, throw the sample away.  If the event
 | |
| //			fires, call me back, I've changed my mind.
 | |
| //			I use pSampleStart (not Stop) so that live sources don't
 | |
| // 			block for the duration of their samples, since the clock
 | |
| //			will always read approximately pSampleStart when called
 | |
| 
 | |
| 
 | |
| // All through this code, you'll notice the following rules:
 | |
| // - When start and stop time are the same, it's as if start was first
 | |
| // - An event is considered inside the sample when it's >= sample start time
 | |
| //   but < sample stop time
 | |
| // - if any part of the sample is supposed to be sent, we'll send the whole
 | |
| //   thing since we don't break it into smaller pieces
 | |
| // - If we skip over a start or stop without doing it, we still signal the event
 | |
| //   and reset ourselves in case somebody's waiting for the event, and to make
 | |
| //   sure we notice that the event is past and should be forgotten
 | |
| // Here are the 19 cases that have to be handled (x=start o=stop <-->=sample):
 | |
| //
 | |
| // 1.	xo<-->		start then stop
 | |
| // 2.	ox<-->		stop then start
 | |
| // 3.	 x<o->		start
 | |
| // 4.	 o<x->		stop then start
 | |
| // 5.	 x<-->o		start
 | |
| // 6.	 o<-->x		stop
 | |
| // 7.	  <x->o		start
 | |
| // 8.	  <o->x		no change
 | |
| // 9.	  <xo>		start
 | |
| // 10.	  <ox>		stop then start
 | |
| // 11.	  <-->xo	no change
 | |
| // 12.	  <-->ox	no change
 | |
| // 13.	 x<-->		start
 | |
| // 14.    <x->		start
 | |
| // 15.    <-->x		no change
 | |
| // 16.   o<-->		stop
 | |
| // 17.	  <o->		no change
 | |
| // 18.	  <-->o		no change
 | |
| // 19.    <-->		no change
 | |
| 
 | |
| 
 | |
| enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckSampleTimes
 | |
| ( __in const REFERENCE_TIME * pSampleStart, __in const REFERENCE_TIME * pSampleStop )
 | |
| {
 | |
|     CAutoLock lck(&m_CritSec);
 | |
| 
 | |
|     ASSERT(!m_bIsFlushing);
 | |
|     ASSERT(pSampleStart && pSampleStop);
 | |
| 
 | |
|     // Don't ask me how I came up with the code below to handle all 19 cases
 | |
|     // - DannyMi
 | |
| 
 | |
|     if (m_tStopTime >= *pSampleStart)
 | |
|     {
 | |
|         if (m_tStartTime >= *pSampleStop)
 | |
| 	    return m_StreamState;		// cases  8 11 12 15 17 18 19
 | |
| 	if (m_tStopTime < m_tStartTime)
 | |
| 	    ExecuteStop();			// case 10
 | |
| 	ExecuteStart();                         // cases 3 5 7 9 13 14
 | |
| 	return m_StreamState;
 | |
|     }
 | |
| 
 | |
|     if (m_tStartTime >= *pSampleStop)
 | |
|     {
 | |
|         ExecuteStop();                          // cases 6 16
 | |
|         return m_StreamState;
 | |
|     }
 | |
| 
 | |
|     if (m_tStartTime <= m_tStopTime)
 | |
|     {
 | |
| 	ExecuteStart();
 | |
| 	ExecuteStop();
 | |
|         return m_StreamState;		// case 1
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 	ExecuteStop();
 | |
| 	ExecuteStart();
 | |
|         return m_StreamState;		// cases 2 4
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckStreamState( IMediaSample * pSample )
 | |
| {
 | |
| 
 | |
|     REFERENCE_TIME rtBufferStart, rtBufferStop;
 | |
|     const BOOL bNoBufferTimes =
 | |
|               pSample == NULL ||
 | |
|               FAILED(pSample->GetTime(&rtBufferStart, &rtBufferStop));
 | |
| 
 | |
|     StreamControlState state;
 | |
|     LONG lWait;
 | |
| 
 | |
|     do
 | |
|         {
 | |
|  	    // something has to break out of the blocking
 | |
|             if (m_bIsFlushing || m_FilterState == State_Stopped)
 | |
| 		return STREAM_DISCARDING;
 | |
| 
 | |
|             if (bNoBufferTimes) {
 | |
|                 //  Can't do anything until we get a time stamp
 | |
|                 state = m_StreamState;
 | |
|                 break;
 | |
|             } else {
 | |
|                 state = CheckSampleTimes( &rtBufferStart, &rtBufferStop );
 | |
|                 if (state == STREAM_FLOWING)
 | |
| 		    break;
 | |
| 
 | |
| 		// we aren't supposed to send this, but we've been
 | |
| 		// told to send one more than we were supposed to
 | |
| 		// (and the stop isn't still pending and we're streaming)
 | |
| 		if (m_bStopSendExtra && !m_bStopExtraSent &&
 | |
| 					m_tStopTime == MAX_TIME &&
 | |
| 					m_FilterState != State_Stopped) {
 | |
| 		    m_bStopExtraSent = TRUE;
 | |
| 		    DbgLog((LOG_TRACE,2,TEXT("%d sending an EXTRA frame"),
 | |
| 							    m_dwStopCookie));
 | |
| 		    state = STREAM_FLOWING;
 | |
| 		    break;
 | |
| 		}
 | |
|             }
 | |
| 
 | |
|             // We're in discarding mode
 | |
| 
 | |
|             // If we've no clock, discard as fast as we can
 | |
|             if (!m_pRefClock) {
 | |
| 		break;
 | |
| 
 | |
| 	    // If we're paused, we can't discard in a timely manner because
 | |
| 	    // there's no such thing as stream times.  We must block until
 | |
| 	    // we run or stop, or we'll end up throwing the whole stream away
 | |
| 	    // as quickly as possible
 | |
| 	    } else if (m_FilterState == State_Paused) {
 | |
| 		lWait = INFINITE;
 | |
| 
 | |
| 	    } else {
 | |
| 	        // wait until it's time for the sample until we say "discard"
 | |
| 	        // ("discard in a timely fashion")
 | |
| 	        REFERENCE_TIME rtNow;
 | |
|                 EXECUTE_ASSERT(SUCCEEDED(m_pRefClock->GetTime(&rtNow)));
 | |
|                 rtNow -= m_tRunStart;   // Into relative ref-time
 | |
|                 lWait = LONG((rtBufferStart - rtNow)/10000); // 100ns -> ms
 | |
|                 if (lWait < 10) break; // Not worth waiting - discard early
 | |
| 	    }
 | |
| 
 | |
|     } while(WaitForSingleObject(GetStreamEventHandle(), lWait) != WAIT_TIMEOUT);
 | |
| 
 | |
|     return state;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CBaseStreamControl::NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart )
 | |
| {
 | |
|     CAutoLock lck(&m_CritSec);
 | |
| 
 | |
|     // or we will get confused
 | |
|     if (m_FilterState == new_state)
 | |
| 	return;
 | |
| 
 | |
|     switch (new_state)
 | |
|     {
 | |
|         case State_Stopped:
 | |
| 
 | |
|             DbgLog((LOG_TRACE,2,TEXT("Filter is STOPPED")));
 | |
| 
 | |
| 	    // execute any pending starts and stops in the right order,
 | |
| 	    // to make sure all notifications get sent, and we end up
 | |
| 	    // in the right state to begin next time (??? why not?)
 | |
| 
 | |
| 	    if (m_tStartTime != MAX_TIME && m_tStopTime == MAX_TIME) {
 | |
| 		ExecuteStart();
 | |
| 	    } else if (m_tStopTime != MAX_TIME && m_tStartTime == MAX_TIME) {
 | |
| 		ExecuteStop();
 | |
| 	    } else if (m_tStopTime != MAX_TIME && m_tStartTime != MAX_TIME) {
 | |
| 		if (m_tStartTime <= m_tStopTime) {
 | |
| 		    ExecuteStart();
 | |
| 		    ExecuteStop();
 | |
| 		} else {
 | |
| 		    ExecuteStop();
 | |
| 		    ExecuteStart();
 | |
| 		}
 | |
| 	    }
 | |
| 	    // always start off flowing when the graph starts streaming
 | |
| 	    // unless told otherwise
 | |
| 	    m_StreamState = STREAM_FLOWING;
 | |
|             m_FilterState = new_state;
 | |
|             break;
 | |
| 
 | |
|         case State_Running:
 | |
| 
 | |
|             DbgLog((LOG_TRACE,2,TEXT("Filter is RUNNING")));
 | |
| 
 | |
|             m_tRunStart = tStart;
 | |
|             // fall-through
 | |
| 
 | |
|         default: // case State_Paused:
 | |
|             m_FilterState = new_state;
 | |
|     }
 | |
|     // unblock!
 | |
|     m_StreamEvent.Set();
 | |
| }
 | |
| 
 | |
| 
 | |
| void CBaseStreamControl::Flushing(BOOL bInProgress)
 | |
| {
 | |
|     CAutoLock lck(&m_CritSec);
 | |
|     m_bIsFlushing = bInProgress;
 | |
|     m_StreamEvent.Set();
 | |
| }
 |