blob: ccc1f2910cb0f0b5a33e71893e9a2930e8775208 [file] [log] [blame]
/**********
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 3 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-2020 Live Networks, Inc. All rights reserved.
// A parser for a MPEG Transport Stream
// Implementation
#include "MPEG2TransportStreamParser.hh"
#include "FileSink.hh"
#include <time.h> // for time_t
Boolean MPEG2TransportStreamParser
::processStreamPacket(PIDState_STREAM* pidState, Boolean pusi, unsigned numDataBytes) {
#ifdef DEBUG_CONTENTS
extern StreamType StreamTypes[];
fprintf(stderr, "\t%s stream (stream_type 0x%02x)\n",
StreamTypes[pidState->stream_type].description, pidState->stream_type);
#endif
do {
MPEG2TransportStreamDemuxedTrack* streamSource = pidState->streamSource;
if (streamSource == NULL) {
// There's no source for this track; just skip the data:
skipBytes(numDataBytes);
break;
}
if (!streamSource->isCurrentlyAwaitingData()) {
// Wait until the source next gets read from. (The parsing will continue then.)
return False;
}
// If the data begins with a PES header, parse it first
unsigned pesHeaderSize = 0;
if (pusi && pidState->stream_type != 0x05/*these special private streams don't have PES hdrs*/) {
pesHeaderSize = parsePESHeader(pidState, numDataBytes);
if (pesHeaderSize == 0) break; // PES header parsing failed
}
// Deliver the data:
unsigned numBytesToDeliver = numDataBytes - pesHeaderSize;
if (numBytesToDeliver > streamSource->maxSize()) {
streamSource->frameSize() = streamSource->maxSize();
streamSource->numTruncatedBytes() = numBytesToDeliver - streamSource->maxSize();
} else {
streamSource->frameSize() = numBytesToDeliver;
streamSource->numTruncatedBytes() = 0;
}
getBytes(streamSource->to(), streamSource->frameSize());
skipBytes(streamSource->numTruncatedBytes());
double pts = pidState->lastSeenPTS == 0.0 ? fLastSeenPCR : pidState->lastSeenPTS;
streamSource->presentationTime().tv_sec = (time_t)pts;
streamSource->presentationTime().tv_usec = int(pts*1000000.0)%1000000;
FramedSource::afterGetting(streamSource); // completes delivery
} while (0);
return True;
}
static Boolean isSpecialStreamId[0x100];
unsigned MPEG2TransportStreamParser
::parsePESHeader(PIDState_STREAM* pidState, unsigned numDataBytes) {
static Boolean haveInitializedIsSpecialStreamId = False;
if (!haveInitializedIsSpecialStreamId) {
for (unsigned i = 0; i < 0x100; ++i) isSpecialStreamId[i] = False;
isSpecialStreamId[0xBC] = True; // program_stream_map
isSpecialStreamId[0xBE] = True; // padding_stream
isSpecialStreamId[0xBF] = True; // private_stream_2
isSpecialStreamId[0xF0] = True; // ECM_stream
isSpecialStreamId[0xF1] = True; // EMM_stream
isSpecialStreamId[0xF2] = True; // DSMCC_stream
isSpecialStreamId[0xF8] = True; // ITU-T Rec. H.222.1 type E
isSpecialStreamId[0xFF] = True; // program_stream_directory
haveInitializedIsSpecialStreamId = True; // from now on
}
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\tPES Header:\n");
#endif
unsigned startPos = curOffset();
do {
u_int32_t startCodePlusStreamId = get4Bytes();
if ((startCodePlusStreamId&0xFFFFFF00) != 0x00000100) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Bad start code: 0x%06x\n",
pidState->PID, numDataBytes, startCodePlusStreamId>>8);
#endif
break;
}
u_int8_t stream_id = startCodePlusStreamId&0xFF;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tstream_id: 0x%02x; PES_packet_length: %d\n",
stream_id, get2Bytes());
#else
skipBytes(2);
#endif
if (!isSpecialStreamId[stream_id]) {
u_int16_t flags = get2Bytes();
if ((flags&0xC000) != 0x8000) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Bad flags: 0x%04x\n",
pidState->PID, numDataBytes, flags);
#endif
break;
}
u_int8_t PTS_DTS_flags = (flags&0x00C0)>>6;
Boolean ESCR_flag = (flags&0x0020) != 0;
Boolean ES_rate_flag = (flags&0x0010) != 0;
Boolean DSM_trick_mode_flag = (flags&0x0008) != 0;
Boolean additional_copy_info_flag = (flags&0x0004) != 0;
Boolean PES_CRC_flag = (flags&0x0002) != 0;
Boolean PES_extension_flag = (flags&0x0001) != 0;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tflags: 0x%04x (PTS_DTS:%d; ESCR:%d; ES_rate:%d; DSM_trick_mode:%d; additional_copy_info:%d; PES_CRC:%d; PES_extension:%d)\n",
flags, PTS_DTS_flags, ESCR_flag, ES_rate_flag, DSM_trick_mode_flag, additional_copy_info_flag, PES_CRC_flag, PES_extension_flag);
#endif
u_int8_t PES_header_data_length = get1Byte();
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tPES_header_data_length: %d\n", PES_header_data_length);
#endif
if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) {
// Begin with a PTS:
u_int8_t first8PTSBits = get1Byte();
u_int32_t last32PTSBits = get4Bytes();
if ((first8PTSBits&0xF1) != ((PTS_DTS_flags<<4)|0x01) ||
(last32PTSBits&0x00010001) != 0x00010001) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Bad PTS bits: 0x%02x,0x%08x\n",
pidState->PID, numDataBytes, first8PTSBits, last32PTSBits);
#endif
break;
}
u_int32_t ptsUpper32 = ((first8PTSBits&0x0E)<<28) | ((last32PTSBits&0xFFFE0000)>>3) | ((last32PTSBits&0x0000FFFC)>>2);
u_int8_t ptsLowBit = (last32PTSBits&0x00000002)>>1;
double PTS = ptsUpper32/45000.0;
if (ptsLowBit) PTS += 1/90000.0;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tPTS: 0x%02x%08x => 0x%08x+%d => %.10f\n",
first8PTSBits, last32PTSBits, ptsUpper32, ptsLowBit, PTS);
#endif
// Record this PTS:
pidState->lastSeenPTS = PTS;
}
if (PTS_DTS_flags == 3) {
// Continue with a DTS:
u_int8_t first8DTSBits = get1Byte();
u_int32_t last32DTSBits = get4Bytes();
if ((first8DTSBits&0x11) != 0x11 ||
(last32DTSBits&0x00010001) != 0x00010001) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Bad DTS bits: 0x%02x,0x%08x\n",
pidState->PID, numDataBytes, first8DTSBits, last32DTSBits);
#endif
break;
}
u_int32_t dtsUpper32 = ((first8DTSBits&0x0E)<<28) | ((last32DTSBits&0xFFFE0000)>>3) | ((last32DTSBits&0x0000FFFC)>>2);
u_int8_t dtsLowBit = (last32DTSBits&0x00000002)>>1;
double DTS = dtsUpper32/45000.0;
if (dtsLowBit) DTS += 1/90000.0;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tDTS: 0x%02x%08x => 0x%08x+%d => %.10f\n",
first8DTSBits, last32DTSBits, dtsUpper32, dtsLowBit, DTS);
#endif
}
if (ESCR_flag) {
// Skip over the ESCR
skipBytes(6);
}
if (ES_rate_flag) {
// Skip over the ES_rate
skipBytes(6);
}
if (DSM_trick_mode_flag) {
// Skip over this
skipBytes(1);
}
if (additional_copy_info_flag) {
// Skip over this
skipBytes(1);
}
if (PES_CRC_flag) {
// Skip over this
skipBytes(2);
}
if (PES_extension_flag) {
u_int8_t flags = get1Byte();
Boolean PES_private_data_flag = (flags&0x80) != 0;
Boolean pack_header_field_flag = (flags&0x40) != 0;
Boolean program_packet_sequence_counter_flag = (flags&0x20) != 0;
Boolean P_STD_buffer_flag = (flags&0x10) != 0;
Boolean PES_extension_flag_2 = (flags&0x01) != 0;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\tPES_extension: flags: 0x%02x (PES_private_data:%d; pack_header_field:%d; program_packet_sequence_counter:%d; P_STD_buffer:%d; PES_extension_2:%d\n",
flags, PES_private_data_flag, pack_header_field_flag, program_packet_sequence_counter_flag, P_STD_buffer_flag, PES_extension_flag_2);
#endif
if (PES_private_data_flag) {
// Skip over this
skipBytes(16);
}
if (pack_header_field_flag) {
// Skip over this
skipBytes(1 + 12); // "pack_header()" is 12 bytes in size
}
if (program_packet_sequence_counter_flag) {
// Skip over this
skipBytes(2);
}
if (P_STD_buffer_flag) {
// Skip over this
skipBytes(2);
}
if (PES_extension_flag_2) {
u_int8_t PES_extension_field_length = get1Byte()&0x7F;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\t\tPES_extension_field_length: %d\n", PES_extension_field_length);
#endif
skipBytes(PES_extension_field_length);
}
}
// Make sure that the number of header bytes parsed is consistent with "PES_header_data_length"
// (and skip over any remasining 'stuffing' bytes):
if (curOffset() - startPos > 9 + PES_header_data_length) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Error: Parsed %d PES header bytes; expected %d (based on \"PES_header_data_length\": %d)\n",
pidState->PID, numDataBytes, curOffset() - startPos, 9 + PES_header_data_length,
PES_header_data_length);
#endif
break;
}
skipBytes(9 + PES_header_data_length - (curOffset() - startPos)); // >= 0
}
unsigned PESHeaderSize = curOffset() - startPos;
#ifdef DEBUG_CONTENTS
fprintf(stderr, "\t\t\t=> PES header size: %d\n", PESHeaderSize);
#endif
if (PESHeaderSize > numDataBytes) {
#ifdef DEBUG_ERRORS
fprintf(stderr, "MPEG2TransportStreamParser::parsePESHeader(0x%02x, %d): Error: PES header size %d is larger than the number of bytes available (%d)\n",
pidState->PID, numDataBytes, PESHeaderSize, numDataBytes);
#endif
break;
}
return PESHeaderSize;
} while (0);
// An error occurred. Skip over any remaining bytes in the packet:
int numBytesLeft = numDataBytes - (curOffset() - startPos);
if (numBytesLeft > 0) skipBytes((unsigned)numBytesLeft);
return 0;
}
//########## PIDState_STREAM implementation ##########
PIDState_STREAM::PIDState_STREAM(MPEG2TransportStreamParser& parser,
u_int16_t pid, u_int16_t programNumber, u_int8_t streamType)
: PIDState(parser, pid, STREAM),
program_number(programNumber), stream_type(streamType), lastSeenPTS(0.0) {
// Create the 'source' and 'sink' objects for this track, and 'start playing' them:
streamSource = new MPEG2TransportStreamDemuxedTrack(parser, pid);
char fileName[100];
extern StreamType StreamTypes[];
StreamType& st = StreamTypes[streamType]; // alias
sprintf(fileName, "%s-0x%04x-0x%04x%s",
st.dataType == StreamType::AUDIO ? "AUDIO" :
st.dataType == StreamType::VIDEO ? "VIDEO" :
st.dataType == StreamType::DATA ? "DATA" :
st.dataType == StreamType::TEXT ? "TEXT" :
"UNKNOWN",
program_number, pid, st.filenameSuffix);
fprintf(stderr, "Creating new output file \"%s\"\n", fileName);
streamSink = FileSink::createNew(parser.envir(), fileName);
streamSink->startPlaying(*streamSource, NULL, NULL);
}
PIDState_STREAM::~PIDState_STREAM() {
Medium::close(streamSink);
Medium::close(streamSource);
}