410 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			410 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //------------------------------------------------------------------------------
 | ||
|  | // File: RefClock.cpp
 | ||
|  | //
 | ||
|  | // Desc: DirectShow base classes - implements the IReferenceClock interface.
 | ||
|  | //
 | ||
|  | // Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
 | ||
|  | //------------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | #undef NOMINMAX
 | ||
|  | #include <streams.h>
 | ||
|  | #include <limits.h>
 | ||
|  | 
 | ||
|  | #ifdef DXMPERF
 | ||
|  | #include "dxmperf.h"
 | ||
|  | #endif // DXMPERF
 | ||
|  | 
 | ||
|  | #ifndef max
 | ||
|  | #define max(a, b) (((a) > (b)) ? (a) : (b))
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #ifndef min
 | ||
|  | #define min(a, b) (((a) < (b)) ? (a) : (b))
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | // 'this' used in constructor list
 | ||
|  | #pragma warning(disable:4355)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::NonDelegatingQueryInterface( | ||
|  |     REFIID riid, | ||
|  |     __deref_out void ** ppv) | ||
|  | { | ||
|  |     HRESULT hr; | ||
|  | 
 | ||
|  |     if (riid == IID_IReferenceClock) | ||
|  |     { | ||
|  |         hr = GetInterface((IReferenceClock *) this, ppv); | ||
|  |     } | ||
|  |     else if (riid == IID_IReferenceClockTimerControl) | ||
|  |     { | ||
|  |         hr = GetInterface((IReferenceClockTimerControl *) this, ppv); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         hr = CUnknown::NonDelegatingQueryInterface(riid, ppv); | ||
|  |     } | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | CBaseReferenceClock::~CBaseReferenceClock() | ||
|  | { | ||
|  | #ifdef DXMPERF
 | ||
|  |     PERFLOG_DTOR( L"CBaseReferenceClock", (IReferenceClock *) this ); | ||
|  | #endif // DXMPERF
 | ||
|  | 
 | ||
|  |     if (m_TimerResolution) timeEndPeriod(m_TimerResolution); | ||
|  | 
 | ||
|  |     if (m_pSchedule) | ||
|  |     { | ||
|  |         m_pSchedule->DumpLinkedList(); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (m_hThread) | ||
|  |     { | ||
|  |         m_bAbort = TRUE; | ||
|  |         TriggerThread(); | ||
|  |         WaitForSingleObject( m_hThread, INFINITE ); | ||
|  |         EXECUTE_ASSERT( CloseHandle(m_hThread) ); | ||
|  |         m_hThread = 0; | ||
|  |         EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) ); | ||
|  | 	delete m_pSchedule; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // A derived class may supply a hThreadEvent if it has its own thread that will take care
 | ||
|  | // of calling the schedulers Advise method.  (Refere to CBaseReferenceClock::AdviseThread()
 | ||
|  | // to see what such a thread has to do.)
 | ||
|  | CBaseReferenceClock::CBaseReferenceClock( __in_opt LPCTSTR pName,  | ||
|  |                                           __inout_opt LPUNKNOWN pUnk,  | ||
|  |                                           __inout HRESULT *phr,  | ||
|  |                                           __inout_opt CAMSchedule * pShed ) | ||
|  | : CUnknown( pName, pUnk ) | ||
|  | , m_rtLastGotTime(0) | ||
|  | , m_TimerResolution(0) | ||
|  | , m_bAbort( FALSE ) | ||
|  | , m_pSchedule( pShed ? pShed : new CAMSchedule(CreateEvent(NULL, FALSE, FALSE, NULL)) ) | ||
|  | , m_hThread(0) | ||
|  | { | ||
|  | 
 | ||
|  | #ifdef DXMPERF
 | ||
|  |     PERFLOG_CTOR( pName ? pName : L"CBaseReferenceClock", (IReferenceClock *) this ); | ||
|  | #endif // DXMPERF
 | ||
|  | 
 | ||
|  |     ASSERT(m_pSchedule); | ||
|  |     if (!m_pSchedule) | ||
|  |     { | ||
|  |         *phr = E_OUTOFMEMORY; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         // Set up the highest resolution timer we can manage
 | ||
|  |         TIMECAPS tc; | ||
|  |         m_TimerResolution = (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(tc))) | ||
|  |                             ? tc.wPeriodMin | ||
|  |                             : 1; | ||
|  | 
 | ||
|  |         timeBeginPeriod(m_TimerResolution); | ||
|  | 
 | ||
|  |         /* Initialise our system times - the derived clock should set the right values */ | ||
|  |         m_dwPrevSystemTime = timeGetTime(); | ||
|  |         m_rtPrivateTime = (UNITS / MILLISECONDS) * m_dwPrevSystemTime; | ||
|  | 
 | ||
|  |         #ifdef PERF
 | ||
|  |             m_idGetSystemTime = MSR_REGISTER(TEXT("CBaseReferenceClock::GetTime")); | ||
|  |         #endif
 | ||
|  | 
 | ||
|  |         if ( !pShed ) | ||
|  |         { | ||
|  |             DWORD ThreadID; | ||
|  |             m_hThread = ::CreateThread(NULL,                  // Security attributes
 | ||
|  |                                        (DWORD) 0,             // Initial stack size
 | ||
|  |                                        AdviseThreadFunction,  // Thread start address
 | ||
|  |                                        (LPVOID) this,         // Thread parameter
 | ||
|  |                                        (DWORD) 0,             // Creation flags
 | ||
|  |                                        &ThreadID);            // Thread identifier
 | ||
|  | 
 | ||
|  |             if (m_hThread) | ||
|  |             { | ||
|  |                 SetThreadPriority( m_hThread, THREAD_PRIORITY_TIME_CRITICAL ); | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 *phr = E_FAIL; | ||
|  |                 EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) ); | ||
|  |                 delete m_pSchedule; | ||
|  |                 m_pSchedule = NULL; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void CBaseReferenceClock::Restart (IN REFERENCE_TIME rtMinTime) | ||
|  | { | ||
|  |     Lock(); | ||
|  |     m_rtLastGotTime = rtMinTime ; | ||
|  |     Unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::GetTime(__out REFERENCE_TIME *pTime) | ||
|  | { | ||
|  |     HRESULT hr; | ||
|  |     if (pTime) | ||
|  |     { | ||
|  |         REFERENCE_TIME rtNow; | ||
|  |         Lock(); | ||
|  |         rtNow = GetPrivateTime(); | ||
|  |         if (rtNow > m_rtLastGotTime) | ||
|  |         { | ||
|  |             m_rtLastGotTime = rtNow; | ||
|  |             hr = S_OK; | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             hr = S_FALSE; | ||
|  |         } | ||
|  |         *pTime = m_rtLastGotTime; | ||
|  |         Unlock(); | ||
|  |         MSR_INTEGER(m_idGetSystemTime, LONG((*pTime) / (UNITS/MILLISECONDS)) ); | ||
|  | 
 | ||
|  | #ifdef DXMPERF
 | ||
|  |         PERFLOG_GETTIME( (IReferenceClock *) this, *pTime ); | ||
|  | #endif // DXMPERF
 | ||
|  | 
 | ||
|  |     } | ||
|  |     else hr = E_POINTER; | ||
|  | 
 | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | /* Ask for an async notification that a time has elapsed */ | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::AdviseTime( | ||
|  |     REFERENCE_TIME baseTime,         // base reference time
 | ||
|  |     REFERENCE_TIME streamTime,       // stream offset time
 | ||
|  |     HEVENT hEvent,                   // advise via this event
 | ||
|  |     __out DWORD_PTR *pdwAdviseCookie)// where your cookie goes
 | ||
|  | { | ||
|  |     CheckPointer(pdwAdviseCookie, E_POINTER); | ||
|  |     *pdwAdviseCookie = 0; | ||
|  | 
 | ||
|  |     // Check that the event is not already set
 | ||
|  |     ASSERT(WAIT_TIMEOUT == WaitForSingleObject(HANDLE(hEvent),0)); | ||
|  | 
 | ||
|  |     HRESULT hr; | ||
|  | 
 | ||
|  |     const REFERENCE_TIME lRefTime = baseTime + streamTime; | ||
|  |     if ( lRefTime <= 0 || lRefTime == MAX_TIME ) | ||
|  |     { | ||
|  |         hr = E_INVALIDARG; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( lRefTime, 0, HANDLE(hEvent), FALSE ); | ||
|  |         hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY; | ||
|  |     } | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Ask for an asynchronous periodic notification that a time has elapsed */ | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::AdvisePeriodic( | ||
|  |     REFERENCE_TIME StartTime,         // starting at this time
 | ||
|  |     REFERENCE_TIME PeriodTime,        // time between notifications
 | ||
|  |     HSEMAPHORE hSemaphore,            // advise via a semaphore
 | ||
|  |     __out DWORD_PTR *pdwAdviseCookie) // where your cookie goes
 | ||
|  | { | ||
|  |     CheckPointer(pdwAdviseCookie, E_POINTER); | ||
|  |     *pdwAdviseCookie = 0; | ||
|  | 
 | ||
|  |     HRESULT hr; | ||
|  |     if (StartTime > 0 && PeriodTime > 0 && StartTime != MAX_TIME ) | ||
|  |     { | ||
|  |         *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( StartTime, PeriodTime, HANDLE(hSemaphore), TRUE ); | ||
|  |         hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY; | ||
|  |     } | ||
|  |     else hr = E_INVALIDARG; | ||
|  | 
 | ||
|  |     return hr; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::Unadvise(DWORD_PTR dwAdviseCookie) | ||
|  | { | ||
|  |     return m_pSchedule->Unadvise(dwAdviseCookie); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | REFERENCE_TIME CBaseReferenceClock::GetPrivateTime() | ||
|  | { | ||
|  |     CAutoLock cObjectLock(this); | ||
|  | 
 | ||
|  | 
 | ||
|  |     /* If the clock has wrapped then the current time will be less than
 | ||
|  |      * the last time we were notified so add on the extra milliseconds | ||
|  |      * | ||
|  |      * The time period is long enough so that the likelihood of | ||
|  |      * successive calls spanning the clock cycle is not considered. | ||
|  |      */ | ||
|  | 
 | ||
|  |     DWORD dwTime = timeGetTime(); | ||
|  |     { | ||
|  |         m_rtPrivateTime += Int32x32To64(UNITS / MILLISECONDS, (DWORD)(dwTime - m_dwPrevSystemTime)); | ||
|  |         m_dwPrevSystemTime = dwTime; | ||
|  |     } | ||
|  | 
 | ||
|  |     return m_rtPrivateTime; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Adjust the current time by the input value.  This allows an
 | ||
|  |    external time source to work out some of the latency of the clock | ||
|  |    system and adjust the "current" time accordingly.  The intent is | ||
|  |    that the time returned to the user is synchronised to a clock | ||
|  |    source and allows drift to be catered for. | ||
|  | 
 | ||
|  |    For example: if the clock source detects a drift it can pass a delta | ||
|  |    to the current time rather than having to set an explicit time. | ||
|  | */ | ||
|  | 
 | ||
|  | STDMETHODIMP CBaseReferenceClock::SetTimeDelta(const REFERENCE_TIME & TimeDelta) | ||
|  | { | ||
|  | #ifdef DEBUG
 | ||
|  | 
 | ||
|  |     // Just break if passed an improper time delta value
 | ||
|  |     LONGLONG llDelta = TimeDelta > 0 ? TimeDelta : -TimeDelta; | ||
|  |     if (llDelta > UNITS * 1000) { | ||
|  |         DbgLog((LOG_TRACE, 0, TEXT("Bad Time Delta"))); | ||
|  |         //DebugBreak();
 | ||
|  |     } | ||
|  | 
 | ||
|  |     // We're going to calculate a "severity" for the time change. Max -1
 | ||
|  |     // min 8.  We'll then use this as the debug logging level for a
 | ||
|  |     // debug log message.
 | ||
|  |     const LONG usDelta = LONG(TimeDelta/10);      // Delta in micro-secs
 | ||
|  | 
 | ||
|  |     DWORD delta        = abs(usDelta);            // varying delta
 | ||
|  |     // Severity == 8 - ceil(log<base 8>(abs( micro-secs delta)))
 | ||
|  |     int   Severity     = 8; | ||
|  |     while ( delta > 0 ) | ||
|  |     { | ||
|  |         delta >>= 3;                              // div 8
 | ||
|  |         Severity--; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Sev == 0 => > 2 second delta!
 | ||
|  |     DbgLog((LOG_TIMING, Severity < 0 ? 0 : Severity, | ||
|  |         TEXT("Sev %2i: CSystemClock::SetTimeDelta(%8ld us) %lu -> %lu ms."), | ||
|  |         Severity, usDelta, DWORD(ConvertToMilliseconds(m_rtPrivateTime)), | ||
|  |         DWORD(ConvertToMilliseconds(TimeDelta+m_rtPrivateTime)) )); | ||
|  | 
 | ||
|  |     // Don't want the DbgBreak to fire when running stress on debug-builds.
 | ||
|  |     #ifdef BREAK_ON_SEVERE_TIME_DELTA
 | ||
|  |         if (Severity < 0) | ||
|  |             DbgBreakPoint(TEXT("SetTimeDelta > 16 seconds!"), | ||
|  |                           TEXT(__FILE__),__LINE__); | ||
|  |     #endif
 | ||
|  | 
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     CAutoLock cObjectLock(this); | ||
|  |     m_rtPrivateTime += TimeDelta; | ||
|  |     // If time goes forwards, and we have advises, then we need to
 | ||
|  |     // trigger the thread so that it can re-evaluate its wait time.
 | ||
|  |     // Since we don't want the cost of the thread switches if the change
 | ||
|  |     // is really small, only do it if clock goes forward by more than
 | ||
|  |     // 0.5 millisecond.  If the time goes backwards, the thread will
 | ||
|  |     // wake up "early" (relativly speaking) and will re-evaluate at
 | ||
|  |     // that time.
 | ||
|  |     if ( TimeDelta > 5000 && m_pSchedule->GetAdviseCount() > 0 ) TriggerThread(); | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | // Thread stuff
 | ||
|  | 
 | ||
|  | DWORD __stdcall CBaseReferenceClock::AdviseThreadFunction(__in LPVOID p) | ||
|  | { | ||
|  |     return DWORD(reinterpret_cast<CBaseReferenceClock*>(p)->AdviseThread()); | ||
|  | } | ||
|  | 
 | ||
|  | HRESULT CBaseReferenceClock::AdviseThread() | ||
|  | { | ||
|  |     DWORD dwWait = INFINITE; | ||
|  | 
 | ||
|  |     // The first thing we do is wait until something interesting happens
 | ||
|  |     // (meaning a first advise or shutdown).  This prevents us calling
 | ||
|  |     // GetPrivateTime immediately which is goodness as that is a virtual
 | ||
|  |     // routine and the derived class may not yet be constructed.  (This
 | ||
|  |     // thread is created in the base class constructor.)
 | ||
|  | 
 | ||
|  |     while ( !m_bAbort ) | ||
|  |     { | ||
|  |         // Wait for an interesting event to happen
 | ||
|  |         DbgLog((LOG_TIMING, 3, TEXT("CBaseRefClock::AdviseThread() Delay: %lu ms"), dwWait )); | ||
|  |         WaitForSingleObject(m_pSchedule->GetEvent(), dwWait); | ||
|  |         if (m_bAbort) break; | ||
|  | 
 | ||
|  |         // There are several reasons why we need to work from the internal
 | ||
|  |         // time, mainly to do with what happens when time goes backwards.
 | ||
|  |         // Mainly, it stop us looping madly if an event is just about to
 | ||
|  |         // expire when the clock goes backward (i.e. GetTime stop for a
 | ||
|  |         // while).
 | ||
|  |         const REFERENCE_TIME  rtNow = GetPrivateTime(); | ||
|  | 
 | ||
|  |         DbgLog((LOG_TIMING, 3, | ||
|  |               TEXT("CBaseRefClock::AdviseThread() Woke at = %lu ms"), | ||
|  |               ConvertToMilliseconds(rtNow) )); | ||
|  | 
 | ||
|  |         // We must add in a millisecond, since this is the resolution of our
 | ||
|  |         // WaitForSingleObject timer.  Failure to do so will cause us to loop
 | ||
|  |         // franticly for (approx) 1 a millisecond.
 | ||
|  |         m_rtNextAdvise = m_pSchedule->Advise( 10000 + rtNow ); | ||
|  |         LONGLONG llWait = m_rtNextAdvise - rtNow; | ||
|  | 
 | ||
|  |         ASSERT( llWait > 0 ); | ||
|  | 
 | ||
|  |         llWait = ConvertToMilliseconds(llWait); | ||
|  |         // DON'T replace this with a max!! (The type's of these things is VERY important)
 | ||
|  |         dwWait = (llWait > REFERENCE_TIME(UINT_MAX)) ? UINT_MAX : DWORD(llWait); | ||
|  |     }; | ||
|  |     return NOERROR; | ||
|  | } | ||
|  | 
 | ||
|  | HRESULT CBaseReferenceClock::SetDefaultTimerResolution( | ||
|  |         REFERENCE_TIME timerResolution // in 100ns
 | ||
|  |     ) | ||
|  | { | ||
|  |     CAutoLock cObjectLock(this); | ||
|  |     if( 0 == timerResolution  ) { | ||
|  |         if( m_TimerResolution ) { | ||
|  |            timeEndPeriod( m_TimerResolution ); | ||
|  |            m_TimerResolution = 0; | ||
|  |         } | ||
|  |     } else { | ||
|  |         TIMECAPS tc; | ||
|  |         DWORD dwMinResolution = (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(tc))) | ||
|  |                             ? tc.wPeriodMin | ||
|  |                             : 1; | ||
|  |         DWORD dwResolution = max( dwMinResolution, DWORD(timerResolution / 10000) ); | ||
|  |         if( dwResolution != m_TimerResolution ) { | ||
|  |             timeEndPeriod(m_TimerResolution); | ||
|  |             m_TimerResolution = dwResolution; | ||
|  |             timeBeginPeriod( m_TimerResolution ); | ||
|  |         } | ||
|  |     } | ||
|  |     return S_OK; | ||
|  | } | ||
|  | 
 | ||
|  | HRESULT CBaseReferenceClock::GetDefaultTimerResolution( | ||
|  |         __out REFERENCE_TIME* pTimerResolution // in 100ns
 | ||
|  |     ) | ||
|  | { | ||
|  |     if( !pTimerResolution ) { | ||
|  |         return E_POINTER; | ||
|  |     } | ||
|  |     CAutoLock cObjectLock(this); | ||
|  |     *pTimerResolution = m_TimerResolution * 10000; | ||
|  |     return S_OK; | ||
|  | } |