| //------------------------------------------------------------------------------ | |
| // File: RefClock.cpp | |
| // | |
| // Desc: DirectShow base classes - implements the IReferenceClock interface. | |
| // | |
| // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
| //------------------------------------------------------------------------------ | |
| #include <streams.h> | |
| #include <limits.h> | |
| #ifdef DXMPERF | |
| #include "dxmperf.h" | |
| #endif // DXMPERF | |
| // '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; | |
| } |