summaryrefslogtreecommitdiff
path: root/third_party/BaseClasses/strmctl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/BaseClasses/strmctl.cpp')
-rw-r--r--third_party/BaseClasses/strmctl.cpp402
1 files changed, 402 insertions, 0 deletions
diff --git a/third_party/BaseClasses/strmctl.cpp b/third_party/BaseClasses/strmctl.cpp
new file mode 100644
index 00000000..b7f59521
--- /dev/null
+++ b/third_party/BaseClasses/strmctl.cpp
@@ -0,0 +1,402 @@
+//------------------------------------------------------------------------------
+// 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();
+}