//////////////////////////////////////////////////////////////////////////
//
// Demux class
// bbDemux by : Brent Beyler
// Created by : Varol Okan
// Date       : Dec 01 2006
// 
// This class is derived off of Brent Beylers bbDemux version 1.9.
// It was modified such that it could either be used as a stand alone 
// application or as a library.
//
//
//
/////////////////////////////////////////////////////////////////////////

/*************************************************************************
    bbDMUX by Brent Beyeler, [beyeler ( at ) home dot com]
*************************************************************************/

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include "demux.h"

#ifdef MAKE_EXE
int main ( int argc, char *argv[] )
{
  Mpeg2::Demux bbDemux;

  bbDemux.main ( argc, argv );

  return 0;
}
#endif

namespace Mpeg2
{

Demux::Stream::Stream ( )
{
  type          = UndefContainer;
  iFileSize     = 0;
  pFileName     = NULL;
  pLastMessage  = NULL;
  iErrorMessage = 0
}

Demux::Stream::~Stream ( )
{
  setLastMessage ( NULL );
}

void Demux::Stream::setLastMessage ( const char *pMessage )
{
  if ( pLastMessage )
    free ( pLastMessage );
  pLastMessage = NULL;
  if ( pMessage )
    pLastMessage = strdup ( pMessage );
}

Demux::Demux ( )
  : BufferSize ( 32768 )
{
  m_pBuffer = new unsigned char [ BufferSize ];
  m_pOutputFileName = NULL;
}

Demux::~Demux ( )
{
  delete []m_pBuffer;
  m_pBuffer = NULL;
}

void Demux::debugOut ( const char *pString, ... )
{
  // For the library version we are only interested in the generated structures
  // and do not want to print to stdout.
  // at this point m_cBuffer should have file/func/line or should be empty.
  char cBuffer [ 1024 ];
  va_list theList;
  va_start ( theList, pString );
  vsprintf((char *)&cBuffer, pString, theList);
  //  vsprintf((char *)&(cBuffer[ strlen(cBuffer) ]), pString, theList);
  va_end ( theList );
#ifdef MAKE_EXE
  printf ( cBuffer );
#else // Only store the last message so we can check in case of an error.
  m_streamInfo.setLastMessage ( (const char *) cBuffer );
#endif
}

int Demux::demuxAllStreams ( )
{

  return 0;
}

Demux::Stream *Demux::getStreamInfo ( )
{
  return &m_streamInfo;
}

bool Demux::writeBuffer()
{
  if (fwrite(m_pBuffer, sizeof(unsigned char), m_iBufferIdx + 1, m_pOutputFile) != (unsigned int)m_iBufferIdx + 1)
  {
    debugOut("Error writing to output file %s.", m_pOutputFileName ? m_pOutputFileName : "NULL" );
    m_streamInfo.iErrorCode = -1;
    return false;
  }
  m_iBufferIdx = -1;
  return true;
}

bool Demux::flushBuffer()
{
  if ( m_iBufferIdx >= 0 )
    return writeBuffer ( );
  return true;
}

const char *Demux::main ( int argc, char *argv[] )
{
  unsigned long i, j, k, PES_packet_length, PES_header_data_length;
  unsigned long PTS_DTS_flags, ESCR_flag, ES_rate_flag, DSM_trick_mode_flag;
  unsigned long additional_copy_info_flag, PES_CRC_flag, PES_extension_flag;
  unsigned long PES_private_data_flag, pack_header_field_flag;
  unsigned long program_packet_sequence_counter_flag, PSTD_buffer_flag;
  unsigned long PES_extension_flag_2, PES_extension_field_length;
  unsigned long bytes_used, encrypted;
  int substream_to_get, stream_to_get, stream_id, substream_id;
  bool firsttime = true;
  unsigned long stream_packets[256], stream_bytes[256], pack_packets;
  unsigned long system_packets, mpeg_type;
  unsigned long ac3_bytes[8], dts_bytes[8], pcm_bytes[16], subpic_bytes[32];;
  unsigned long substream_packets[256], substream_bytes[256];
  bool streams_found[256];
  bool stream_encrypted[256];
  bool substream_encrypted[256];
  bool substreams_found[256];
  bool mpeg2;
  char tmpStr[256];

  for (i = 0; i < 256; i++)
  {
    streams_found[i] = false;
    stream_packets[i] = 0;
    stream_bytes[i] = 0;
    substreams_found[i] = false;
    stream_encrypted[i] = false;
    substream_encrypted[i] = false;
    substream_packets[i] = 0;
    substream_bytes[i] = 0;
  }
  for (i = 0; i < 8; i++)
  {
    ac3_bytes[i] = 0;
    dts_bytes[i] = 0;
  }
  for (i = 0; i < 16; i++)
    pcm_bytes[i] = 0;
  for (i = 0; i < 32; i++)
    subpic_bytes[i] = 0;
  pack_packets = 0;
  system_packets = 0;
  m_pOutputFile = NULL;
  stream_to_get = -1;
  substream_to_get = -1;
  mpeg2 = false;
  m_iBufferIdx = -1;

  debugOut("bbDMUX - version 1.9, by Brent Beyeler (beyeler@home.com)\n");
  debugOut("  speed increases by, Apachez and Christian Vogelgsang\n\n");
  if (argc < 2 || argc == 3)
  {
    debugOut("bbDUMX is an MPEG transport and program stream de-multiplexor\n\n");
    debugOut("Usage: bbDMUX  MPEG filename  <stream id/PID  saved name>  <substream id>\n\n");
    debugOut("Program stream examples:\n");
    debugOut("  To get a list of the stream and substream id's present in an MPEG file\n");
    debugOut("    bbDMUX  testfile.mpg\n\n");
    debugOut("  To save a video stream (id 0xe0) to the file video.m2v\n");
    debugOut("    bbDMUX  testfile.m2p  0xe0  video.m2v\n\n");
    debugOut("  To save an audio stream (id 0xc0) to the file audio.m2a\n");
    debugOut("    bbDMUX  testfile.mpg  0xc0  audio.m2a\n\n");
    debugOut("  To save an AC3 audio stream (substream 0x80) to the file audio.ac3\n");
    debugOut("    bbDMUX  testfile.vob  0xbd  audio.ac3  0x80\n\n");
    debugOut("Transport stream example:\n");
    debugOut("  To save an video stream (PID = 0x001B) to the file video.m2v\n");
    debugOut("    bbDMUX  testfile.vob  0x001B  video.m2v\n");
    return "Printing Usage\n";
  }

  m_bitStream.initGetbits ( argv[1] );
  strcpy ( tmpStr, argv[1] );
  m_bVobFlag = (strcasestr(tmpStr, ".vob") != NULL);
  mpeg_type = 0;
  while (!mpeg_type)
  {
    if ( m_bitStream.lookAhead(32) == PACK_START_CODE)
      mpeg_type = 1;
    else
      if ( m_bitStream.lookAhead(8) == TRANSPORT_SYNC_BYTE)
        mpeg_type = 2;
      else
        m_bitStream.getBits ( 8 );
  }
  if (!mpeg_type)
    return "\nUnknown file type.\n";

  if (argc > 2)
  {
    sscanf(argv[2], "0x%x", &i);
    if (mpeg_type == 1)
    {
      if (i < 0 || i > 0xFF)
        return "Stream ID must be in the range 0..0xFF\n";
    }
    else
    {
      if (i < 0 || i > 0x1FFF)
        return "PID must be in the range 0..0x1FFF\n";
    }
    stream_to_get = i;
    if (argc > 3)
    {
      m_pOutputFile = fopen(argv[3], "wb");
      if (!m_pOutputFile)
        return "Unable to open file for output\n";

      m_pOutputFileName = strdup ( argv[3] );
    }
    else
      return "An output filename must be specified\n";

    if (argc > 4)
    {
      sscanf(argv[4], "0x%x", &i);
      if (i < 0 || i > 0xFF)
        return "Substream ID must be in the range 0..0xFF\n";
      substream_to_get = i;
      m_bVobFlag = true;
    }
  }
  else
    if (mpeg_type == 1)
      debugOut("Scanning for stream id's, press control-c to quit ...\n");
    else
      debugOut("Scanning for PID's, press control-c to quit ...\n");

  if (m_pOutputFile)
  {
    if (mpeg_type == 1)
    {
      if ((m_bVobFlag) && (stream_to_get == PRIVATE_STREAM_1) && (substream_to_get > -1))
        debugOut("Saving stream 0x%02X, substream 0x%02X into file %s...\n", stream_to_get, substream_to_get, argv[3]);
      else
        debugOut("Saving stream 0x%02X into file %s...\n", stream_to_get, argv[3]);
    }
    else
      debugOut("Saving PID stream 0x%04X into file %s...\n", stream_to_get, argv[3]);
  }

  if (mpeg_type == 2)
  {
    transportStream ( argv[1], argc, stream_to_get );
    return NULL;
  }

  do
  {
    i = m_bitStream.getBits ( 32 );
//nextpass:
    switch (i)
    {
      case PACK_START_CODE:
        pack_packets++;
        if (firsttime)
        {
          mpeg2 = m_bitStream.lookAhead(2) == 1; // check if MPEG-1 or MPEG-2
          if (mpeg2)
            debugOut("\nFile %s is an MPEG-2 Program Stream\n\n", argv[1]);
          else
            debugOut("\nFile %s is an MPEG-1 Program Stream\n\n", argv[1]);
          firsttime = false;
        }
        if (mpeg2)
        {
          m_bitStream.getBits ( 32 );   // MPEG2 pack header
          m_bitStream.getBits ( 32 );
          m_bitStream.getBits ( 13 );
          i = m_bitStream.getBits ( 3 );
          for (j = 0; j < i; j++)
            m_bitStream.getBits ( 8 );  // stuffing bytes
        }
        else
        {
          m_bitStream.getBits ( 32 );   // MPEG1 pack header
          m_bitStream.getBits ( 32 );
        }
        break;

      case SYSTEM_HEADER_START_CODE:
        system_packets++;
        m_bitStream.getBits ( 32 );   // system header
        m_bitStream.getBits ( 32 );
        while ( m_bitStream.lookAhead ( 1 ) == 1 )
          m_bitStream.getBits ( 24 ); // P-STD info
        break;

      case MPEG_PROGRAM_END_CODE:
        break;

      default:
        if ((i >> 8) != PACKET_START_CODE_PREFIX)
//        {
          break;
//#ifdef SHOW_RESYNCHS
//          if (argc > 3)
//          {
//            debugOut("\nUnknown bits in stream = 0x%08X at byte %.0f\n", i, bitcount() / 8 - 4);
//            debugOut("\nAttempting resynch ... ");
//          }
//#endif
//          if (seek_sync(PACKET_START_CODE_PREFIX, 24))
//          {
//            i = 0x00000100 | getbits(8);
//#ifdef SHOW_RESYNCHS
//            if (argc > 3)
//              debugOut("succesfully resynched\n");
//#endif
//            goto nextpass;
//          }
//#ifdef SHOW_RESYNCHS
//          if (argc > 3)
//            debugOut("could not resynch\n");
//          else
//#endif
//            debugOut("\nUnknown bits in stream = 0x%08X at byte %.0f\n", i, bitcount() / 8 - 4);
//          if (m_pOutputFile)
//          {
//            flush_buffer();
//            fclose(m_pOutputFile);
//          }
//          return 1;
//        }
        stream_id = i & 0x000000FF;
        stream_packets[stream_id]++;

        PES_packet_length = m_bitStream.getBits(16);
        encrypted = 0;
        switch (stream_id)
        {
          case PROGRAM_STREAM_MAP:
          case ECM_STREAM:
          case EMM_STREAM:
          case PROGRAM_STREAM_DIRECTORY:
          case DSMCC_STREAM:
          case ITUTRECH222TYPEE_STREAM:
          case PRIVATE_STREAM_2:
          case PADDING_STREAM:
            if (stream_id == stream_to_get)
            {
              for (j = 0; j < PES_packet_length; j++)
              {
                m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                if (m_iBufferIdx == BufferSize - 1)
                  writeBuffer();
              }
            }
            for (j = 0; j < PES_packet_length; j++)
              m_bitStream.getBits(8);
            stream_bytes[stream_id] += j;
            break;

          default:
            bytes_used = 0;
            if (!mpeg2)
            {
              /* flush stuffing bytes */
              while ( m_bitStream.lookAhead ( 8 ) == 0xff)
              {
                m_bitStream.getBits ( 8 );
                bytes_used++;
              }

              if ( m_bitStream.lookAhead ( 2 ) == 1)
              {
                /* Get STD buffer size */
                m_bitStream.getBits ( 16 );
                bytes_used += 2;
              }

              if ( m_bitStream.lookAhead ( 4 ) == 2)
              {
                /* Get presentation time stamp and flag it as present in packet*/
                m_bitStream.getBits ( 32 );
                m_bitStream.getBits (  8 );
                bytes_used += 5;
              }
              else
                if ( m_bitStream.lookAhead ( 4 ) == 3)
                {
                  /* Get presentation time stamp and decoding time stamp */
                  m_bitStream.getBits ( 32 );
                  m_bitStream.getBits (  8 );
                  m_bitStream.getBits ( 32 );
                  m_bitStream.getBits (  8 );
                  bytes_used += 10;
                }
                else
                {
                  if ( m_bitStream.getBits ( 8 ) != 0x0f )
                    return "Invalid bits in MPEG-1 file ";//argv[1];
                  bytes_used++;
                }
              stream_bytes[stream_id] += PES_packet_length - bytes_used;
              if (stream_id == stream_to_get)
              {
                for (j = 0; j < PES_packet_length - bytes_used; j++)
                {
                  m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                  if (m_iBufferIdx == BufferSize - 1)
                     writeBuffer();
                }
              }
              else
                for (j = 0; j < PES_packet_length - bytes_used; j++)
                  m_bitStream.getBits ( 8 );
            }
            else
            {
              m_bitStream.getBits ( 2 );
              encrypted = m_bitStream.getBits ( 2 );   // PES_scrambling_control
              m_bitStream.getBits ( 4 );
              PTS_DTS_flags = m_bitStream.getBits ( 2 );
              ESCR_flag     = m_bitStream.get1Bit ( );
              ES_rate_flag  = m_bitStream.get1Bit ( );
              DSM_trick_mode_flag = m_bitStream.get1Bit( );
              additional_copy_info_flag = m_bitStream.get1Bit ( );
              PES_CRC_flag = m_bitStream.get1Bit ( );
              PES_extension_flag = m_bitStream.get1Bit ( );
              PES_header_data_length = m_bitStream.getBits ( 8 );
              if (PTS_DTS_flags > 1)
              {
                m_bitStream.getBits ( 32 );   // PTS stamp
                m_bitStream.getBits (  8 );
                bytes_used += 5;
              }
              if ( PTS_DTS_flags > 2 )
              {
                m_bitStream.getBits ( 32 );   // DTS stamp
                m_bitStream.getBits (  8 );
                bytes_used += 5;
              }

              if (ESCR_flag == 1)
              {
                m_bitStream.getBits ( 32 );
                m_bitStream.getBits ( 16 );
                bytes_used += 6;
              }

              if (ES_rate_flag == 1)
              {
                m_bitStream.getBits ( 24 );
                bytes_used += 3;
              }

              if (DSM_trick_mode_flag == 1)
              {
                m_bitStream.getBits ( 8 );
                bytes_used++;
              }

              if (additional_copy_info_flag == 1)
              {
                m_bitStream.getBits ( 8 );
                bytes_used++;
              }

              if (PES_CRC_flag == 1)
              {
                m_bitStream.getBits ( 16 );
                bytes_used += 2;
              }

              if (PES_extension_flag == 1)
              {
                PES_private_data_flag  = m_bitStream.get1Bit ( );
                pack_header_field_flag = m_bitStream.get1Bit ( );
                program_packet_sequence_counter_flag = m_bitStream.get1Bit ( );
                PSTD_buffer_flag = m_bitStream.get1Bit ( );
                m_bitStream.getBits ( 3 );
                PES_extension_flag_2 = m_bitStream.get1Bit ( );
                bytes_used++;

                if (PES_private_data_flag == 1)
                {
                  for (j = 0; j < 128; j++)
                    m_bitStream.getBits ( 8 );
                  bytes_used += 128;
                }

                if ( pack_header_field_flag == 1 )
                  return "    pack header field flag value not allowed in program streams\n";

                if ( program_packet_sequence_counter_flag == 1 )
                {
                  m_bitStream.getBits ( 16 );
                  bytes_used += 2;
                }

                if ( PSTD_buffer_flag == 1 ) 
                {
                  m_bitStream.getBits ( 16 );
                  bytes_used += 2;
                }

                if (PES_extension_flag_2 == 1)
                {
                  m_bitStream.get1Bit ( );
                  PES_extension_field_length = m_bitStream.getBits ( 7 );
                  bytes_used++;
                  for (j = 0; j < PES_extension_field_length; j++)
                  {
                    m_bitStream.getBits ( 8 );
                    bytes_used++;
                  }
                }
              }
              for (j = 0; j < PES_header_data_length - bytes_used; j++)
                m_bitStream.getBits ( 8 );

              substream_id = m_bitStream.getBits ( 8 );
              stream_bytes[stream_id] += PES_packet_length - PES_header_data_length - 3;
              if ((stream_id == PRIVATE_STREAM_1) && (m_bVobFlag))
              {
                substream_packets[substream_id]++;
                j = PES_packet_length - PES_header_data_length - 3;
                substream_bytes[substream_id] += j;
                if ((substream_id >= SUBSTREAM_AC3_0) && (substream_id <= SUBSTREAM_AC3_8))
                  ac3_bytes[substream_id - SUBSTREAM_AC3_0] += j - 4;
                if ((substream_id >= SUBSTREAM_DTS_0) && (substream_id <= SUBSTREAM_DTS_8))
                  dts_bytes[substream_id - SUBSTREAM_DTS_0] += j - 4;
                if ((substream_id >= SUBSTREAM_PCM_0) && (substream_id <= SUBSTREAM_PCM_F))
                  pcm_bytes[substream_id - SUBSTREAM_PCM_0] += j - 7;
                if ((substream_id >= SUBSTREAM_SUBPIC_0) && (substream_id <= SUBSTREAM_SUBPIC_1F))
                  subpic_bytes[substream_id - SUBSTREAM_SUBPIC_0] += j - 1;
              }
              if (stream_id == stream_to_get)
              {
                if ((stream_id == PRIVATE_STREAM_1) && (m_bVobFlag) && (substream_to_get > -1))
                {
                  if (substream_id == substream_to_get)
                  {
                    if ((substream_id >= SUBSTREAM_AC3_0) && (substream_id <= SUBSTREAM_AC3_8))
                    {
                      m_bitStream.getBits ( 24 );  // discard the 3 AC3 info bytes
                      j = 4;
                    }
                    else
                      if ((substream_id >= SUBSTREAM_DTS_0) && (substream_id <= SUBSTREAM_DTS_8))
                      {
                        m_bitStream.getBits ( 24 );  // discard the 3 DTS info bytes
                        j = 4;
                      }
                      else
                        if ((substream_id >= SUBSTREAM_PCM_0) && (substream_id <= SUBSTREAM_PCM_F))
                        {
                          m_bitStream.getBits ( 24 );  // discard the 6 PCM info bytes
                          m_bitStream.getBits ( 24 );
                          j = 7;
                        }
                        else
                          if ((substream_id >= SUBSTREAM_SUBPIC_0) && (substream_id <= SUBSTREAM_SUBPIC_1F))
                            j = 1;
                          else
                          {
                            m_pBuffer[++m_iBufferIdx] = (unsigned char)substream_id;
                            j = 1;
                          }

                    for (k = 0; k < PES_packet_length - PES_header_data_length - 3 - j; k++)
                    {
                      m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                      if (m_iBufferIdx == BufferSize - 1)
                        writeBuffer();
                    }
                  }
                  else
                    for (j = 0; j < PES_packet_length - PES_header_data_length - 4; j++)
                      m_bitStream.getBits ( 8 );
                }
                else
                {
                  m_pBuffer[++m_iBufferIdx] = (unsigned char)substream_id;
                  if (m_iBufferIdx == BufferSize - 1)
                    writeBuffer ( );
                  for (j = 0; j < PES_packet_length - PES_header_data_length - 4; j++)
                  {
                    m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                    if (m_iBufferIdx == BufferSize - 1)
                      writeBuffer();
                  }
                }
              }
              else
                for (j = 0; j < PES_packet_length - PES_header_data_length - 4; j++)
                  m_bitStream.getBits ( 8 );
            }
            break;
        }
        if (argc == 2)
        {
          if ((!streams_found[stream_id]) || ((m_bVobFlag) && (stream_id == PRIVATE_STREAM_1) && (!substreams_found[substream_id])))
          {
            switch (stream_id)
            {
              case PROGRAM_STREAM_MAP:
                debugOut("Found stream id 0x%02X  = Program Stream Map\n", stream_id);
                break;
              case PRIVATE_STREAM_1:
                if (m_bVobFlag)
                {
                  if ((substream_id >= SUBSTREAM_AC3_0) && (substream_id <= SUBSTREAM_AC3_8))
                    debugOut("Found stream id 0x%02X  = Private Stream 1, substream 0x%02X, AC3 Audio stream %u\n", stream_id,  substream_id, substream_id - SUBSTREAM_AC3_0);
                  else
                    if ((substream_id >= SUBSTREAM_DTS_0) && (substream_id <= SUBSTREAM_DTS_8))
                      debugOut("Found stream id 0x%02X  = Private Stream 1, substream 0x%02X, DTS Audio stream %u\n", stream_id, substream_id, substream_id - SUBSTREAM_DTS_0);
                    else
                      if ((substream_id >= SUBSTREAM_PCM_0) && (substream_id <= SUBSTREAM_PCM_F))
                        debugOut("Found stream id 0x%02X  = Private Stream 1, substream 0x%02X, PCM Audio stream %u\n", stream_id, substream_id, substream_id - SUBSTREAM_PCM_0);
                      else
                        if ((substream_id >= SUBSTREAM_SUBPIC_0) && (substream_id <= SUBSTREAM_SUBPIC_1F))
                          debugOut("Found stream id 0x%02X  = Private Stream 1, substream 0x%02X, Subpicture stream %u\n", stream_id, substream_id, substream_id - SUBSTREAM_SUBPIC_0);
                        else
                          debugOut("Found stream id 0x%02X  = Private Stream 1, substream 0x%02X\n", stream_id, substream_id);
                  substreams_found[substream_id] = true;
                }
                else
                  debugOut("Found stream id 0x%02X  = Private Stream 1\n", stream_id);
                break;
              case PRIVATE_STREAM_2:
                debugOut("Found stream id 0x%02X  = Private Stream 2\n", stream_id);
                break;
              case ECM_STREAM:
                debugOut("Found stream id 0x%02X  = ECM Stream\n", stream_id);
                break;
              case EMM_STREAM:
                debugOut("Found stream id 0x%02X  = EMM Stream\n", stream_id);
                break;
              case PROGRAM_STREAM_DIRECTORY:
                debugOut("Found stream id 0x%02X  = Program Stream Directory\n", stream_id);
                break;
              case DSMCC_STREAM:
                debugOut("Found stream id 0x%02X  = DSMCC Stream\n", stream_id);
                break;
              case ITUTRECH222TYPEE_STREAM:
                debugOut("Found stream id 0x%02X  = ITU-T Rec. H.222.1 type E\n", stream_id);
                break;
              case PADDING_STREAM:
                debugOut("Found stream id 0x%02X  = Padding Stream\n", stream_id);
                break;
              default:
                if ((stream_id >= 0xE0) && (stream_id <= 0xEF))
                  debugOut("Found stream id 0x%02X  = Video Stream %d\n", stream_id, stream_id - 0xE0);
                else
                  if ((stream_id >= 0xC0) && (stream_id <= 0xDF))
                    debugOut("Found stream id 0x%02X  = MPEG Audio Stream %d\n", stream_id, stream_id - 0xC0);
                  else
                    debugOut("Found stream id 0x%02X  = other stream\n", stream_id);
            }
            streams_found[stream_id] = true;
          }
          if ((stream_id == PRIVATE_STREAM_1) && (m_bVobFlag))
          {
            if (encrypted)
            {
              if (!substream_encrypted[substream_id])
              {
                debugOut("Stream id 0x%02X, substream 0x%02X is encrypted\n", stream_id, substream_id);
                substream_encrypted[substream_id] = true;
              }
            }
          }
          else
          {
            if (encrypted)
            {
              if (!stream_encrypted[stream_id])
              {
                debugOut("Stream id 0x%02X is encrypted\n", stream_id);
                stream_encrypted[stream_id] = true;
              }
            }
          }
        }
    }
  } while ((i != MPEG_PROGRAM_END_CODE) && ( ! m_bitStream.endBitStream ( ) ) );

  debugOut("\nSummary:\n");
  debugOut("\nMPEG Packs = %d\n", pack_packets);
  if (system_packets)
    debugOut("System headers = %d\n", system_packets);
  for (i = 0; i < 256; i++)
  {
    if (stream_packets[i])
    {
      switch (i)
      {
        case PROGRAM_STREAM_MAP:
          debugOut("Program Stream Map packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case PRIVATE_STREAM_1:
          debugOut("Private Stream 1 packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          if (m_bVobFlag)
          {
            for (j = 0; j < 256; j++)
            {
              if (substream_packets[j])
              {
                if ((j >= SUBSTREAM_AC3_0) && (j <= SUBSTREAM_AC3_8))
                  debugOut("    AC3 Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_AC3_0, substream_packets[j], ac3_bytes[j - SUBSTREAM_AC3_0]);
                else
                  if ((j >= SUBSTREAM_DTS_0) && (j <= SUBSTREAM_DTS_8))
                    debugOut("    DTS Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_DTS_0, substream_packets[j], dts_bytes[j - SUBSTREAM_DTS_0]);
                  else
                    if ((j >= SUBSTREAM_PCM_0) && (j <= SUBSTREAM_PCM_F))
                      debugOut("    PCM Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_PCM_0, substream_packets[j], pcm_bytes[j - SUBSTREAM_PCM_0]);
                    else
                      if ((j >= SUBSTREAM_SUBPIC_0) && (j <= SUBSTREAM_SUBPIC_1F))
                        debugOut("    Subpicture stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_SUBPIC_0, substream_packets[j], subpic_bytes[j - SUBSTREAM_SUBPIC_0]);
                      else
                        debugOut("    Substream 0x%02X packets = %u, total bytes = %u\n", j, substream_packets[j], substream_bytes[j]);
              }
            }
          }
          break;
        case PRIVATE_STREAM_2:
          debugOut("Private Stream 2 packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case ECM_STREAM:
          debugOut("ECM Stream packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case EMM_STREAM:
          debugOut("EMM Stream packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case PROGRAM_STREAM_DIRECTORY:
          debugOut("Program Stream Directory packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case DSMCC_STREAM:
          debugOut("DSMCC Stream packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case ITUTRECH222TYPEE_STREAM:
          debugOut("ITU-T Rec. H.222.1 type E packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        case PADDING_STREAM:
          debugOut("Padding Stream packets = %u, total bytes = %u\n", stream_packets[i], stream_bytes[i]);
          break;
        default:
          if ((i >= 0xE0) && (i <= 0xEF))
            debugOut("Video stream %d packets = %u, total bytes = %u\n", i - 0xE0, stream_packets[i], stream_bytes[i]);
          else
            if ((i >= 0xC0) && (i <= 0xDF))
              debugOut("MPEG Audio stream %d packets = %u, total bytes = %u\n", i - 0xC0, stream_packets[i], stream_bytes[i]);
            else
              debugOut("Other stream 0x%02X packets = %u, total bytes = %u\n", i, stream_packets[i], stream_bytes[i]);
      }
    }
  }
  m_bitStream.finishGetbits ( );
  if (m_pOutputFile)
  {
    flushBuffer ( );
    fclose ( m_pOutputFile );
  }
  return NULL;
}

const char *Demux::transportStream(char *fname, int argc, int PID_to_get)
{
  unsigned long i, j, k, PES_packet_length, PES_header_data_length;
  unsigned long adaptation_bytes;
  int payload_unit_start_indicator, PID;
  bool firsttime = true;
  unsigned long transport_packets, packet_data_bytes;
  stream_entry *streams[8192];

  for (i = 0; i < 8192; i++)
    streams[i] = NULL;
  transport_packets = 0;
  m_iBufferIdx = -1;

  do
  {
    i = m_bitStream.getBits ( 8 );
//nextpass:
    if (i == TRANSPORT_SYNC_BYTE)
    {
      transport_packets++;
      if (firsttime)
      {
        debugOut("\nFile %s is an MPEG-2 Transport Stream\n\n", fname);
        firsttime = false;
      }
      j = m_bitStream.getBits ( 3 );
      payload_unit_start_indicator = (j & 0x2) >> 1;

      PID = m_bitStream.getBits ( 13 );
      if (streams[PID] == NULL)
      {
        streams[PID] = (stream_entry*)malloc(sizeof(stream_entry));
        if (streams[PID] == NULL)
          return "Unable to allocate memory for stream entry.\n";

        streams[PID]->stream_id = 0;
        streams[PID]->substream_id = 0;
        streams[PID]->stream_packets = 0;
        streams[PID]->stream_bytes = 0;
        streams[PID]->stream_encrypted = 0;
        streams[PID]->stream_found = 0;
        streams[PID]->pcm_bytes = 0;
        streams[PID]->subpic_bytes = 0;
        streams[PID]->ac3_bytes = 0;
        streams[PID]->dts_bytes = 0;
      }
      streams[PID]->stream_packets++;
      m_bitStream.getBits ( 2 );
      i = m_bitStream.getBits ( 2 );
      m_bitStream.getBits ( 4 );
      if (i > 1)
      {
        j = m_bitStream.getBits ( 8 );
        for (k = 0; k < j; k++)
          m_bitStream.getBits ( 8 );
        adaptation_bytes = j + 1;
      }
      else
        adaptation_bytes = 0;
      if ((i == 1) || (i == 3))
      {
        packet_data_bytes = 184 - adaptation_bytes;
        if (packet_data_bytes > 0)
        {
          if (( m_bitStream.lookAhead ( 24 ) == 0x000001) && payload_unit_start_indicator && (PID > 0x0F))
          {
            i = m_bitStream.getBits ( 32 );
            packet_data_bytes -= 4;
            if ( (i >> 8) != PACKET_START_CODE_PREFIX )
              return "Packet start code not found.\n";

            streams[PID]->stream_id = i & 0x000000FF;
            PES_packet_length = m_bitStream.getBits ( 16 );
            packet_data_bytes -= 2;
            switch (streams[PID]->stream_id)
            {
              case PROGRAM_STREAM_MAP:
              case ECM_STREAM:
              case EMM_STREAM:
              case PROGRAM_STREAM_DIRECTORY:
              case DSMCC_STREAM:
              case ITUTRECH222TYPEE_STREAM:
              case PRIVATE_STREAM_2:
              case PADDING_STREAM:
                break;

              default:
                m_bitStream.getBits ( 2 );
                streams[PID]->stream_encrypted = m_bitStream.getBits ( 2 );   // PES_scrambling_control
                m_bitStream.getBits ( 12 );
                PES_header_data_length = m_bitStream.getBits ( 8 );
                packet_data_bytes -= (3 + PES_header_data_length);
                for (j = 0; j < PES_header_data_length; j++)
                  m_bitStream.getBits ( 8 );

                streams[PID]->substream_id = m_bitStream.getBits ( 8 );
                if ((streams[PID]->stream_id == PRIVATE_STREAM_1) && (m_bVobFlag))
                {
                  j = PES_packet_length - PES_header_data_length - 3;
                  if ((streams[PID]->substream_id >= SUBSTREAM_AC3_0) && (streams[PID]->substream_id <= SUBSTREAM_AC3_8))
                    streams[PID]->ac3_bytes += j - 4;
                  if ((streams[PID]->substream_id >= SUBSTREAM_DTS_0) && (streams[PID]->substream_id <= SUBSTREAM_DTS_8))
                    streams[PID]->dts_bytes += j - 4;
                  if ((streams[PID]->substream_id >= SUBSTREAM_PCM_0) && (streams[PID]->substream_id <= SUBSTREAM_PCM_F))
                    streams[PID]->pcm_bytes += j - 7;
                  if ((streams[PID]->substream_id >= SUBSTREAM_SUBPIC_0) && (streams[PID]->substream_id <= SUBSTREAM_SUBPIC_1F))
                    streams[PID]->subpic_bytes += j - 1;
                }

                if ((streams[PID]->stream_id == PRIVATE_STREAM_1) && (m_bVobFlag))
                {
                  if ((streams[PID]->substream_id >= SUBSTREAM_AC3_0) && (streams[PID]->substream_id <= SUBSTREAM_AC3_8))
                  {
                    m_bitStream.getBits ( 24 );  // discard the 3 AC3 info bytes
                    j = 4;
                  }
                  else
                    if ((streams[PID]->substream_id >= SUBSTREAM_DTS_0) && (streams[PID]->substream_id <= SUBSTREAM_DTS_8))
                    {
                      m_bitStream.getBits ( 24 );  // discard the 3 DTS info bytes
                      j = 4;
                    }
                    else
                      if ((streams[PID]->substream_id >= SUBSTREAM_PCM_0) && (streams[PID]->substream_id <= SUBSTREAM_PCM_F))
                      {
                        m_bitStream.getBits ( 24 );  // discard the 6 PCM info bytes
                        m_bitStream.getBits ( 24 );
                        j = 7;
                      }
                      else
                        if ((streams[PID]->substream_id >= SUBSTREAM_SUBPIC_0) && (streams[PID]->substream_id <= SUBSTREAM_SUBPIC_1F))
                          j = 1;
                        else
                        {
                          if (PID == PID_to_get)
                          {
                            m_pBuffer[++m_iBufferIdx] = (unsigned char)streams[PID]->substream_id;
                            if (m_iBufferIdx == BufferSize - 1)
                              writeBuffer();
                          }
                          j = 1;
                          streams[PID]->stream_bytes++;
                        }
                  packet_data_bytes -= j;
                }
                else
                {
                  if (PID == PID_to_get)
                  {
                    m_pBuffer[++m_iBufferIdx] = (unsigned char)streams[PID]->substream_id;
                    if (m_iBufferIdx == BufferSize - 1)
                      writeBuffer();
                  }
                  streams[PID]->stream_bytes++;
                  packet_data_bytes--;
                }
                break;
            }
            if (packet_data_bytes > 0)
            {
              if (PID == PID_to_get)
              {
                for (j = 0; j < packet_data_bytes; j++)
                {
                  m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                  if (m_iBufferIdx == BufferSize - 1)
                    writeBuffer();
                }
              }
              else
              {
                for (j = 0; j < packet_data_bytes; j++)
                  m_bitStream.getBits ( 8 );
              }
              streams[PID]->stream_bytes += j;
            }
          }
          else
          {
            if (PID == PID_to_get)
            {
              for (j = 0; j < packet_data_bytes; j++)
              {
                m_pBuffer[++m_iBufferIdx] = (unsigned char)m_bitStream.getBits ( 8 );
                if (m_iBufferIdx == BufferSize - 1)
                  writeBuffer();
              }
            }
            else
            {
              for (j = 0; j < packet_data_bytes; j++)
                m_bitStream.getBits ( 8 );
            }
            streams[PID]->stream_bytes += j;
          }
        }

        if ((argc == 2) && (!streams[PID]->stream_found))
        {
          switch (streams[PID]->stream_id)
          {
            case PROGRAM_STREAM_MAP:
              debugOut("Found PID 0x%04X, stream id 0x%02X = Program Stream Map\n", PID, streams[PID]->stream_id);
              break;
            case PRIVATE_STREAM_1:
              if (m_bVobFlag)
              {
                if ((streams[PID]->substream_id >= SUBSTREAM_AC3_0) && (streams[PID]->substream_id <= SUBSTREAM_AC3_8))
                  debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1, substream 0x%02X, AC3 Audio stream %u\n", PID, streams[PID]->stream_id,  streams[PID]->substream_id, streams[PID]->substream_id - SUBSTREAM_AC3_0);
                else
                  if ((streams[PID]->substream_id >= SUBSTREAM_DTS_0) && (streams[PID]->substream_id <= SUBSTREAM_DTS_8))
                    debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1, substream 0x%02X, DTS Audio stream %u\n", PID, streams[PID]->stream_id, streams[PID]->substream_id, streams[PID]->substream_id - SUBSTREAM_DTS_0);
                  else
                    if ((streams[PID]->substream_id >= SUBSTREAM_PCM_0) && (streams[PID]->substream_id <= SUBSTREAM_PCM_F))
                      debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1, substream 0x%02X, PCM Audio stream %u\n", PID, streams[PID]->stream_id, streams[PID]->substream_id, streams[PID]->substream_id - SUBSTREAM_PCM_0);
                    else
                      if ((streams[PID]->substream_id >= SUBSTREAM_SUBPIC_0) && (streams[PID]->substream_id <= SUBSTREAM_SUBPIC_1F))
                        debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1, substream 0x%02X, Subpicture stream %u\n", PID, streams[PID]->stream_id, streams[PID]->substream_id, streams[PID]->substream_id - SUBSTREAM_SUBPIC_0);
                      else
                        debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1, substream 0x%02X\n", PID, streams[PID]->stream_id, streams[PID]->substream_id);
              }
              else
                debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 1\n", PID, streams[PID]->stream_id);
              break;
            case PRIVATE_STREAM_2:
              debugOut("Found PID 0x%04X, stream id 0x%02X = Private Stream 2\n", PID, streams[PID]->stream_id);
              break;
            case ECM_STREAM:
              debugOut("Found PID 0x%04X, stream id 0x%02X = ECM Stream\n", PID, streams[PID]->stream_id);
              break;
            case EMM_STREAM:
              debugOut("Found PID 0x%04X, stream id 0x%02X = EMM Stream\n", PID, streams[PID]->stream_id);
              break;
            case PROGRAM_STREAM_DIRECTORY:
              debugOut("Found PID 0x%04X, stream id 0x%02X = Program Stream Directory\n", PID, streams[PID]->stream_id);
              break;
            case DSMCC_STREAM:
              debugOut("Found PID 0x%04X, stream id 0x%02X = DSMCC Stream\n", PID, streams[PID]->stream_id);
              break;
            case ITUTRECH222TYPEE_STREAM:
              debugOut("Found PID 0x%04X, stream id 0x%02X = ITU-T Rec. H.222.1 type E\n", PID, streams[PID]->stream_id);
              break;
            case PADDING_STREAM:
              debugOut("Found PID 0x%04X, stream id 0x%02X = Padding Stream\n", PID, streams[PID]->stream_id);
              break;
            default:
              if ((streams[PID]->stream_id >= 0xE0) && (streams[PID]->stream_id <= 0xEF))
                debugOut("Found PID 0x%04X, stream id 0x%02X = Video Stream %d\n", PID, streams[PID]->stream_id, streams[PID]->stream_id - 0xE0);
              else
                if ((streams[PID]->stream_id >= 0xC0) && (streams[PID]->stream_id <= 0xDF))
                  debugOut("Found PID 0x%04X, stream id 0x%02X = MPEG Audio Stream %d\n", PID, streams[PID]->stream_id, streams[PID]->stream_id - 0xC0);
                else
                {
                  if (!streams[PID]->stream_id)
                  {
                    switch (PID)
                    {
                      case PROGRAM_ASSOCIATION_TABLE:
                        debugOut("Found PID 0x%04X, Program Association Table Stream\n", PID);
                        break;
                      case CONDITIONAL_ACCESS_TABLE:
                        debugOut("Found PID 0x%04X, Conditional Access Table Stream\n", PID);
                        break;
                      case NULL_PACKET:
                        debugOut("Found PID 0x%04X, Null Packet Stream\n", PID);
                        break;
                      default:
                        debugOut("Found PID 0x%04X, Other Stream\n", PID);
                        break;
                    }
                  }
                  else
                    debugOut("Found PID 0x%04X, stream id 0x%02X = other stream\n", PID, streams[PID]->stream_id);
                }
          }
          streams[PID]->stream_found = 1;

          if ((streams[PID]->stream_id == PRIVATE_STREAM_1) && (m_bVobFlag))
          {
            if (streams[PID]->stream_encrypted)
              debugOut("PID 0x%04X, Stream id 0x%02X, substream 0x%02X is encrypted\n", PID, streams[PID]->stream_id, streams[PID]->substream_id);
          }
          else
          {
            if (streams[PID]->stream_encrypted)
              debugOut("PID 0x%04X, Stream id 0x%02X is encrypted\n", PID, streams[PID]->stream_id);
          }
        }
      }
    }
  } while ( ! m_bitStream.endBitStream ( ) );

  debugOut("\nSummary:\n");
  debugOut("\nMPEG Transport Packets = %d\n", transport_packets);
  for (i = 0; i < 8192; i++)
  {
    if (streams[i] != NULL)
    {
      if (streams[i]->stream_packets)
      {
        switch (streams[i]->stream_id)
        {
          case PROGRAM_STREAM_MAP:
            debugOut("PID 0x%04X, Program Stream Map packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case PRIVATE_STREAM_1:
            debugOut("PID 0x%04X, Private Stream 1 packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
            if (m_bVobFlag)
            {
              j = streams[i]->substream_id;
              if ((j >= SUBSTREAM_AC3_0) && (j <= SUBSTREAM_AC3_8))
                debugOut("    AC3 Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_AC3_0, streams[i]->stream_packets, streams[i]->ac3_bytes);
              else
                if ((j >= SUBSTREAM_DTS_0) && (j <= SUBSTREAM_DTS_8))
                  debugOut("    DTS Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_DTS_0, streams[i]->stream_packets, streams[i]->dts_bytes);
                else
                  if ((j >= SUBSTREAM_PCM_0) && (j <= SUBSTREAM_PCM_F))
                    debugOut("    PCM Audio stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_PCM_0, streams[i]->stream_packets, streams[i]->pcm_bytes);
                  else
                    if ((j >= SUBSTREAM_SUBPIC_0) && (j <= SUBSTREAM_SUBPIC_1F))
                      debugOut("    Subpicture stream %u packets = %u, total bytes = %u\n", j - SUBSTREAM_SUBPIC_0, streams[i]->stream_packets, streams[i]->subpic_bytes);
                    else
                      debugOut("    Substream 0x%02X packets = %u, total bytes = %u\n", j, streams[i]->stream_packets, streams[i]->stream_bytes);
            }
            break;
          case PRIVATE_STREAM_2:
            debugOut("PID 0x%04X, Private Stream 2 packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case ECM_STREAM:
            debugOut("PID 0x%04X, ECM Stream packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case EMM_STREAM:
            debugOut("PID 0x%04X, EMM Stream packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case PROGRAM_STREAM_DIRECTORY:
            debugOut("PID 0x%04X, Program Stream Directory packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case DSMCC_STREAM:
            debugOut("PID 0x%04X, DSMCC Stream packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case ITUTRECH222TYPEE_STREAM:
            debugOut("PID 0x%04X, ITU-T Rec. H.222.1 type E packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          case PADDING_STREAM:
            debugOut("PID 0x%04X, Padding Stream packets = %u, total bytes = %u\n", PID, streams[i]->stream_packets, streams[i]->stream_bytes);
            break;
          default:
            j = streams[i]->stream_id;
            if ((j >= 0xE0) && (j <= 0xEF))
              debugOut("PID 0x%04X, Video stream %d packets = %u, total bytes = %u\n", i, j - 0xE0, streams[i]->stream_packets, streams[i]->stream_bytes);
            else
              if ((j >= 0xC0) && (j <= 0xDF))
                debugOut("PID 0x%04X, MPEG Audio stream %d packets = %u, total bytes = %u\n", i, j - 0xC0, streams[i]->stream_packets, streams[i]->stream_bytes);
              else
                if (!j)
                {
                  switch (i)
                  {
                    case PROGRAM_ASSOCIATION_TABLE:
                      debugOut("PID 0x%04X, Program Association Table packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
                      break;
                    case CONDITIONAL_ACCESS_TABLE:
                      debugOut("PID 0x%04X, Conditional Access Table packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
                      break;
                    case NULL_PACKET:
                      debugOut("PID 0x%04X, Null packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
                      break;
                    default:
                      debugOut("PID 0x%04X, Other packets = %u, total bytes = %u\n", i, streams[i]->stream_packets, streams[i]->stream_bytes);
                      break;
                  }
                }
                else
                  debugOut("PID %04X, Other stream 0x%02X packets = %u, total bytes = %u\n", i, j, streams[i]->stream_packets, streams[i]->stream_bytes);
        }
      }
      free(streams[i]);
    }
  }

  m_bitStream.finishGetbits ( );
  if ( m_pOutputFile )  {
    flushBuffer ( );
    fclose ( m_pOutputFile );
  }
}

}; // End namespace Mpeg2
