757 lines
25 KiB
C++
757 lines
25 KiB
C++
|
/**********
|
||
|
This library is free software; you can redistribute it and/or modify it under
|
||
|
the terms of the GNU Lesser General Public License as published by the
|
||
|
Free Software Foundation; either version 2.1 of the License, or (at your
|
||
|
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
|
||
|
|
||
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||
|
more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public License
|
||
|
along with this library; if not, write to the Free Software Foundation, Inc.,
|
||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
**********/
|
||
|
// "liveMedia"
|
||
|
// Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved.
|
||
|
// Demultiplexer for a MPEG 1 or 2 Program Stream
|
||
|
// Implementation
|
||
|
|
||
|
#include "MPEG1or2Demux.hh"
|
||
|
#include "MPEG1or2DemuxedElementaryStream.hh"
|
||
|
#include "StreamParser.hh"
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
////////// MPEGProgramStreamParser definition //////////
|
||
|
|
||
|
// An enum representing the current state of the parser:
|
||
|
enum MPEGParseState {
|
||
|
PARSING_PACK_HEADER,
|
||
|
PARSING_SYSTEM_HEADER,
|
||
|
PARSING_PES_PACKET
|
||
|
};
|
||
|
|
||
|
class MPEGProgramStreamParser: public StreamParser {
|
||
|
public:
|
||
|
MPEGProgramStreamParser(MPEG1or2Demux* usingDemux, FramedSource* inputSource);
|
||
|
virtual ~MPEGProgramStreamParser();
|
||
|
|
||
|
public:
|
||
|
unsigned char parse();
|
||
|
// returns the stream id of a stream for which a frame was acquired,
|
||
|
// or 0 if no such frame was acquired.
|
||
|
|
||
|
private:
|
||
|
void setParseState(MPEGParseState parseState);
|
||
|
|
||
|
void parsePackHeader();
|
||
|
void parseSystemHeader();
|
||
|
unsigned char parsePESPacket(); // returns as does parse()
|
||
|
|
||
|
Boolean isSpecialStreamId(unsigned char stream_id) const;
|
||
|
// for PES packet header parsing
|
||
|
|
||
|
private:
|
||
|
MPEG1or2Demux* fUsingDemux;
|
||
|
MPEGParseState fCurrentParseState;
|
||
|
};
|
||
|
|
||
|
|
||
|
////////// MPEG1or2Demux::OutputDescriptor::SavedData definition/implementation //////////
|
||
|
|
||
|
class MPEG1or2Demux::OutputDescriptor::SavedData {
|
||
|
public:
|
||
|
SavedData(unsigned char* buf, unsigned size)
|
||
|
: next(NULL), data(buf), dataSize(size), numBytesUsed(0) {
|
||
|
}
|
||
|
virtual ~SavedData() {
|
||
|
delete[] data;
|
||
|
delete next;
|
||
|
}
|
||
|
|
||
|
SavedData* next;
|
||
|
unsigned char* data;
|
||
|
unsigned dataSize, numBytesUsed;
|
||
|
};
|
||
|
|
||
|
|
||
|
////////// MPEG1or2Demux implementation //////////
|
||
|
|
||
|
MPEG1or2Demux
|
||
|
::MPEG1or2Demux(UsageEnvironment& env,
|
||
|
FramedSource* inputSource, Boolean reclaimWhenLastESDies)
|
||
|
: Medium(env),
|
||
|
fInputSource(inputSource), fMPEGversion(0),
|
||
|
fNextAudioStreamNumber(0), fNextVideoStreamNumber(0),
|
||
|
fReclaimWhenLastESDies(reclaimWhenLastESDies), fNumOutstandingESs(0),
|
||
|
fNumPendingReads(0), fHaveUndeliveredData(False) {
|
||
|
fParser = new MPEGProgramStreamParser(this, inputSource);
|
||
|
for (unsigned i = 0; i < 256; ++i) {
|
||
|
fOutput[i].savedDataHead = fOutput[i].savedDataTail = NULL;
|
||
|
fOutput[i].isPotentiallyReadable = False;
|
||
|
fOutput[i].isCurrentlyActive = False;
|
||
|
fOutput[i].isCurrentlyAwaitingData = False;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MPEG1or2Demux::~MPEG1or2Demux() {
|
||
|
delete fParser;
|
||
|
for (unsigned i = 0; i < 256; ++i) delete fOutput[i].savedDataHead;
|
||
|
Medium::close(fInputSource);
|
||
|
}
|
||
|
|
||
|
MPEG1or2Demux* MPEG1or2Demux
|
||
|
::createNew(UsageEnvironment& env,
|
||
|
FramedSource* inputSource, Boolean reclaimWhenLastESDies) {
|
||
|
// Need to add source type checking here??? #####
|
||
|
|
||
|
return new MPEG1or2Demux(env, inputSource, reclaimWhenLastESDies);
|
||
|
}
|
||
|
|
||
|
MPEG1or2Demux::SCR::SCR()
|
||
|
: highBit(0), remainingBits(0), extension(0), isValid(False) {
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux
|
||
|
::noteElementaryStreamDeletion(MPEG1or2DemuxedElementaryStream* /*es*/) {
|
||
|
if (--fNumOutstandingESs == 0 && fReclaimWhenLastESDies) {
|
||
|
Medium::close(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::flushInput() {
|
||
|
fParser->flushInput();
|
||
|
}
|
||
|
|
||
|
MPEG1or2DemuxedElementaryStream*
|
||
|
MPEG1or2Demux::newElementaryStream(u_int8_t streamIdTag) {
|
||
|
++fNumOutstandingESs;
|
||
|
fOutput[streamIdTag].isPotentiallyReadable = True;
|
||
|
return new MPEG1or2DemuxedElementaryStream(envir(), streamIdTag, *this);
|
||
|
}
|
||
|
|
||
|
MPEG1or2DemuxedElementaryStream* MPEG1or2Demux::newAudioStream() {
|
||
|
unsigned char newAudioStreamTag = 0xC0 | (fNextAudioStreamNumber++&~0xE0);
|
||
|
// MPEG audio stream tags are 110x xxxx (binary)
|
||
|
return newElementaryStream(newAudioStreamTag);
|
||
|
}
|
||
|
|
||
|
MPEG1or2DemuxedElementaryStream* MPEG1or2Demux::newVideoStream() {
|
||
|
unsigned char newVideoStreamTag = 0xE0 | (fNextVideoStreamNumber++&~0xF0);
|
||
|
// MPEG video stream tags are 1110 xxxx (binary)
|
||
|
return newElementaryStream(newVideoStreamTag);
|
||
|
}
|
||
|
|
||
|
// Appropriate one of the reserved stream id tags to mean: return raw PES packets:
|
||
|
#define RAW_PES 0xFC
|
||
|
|
||
|
MPEG1or2DemuxedElementaryStream* MPEG1or2Demux::newRawPESStream() {
|
||
|
return newElementaryStream(RAW_PES);
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
|
||
|
unsigned char* to, unsigned maxSize,
|
||
|
FramedSource::afterGettingFunc* afterGettingFunc,
|
||
|
void* afterGettingClientData,
|
||
|
FramedSource::onCloseFunc* onCloseFunc,
|
||
|
void* onCloseClientData) {
|
||
|
struct OutputDescriptor& out = fOutput[streamIdTag];
|
||
|
|
||
|
// Make sure this stream is not already being read:
|
||
|
if (out.isCurrentlyAwaitingData) {
|
||
|
envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream more than once!\n";
|
||
|
envir().internalError();
|
||
|
}
|
||
|
|
||
|
out.to = to; out.maxSize = maxSize;
|
||
|
out.fAfterGettingFunc = afterGettingFunc;
|
||
|
out.afterGettingClientData = afterGettingClientData;
|
||
|
out.fOnCloseFunc = onCloseFunc;
|
||
|
out.onCloseClientData = onCloseClientData;
|
||
|
out.isCurrentlyActive = True;
|
||
|
out.isCurrentlyAwaitingData = True;
|
||
|
// out.frameSize and out.presentationTime will be set when a frame's read
|
||
|
|
||
|
++fNumPendingReads;
|
||
|
}
|
||
|
|
||
|
Boolean MPEG1or2Demux::useSavedData(u_int8_t streamIdTag,
|
||
|
unsigned char* to, unsigned maxSize,
|
||
|
FramedSource::afterGettingFunc* afterGettingFunc,
|
||
|
void* afterGettingClientData) {
|
||
|
struct OutputDescriptor& out = fOutput[streamIdTag];
|
||
|
if (out.savedDataHead == NULL) return False; // common case
|
||
|
|
||
|
unsigned totNumBytesCopied = 0;
|
||
|
while (maxSize > 0 && out.savedDataHead != NULL) {
|
||
|
OutputDescriptor::SavedData& savedData = *(out.savedDataHead);
|
||
|
unsigned char* from = &savedData.data[savedData.numBytesUsed];
|
||
|
unsigned numBytesToCopy = savedData.dataSize - savedData.numBytesUsed;
|
||
|
if (numBytesToCopy > maxSize) numBytesToCopy = maxSize;
|
||
|
memmove(to, from, numBytesToCopy);
|
||
|
to += numBytesToCopy;
|
||
|
maxSize -= numBytesToCopy;
|
||
|
out.savedDataTotalSize -= numBytesToCopy;
|
||
|
totNumBytesCopied += numBytesToCopy;
|
||
|
savedData.numBytesUsed += numBytesToCopy;
|
||
|
if (savedData.numBytesUsed == savedData.dataSize) {
|
||
|
out.savedDataHead = savedData.next;
|
||
|
if (out.savedDataHead == NULL) out.savedDataTail = NULL;
|
||
|
savedData.next = NULL;
|
||
|
delete &savedData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out.isCurrentlyActive = True;
|
||
|
if (afterGettingFunc != NULL) {
|
||
|
struct timeval presentationTime;
|
||
|
presentationTime.tv_sec = 0; presentationTime.tv_usec = 0; // should fix #####
|
||
|
(*afterGettingFunc)(afterGettingClientData, totNumBytesCopied,
|
||
|
0 /* numTruncatedBytes */, presentationTime,
|
||
|
0 /* durationInMicroseconds ?????#####*/);
|
||
|
}
|
||
|
return True;
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux
|
||
|
::continueReadProcessing(void* clientData,
|
||
|
unsigned char* /*ptr*/, unsigned /*size*/,
|
||
|
struct timeval /*presentationTime*/) {
|
||
|
MPEG1or2Demux* demux = (MPEG1or2Demux*)clientData;
|
||
|
demux->continueReadProcessing();
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::continueReadProcessing() {
|
||
|
while (fNumPendingReads > 0) {
|
||
|
unsigned char acquiredStreamIdTag = fParser->parse();
|
||
|
|
||
|
if (acquiredStreamIdTag != 0) {
|
||
|
// We were able to acquire a frame from the input.
|
||
|
struct OutputDescriptor& newOut = fOutput[acquiredStreamIdTag];
|
||
|
newOut.isCurrentlyAwaitingData = False;
|
||
|
// indicates that we can be read again
|
||
|
// (This needs to be set before the 'after getting' call below,
|
||
|
// in case it tries to read another frame)
|
||
|
|
||
|
// Call our own 'after getting' function. Because we're not a 'leaf'
|
||
|
// source, we can call this directly, without risking infinite recursion.
|
||
|
if (newOut.fAfterGettingFunc != NULL) {
|
||
|
(*newOut.fAfterGettingFunc)(newOut.afterGettingClientData,
|
||
|
newOut.frameSize, 0 /* numTruncatedBytes */,
|
||
|
newOut.presentationTime,
|
||
|
0 /* durationInMicroseconds ?????#####*/);
|
||
|
--fNumPendingReads;
|
||
|
}
|
||
|
} else {
|
||
|
// We were unable to parse a complete frame from the input, because:
|
||
|
// - we had to read more data from the source stream, or
|
||
|
// - we found a frame for a stream that was being read, but whose
|
||
|
// reader is not ready to get the frame right now, or
|
||
|
// - the source stream has ended.
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::getNextFrame(u_int8_t streamIdTag,
|
||
|
unsigned char* to, unsigned maxSize,
|
||
|
FramedSource::afterGettingFunc* afterGettingFunc,
|
||
|
void* afterGettingClientData,
|
||
|
FramedSource::onCloseFunc* onCloseFunc,
|
||
|
void* onCloseClientData) {
|
||
|
// First, check whether we have saved data for this stream id:
|
||
|
if (useSavedData(streamIdTag, to, maxSize,
|
||
|
afterGettingFunc, afterGettingClientData)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Then save the parameters of the specified stream id:
|
||
|
registerReadInterest(streamIdTag, to, maxSize,
|
||
|
afterGettingFunc, afterGettingClientData,
|
||
|
onCloseFunc, onCloseClientData);
|
||
|
|
||
|
// Next, if we're the only currently pending read, continue looking for data:
|
||
|
if (fNumPendingReads == 1 || fHaveUndeliveredData) {
|
||
|
fHaveUndeliveredData = 0;
|
||
|
continueReadProcessing();
|
||
|
} // otherwise the continued read processing has already been taken care of
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::stopGettingFrames(u_int8_t streamIdTag) {
|
||
|
struct OutputDescriptor& out = fOutput[streamIdTag];
|
||
|
|
||
|
if (out.isCurrentlyAwaitingData && fNumPendingReads > 0) --fNumPendingReads;
|
||
|
|
||
|
out.isCurrentlyActive = out.isCurrentlyAwaitingData = False;
|
||
|
}
|
||
|
|
||
|
void MPEG1or2Demux::handleClosure(void* clientData) {
|
||
|
MPEG1or2Demux* demux = (MPEG1or2Demux*)clientData;
|
||
|
|
||
|
demux->fNumPendingReads = 0;
|
||
|
|
||
|
// Tell all pending readers that our source has closed.
|
||
|
// Note that we need to make a copy of our readers' close functions
|
||
|
// (etc.) before we start calling any of them, in case one of them
|
||
|
// ends up deleting this.
|
||
|
struct {
|
||
|
FramedSource::onCloseFunc* fOnCloseFunc;
|
||
|
void* onCloseClientData;
|
||
|
} savedPending[256];
|
||
|
unsigned i, numPending = 0;
|
||
|
for (i = 0; i < 256; ++i) {
|
||
|
struct OutputDescriptor& out = demux->fOutput[i];
|
||
|
if (out.isCurrentlyAwaitingData) {
|
||
|
if (out.fOnCloseFunc != NULL) {
|
||
|
savedPending[numPending].fOnCloseFunc = out.fOnCloseFunc;
|
||
|
savedPending[numPending].onCloseClientData = out.onCloseClientData;
|
||
|
++numPending;
|
||
|
}
|
||
|
}
|
||
|
delete out.savedDataHead; out.savedDataHead = out.savedDataTail = NULL;
|
||
|
out.savedDataTotalSize = 0;
|
||
|
out.isPotentiallyReadable = out.isCurrentlyActive = out.isCurrentlyAwaitingData
|
||
|
= False;
|
||
|
}
|
||
|
for (i = 0; i < numPending; ++i) {
|
||
|
(*savedPending[i].fOnCloseFunc)(savedPending[i].onCloseClientData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
////////// MPEGProgramStreamParser implementation //////////
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
MPEGProgramStreamParser::MPEGProgramStreamParser(MPEG1or2Demux* usingDemux,
|
||
|
FramedSource* inputSource)
|
||
|
: StreamParser(inputSource, MPEG1or2Demux::handleClosure, usingDemux,
|
||
|
&MPEG1or2Demux::continueReadProcessing, usingDemux),
|
||
|
fUsingDemux(usingDemux), fCurrentParseState(PARSING_PACK_HEADER) {
|
||
|
}
|
||
|
|
||
|
MPEGProgramStreamParser::~MPEGProgramStreamParser() {
|
||
|
}
|
||
|
|
||
|
void MPEGProgramStreamParser::setParseState(MPEGParseState parseState) {
|
||
|
fCurrentParseState = parseState;
|
||
|
saveParserState();
|
||
|
}
|
||
|
|
||
|
unsigned char MPEGProgramStreamParser::parse() {
|
||
|
unsigned char acquiredStreamTagId = 0;
|
||
|
|
||
|
try {
|
||
|
do {
|
||
|
switch (fCurrentParseState) {
|
||
|
case PARSING_PACK_HEADER: {
|
||
|
parsePackHeader();
|
||
|
break;
|
||
|
}
|
||
|
case PARSING_SYSTEM_HEADER: {
|
||
|
parseSystemHeader();
|
||
|
break;
|
||
|
}
|
||
|
case PARSING_PES_PACKET: {
|
||
|
acquiredStreamTagId = parsePESPacket();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} while(acquiredStreamTagId == 0);
|
||
|
|
||
|
return acquiredStreamTagId;
|
||
|
} catch (int /*e*/) {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "MPEGProgramStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
|
||
|
fflush(stderr);
|
||
|
#endif
|
||
|
return 0; // the parsing got interrupted
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define PACK_START_CODE 0x000001BA
|
||
|
#define SYSTEM_HEADER_START_CODE 0x000001BB
|
||
|
#define PACKET_START_CODE_PREFIX 0x00000100
|
||
|
|
||
|
static inline Boolean isPacketStartCode(unsigned code) {
|
||
|
return (code&0xFFFFFF00) == PACKET_START_CODE_PREFIX
|
||
|
&& code > SYSTEM_HEADER_START_CODE;
|
||
|
}
|
||
|
|
||
|
void MPEGProgramStreamParser::parsePackHeader() {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "parsing pack header\n"); fflush(stderr);
|
||
|
#endif
|
||
|
unsigned first4Bytes;
|
||
|
while (1) {
|
||
|
first4Bytes = test4Bytes();
|
||
|
|
||
|
// We're supposed to have a pack header here, but check also for
|
||
|
// a system header or a PES packet, just in case:
|
||
|
if (first4Bytes == PACK_START_CODE) {
|
||
|
skipBytes(4);
|
||
|
break;
|
||
|
} else if (first4Bytes == SYSTEM_HEADER_START_CODE) {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "found system header instead of pack header\n");
|
||
|
#endif
|
||
|
setParseState(PARSING_SYSTEM_HEADER);
|
||
|
return;
|
||
|
} else if (isPacketStartCode(first4Bytes)) {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "found packet start code 0x%02x instead of pack header\n", first4Bytes);
|
||
|
#endif
|
||
|
setParseState(PARSING_PES_PACKET);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
setParseState(PARSING_PACK_HEADER); // ensures we progress over bad data
|
||
|
if ((first4Bytes&0xFF) > 1) { // a system code definitely doesn't start here
|
||
|
skipBytes(4);
|
||
|
} else {
|
||
|
skipBytes(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The size of the pack header differs depending on whether it's
|
||
|
// MPEG-1 or MPEG-2. The next byte tells us this:
|
||
|
unsigned char nextByte = get1Byte();
|
||
|
MPEG1or2Demux::SCR& scr = fUsingDemux->fLastSeenSCR; // alias
|
||
|
if ((nextByte&0xF0) == 0x20) { // MPEG-1
|
||
|
fUsingDemux->fMPEGversion = 1;
|
||
|
scr.highBit = (nextByte&0x08)>>3;
|
||
|
scr.remainingBits = (nextByte&0x06)<<29;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
scr.remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
scr.remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
scr.extension = 0;
|
||
|
scr.isValid = True;
|
||
|
skipBits(24);
|
||
|
|
||
|
#if defined(DEBUG_TIMESTAMPS) || defined(DEBUG_SCR_TIMESTAMPS)
|
||
|
fprintf(stderr, "pack hdr system_clock_reference_base: 0x%x",
|
||
|
scr.highBit);
|
||
|
fprintf(stderr, "%08x\n", scr.remainingBits);
|
||
|
#endif
|
||
|
} else if ((nextByte&0xC0) == 0x40) { // MPEG-2
|
||
|
fUsingDemux->fMPEGversion = 2;
|
||
|
scr.highBit = (nextByte&0x20)>>5;
|
||
|
scr.remainingBits = (nextByte&0x18)<<27;
|
||
|
scr.remainingBits |= (nextByte&0x03)<<28;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
scr.remainingBits |= (next4Bytes&0xFFF80000)>>4;
|
||
|
scr.remainingBits |= (next4Bytes&0x0003FFF8)>>3;
|
||
|
scr.extension = (next4Bytes&0x00000003)<<7;
|
||
|
next4Bytes = get4Bytes();
|
||
|
scr.extension |= (next4Bytes&0xFE000000)>>25;
|
||
|
scr.isValid = True;
|
||
|
skipBits(5);
|
||
|
|
||
|
#if defined(DEBUG_TIMESTAMPS) || defined(DEBUG_SCR_TIMESTAMPS)
|
||
|
fprintf(stderr, "pack hdr system_clock_reference_base: 0x%x",
|
||
|
scr.highBit);
|
||
|
fprintf(stderr, "%08x\n", scr.remainingBits);
|
||
|
fprintf(stderr, "pack hdr system_clock_reference_extension: 0x%03x\n",
|
||
|
scr.extension);
|
||
|
#endif
|
||
|
unsigned char pack_stuffing_length = getBits(3);
|
||
|
skipBytes(pack_stuffing_length);
|
||
|
} else { // unknown
|
||
|
fUsingDemux->envir() << "StreamParser::parsePack() saw strange byte following pack_start_code\n";
|
||
|
}
|
||
|
|
||
|
// Check for a System Header next:
|
||
|
setParseState(PARSING_SYSTEM_HEADER);
|
||
|
}
|
||
|
|
||
|
void MPEGProgramStreamParser::parseSystemHeader() {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "parsing system header\n"); fflush(stderr);
|
||
|
#endif
|
||
|
unsigned next4Bytes = test4Bytes();
|
||
|
if (next4Bytes != SYSTEM_HEADER_START_CODE) {
|
||
|
// The system header was optional. Look for a PES Packet instead:
|
||
|
setParseState(PARSING_PES_PACKET);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "saw system_header_start_code\n"); fflush(stderr);
|
||
|
#endif
|
||
|
skipBytes(4); // we've already seen the system_header_start_code
|
||
|
|
||
|
unsigned short remaining_header_length = get2Bytes();
|
||
|
|
||
|
// According to the MPEG-1 and MPEG-2 specs, "remaining_header_length" should be
|
||
|
// at least 6 bytes. Check this now:
|
||
|
if (remaining_header_length < 6) {
|
||
|
fUsingDemux->envir() << "StreamParser::parseSystemHeader(): saw strange header_length: "
|
||
|
<< remaining_header_length << " < 6\n";
|
||
|
}
|
||
|
skipBytes(remaining_header_length);
|
||
|
|
||
|
// Check for a PES Packet next:
|
||
|
setParseState(PARSING_PES_PACKET);
|
||
|
}
|
||
|
|
||
|
#define private_stream_1 0xBD
|
||
|
#define private_stream_2 0xBF
|
||
|
|
||
|
// A test for stream ids that are exempt from normal PES packet header parsing
|
||
|
Boolean MPEGProgramStreamParser
|
||
|
::isSpecialStreamId(unsigned char stream_id) const {
|
||
|
if (stream_id == RAW_PES) return True; // hack
|
||
|
|
||
|
if (fUsingDemux->fMPEGversion == 1) {
|
||
|
return stream_id == private_stream_2;
|
||
|
} else { // assume MPEG-2
|
||
|
if (stream_id <= private_stream_2) {
|
||
|
return stream_id != private_stream_1;
|
||
|
} else if ((stream_id&0xF0) == 0xF0) {
|
||
|
unsigned char lower4Bits = stream_id&0x0F;
|
||
|
return lower4Bits <= 2 || lower4Bits == 0x8 || lower4Bits == 0xF;
|
||
|
} else {
|
||
|
return False;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define READER_NOT_READY 2
|
||
|
|
||
|
unsigned char MPEGProgramStreamParser::parsePESPacket() {
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "parsing PES packet\n"); fflush(stderr);
|
||
|
#endif
|
||
|
unsigned next4Bytes = test4Bytes();
|
||
|
if (!isPacketStartCode(next4Bytes)) {
|
||
|
// The PES Packet was optional. Look for a Pack Header instead:
|
||
|
setParseState(PARSING_PACK_HEADER);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "saw packet_start_code_prefix\n"); fflush(stderr);
|
||
|
#endif
|
||
|
skipBytes(3); // we've already seen the packet_start_code_prefix
|
||
|
|
||
|
unsigned char stream_id = get1Byte();
|
||
|
#if defined(DEBUG) || defined(DEBUG_TIMESTAMPS)
|
||
|
unsigned char streamNum = stream_id;
|
||
|
char const* streamTypeStr;
|
||
|
if ((stream_id&0xE0) == 0xC0) {
|
||
|
streamTypeStr = "audio";
|
||
|
streamNum = stream_id&~0xE0;
|
||
|
} else if ((stream_id&0xF0) == 0xE0) {
|
||
|
streamTypeStr = "video";
|
||
|
streamNum = stream_id&~0xF0;
|
||
|
} else if (stream_id == 0xbc) {
|
||
|
streamTypeStr = "reserved";
|
||
|
} else if (stream_id == 0xbd) {
|
||
|
streamTypeStr = "private_1";
|
||
|
} else if (stream_id == 0xbe) {
|
||
|
streamTypeStr = "padding";
|
||
|
} else if (stream_id == 0xbf) {
|
||
|
streamTypeStr = "private_2";
|
||
|
} else {
|
||
|
streamTypeStr = "unknown";
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef DEBUG
|
||
|
static unsigned frameCount = 1;
|
||
|
fprintf(stderr, "%d, saw %s stream: 0x%02x\n", frameCount, streamTypeStr, streamNum); fflush(stderr);
|
||
|
#endif
|
||
|
|
||
|
unsigned short PES_packet_length = get2Bytes();
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "PES_packet_length: %d\n", PES_packet_length); fflush(stderr);
|
||
|
#endif
|
||
|
|
||
|
// Parse over the rest of the header, until we get to the packet data itself.
|
||
|
// This varies depending upon the MPEG version:
|
||
|
if (fUsingDemux->fOutput[RAW_PES].isPotentiallyReadable) {
|
||
|
// Hack: We've been asked to return raw PES packets, for every stream:
|
||
|
stream_id = RAW_PES;
|
||
|
}
|
||
|
unsigned savedParserOffset = curOffset();
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
unsigned char pts_highBit = 0;
|
||
|
unsigned pts_remainingBits = 0;
|
||
|
unsigned char dts_highBit = 0;
|
||
|
unsigned dts_remainingBits = 0;
|
||
|
#endif
|
||
|
if (fUsingDemux->fMPEGversion == 1) {
|
||
|
if (!isSpecialStreamId(stream_id)) {
|
||
|
unsigned char nextByte;
|
||
|
while ((nextByte = get1Byte()) == 0xFF) { // stuffing_byte
|
||
|
}
|
||
|
if ((nextByte&0xC0) == 0x40) { // '01'
|
||
|
skipBytes(1);
|
||
|
nextByte = get1Byte();
|
||
|
}
|
||
|
if ((nextByte&0xF0) == 0x20) { // '0010'
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
pts_highBit = (nextByte&0x08)>>3;
|
||
|
pts_remainingBits = (nextByte&0x06)<<29;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
pts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
pts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
#else
|
||
|
skipBytes(4);
|
||
|
#endif
|
||
|
} else if ((nextByte&0xF0) == 0x30) { // '0011'
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
pts_highBit = (nextByte&0x08)>>3;
|
||
|
pts_remainingBits = (nextByte&0x06)<<29;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
pts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
pts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
|
||
|
nextByte = get1Byte();
|
||
|
dts_highBit = (nextByte&0x08)>>3;
|
||
|
dts_remainingBits = (nextByte&0x06)<<29;
|
||
|
next4Bytes = get4Bytes();
|
||
|
dts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
dts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
#else
|
||
|
skipBytes(9);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
} else { // assume MPEG-2
|
||
|
if (!isSpecialStreamId(stream_id)) {
|
||
|
// Fields in the next 3 bytes determine the size of the rest:
|
||
|
unsigned next3Bytes = getBits(24);
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
unsigned char PTS_DTS_flags = (next3Bytes&0x00C000)>>14;
|
||
|
#endif
|
||
|
#ifdef undef
|
||
|
unsigned char ESCR_flag = (next3Bytes&0x002000)>>13;
|
||
|
unsigned char ES_rate_flag = (next3Bytes&0x001000)>>12;
|
||
|
unsigned char DSM_trick_mode_flag = (next3Bytes&0x000800)>>11;
|
||
|
#endif
|
||
|
unsigned char PES_header_data_length = (next3Bytes&0x0000FF);
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "PES_header_data_length: 0x%02x\n", PES_header_data_length); fflush(stderr);
|
||
|
#endif
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
if (PTS_DTS_flags == 0x2 && PES_header_data_length >= 5) {
|
||
|
unsigned char nextByte = get1Byte();
|
||
|
pts_highBit = (nextByte&0x08)>>3;
|
||
|
pts_remainingBits = (nextByte&0x06)<<29;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
pts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
pts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
|
||
|
skipBytes(PES_header_data_length-5);
|
||
|
} else if (PTS_DTS_flags == 0x3 && PES_header_data_length >= 10) {
|
||
|
unsigned char nextByte = get1Byte();
|
||
|
pts_highBit = (nextByte&0x08)>>3;
|
||
|
pts_remainingBits = (nextByte&0x06)<<29;
|
||
|
unsigned next4Bytes = get4Bytes();
|
||
|
pts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
pts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
|
||
|
nextByte = get1Byte();
|
||
|
dts_highBit = (nextByte&0x08)>>3;
|
||
|
dts_remainingBits = (nextByte&0x06)<<29;
|
||
|
next4Bytes = get4Bytes();
|
||
|
dts_remainingBits |= (next4Bytes&0xFFFE0000)>>2;
|
||
|
dts_remainingBits |= (next4Bytes&0x0000FFFE)>>1;
|
||
|
|
||
|
skipBytes(PES_header_data_length-10);
|
||
|
}
|
||
|
#else
|
||
|
skipBytes(PES_header_data_length);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
#ifdef DEBUG_TIMESTAMPS
|
||
|
fprintf(stderr, "%s stream, ", streamTypeStr);
|
||
|
fprintf(stderr, "packet presentation_time_stamp: 0x%x", pts_highBit);
|
||
|
fprintf(stderr, "%08x\n", pts_remainingBits);
|
||
|
fprintf(stderr, "\t\tpacket decoding_time_stamp: 0x%x", dts_highBit);
|
||
|
fprintf(stderr, "%08x\n", dts_remainingBits);
|
||
|
#endif
|
||
|
|
||
|
// The rest of the packet will be the "PES_packet_data_byte"s
|
||
|
// Make sure that "PES_packet_length" was consistent with where we are now:
|
||
|
unsigned char acquiredStreamIdTag = 0;
|
||
|
unsigned currentParserOffset = curOffset();
|
||
|
unsigned bytesSkipped = currentParserOffset - savedParserOffset;
|
||
|
if (stream_id == RAW_PES) {
|
||
|
restoreSavedParserState(); // so we deliver from the beginning of the PES packet
|
||
|
PES_packet_length += 6; // to include the whole of the PES packet
|
||
|
bytesSkipped = 0;
|
||
|
}
|
||
|
if (PES_packet_length < bytesSkipped) {
|
||
|
fUsingDemux->envir() << "StreamParser::parsePESPacket(): saw inconsistent PES_packet_length "
|
||
|
<< PES_packet_length << " < "
|
||
|
<< bytesSkipped << "\n";
|
||
|
} else {
|
||
|
PES_packet_length -= bytesSkipped;
|
||
|
#ifdef DEBUG
|
||
|
unsigned next4Bytes = test4Bytes();
|
||
|
#endif
|
||
|
|
||
|
// Check whether our using source is interested in this stream type.
|
||
|
// If so, deliver the frame to him:
|
||
|
MPEG1or2Demux::OutputDescriptor_t& out = fUsingDemux->fOutput[stream_id];
|
||
|
if (out.isCurrentlyAwaitingData) {
|
||
|
unsigned numBytesToCopy;
|
||
|
if (PES_packet_length > out.maxSize) {
|
||
|
fUsingDemux->envir() << "MPEGProgramStreamParser::parsePESPacket() error: PES_packet_length ("
|
||
|
<< PES_packet_length
|
||
|
<< ") exceeds max frame size asked for ("
|
||
|
<< out.maxSize << ")\n";
|
||
|
numBytesToCopy = out.maxSize;
|
||
|
} else {
|
||
|
numBytesToCopy = PES_packet_length;
|
||
|
}
|
||
|
|
||
|
getBytes(out.to, numBytesToCopy);
|
||
|
out.frameSize = numBytesToCopy;
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "%d, %d bytes of PES_packet_data (out.maxSize: %d); first 4 bytes: 0x%08x\n", frameCount, numBytesToCopy, out.maxSize, next4Bytes); fflush(stderr);
|
||
|
#endif
|
||
|
// set out.presentationTime later #####
|
||
|
acquiredStreamIdTag = stream_id;
|
||
|
PES_packet_length -= numBytesToCopy;
|
||
|
} else if (out.isCurrentlyActive) {
|
||
|
// Someone has been reading this stream, but isn't right now.
|
||
|
// We can't deliver this frame until he asks for it, so punt for now.
|
||
|
// The next time he asks for a frame, he'll get it.
|
||
|
#ifdef DEBUG
|
||
|
fprintf(stderr, "%d, currently undeliverable PES data; first 4 bytes: 0x%08x - currently undeliverable!\n", frameCount, next4Bytes); fflush(stderr);
|
||
|
#endif
|
||
|
restoreSavedParserState(); // so we read from the beginning next time
|
||
|
fUsingDemux->fHaveUndeliveredData = True;
|
||
|
throw READER_NOT_READY;
|
||
|
} else if (out.isPotentiallyReadable &&
|
||
|
out.savedDataTotalSize + PES_packet_length < 1000000 /*limit*/) {
|
||
|
// Someone is interested in this stream, but hasn't begun reading it yet.
|
||
|
// Save this data, so that the reader will get it when he later asks for it.
|
||
|
unsigned char* buf = new unsigned char[PES_packet_length];
|
||
|
getBytes(buf, PES_packet_length);
|
||
|
MPEG1or2Demux::OutputDescriptor::SavedData* savedData
|
||
|
= new MPEG1or2Demux::OutputDescriptor::SavedData(buf, PES_packet_length);
|
||
|
if (out.savedDataHead == NULL) {
|
||
|
out.savedDataHead = out.savedDataTail = savedData;
|
||
|
} else {
|
||
|
out.savedDataTail->next = savedData;
|
||
|
out.savedDataTail = savedData;
|
||
|
}
|
||
|
out.savedDataTotalSize += PES_packet_length;
|
||
|
PES_packet_length = 0;
|
||
|
}
|
||
|
skipBytes(PES_packet_length);
|
||
|
}
|
||
|
|
||
|
// Check for another PES Packet next:
|
||
|
setParseState(PARSING_PES_PACKET);
|
||
|
#ifdef DEBUG
|
||
|
++frameCount;
|
||
|
#endif
|
||
|
return acquiredStreamIdTag;
|
||
|
}
|