STRMCTL.CPP
//==========================================================================; 
// 
//  Copyright (c) 1997Microsoft Corporation.All Rights Reserved. 
// 
//--------------------------------------------------------------------------; 
 
#include <streams.h> 
#include <strmctl.h> 
 
CBaseStreamControl::CBaseStreamControl() 
: m_StreamState(STREAM_FLOWING) 
, m_StreamStateOnStop(STREAM_FLOWING) // means no pending stop 
, m_tStartTime(MAX_TIME) 
, m_tStopTime(MAX_TIME) 
, 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 screw 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(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)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)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<-->ostart 
// 6. o<-->xstop 
// 7.  <x->ostart 
// 8.  <o->xno change 
// 9.  <xo>start 
// 10.  <ox>stop then start 
// 11.  <-->xono change 
// 12.  <-->oxno change 
// 13. x<-->start 
// 14.    <x->start 
// 15.    <-->xno change 
// 16.   o<-->stop 
// 17.  <o->no change 
// 18.  <-->ono change 
// 19.    <-->no change 
 
 
enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckSampleTimes 
( const REFERENCE_TIME * pSampleStart, 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(); 
}