//------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // The use and distribution terms for this software are covered by the // Microsoft Limited Public License (Ms-LPL) // which can be found in the file MS-LPL.txt at the root of this distribution. // By using this software in any fashion, you are agreeing to be bound by // the terms of this license. // // The software is licensed “as-is.” // // You must not remove this notice, or any other, from this software. // // // // Demux code for Windows CE // //------------------------------------------------------------------------- //====================================================================== // Demux code for Windows CE //====================================================================== /*++ Module Name: clock.cpp Abstract: This module contains the IReferenceClock implementation, as well as all the push-clock code. Revision History: 23-Jan-2000 created 17-Apr-2000 CMonotonicClock added; made PCR clock subordination adaptive 11-Oct-2001 add moving avg window clock-subordinating used in timeshifting Notes: --*/ #include "precomp.h" #include "mp2demux.h" #include "tsstats.h" #include "mp2seek.h" #include "pin_out.h" #include "bufsrc.h" #include "plparse.h" #include "program.h" #include "clock.h" #include "filter.h" #include #pragma warning (disable:4355) #define MAX_REFERENCE_TIME 0x7FFFFFFFFFFFFFFF // ============================================================================ // ============================================================================ template CTClockSubordinate ::CTClockSubordinate ( IN CTIGetTime * pICTGetTime, IN MasterClock MasterClockFreq, // ticks per second IN DWORD dwMinSamplingWinTicks, // we measure clock tuples of min delta of this IN DWORD dwMaxHistoryMillis, // we maintain a history up to this max; default = 10 minutes IN DWORD MinSubordinatable, // min % we consider "subordinatable" IN DWORD MaxSubordinatable, // max % we consider "subordinatable" IN CMpeg2Stats * pStats ) : m_MasterFreq (MasterClockFreq), m_dClockSubordinateRatio(CLOCKS_SAME_SCALING_VALUE), m_dwMinSamplingWinTicks (dwMinSamplingWinTicks), m_pStats (pStats), m_BracketQueue (ALLOC_QUANTUM), m_pICTGetTime (pICTGetTime), m_dwMaxHistoryMillis (dwMaxHistoryMillis) { m_pStats -> AddRef () ; m_dMinSubordinatable = (double) Min (MinSubordinatable, 100) / 100.0 ; m_dMaxSubordinatable = (double) Max (MaxSubordinatable, 100) / 100.0 ; m_HostFreq = m_pICTGetTime -> GetFreq () ; } template CTClockSubordinate ::~CTClockSubordinate ( ) { Reset () ; m_pStats -> Release () ; } template void CTClockSubordinate ::TrimQueue_ ( IN DWORD dwTicksNow ) { CLOCK_SUBORDINATE_BRACKET * pHead ; while (!m_BracketQueue.Empty ()) { m_BracketQueue.Head (& pHead) ; if (dwTicksNow - pHead -> TicksStart >= m_dwMaxHistoryMillis) { m_BracketQueue.Pop (& pHead) ; m_BracketPool.Recycle (pHead) ; } else { break ; } } m_pStats -> QueueLength (m_BracketQueue.Length ()) ; } template void CTClockSubordinate ::ComputeNewScalingVal_ ( ) { CLOCK_SUBORDINATE_BRACKET * pHead ; CLOCK_SUBORDINATE_BRACKET * pTail ; double dHostDelta ; double dMasterDelta ; double dNewRatio ; if (m_BracketQueue.Length () > 0) { m_BracketQueue.Head (& pHead) ; m_BracketQueue.Tail (& pTail) ; ASSERT (pHead) ; ASSERT (pTail) ; if (pTail -> Start_Host > pHead -> Start_Host) { dHostDelta = (double) (pTail -> Start_Host - pHead -> Start_Host) / (double) m_HostFreq ; dMasterDelta = (double) (pTail -> Start_Master - pHead -> Start_Master) / (double) m_MasterFreq ; if (dHostDelta != 0) { dNewRatio = dMasterDelta / dHostDelta ; if (dNewRatio >= m_dMinSubordinatable && dNewRatio <= m_dMaxSubordinatable) { m_dClockSubordinateRatio = dNewRatio ; m_pStats -> ScalingVal (m_dClockSubordinateRatio) ; m_pStats -> SubordinateState (TRUE) ; } else { m_pStats -> SubordinateState (FALSE) ; } } } } } template DWORD CTClockSubordinate ::QueueNewBracket_ ( IN MasterClock MasterTime, IN HostClock HostTime, IN DWORD dwTicksNow ) { DWORD dw ; CLOCK_SUBORDINATE_BRACKET * pNew ; pNew = m_BracketPool.Get () ; if (pNew) { pNew -> Start_Master = MasterTime ; pNew -> Start_Host = HostTime ; pNew -> TicksStart = dwTicksNow ; dw = m_BracketQueue.Push (pNew) ; if (dw != NOERROR) { m_BracketPool.Recycle (pNew) ; } m_pStats -> QueueLength (m_BracketQueue.Length ()) ; } else { dw = ERROR_NOT_ENOUGH_MEMORY ; } return dw ; } template double CTClockSubordinate ::ObservedBracketScalingVal_ ( IN CLOCK_SUBORDINATE_BRACKET * pBracket ) { double dHostDelta ; double dMasterDelta ; double dRatio ; ASSERT (pBracket) ; dRatio = CLOCKS_SAME_SCALING_VALUE ; if (m_Last.Start_Master != UNDEFINED && m_Last.Start_Host != UNDEFINED) { if (m_Last.Start_Host > pBracket -> Start_Host) { dHostDelta = (double) (m_Last.Start_Host - pBracket -> Start_Host) / (double) m_HostFreq ; dMasterDelta = (double) (m_Last.Start_Master - pBracket -> Start_Master) / (double) m_MasterFreq ; if (dHostDelta != 0) { dRatio = dMasterDelta / dHostDelta ; } } } return dRatio ; } template double CTClockSubordinate ::ObservedTailScalingVal_ ( ) { CLOCK_SUBORDINATE_BRACKET * pTail ; double dRatio ; dRatio = CLOCKS_SAME_SCALING_VALUE ; if (m_BracketQueue.Length () > 0) { ASSERT (m_Last.Start_Master != UNDEFINED) ; ASSERT (m_Last.Start_Host != UNDEFINED) ; m_BracketQueue.Tail (& pTail) ; ASSERT (pTail) ; dRatio = ObservedBracketScalingVal_ (pTail) ; } return dRatio ; } template double CTClockSubordinate ::ObservedHeadScalingVal_ ( ) { CLOCK_SUBORDINATE_BRACKET * pHead ; double dRatio ; dRatio = CLOCKS_SAME_SCALING_VALUE ; if (m_BracketQueue.Length () > 0) { ASSERT (m_Last.Start_Master != UNDEFINED) ; ASSERT (m_Last.Start_Host != UNDEFINED) ; m_BracketQueue.Head (& pHead) ; ASSERT (pHead) ; dRatio = ObservedBracketScalingVal_ (pHead) ; } return dRatio ; } template void CTClockSubordinate ::OnMasterTime ( IN MasterClock MasterTime ) { DWORD dwTicksNow ; CLOCK_SUBORDINATE_BRACKET * pTail ; #ifndef UNDER_CE CLOCK_SUBORDINATE_BRACKET * pHead ; #endif //UNDER_CE DWORD dw ; DWORD dwTicksTail ; HostClock HostTimeNow ; double dBracketScalingVal ; CAutoLock Lock (& m_cs) ; HostTimeNow = m_pICTGetTime -> SampleClock () ; dwTicksNow = ::GetTickCount () ; if (m_BracketQueue.Length () == 0) { dw = QueueNewBracket_ (MasterTime, HostTimeNow, dwTicksNow) ; if (dw != NOERROR) { goto cleanup ; } } ASSERT (m_BracketQueue.Length () > 0) ; m_BracketQueue.Tail (& pTail) ; ASSERT (pTail) ; m_Last.Start_Master = MasterTime ; m_Last.Start_Host = HostTimeNow ; dwTicksTail = dwTicksNow - pTail -> TicksStart ; if (dwTicksTail >= m_dwMinSamplingWinTicks) { dBracketScalingVal = ObservedTailScalingVal_ () ; if (dBracketScalingVal >= m_dMinSubordinatable && dBracketScalingVal <= m_dMaxSubordinatable) { m_pStats -> InboundsBracket (dBracketScalingVal) ; } else { m_pStats -> OutOfBoundsBracket (dBracketScalingVal) ; } TrimQueue_ (dwTicksNow) ; QueueNewBracket_ (MasterTime, HostTimeNow, dwTicksNow) ; ComputeNewScalingVal_ () ; } cleanup : return ; } template void CTClockSubordinate ::Reset ( IN BOOL fSubordinate = TRUE ) { CAutoLock Lock (& m_cs) ; CLOCK_SUBORDINATE_BRACKET * pHead ; while (!m_BracketQueue.Empty ()) { m_BracketQueue.Pop (& pHead) ; ASSERT (pHead) ; m_BracketPool.Recycle (pHead) ; } m_dClockSubordinateRatio = CLOCKS_SAME_SCALING_VALUE ; m_Last.Start_Master = UNDEFINED ; m_Last.Start_Host = UNDEFINED ; } // --------------------------------------------------------------------------- // CMonotonicClock // --------------------------------------------------------------------------- CMonotonicClock::CMonotonicClock ( IN LONGLONG llPosDiscontinuityThreshold, // packet - packet threshold IN LONGLONG llNegDiscontinuityThreshold, // packet - packet threshold IN LONGLONG llInitialJitterPadding, // gives first few packets a bit of // breathing room in case there's a // a negative PTS delta early on IN DWORD dwSysClockMaxNonSpanningMillis, // spanning time threshold IN CMpeg2Stats * pStats // stats ) : m_llPosDiscontinuityThreshold (llPosDiscontinuityThreshold), m_llNegDiscontinuityThreshold (0 - llNegDiscontinuityThreshold), m_llInitialJitterPadding (llInitialJitterPadding), m_dwSysClockMaxNonSpanningMillis (dwSysClockMaxNonSpanningMillis), m_pStats (pStats) { ASSERT (m_pStats) ; m_pStats -> AddRef () ; Reset () ; } CMonotonicClock::~CMonotonicClock ( ) { m_pStats -> Release () ; } void CMonotonicClock::Reset ( ) { ASSERT (m_llInitialJitterPadding <= m_llPosDiscontinuityThreshold) ; // we already have our discontinuity threshold set m_llNormalizingTicks = UNDEFINED ; m_llLastTicksNormalized = UNDEFINED ; m_llLastGoodPosDelta = m_llInitialJitterPadding ; m_llLastGoodNegDelta = m_llInitialJitterPadding ; m_dwSysClockTicksLast = UNDEFINED ; } LONGLONG CMonotonicClock::Normalize ( IN LONGLONG llTicks, OUT BOOL * pfTimeDiscontinuity, OUT BOOL * pfSpanningTimeExceeded ) /*++ don't really care about discontinuities because we're going to normalize anyways; of the discontinuity causes a threshold trigger, we already adjust for it; if the discontinuity doesn't cause a threshold trigger, no big deal because the value is closer than we would adjust it to --*/ { LONGLONG llIntraCallTickDelta ; LONGLONG llNormalizedTicks ; DWORD dwSysClockTicksNow ; ASSERT (pfSpanningTimeExceeded) ; dwSysClockTicksNow = GetTickCount () ; (* pfSpanningTimeExceeded) = FALSE ; (* pfTimeDiscontinuity) = FALSE ; // only deal with positive values; if we do get a negative value, we'll get // 1 discontinuity, then continue on llTicks &= MAXLONGLONG ; if (m_llNormalizingTicks != UNDEFINED) { // normalize what we got llNormalizedTicks = llTicks - m_llNormalizingTicks ; // compute the delta llIntraCallTickDelta = llNormalizedTicks - m_llLastTicksNormalized ; // check that we're within threshold if ((llIntraCallTickDelta >= 0 && llIntraCallTickDelta <= m_llPosDiscontinuityThreshold) || (llIntraCallTickDelta < 0 && llIntraCallTickDelta > m_llNegDiscontinuityThreshold)) { // // we're within bounds; we now need to make sure that we return // a value that is greater than 0; we might run into a case where // the first packet we see has value X; we set our normalizing value // to X; the next packet has Y, and Y is actually less than X i.e. // we are seeing an out-of-order packet (perhaps a B-frame in the case // of mpeg-2 elementary video); ok, so if we blindly normalize, we'd // return a negative value i.e. a value 200 years hence, and totally // bogus and invalid considering we never expect a negative value; // we check for this here; now we should be equiped to periodically // get out-of-order timestamps, but that's ok, as long as we return // a value >= 0, and they don't occur for more than 1-2 packets // // we expect this case if (llNormalizedTicks >= 0) { // save off the last good delta, either positive or negative if (llNormalizedTicks > m_llLastTicksNormalized) { // we moved ahead m_llLastGoodPosDelta = llIntraCallTickDelta ; } else { // move backwards m_llLastGoodNegDelta = llIntraCallTickDelta ; } } else { // else we normalized back to a negative value m_llNormalizingTicks = llTicks ; // ratchet our normalizing value llNormalizedTicks = 0 ; // artificially set it to 0 // // no good delta to save off since we are artificially // hard-stopping this at 0 ; leave what we currently have // } // our last ticks, normalized m_llLastTicksNormalized = llNormalizedTicks ; } else { // we've exceeded the threshold; compute a new normalizing value // based on historical tick delta information; assume we are moving // forward; this may result in normalizing ticks that are negative // and thus result in a normalized value that is > than the incoming // tick values; this is ok m_llNormalizingTicks = llTicks - (m_llLastTicksNormalized + m_llLastGoodPosDelta) ; // and recompute our current normalized ticks m_llLastTicksNormalized = llTicks - m_llNormalizingTicks ; // // don't overwrite a last known good delta // // now check if we've exceeded our max spanning time if (m_dwSysClockTicksLast != UNDEFINED && dwSysClockTicksNow - m_dwSysClockTicksLast > m_dwSysClockMaxNonSpanningMillis) { (* pfSpanningTimeExceeded) = TRUE ; } (* pfTimeDiscontinuity) = TRUE ; // log to stats m_pStats -> TimeDiscontinuity () ; } } else { // first time called; compute the normalizing value m_llNormalizingTicks = llTicks - m_llLastGoodPosDelta ; // we may start off at non-zero m_llLastTicksNormalized = m_llLastGoodPosDelta ; } // save these off m_dwSysClockTicksLast = dwSysClockTicksNow ; return m_llLastTicksNormalized ; } // --------------------------------------------------------------------------- // CMPEG2PushClock // --------------------------------------------------------------------------- CMPEG2PushClock::CMPEG2PushClock ( IN BOOL fActive, IN HKEY hkeyRoot, IN CMpeg2Stats * pStats, IN CMPEG2Demultiplexer * pMPEG2Demux, IN DWORD dwMinSamplingWinTicks, IN DWORD dwMaxHistoryMillis, IN DWORD MinSubordinatable, IN DWORD MaxSubordinatable, IN DWORD MaxGlitchesPerHour, OUT HRESULT * pHr ) : m_pStats (pStats), m_qpcCurrentBufferTime (0), m_QPCTicksPerSecond (1), m_ullTotalContInputBufferBytes (0), m_fActive (fActive), m_pMPEG2Demux (pMPEG2Demux), m_hThread (NULL), m_pAdviseListHead (NULL), m_pAdviseNodeFreePool (NULL), m_llLastReturnedIRefTime (0), m_llBaseQPCForLastReturnedTime (0), m_uiTimerResolution (0), m_pIReferenceClock (NULL), m_pPCRMonotonicClock (NULL), m_pIAVStreamNotify (NULL), m_llLastPCR (UNDEFINED), m_llBasePTS (UNDEFINED), m_rtGraphStart (0), m_dCarriedError (0.0), m_rtStartToFirstChannelPCR (0), m_dPlaybackRate (1.0), m_ClockSubordinate (this, 27 * 1000000, // 27 Mhz dwMinSamplingWinTicks, dwMaxHistoryMillis, MinSubordinatable, MaxSubordinatable, pStats ), m_rtBaselinePTSPadding (0), m_rtMinDownstreamBuffering (::MillisToDShow (REG_DEF_MIN_DOWNSTREAM_BUFFERING_MILLIS)), m_rtOverPad (::MillisToDShow (REG_DEF_OVERPAD_MILLIS)), // assume 30 fps, and 1 glitch after we've crept 1 frame (33 ms); convert N*33ms/hr into // REFERENCE_TIME ticks/ms #define REFTIME_SHIFT_PER_MS(glitch_per_hour) (REFERENCE_TIME) (((glitch_per_hour)*33*10000)/3600000) m_RefClockShift (REFTIME_SHIFT_PER_MS(MaxGlitchesPerHour), 0.8), m_fSettling (TRUE), m_dwStartTicks (UNDEFINED), m_cSettlingTicks (0) { LONG l ; DWORD dwMinBufferMillis ; DWORD dwOverpadMillis ; DWORD dw ; TRACE_CONSTRUCTOR (TEXT ("CMPEG2PushClock")) ; ASSERT (pStats) ; ASSERT (hkeyRoot) ; ASSERT (m_pMPEG2Demux) ; ASSERT (pHr) ; * pHr = S_OK ; InitializeCriticalSection (& m_crtIRefConfig) ; InitializeCriticalSection (& m_crtGetTime) ; InitializeListHead (& m_MonotonicClockListHead) ; ZeroMemory (m_rhEvents, EVENT_COUNT * sizeof HANDLE) ; for (dw = 0; dw < EVENT_COUNT; dw++) { m_rhEvents [dw] = CreateEvent (NULL, TRUE, FALSE, NULL) ; if (m_rhEvents [dw] == NULL) { dw = GetLastError () ; * pHr = HRESULT_FROM_WIN32 (dw) ; goto cleanup ; } } // reset all counters ResetClockSubordinatingCounters_ () ; // qpc frequency (filter makes sure that QueryPerformance_ calls // succeed on the host system) m_QPCTicksPerSecond = GetFreq () ; ASSERT (m_QPCTicksPerSecond > 0) ; // overpad dwOverpadMillis = REG_DEF_OVERPAD_MILLIS ; l = RegGetValIfExist ( hkeyRoot, REG_OVERPAD_MILLIS_NAME, TRUE, & dwOverpadMillis ) ; if (l != ERROR_SUCCESS) { dw = GetLastError () ; * pHr = HRESULT_FROM_WIN32 (dw) ; goto cleanup ; } // convert millis to 10mhz m_rtOverPad = ::MillisToDShow (dwOverpadMillis) ; // retrieve min acceptable downstream buffering dwMinBufferMillis = REG_DEF_MIN_DOWNSTREAM_BUFFERING_MILLIS ; l = RegGetValIfExist ( hkeyRoot, REG_MIN_DOWNSTREAM_BUFFERING_MILLIS_NAME, TRUE, & dwMinBufferMillis ) ; if (l != ERROR_SUCCESS) { dw = GetLastError () ; * pHr = HRESULT_FROM_WIN32 (dw) ; goto cleanup ; } // convert millisecond units to 10mhz m_rtMinDownstreamBuffering = ::MillisToDShow (dwMinBufferMillis) ; // settling milliseconds m_cSettlingTicks = REG_DEF_CLOCKSUBORDINATE_SETTLING_MILLIS ; l = RegGetValIfExist ( hkeyRoot, REG_CLOCKSUBORDINATE_SETTLING_MILLIS, TRUE, & m_cSettlingTicks ) ; if (l != ERROR_SUCCESS) { dw = GetLastError () ; * pHr = HRESULT_FROM_WIN32 (dw) ; goto cleanup ; } // snapshot our baseline qpc m_llQPCNormalizer = 0 - SampleClock () ; // we ADD this value to normalize, so it must be signed TRACE_1 (LOG_TRACE, 1, TEXT ("Normalizer: %I64d"), m_llQPCNormalizer) ; ASSERT (m_pStats) ; m_pStats -> AddRef () ; cleanup : return ; } CMPEG2PushClock::~CMPEG2PushClock ( ) { SINGLE_LIST_ENTRY * pSListEntry ; ADVISE_NODE * pAdviseNode ; DWORD dw ; TRACE_DESTRUCTOR (TEXT ("CMPEG2PushClock")) ; // should have been "set" with a NULL clock pointer by now ASSERT (m_pIReferenceClock == NULL) ; if (m_hThread) { ASSERT (m_rhEvents [EVENT_STOP] != NULL) ; SetEvent (m_rhEvents [EVENT_STOP]) ; WaitForSingleObject (m_hThread, INFINITE) ; CloseHandle (m_hThread) ; } for (dw = 0; dw < EVENT_COUNT; dw++) { if (m_rhEvents [dw] != NULL) { CloseHandle (m_rhEvents [dw]) ; } else { break ; } } if (m_uiTimerResolution != 0) { timeBeginPeriod (m_uiTimerResolution) ; } // free up what's in our free pool list while (m_pAdviseNodeFreePool != NULL) { // pull the entry off the front pSListEntry = m_pAdviseNodeFreePool ; // advance the list head m_pAdviseNodeFreePool = m_pAdviseNodeFreePool -> Next ; // free the resources pAdviseNode = CONTAINING_RECORD (pSListEntry, ADVISE_NODE, SListEntry) ; delete pAdviseNode ; } // and in case there are filters that have not canceled their advise requests // we free those too; but we don't expect any ASSERT (m_pAdviseListHead == NULL) ; while (m_pAdviseListHead != NULL) { // pull entry off the front pSListEntry = m_pAdviseListHead ; // move the listhead ahead m_pAdviseListHead = m_pAdviseListHead -> Next ; // free the resources pAdviseNode = CONTAINING_RECORD (pSListEntry, ADVISE_NODE, SListEntry) ; delete pAdviseNode ; } if (m_pPCRMonotonicClock) { RemoveEntryList (& m_pPCRMonotonicClock -> m_ListEntry) ; delete m_pPCRMonotonicClock ; } ASSERT (IsListEmpty (& m_MonotonicClockListHead)) ; ASSERT (m_pStats) ; m_pStats -> Release () ; DeleteCriticalSection (& m_crtIRefConfig) ; DeleteCriticalSection (& m_crtGetTime) ; } LONGLONG CMPEG2PushClock::SampleClock ( ) { BOOL r ; LARGE_INTEGER li ; r = ::QueryPerformanceCounter (& li) ; // check is made in filter that these QPC calls work on the current system ASSERT (r) ; return li.QuadPart ; } LONGLONG CMPEG2PushClock::GetFreq ( ) { LARGE_INTEGER li ; BOOL r ; r = ::QueryPerformanceFrequency (& li) ; // check is made in filter that these QPC calls work on the current system ASSERT (r) ; if (!r) { // don't get a divide-by-0.. li.QuadPart = 1 ; } return li.QuadPart ; } void CMPEG2PushClock::ResetMonotonicClocks_ ( ) { LIST_ENTRY * pListEntry ; CStreamMonotonicClock * pMonotonicClock ; for (pListEntry = m_MonotonicClockListHead.Flink ; pListEntry != & m_MonotonicClockListHead ; pListEntry = pListEntry -> Flink) { pMonotonicClock = CONTAINING_RECORD (pListEntry, CStreamMonotonicClock, m_ListEntry) ; pMonotonicClock -> Reset () ; } } void CMPEG2PushClock::LogInputBufferQPCTime ( IN LONGLONG qpcInputBufferTime, IN LONGLONG qpcLocalTime, IN int iInputBufferLengthBytes ) /*++ purpose: Logs qpc times for each new input buffer (containing the transport stream). Gets the buffer's qpc time, and the local qpc time. This allows us to shift by the proper amount when we get a time request. parameters: qpcInputBufferTime QPC time associated with the last byte of the buffer qpcLocalTime QPC time at which the input buffer was received by the demux iInputBufferLengthBytes length of the input buffer --*/ { m_ullTotalContInputBufferBytes += iInputBufferLengthBytes ; m_qpcCurrentBufferTime = NORMALIZED_QPC (qpcInputBufferTime) ; // normalized QPC buffer time } void CMPEG2PushClock::AdviseThreadBody ( ) { DWORD dwWaitRetVal ; DWORD dwWait ; REFERENCE_TIME rtNow ; HRESULT hr ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::AdviseThreadBody ()")) ; dwWait = INFINITE ; TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Advise Thread [%08xh,%08xh]: starting"), GetCurrentThread (), GetCurrentThreadId ()) ; for (;;) { dwWaitRetVal = WaitForMultipleObjects (EVENT_COUNT, m_rhEvents, FALSE, dwWait) ; // xxxx // is it possible to get stuck processing other recurring events all the while // the EVENT_STOP event is signaled ?? if (dwWaitRetVal == EVENT_STOP) { TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Advise Thread [%08xh,%08xh]: EVENT_STOP"), GetCurrentThread (), GetCurrentThreadId ()) ; break ; } hr = GetTime (& rtNow) ; if (SUCCEEDED (hr)) { switch (dwWaitRetVal) { case WAIT_TIMEOUT : LockIRefConfig_ () ; // add on 1 millisecond so we won't spin freely if there's another // advise node that is less than 1 millisecond from now (giving // us dwWait of 0) dwWait = ProcessNotificationTimeoutLocked_ (rtNow + 10000) ; UnlockIRefConfig_ () ; TRACE_3 (LOG_DEMUX_PSI, 4, TEXT ("Advise Thread [%08xh,%08xh]: WAIT_TIMEOUT (%d)"), GetCurrentThread (), GetCurrentThreadId (), dwWait) ; break ; case EVENT_NEW_ADVISE : LockIRefConfig_ () ; dwWait = ResetWaitTimeLocked_ (rtNow) ; ResetEvent (m_rhEvents [EVENT_NEW_ADVISE]) ; UnlockIRefConfig_ () ; TRACE_3 (LOG_DEMUX_PSI, 4, TEXT ("Advise Thread [%08xh,%08xh]: EVENT_NEW_ADVISE (%d)"), GetCurrentThread (), GetCurrentThreadId (), dwWait) ; break ; } ; } } TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Advise Thread [%08xh,%08xh]: exiting"), GetCurrentThread (), GetCurrentThreadId ()) ; return ; } void CMPEG2PushClock::QueueAdviseTimeout_ ( IN ADVISE_NODE * pNewAdviseNode ) // must hold the list lock { SINGLE_LIST_ENTRY ** ppCurSListEntry ; ADVISE_NODE * pCurAdviseNode ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::QueueAdviseTimeout_ ()")) ; ASSERT (pNewAdviseNode) ; // list is sorted by advise time; find the slot for (ppCurSListEntry = & m_pAdviseListHead; * ppCurSListEntry != NULL; ppCurSListEntry = & (* ppCurSListEntry) -> Next) { pCurAdviseNode = CONTAINING_RECORD (* ppCurSListEntry, ADVISE_NODE, SListEntry) ; if (pCurAdviseNode -> rtAdviseTime >= pNewAdviseNode -> rtAdviseTime) { break ; } } // and insert it pNewAdviseNode -> SListEntry.Next = * ppCurSListEntry ; * ppCurSListEntry = & pNewAdviseNode -> SListEntry ; m_pStats -> QueuedAdvise () ; } HRESULT CMPEG2PushClock::CancelAdviseTimeout_ ( IN ADVISE_NODE * pAdviseNode ) // searches the list of advise nodes, and removes it if found { SINGLE_LIST_ENTRY ** ppCurSListEntry ; ADVISE_NODE * pCurAdviseNode ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::CanceldviseTimeout_ ()")) ; // search from beginning to end for the advise node; unfortunately there's // no way to ensure that the caller has not given us a bogus pointer for (ppCurSListEntry = & m_pAdviseListHead; * ppCurSListEntry != NULL; ppCurSListEntry = & ((* ppCurSListEntry) -> Next)) { pCurAdviseNode = CONTAINING_RECORD (*ppCurSListEntry, ADVISE_NODE, SListEntry) ; if (pCurAdviseNode == pAdviseNode) { // unhook * ppCurSListEntry = pCurAdviseNode -> SListEntry.Next ; RecycleAdviseNode_ (pCurAdviseNode) ; // count it as an advise m_pStats -> Advise () ; // success return S_OK ; } } // failure return E_FAIL ; } DWORD CMPEG2PushClock::ProcessNotificationTimeoutLocked_ ( IN REFERENCE_TIME rtNow ) // locks held: list lock { SINGLE_LIST_ENTRY * pSListEntry ; ADVISE_NODE * pAdviseNode ; #ifndef UNDER_CE REFERENCE_TIME rtDelta ; #endif //UNDER_CE LONG lPreviousCount ; TRACE_ENTER_1 (TEXT ("CMPEG2PushClock::ProcessNotificationTimeoutLocked_ (%016I64x)"), rtNow) ; for (pSListEntry = m_pAdviseListHead ; pSListEntry != NULL ; pSListEntry = m_pAdviseListHead) { // recover the advise_node pAdviseNode = CONTAINING_RECORD (pSListEntry, ADVISE_NODE, SListEntry) ; // break from the loop if we are into notifications which must be // signaled in the future. if (pAdviseNode -> rtAdviseTime > rtNow) { break ; } // the list head points to a node that has a notification that must be made // now or in the past // and remove it from the front m_pAdviseListHead = pAdviseNode -> SListEntry.Next ; // if it's a period advise if (pAdviseNode -> rtPeriodTime != 0) { // signal the semaphore ReleaseSemaphore ( pAdviseNode -> hSignal, 1, & lPreviousCount ) ; m_pStats -> Advise () ; #ifdef DEBUG REFERENCE_TIME rtNow1 ; REFERENCE_TIME rtDelta1 ; GetTime (& rtNow1) ; rtDelta1 = rtNow1 - pAdviseNode -> rtAdviseTime ; TRACE_3 (LOG_DEMUX_PSI, 3, TEXT ("Periodic: now - advise = %10I64d %5I64d; %08xh"), rtDelta1, DSHOW_TO_MILLISECONDS (rtDelta1), pAdviseNode -> hSignal) ; #endif // increment to the next time we need to notify pAdviseNode -> rtAdviseTime += pAdviseNode -> rtPeriodTime ; // and queue for the next timeout QueueAdviseTimeout_ ( pAdviseNode ) ; } else { // otherwise, it's a one shot notification // signal the event SetEvent (pAdviseNode -> hSignal) ; m_pStats -> Advise () ; #ifdef DEBUG REFERENCE_TIME rtNow1 ; REFERENCE_TIME rtDelta1 ; GetTime (& rtNow1) ; rtDelta1 = rtNow1 - pAdviseNode -> rtAdviseTime ; TRACE_3 (LOG_DEMUX_PSI, 3, TEXT ("single: now - advise = %10I64d %5I64d; %08xh"), rtDelta1, DSHOW_TO_MILLISECONDS (rtDelta1), pAdviseNode -> hSignal) ; #endif RecycleAdviseNode_ (pAdviseNode) ; } } return ResetWaitTimeLocked_ (rtNow) ; } DWORD CMPEG2PushClock::ResetWaitTimeLocked_ ( IN REFERENCE_TIME rtNow ) // locks held: list lock { REFERENCE_TIME rtDelta ; ADVISE_NODE * pAdviseNode ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::ResetWaitTimeLocked_ ()")) ; // figure out how long we must wait until the next one if (m_pAdviseListHead) { pAdviseNode = CONTAINING_RECORD (m_pAdviseListHead, ADVISE_NODE, SListEntry) ; rtDelta = pAdviseNode -> rtAdviseTime > rtNow ? pAdviseNode -> rtAdviseTime - rtNow : 0 ; TRACE_3 (LOG_DEMUX_PSI, 4, TEXT ("WaitNext() : rtAdviseTime = %I64d, rtNow = %I64d, rtAdviseTime - rtNow = %I64d"), pAdviseNode -> rtAdviseTime, rtNow, pAdviseNode -> rtAdviseTime - rtNow) ; // safe cast because we are dealing with a delta vs. an absolute time return SkewedWaitMilliseconds_ ((DWORD) (DSHOW_TO_MILLISECONDS (rtDelta))) ; } else { // there are none queued to be processed; wait infinitely long return INFINITE ; } } HRESULT CMPEG2PushClock::AdvisePeriodicLocked_ ( IN REFERENCE_TIME rtStartTime, IN REFERENCE_TIME rtPeriodTime, IN HSEMAPHORE hSemaphore, OUT DWORD_PTR * pdwpContext ) { ADVISE_NODE * pAdviseNode ; HRESULT hr ; TRACE_ENTER_4 (TEXT ("CMPEG2PushClock::AdvisePeriodicLocked_ (%016I64x, %016I64x, %08xh, %8xh)"), rtStartTime, rtPeriodTime, hSemaphore, pdwpContext) ; // confirm that this is a valid request if (rtStartTime < 0 || rtPeriodTime <= 0 || rtStartTime == MAX_REFERENCE_TIME) { return E_INVALIDARG ; } // make sure our advisory thread is up and running hr = ConfirmAdviseThreadRunning_ () ; if (FAILED (hr)) { return hr ; } // get a node pAdviseNode = GetAdviseNode_ () ; if (pAdviseNode == NULL) { return E_OUTOFMEMORY ; } // set the fields pAdviseNode -> hSignal = reinterpret_cast (hSemaphore) ; pAdviseNode -> rtAdviseTime = rtStartTime ; pAdviseNode -> rtPeriodTime = rtPeriodTime ; QueueAdviseTimeout_ (pAdviseNode) ; // if we inserted at the head, processing thread will need to reset its // wait period if (m_pAdviseListHead == & pAdviseNode -> SListEntry) { SetEvent (m_rhEvents [EVENT_NEW_ADVISE]) ; } * pdwpContext = (DWORD_PTR) pAdviseNode ; return S_OK ; } HRESULT CMPEG2PushClock::AdviseTimeLocked_ ( IN REFERENCE_TIME rtBaseTime, IN REFERENCE_TIME rtStreamTime, IN HEVENT hEvent, OUT DWORD_PTR * pdwpContext ) { ADVISE_NODE * pAdviseNode ; REFERENCE_TIME rt ; HRESULT hr ; TRACE_ENTER_4 (TEXT ("CMPEG2PushClock::AdviseTimeLocked_ (%016I64x, %016I64x, %08xh, %8xh)"), rtBaseTime, rtStreamTime, hEvent, pdwpContext) ; // confirm that this is a valid request rt = rtBaseTime + rtStreamTime ; if (rt <= 0 || rt == MAX_REFERENCE_TIME || rtStreamTime < 0) { return E_INVALIDARG ; } // make sure our advisory thread is up and running hr = ConfirmAdviseThreadRunning_ () ; if (FAILED (hr)) { return hr ; } // get a node pAdviseNode = GetAdviseNode_ () ; if (pAdviseNode == NULL) { return E_OUTOFMEMORY ; } // set the fields pAdviseNode -> hSignal = reinterpret_cast (hEvent) ; pAdviseNode -> rtAdviseTime = rtBaseTime + rtStreamTime ; ASSERT (pAdviseNode -> rtPeriodTime == 0) ; QueueAdviseTimeout_ (pAdviseNode) ; // if we inserted at the head, processing thread will need to reset its // wait period if (m_pAdviseListHead == & pAdviseNode -> SListEntry) { SetEvent (m_rhEvents [EVENT_NEW_ADVISE]) ; } * pdwpContext = (DWORD_PTR) pAdviseNode ; return S_OK ; } // --------------------------------------------------------------------------- // CMPEG2PushClock // --------------------------------------------------------------------------- STDMETHODIMP CMPEG2PushClock::QueryInterface ( IN REFIID riid, OUT void ** ppv ) { // delegate always return m_pMPEG2Demux -> QueryInterface (riid, ppv) ; } STDMETHODIMP_(ULONG) CMPEG2PushClock::AddRef ( ) { // delegate always return m_pMPEG2Demux -> AddRef () ; } STDMETHODIMP_(ULONG) CMPEG2PushClock::Release ( ) { // hmm.. if we return what the demuxfilter returns, could be the case where // the demux filter destroys itself, and thus destroys this object (if we're // in the destructor), and the code then comes back out through here .. // could this happen .. ? The remedy is to not explicitely delete this object // from the filter's destructor, but instead to examine the value of the // the Release call to the filter and delete self if it's 0 // delegate always return m_pMPEG2Demux -> Release () ; } STDMETHODIMP CMPEG2PushClock::AdvisePeriodic ( IN REFERENCE_TIME rtStartTime, IN REFERENCE_TIME rtPeriodTime, IN HSEMAPHORE hSemaphore, OUT DWORD_PTR * pdwpContext ) { HRESULT hr ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::AdvisePeriodic ()")) ; // validate the parameters if (pdwpContext == NULL || hSemaphore == NULL || rtPeriodTime == 0) { return E_INVALIDARG ; } LockIRefConfig_ () ; hr = AdvisePeriodicLocked_ ( rtStartTime, rtPeriodTime, hSemaphore, pdwpContext ) ; UnlockIRefConfig_ () ; return hr ; } STDMETHODIMP CMPEG2PushClock::AdviseTime ( IN REFERENCE_TIME rtBaseTime, IN REFERENCE_TIME rtStreamTime, IN HEVENT hEvent, OUT DWORD_PTR * pdwpContext ) { HRESULT hr ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::AdviseTime ()")) ; if (hEvent == NULL || pdwpContext == NULL) { return E_INVALIDARG ; } LockIRefConfig_ () ; hr = AdviseTimeLocked_ ( rtBaseTime, rtStreamTime, hEvent, pdwpContext ) ; UnlockIRefConfig_ () ; return hr ; } STDMETHODIMP CMPEG2PushClock::GetTime ( OUT REFERENCE_TIME * pTime ) { LONGLONG llDelta ; double dDelta ; double dDeltaScaled ; #ifndef UNDER_CE REFERENCE_TIME rtGetTime ; #endif //UNDER_CE LONGLONG llNow ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::GetTime ()")) ; if (pTime == NULL) { return E_POINTER ; } LockGetTime_ () ; // sample llNow = SampleClock () ; // delta llDelta = NORMALIZED_QPC (llNow) - m_llBaseQPCForLastReturnedTime ; // make sure we don't go backwards; BIOS bugs have made this happen llDelta = (llDelta > 0 ? llDelta : 0) ; // save the delta; safe cast because this is a very small delta dDelta = (double) llDelta ; // scale according to our slope (if we're subordinating) dDeltaScaled = dDelta * m_ClockSubordinate.ScalingVal () ; // accumulate the error dDeltaScaled += m_dCarriedError ; // compute the non FP value that we are going to use llDelta = (LONGLONG) dDeltaScaled ; // make sure bizarre conditions have not conspired to make the clock run // backwards if (llDelta < 0) { llDelta = 0 ; m_dCarriedError = 0.0 ; } // carry the error to the next call m_dCarriedError = dDeltaScaled - ((double) llDelta) ; // save the "now: value off as the basis for the last returned time m_llBaseQPCForLastReturnedTime = NORMALIZED_QPC (llNow) ; // save this off as the last returned time m_llLastReturnedIRefTime += llDelta ; // set the return value & scale (* pTime) = QPCToDShow (m_llLastReturnedIRefTime, m_QPCTicksPerSecond) ; // shift (* pTime) += m_RefClockShift.AdjustCurVal () ; // make sure we never set a time that runs backwards (* pTime) = Max ((* pTime), m_rtMinReturnedTime) ; // save this off m_rtMinReturnedTime = (* pTime) ; UnlockGetTime_ () ; return S_OK ; } STDMETHODIMP CMPEG2PushClock::Unadvise ( IN DWORD_PTR dwpContext ) { HRESULT hr ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::Unadvise ()")) ; if (dwpContext == 0) { return E_INVALIDARG ; } LockIRefConfig_ () ; hr = CancelAdviseTimeout_ (reinterpret_cast (dwpContext)) ; UnlockIRefConfig_ () ; return hr ; } // called by the content manager to notify the clock that a new PCR // source will be providing PCRs and that is must reset its historical // QPC - PCR data. HRESULT CMPEG2PushClock::ResetPCRSource ( ) { TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::ResetPCRSource ()")) ; if (m_pPCRMonotonicClock) { // PCR clock m_pPCRMonotonicClock -> Reset () ; } else { // ---------------------------------- // PCR clock // get a new clock smoothing object for the PCR values; the object // ensures that the PCR ticks always monotonically increase m_pPCRMonotonicClock = new CStreamMonotonicClock ( // positive threshold + 100 millis over spec MILLISECONDS_TO_PCR_TIME_BASE (Max (MAX_INTRA_PCR_INTERVAL_MILLIS, MAX_INTRA_SCR_INTERVAL_MILLIS) + 100), 0, // no negative threshold 0, // starting normalized offset Max (MAX_INTRA_PCR_INTERVAL_MILLIS, MAX_INTRA_SCR_INTERVAL_MILLIS) + 100, // + 100 millis jitter room m_pStats, 0 // don't care about the stream id ) ; if (m_pPCRMonotonicClock == NULL) { return E_OUTOFMEMORY ; } // keep it on our list of clock InsertHeadList (& m_MonotonicClockListHead, & m_pPCRMonotonicClock -> m_ListEntry) ; } // regardless, we clear out all history ResetClockSubordinatingCounters_ () ; // reset all monotonic clocks ResetMonotonicClocks_ () ; LockGetTime_ () ; m_rtMinReturnedTime = 0 ; // reset carried error m_dCarriedError = 0.0 ; UnlockGetTime_ () ; m_RefClockShift.Reset () ; m_rtBaselinePTSPadding = 0 ; // and this gets reset as well; m_llLastPCR = UNDEFINED ; m_llBasePTS = UNDEFINED ; m_pStats -> NewTimeline () ; m_dwStartTicks = ::timeGetTime () ; m_fSettling = TRUE ; return S_OK ; } void CMPEG2PushClock::FilterStateChanged ( IN FILTER_STATE NewFilterState, IN REFERENCE_TIME rtStart // 0 if not start run ) { TRACE_ENTER_1(TEXT ("CMPEG2PushClock::FilterStateChanged (%08xh)"), NewFilterState) ; switch (NewFilterState) { case State_Running : m_rtGraphStart = rtStart ; m_rtMinReturnedTime = rtStart ; // make sure time never runs backwards m_dwStartTicks = ::timeGetTime () ; m_fSettling = TRUE ; break ; case State_Paused : break ; case State_Stopped : m_rtGraphStart = 0 ; ResetMonotonicClocks_ () ; ResetClockSubordinatingCounters_ () ; break ; } ; } // called by the current PCR buffer source HRESULT CMPEG2PushClock::NewPCRValue ( IN LONGLONG llPCR, // PCR value IN BOOL fDiscontinuity, // TRUE if this PCR follows a discontinuity IN int iLastPCRByteOffset, // input buffer offset IN CTStickyVal * pReset ) /*++ all QPC values are normalized, so we should "never" reach a point where they are less than 0 Discontinuities: ---------------- (1) no discontinuity the clock value smoothing object will make sure that PCR-PCR we are within allowable boundaries; (2) a discontinuity if there is a discontinuity, the clock smoothing will smooth out the the discontinuity; if we subsequently compare it with system time we'll get a bogus ratio, so we don't try; we let the smoothing object get the current ticks and it will smooth them appropriately, and the the next (if there's no discontinuity) will occur a proportionally equivalent time afterwards where we can compare them again Time-based Reset Triggers: -------------------------- We cannot blithely smooth out PCR stream discontinuities because that will result in smoothed out time & stream discontinuities in the av stream timestamping. We'll then incorrectly smooth out the presentation timestamps by collapsing the timegap since the last, and thereby cause an immediate underflow downstream. We determine if the demux needs a reset, including a new presentation timestamping base here because, from a timestamping point of view, we only really care if there are av streams and we are generating timestamps. We determine whether or not to reset if the m_llLastPCR is not UNDEFINED and we have a discontinuity. If this happens, it means we were processing a stream of PCRs and encountered a discontinuity. We then set the outgoing flag to TRUE to instruct the caller, ultimately the mapper, to reset the demultiplexer, including the timestamping. The monotonic clocks help in this matter as well. If they detect a spanned PCR/SCR the reset flag gets automatically set to TRUE. There is no way to recover in that case. An example of this occurs with EchoStar satellite service when the decrypting card is unplugged. The transport stream continues, but is encrypted. Later, passed the max spanning time, the card can be plugged back in and the stream now is decrypted. There were no discontinuities in between, but we cannot collapse the spanned time. --*/ { LONGLONG llNormalizedPCR ; BOOL fSpanned ; BOOL fTimeDiscontinuity ; m_pStats -> PCR (llPCR) ; // check for a reset condition: in the midst of a PCR stream and a // discontinuity; we can safely surmise that the input stream has been // disconnected and instruct, via the fReset param, the demultiplexer // to reset itself. The reset will cause the m_llLastPCR = UNDEFINED, // so the first time following a reset, we'll pass over this fine. if (fDiscontinuity && m_llLastPCR != UNDEFINED) { pReset -> Set (TRUE) ; return S_OK ; } // log this so we know we're receiving PCRs m_llLastPCR = llPCR ; //TRACE_2 (LOG_TIMING, 2, TEXT ("PCR = %I64d (%I64d ms)"), m_llLastPCR, m_llLastPCR / 27000) ; // reset if the spanning was exceeded; it's impossible to accurately // compute where the stream should restart ASSERT (m_pPCRMonotonicClock) ; llNormalizedPCR = m_pPCRMonotonicClock -> Normalize (llPCR, & fTimeDiscontinuity, & fSpanned) ; pReset -> Set (fSpanned || fTimeDiscontinuity) ; if (fSpanned || fTimeDiscontinuity) { TRACE_2 (LOG_TIMING, 1, TEXT ("SCR: fSpanned = %d, fTimeDiscontinuity = %d"), fSpanned, fTimeDiscontinuity) ; } // only adjust if there hasn't been a discontinuity if (!fDiscontinuity && !fTimeDiscontinuity && !fSpanned) { if (m_fSettling) { ASSERT (m_dwStartTicks != UNDEFINED) ; if (::timeGetTime () - m_dwStartTicks >= m_cSettlingTicks) { m_fSettling = FALSE ; } else { goto cleanup ; } } // log receipt m_ClockSubordinate.OnMasterTime (llPCR) ; } cleanup : return S_OK ; } HRESULT CMPEG2PushClock::GetNewPTSContext ( IN DWORD dwStreamIdentifier, OUT DWORD_PTR * pdwContext ) { CStreamMonotonicClock * pMonotonicClock ; HRESULT hr ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::GetNewPTSContext ()")) ; ASSERT (pdwContext) ; pMonotonicClock = new CStreamMonotonicClock ( MILLISECONDS_TO_PTS_TIME_BASE (MAX_INTRA_PTS_INTERVAL_MILLIS), // pos threshold MILLISECONDS_TO_PTS_TIME_BASE (MAX_INTRA_PTS_INTERVAL_MILLIS), // neg threshold 0, // starting normalized offset MAX_INTRA_PTS_INTERVAL_MILLIS + 100, // give ourselves 100 millis' room m_pStats, dwStreamIdentifier ) ; if (pMonotonicClock == NULL) { return E_OUTOFMEMORY ; } ASSERT (m_pIAVStreamNotify) ; hr = m_pIAVStreamNotify -> AVStreamMapped (pMonotonicClock -> GetStreamIdentifier ()) ; if (FAILED (hr)) { delete pMonotonicClock ; return hr ; } // parser creation should be serialized, so we should not have to // serialize here InsertHeadList (& m_MonotonicClockListHead, & pMonotonicClock -> m_ListEntry) ; * pdwContext = reinterpret_cast (pMonotonicClock) ; return S_OK ; } void CMPEG2PushClock::RecyclePTSContext ( IN DWORD_PTR dwContext ) { CStreamMonotonicClock * pMonotonicClock ; TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::RecyclePTSContext ()")) ; pMonotonicClock = reinterpret_cast (dwContext) ; // parser creation should be serialized, so we should not have to // serialize here RemoveEntryList (& pMonotonicClock -> m_ListEntry) ; ASSERT (m_pIAVStreamNotify) ; m_pIAVStreamNotify -> AVStreamUnmapped (pMonotonicClock -> GetStreamIdentifier ()) ; delete pMonotonicClock ; } BOOL CMPEG2PushClock::FirstPCRProcessed ( IN DWORD_PTR dwContext ) { TRACE_ENTER_0 (TEXT ("CMPEG2PushClock::FirstPCRProcessed ()")) ; return (m_llLastPCR != UNDEFINED) ; } void CMPEG2PushClock::MediaSampleTimesFromPTS ( IN DWORD_PTR dwContext, // PTS context IN DWORD stream_id, // PES stream id IN LONGLONG llPES_PTS, // PES value IN CTStickyVal * pReset, OUT REFERENCE_TIME * pStartTime, // return start time OUT REFERENCE_TIME * pStopTime, // return stop time OUT BOOL * pfPaddingAdjust ) { LONGLONG llPESNormalized ; CStreamMonotonicClock * pMonotonicClock ; REFERENCE_TIME rtNow ; LONGLONG llPTSBasedPCR ; LONGLONG llScaledPCRLast ; BOOL fSpanned ; BOOL fTimeDiscontinuity ; REFERENCE_TIME rtDownstreamBuffering ; REFERENCE_TIME rtAdjustment ; BOOL fFirstPTSForContext ; REFERENCE_TIME rtShiftCorrection ; (* pfPaddingAdjust) = FALSE ; if (m_llLastPCR != UNDEFINED) { // toggle this later if we are the first for this context fFirstPTSForContext = FALSE ; if (m_llBasePTS == UNDEFINED) { // still need to set a base PTS; we can do this now because we've // seen the first PCR // this is the first stream to pass to see the state; next stream // won't need to do this, because we compute the baseline PTS from // the last PCR processed, now; before any other AV streams pass // through here several more PCRs might be received, but the baseline // is still the same // make sure we're not dealing with the case where the sampling // clock wrapped between PTS and PCR values; PCRs will *always* // be ahead of the PTSs; if not, we bail and wait for the next // PCR (< 1/10th of a second) rather than dealing with complicated // wrapped values; ALLOWABLE_DISPARITY is what we're prepared to // handle wrt to allowable buffering; obviously we don't know // how much buffering exists at the headend between the packetizers // and the MUXer, but it's a fair assumption that it's quite small llPTSBasedPCR = PCRToPTS (m_llLastPCR) ; if (llPES_PTS >= llPTSBasedPCR && llPES_PTS <= llPTSBasedPCR + SCR_PTS_ALLOWABLE_DISPARITY) { // found our base PTS for this segment m_llBasePTS = llPTSBasedPCR ; // baseline is non-zero if there's a clock in the graph and // we're not going to generage timestamps for a stream being // played back from file if (m_pIReferenceClock && !m_pMPEG2Demux -> IsInPullMode ()) { m_pIReferenceClock -> GetTime (& rtNow) ; m_rtStartToFirstChannelPCR = rtNow - m_rtGraphStart ; } else { // clock has been explicitely cleared; timestamps won't // matter, but we need a value here anyways m_rtStartToFirstChannelPCR = 0 ; } m_pStats -> BasePCR (m_llLastPCR) ; } else { // either the sampling clock wrapped or the PTSs in this stream // are from another clock i.e. not related to those in the PCR // stream; drop this one and wait for the next (* pStartTime) = UNDEFINED ; m_pStats -> OutOfBoundsPTS (llPES_PTS) ; TRACE_2 (LOG_DEMUX_PSI, 2, TEXT ("PTS-PCR values out of bounds (#1): PCR(90kz) = %I64d ms, llPES_PTS = %I64d ms"), llPTSBasedPCR / 90, llPES_PTS / 90) ; //ASSERT (0) ; return ; } // initialize our PTS shifter m_RefClockShift.Reset () ; TRACE_1 (LOG_DEMUX_PSI, 2, TEXT ("new timeline: %I64d"), m_llBasePTS) ; } // obtain our current PTS_scaled PCR value llScaledPCRLast = PCRToPTS (m_llLastPCR) ; // recover the clock pMonotonicClock = reinterpret_cast (dwContext) ; // might still need to compute this stream's timeline offset if (pMonotonicClock -> GetStreamTimelineOffset () == UNDEFINED) { // make sure we're within acceptable bounds if (llPES_PTS >= m_llBasePTS && llPES_PTS <= m_llBasePTS + SCR_PTS_ALLOWABLE_DISPARITY) { pMonotonicClock -> SetStreamTimelineOffset (PTSToDShow (llPES_PTS - m_llBasePTS)) ; // first timestamp for this stream fFirstPTSForContext = TRUE ; } else { // a PES PTS that seems to be out of bounds with the base PTS // was found; could be that this stream does not belong to // the same stream as the PCR stream as would occur if some // has audio from 1 program mapped with video from another // program (* pStartTime) = UNDEFINED ; m_pStats -> OutOfBoundsPTS (llPES_PTS) ; TRACE_2 (LOG_DEMUX_PSI, 2, TEXT ("PTS-PCR values out of bounds (#2): PCR(90kz) = %I64d ms, llPES_PTS = %I64d ms"), llPTSBasedPCR / 90, llPES_PTS / 90) ; //ASSERT (0) ; return ; } TRACE_2 (LOG_DEMUX_PSI, 2, TEXT ("new offset: clock = %08x, offset = %I64d"), pMonotonicClock, pMonotonicClock -> GetStreamTimelineOffset ()) ; } // check for an out-of-bounds PTS if (llPES_PTS >= llScaledPCRLast && // don't check if 1 has wrapped llPES_PTS > llScaledPCRLast + SCR_PTS_ALLOWABLE_DISPARITY) { m_pStats -> OutOfBoundsPTS (llPES_PTS) ; (* pStartTime) = UNDEFINED ; TRACE_2 (LOG_DEMUX_PSI, 2, TEXT ("PTS-PCR values out of bounds (#3): PCR(90kz) = %I64d ms, llPES_PTS = %I64d ms"), llPTSBasedPCR / 90, llPES_PTS / 90) ; //ASSERT (0) ; return ; } // normalize the value we got; m_llBasePTS is the first PCR, scaled // to PTS value, that we saw for this stream; this assures a/v sync llPESNormalized = pMonotonicClock -> Normalize (llPES_PTS, & fTimeDiscontinuity, & fSpanned) ; // reset if either occured; because video presentation timestamps do not // monotonically increase, it is impossible to guess whether or not we // should add or subtract; if we add when we should have subtracted // repeatedly we run into av sync issues and/or buffering problems pReset -> Set (fSpanned || fTimeDiscontinuity) ; if (fSpanned || fTimeDiscontinuity) { TRACE_2 (LOG_TIMING, 1, TEXT ("PTS: fSpanned = %d, fTimeDiscontinuity = %d"), fSpanned, fTimeDiscontinuity) ; } // convert to dshow time (* pStartTime) = PTSToDShow (llPESNormalized) ; // add on the timeline offset (* pStartTime) += pMonotonicClock -> GetStreamTimelineOffset () ; // add on stream time offset (* pStartTime) += m_rtStartToFirstChannelPCR ; // pad if to allow for downstream processing prior to rendering (* pStartTime) += m_rtBaselinePTSPadding ; // stats; it's legal to have the clock reference clock be NULL if (m_pIReferenceClock && !m_pMPEG2Demux -> IsInPullMode ()) { m_pIReferenceClock -> GetTime (& rtNow) ; // compute our buffering & check for an underflow rtDownstreamBuffering = (* pStartTime) - (rtNow - m_rtGraphStart) ; #if 0 // ---------------------------------------------------------------- // // use these traces to help troubleshoot clock-subordinating issues static DWORD dw = 0 ; static DWORD_PTR dwp = 0 ; if (GetTickCount () - dw > 1000) { if (dwp == 0) { dwp = dwContext ; } if (dwp == dwContext) { TRACE_5 (LOG_TIMING,1, TEXT (",%I64d,%I64d,%I64d,%I64d,%I64d,"), rtNow - m_RefClockShift.CurVal (), (rtNow - m_RefClockShift.CurVal ()) - (REFERENCE_TIME) (((double) m_llLastPCR)/2.7), m_llLastPCR, llPES_PTS, (* pStartTime)) ; dw = GetTickCount () ; } } // ---------------------------------------------------------------- #endif if (rtDownstreamBuffering < m_rtMinDownstreamBuffering) { // underflow detected; compute our deficit rtAdjustment = m_rtMinDownstreamBuffering - rtDownstreamBuffering ; // if this isn't the first of this timeline (99.9% case), set // the target val as what we're currently shifting by + the // deficit if (!fFirstPTSForContext) { // we want to slow the clock down so we only put negative // values in the shift rtShiftCorrection = -(rtAdjustment - m_RefClockShift.CurVal ()) ; //TRACE_2 (LOG_TIMING, 1, TEXT ("(runtime) Padding Adjustment (short by %u ms); CurVal = %d ms"), ::DShowToMillis (rtAdjustment), ::DShowToMillis (m_RefClockShift.CurVal ())) ; m_RefClockShift.SetTargetVal (rtShiftCorrection) ; // we'll adjust on the next time through (BUGBUG : should we) } else { // this is our first time through on this timeline; adjust // the baseline padding TRACE_2 (LOG_TIMING, 1, TEXT ("[%08xh] (1st time) Padding Adjustment (short by %u ms)"), dwContext, ::DShowToMillis (rtAdjustment)) ; rtAdjustment += m_rtOverPad ; m_rtBaselinePTSPadding += rtAdjustment ; (* pStartTime) += rtAdjustment ; (* pfPaddingAdjust) = TRUE ; } m_pStats -> BufferAdjustment () ; } m_pStats -> Buffering (& rtDownstreamBuffering) ; m_pStats -> RefclockShift (& m_RefClockShift) ; } // make sure we have a positive time (* pStartTime) = Max ((* pStartTime), 0) ; // bogus stop time but we need something .. (* pStopTime) = * pStartTime + 1 ; // if the rate is not 1x, factor it in if (m_dPlaybackRate != 1.0) { (* pStartTime) = (REFERENCE_TIME) ((double) (* pStartTime) / m_dPlaybackRate) ; (* pStopTime) = (REFERENCE_TIME) ((double) (* pStopTime) / m_dPlaybackRate) ; } PERFLOG_STREAMTRACE( 1, PERFINFO_STREAMTRACE_MPEG2DEMUX_PTS_TRANSLATION, rtNow - m_rtGraphStart, *pStartTime, *pStopTime, stream_id, llPES_PTS ); } else { // no PCR received yet, so there's no timeline (* pStartTime) = UNDEFINED ; } }