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();
 | 
						|
}
 |