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