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