//------------------------------------------------------------------------------
//
// 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.h
Abstract:
This module contains the IReferenceClock declarations, as well
as all the push-clock declarations.
Revision History:
23-Jan-2000 created
17-Apr-2000 CMonotonicClock added;
refined the PCR clock subordinating algorithm
to be adaptive
Notes:
skews WaitFor_ milliseconds only; does not skew requested advise
notification times because it is assumed that they are already
scaled i.e. a media sample has been received by a renderer with
a PTS-derived timestamp on it (therefore subordinated to the PCR clock
already), and the rendere wants to be notified when the PTS time
is.
------------------------------------------------------------------
PCR clock subordinating
-----------------------
The demux exposes a clock that is subordinated to a PCR stream. The
PCR is a sampled value from the headend clock which is also used
to generate the PES PTS. Since the demux produces dshow presentation
timestamps which are non-morphed but scaled values of the PES PTS
values, keeping a graph clock in sync with the headend clock ensures
that buffers will never underflow/overflow.
The demux subordinates to the PCRs by sampling the QPC counter every
time a PCR is received. Over time, a delta which is relatively
free of on-host jitter such as batched media sample deliveries,
interrupts between buffer reception and buffer processing, etc...,
is built up for each clock. The slope PCR(delta) / QPC(delta) would
be 1.0 if they are identical. Most likely, they are not, in which
case the slope provides a direct scaling value to skew the QPC clock
values to the PCR clock.
To return a time, we keep track of the following tuples:
1. QPC when we were last called; QPC(last)
2. QPC_derived time we last returned; QPC(last)_derived
we then do as follows -
1. sample the QPC clock to get QPC(now)
2. QPC(delta) = QPC(now) - QPC(last)
3. QPC(now)_derived = QPC(last)_derived + PCR_QPCUsed * QPC(delta)
** PCR_QPCUsed obtained as described in the algorithm below
PCR subordinating algorithm:
----------------------------
Receive (PCR)
{
QPC(now) = QueryPerformanceCounter ()
QPC(total) += QPC(now)
PCR(total) += PCR
PCR_QPCSlopeNew = PCR(total) / QPC(total)
if (PCR_QPCSlopeNew > PCR_QPCUsed + AllowableError) {
// above allowable bounds;
// ADJUST up
Diff = PCR_QPCSlopeNew - PCR_QPCUsed
SlopeStep = MIN (Diff, MaxSlopeStepValue)
PCR_QPCUsed += SlopeStep ;
// allowable error is initialized to a smaller and smaller
// value as we converge
AllowableError = MIN (MaxAllowableErrorBracket, Diff)
}
else if (PCR_QPCSlopeNew < PCR_QPCUsed - AllowableError) {
// below allowable bounds;
// ADJUST down
Diff = PCR_QPCUsed - PCR_QPCSlopeNew
SlopeStep = MIN (Diff, MaxSlopeStepValue)
PCR_QPCUsed -= SlopeStep
// allowable error is initialized to a smaller and smaller
// value as we converge
AllowableError = MIN (MaxAllowableErrorBracket, Diff)
}
else {
// within allowable bounds
// DEGRADE AllowableError
AllowableError -= ErrorBracketDegradation
}
}
Initial Values:
---------------
Initial values are as follows:
// we immediately start correcting
AllowableError = 0 ;
// macro defined in mp2const.h
MaxSlopeStepValue = MAX_SLOPE_STEP_VALUE () ;
// expect perfect
PCR_QPCUsed = 1 ;
// macro defined in mp2const.h
ErrorBracketDegradation = ERROR_BRACKET_DEGRADATION () ;
// macro defined in mp2const.h
MaxAllowableErrorBracket = MAX_ALLOWABLE_ERROR_BRACKET () ;
** see mp2const.h for more details
Improvement ideas:
------------------
1. Set an averaging window vs. averaging over the lifetime
of the demux. The current averaging window may well be
too big and not flex sufficiently to effectively subordinate to
headend jitter after a long time as passed. Instead, a 2-3
minute subordination window could be used, with slope values older
than the window getting dropped from the average.
--*/
// forward declarations
class CMPEG2Demultiplexer ;
class CMPEG2PushClock ;
class CMonotonicClock ;
// ============================================================================
// ============================================================================
template
class CTIGetTime
{
public :
virtual T SampleClock () = 0 ; // can wrap
virtual T GetFreq () = 0 ; // in ticks/sec; cannot change per run
} ;
// ============================================================================
// ============================================================================
template <
class HostClock, // host clock; ratio subordinates this clock to the master clock
class MasterClock // master clock; we'll have a ratio that is host-subordinated to this
>
class CTClockSubordinate
{
enum {
ALLOC_QUANTUM = 30
} ;
struct CLOCK_SUBORDINATE_BRACKET {
DWORD TicksStart ;
MasterClock Start_Master ;
HostClock Start_Host ;
} ;
CTDynQueue m_BracketQueue ;
ObjectPool
m_BracketPool ;
CTIGetTime * m_pICTGetTime ;
MasterClock m_MasterFreq ;
HostClock m_HostFreq ;
double m_dClockSubordinateRatio ;
DWORD m_dwMinSamplingWinTicks ;
DWORD m_dwMaxHistoryMillis ;
double m_dMinSubordinatable ;
double m_dMaxSubordinatable ;
CMpeg2Stats * m_pStats ;
CCritSec m_cs ;
CLOCK_SUBORDINATE_BRACKET m_Last ;
void
TrimQueue_ (
IN DWORD dwTicksNow
) ;
void
ComputeNewScalingVal_ (
) ;
DWORD
QueueNewBracket_ (
IN MasterClock MasterTime,
IN HostClock HostTime,
IN DWORD dwTicksNow
) ;
double
ObservedBracketScalingVal_ (
IN CLOCK_SUBORDINATE_BRACKET * pBracket
) ;
double
ObservedTailScalingVal_ (
) ;
double
ObservedHeadScalingVal_ (
) ;
public :
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
) ;
~CTClockSubordinate (
) ;
void
OnMasterTime (
IN MasterClock MasterTime
) ;
void
Reset (
IN BOOL fSubordinate = TRUE
) ;
BOOL IsWithinBounds (IN double dRatio) { return (dRatio >= m_dMinSubordinatable && dRatio <= m_dMaxSubordinatable) ; }
double ObservedScalingVal () { return ObservedHeadScalingVal_ () ; }
double ScalingVal () { return m_dClockSubordinateRatio ; }
} ;
// ============================================================================
// ============================================================================
class CMonotonicClock
/*++
given an incoming clock tick, guarantees that that clock tick will
be normalized to monotonically increase from 0 (first), etc...;
incoming ticks can increase or decrease, tick to tick, as long as
they don't exceed the specified threshold
m_dwMaxNonSpanningMillis however protects against sucking out legitimate
spans. For example, successive calls to this object might be far apart.
We'll detect a discontinuity and smooth the clock. However, if the time
since the last packet was processed is non "instantaneous" smoothing the
clock isn't a discontinuity as much as it is a spanned carry. The
timestamps should reflect the spanned time. We'll still smooth in that
case but also notify that the call spanned & exceeded a specified spanning
threshold. Typically the demultiplexer will be reset in that case so
timestamps can be reinitialized correctly.
--*/
{
LONGLONG m_llPosDiscontinuityThreshold ;
LONGLONG m_llNegDiscontinuityThreshold ;
LONGLONG m_llNormalizingTicks ;
LONGLONG m_llLastTicksNormalized ;
LONGLONG m_llLastGoodPosDelta ;
LONGLONG m_llLastGoodNegDelta ;
LONGLONG m_llInitialJitterPadding ;
DWORD m_dwSysClockTicksLast ;
DWORD m_dwSysClockMaxNonSpanningMillis ;
CMpeg2Stats * m_pStats ;
public :
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 dwMaxSysClockNonSpanningMillis, // spanning time threshold
IN CMpeg2Stats * pStats // stats
) ;
virtual
~CMonotonicClock (
) ;
// guaranteed to return a monotonically increasing value; even
// if it means artificially setting the return to 0
LONGLONG
Normalize (
IN LONGLONG llTicks, // clock value sample
OUT BOOL * pfDiscontinuity, // TRUE/FALSE if a time discontinuity was detected
OUT BOOL * pfSpanningTimeExceeded // TRUE/FALSE if max timespanning was not exceeded
) ;
void
Reset (
) ;
} ;
class CStreamMonotonicClock :
public CMonotonicClock
{
DWORD m_dwStreamIdentifier ;
LONGLONG m_llTimelineOffset ; // offset between this stream's
// timestamps the master timeline
// (PCRs)
public :
LIST_ENTRY m_ListEntry ;
CStreamMonotonicClock (
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 dwMaxSysClockNonSpanningMillis, // spanning time threshold
IN CMpeg2Stats * pStats, // stats
IN DWORD dwStreamIdentifier
) : CMonotonicClock (
llPosDiscontinuityThreshold,
llNegDiscontinuityThreshold,
llInitialJitterPadding,
dwMaxSysClockNonSpanningMillis,
pStats
),
m_dwStreamIdentifier (dwStreamIdentifier),
m_llTimelineOffset (UNDEFINED) {}
DWORD GetStreamIdentifier () { return m_dwStreamIdentifier ; }
LONGLONG GetStreamTimelineOffset () { return m_llTimelineOffset ; }
void SetStreamTimelineOffset (IN LONGLONG ll) { m_llTimelineOffset = ll ; }
void
Reset (
)
{
m_llTimelineOffset = UNDEFINED ;
CMonotonicClock::Reset () ;
}
} ;
class CMPEG2PushClock :
public IReferenceClock, // reference clock interface
public CIPCRValueNotify, // PCR value notifications
public CTIGetTime , // get the time
public CIPTSConvert // PTS -> REFERENCE_TIME conversion
{
// smoothing window sizes, etc...
enum {
// allowable PTS to scaledSCR discrepency; we allow for 2 seconds
// worth of PTS to SCR diff
SCR_PTS_ALLOWABLE_DISPARITY = PTS_HZ * 2,
} ;
// events
enum {
EVENT_STOP, // thread stop
EVENT_NEW_ADVISE, // new advise received
EVENT_COUNT // always last
} ;
struct ADVISE_NODE {
SINGLE_LIST_ENTRY SListEntry ; // link
HANDLE hSignal ; // semaphore or event
REFERENCE_TIME rtAdviseTime ; // when to advise
REFERENCE_TIME rtPeriodTime ; // what the period is; 0 in non-periodic advise nodes
} ;
CTClockSubordinate
m_ClockSubordinate ; // clock subordinate object
REFERENCE_TIME m_rtGraphStart ; // graph start time; ::GetTime() returns 0 if earlier than this
LIST_ENTRY m_MonotonicClockListHead ; // list head for the PTS clock ticks
SINGLE_LIST_ENTRY * m_pAdviseListHead ; // list head to the advise list nodes
SINGLE_LIST_ENTRY * m_pAdviseNodeFreePool ; // list head to free pool of list nodes
CMpeg2Stats * m_pStats ; // stats
CMPEG2Demultiplexer * m_pMPEG2Demux ; // our controlling unknown (always the filter)
HANDLE m_hThread ; // adviser thread
HANDLE m_rhEvents [EVENT_COUNT] ; // array of events to signal the thread
CRITICAL_SECTION m_crtIRefConfig ; // IReferenceClock configuration lock
CRITICAL_SECTION m_crtGetTime ; // IReferenceClock::GetTime () lock
BOOL m_fActive ; // TRUE/FALSE depending whether the hosting filter
// will implement IReferenceClock;
LONGLONG m_qpcCurrentBufferTime ; // qpc time that the last byte of the current
// input buffer was received; the current input buffer
// contains the multiplexed transport packets
LONGLONG m_QPCTicksPerSecond ; // set at initialization to scale the qpc-raw numbers
LONGLONG m_ullTotalContInputBufferBytes ; // total input buffer bytes without discontinuities
LONGLONG m_llLastReturnedIRefTime ; // value for the last time we returned
LONGLONG m_llBaseQPCForLastReturnedTime ; // QPC time that formed the basis for the last returned time
double m_dCarriedError ; // fractional error that gets rounded out for each call, but which
// must be carried from call-call
UINT m_uiTimerResolution ; // for timeBeginPeriod and timeEndPeriod calls
IReferenceClock * m_pIReferenceClock ; // graph reference clock; may/may not be us
LONGLONG m_llQPCNormalizer ; // sampled when object is instantiated
#define NORMALIZED_QPC(qpc) ((qpc) + m_llQPCNormalizer)
LONGLONG m_llBasePTS ; // timeline base PTS; morphed PCR value (90khz)
LONGLONG m_llLastPCR ; // last-processed PCR (90khz)
REFERENCE_TIME m_rtStartToFirstChannelPCR ; // time from graph start to first PCR for current stream
CIAVStreamNotify * m_pIAVStreamNotify ;
CStreamMonotonicClock * m_pPCRMonotonicClock ; // object ensures that clock ticks always increase monotononically
double m_dPlaybackRate ; // playback rate; if we must, we can generate timestamps that
// reflect the playback rate; playback rate can only be set when
// we are playing back from file i.e. are operating in pull mode;
// furthermore, this should be done in conjunction with resetting
// the PCR stream, before any of the new timestamps are generated
REFERENCE_TIME m_rtMinReturnedTime ; // use this value to make sure our clock never runs backwards;
// must watch for this at starup when the time is padded by FG
REFERENCE_TIME m_rtMinDownstreamBuffering ; // min downstream buffering; this is our threshold
REFERENCE_TIME m_rtBaselinePTSPadding ; // amount of padding on PTSs to ensure they are padded to the
// min downtream buffering; set when the timeline is first constructed
REFERENCE_TIME m_rtOverPad ; // we overpad when we detect a downstream starvation
CTSmoothingFilter m_RefClockShift ; // we dynamically detect if we're underflowing downstream and shift
// the refclock values back; we've observed shifts on some PCR values
// that don't change the rate of advance except across the one discontinuity
// for which we can only account by shifting with them
DWORD m_dwStartTicks ;
BOOL m_fSettling ;
DWORD m_cSettlingTicks ;
void LockIRefConfig_ () { EnterCriticalSection (& m_crtIRefConfig) ; }
void UnlockIRefConfig_ () { LeaveCriticalSection (& m_crtIRefConfig) ; }
void LockGetTime_ () { EnterCriticalSection (& m_crtGetTime) ; }
void UnlockGetTime_ () { LeaveCriticalSection (& m_crtGetTime) ; }
void
ResetClockSubordinatingCounters_ (
)
{
m_ullTotalContInputBufferBytes = 0 ; // reset continuous byte count
m_qpcCurrentBufferTime = UNDEFINED ; // current buffer time is undefined
m_ClockSubordinate.Reset () ;
}
void
ResetMonotonicClocks_ (
) ;
HRESULT
ConfirmAdviseThreadRunning_ (
)
{
DWORD dw ;
TIMECAPS tc ;
MMRESULT mmRes ;
if (m_hThread == NULL) {
mmRes = timeGetDevCaps (& tc, sizeof tc) ;
if (mmRes == TIMERR_NOERROR) {
m_uiTimerResolution = tc.wPeriodMin ;
}
else {
m_uiTimerResolution = 1 ;
}
timeBeginPeriod (m_uiTimerResolution) ;
m_hThread = CreateThread (
NULL, // security
0, // calling thread's stack size
CMPEG2PushClock::ThreadEntry, // entry point
(LPVOID) this, // parameter
NULL, // flags
& dw // id; required on win9x
) ;
if (m_hThread == NULL) {
// failure
dw = GetLastError () ;
return HRESULT_FROM_WIN32 (dw) ;
}
SetThreadPriority (m_hThread, THREAD_PRIORITY_TIME_CRITICAL) ;
}
ASSERT (m_rhEvents [EVENT_STOP] != NULL) ;
// thread is running
return S_OK ;
}
// returns the milliseconds until the next timeout
DWORD
ProcessNotificationTimeoutLocked_ (
IN REFERENCE_TIME rtNow
) ;
// returns the milliseconds until the next timeout
DWORD
ResetWaitTimeLocked_ (
IN REFERENCE_TIME rtNow
) ;
HRESULT
AdvisePeriodicLocked_ (
IN REFERENCE_TIME rtStartTime,
IN REFERENCE_TIME rtPeriodTime,
IN HSEMAPHORE hSemaphore,
OUT DWORD_PTR * pdwpContext
) ;
HRESULT
AdviseTimeLocked_ (
IN REFERENCE_TIME rtBaseTime,
IN REFERENCE_TIME rtStreamTime,
IN HEVENT hEvent,
OUT DWORD_PTR * pdwpContext
) ;
void
QueueAdviseTimeout_ (
IN ADVISE_NODE * pAdviseNode
) ;
HRESULT
CancelAdviseTimeout_ (
IN ADVISE_NODE * pAdviseNode
) ;
ADVISE_NODE *
GetAdviseNode_ (
)
// locks held: the list lock
{
ADVISE_NODE * pAdviseNode ;
SINGLE_LIST_ENTRY * pSListEntry ;
if (m_pAdviseNodeFreePool != NULL) {
pSListEntry = m_pAdviseNodeFreePool ;
m_pAdviseNodeFreePool = m_pAdviseNodeFreePool -> Next ;
pAdviseNode = CONTAINING_RECORD (pSListEntry, ADVISE_NODE, SListEntry) ;
ZeroMemory (pAdviseNode, sizeof ADVISE_NODE) ;
}
else {
pAdviseNode = new ADVISE_NODE ;
if (pAdviseNode) {
ZeroMemory (pAdviseNode, sizeof ADVISE_NODE) ;
}
}
return pAdviseNode ;
}
void
RecycleAdviseNode_ (
IN ADVISE_NODE * pAdviseNode
)
// locks held: the list lock
{
ASSERT (pAdviseNode) ;
pAdviseNode -> SListEntry.Next = m_pAdviseNodeFreePool ;
m_pAdviseNodeFreePool = & pAdviseNode -> SListEntry ;
}
// scales the wait milliseconds to the PCR clock so we wait an
// amount that is synced with the headend clock; might increase
// or decrease the number of milliseconds we actually wait (based
// on the host's clock)
DWORD
SkewedWaitMilliseconds_ (
IN DWORD dwMilliseconds
)
{
return (DWORD) (m_ClockSubordinate.ScalingVal () * (double) dwMilliseconds) ;
}
public :
CMPEG2PushClock (
IN BOOL fActive, // hosting filter may not wish to implement IReferenceClock
IN HKEY hkey,
IN CMpeg2Stats * pStats,
IN CMPEG2Demultiplexer * pMPEG2Demux, // always the filter
IN DWORD dwMinSamplingWinTicks,
IN DWORD dwMaxHistoryMillis,
IN DWORD MinSubordinatable,
IN DWORD MaxSubordinatable,
IN DWORD MaxGlitchesPerHour,
OUT HRESULT * pHr
) ;
~CMPEG2PushClock (
) ;
// -------------------------------------------------------------------
// clock sampling interface; used by the clock subordinating module
virtual
LONGLONG
SampleClock (
) ;
virtual
LONGLONG
GetFreq (
) ;
// -------------------------------------------------------------------
void
LogTransportStreamDiscontinuity (
)
{
ResetClockSubordinatingCounters_ () ;
}
void
SetGraphClock (
IN IReferenceClock * pIRefClock
)
{
// only release if we have a reference clock and it's not us
RELEASE_AND_CLEAR (m_pIReferenceClock) ;
if (pIRefClock != NULL) {
m_pIReferenceClock = pIRefClock ;
m_pIReferenceClock -> AddRef () ;
}
}
void SetIAVStreamNotify (IN CIAVStreamNotify * pIAVStreamNotify) { m_pIAVStreamNotify = pIAVStreamNotify ; }
// -------------------------------------------------------------------
// called by the content manager to set and unset a PCR value
// object (receives the PCR values)
// 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
ResetPCRSource (
) ;
void SetPlaybackRate (IN double d) { ASSERT (d != 0.0) ; m_dPlaybackRate = d ; }
double GetPlaybackRate () { return m_dPlaybackRate ; }
// -------------------------------------------------------------------
// IPTSConvert implementation
virtual
HRESULT
GetNewPTSContext (
IN DWORD dwStreamIdentifier,
OUT DWORD_PTR * pdwContext
) ;
virtual
void
RecyclePTSContext (
IN DWORD_PTR dwContext
) ;
virtual
BOOL
FirstPCRProcessed (
IN DWORD_PTR dwContext
) ;
virtual
void
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
) ;
// -------------------------------------------------------------------
// IPCRValueNotify implementation
virtual
HRESULT
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
) ;
// -------------------------------------------------------------------
// called by the controller whenever a new buffer is received
void
LogInputBufferQPCTime (
IN LONGLONG qpcInputBufferTime,
IN LONGLONG qpcLocalTime,
IN int iInputBufferLengthBytes
) ;
// -------------------------------------------------------------------
// called by the filter when there's a graph state transition
void
FilterStateChanged (
IN FILTER_STATE NewFilterState,
IN REFERENCE_TIME rtStart // 0 if not start run
) ;
// -------------------------------------------------------------------
// Advise thread entry point and worker method
void
AdviseThreadBody (
) ;
static
DWORD
WINAPI
ThreadEntry (
IN LPVOID pv
)
{
(reinterpret_cast (pv)) -> AdviseThreadBody () ;
return EXIT_SUCCESS ;
}
// -------------------------------------------------------------------
// IReferenceClock methods, including IUnknown
STDMETHODIMP
QueryInterface (
IN REFIID riid,
OUT void ** ppv
) ;
STDMETHODIMP_(ULONG)
AddRef (
) ;
STDMETHODIMP_(ULONG)
Release (
) ;
STDMETHODIMP
AdvisePeriodic (
IN REFERENCE_TIME rtStartTime,
IN REFERENCE_TIME rtPeriodTime,
IN HSEMAPHORE hSemaphore,
OUT DWORD_PTR * pdwpContext
) ;
STDMETHODIMP
AdviseTime (
IN REFERENCE_TIME rtBaseTime,
IN REFERENCE_TIME rtStreamTime,
IN HEVENT hEvent,
OUT DWORD_PTR * pdwpContext
) ;
STDMETHODIMP
GetTime (
OUT REFERENCE_TIME * pTime
) ;
STDMETHODIMP
Unadvise (
IN DWORD_PTR dwpContext
) ;
} ;