//------------------------------------------------------------------------------ // // 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: program.h Abstract: This module contains all the class declarations that are used to manage and track the content of one transport stream. Revision History: 27-Aug-1999 created 07-Jan-2000 added PSI parsing and change tracking to PAT/PMT depth Notes: --*/ #include "precomp.h" #include "mp2demux.h" #include "mp2enum.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" #include "tsctrlr.h" static void SetPMTStreamRecordBracketPointers ( IN BYTE * pbSection, OUT BYTE ** ppbFirstStreamRecord, OUT BYTE ** ppbCRC_32 ) /*++ purpose: Given a PMT section, sets two BYTE pointers to bracket the stream records described in the PMT section. Assumes the pointers are valid and point to a valid PMT section. parameters: pbSection points to first byte of section; assumed to be a valid PMT section ppbFirstStreamRecord will point to first byte of first stream record i.e. stream_type ppbCRC_32 will point to first byte of CRC_32 that follows the stream records --*/ { ASSERT (pbSection) ; ASSERT (ppbFirstStreamRecord) ; ASSERT (ppbCRC_32) ; ASSERT (VALID_PMT_SECTION (pbSection)) ; // set the pointer to the beginning of the first stream record (stream_type, etc...); // 12 is the number of bytes of the descriptor block, including program_info_length; * ppbFirstStreamRecord = pbSection + 12 + PMT_PROGRAM_INFO_LENGTH_VALUE (pbSection) ; // set pmCRC_32 to first byte of CRC_32, so we know when to stop looking at stream records * ppbCRC_32 = pbSection + COMPLETE_SECTION_LENGTH (PMT_SECTION_LENGTH_VALUE (pbSection)) - 4 ; } static void AdvancePMTStreamRecordPointer ( IN BYTE * pbCRC_32, IN OUT BYTE ** ppbCurrentStreamRecord ) /*++ purpose: Given a pointer to the current PMT stream record (i.e. stream_type, etc...), advances it beyond the descriptor field in the stream record. Assumes the pointers are valid and point to a valid PMT section. parameters: pbCRC_32 points to first byte in CRC_32 ppbCurrentStreamRecord will point to first byte (i.e. stream_type) of next stream record, or be NULL if it pointed to the last stream record when this function was called. --*/ { WORD wDescriptorLength ; ASSERT (pbCRC_32) ; ASSERT (ppbCurrentStreamRecord) ; wDescriptorLength = PMT_STREAM_RECORD_ES_INFO_LENGTH (* ppbCurrentStreamRecord) ; // do we have room ? // 5 bytes is the length of the fixed size stream record (stream_type -> ES_info_length) if (* ppbCurrentStreamRecord + wDescriptorLength + 5 < pbCRC_32) { * ppbCurrentStreamRecord += wDescriptorLength + 5 ; } else { * ppbCurrentStreamRecord = NULL ; } } // --------------------------------------------------------------------------- // CPCRRecordBufferSource // --------------------------------------------------------------------------- CPCRRecordBufferSource::CPCRRecordBufferSource ( IN CMpeg2Stats * pStats ) : CBufferSource (TRUE, // copy buffer pStats, FALSE ), m_pStats (pStats), m_pIPCRValueNotify (NULL) { TRACE_CONSTRUCTOR (TEXT ("CPCRRecordBufferSource")) ; // our ref ASSERT (m_pStats) ; m_pStats -> AddRef () ; InitializeCriticalSection (& m_crt) ; } CPCRRecordBufferSource::~CPCRRecordBufferSource ( ) { TRACE_DESTRUCTOR (TEXT ("CPCRRecordBufferSource")) ; // should not be getting deleted if we're still the PCR buffer source ASSERT (m_pIPCRValueNotify == NULL) ; m_pStats -> Release () ; DeleteCriticalSection (& m_crt) ; } void CPCRRecordBufferSource::RegisterIPCRValueNotify ( IN CIPCRValueNotify * pIPCRValueNotify ) { Lock_ () ; m_pIPCRValueNotify = pIPCRValueNotify ; Unlock_ () ; } HRESULT CPCRRecordBufferSource::GetCopyBuffer ( OUT DWORD_PTR * pdwContext, OUT BYTE ** ppbBuffer, IN OUT int * piBufferLength ) { ASSERT (pdwContext) ; ASSERT (ppbBuffer) ; ASSERT (piBufferLength) ; ZeroMemory (& m_PCR_RECORD, sizeof m_PCR_RECORD) ; // set the outgoing fields * pdwContext = reinterpret_cast (this) ; * ppbBuffer = reinterpret_cast (& m_PCR_RECORD) ; * piBufferLength = sizeof m_PCR_RECORD ; // add ref ourselves - we are the copy buffer AddRef () ; return S_OK ; } HRESULT CPCRRecordBufferSource::CompleteCopyBuffer ( IN DWORD_PTR dwContext, IN BYTE * pbBuffer, IN int iBufferLength, IN BOOL fDiscontinuity, IN CTStickyVal * pReset // OUT: true if the demux needs a reset ) /*++ assumption: buffer is completed, THEN a new one is obtained vs. buffer is obtained (& possibly written into), then previous one is completed --*/ { HRESULT hr ; ASSERT (pbBuffer) ; ASSERT (dwContext == reinterpret_cast (this)) ; ASSERT (pbBuffer == reinterpret_cast (& m_PCR_RECORD)) ; hr = S_OK ; Lock_ () ; if (m_pIPCRValueNotify) { hr = m_pIPCRValueNotify -> NewPCRValue ( m_PCR_RECORD.llPCR, fDiscontinuity, m_PCR_RECORD.iLastPCRByteOffset, pReset ) ; } Unlock_ () ; return hr ; } HRESULT CPCRRecordBufferSource::ReleaseCopyBuffer ( IN DWORD_PTR dwContext, IN BYTE * pbBuffer, IN int iBufferLength ) { if (dwContext) { // we're the copy buffer.. Release () ; } return S_OK ; } // --------------------------------------------------------------------------- // CMpeg2StreamContentManager // --------------------------------------------------------------------------- CMpeg2StreamContentManager::CMpeg2StreamContentManager ( IN CMPEG2Controller * pController, IN CMpeg2Stats * pStats, IN CMPEG2PushClock * pCMPEG2PushClock ) : m_pStats (pStats), m_pMPEG2Controller (pController), m_pCMPEG2PushClock (pCMPEG2PushClock) { TRACE_CONSTRUCTOR (TEXT ("CMpeg2StreamContentManager")) ; ASSERT (m_pMPEG2Controller) ; ASSERT (m_pStats) ; ASSERT (m_pCMPEG2PushClock) ; m_pStats -> AddRef () ; } CMpeg2StreamContentManager::~CMpeg2StreamContentManager ( ) { TRACE_DESTRUCTOR (TEXT ("CMpeg2StreamContentManager")) ; ASSERT (m_pStats) ; m_pStats -> Release () ; } // --------------------------------------------------------------------------- // CPSContentManager // --------------------------------------------------------------------------- CPSContentManager::CPSContentManager ( IN CMPEG2Controller * pController, IN CMpeg2Stats * pStats, IN CMPEG2PushClock * pCMPEG2PushClock, IN HKEY hkey, OUT HRESULT * pHr ) : CMpeg2StreamContentManager ( pController, pStats, pCMPEG2PushClock ), m_pSCR (NULL), m_dwpSCRMapContext (0) { TRACE_CONSTRUCTOR (TEXT ("CPSContentManager")) ; } CPSContentManager::~CPSContentManager ( ) { TRACE_DESTRUCTOR (TEXT ("CPSContentManager")) ; ClearConfig_ () ; } void CPSContentManager::ClearConfig_ ( ) { if (m_dwpSCRMapContext != 0) { ASSERT (m_pSCR != NULL) ; m_pMPEG2Controller -> UnmapStreamInternal ( MPEG2_PACK_START_CODE, m_dwpSCRMapContext ) ; m_pSCR -> RegisterIPCRValueNotify (NULL) ; m_pSCR -> Release () ; m_pSCR = NULL ; m_dwpSCRMapContext = 0 ; } ASSERT (m_pSCR == NULL) ; ASSERT (m_dwpSCRMapContext == 0) ; } HRESULT CPSContentManager::Initialize_ ( ) { HRESULT hr ; TRACE_ENTER_0 (TEXT ("CPSContentManager::Initialize_ ()")) ; TRACE_0 (LOG_DEMUX_PSI, 4, TEXT ("Content manager initializing")) ; hr = S_OK ; if (m_dwpSCRMapContext == 0) { ASSERT (m_pSCR == NULL) ; m_pSCR = new CPCRRecordBufferSource (m_pStats) ; if (m_pSCR) { hr = m_pMPEG2Controller -> MapStreamInternal ( MPEG2_PACK_START_CODE, m_pSCR, MPEG2_MEDIA_PROGRAM_SCR, NULL, & m_dwpSCRMapContext ) ; if (SUCCEEDED (hr)) { // we're changing the source hr = m_pCMPEG2PushClock -> ResetPCRSource () ; // set it so the SCRs are sent to the clock if (SUCCEEDED (hr)) { m_pSCR -> RegisterIPCRValueNotify ( m_pCMPEG2PushClock ) ; } } else { DELETE_RESET (m_pSCR) ; } } else { hr = E_OUTOFMEMORY ; } } return hr ; } HRESULT CPSContentManager::AVStreamMapped ( IN DWORD dwStreamId ) { return S_OK ; } void CPSContentManager::AVStreamUnmapped ( IN DWORD dwStreamId ) { } // --------------------------------------------------------------------------- // CTSContentManager // --------------------------------------------------------------------------- CTSContentManager::CTSContentManager ( IN CMPEG2Controller * pController, IN CMpeg2Stats * pStats, IN CMPEG2PushClock * pCMPEG2PushClock, IN HKEY hkey, OUT HRESULT * pHr ) : CMpeg2StreamContentManager (pController, pStats, pCMPEG2PushClock ), m_pPATSectionBuffers (NULL), m_dwpPAT_PIDMapContext (0), m_fTrackPSI (TRUE), m_pPATPSIStream (NULL), m_fAllPCRStreamsQuenched (TRUE) { //LONG l ; //DWORD dw ; TRACE_CONSTRUCTOR (TEXT ("CTSContentManager")) ; m_pPATSectionBuffers = new CMPEG2PSISectionBufferSource (m_pStats, this) ; // non-failable calls InitializeListHead (& m_SingularStreamFreePool) ; InitializeListHead (& m_MappedAVStreamQueue) ; InitializeCriticalSection (& m_crtAVStreamLock) ; // read the registry to see if we are going to track PSI //dw = REG_DEFAULT_TRACK_PSI ; //l = RegGetValIfExist ( // hkey, // REG_TRACK_PSI, // TRUE, // & dw // ) ; //if (l != ERROR_SUCCESS) { // dw = GetLastError () ; // * pHr = HRESULT_FROM_WIN32 (dw) ; // return ; //} // //m_fTrackPSI = (dw != 0) ; * pHr = m_pPATSectionBuffers != NULL ? S_OK : E_OUTOFMEMORY ; } CTSContentManager::~CTSContentManager ( ) // stream MUST be stopped; all pin PID maps must be deleted { #ifndef UNDER_CE CTSProgram * pTSProgram ; DWORD_PTR dwContext ; #endif //UNDER_CE LIST_ENTRY * pCurListEntry ; SINGULAR_STREAM * pSingularStream ; TRACE_DESTRUCTOR (TEXT ("CTSContentManager")) ; // clears the current configuration ClearConfig_ () ; // the above call to clear should have removed PCR values ASSERT (m_PCRValues.GetCount () == 0) ; // there should be nothing left ASSERT (m_Programs.IsEmpty ()) ; // we should have nothing left that indicates PID maps ASSERT (m_PIDsInPSI.IsEmpty ()) ; // should not be registered ASSERT (m_pPATPSIStream == NULL) ; // PAT sections delete m_pPATSectionBuffers ; // pump off our free stream object pool while (!IsListEmpty (& m_SingularStreamFreePool)) { pCurListEntry = RemoveHeadList (& m_SingularStreamFreePool) ; pSingularStream = CONTAINING_RECORD (pCurListEntry, SINGULAR_STREAM, ListEntry) ; delete pSingularStream ; } DeleteCriticalSection (& m_crtAVStreamLock) ; } void CTSContentManager::MappedAVStreamPush_ ( IN MAPPED_AV_STREAM * pMappedAVStream ) { // should not be on any lists and/or LIST_ENTRY should be initialized ASSERT (IsListEmpty (& (pMappedAVStream -> ListEntry))) ; // must hold the mapped AV stream lock InsertHeadList (& m_MappedAVStreamQueue, & (pMappedAVStream -> ListEntry)) ; } void CTSContentManager::MappedAVStreamPop_ ( IN MAPPED_AV_STREAM * pMappedAVStream ) { // must hold the mapped AV stream lock RemoveEntryList (& (pMappedAVStream -> ListEntry)) ; InitializeListHead (& (pMappedAVStream -> ListEntry)) ; } CTSContentManager::MAPPED_AV_STREAM * CTSContentManager::MappedAVStreamFind_ ( IN DWORD dwPID ) { MAPPED_AV_STREAM * pMappedAVStream ; LIST_ENTRY * pListEntry ; for (pListEntry = m_MappedAVStreamQueue.Flink ; pListEntry != & m_MappedAVStreamQueue; pListEntry = pListEntry -> Flink) { pMappedAVStream = CONTAINING_RECORD (pListEntry, MAPPED_AV_STREAM, ListEntry) ; if (pMappedAVStream -> dwPID == dwPID) { return pMappedAVStream ; } } return NULL ; } CTSContentManager::MAPPED_AV_STREAM * CTSContentManager::GetAVStreamHead_ ( ) { MAPPED_AV_STREAM * pMappedAVStream ; LIST_ENTRY * pListEntry ; // must hold the mapped AV stream lock if (!IsListEmpty (& m_MappedAVStreamQueue)) { // first item pListEntry = m_MappedAVStreamQueue.Flink ; pMappedAVStream = CONTAINING_RECORD (pListEntry, MAPPED_AV_STREAM, ListEntry) ; return pMappedAVStream ; } else { return NULL ; } } void CTSContentManager::ClearConfig_ ( ) { #ifndef UNDER_CE LIST_ENTRY * pListEntry ; LIST_ENTRY_CONTAINER * pSectionBuffer ; #endif //UNDER_CE CTSProgram * pTSProgram ; TRACE_ENTER_0 (TEXT ("CTSContentManager::ClearConfig_ ()")) ; if (m_dwpPAT_PIDMapContext != 0) { ASSERT (m_pPATPSIStream != NULL) ; ASSERT (m_pPATPSIStream -> dwPID == PSI_PAT_PID) ; ASSERT (m_pPATPSIStream -> dwStreamType == STREAM_TYPE_PAT) ; ASSERT (m_pPATPSIStream -> dwProgramNumber == UNDEFINED) ; TRACE_0 (LOG_DEMUX_PSI, 4, TEXT ("Content manager clearing")) ; UnmapPID (PSI_PAT_PID, m_dwpPAT_PIDMapContext) ; m_dwpPAT_PIDMapContext = 0 ; // clear out our PAT sections ASSERT (m_pPATSectionBuffers) ; m_pPATSectionBuffers -> Clear () ; // unregister the PAT traffic UnregisterKnownStream ( m_pPATPSIStream ) ; Recycle (m_pPATPSIStream) ; m_pPATPSIStream = NULL ; // and remove all programs while (!m_Programs.IsEmpty ()) { m_Programs.RemoveFirst ( reinterpret_cast (& pTSProgram) ) ; ASSERT (pTSProgram) ; // and recycle the program object pTSProgram -> Shutdown () ; Recycle (pTSProgram) ; } } } HRESULT CTSContentManager::Initialize_ ( ) { HRESULT hr ; DWORD dwTransportStreamIdFilter ; TRACE_ENTER_0 (TEXT ("CTSContentManager::Initialize_ ()")) ; TRACE_0 (LOG_DEMUX_PSI, 4, TEXT ("Content manager initializing")) ; // both of these should be empty if we are being initialized ASSERT (m_Programs.IsEmpty ()) ; ASSERT (m_dwpPAT_PIDMapContext == 0) ; ASSERT (m_pPATPSIStream == NULL) ; if (!m_fTrackPSI) { return S_OK ; } TRACE_0 (LOG_DEMUX_PSI, 4, TEXT ("Content manager creating a PID map to collect PAT sections")) ; dwTransportStreamIdFilter = WILDCARD_STREAM ; // all are ok hr = MapPID ( PSI_PAT_PID, m_pPATSectionBuffers, MPEG2_MEDIA_TRANSPORT_PSI_PAT, & dwTransportStreamIdFilter, & m_dwpPAT_PIDMapContext ) ; if (FAILED (hr)) { goto error ; } ASSERT (m_dwpPAT_PIDMapContext != 0) ; TRACE_0 (LOG_DEMUX_PSI, 4, TEXT ("PAT PID map successfully created; registering PAT PID as known PID")) ; hr = GetNew (& m_pPATPSIStream) ; if (FAILED (hr)) { m_pPATPSIStream = NULL ; return hr ; } ASSERT (m_pPATPSIStream != NULL) ; m_pPATPSIStream -> dwPID = PSI_PAT_PID ; m_pPATPSIStream -> dwStreamType = STREAM_TYPE_PAT ; m_pPATPSIStream -> dwProgramNumber = UNDEFINED ; hr = RegisterKnownStream ( m_pPATPSIStream ) ; if (FAILED (hr)) { goto error ; } m_dwPCRInUse = UNDEFINED ; m_fAllPCRStreamsQuenched = TRUE ; m_pCMPEG2PushClock -> ResetPCRSource () ; return S_OK ; error : ASSERT (FAILED (hr)) ; if (m_dwpPAT_PIDMapContext != 0) { UnmapPID ( PSI_PAT_PID, m_dwpPAT_PIDMapContext ) ; m_dwpPAT_PIDMapContext = 0 ; if (m_pPATPSIStream) { UnregisterKnownStream ( m_pPATPSIStream ) ; Recycle (m_pPATPSIStream) ; m_pPATPSIStream = NULL ; } } return hr ; } HRESULT CTSContentManager::RegisterPCRValueSource ( IN CPCRValue * pCPCRValue ) { HRESULT hr ; m_PCRValues.Lock () ; hr = m_PCRValues.Append (pCPCRValue) ; m_PCRValues.Unlock () ; if (SUCCEEDED (hr)) { pCPCRValue -> RegisterIPCRValueNotify (this) ; } return hr ; } void CTSContentManager::UnregisterPCRValueSource ( IN CPCRValue * pCPCRValue ) { CPCRValue * pCurPCRValue ; DWORD i ; m_PCRValues.Lock () ; for (i = 0;; i++) { pCurPCRValue = m_PCRValues [i] ; if (pCurPCRValue == pCPCRValue) { // found it; remove it m_PCRValues.Remove (i) ; // shutoff pCurPCRValue -> RegisterIPCRValueNotify (NULL) ; if (pCPCRValue -> GetPCRPID () == m_dwPCRInUse) { // this the PCR PID that is currently in use PCRStreamConfigLock_ () ; m_pCMPEG2PushClock -> ResetPCRSource () ; m_fAllPCRStreamsQuenched = TRUE ; m_dwPCRInUse = UNDEFINED ; // states are now set such that newly received PCRs will // try to match up the right stream with the last AV stream // registered; the clock will not receive any more PCRs, // meaning all timestamps become UNDEFINED as well PCRStreamConfigUnlock_ () ; } break ; } else if (pCurPCRValue == NULL) { // off the end break ; } } m_PCRValues.Unlock () ; } BOOL CTSContentManager::MaybeSetNewPCRStream_ ( ) { MAPPED_AV_STREAM * pMappedAVStream ; SINGULAR_STREAM * pStream ; CTSProgram * pTSProgram ; CPCRValue * pPCRValue ; HRESULT hr ; #ifndef UNDER_CE DWORD dwProgramNumber ; #endif //UNDER_CE BOOL r ; r = FALSE ; // if things are quenched and the list is not empty if (m_fAllPCRStreamsQuenched && !IsListEmpty (& m_MappedAVStreamQueue)) { // most likely a new AV stream has been mapped // examine the head of the queue only ; we assume that last mapped // stream is the direction i.e. substream direction, we are going pMappedAVStream = GetAVStreamHead_ () ; ASSERT (pMappedAVStream) ; // try to find the program this one belongs to hr = m_PIDsInPSI.Find ( pMappedAVStream -> dwPID, reinterpret_cast (& pStream) ) ; if (SUCCEEDED (hr)) { ASSERT (pStream) ; if (pStream -> dwProgramNumber != UNDEFINED) { hr = m_Programs.Find ( pStream -> dwProgramNumber, reinterpret_cast (& pTSProgram) ) ; if (SUCCEEDED (hr)) { ASSERT (pTSProgram) ; pPCRValue = pTSProgram -> GetCPCRValue () ; if (pPCRValue) { // found the PCR stream for the AV stream last mapped; // redirect to the clock pPCRValue -> RegisterIPCRValueNotify (m_pCMPEG2PushClock) ; // no longer all quenched m_fAllPCRStreamsQuenched = FALSE ; // save the PID that's in use m_dwPCRInUse = pPCRValue -> GetPCRPID () ; r = TRUE ; } } } else { // stream exists, but doesn't have a program number; we'll // never process it; remove the request so we don't have to // mess with it again MappedAVStreamPop_ (pMappedAVStream) ; m_AVStreamRefPool.Recycle (pMappedAVStream) ; } } } return r ; } HRESULT CTSContentManager::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 // OUT: true if the demux needs a reset ) /*++ These are our "timer interrupts". All PCR streams are tuned and received. If there is more than 1, those that are unused will enter here. --*/ { PCRStreamConfigLock_ () ; MaybeSetNewPCRStream_ () ; PCRStreamConfigUnlock_ () ; return S_OK ; } HRESULT CTSContentManager::AVStreamMapped ( IN DWORD dwPID ) // must hold the receiver lock !! { HRESULT hr ; MAPPED_AV_STREAM * pMappedAVStream ; DWORD i ; BOOL r ; PCRStreamConfigLock_ () ; pMappedAVStream = m_AVStreamRefPool.Get () ; if (pMappedAVStream) { // init the list entry InitializeListHead (& (pMappedAVStream -> ListEntry)) ; // walk the list of PCR streams and reset them all to self, so stream // to clock is quenched; receiver lock is being held so it's safe to // party on the PCR streams for (i = 0;;i++) { if (m_PCRValues [i]) { m_PCRValues [i] -> RegisterIPCRValueNotify (this) ; } else { break ; } } // all PCR streams have now been quenched m_fAllPCRStreamsQuenched = TRUE ; // notify the clock hr = m_pCMPEG2PushClock -> ResetPCRSource () ; if (SUCCEEDED (hr)) { // push the mapped stream; lock protects us from unmappings pMappedAVStream -> dwPID = dwPID ; pMappedAVStream -> dwProgramNumber = UNDEFINED ; // insert into the list MappedAVStreamPush_ (pMappedAVStream) ; // now try to set the correct PCR PID, without resetting the // whole PSI tables r = MaybeSetNewPCRStream_ () ; if (!r) { // PCR source corresponding to our just pushed AV stream // was not found; reset all the tables and gather a fresh // set for the current transport stream Reset () ; } } else { m_AVStreamRefPool.Recycle (pMappedAVStream) ; } } else { hr = E_OUTOFMEMORY ; } PCRStreamConfigUnlock_ () ; return hr ; } void CTSContentManager::AVStreamUnmapped ( IN DWORD dwPID ) { MAPPED_AV_STREAM * pMappedAVStream ; PCRStreamConfigLock_ () ; pMappedAVStream = MappedAVStreamFind_ (dwPID) ; if (pMappedAVStream) { MappedAVStreamPop_ (pMappedAVStream) ; m_AVStreamRefPool.Recycle (pMappedAVStream) ; } PCRStreamConfigUnlock_ () ; } HRESULT CTSContentManager::RegisterKnownStream ( IN SINGULAR_STREAM * pStream ) { ASSERT (pStream) ; TRACE_ENTER_1 (TEXT ("CTSContentManager::RegisterKnownStream (%08xh)"), pStream -> dwPID) ; TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Registering PID %08xh as known; stream_type = %08xh"), pStream -> dwPID, pStream -> dwStreamType) ; return m_PIDsInPSI.Store ( reinterpret_cast (pStream), pStream -> dwPID ) ; } HRESULT CTSContentManager::UnregisterKnownStream ( IN SINGULAR_STREAM * pStream ) { HRESULT hr ; ASSERT (pStream) ; TRACE_ENTER_1 (TEXT ("CTSContentManager::UnregisterKnownStream (%08xh)"), pStream -> dwPID) ; TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Unregistering PID %08xh (stream_type = %08x) as known"), pStream -> dwPID, pStream -> dwStreamType) ; hr = m_PIDsInPSI.RemoveSpecific ( reinterpret_cast (pStream), pStream -> dwPID ) ; ASSERT (SUCCEEDED (hr)) ; return S_OK ; } HRESULT CTSContentManager::GetNewInitializedTSProgram_ ( IN DWORD dwProgramNumber, IN DWORD dwPID, OUT CTSProgram ** ppTSProgram ) { HRESULT hr ; TRACE_ENTER_3 (TEXT ("CTSContentManager::GetNewInitializedTSProgram_ (%08xh, %08xh, %08xh)"), dwProgramNumber, dwPID, ppTSProgram) ; ASSERT (ppTSProgram) ; hr = GetNew ( ppTSProgram ) ; if (SUCCEEDED (hr)) { ASSERT (* ppTSProgram) ; // initialize hr = (* ppTSProgram) -> Initialize ( dwProgramNumber, dwPID ) ; if (FAILED (hr)) { Recycle (* ppTSProgram) ; } } return hr ; } HRESULT CTSContentManager::ReplaceSection_ ( IN SECTION_BUFFER * pNewSectionBuffer, IN SECTION_BUFFER * pOldSectionBuffer ) /*++ purpose: parameters: return values: locks held: --*/ { HRESULT hr ; DWORD cPrograms ; DWORD i ; CTSProgram * pTSProgram ; #ifndef UNDER_CE DWORD_PTR dwpContext ; #endif //UNDER_CE TRACE_ENTER_2 (TEXT ("CTSContentManager::ReplaceSection_ ()"), pNewSectionBuffer, pOldSectionBuffer) ; ASSERT (pNewSectionBuffer) ; ASSERT (pOldSectionBuffer) ; ASSERT (VALID_PAT_SECTION (pNewSectionBuffer -> pbSection)) ; ASSERT (PAT_CURRENT_NEXT_INDICATOR_BIT (pNewSectionBuffer -> pbSection)) ; TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("New PAT section received (version = %08x); replaces version %08xh"), PAT_VERSION_NUMBER_VALUE (pNewSectionBuffer -> pbSection), PAT_VERSION_NUMBER_VALUE (pOldSectionBuffer -> pbSection)) ; hr = S_OK ; // first move all the programs described in the old section to the scratch store ASSERT (m_ScratchPrograms.IsEmpty ()) ; cPrograms = NUMBER_PROGRAMS_IN_PAT_SECTION (pOldSectionBuffer -> pbSection) ; for (i = 0; i < cPrograms; i++) { if (!PAT_PROGRAM_DESCRIPTOR_IS_PROGRAM (pOldSectionBuffer -> pbSection, i)) { // skip if this is not a program_map_PID TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("searching existing store: skipping a network_PID; program_number %08xh"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i)) ; continue ; } // remove from regular store hr = m_Programs.Remove ( PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pOldSectionBuffer -> pbSection, i), reinterpret_cast (& pTSProgram) ) ; if (SUCCEEDED (hr)) { // we may be back into this after a failure, so there is a chance // that a discrepency exists between the stored section and the // program map ASSERT (pTSProgram) ; ASSERT (pTSProgram -> GetProgramNumber () == (DWORD) PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pOldSectionBuffer -> pbSection, i)) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("Moving program_number %08xh to scratch store."), pTSProgram -> GetProgramNumber ()) ; // and move to new store hr = m_ScratchPrograms.Store ( reinterpret_cast (pTSProgram), pTSProgram -> GetProgramNumber () ) ; if (FAILED (hr)) { // we're going to send the error all the way back out and we're going // to want to clear ourselves anwyays; start with the program that // that is in neither store // shutdown and recycle pTSProgram -> Shutdown () ; Recycle (pTSProgram) ; goto cleanup ; } } } // now move those programs that are referenced in the new section back into the // regular store, create new ones as needed, and delete all those that are left // in the scratch store cPrograms = NUMBER_PROGRAMS_IN_PAT_SECTION (pNewSectionBuffer -> pbSection) ; for (i = 0; i < cPrograms; i++) { if (!PAT_PROGRAM_DESCRIPTOR_IS_PROGRAM (pNewSectionBuffer -> pbSection, i)) { // skip if this is not a program_map_PID TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("searching scratch store: skipping a network_PID; program_number %08xh"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i)) ; continue ; } TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("Searching scratch store for program_number %08x"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i)) ; hr = m_ScratchPrograms.Remove ( PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i), reinterpret_cast (& pTSProgram) ) ; // if it was not found, we create & initialize a new one if (FAILED (hr)) { TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("program_number %08xh is new; PMT PID is %08xh"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i), PAT_PROGRAM_DESCRIPTOR_PID_VALUE (pNewSectionBuffer -> pbSection, i)) ; hr = GetNewInitializedTSProgram_ ( PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i), PAT_PROGRAM_DESCRIPTOR_PID_VALUE (pNewSectionBuffer -> pbSection, i), & pTSProgram ) ; if (FAILED (hr)) { goto cleanup ; } } ASSERT (pTSProgram) ; ASSERT (pTSProgram -> GetProgramNumber () == (DWORD) PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i)) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("putting program_number %08xh into the main store"), pTSProgram -> GetProgramNumber ()) ; // put it in the regular store hr = m_Programs.Store ( reinterpret_cast (pTSProgram), pTSProgram -> GetProgramNumber () ) ; if (FAILED (hr)) { // we're going to send the error all the way back out and we're going // to want to clear ourselves anwyays; start with the program that // that is in neither store // shutdown and recycle pTSProgram -> Shutdown () ; Recycle (pTSProgram) ; goto cleanup ; } } cleanup : // everything in our regular store is current; everything in the scratch store // is expired; free all those resources now while (!m_ScratchPrograms.IsEmpty ()) { m_ScratchPrograms.RemoveFirst ( reinterpret_cast (& pTSProgram) ) ; ASSERT (pTSProgram) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("program_number %08xh has expired and is being deleted"), pTSProgram -> GetProgramNumber ()) ; // shutdown and recycle the object pTSProgram -> Shutdown () ; Recycle (pTSProgram) ; } // success return hr ; } HRESULT CTSContentManager::NewSection_ ( IN SECTION_BUFFER * pNewSectionBuffer ) /*++ purpose: called when a brand new section is received i.e. there is no section that is being replaced and must be reconciled. parameters: return values: locks held: --*/ { DWORD cPrograms ; DWORD i ; CTSProgram * pTSProgram ; HRESULT hr ; TRACE_ENTER_1 (TEXT ("CTSContentManager::NewSection_ (%08xh)"), pNewSectionBuffer) ; ASSERT (pNewSectionBuffer) ; ASSERT (VALID_PAT_SECTION (pNewSectionBuffer -> pbSection)) ; ASSERT (PAT_CURRENT_NEXT_INDICATOR_BIT (pNewSectionBuffer -> pbSection)) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("New PAT section received (version = %08x)"), PAT_VERSION_NUMBER_VALUE (pNewSectionBuffer -> pbSection)) ; // in case we have 0 programs, we initialize this now hr = S_OK ; // number of programs cPrograms = NUMBER_PROGRAMS_IN_PAT_SECTION (pNewSectionBuffer -> pbSection) ; // now loop through, and add each new program for (i = 0; i < cPrograms; i++) { // only add programs i.e. not if this is a network PID if (!PAT_PROGRAM_DESCRIPTOR_IS_PROGRAM (pNewSectionBuffer -> pbSection, i)) { TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("new program: skipping a network_PID; program_number %08xh"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i)) ; continue ; } TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("new program: program_number %08xh; PMT PID is %08xh"), PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i), PAT_PROGRAM_DESCRIPTOR_PID_VALUE (pNewSectionBuffer -> pbSection, i)) ; hr = GetNewInitializedTSProgram_ ( PAT_PROGRAM_DESCRIPTOR_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection, i), PAT_PROGRAM_DESCRIPTOR_PID_VALUE (pNewSectionBuffer -> pbSection, i), & pTSProgram ) ; if (FAILED (hr)) { goto cleanup ; } // store it away, based on the program_number hr = m_Programs.Store ( reinterpret_cast (pTSProgram), pTSProgram -> GetProgramNumber () ) ; if (FAILED (hr)) { // we failed to store this one; recycle what we were attempting to store // and let the error code recover what was stored pTSProgram -> Shutdown () ; Recycle (pTSProgram) ; goto cleanup ; } } cleanup : return hr ; } HRESULT CTSContentManager::MapPID ( IN DWORD dwPID, IN CBufferSource * pBufferSource, IN DWORD dwTypeParser, // see mp2const.h IN LPVOID pvParserArg, // depends on the type of parser; can be NULL OUT DWORD_PTR * pdwpContext ) { TRACE_ENTER_4 (TEXT ("CTSContentManager::MapPID (%08xh, %08xh, %08xh, %08xh)"), dwPID, pBufferSource, dwTypeParser, pdwpContext) ; ASSERT (m_pMPEG2Controller) ; return m_pMPEG2Controller -> MapStreamInternal (dwPID, pBufferSource, dwTypeParser, pvParserArg, pdwpContext) ; } HRESULT CTSContentManager::UnmapPID ( IN DWORD dwPID, IN DWORD_PTR dwpContext ) { TRACE_ENTER_2 (TEXT ("CTSContentManager::UnmapPID (%08xh, %08xh)"), dwPID, dwpContext) ; ASSERT (m_pMPEG2Controller) ; return m_pMPEG2Controller -> UnmapStreamInternal (dwPID, dwpContext) ; } HRESULT CTSContentManager::GetNew ( OUT CTSProgram ** ppTSProgram ) { TRACE_ENTER_1 (TEXT ("CTSContentManager::GetNew (%08xh)"), ppTSProgram) ; ASSERT (ppTSProgram) ; * ppTSProgram = new CTSProgram ( m_pStats, this ) ; return * ppTSProgram != NULL ? S_OK : E_OUTOFMEMORY ; } void CTSContentManager::Recycle ( IN CTSProgram * pTSProgram ) { TRACE_ENTER_1 (TEXT ("CTSContentManager::Recycle (%08xh)"), pTSProgram) ; ASSERT (pTSProgram) ; delete pTSProgram ; } HRESULT CTSContentManager::GetNew ( OUT SINGULAR_STREAM ** ppStream ) { LIST_ENTRY * pListEntry ; TRACE_ENTER_1 (TEXT ("CTSContentManager::GetNew (%08xh)"), ppStream) ; ASSERT (ppStream) ; if (!IsListEmpty (& m_SingularStreamFreePool)) { pListEntry = RemoveHeadList (& m_SingularStreamFreePool) ; * ppStream = CONTAINING_RECORD (pListEntry, SINGULAR_STREAM, ListEntry) ; } else { * ppStream = new SINGULAR_STREAM ; } return * ppStream != NULL ? S_OK : E_OUTOFMEMORY ; } void CTSContentManager::Recycle ( IN SINGULAR_STREAM * pStream ) { TRACE_ENTER_1 (TEXT ("CTSContentManager::Recycle (%08xh)"), pStream) ; ASSERT (pStream) ; InsertHeadList (& m_SingularStreamFreePool, & pStream -> ListEntry) ; } // --------------------------------------------------------------------------- // CTSProgram // --------------------------------------------------------------------------- CTSProgram::CTSProgram ( IN CMpeg2Stats * pStats, IN CTSContentManager * pTSContentManager ) : m_pStats (pStats), m_pPMTSectionBuffers (NULL), m_pContentManager (pTSContentManager), m_dwpPMTPIDMapContext (0), m_dwpPCRPIDMapContext (0), m_dwPMTPID (PID_UNDEFINED), m_pCPCRValue (NULL) { TRACE_CONSTRUCTOR (TEXT ("CTSProgram")) ; ASSERT (m_pContentManager) ; ASSERT (m_pStats) ; // we know the stream type, but not the PID m_PCRStream.dwStreamType = STREAM_TYPE_PCR ; m_PCRStream.dwPID = PID_UNDEFINED ; m_pStats -> AddRef () ; } CTSProgram::~CTSProgram ( ) { TRACE_DESTRUCTOR (TEXT ("CTSProgram")) ; // should be cleared out completely ASSERT (m_Streams.IsEmpty ()) ; ASSERT (m_dwpPMTPIDMapContext == 0) ; ASSERT (m_dwpPCRPIDMapContext == 0) ; ASSERT (m_dwPMTPID == PID_UNDEFINED) ; ASSERT (m_pCPCRValue == NULL) ; // free the resources for the section buffer object delete m_pPMTSectionBuffers ; ASSERT (m_pStats) ; m_pStats -> Release () ; } HRESULT CTSProgram::Shutdown ( ) /*++ this method should be called AFTER the PMT PID has been unmapped --*/ { SINGULAR_STREAM * pStream ; HRESULT hr ; TRACE_ENTER_0 (TEXT ("CTSProgram::Shutdown ()")) ; // should be getting called unless we were successfully initialized ASSERT (m_pPMTSectionBuffers) ; // unmap the PID that is gathering the PMT sections if (m_dwpPMTPIDMapContext != 0) { ASSERT (m_dwPMTPID != PID_UNDEFINED) ; ClearPCRReception_ () ; m_pContentManager -> UnmapPID ( m_dwPMTPID, m_dwpPMTPIDMapContext ) ; m_dwpPMTPIDMapContext = 0 ; // clear the sections m_pPMTSectionBuffers -> Clear () ; // now that we won't be getting anymore sections, we clear out all // stream references that we have ASSERT (m_ScratchStreams.IsEmpty ()) ; while (!m_Streams.IsEmpty ()) { // remove the first and recycle it hr = m_Streams.RemoveFirst ( reinterpret_cast (& pStream) ) ; ASSERT (SUCCEEDED (hr)) ; ASSERT (pStream) ; ASSERT (pStream -> dwProgramNumber == m_dwProgramNumber) ; // PID is no longer valid m_pContentManager -> UnregisterKnownStream ( pStream ) ; m_pContentManager -> Recycle (pStream) ; } m_dwPMTPID = PID_UNDEFINED ; } ASSERT (m_dwPMTPID == PID_UNDEFINED) ; ASSERT (m_dwpPMTPIDMapContext == 0) ; ASSERT (m_dwpPCRPIDMapContext == 0) ; ASSERT (m_pCPCRValue == NULL) ; return S_OK ; } HRESULT CTSProgram::Initialize ( IN DWORD dwProgramNumber, // program_number IN DWORD dwPMTPID // program_map_PID in PAT ) /*++ purpose: Called from the constructor to initialize the object. parameters: return values: locks held: --*/ { HRESULT hr ; SINGULAR_STREAM * pStream ; TRACE_ENTER_2 (TEXT ("CTSProgram::Initialize (%08xh, %08xh)"), dwProgramNumber, dwPMTPID) ; // should not be getting initialized unless we were shutdown ASSERT (m_Streams.IsEmpty ()) ; ASSERT (m_dwpPMTPIDMapContext == 0) ; ASSERT (m_dwPMTPID == PID_UNDEFINED) ; ASSERT (m_pCPCRValue == NULL) ; // get a singular stream object to describe the PMT traffic hr = m_pContentManager -> GetNew (& pStream) ; if (FAILED (hr)) { return hr ; } // set things up pStream -> dwPID = dwPMTPID ; pStream -> dwStreamType = STREAM_TYPE_PMT ; pStream -> dwProgramNumber = dwProgramNumber ; // // wait to register it until we've successfully set everything else up // m_dwProgramNumber = dwProgramNumber ; if (m_pPMTSectionBuffers == NULL) { m_pPMTSectionBuffers = new CMPEG2PSISectionBufferSource ( m_pStats, this ) ; if (m_pPMTSectionBuffers == NULL) { m_pContentManager -> Recycle (pStream) ; return E_OUTOFMEMORY ; } } hr = m_pContentManager -> MapPID ( dwPMTPID, m_pPMTSectionBuffers, MPEG2_MEDIA_TRANSPORT_PSI_PMT, (LPVOID) & m_dwProgramNumber, // parser param is the program_number to filter on & m_dwpPMTPIDMapContext ) ; if (FAILED (hr)) { goto error ; } ASSERT (m_dwpPMTPIDMapContext) ; // register PMT PID hr = m_pContentManager -> RegisterKnownStream ( pStream ) ; if (FAILED (hr)) { goto error ; } // register in our own streams as well hr = m_Streams.Store ( reinterpret_cast (pStream), pStream -> dwPID ) ; if (FAILED (hr)) { goto error ; } // last we do this m_dwPMTPID = dwPMTPID ; return S_OK ; error : if (m_dwpPMTPIDMapContext != 0) { m_pContentManager -> UnmapPID ( dwPMTPID, m_dwpPMTPIDMapContext ) ; m_dwpPMTPIDMapContext = 0 ; } if (pStream != NULL) { // we failed after obtaining a stream; recycle it m_pContentManager -> UnregisterKnownStream ( pStream ) ; m_pContentManager -> Recycle (pStream) ; } // something failed; exit with a clean slate ASSERT (FAILED (hr)) ; ASSERT (m_dwpPMTPIDMapContext == 0) ; return hr ; } void CTSProgram::ClearPCRReception_ ( ) { HRESULT hr ; TRACE_ENTER_0 (TEXT ("CTSProgram::ClearPCRReception_ ()")) ; // should not be getting called in if we were unable to create PID map // to collect PMT sections ASSERT (m_dwpPMTPIDMapContext) ; // if the PCR PID of record has a PID value that indicates we are collecting // PCRs, we need to clear the PID map, etc... if (m_PCRStream.dwPID != PID_UNDEFINED && m_PCRStream.dwPID != PCR_PID_NOT_DEFINED_FOR_PROGRAM) { TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Program %08xh : clearing old PCR PID (%08xh) collection"), m_dwProgramNumber, m_PCRStream.dwPID) ; // unregister hr = m_pContentManager -> UnregisterKnownStream ( & m_PCRStream ) ; // should not be getting called if we're not in ASSERT (SUCCEEDED (hr)) ; // if we have a valid PCR PID, we must be collecting the PCRs ASSERT (m_dwpPCRPIDMapContext != 0) ; ASSERT (m_pCPCRValue != NULL) ; m_pContentManager -> UnmapPID ( m_PCRStream.dwPID, m_dwpPCRPIDMapContext ) ; // reset the PID map context m_dwpPCRPIDMapContext = 0 ; // unregister PCR value with the content manager m_pContentManager -> UnregisterPCRValueSource (m_pCPCRValue) ; // release our ref to the PCR value object RELEASE_AND_CLEAR (m_pCPCRValue) ; // undefine the PCR PID m_PCRStream.dwPID = PID_UNDEFINED ; } // always ASSERT (m_pCPCRValue == NULL) ; ASSERT (m_dwpPCRPIDMapContext == 0) ; ASSERT (m_PCRStream.dwPID == PID_UNDEFINED || m_PCRStream.dwPID == PCR_PID_NOT_DEFINED_FOR_PROGRAM) ; return ; } HRESULT CTSProgram::CheckPCRPID_ ( IN SECTION_BUFFER * pNewSectionBuffer ) { HRESULT hr ; DWORD dwPCRPID ; TRACE_ENTER_1 (TEXT ("CTSProgram::CheckPCRPID_ (%08xh)"), pNewSectionBuffer) ; ASSERT (pNewSectionBuffer) ; // if we're there already, return right now if (m_PCRStream.dwPID == (DWORD) PMT_PCR_PID_VALUE (pNewSectionBuffer -> pbSection)) { return S_OK ; } // initialize hr = S_OK ; // save a local copy of the new PCR PID dwPCRPID = PMT_PCR_PID_VALUE (pNewSectionBuffer -> pbSection) ; TRACE_3 (LOG_DEMUX_PSI, 4, TEXT ("Program %08xh : New PCR PID; old = %08x; new = %08x"), m_dwProgramNumber, m_PCRStream.dwPID, dwPCRPID) ; // clear previously setup reception, if it exists ClearPCRReception_ () ; // if this program has a valid PCR if (dwPCRPID != PCR_PID_NOT_DEFINED_FOR_PROGRAM) { // we should not be collecting after calling ClearPCRReception_ ASSERT (m_dwpPCRPIDMapContext == 0) ; ASSERT (m_pCPCRValue == NULL) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("Program %08xh : creating a CPCRValue buffer source object"), m_dwProgramNumber) ; m_pCPCRValue = new CPCRValue (dwPCRPID, m_pStats) ; if (m_pCPCRValue != NULL) { TRACE_2 (LOG_DEMUX_PSI, 4, TEXT ("Program %08xh : requesting a PID map for PID %08xh"), m_dwProgramNumber, dwPCRPID) ; // now try to create a PID map, using the CPCRValue object as a // the buffer source hr = m_pContentManager -> MapPID ( dwPCRPID, m_pCPCRValue, MPEG2_MEDIA_TRANSPORT_PCR, NULL, // no parser param & m_dwpPCRPIDMapContext ) ; if (SUCCEEDED (hr)) { ASSERT (m_dwpPCRPIDMapContext != 0) ; TRACE_1 (LOG_DEMUX_PSI, 4, TEXT ("Program %08xh : PID map created; starting to collect PCRs .."), m_dwProgramNumber) ; // last, we register this as a PCR source with the content manager hr = m_pContentManager -> RegisterPCRValueSource (m_pCPCRValue) ; // we're all set; the last thing we do is register the PCR PID // with the content provider if (SUCCEEDED (hr)) { // set the PID ASSERT (m_PCRStream.dwPID == PID_UNDEFINED) ; m_PCRStream.dwPID = dwPCRPID ; m_PCRStream.dwProgramNumber = m_dwProgramNumber ; // and register as known with the content manager hr = m_pContentManager -> RegisterKnownStream ( & m_PCRStream ) ; } // if the above (last) call fails, we unmap the PID, free the object, and back // the error all the way back out if (FAILED (hr)) { m_pContentManager -> UnmapPID ( dwPCRPID, m_dwpPCRPIDMapContext ) ; RELEASE_AND_CLEAR (m_pCPCRValue) ; m_PCRStream.dwPID = PID_UNDEFINED ; } } else { ASSERT (m_dwpPCRPIDMapContext == 0) ; RELEASE_AND_CLEAR (m_pCPCRValue) ; } } else { // failed to create a CPCRValue object hr = E_OUTOFMEMORY ; } } return hr ; } HRESULT CTSProgram::NewSection_ ( IN SECTION_BUFFER * pNewSectionBuffer ) /*++ purpose: Brand new section. None of the streams should be currently described. parameters: return values: locks held: --*/ { BYTE * pbStreamRecord ; // points to the current stream record BYTE * pbCRC_32 ; // points to the first byte of the CRC_32 SINGULAR_STREAM * pStream ; HRESULT hr ; TRACE_ENTER_1 (TEXT ("CTSProgram::NewSection_ (%08xh)"), pNewSectionBuffer) ; ASSERT (pNewSectionBuffer) ; ASSERT (PMT_CURRENT_NEXT_INDICATOR_BIT (pNewSectionBuffer -> pbSection)) ; if (!VALID_PMT_SECTION (pNewSectionBuffer -> pbSection)) { return E_INVALIDARG ; } // this filtering is done in the parser ASSERT ((DWORD) PMT_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection) == m_dwProgramNumber) ; // check the PCR PID against what we have hr = CheckPCRPID_ ( pNewSectionBuffer ) ; if (FAILED (hr)) { return hr ; } SetPMTStreamRecordBracketPointers ( pNewSectionBuffer -> pbSection, & pbStreamRecord, & pbCRC_32 ) ; ASSERT (pbStreamRecord) ; ASSERT (pbCRC_32) ; ASSERT (m_pContentManager) ; while (pbStreamRecord != NULL) { hr = m_pContentManager -> GetNew ( & pStream ) ; if (FAILED (hr)) { goto cleanup ; } ASSERT (pStream) ; pStream -> dwPID = PMT_STREAM_RECORD_ELEMENTARY_PID (pbStreamRecord) ; pStream -> dwStreamType = PMT_STREAM_RECORD_STREAM_TYPE_VALUE (pbStreamRecord) ; pStream -> dwProgramNumber = m_dwProgramNumber ; hr = m_pContentManager -> RegisterKnownStream ( pStream ) ; if (FAILED (hr)) { m_pContentManager -> Recycle (pStream) ; goto cleanup ; } hr = m_Streams.Store ( reinterpret_cast (pStream), pStream -> dwPID ) ; if (FAILED (hr)) { m_pContentManager -> UnregisterKnownStream (pStream) ; m_pContentManager -> Recycle (pStream) ; goto cleanup ; } // advance to next record AdvancePMTStreamRecordPointer ( pbCRC_32, & pbStreamRecord ) ; } cleanup : // success return hr ; } HRESULT CTSProgram::ReplaceSection_ ( IN SECTION_BUFFER * pNewSectionBuffer, IN SECTION_BUFFER * pOldSectionBuffer ) { BYTE * pbStreamRecord ; // points to the current stream record BYTE * pbCRC_32 ; // points to the first byte of the CRC_32 SINGULAR_STREAM * pStream ; HRESULT hr ; TRACE_ENTER_2 (TEXT ("CTSProgram::ReplaceSection_ (%08xh, %08xh)"), pNewSectionBuffer, pOldSectionBuffer) ; ASSERT (pNewSectionBuffer) ; ASSERT (pOldSectionBuffer) ; ASSERT (VALID_PMT_SECTION (pNewSectionBuffer -> pbSection)) ; ASSERT (PMT_CURRENT_NEXT_INDICATOR_BIT (pNewSectionBuffer -> pbSection)) ; ASSERT (m_pContentManager) ; // could happen if there's a discontinuity upstream and the same PID now carries // traffic for a different PMT; we'll hit this every time until one of the // PAT sections is processed and the current program_number is found to be // stale if ((DWORD) PMT_PROGRAM_NUMBER_VALUE (pNewSectionBuffer -> pbSection) != m_dwProgramNumber) { return E_UNEXPECTED ; } // check the PCR PID against what we have hr = CheckPCRPID_ ( pNewSectionBuffer ) ; if (FAILED (hr)) { return hr ; } SetPMTStreamRecordBracketPointers ( pOldSectionBuffer -> pbSection, & pbStreamRecord, & pbCRC_32 ) ; ASSERT (pbStreamRecord) ; ASSERT (pbCRC_32) ; // move streams described in the old section to the scratch store while (pbStreamRecord != NULL) { hr = m_Streams.Remove ( PMT_STREAM_RECORD_ELEMENTARY_PID (pbStreamRecord), reinterpret_cast (& pStream) ) ; if (SUCCEEDED (hr)) { ASSERT (pStream) ; ASSERT (pStream -> dwPID == (DWORD) PMT_STREAM_RECORD_ELEMENTARY_PID (pbStreamRecord)) ; hr = m_ScratchStreams.Store ( reinterpret_cast (pStream), pStream -> dwPID ) ; if (FAILED (hr)) { // failed to store into scratch store; there's no guarantee // that we can store it back into the regular store; we abort // it and bail m_pContentManager -> UnregisterKnownStream (pStream) ; m_pContentManager -> Recycle (pStream) ; goto cleanup ; } } // advance to next record AdvancePMTStreamRecordPointer ( pbCRC_32, & pbStreamRecord ) ; } // // now move those referenced in the new section back; what's // left is destroyed // SetPMTStreamRecordBracketPointers ( pNewSectionBuffer -> pbSection, & pbStreamRecord, & pbCRC_32 ) ; // move streams described in the old section to the scratch store while (pbStreamRecord != NULL) { hr = m_ScratchStreams.Remove ( PMT_STREAM_RECORD_ELEMENTARY_PID (pbStreamRecord), reinterpret_cast (& pStream) ) ; if (FAILED (hr)) { // new stream hr = m_pContentManager -> GetNew (& pStream) ; if (FAILED (hr)) { goto cleanup ; } ASSERT (pStream) ; pStream -> dwPID = PMT_STREAM_RECORD_ELEMENTARY_PID (pbStreamRecord) ; pStream -> dwStreamType = PMT_STREAM_RECORD_STREAM_TYPE_VALUE (pbStreamRecord) ; hr = m_pContentManager -> RegisterKnownStream (pStream) ; if (FAILED (hr)) { m_pContentManager -> Recycle (pStream) ; goto cleanup ; } } ASSERT (pStream) ; hr = m_Streams.Store ( reinterpret_cast (pStream), pStream -> dwPID ) ; if (FAILED (hr)) { // failed to store into regular store; we abort it and bail m_pContentManager -> UnregisterKnownStream (pStream) ; m_pContentManager -> Recycle (pStream) ; goto cleanup ; } // advance to next record AdvancePMTStreamRecordPointer ( pbCRC_32, & pbStreamRecord ) ; } cleanup : // now recycle all streams left in the scratch store while (!m_ScratchStreams.IsEmpty ()) { m_ScratchStreams.RemoveFirst ( reinterpret_cast (& pStream) ) ; ASSERT (pStream) ; m_pContentManager -> UnregisterKnownStream (pStream) ; m_pContentManager -> Recycle (pStream) ; } return hr ; } // --------------------------------------------------------------------------- // CMPEG2PSISectionBufferSource // --------------------------------------------------------------------------- CMPEG2PSISectionBufferSource::CMPEG2PSISectionBufferSource ( IN CMpeg2Stats * pStats, IN CISectionEventCallback * pICallback ) : CBufferSource (FALSE, // copy buffersource pStats, FALSE), m_pIEventCallback (pICallback) { TRACE_CONSTRUCTOR (TEXT ("CMPEG2PSISectionBufferSource")) ; ASSERT (m_pIEventCallback) ; InitializeListHead (& m_PSISectionListHead) ; ASSERT (m_pStats) ; m_pStats -> AddRef () ; } CMPEG2PSISectionBufferSource::~CMPEG2PSISectionBufferSource ( ) { TRACE_DESTRUCTOR (TEXT ("CMPEG2PSISectionBufferSource")) ; Clear () ; m_pStats -> Release () ; } void CMPEG2PSISectionBufferSource::Clear ( ) { LIST_ENTRY * pListEntry ; LIST_ENTRY_CONTAINER * pSectionBuffer ; // PSI sections while (!IsListEmpty (& m_PSISectionListHead)) { // unhook the first pListEntry = RemoveHeadList (& m_PSISectionListHead) ; pSectionBuffer = CONTAINING_RECORD (pListEntry, LIST_ENTRY_CONTAINER , ListEntry) ; // and recycle (should just have 1 ref - the list's) ASSERT (pSectionBuffer -> lRef == 1) ; ReleaseSectionBuffer (pSectionBuffer) ; } } HRESULT CMPEG2PSISectionBufferSource::GetCopyBuffer ( OUT DWORD_PTR * pdwContext, OUT BYTE ** ppbBuffer, IN OUT int * piBufferLength ) { HRESULT hr ; LIST_ENTRY_CONTAINER * pSectionBufferContainer ; SECTION_BUFFER * pSectionBuffer ; ASSERT (pdwContext) ; ASSERT (ppbBuffer) ; ASSERT (piBufferLength) ; if (* piBufferLength <= PSI_MAX_SECTION_LENGTH_ROUNDED) { hr = m_PSISectionBufferPool.Get ( & pSectionBufferContainer ) ; // if we got a buffer, setup the context & length information if (SUCCEEDED (hr)) { ASSERT (pSectionBufferContainer) ; // outgoing ref pSectionBufferContainer -> lRef = 1 ; pSectionBuffer = & pSectionBufferContainer -> Payload ; * pdwContext = reinterpret_cast (pSectionBufferContainer) ; * ppbBuffer = pSectionBuffer -> pbSection ; * piBufferLength = sizeof (pSectionBuffer -> pbSection) ; } } else { // requested buffer is longer than what is carried in a SECTION_BUFFER; // fail hr = E_FAIL ; } return hr ; } void CMPEG2PSISectionBufferSource::InsertNewSection_ ( IN LIST_ENTRY_CONTAINER * pNew, OUT LIST_ENTRY_CONTAINER ** ppOld ) /*++ purpose: we have a new section that must be inserted in the list parameters: pNew new section pOld if there's an existing section, this is returned; if none existed i.e. pNew points to a brand new section, this will be NULL locks held: --*/ { LIST_ENTRY * pCurListEntry ; // used to walk the list LIST_ENTRY_CONTAINER * pCurSectionBufferContainer ; SECTION_BUFFER * pCurSection ; // corresponds to pCurListEntry SECTION_BUFFER * pNewSection ; ASSERT (pNew) ; ASSERT (ppOld) ; ASSERT (pNew -> lRef > 0) ; pNewSection = & pNew -> Payload ; // locate the section (they are sorted by section_number); section // will be either of the following cases: // 1. a brand new section // 2. a newer section that we already have for (pCurListEntry = m_PSISectionListHead.Flink; ; // termination criteria is checked inside loop pCurListEntry = pCurListEntry -> Flink) { if (pCurListEntry == & m_PSISectionListHead) { // brand new section; must be inserted at tail InsertTailList (& m_PSISectionListHead, & pNew -> ListEntry) ; // list's InterlockedIncrement (& pNew -> lRef) ; // nothing is being replaced * ppOld = NULL ; break ; } pCurSectionBufferContainer = CONTAINING_RECORD (pCurListEntry, LIST_ENTRY_CONTAINER , ListEntry) ; pCurSection = & pCurSectionBufferContainer -> Payload ; if (pCurSection -> dwSection > pNewSection -> dwSection) { // brand new section; in the middle of the list somewhere; // insert before pCurSection InsertListEntry (pCurSectionBufferContainer -> ListEntry.Blink, & pNew -> ListEntry) ; // list's InterlockedIncrement (& pNew -> lRef) ; // nothing to replace * ppOld = NULL ; break ; } else if (pCurSection -> dwSection == pNewSection -> dwSection) { // existing section // insert before old InsertListEntry (& pCurSectionBufferContainer -> ListEntry, & pNew -> ListEntry) ; // list's InterlockedIncrement (& pNew -> lRef) ; // remove old RemoveEntryList (& pCurSectionBufferContainer -> ListEntry) ; // keep the old's ref // we are replacing this * ppOld = pCurSectionBufferContainer ; break ; } } } HRESULT CMPEG2PSISectionBufferSource::ReleaseSectionBuffer ( IN LIST_ENTRY_CONTAINER * pSectionBuffer ) { HRESULT hr ; ASSERT (pSectionBuffer) ; if (InterlockedDecrement (& pSectionBuffer -> lRef) == 0) { hr = m_PSISectionBufferPool.Recycle (pSectionBuffer) ; } else { hr = S_OK ; } return hr ; } HRESULT CMPEG2PSISectionBufferSource::CompleteCopyBuffer ( IN DWORD_PTR dwContext, IN BYTE * pbBuffer, IN int iBufferLength, IN BOOL fDiscontinuity, IN CTStickyVal * pReset // OUT: true if the demux needs a reset ) /*++ we assume that the section we've been passed has been verified as valid by the parser. --*/ { LIST_ENTRY_CONTAINER * pNewSectionBufferContainer ; LIST_ENTRY_CONTAINER * pOldSectionBufferContainer ; HRESULT hr ; ASSERT (dwContext) ; ASSERT (0 < iBufferLength && iBufferLength <= PSI_MAX_SECTION_LENGTH_ROUNDED) ; // recover our section buffer pNewSectionBufferContainer = reinterpret_cast *> (dwContext) ; ASSERT (pNewSectionBufferContainer -> Payload.pbSection == pbBuffer) ; // set the section and version numbers pNewSectionBufferContainer -> Payload.dwSection = PSI_SECTION_NUMBER_VALUE (pNewSectionBufferContainer -> Payload.pbSection) ; pNewSectionBufferContainer -> Payload.dwVersion = PSI_VERSION_NUMBER_VALUE (pNewSectionBufferContainer -> Payload.pbSection) ; // section we got must not only be new, but must be applicable ASSERT (PSI_CURRENT_NEXT_INDICATOR_BIT (pNewSectionBufferContainer -> Payload.pbSection)) ; InsertNewSection_ ( pNewSectionBufferContainer, & pOldSectionBufferContainer ) ; ASSERT (m_pIEventCallback) ; hr = m_pIEventCallback -> NewSectionBuffer ( & pNewSectionBufferContainer -> Payload, pOldSectionBufferContainer ? & pOldSectionBufferContainer -> Payload : NULL ) ; if (SUCCEEDED (hr)) { // if there's an old section buffer, we recycle it if (pOldSectionBufferContainer) { ReleaseSectionBuffer (pOldSectionBufferContainer) ; } } else { // a failure occured; the fallback is to remove the new, reinsert the old // and return the error code all the way back out RemoveEntryList (& pNewSectionBufferContainer -> ListEntry) ; // recycle the new buffer ReleaseSectionBuffer (pNewSectionBufferContainer) ; // if we replaced an existing section, put it back in the list if (pOldSectionBufferContainer) { InsertNewSection_ ( pOldSectionBufferContainer, & pNewSectionBufferContainer // used as scratch here ) ; // should not be replacing anything ASSERT (pNewSectionBufferContainer == NULL) ; } } return hr ; } HRESULT CMPEG2PSISectionBufferSource::ReleaseCopyBuffer ( IN DWORD_PTR dwContext, IN BYTE * pbBuffer, IN int iBufferLength ) { HRESULT hr ; if (dwContext != 0) { ASSERT ((& (reinterpret_cast *> (dwContext)) -> Payload) -> pbSection == pbBuffer) ; hr = ReleaseSectionBuffer (reinterpret_cast *> (dwContext)) ; } else { hr = S_OK ; } return hr ; }