/**********
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-2026 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);
}
