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