2009-02-09 21:15:56 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// File: StrmCtl.cpp
|
|
|
|
//
|
|
|
|
// Desc: DirectShow base classes.
|
|
|
|
//
|
|
|
|
// Copyright (c) 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();
|
|
|
|
}
|