//------------------------------------------------------------------------------
//
// 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:
tsmapper.cpp
Abstract:
This module contains the implementation for the transport
stream mapper/parser.
Revision History:
29-Jul-1999 created
--*/
#include "precomp.h"
#include "mp2demux.h"
#include "tsstats.h"
#include "mp2seek.h"
#include "pin_out.h"
#include "filter.h"
#include "bufsrc.h"
#include "plparse.h"
#include "program.h"
#include "clock.h"
#include "tsmapper.h"
static BYTE g_PrefixString [] = { 0x00, 0x00, 0x01 } ;
CStreamMapper::CStreamMapper (
IN CMpeg2Stats * pStats,
IN DWORD dwTimeoutCheckThreshold,
IN CMPEG2PushClock * pCMPEG2PushClock,
IN TLookupTableBase * pStreamFilter,
IN CIInputStreamEvent * pIInputStreamEvent
) : m_pStats (pStats),
m_dwLastTimeoutCheckTickCount (0),
m_dwTimeoutCheckThreshold (dwTimeoutCheckThreshold),
m_pCMPEG2PushClock (pCMPEG2PushClock),
m_fEnableTimeouts (TRUE),
m_pStreamFilter (pStreamFilter),
m_pIInputStreamEvent (pIInputStreamEvent),
m_Reset (TRUE)
{
O_TRACE_ENTER_0 (TEXT ("CStreamMapper::CStreamMapper ()")) ;
TRACE_CONSTRUCTOR (TEXT ("CStreamMapper")) ;
ASSERT (m_pStats) ;
ASSERT (m_pCMPEG2PushClock) ;
ASSERT (m_pIInputStreamEvent) ;
// our ref
m_pStats -> AddRef () ;
InitializeListHead (& m_TimeoutParserListHead) ;
}
CStreamMapper::CStreamMapper (
IN CMpeg2Stats * pStats,
IN CMPEG2PushClock * pCMPEG2PushClock,
IN TLookupTableBase * pStreamFilter,
IN CIInputStreamEvent * pIInputStreamEvent
) : m_pStats (pStats),
m_dwLastTimeoutCheckTickCount (0), // BUGBUG
m_dwTimeoutCheckThreshold (-1), // BUGBUG
m_pCMPEG2PushClock (pCMPEG2PushClock),
m_fEnableTimeouts (FALSE),
m_pStreamFilter (pStreamFilter),
m_pIInputStreamEvent (pIInputStreamEvent),
m_Reset (TRUE)
{
#ifndef UNDER_CE
LARGE_INTEGER li ;
#endif //UNDER_CE
O_TRACE_ENTER_0 (TEXT ("CStreamMapper::CStreamMapper ()")) ;
TRACE_CONSTRUCTOR (TEXT ("CStreamMapper")) ;
ASSERT (m_pStats) ;
ASSERT (m_pCMPEG2PushClock) ;
ASSERT (m_pIInputStreamEvent) ;
// our ref
m_pStats -> AddRef () ;
// initialize anywayws, so our destructor just finds it empty and we don't need
// to special case anything
InitializeListHead (& m_TimeoutParserListHead) ;
}
CStreamMapper::~CStreamMapper (
)
{
O_TRACE_ENTER_0 (TEXT ("CStreamMapper::~CStreamMapper ()")) ;
TRACE_DESTRUCTOR (TEXT ("CStreamMapper")) ;
// should have no streams mapped
ASSERT (IsListEmpty (& m_TimeoutParserListHead)) ;
RELEASE_AND_CLEAR (m_pStats) ;
}
// returns existing, or creates new, CStreamMapContext; AddRef'd
CStreamMapContext *
CStreamMapper::GetStreamMapContext_ (
IN DWORD dwStreamIdentifier
)
// called when a new stream is mapped
{
CStreamMapContext * pStreamMapContext ;
HRESULT hr ;
O_TRACE_ENTER_1 (TEXT ("CStreamMapper::GetStreamMapContext_ (dwStreamIdentifier = %08xh)"), dwStreamIdentifier) ;
pStreamMapContext = m_pStreamFilter -> Get (dwStreamIdentifier) ;
if (pStreamMapContext) {
// we already have 1; add outgoing ref
pStreamMapContext -> AddRef () ;
}
else {
// instantiate a new one
pStreamMapContext = NewStreamMapContext_ (dwStreamIdentifier) ;
if (pStreamMapContext) {
hr = m_pStreamFilter -> Add (
dwStreamIdentifier,
pStreamMapContext
) ;
if (FAILED (hr)) {
delete pStreamMapContext ;
pStreamMapContext = NULL ;
}
}
}
return pStreamMapContext ;
}
HRESULT
CStreamMapper::RemoveStreamMapContext_ (
IN DWORD dwStreamIdentifier
)
{
O_TRACE_ENTER_1 (TEXT ("CStreamMapper::RemoveStreamMapContext_ (dwStreamIdentifier = %08xh)"), dwStreamIdentifier) ;
m_pStreamFilter -> Remove (dwStreamIdentifier) ;
return S_OK ;
}
void
CStreamMapper::ParserInstructedReset_ (
)
{
ASSERT (m_pIInputStreamEvent) ;
m_pIInputStreamEvent -> InputStreamDiscontinuity () ;
}
// checks for timeouts
void
CStreamMapper::CheckForTimeouts_ (
IN CTStickyVal * pReset
)
{
DWORD dwTickCount ;
LIST_ENTRY * pCurListEntry ;
CParserRef * pParserRef ;
// check for timeouts; if it's been long enough since we last checked
ASSERT (m_fEnableTimeouts) ;
dwTickCount = GetTickCount () ;
if (dwTickCount - m_dwLastTimeoutCheckTickCount > m_dwTimeoutCheckThreshold) {
TRACE_1 (LOG_CUSTOM2, 4, TEXT ("CStreamMapper::CheckForTimeouts_ () threshold (%u) .. checking"), dwTickCount - m_dwLastTimeoutCheckTickCount) ;
// if this list gets long, it might be better to sort it by timeout so we
// could then just traverse it up to a point and know everything that followed
// would not be timedout.
// check each parser in the list for possible timeout
for (pCurListEntry = m_TimeoutParserListHead.Flink ;
pCurListEntry != & m_TimeoutParserListHead ;
pCurListEntry = pCurListEntry -> Flink) {
pParserRef = CParserRef::RecoverParserRef (pCurListEntry) ;
pParserRef -> CheckTimeout (dwTickCount, pReset) ;
ASSERT (pParserRef -> CanTimeout ()) ;
}
m_dwLastTimeoutCheckTickCount = dwTickCount ;
}
return ;
}
// maps a stream to the passed parser
HRESULT
CStreamMapper::MapStream (
IN DWORD dwStreamIdentifier,
IN CDemuxBaseParser * pPayloadParse
)
/*++
locks held:
filter
--*/
{
CStreamMapContext * pStreamMapContext ;
HRESULT hr ;
CParserRef * pParserRef ;
O_TRACE_ENTER_2 (TEXT ("CStreamMapper::MapStream (dwStreamIdentifier = %08xh, pPayloadParse = %08xh)"), dwStreamIdentifier, pPayloadParse) ;
// get an AddRef'd CStreamMapContext
pStreamMapContext = GetStreamMapContext_ (dwStreamIdentifier) ;
if (pStreamMapContext == NULL) {
TRACE_ERROR_0 (TEXT ("CStreamMapper::MapStream () : GetStreamMapContext_ () call failed")) ;
return E_FAIL ;
}
// add the stream map
hr = pStreamMapContext -> Add (pPayloadParse) ;
// refcount: keep the refcount the above call to GetStreamMapContext_ added
// as the parser's
if (SUCCEEDED (hr)) {
// last we check if the parser can timeout; if so, we hook it into
// the timeout list
if (m_fEnableTimeouts &&
pPayloadParse -> CanTimeout ()) {
pParserRef = new CParserRef (pPayloadParse) ;
if (pParserRef) {
// refcount: pPayloadParse gets refcounted in CParserRef's
// constructor
// xxxx: inefficiency: if we have the same parser used for
// more than 1 stream map, as is the case with pass-through
// mode, we have > 1 reference to the same parser in the
// list, and thus the same parser gets checked > 1 for a
// timeout; to remedy this we really need to have a 1-1
// stream map - parser ratio
// hook in
pParserRef -> InsertHead (& m_TimeoutParserListHead) ;
// leave the ref as list's
}
else {
// failed to get a CParserRef; fail back out
pStreamMapContext -> Remove (pPayloadParse) ;
pStreamMapContext -> Release () ;
// fail the call
hr = E_OUTOFMEMORY ;
}
}
}
else {
// if the above call failed, release the ref added when we got the parser
pStreamMapContext -> Release () ;
}
return hr ;
}
HRESULT
CStreamMapper::UnmapStream (
IN DWORD dwStreamIdentifier,
IN CDemuxBaseParser * pPayloadParse
)
/*++
locks held:
filter
--*/
{
CStreamMapContext * pStreamMapContext ;
CParserRef * pParserRef ;
LIST_ENTRY * pListEntry ;
pStreamMapContext = m_pStreamFilter -> Get (dwStreamIdentifier) ;
if (pStreamMapContext == NULL) {
TRACE_ERROR_2 (TEXT ("CStreamMapper::UnmapStream () called for a non-existent map; %08xh %08xh"), dwStreamIdentifier, pPayloadParse) ;
return S_OK ;
}
// if it's a timeout buffer
if (m_fEnableTimeouts &&
pPayloadParse -> CanTimeout ()) {
// traverse the *timeout* list until we find the parser
for (pListEntry = m_TimeoutParserListHead.Flink ;
pListEntry != & m_TimeoutParserListHead ;
pListEntry = pListEntry -> Flink) {
// recover the CParserRef
pParserRef = CParserRef::RecoverParserRef (pListEntry) ;
// check if we've found the one we want
if (pParserRef -> GetParser () == pPayloadParse) {
// unhook regardless of refcount (might be getting called on
// callback from a delivery thread)
pParserRef -> Unhook () ;
// ref count might not yet be 0, but we're unhooked and
// subsequently inaccessible
pParserRef -> Release () ;
break ;
}
}
}
pStreamMapContext -> Remove (pPayloadParse) ; // remove the parser
pStreamMapContext -> Release () ; // one less parser
return S_OK ;
}
// ----------------------------------------------------------------------------
// CTransportStreamMapper
// ----------------------------------------------------------------------------
CStreamMapContext *
CTransportStreamMapper::NewStreamMapContext_ (
IN DWORD dwStreamIdentifier
)
{
return new CTransportStreamMapContext (this, dwStreamIdentifier) ;
}
BOOL
CTransportStreamMapper::SeekSyncByte_ (
IN OUT BYTE ** ppbBuffer,
IN OUT int * piBufferLength
)
/*++
purpose:
Advances the BYTE pointer to the next MPEG2 sync byte.
parameters:
ppbBuffer double-indirected BYTE array pointer; upon
return will point to sync byte, or last checked
byte value in the case of failure
piBufferLength buffer size; upon return will contain the size
of the buffer remaining
return values:
TRUE sync byte found
FALSE sync byte not found
--*/
{
ASSERT (ppbBuffer) ;
ASSERT (piBufferLength) ;
O_TRACE_ENTER_2 (TEXT ("CTransportStreamMapper::SeekSyncByte_ (* ppbBuffer = %08xh, * piBufferLength = %u)"), * ppbBuffer, * piBufferLength) ;
// stat that we're not looking at packed packets
m_pStats -> SyncByteSeek () ;
while (* piBufferLength >= 1) {
// check the byte array pointer
if ((* ppbBuffer) [0] == TS_PACKET_SYNC_BYTE) {
return TRUE ;
}
// otherwise advance the pointer and decrement the bytes remaining
(* ppbBuffer)++ ;
(* piBufferLength)-- ;
}
TRACE_1 (LOG_TRACE, 4, TEXT ("CTransportStreamMapper::SeekSyncByte_ () : sync_byte not found; length remaining = %u bytes"), * piBufferLength) ;
return FALSE ;
}
HRESULT
CTransportStreamMapper::Process (
IN IMediaSample * pIOwningMediaSample,
IN BYTE * pbBuffer,
IN int iBufferLength
)
/*++
purpose:
entry point for incoming TS; called by the controller
parameters:
pIOwningMediaSample
pbBuffer
iBufferLength
return values:
success S_OK
error error code (HRESULT)
locks held:
filter
--*/
{
#ifndef UNDER_CE
HRESULT hr ;
int iCopy ;
#endif //UNDER_CE
int iAdvance ;
O_TRACE_ENTER_2 (TEXT ("CTransportStreamMapper::ProcessTS (pbBuffer = %08xh, iBufferLength = %u)"), pbBuffer, iBufferLength) ;
while (iBufferLength > 0) {
switch (m_TSMapperState) {
case IN_PRE_STRIDE :
//
// we are in the bytes that precede the transport packet
// proper; we do nothing but skip these; remain in this state
// until all pre-packet stride bytes have been skipped
//
iAdvance = Min (iBufferLength, m_iPreStrideBytesRemaining) ;
// skip the bytes
m_iPreStrideBytesRemaining -= iAdvance ;
// less buffer to process
iBufferLength -= iAdvance ;
pbBuffer += iAdvance ;
// if we still have pre-packet bytes remaining, break
if (m_iPreStrideBytesRemaining > 0) {
ASSERT (iBufferLength == 0) ;
break ;
}
// we've skipped all the pre-packet bytes; update state
m_TSMapperState = IN_TS_PACKET ;
// fall through only if we have buffer remaining
if (iBufferLength == 0) {
break ;
}
case IN_TS_PACKET :
//
// we're processing the transport packet itself; we are either
// processing it directly out of the input buffer (with muxed
// transport packets) or possibly out of a cache we maintain;
// the cache is there for spanning packets only - a transport
// packet that is not entirely in the input buffer as is the
// case when we have less than 188 bytes, and we're looking
// at what appears to be a transport packet, based on the
// presence of a sync_byte
//
ASSERT (iBufferLength > 0) ;
if (m_SpanningPacket.IsEmpty ()) {
// spanning packet cache is empty; process what we can out
// of the input buffer - hopefully the entire packet;
// might need to seek to down to a sync_byte
if ((* pbBuffer) != TS_PACKET_SYNC_BYTE) {
// we're not on a sync_byte - attempt to seek to it,
// or fail (run out of sufficient buffer for a
// complete packet) trying;
if (!SeekSyncByte_ (& pbBuffer, & iBufferLength)) {
// we didn't have room to seek a sync_byte, or we ran
// out of room looking for one;
// "consume" the entire buffer, so we break from the
// the containing while-loop
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
// when we get the next packet, we'll start over
InitForNextTSPacket_ () ;
break ;
}
}
// should have at least 1 byte left (sync_byte) and be on
// a sync_byte
ASSERT (iBufferLength > 0) ;
ASSERT ((* pbBuffer) == TS_PACKET_SYNC_BYTE) ;
// if we have an entire ts packet (188 bytes) process it; else
// span it to next input buffer via spanning packet cache
if (iBufferLength >= TS_PACKET_SIZE) {
m_Reset.Init (FALSE) ;
// ignore return value: don't fail the whole buffer if
// 1 packet fails
ProcessTSPacket_ (
pIOwningMediaSample,
pbBuffer,
0 - (iBufferLength - TS_PACKET_SIZE),
& m_Reset
) ;
// advance pointer and decrement size left
pbBuffer += TS_PACKET_SIZE ;
iBufferLength -= TS_PACKET_SIZE ;
}
else {
// cache what we can and break from switch
ASSERT (m_SpanningPacket.CurCacheSize () == 0) ;
m_SpanningPacket.Append (pbBuffer, iBufferLength) ;
// set these to consume the entire buffer so while-loop
// terminates; we should have less than 1 transport
// packet remaining in the input buffer
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
break ;
}
}
else {
// packet was spanned from previous input buffer
// 1 more spanned packet processed
m_pStats -> SpannedPacket () ;
ASSERT (m_SpanningPacket.CurCacheSize () < TS_PACKET_SIZE) ;
ASSERT ((* (m_SpanningPacket.Get ())) == TS_PACKET_SYNC_BYTE) ;
// get what we can
iAdvance = Min (TS_PACKET_SIZE - m_SpanningPacket.CurCacheSize (), iBufferLength ) ;
// append
m_SpanningPacket.Append (pbBuffer, iAdvance) ;
pbBuffer += iAdvance ;
iBufferLength -= iAdvance ;
if (m_SpanningPacket.CurCacheSize () == TS_PACKET_SIZE) {
// cache contains entire transport packet
m_Reset.Init (FALSE) ;
// ignore return value: don't fail the whole buffer if
// 1 packet fails
ProcessTSPacket_ (
pIOwningMediaSample, // cache gets copied regardless
m_SpanningPacket.Get (),
-iBufferLength,
& m_Reset
) ;
// packet processed, now reset
m_SpanningPacket.Reset () ;
}
else {
// still don't have a complete TS packet in our cache
ASSERT (iBufferLength == 0) ;
break ;
}
}
// fall through to post-packet state
m_TSMapperState = IN_POST_STRIDE ;
case IN_POST_STRIDE :
iAdvance = Min (iBufferLength, m_iPostStrideBytesRemaining) ;
// skip the bytes
m_iPostStrideBytesRemaining -= iAdvance ;
// less buffer to process
iBufferLength -= iAdvance ;
pbBuffer += iAdvance ;
// if we've processed all post-packet bytes, update state to
// start next at pre-packet bytes
if (m_iPostStrideBytesRemaining == 0) {
// we've skipped all the post-packet bytes; update state
// for next ts packet
InitForNextTSPacket_ () ;
}
// break regardless
break ;
} ;
if (m_Reset.IsSet ()) {
ParserInstructedReset_ () ;
}
}
// last - check for timeouts; ignore the reset val
CheckForTimeouts_ (& m_Reset) ;
return S_OK ;
}
void
CTransportStreamMapper::ProcessTSPacket_ (
IN IMediaSample * pIOwningMediaSample,
IN BYTE * pbTSPacket,
IN int iPacketLastByteOffset,
IN CTStickyVal * pReset
)
/*++
purpose:
parameters:
pbTSPacket
iPacketLastByteOffset offset relative to current buffer's
return values:
--*/
{
CTransportStreamMapContext * pStreamMapContext ;
MPEG2_TRANSPORT_PACKET Mpeg2TransportPacket ;
BOOL r ;
BOOL fPacketError ;
BOOL fDiscontinuity ;
BOOL fMaybeDuplicate ;
//O_TRACE_ENTER_1 (TEXT ("CTransportStreamMapper::ProcessTSPacket_ (pbTSPacket = %08xh)"), pbTSPacket) ;
ASSERT (SYNC_BYTE_VALUE (pbTSPacket) == TS_PACKET_SYNC_BYTE) ;
// should always be <= 0 because the last byte either coincides or is
// not to the last byte of the buffer
ASSERT (iPacketLastByteOffset <= 0) ;
// parse out the entire TS packet header, including adaptation field
r = Mpeg2TransportPacket.Parse (
pbTSPacket
) ;
if (!r) {
// stop here if the packet's header fields contain an error - nothing
// else can be trusted
return ;
}
// log stats
m_pStats -> NewPacketStats (& Mpeg2TransportPacket) ;
// only proceed with this stream if there's a mapping
if (m_PIDFilter.Exists (Mpeg2TransportPacket.PID)) {
// get the context
pStreamMapContext = static_cast (m_PIDFilter [Mpeg2TransportPacket.PID]) ;
ASSERT (pStreamMapContext) ;
// make sure we have a packet that checks
// XXXX: should actually fail out here..
ASSERT (TS_PACKET_OK (& Mpeg2TransportPacket)) ;
// set the expected continuity counter
Mpeg2TransportPacket.dwExpectedContinuityCounter = pStreamMapContext -> GetExpectedContinuityCounter () ;
// set error, discontinuity, possible duplicate packet flags
fPacketError = TS_PACKET_ERROR (& Mpeg2TransportPacket) ;
fDiscontinuity = TS_DISCONTINUITY (& Mpeg2TransportPacket) ;
fMaybeDuplicate = TS_MAYBE_DUPLICATE (& Mpeg2TransportPacket) ;
// if this is a discontinuity, log it
if (fDiscontinuity) {
m_pCMPEG2PushClock -> LogTransportStreamDiscontinuity () ;
}
// send it to the parsers
pStreamMapContext -> AddRef () ;
pStreamMapContext -> Process (
pIOwningMediaSample,
& Mpeg2TransportPacket,
fPacketError,
fDiscontinuity,
fMaybeDuplicate,
Mpeg2TransportPacket.payload_unit_start_indicator,
iPacketLastByteOffset,
pReset
) ;
// update the next same-PID packet's expected continuity counter
if (EXPECT_CONTINUITY_COUNTER_INCREMENT(Mpeg2TransportPacket.PID, Mpeg2TransportPacket.adaptation_field_control)) {
pStreamMapContext -> SetExpectedContinuityCounter (INCREMENTED_CONTINUITY_COUNTER_VALUE (Mpeg2TransportPacket.continuity_counter)) ;
}
pStreamMapContext -> Release () ;
}
return ;
}
HRESULT
CTransportStreamMapper::Initialize (
)
{
O_TRACE_ENTER_0 (TEXT ("CTransportStreamMapper::Initialize ()")) ;
m_SpanningPacket.Reset () ;
InitForNextTSPacket_ () ;
return S_OK ;
}
// ---------------------------------------------------------------------------
// CProgramStreamMapper
// ---------------------------------------------------------------------------
CProgramStreamMapper::CProgramStreamMapper (
IN CMpeg2Stats * pStats,
IN CMPEG2PushClock * pCMPEG2PushClock,
IN CIInputStreamEvent * pIInputStreamEvent
) : CStreamMapper (pStats,
pCMPEG2PushClock,
& m_StreamIdFilter,
pIInputStreamEvent
)
{
Initialize () ;
}
CProgramStreamMapper::~CProgramStreamMapper (
)
{
}
CStreamMapContext *
CProgramStreamMapper::NewStreamMapContext_ (
IN DWORD dwStreamIdentifier
)
{
return new CProgramStreamMapContext (this, dwStreamIdentifier) ;
}
void
CProgramStreamMapper::WaitForNextStartPrefix_ (
)
{
m_PSMapperState = WAIT_START ;
m_HeaderCache.Reset () ;
}
void
CProgramStreamMapper::WaitForNextPack_ (
)
{
CStreamMapContext * pContext ;
DWORD i ;
WaitForNextStartPrefix_ () ;
m_fWaitNextPack = TRUE ; // prefix must be pack start
// set a discontinuity for all parsers
m_pStats -> Global_Discontinuity () ;
// set discontinuity on all the contexts; this method is called upon error
for (i = 0;;i++) {
pContext = GetIndexed (i) ;
if (pContext) {
pContext -> SetDiscontinuity (TRUE) ;
}
else {
break ;
}
}
}
BOOL
CProgramStreamMapper::ParseHeader_ (
IN BYTE bStartCode,
IN BYTE * pbHeader
)
{
BOOL r ;
ASSERT (IsStartCodePrefix (pbHeader)) ;
switch (bStartCode)
{
case MPEG2_PACK_START_CODE :
ASSERT (StartCode (pbHeader) == MPEG2_PACK_START_CODE) ;
// parse the pack header fields
system_clock_reference_base = PACK_HEADER_SCR_BASE (pbHeader) ; // should let parser do this
system_clock_reference_extension = PACK_HEADER_SCR_EXT (pbHeader) ;
program_mux_rate = PACK_PROGRAM_MUX_RATE (pbHeader) ;
pack_stuffing_length = PACK_STUFFING_LENGTH (pbHeader) ;
m_iPacketLength = PACK_HEADER_CORE_LEN + pack_stuffing_length ;
r = TRUE ;
break ;
default :
packet_length_value = PACKET_LENGTH_VALUE (pbHeader) ;
if (packet_length_value > 0) {
m_iPacketLength = PACK_PAYLOAD_HEADER_LEN + packet_length_value ;
r = TRUE ;
}
else {
// this value can be 0 only if transport AND video; we're
// definitely not transport
r = FALSE ;
}
break ;
} ;
return r ;
}
void
CProgramStreamMapper::AbortCurrent_ (
)
{
WaitForNextPack_ () ;
}
BOOL
CProgramStreamMapper::ProcessStream_ (
IN IMediaSample * pIOwningMediaSample,
IN DWORD dwStartCode,
IN MPEG2_SYS_BUFFER * pSysBuffer,
IN CTStickyVal * pReset
)
{
CProgramStreamMapContext * pStreamMapContext ;
BOOL fNewPacket ;
CScratchMediaSample * pScratchMS ;
HRESULT hr ;
BYTE * pbNew ;
m_pStats -> NewPacketStats (pSysBuffer, dwStartCode) ;
if (m_StreamIdFilter.Exists (dwStartCode)) {
if (pSysBuffer -> iSysBufferLength > START_CODE_LENGTH) {
fNewPacket = (IsStartCodePrefix (pSysBuffer -> pbSysBuffer) &&
StartCode (pSysBuffer -> pbSysBuffer) == dwStartCode) ;
}
else {
fNewPacket = FALSE ;
}
pStreamMapContext = static_cast (m_StreamIdFilter [dwStartCode]) ;
if (pIOwningMediaSample != NULL) {
// send it to the parsers
pStreamMapContext -> Process (
pIOwningMediaSample,
pSysBuffer,
FALSE, // not an error
FALSE, // not a discontinuity (parsers get this directly)
FALSE, // not a duplicate
fNewPacket,
0, // last byte offset; BUGBUG implement
pReset // some parsers can detect if we should be reset
) ;
}
else {
// need to wrap this one up; it's out of our cache so there's no
// owning media sample; this is the rare case - only occurs if a
// header spanned across received media samples
pScratchMS = new CScratchMediaSample ;
if (pScratchMS) {
hr = pScratchMS -> Copy (
pSysBuffer -> pbSysBuffer,
pSysBuffer -> iSysBufferLength,
& pbNew
) ;
if (SUCCEEDED (hr)) {
// fixup pSysBuffer and send it to the parsers; length
// is already set correctly
pSysBuffer -> pbSysBuffer = pSysBuffer -> pbSysBufferPayload = pbNew ;
pStreamMapContext -> Process (
pScratchMS, // scratch MS owns the buffer space
pSysBuffer,
FALSE, // not an error
FALSE, // not a discontinuity (parsers get this directly)
FALSE, // not a duplicate
fNewPacket,
0, // last byte offset; BUGBUG implement
pReset // some parsers can detect if we should be reset
) ;
}
pScratchMS -> Release () ;
}
else {
return FALSE ;
}
}
}
return TRUE ;
}
// given a buffer of program stream packs, processes them
HRESULT
CProgramStreamMapper::Process (
IN IMediaSample * pIOwningMediaSample,
IN BYTE * pbBuffer,
IN int iBufferLength
)
{
BOOL r ;
int iProcessed ;
MPEG2_SYS_BUFFER Mpeg2SysBuffer ;
while (iBufferLength > 0) {
switch (m_PSMapperState)
{
case WAIT_START :
r = SeekToPrefix (
& pbBuffer,
& iBufferLength
) ;
if (!r) {
// no whole prefix was found; check that the remaining bytes
// cannot be the start of a prefix that spans to the next
// packet
if ((iBufferLength == 2 && pbBuffer [0] == 0x00 && pbBuffer [1] == 0x00) ||
(iBufferLength == 1 && pbBuffer [0] == 0x00)) {
// might have a prefix; cache it and fall through
m_HeaderCache.Append (pbBuffer, iBufferLength) ;
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
// didn't find an entire prefix, but did find the start
// of one
r = TRUE ;
}
else {
ASSERT (iBufferLength < START_CODE_PREFIX_LENGTH) ;
// advance to the end of the buffer anyways so we break
// from the loop
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
break ;
}
}
// we've either found an entire start code or a partial one;
// update state and fall through; pbBuffer points to prefix
// (00,00,01), or start of at least
// ------------------------------------------------------------
// transition: WAIT_START -> IN_START_CODE
m_PSMapperState = IN_START_CODE ;
case IN_START_CODE :
if (m_HeaderCache.CurCacheSize () == 0) {
// we're not operating out of the cache; this means that
// pbBuffer is the pointer that points to the stat code
// vs. the cache pointer
if (iBufferLength > START_CODE_PREFIX_LENGTH) {
// pbBuffer points to the prefix value
m_dwStartCode = StartCode (pbBuffer) ;
// if we're waiting for a pack, don't continue unless
// we've found one
if (m_fWaitNextPack &&
m_dwStartCode != MPEG2_PACK_START_CODE) {
// not a pack start, break and look for next prefix
WaitForNextStartPrefix_ () ;
// advance beyond the start code + prefix
iBufferLength -= START_CODE_LENGTH ;
pbBuffer += START_CODE_LENGTH ;
break ;
}
// because we are not out of the cache, m_pbBuffer
// still points to the prefix
}
else {
// we're going to have to work out of the cache; insert
// the prefix value now
ASSERT (iBufferLength == START_CODE_PREFIX_LENGTH) ;
m_HeaderCache.Append (g_PrefixString, START_CODE_PREFIX_LENGTH) ;
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
break ;
}
}
else {
if (iBufferLength > 0) {
iProcessed = Min (iBufferLength, START_CODE_LENGTH - m_HeaderCache.CurCacheSize ()) ;
// append just the start_code
m_HeaderCache.Append (pbBuffer, iProcessed) ;
// to keep cache-buffer consistency, we make sure that
// nothing overlaps; advance the pointers
pbBuffer += iProcessed ;
iBufferLength -= iProcessed ;
if (m_HeaderCache.CurCacheSize () == START_CODE_LENGTH) {
// snarf the start code
m_dwStartCode = StartCode (m_HeaderCache.Get ()) ;
// if we're waiting for a pack, don't continue unless
// we've found one
if (m_fWaitNextPack &&
m_dwStartCode != MPEG2_PACK_START_CODE) {
// not a pack start, break and look for next prefix
WaitForNextStartPrefix_ () ;
break ;
}
}
else {
// still don't have a complete start code (including
// prefix)
ASSERT (iBufferLength == 0) ;
break ;
}
}
else {
// do nothing; iBufferLength == 0, so we'll break
// from the loop as well
ASSERT (iBufferLength == 0) ;
break ;
}
}
m_fWaitNextPack = FALSE ;
// m_dwStartCode is valid; we continue
// transition to the header state
m_iHeaderLength = HeaderLength_ (m_dwStartCode) ;
// ------------------------------------------------------------
// transition: IN_START_CODE -> IN_HEADER
m_PSMapperState = IN_HEADER ;
case IN_HEADER :
if (m_HeaderCache.CurCacheSize () == 0) {
if (iBufferLength >= m_iHeaderLength) {
r = ParseHeader_ (
m_dwStartCode,
pbBuffer
) ;
if (!r) {
pbBuffer += m_iHeaderLength ;
iBufferLength -= m_iHeaderLength ;
AbortCurrent_ () ;
break ;
}
}
else {
// gotta cache and wait for the next; because we've
// got nothing in the cache now, we know pbBuffer
// points to the start code
ASSERT (IsStartCodePrefix (pbBuffer)) ;
r = m_HeaderCache.Append (pbBuffer, iBufferLength) ;
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
if (!r) {
// if that failed; wait for the next pack
AbortCurrent_ () ;
}
break ;
}
}
else {
// we're out of the cache; append what we can
iProcessed = Min (m_iHeaderLength - m_HeaderCache.CurCacheSize (), iBufferLength) ;
r = m_HeaderCache.Append (pbBuffer, iProcessed) ;
iBufferLength -= iProcessed ;
pbBuffer += iProcessed ;
if (!r) {
AbortCurrent_ () ;
break ;
}
if (m_HeaderCache.CurCacheSize () == m_iHeaderLength) {
r = ParseHeader_ (
m_dwStartCode,
m_HeaderCache.Get ()
) ;
if (!r) {
AbortCurrent_ () ;
break ;
}
}
else {
ASSERT (iBufferLength == 0) ;
break ;
}
}
// we're done with the header; initialize this one before
// possibly dealing with cache
m_iPacketBytesProcessed = 0 ;
// header has been successfully parsed; if we've been operating
// out of the cache, we may need to send it out
if (m_HeaderCache.CurCacheSize () > 0) {
Mpeg2SysBuffer.pbSysBuffer = Mpeg2SysBuffer.pbSysBufferPayload = m_HeaderCache.Get () ;
Mpeg2SysBuffer.iSysBufferLength = Mpeg2SysBuffer.iSysBufferPayloadLength = m_HeaderCache.CurCacheSize () ;
m_Reset.Init (FALSE) ;
r = ProcessStream_ (
NULL, // media sample does not own the buffer
m_dwStartCode,
& Mpeg2SysBuffer,
& m_Reset
) ;
if (!r) {
// failed
AbortCurrent_ () ;
break ;
}
// we've processed the header
m_iPacketBytesProcessed += m_HeaderCache.CurCacheSize () ;
}
// ------------------------------------------------------------
// transition: IN_HEADER -> IN_PAYLOAD
m_PSMapperState = IN_PAYLOAD ;
case IN_PAYLOAD :
iProcessed = Min (PacketBytesRemaining_ (), iBufferLength) ;
if (iProcessed > 0) {
Mpeg2SysBuffer.pbSysBuffer = Mpeg2SysBuffer.pbSysBufferPayload = pbBuffer ;
Mpeg2SysBuffer.iSysBufferLength = Mpeg2SysBuffer.iSysBufferPayloadLength = iProcessed ;
m_Reset.Init (FALSE) ;
r = ProcessStream_ (
pIOwningMediaSample,
m_dwStartCode,
& Mpeg2SysBuffer,
& m_Reset
) ;
// advance
pbBuffer += iProcessed ;
iBufferLength -= iProcessed ;
m_iPacketBytesProcessed += iProcessed ;
// if we failed; abort and start on next
if (!r) {
AbortCurrent_ () ;
break ;
}
}
if (PacketBytesRemaining_ () == 0) {
// if we're done, set things up for the next packet
m_HeaderCache.Reset () ;
if (iBufferLength >= START_CODE_PREFIX_LENGTH) {
if (IsStartCodePrefix (pbBuffer)) {
// we are immediately followed by a start code prefix
// which is what we want
m_PSMapperState = IN_START_CODE ;
}
else {
// start code prefixes must appear contiguous to
// us; this is not the case; wait for the next pack
AbortCurrent_ () ;
}
}
else if (iBufferLength > 0) {
// check for
// 00 00
// or
// 00
// to see if we might be starting a prefix that spans
// to the next buffer
if ((iBufferLength == 2 && pbBuffer [0] == 0x00 && pbBuffer [1] == 0x00) ||
(iBufferLength == 1 && pbBuffer [0] == 0x00)) {
// this looks like a spanning prefix (spans to the
// next media sample); we cache it across
m_HeaderCache.Append (pbBuffer, iBufferLength) ;
m_PSMapperState = IN_START_CODE ;
// advance in the input buffer
pbBuffer += iBufferLength ;
iBufferLength = 0 ;
}
else {
// doesn't look like a prefix; abort and wait
// for the next pack
AbortCurrent_ () ;
}
}
else {
// we've got nothing left
// BUGBUG: we ought to signal that we expect a contiguous
// start code prefix ...
WaitForNextStartPrefix_ () ;
}
}
break ;
} ;
if (m_Reset.IsSet ()) {
ParserInstructedReset_ () ;
}
}
return S_OK ;
}
HRESULT
CProgramStreamMapper::Initialize (
)
{
O_TRACE_ENTER_0 (TEXT ("CProgramStreamMapper::Initialize ()")) ;
WaitForNextPack_ () ;
return S_OK ;
}
// ---------------------------------------------------------------------------
// CStreamMapContext
// ---------------------------------------------------------------------------
CStreamMapContext::CStreamMapContext (
IN CStreamMapper * pStreamMapper,
IN DWORD StreamIdentifier
) : m_pStreamMapper (pStreamMapper),
m_dwStreamIdentifier (StreamIdentifier),
m_cRef (1),
m_fDiscontinuity (TRUE)
{
O_TRACE_ENTER_1 (TEXT ("CStreamMapContext::CStreamMapContext (stream = %08xh)"), m_dwStreamIdentifier) ;
TRACE_CONSTRUCTOR (TEXT ("CStreamMapContext")) ;
InitializeListHead (& m_PayloadParsers) ;
}
CStreamMapContext::~CStreamMapContext (
)
{
O_TRACE_ENTER_0 (TEXT ("CStreamMapContext::~CStreamMapContext ()")) ;
TRACE_DESTRUCTOR (TEXT ("CStreamMapContext")) ;
// cleanup the stream mapping
ASSERT (IsListEmpty (& m_PayloadParsers)) ;
// remove the mapping
m_pStreamMapper -> RemoveStreamMapContext_ (m_dwStreamIdentifier) ;
}
__inline
void
CStreamMapContext::Process (
IN IMediaSample * pIOwningMediaSample, // owning media sample
IN MPEG2_SYS_BUFFER * pSysBuffer, // buffer
IN BOOL fPacketError, // packet has an error
IN BOOL fDiscontinuity, // discontinuity
IN BOOL fMaybeDuplicate, // packet looks like it might be a duplicate
IN BOOL fNewPayload, // packet is the first of a new payload
IN int iPacketLastByteOffset, // offset of first byte, relative to first byte of input buffer
IN CTStickyVal * pReset
)
{
LIST_ENTRY * pListEntry ;
CParserRef * pParserRef ;
ASSERT (!IsListEmpty (& m_PayloadParsers)) ;
// cycle through the parsers
pListEntry = m_PayloadParsers.Flink ;
while (pListEntry != & m_PayloadParsers) {
// recover the parser ref object
pParserRef = CParserRef::RecoverParserRef (pListEntry) ;
// ignore return values; we don't want one parser (possible pin) to
// muck things up for everyone else
pParserRef -> AddRef () ;
pParserRef -> GetParser () -> ProcessSysBuffer (
pIOwningMediaSample,
pSysBuffer,
fPacketError,
(fDiscontinuity || m_fDiscontinuity),
fMaybeDuplicate,
fNewPayload,
iPacketLastByteOffset,
pReset
) ;
// next; advance before releasing in case release is last, with
// subsequent list removal during destructor exec
pListEntry = pListEntry -> Flink ;
pParserRef -> Release () ;
}
// reset
m_fDiscontinuity = FALSE ;
}
HRESULT
CStreamMapContext::Add (
IN CDemuxBaseParser * pPayloadParser
)
{
CParserRef * pParserRef ;
O_TRACE_ENTER_1 (TEXT ("CStreamMapContext::Add (pPayloadParser = %08xh)"), pPayloadParser) ;
ASSERT (pPayloadParser) ;
pParserRef = new CParserRef (pPayloadParser) ;
if (pParserRef) {
pParserRef -> InsertTail (& m_PayloadParsers) ;
// leave ref as list's
TRACE_1 (LOG_TRACE, 3, TEXT ("CStreamMapContext::Add () current MAPs = %u"), m_cRef) ;
return S_OK ;
}
else {
return E_FAIL ;
}
}
void
CStreamMapContext::Remove (
IN CDemuxBaseParser * pPayloadParser
)
{
LIST_ENTRY * pListEntry ;
CParserRef * pParserRef ;
O_TRACE_ENTER_1 (TEXT ("CStreamMapContext::Remove (pPayloadParser = %08xh)"), pPayloadParser) ;
ASSERT (!IsListEmpty (& m_PayloadParsers)) ;
for (pListEntry = m_PayloadParsers.Flink ;
pListEntry != & m_PayloadParsers ;
pListEntry = pListEntry -> Flink) {
pParserRef = CParserRef::RecoverParserRef (pListEntry) ;
if (pParserRef -> GetParser () == pPayloadParser) {
// found the parser we're looking for
// release context's ref and break from the loop; might remove
// it from the list as well, if the refcount goes to 0
pParserRef -> Release () ;
break ;
}
}
return ;
}
// ---------------------------------------------------------------------------
// must be in this "translation unit" per p. 199 Stroustrup
void
CMpeg2Stats::NewPacketStats (
IN MPEG2_SYS_BUFFER * pSysBuffer,
IN DWORD dw // ignored for transport; program: stream_id
)
// called for ALL packets processed, regardless of the existence of a mapping or not
{
MPEG2_TRANSPORT_HEADER * pMPEG2TransportHeader ;
if (m_pStats) {
m_pStats -> cGlobalPackets++ ; // global
if (m_Mpeg2StreamType == MPEG2_STREAM_TRANSPORT) {
// ----------------------------------------------------------------
// transport stream
// subsequently interpret as a transport packet
pMPEG2TransportHeader = static_cast (pSysBuffer) ;
ASSERT (VALID_PID (pMPEG2TransportHeader -> PID)) ;
ASSERT (VALID_CONTINUITY_COUNTER (pMPEG2TransportHeader -> continuity_counter)) ;
// one more global packet
PID_Packet_ (pMPEG2TransportHeader -> PID) ; // per PID (maybe)
// accumulate errors
if (pMPEG2TransportHeader -> transport_error_indicator) {
m_pStats -> cGlobalMPEG2Errors++ ; // global
PID_MPEG2Error_ (pMPEG2TransportHeader -> PID) ; // per PID (maybe)
}
// accumulate new payloads
if (pMPEG2TransportHeader -> payload_unit_start_indicator) {
m_pStats -> cGlobalNewPayloads++ ; // global
PID_NewPayload_ (pMPEG2TransportHeader -> PID) ; // per PID (maybe)
}
if (EXPECT_CONTINUITY_COUNTER_INCREMENT (pMPEG2TransportHeader -> PID, pMPEG2TransportHeader -> adaptation_field_control)) {
// check the continuity counter
if (m_ExpectedContinuityCounter [pMPEG2TransportHeader -> PID] != pMPEG2TransportHeader -> continuity_counter) {
m_pStats -> cGlobalDiscontinuities++ ; // global
PID_Discontinuity_ (pMPEG2TransportHeader -> PID) ; // per PID (maybe)
}
// update what we expect next
m_ExpectedContinuityCounter [pMPEG2TransportHeader -> PID] = INCREMENTED_CONTINUITY_COUNTER_VALUE (pMPEG2TransportHeader -> continuity_counter) ;
}
}
else {
// program stream; dw = stream_id
if (pSysBuffer -> iSysBufferLength >= START_CODE_LENGTH) {
StartCode_Packet_ (dw, pSysBuffer -> iSysBufferLength) ;
}
m_pProgramStats -> cBytesProcessed += pSysBuffer -> iSysBufferLength ;
}
}
}