|
|
//------------------------------------------------------------------------------
// File: StrmCtl.cpp
//
// Desc: DirectShow base classes.
//
// Copyright (c) 1996-2001 Microsoft 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 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(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 ( 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(); }
|