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