topical media & game development

talk show tell print

hush-src-multi-BaseClasses-strmctl.cpp / cpp



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


(C) Æliens 20/2/2008

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.