#include "ka_demux.h"

#include <stdbool.h>
#include <string.h>

#include "config.h"
#include "ka_log.h"
#include "ka_mem.h"
#include "ka_codecs.h"
#include "ka_acodec.h"
#include "dvd/dvd_packets.h"

#define PID_PAT  0x0000 // Program Association Table
#define PID_CAT  0x0001 // Conditional Access Table (ITU-T Rec. H222)
#define PID_TSDT 0x0002 // Transport Stream Description Table
#define PID_CIT  0x0003 // IPMP Control Information Table (ISO/IEC 14496-13)
#define PID_USE  0x0020 // to 0x 1FFE: Program Map Tables, Elementary streams, ...
#define PID_NIL  0x1FFF // Null Packet (padding)

#define TS_PH_PACKET_SIZE  192
#define TS_FEC_PACKET_SIZE 204
#define TS_PACKET_SIZE     188
#define MAX_PES_PAYLOAD    200 * 1024

#define MAX_PIDS_PER_PROGRAM 64

#define ERR_CORRUPTED -3
#define ERR_NOMEM -2

typedef struct
{
  uint8_t   tid;
  uint8_t   version;
  uint8_t   sec_num;
  uint8_t   last_sec_num;
  uint32_t  idext;
  uint32_t  len;
  uint32_t  crc;
} ts_section_hdr;

typedef struct
{
  ts_section_hdr  hdr;
  uint8_t*        buf;
  uint32_t        buf_size;
  uint32_t        buf_pos;
  int32_t         h_size;
  bool            done;
} ts_section_t;

// PES

typedef struct ts_pes_t ts_pes_t;

struct ts_pes_t
{
  ts_pes_t*    next;
  uint32_t     pid; // pes packet id
  ka_block_id  kid;
  uint32_t     pts;
  uint32_t     dts;
  uint8_t*     buf;
  uint32_t     buf_size;
  uint32_t     buf_pos;
  int32_t      hdrsize;
  int32_t      pes_size;
  ka_aparams_t audioparams;
};

// PMT (Program Map Table)

typedef struct ts_program_t ts_program_t;

struct ts_program_t
{
  ts_program_t*  next;
  uint32_t       pmt_pid; // program map table packet id
  ts_section_t   section;
};

typedef struct
{
  ka_demux_t     hdr;
  ka_input_t*    input;
  // transport stream (size > 0)
  uint32_t       ts_packetsize;
  // Program Allocation Table, Program Map Tables decoding
  ts_section_t   pat_section;
  ts_program_t*  programs;
  ts_pes_t*      peslist;
  // Each DVD block (2048 bytes) starts with a pack header
  dvd_dsi_t      dsi;
  int32_t        dsi_delta_pts;
  uint32_t       blocknr[10]; // next ILVUs: 0 for interleaved non-selectable info, 1-9 for angle info
  uint32_t       angle;
  uint32_t       max_angles;
  int32_t        dsi_angle; // -1 invalid angle/other interleaved data, 0 normal or correct interleaved data, > 0 angle
  bool           isDVD;
  bool           pushEOS; // pushEOS before new NAV packet to signal no more video (cf. still followed by audio only VOBUs).
} data_t;

static void mpeg_pes_delete(ts_pes_t** ppes)
{
  ts_pes_t* pes = *ppes;
  *ppes = NULL;

  if (!pes)
    return;

  if (pes->buf) ka_mem_free(pes->buf);
  ka_mem_free(pes);
}

static void mpeg_pes_clear(ts_pes_t* pes)
{
  pes->hdrsize = 0;
  pes->pes_size = 0;
  pes->buf_pos = 0;
}

/**
 * Returns pes associated to pes_pid, allocating a new pes if none corresponds.
 */
static ts_pes_t* mpeg_ensure_pes(ka_demux_t* pdemux, ka_stack_t* stack, uint32_t pes_pid, ka_block_id kid)
{
  data_t* data = (data_t*) pdemux;
  ka_idinfo_t* info = ka_stack_idInfo(stack, pes_pid, kid);

  if (info == NULL)
  {
    ka_error_fill(pdemux->pErrorBlock, "No info found for PES %d", kid);
    return NULL;
  }

  if (info->data != NULL)
  {
    ts_pes_t* pes = info->data;
    if (kid) pes->kid = kid;
    return pes;
  }

  ts_pes_t* pes = ka_mem_calloc(sizeof(*pes));
  if (pes == NULL)
  {
    ka_error_fill(pdemux->pErrorBlock, ka_error_nomem);
    return NULL;
  }

  pes->pid = pes_pid;
  pes->kid = kid;
  pes->pes_size = 0;
  pes->buf_size = 1024;
  pes->buf = ka_mem_calloc(pes->buf_size);
  if (!pes->buf)
  {
    mpeg_pes_delete(&pes);
    ka_error_fill(pdemux->pErrorBlock, ka_error_nomem);
    return NULL;
  }

  info->data = pes;

  pes->next = data->peslist;
  data->peslist = pes;

  return pes;
}

/**
 * Return pes associated to pes_pid, or NULL if not found.
 */
static ts_pes_t* mpeg_find_pes_by_id(data_t* data, uint32_t pes_pid)
{
  ts_pes_t* pes;

  for (pes = data->peslist; pes; pes = pes->next)
  {
    if (pes->pid == pes_pid)
      return pes;
  }

  return NULL;
}

/**
 * Returns pes associated to typ, or NULL if not found.
 */
static ts_pes_t* mpeg_find_pes_by_kid(const data_t* data, ka_block_id kid)
{
  ts_pes_t* pes;

  for (pes = data->peslist; pes; pes = pes->next)
  {
    if (pes->kid == kid)
      return pes;
  }

  return NULL;
}

/**
 * Returns program associated to id, allocating a new program if none corresponds.
 */
static ts_program_t* mpeg_ensure_program(data_t* data, ka_stack_t* stack, uint32_t pmt_pid)
{
  ka_idinfo_t* info = ka_stack_programInfo(stack, pmt_pid);

  if (info == NULL)
  {
    ka_error_fill(data->hdr.pErrorBlock, "No program found");
    return NULL;
  }

  if (info->data != NULL)
    return info->data;

  ts_program_t** pprogram = &data->programs;
  while (*pprogram)
  {
    if ((*pprogram)->pmt_pid == pmt_pid)
      return *pprogram;
    pprogram = &(*pprogram)->next;
  }

  ts_program_t* program = ka_mem_calloc(sizeof(*program));
  if (program == NULL)
  {
    ka_error_fill(data->hdr.pErrorBlock, ka_error_nomem);
    return NULL;
  }

  program->pmt_pid = pmt_pid;

  program->section.buf_size = 1024;
  program->section.buf = ka_mem_calloc(program->section.buf_size);
  if (!program->section.buf)
  {
    ka_error_fill(data->hdr.pErrorBlock, ka_error_nomem);
    ka_mem_free(program);
    return NULL;
  }

  info->data = program;
  program->next = data->programs;
  data->programs = program;

  return program;
}

/**
 * Clean programs.
 */
static void mpeg_clean_programs(data_t* data)
{
  ts_program_t* program = data->programs;
  ts_program_t* next;
  ts_pes_t* pes;
  ts_pes_t* next_pes;

  pes = data->peslist;
  while (pes)
  {
    next_pes = pes->next;
    mpeg_pes_delete(&pes);
    pes = next_pes;
  }
  data->peslist = NULL;

  while (program)
  {
    next = program->next;
    ka_mem_free(program->section.buf);
    ka_mem_free(program);
    program = next;
  }

  data->programs = NULL;
}

/**
 * Decodes the packet time stamps. They are
 * encoded as 33 bit numbers so we loose the most significant bit.
 */
static uint32_t time_stamp(const uint8_t * buf)
{
  return  ((buf[0] & 0xe) << 29) // loose msb
         | (buf[1] << 22)
         |((buf[2] & 0xfe) << 14)
         | (buf[3] << 7)
         |((buf[4] & 0xfe) >> 1);
}

/**
 * Resets demuxer data after a restart.
 *
 * @param  pdemux  Pointer to demuxer data.
 */
static void demux_mpeg_reset(ka_demux_t* pdemux, ka_stack_t* stack)
{
  data_t* data = (data_t*) pdemux;

  stack = stack; // unused

  ka_input_seek(data->input, 0);

  if (data->ts_packetsize != 0)
    mpeg_clean_programs(data);

  uint8_t* buf = data->pat_section.buf;
  uint32_t size = data->pat_section.buf_size;
  memset(&data->pat_section, 0, sizeof(data->pat_section));
  data->pat_section.buf = buf;
  data->pat_section.buf_size = size;

  for (int i = 0; i <10; i++)
  {
    data->blocknr[i] = 0xffffffff;
  }
  data->max_angles = 0;
  data->dsi_angle = 0;

  ts_pes_t* pes = data->peslist;

  while (pes)
  {
    mpeg_pes_clear(pes);
    pes = pes->next;
  }

  data->hdr.demuxSection = 0;
  data->hdr.demuxSectionPts = 0;
  data->dsi_delta_pts = 0;
  data->pushEOS = 0;
}

/**
 * clears demuxer data after a seek.
 *
 * @param  pdemux  Pointer to demuxer data.
 */
static int demux_mpeg_clear(ka_demux_t* pdemux, ka_stack_t* stack)
{
  data_t* data = (data_t*) pdemux;

  for (int i = 0; i <10; i++)
  {
    data->blocknr[i] = 0xffffffff;
  }
  data->max_angles = 0;
  data->dsi_angle = 0;

  if (data->ts_packetsize == 0)
  {
    // MPEG-(P)ES
    if (mpeg_ensure_program(data, stack, 0) == NULL)
      return -1;
  }

  ts_pes_t* pes = data->peslist;

  while (pes)
  {
    mpeg_pes_clear(pes);
    pes = pes->next;
  }

  data->hdr.demuxSection++;
  data->dsi_delta_pts = 0;
  data->pushEOS = 0;

  return 0;
}

/**
 * Returns current/max position.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  pbuffer Pointer to stack buffer.
 * @param  curpos  Pointer to current position to fill.
 * @param  endpos  Pointer to max position to fill.
 *
 * @returns Offset in input aligned to nearest block
 *          or -1 if not possible to determine a position.
 */

static void demux_mpeg_getPos(const ka_demux_t* pdemux, ka_buffer_t* pbuffer, ka_demux_pos_t* pos)
{
  const data_t* data = (data_t*) pdemux;

  pos->titleNr = 0;
  pos->chapterNr = 0;
  pos->curPos = ka_input_tell(data->input) - (uint64_t) ka_buffer_getByteCount(pbuffer);
  pos->endPos = ka_input_getLen(data->input);
}

/**
 * Returns position according to parsed input info.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  pos     Raw offset in input.
 *
 * @returns Offset in input aligned to nearest block
 *          or -1 if not possible to determine a position.
 */
static int64_t demux_mpeg_setPos(ka_demux_t* pdemux, int64_t pos)
{
  data_t* data = (data_t*) pdemux;

  ka_input_seek(data->input, pos);

  return pos;
}

/**
 * Returns current/max angles.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  curpos  Pointer to current angle to fill.
 * @param  endpos  Pointer to max angle to fill.
 */

static void demux_mpeg_getAngle(const ka_demux_t* pdemux, ka_demux_angle_info_t* pInfo)
{
  data_t* data = (data_t*) pdemux;

  pInfo->angleId = data->angle;
  pInfo->nrAngles = data->max_angles ? data->max_angles : data->angle;
}

/**
 * Set current angle.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  angle   New angle.
 *
 * Returns updated angle.
 */
static int demux_mpeg_setAngle(ka_demux_t* pdemux, uint32_t angle)
{
  data_t* data = (data_t*) pdemux;
  uint32_t old_angle = data->angle;

  if (angle)
  {
    if (data->max_angles)
      data->angle = (angle <= data->max_angles) ? angle : 1;
    else
      data->angle = angle;
  }

  return (old_angle != data->angle);
}

/**
 * Fill in buffer.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  buffer  Buffer handle.
 * @param  pdone   Pointer to EOF to fill on exit.
 *
 * Returns number of bytes read.
 */
static int demux_mpeg_fillBuffer(ka_demux_t* pdemux, ka_buffer_t* buffer, int* pdone)
{
  data_t* data = (data_t*) pdemux;

  return ka_input_buffer(data->input, buffer, pdone);
}

/**
 * Analyses part of the input buffer and put it on stack.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack on which to push data block.
 * @param  buf     Pointer to start of data to parse.
 * @param  end     Pointer to end of data to parse.
 *
 * @returns Pointer to remaining unused data or ka_demux_error on error.
 */
static const uint8_t* demux_mpeg_es_push(ka_demux_t* pdemux, ka_stack_t* stack
                                       , const uint8_t* buf, const uint8_t* end)
{
  data_t* data = (data_t*) pdemux;

  ka_stack_setProperties(stack, ps_stack_prop_allowvideo | ps_stack_prop_notimestamp);

  // Ensure pes exists for MPEG-ES and use that one
  ka_block_id kid = KA_VBLOCK_MPEG + 0;
  ts_pes_t* pes = mpeg_ensure_pes(pdemux, stack, kid, kid);
  if (!pes) return ka_demux_error;

  ka_stack_programMapId(stack, data->programs->pmt_pid, kid);

  if (buf != end)
    ka_stack_push(stack, kid, data->hdr.demuxSection, 0, 0, 0, buf, end);

  return end;
}

/**
 * Pushes on stack an empty packet to signal end of video.
 *
 * @param  data    Pointer to demuxer data.
 * @param  stack   Pointer to stack on which to push data block.
 */
static void dvd_push_end_of_data(data_t* data, ka_stack_t* stack, ka_block_id kid)
{
  ka_stack_push(stack, kid, data->hdr.demuxSection, 0, 0, 0, NULL, NULL);
}

/**
 * Decodes pes header.
 * For MPEG-PES streams entry pes is a fake (type is 0 on entry),
 * so we must ensure to ensure the existence of real ones for audio in order to store audio parameters.
 *
 * @param  data    Pointer to demuxer data.
 * @param  pes     Pointer to pes info structure where pes_size, pts, dts and type may be filled on exit.
 * @param  buf     Pointer to start of pes buffer (00 00 01 xx ...)
 * @param  end     Pointer to end of filled part of pes buffer (may not yet contain enough data).
 *
 * @returns Size of pes header, 0 if type type is not supported,
 *           -1 if buffer does not contain enough data to parse header.
 */
static int pes_decode_header(ka_demux_t* pdemux, ka_stack_t* stack, ts_pes_t* pes, const uint8_t* buf, const uint8_t* end)
{
  data_t* data = (data_t*) pdemux;
  ///////////////////////////////////////////////
  // 0x00-0xB8 video packets, should not appear here
  // 0xB9 program end code
  // 0xBA program stream pack header
  // 0xBB program stream system header
  // 0xBC program stream map
  // 0xBD private stream 1
  // 0xBE padding stream
  // 0xBF private stream 2 (DVD NAV)
  // 0xC0-0xDF audio stream 0-0x1F
  // 0xE0-0xEF video stream 0-0x0F
  // 0xF0 ecm stream
  // 0xF1 emm stream
  // 0xF2 dsncc stream
  // 0xF3 IEC 13522 stream
  // 0xF4 H222 type A stream
  // 0xF5 H222 type B stream
  // 0xF6 H222 type C stream
  // 0xF7 H222 type D stream
  // 0xF8 H222 type E stream
  // 0xF9 ancillary stream
  // 0xFA IEC 14496 SL packetized stream
  // 0xFB IEC 14496 FlexMux stream
  // 0xFC-FE Reserved
  // 0xFF program stream directory
  ///////////////////////////////////////////////
  // tested by most likely order of appearence

  const uint8_t *sod, *eod;
  uint32_t old_type = pes->kid;

  pes->pes_size = 0;

  if (end < buf + 7)
    return -1;

  //////////////////////////////////////////////
  // process the required audio or video channel
  if (((buf[3] >= 0xc0) && (buf[3] < 0xf0))
  ||  (buf[3] == 0xbd)
  ||  (buf[3] == 0xbf))
    ;
  else return 0;

  pes->pes_size = 6 + (buf[4] << 8) + buf[5];
  eod = buf + pes->pes_size;
  if ((eod == buf + 6) && pes->kid) // MPEG-TS only
    eod = end; // unlimited

  pes->pts = pes->dts = 0;

  if ((buf[6] & 0xc0) == 0x80)  // mpeg 2
  {
    if (end < buf + 10 + buf[8])
      return -1;

    sod = buf + 9;
    if (buf[7] & 0x80)
      pes->pts = time_stamp(sod);
    if (buf[7] & 0x40)
      pes->dts = time_stamp(sod + 5);
    sod += buf[8];
  }
  else                          // mpeg 1
  {
    // remove stuffing bytes
    for (sod = buf + 6; ; sod++)
    {
      if (end < sod + 2)
        return -1;
      if (*sod != 0xff)
        break;
    }

    // STD buf scale & size
    if ((*sod & 0xc0) == 0x40)
    {
      sod += 2;

      if (end <= sod)
        return -1;
    }

    if ((*sod & 0xf0) == 0x20)
    {
      if (end < sod + 6)
        return -1;
      pes->pts = time_stamp(sod);
      sod += 5;
    }
    else if ((*sod & 0xf0) == 0x30)
    {
      if (end < sod + 11)
        return -1;
      pes->pts = time_stamp(sod);
      pes->dts = time_stamp(sod + 5);
      sod += 10;
    }
    else
    {
      if (end < sod + 2)
        return -1;
      sod++;
    }
  }

  //
  // For MPEG-TS type is usually (mostly) predefined.
  // For MPEG-PES type must always be extraced from header.
  //
  if (!pes->kid) // not predefined
  {
    // Need to identify type

    // has video
    if (buf[3] >= 0xe0)
    {
      pes->kid = KA_VBLOCK_MPEG + (buf[3] - 0xe0);
    }
    // MP2/3 in PES but could be ACC, ... in TS streams
    else if (buf[3] >= 0xc0)
    {
      int channel = buf[3] & 0x1f;

      pes->kid = KA_ABLOCK_MPEG + channel;
    }
    else if (buf[3] == 0xbf)
    {
      sod = buf + 6;
      // ignore private stream 2 packets which are not DVD NAV
      if (end < sod + 1)
        return -1;

      if ((sod[0] == 0x00) && (pes->pes_size == 986))
          pes->kid = KA_NBLOCK_DVD_PCI;
      else if ((sod[0] == 0x01) && (pes->pes_size == 1024))
          pes->kid = KA_NBLOCK_DVD_DSI;
      if (!pes->pts)
        pes->pts = 1;

      if (!pes->kid)
      {
        if (config.debug & cfg_printdemuxstats)
          ka_log(ka_log_note | ka_log_demux
               , "Skipped packet BF with code %02X, size %d, pts %10d"
               , sod[0], pes->pes_size, pes->pts);
      }
      else if (pes->kid == KA_NBLOCK_DVD_DSI)
      {
        uint32_t lba, vobu_ea, ilvu_ea, ilvu_sa, agl_sa, angles, vobu_sri_nvwv;

        if (end < buf + 1024)
          return -1;

        // If previous DSI packet signaled no more VOBU with VIDEO to follow
        // Inject an end of sequence to force completion of last frame instead of waiting
        // for the end of the stream, which may not be reached for still frames
        // if too much audio follows.
        if (data->isDVD && data->pushEOS)
        {
          data->pushEOS = 0;
          dvd_push_end_of_data(data, stack, KA_VBLOCK_MPEG + 0);
        }

        dvd_dsi_t* pdsi = &data->dsi;
        dvd_read_dsi(pdemux->pErrorBlock, &pdsi, sod + 1);
        data->dsi_delta_pts = data->hdr.demuxSectionPts + dvd_time_toRtc(&pdsi->cell_time) - pdsi->scr;
//ka_logn("SCR %10u, CELL %10u + %10u\n", pdsi->scr, data->hdr.demuxSectionPts, dvd_time_toRtc(&pdsi->cell_time));
        data->dsi_angle = 0;
        lba = data->dsi.cur_lbn;
        vobu_ea = data->dsi.vobu_last + lba;
        // Interleaved info
        ilvu_ea = data->dsi.ilvu_last;
        if (ilvu_ea) ilvu_ea += lba;
        ilvu_sa = data->dsi.ilvu_next;
        if (ilvu_sa && (ilvu_sa < 0x3fffffff)) ilvu_sa += lba;
        // Check if next VOBU with video, set pushEOS flag if none
        vobu_sri_nvwv = data->dsi.vobu_next_video;
        vobu_sri_nvwv &= ~0xc0000000; // remove flags
        if (vobu_sri_nvwv >= 0x3fffffff)
          data->pushEOS = 1;

        // ka_logn("LBA %x, VOBU ea %x ILVU ea %x, next %x\n", lba, vobu_ea, ilvu_ea, ilvu_sa);
        // ILVU end block only set if there is angles
        // or interleaved non-selectable stuff (like Star Wars' scroll text in different languages)
        if (ilvu_ea)
        {
          data->dsi_angle = -1;
          // We are in an "angle", determine it from block nr of next IVLUs of each angles set by previous DSI
          for (int i = 0; i <= 9; i++)
          {
            if (lba == data->blocknr[i])
            {
              data->dsi_angle = i;
              break;
            }
          }

          // If not found (very first DSI, so no previous DSI) use offset to next ILVU
          // versus table of offsets to next ILVU of each angle
          angles = 0;
          for (int i = 1; i <= 9; i++)
          {
            agl_sa = data->dsi.angles[i-1].next_ilvu;
            if (agl_sa)
            {
              // angle exists
              angles = i;
              if (agl_sa < 0x3FFFFFFF)
              {
                // has next ILVU
                agl_sa += lba;
                if ((data->dsi_angle < 0) && (ilvu_sa < 0x3FFFFFFF) && (agl_sa == ilvu_sa))
                  data->dsi_angle = i;
              }
              // ka_logn("Angle %d ILVU next sa %x\n", i, agl_sa);
            }
          }

          // Ensure current angle fits in list
          if (angles)
          {
            data->max_angles = angles;
            if (data->angle > angles)
            {
              ka_log(ka_log_error | ka_log_demux, "Reset angle to from %d to 1 of %d", data->angle, angles);
              data->angle = 1;
            }

            if (data->dsi_angle <= 0)
            {
              data->dsi_angle = data->angle; // Reuse last known angle
              ka_log(ka_log_error | ka_log_demux, "Could not determine DSI angle, ILVU ea %x, ILVU next sa %x", ilvu_ea, ilvu_sa);
            }
          }
          else
          {
            // First block of interleaved data ?
            if (data->blocknr[0] == 0xffffffff)
              data->dsi_angle = 0;
          }

          if (data->dsi_angle >= 0)
          {
            // If more VOBU in current ILVU, use next VOBU as next blocknr else next ILVU
            if (vobu_ea != ilvu_ea)
              data->blocknr[data->dsi_angle] = vobu_ea + 1;
            else if (ilvu_sa <= 0x3FFFFFFF) // Marker for last ILVU
              data->blocknr[data->dsi_angle] = ilvu_sa;
          }
        }
        else
        {
          data->blocknr[0] = 0xffffffff;
        }
      }
      sod += 1;

      // If we do not use DVD nav, skip packet
      if (!data->isDVD)
        pes->kid = 0;
    }
    else if (buf[3] == 0xbd)
    {
      // ignore private stream 1 packets which are not audio
      if (end < sod + 1)
        return -1;

      // MPEG-TS without PMT tables seem to always use AC3 here
      if (pes->pid)
      {
        pes->kid = KA_ABLOCK_AC3;
      }
      // (E-)AC3 ? 0x80-0x87 or 0xC0-0xCF
      else if (((sod[0] & 0xf8) == 0x80) || ((sod[0] & 0xf0) == 0xc0))
      {
        int channel = ((sod[0] & 0xf0) == 0xc0) ? sod[0] - 0xb8 : sod[0] & 0x07;

        // sod[1] = nr of ac3 frame headers
        // sod[2-3] = offset of first frame header
        if (end < sod + 4)
          return -1;

        pes->kid = KA_ABLOCK_AC3 + channel;

        // special case for AC3, may be present without 4 bytes header (in some TS streams)
        if (sod[0] != 0x0B && sod[1] != 0x77) // AC3 syncword
          sod += 4;
      }
      // DTS ? 0x88-0x8F or 0x98-0x9F
      else if (((sod[0] & 0xf8) == 0x88) || ((sod[0] & 0xf8) == 0x98))
      {
        int channel = ((sod[0] & 0xf8) == 0x98) ? sod[0] - 0x90 : sod[0] & 0x07;
        pes->kid = KA_ABLOCK_DTS + channel;
      }
      // LPCM ? 0xA0-0xAF
      else if ((sod[0] & 0xf0) == 0xa0)
      {
        int channel = sod[0] - 0xa0;
        pes->kid = KA_ABLOCK_LPCM_DVD + channel;
      }
      // TRUEHD ? 0xB0-BF
      else if ((sod[0] & 0xf0) == 0xb0)
      {
        int channel = sod[0] - 0xb0;
        pes->kid = KA_ABLOCK_TRUEHD + channel;
      }
      else if ((sod[0] & 0xe0) == 0x20)
      {
        int channel = sod[0] - 0x20;
        pes->kid = KA_SBLOCK_DVD_SUBTITLE + channel;
        sod += 1;
      }
      else
      {
        pes->kid = 0;
        if (config.debug & cfg_printdemuxstats)
          ka_log(ka_log_note | ka_log_demux, "Skipped packet BD with code %02X", sod[0]);
      }
    }

    if (pes->kid)
    {
      // Ignore data from wrong angle/interleaved ILVU
      if (data->dsi_angle)
      {
        if ((data->dsi_angle < 0) || (data->dsi_angle != data->angle))
        {
          pes->kid = 0;
          return 0;
        }
      }

      // Ensure such pes exists for and use that one since input pes may be a dummy (MPEG-PES) or an incomplete one
      if (pes->pid) // TS
      {
        pes->kid &= ~KA_BLOCK_STREAMMASK;
        pes->kid |= pes->pid;
        pes = mpeg_ensure_pes(pdemux, stack, pes->pid, pes->kid);
        if (!pes) return ERR_NOMEM;
      }
      else // (P)ES
      {
        pes->pid = pes->kid;
        pes = mpeg_ensure_pes(pdemux, stack, pes->pid, pes->kid);
        if (!pes) return ERR_NOMEM;
        ka_stack_programMapId(stack, data->programs->pmt_pid, pes->kid);
      }
    }
  }

  //
  // Extract stream config from header
  //
  switch(pes->kid & ~KA_BLOCK_STREAMMASK)
  {
    case KA_ABLOCK_DTS:
    {
      // sod[1] = nr of dts frame headers
      // sod[2-3] = offset of first frame header
      if (end < sod + 4)
        return -1;

      sod += 4;
    }
    break;
    case KA_ABLOCK_LPCM_DVD:
    {
      // sod[1] = nr of lpcm frame headers
      // sod[2-3] = offset of first frame header
      // sod[4] = audio frame number
      // sod[5] = audio config
      // sod[6] = dynamic range
      static const int lpcm_freq_tab[4] = {48000, 96000, 44100, 32000};
      ka_aparams_t* params = &pes->audioparams;

      if (end < sod + 7)
        return -1;

      if (!params->channels)
      {
        params->channels = (sod[5] & 0x7) + 1;
        params->samplerate = lpcm_freq_tab[(sod[5]>>4) & 3];
        switch (sod[5] >> 6)
        {
          case 0: params->bitspersample = 16; break;
          case 1: params->bitspersample = 20; break;
          case 2: params->bitspersample = 24; break;
          default:
          {
            params->bitspersample = 0;
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux, "Invalid linear pcm audio format");
            pes->kid = 0;
          }
        }
        params->bitrate = params->bitspersample
                        * params->samplerate
                        * params->channels;
        params->blocksize = 0;
      }

      sod += 7;
    }
    break;
    case KA_ABLOCK_TRUEHD:
    {
      // sod[1] = nr of truehd frame headers
      // sod[2-4] = offset of first frame header (?)
      if (end < sod + 5)
        return -1;

      sod += 5;
    }
    break;
    case KA_ABLOCK_LPCM_BR:
    {
      // sod[2] = channel layout << 4 + samplerate
      // sod[3] = audio config
      static const uint8_t channels[16] = {0, 1, 0, 2, 3, 3, 4, 4, 5, 6, 7, 8, 0, 0, 0, 0};
      ka_aparams_t* params = &pes->audioparams;

      if (end < sod + 4)
        return -1;

      if (!params->channels)
      {
        params->layout = sod[2] >> 4;
        params->channels = channels[params->layout];
        if (!params->channels)
        {
          if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
            ka_log(ka_log_error | ka_log_demux, "Invalid Blu-Ray pcm audio format");
          pes->kid = 0;
        }
        switch (sod[2] & 0xf)
        {
          case 1: params->samplerate = 48000; break;
          case 4: params->samplerate = 96000; break;
          case 5: params->samplerate = 192000; break;
          default:
          {
            params->samplerate = 0;
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux, "Invalid Blu-Ray pcm audio format");
            pes->kid = 0;
          }
        }
        switch (sod[3] >> 6)
        {
          case 1: params->bitspersample = 16; break;
          case 2: params->bitspersample = 20; break;
          case 3: params->bitspersample = 24; break;
          default:
          {
            params->bitspersample = 0;
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux, "Invalid Blu-Ray pcm audio format");
            pes->kid = 0;
          }
        }
        int alchannels = params->channels + (params->channels & 1);
        int blocksize = (alchannels * params->bitspersample) >> 3;
        int extra = blocksize ? (pes->pes_size - (sod + 4 - buf)) % blocksize : 0;
        pes->pes_size -= extra;
        params->bitrate = params->bitspersample
                        * params->samplerate
                        * alchannels;
      }

      sod += 4;
    }
    break;
  }

  if (old_type && (pes->kid != old_type))
  {
    if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
      ka_log(ka_log_error | ka_log_demux, "Mix of packet types %08x -> %08x", old_type, pes->kid);
  }

  if (!pes->kid)
    return 0;

  return sod - buf;
}

/**
 * Analyses part of the input buffer and put it on stack.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack on which to push data block.
 * @param  buf     Pointer to start of data to parse.
 * @param  end     Pointer to end of data to parse.
 *
 * @returns Pointer to remaining unused data or ka_demux_error on error.
 */
static const uint8_t* demux_mpeg_ps_push(ka_demux_t* pdemux, ka_stack_t* stack
                                       , const uint8_t* buf, const uint8_t* end)
{
  data_t* data = (data_t*) pdemux;
  uint32_t scr = 0, mux;

  const uint8_t
      *start = buf // for dummies
    , *sod         // start of packet data
    , *eod         // end of packet data
    , *p;          // temp pointer

  ka_stack_setProperties(stack, ps_stack_prop_allowvideo | ps_stack_prop_allowaudio);

  ///////////////////////////////////////////////////////
  // search for start codes and push completely available
  // video/audio pes packets on stack
  while ((buf + 4) <= end)
  {
    if (buf[1])
      buf += 2;
    else if (buf[0] || (buf[2] != 1))
      buf++;
    else // found start code
    {
      start = buf;

      // process only if fifo is not full
      if (ka_stack_isFull(stack))
        break;

      // Make a note of the packet start.
      // buf will be updated to the packet end only if the
      // whole packet is available in the buffer.
      p = buf;

      do // just so we can use breaks
      {
        //////////////////////////////////////////////
        // process the required audio or video channel
        ts_pes_t pes;

        pes.kid = 0;
        pes.pid = 0;
        int hdrsize = pes_decode_header(pdemux, stack, &pes, buf, end);
        // not enough data ?
        if (hdrsize < 0)
          break;

        /////////////////////////
        // is known/ignored pes ?
        if ((hdrsize >= 0) && (pes.pes_size > 0))
        {
          // process only if completely in buffer
          eod = buf + pes.pes_size;
          if (eod > end) break;

          // skip header
          sod = buf + hdrsize;

          if (pes.kid && (sod < eod))
          {
            ka_stack_push(stack, pes.kid, data->hdr.demuxSection, scr, pes.dts, pes.pts, sod, eod);
          }
          start = buf = eod;
        }

        //////////////
        // pack header
        else if (buf[3] == 0xba)
        {
          if ((buf + 12) > end) break;

          scr = 0;

          if ((buf[4] & 0xc0) == 0x40)  // mpeg2
          {
            if ((buf + 14) > end) break;

            eod = buf + 14 + (buf[13] & 7);
            if (eod > end) break;

            scr = ((buf[4] & 0x38) << 27) // loose msb
                | ((buf[4] & 0x03) << 28)
                |  (buf[5] << 20)
                | ((buf[6] & 0xf8) << 12)
                | ((buf[6] & 0x03) << 13)
                |  (buf[7] << 5)
                | ((buf[8] & 0xf8) >> 3);

            mux =  (buf[10] << 14)
                |  (buf[11] << 6)
                |  (buf[12] >> 2);
          }
          else if ((buf[4] & 0xf0) == 0x20) // mpeg1
          {
            scr = time_stamp(buf + 4);

            mux = ((buf[9] & 0x7f) << 15)
                |  (buf[10] << 7)
                |  (buf[11] >> 1);

            eod = buf + 12;
          }
          else
          {
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux, "Weird pack header %02X", buf[4]);
            // search for the next start code
            buf += 3;
            break;
          }

          buf = eod;
        }

        ////////////////////
        // program end code
        else if (buf[3] == 0xb9)
        {
          buf += 4;
        }

        //////////////////////////////////////////////////////
        // ignore program stream system header, padding stream
        else if ((buf[3] == 0xbb) || (buf[3] == 0xbe))
        {
          // process packet only if entirely in buffer
          // if ((buf + 6) > end) break;
          eod = buf + 6 + (buf[4] << 8) + buf[5];
          if (eod > end) break;
          // just skip packet
          buf = eod;
        }

        ///////////////////////////////////////////////////////////
        // not the selected audio or video packet, so what was it ?
        else if (buf[3] < 0xb9)
        {
          if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
            ka_log(ka_log_warning | ka_log_demux, "Video packet %02X in system stream", buf[3]);
          // if, for some reason like seeking, we have a video stream packet
          // here, then force a search for the next start code in an
          // effort to resynchronise.
          buf += 3;
        }

        ////////////////////////////////////////////////////////
        // unwanted system headers, PES, audio and video packets
        else
        {
          // process packet only if entirely in buffer
          // if ((buf + 6) > end) break;
          eod = buf + 6 + (buf[4] << 8) + buf[5];
          if (eod > end) break;
          // just skip unwanted packet
          if (config.debug & cfg_printdemuxstats)
            ka_log(ka_log_note | ka_log_demux, "Skipped packet %02X", buf[3]);
          buf = eod;
        }
      } while (0); // just so we can use breaks

      // if buf has not been changed then there was not enough data in
      // the buffer to read the whole packet so we need more data from
      // file.
      if (p == buf)
        break;
    } // end of found start code
  }   // end of while enough data in buffer

  return buf;
}

//------------------------------------------------------------------------------
// MEG-TS

/**
 * Returns program associated to id or NULL if not found.
 */
static ts_program_t* ts_find_pmt(data_t* pdata, uint32_t pmt_pid)
{
  ts_program_t* program = pdata->programs;
  while (program)
  {
    if (program->pmt_pid == pmt_pid)
      return program;
    program = program->next;
  }

  return NULL;
}

/**
 * Decode standard psi section headers (3 bytes hdr + 5 bytes extenstion + N bytes data + 4 bytes crc)
 */
static int ts_decode_section_header(ts_section_hdr* href, ts_section_hdr* h
                                  , const uint8_t** pp, const uint8_t* eod)
{
  const uint8_t* p = *pp;

  if ((eod - p) < 8)
  {
    if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
      ka_log(ka_log_error | ka_log_demux, "Section header should have at least 8 bytes");
    return ERR_CORRUPTED;
  }
  *pp = p + 8;

  // table hdr
  h->tid = p[0];
  h->len = ((p[1] << 8) + p[2]) & 0x3ff; // size starting from p + 3
  // table syntax hdr
  h->idext = (p[3] << 8) + p[4];
  h->version = ((p[5]) >> 1) & 0x1f;
  h->sec_num = p[6];
  h->last_sec_num = p[7];

  h->len += 3; // add the hdr, so 8 bytes (hrd + syntax hdr) + N bytes data + 4 bytes CRC
  if (((eod - p) < h->len) || (h->len < 12))
  {
    if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
      ka_log(ka_log_error | ka_log_demux, "Section size %x bytes but calculated %x", eod - p, h->len);
    return ERR_CORRUPTED;
  }

  p += h->len - 4;
  h->crc = (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
  h->len -= 12; // remove 8 bytes (hrd + syntax hdr) + 4 bytes CRC

  // Skip identical
  if ((h->version == href->version) && (h->crc == href->crc))
    return 0;

  *href = *h;

  return 1;
}

/**
 * Write packet data into section buffer.
 * Section starts with a header giving the useful size of the section.
 * Packet data behind the useful part is just filler (cf. fixed size of packets).
 */
static void ts_section_write_data(ts_section_t* section
                                , const uint8_t* buf, const uint8_t* buf_end, bool is_start)
{
  uint32_t len = buf_end - buf;

  if (is_start)
  {
    section->buf_pos = 0;
    section->h_size = -1;
    section->done = false;
  }

  if (section->done)
    return; // ignore filler data
  if (section->buf_pos + len > section->buf_size)
  {
    if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
      ka_log(ka_log_error | ka_log_demux, "packet payload exceeds remaining buffer size by %d bytes"
                                        , section->buf_pos + len - section->buf_size);
    len = section->buf_size - section->buf_pos;
  }
  memcpy(section->buf + section->buf_pos, buf, len);
  section->buf_pos += len;

  // Enough data to compute section real size (3 hdr bytes + size from hdr)
  if ((section->h_size == -1) && (section->buf_pos >= 3))
  {
    len = 3 + (((section->buf[1] << 8) + section->buf[2]) & 0xfff);
    if (len > section->buf_size)
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "computed section real size exceeds buffer size by %d bytes"
                                          , len - section->buf_size);
      len = section->buf_size;
    }
    section->h_size = len;
  }
}

/**
 * Write packet data and decode Program Association Table once section is complete.
 */
static int ts_pat_handle_data(ka_demux_t* pdemux, ka_stack_t* stack, const uint8_t* buf, const uint8_t* buf_end, bool is_start)
{
  data_t* data = (data_t*) pdemux;
  ts_section_t* section = &data->pat_section;

  ts_section_write_data(section, buf, buf_end, is_start);

  // If not yet all info, return
  if ((section->h_size < 0) || (section->buf_pos < section->h_size))
    return 0;

  // Mark as decoded
  section->done = true;

  const uint8_t* p = section->buf;
  const uint8_t* eod = p + section->h_size;
  ts_section_hdr h;

  int rc = ts_decode_section_header(&data->pat_section.hdr, &h, &p, eod);
  if (rc <= 0) return rc; // corrupted or identical to previous one
  if (h.tid != 0) return 0; // ignore (usually 0xff for filler)

  eod = p + h.len;

//
// ts_clear_programs();

  for (;(eod - p) >= 4; p+= 4)
  {
    int32_t stream_id = (p[0] << 8) + p[1];
    int32_t pmt_pid = (p[2] << 8) + p[3];
    pmt_pid &= 0x1fff;
    if (stream_id != 0) // Ignore NIT info
    {
      ts_program_t* program = mpeg_ensure_program(data, stack, pmt_pid);
      if (!program) return ERR_NOMEM;
    }
  }

  return 0;
}

/**
 * Write packet data and decode Program Map Table once section is complete.
 */

typedef struct
{
  uint32_t     tag;
  ka_block_id  kid;
} tag_type;

static const tag_type REGD_TYPES[] =
{ {MKTAG('A','C','-','3'), KA_ABLOCK_AC3}
, {MKTAG('D','T','S','1'), KA_ABLOCK_DTS}
, {MKTAG('D','T','S','2'), KA_ABLOCK_DTS}
, {MKTAG('D','T','S','3'), KA_ABLOCK_DTS}
, {MKTAG('H','E','V','C'), KA_VBLOCK_H265}
, {MKTAG('V','C','-','1'), KA_VBLOCK_VC1}
, {0, 0}
};

static const tag_type DESC_TYPES[] =
{ {0x56, KA_SBLOCK_TELETEXT}// DVB Teletext
, {0x59, KA_SBLOCK_DVB_SUBTITLE} // DVB Subtitle
, {0x6A, KA_ABLOCK_AC3}
, {0x7A, KA_ABLOCK_AC3}     // E-AC3
, {0x7B, KA_ABLOCK_DTS}
, {0,0}
};

static const tag_type STREAM_TYPES[] =
{ {0x01, KA_VBLOCK_MPEG}    // MPEG-1 video, ISO/IEC 11172-2
, {0x02, KA_VBLOCK_MPEG}    // MPEG-2 video, ISO/IEC 13818-2, ITU-Rec. H262
, {0x03, KA_ABLOCK_MPEG}    // MPEG-1 audio, ISO/IEC 11172-3
, {0x04, KA_ABLOCK_MPEG}    // MPEG-2 audio, ISO/IEC 13818-3
// 0x05                     // PRIVATE SECTION, ISO/IEC 13818-1
// 0x06                     // PRIVATE DATA, ISO/IEC 13818-1, DVB subtitles, possibly AC3 ?
, {0x0F, KA_ABLOCK_AAC}     // ADTS AAC, ISO/IEC 13818-7
, {0x10, KA_VBLOCK_H263}    // MPEG-4 video, ISO/IEC 14496-2, ITU-Rec. H263
, {0x11, KA_ABLOCK_AAC}     // AAC LATM, ISO/IEC 14496-3
// 0x12                     // SL PES STREAM, ISO/IEC 14496-1
// 0x13                     // SL SECTION, ISO/IEC 14496-1
, {0x1B, KA_VBLOCK_H264}    // MPEG-4 video, ISO/IEC 14496-10, ITU-Rec. H264
, {0x1C, KA_ABLOCK_AAC}     // AAC, ????
, {0x20, KA_VBLOCK_H264}    // H264
, {0x24, KA_VBLOCK_H265}    // HEVC, ISO/IEC 23008-2, ITU-Rec. H265
, {0x80, KA_VBLOCK_MPEG}    // MPEG-2 video ATSC
, {0x81, KA_ABLOCK_AC3}     // AC3 ATSC, <= 6 channels
, {0x82, KA_ABLOCK_DTS}     // DTS Bly-Ray, 6 channels
, {0x83, KA_ABLOCK_TRUEHD}  // TRUEHD Bly-Ray
, {0x84, KA_ABLOCK_AC3}     // E-AC3 Bly-Ray, <= 16 channels
, {0x85, KA_ABLOCK_DTS}     // DTS Bly-Ray, 8 channels
, {0x86, KA_ABLOCK_DTS}     // DTS Bly-Ray, 8 channels lossless
, {0x87, KA_ABLOCK_AC3}     // E-AC3 ATSC, <= 16 channels
, {0x8A, KA_ABLOCK_DTS}     // DTS ATSC
, {0xEA, KA_VBLOCK_VC1}     // VC1
, {0, 0}
};

static const tag_type HDMV_TYPES[] =
{ {0x80, KA_ABLOCK_LPCM_BR} // PCM Bly-Ray or MPEG-2 video (ATSC)
, {0x81, KA_ABLOCK_AC3}     // AC3 Bly-Ray, <= 6 channels
, {0x82, KA_ABLOCK_DTS}     // DTS Bly-Ray, 6 channels
, {0x83, KA_ABLOCK_TRUEHD}  // TRUEHD Bly-Ray
, {0x84, KA_ABLOCK_AC3}     // E-AC3 Bly-Ray, <= 16 channels
, {0x85, KA_ABLOCK_DTS}     // DTS Bly-Ray, 8 channels
, {0x86, KA_ABLOCK_DTS}     // DTS Bly-Ray, 8 channels lossless
, {0x90, KA_SBLOCK_PGS}     // HVDM PGS SUBTITLE
, {0x92, KA_SBLOCK_TELETEXT}// HVDM TEXT SUBTITLE
, {0xa1, KA_ABLOCK_AC3}     // E-AC3 Bly-Ray, secondary audio
, {0xa2, KA_ABLOCK_DTS}     // DTS Bly-Ray, secondary audio
, {0, 0}
};

static int ts_pmt_handle_data(ka_demux_t* pdemux, ka_stack_t* stack, ts_program_t* program
                            , const uint8_t* buf, const uint8_t* buf_end, bool is_start)
{
  data_t* data = (data_t*) pdemux;
  ts_section_t* section = &data->pat_section;
  ts_section_write_data(section, buf, buf_end, is_start);

  // If not yet all info, return
  if ((section->h_size < 0) || (section->buf_pos < section->h_size))
    return 0;

  // Mark as decoded
  section->done = true;

  const uint8_t* p = section->buf;
  const uint8_t* eod = p + section->h_size;
  ts_section_hdr h;
  int rc = ts_decode_section_header(&program->section.hdr, &h, &p, eod);
  if (rc <= 0) return rc; // corrupted or identical to previous one
  if (h.tid != 2) return 0; // ignore (usually 0xff for filler)

  eod = p + h.len;

  int32_t pcr_packetid = (p[0] << 8) + p[1];
  int32_t program_info_length = (p[2] << 8) + p[3];
  p += 4;

  pcr_packetid &= 0x1fff;
  program_info_length &= 0xfff;

  const uint8_t* info_eod = p + program_info_length;
  if (info_eod > eod)
  {
    if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
      ka_log(ka_log_error | ka_log_demux, "PMT %04x info length exceeds PMT length by %d bytes"
                                        , program->pmt_pid, info_eod - eod);
    return ERR_CORRUPTED;
  }

  int isBluRay = false;

  while ((info_eod - p) >= 2) {
    uint8_t desc_tag, desc_len;
    desc_tag = *p++;
    desc_len = *p++;
    const uint8_t* desc_end = p + desc_len;
    if (desc_end > info_eod)
    {
      // something else is broken, exit the program_descriptors_loop
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "PMT descriptor length exceeds desciptors length by %d bytes"
                                          , (p - info_eod)  + desc_len);
      break;
    }
    // for now no useful info
    if ((desc_tag == 5) && (desc_len >= 4))
    {
      uint32_t tag = MKTAG(p[0], p[1], p[2], p[3]);
      if ((tag == MKTAG('H','D','M','V'))
      ||  (tag == MKTAG('H','D','P','R')))
       isBluRay = true;
    }
    p = desc_end;
  }
  p = info_eod;

  if (config.debug & cfg_printstatssummary)
    ka_log(ka_log_info | ka_log_demux, "Program Map Table %04x", program->pmt_pid);

  for (; (eod - p) >= 5;) {
    uint32_t stream_type = p[0];
    int32_t packetid = (p[1] << 8) + p[2];
    int32_t desc_list_len = (p[3] << 8) + p[4];;
    p += 5;

    packetid &= 0x1fff;
    desc_list_len &= 0xfff;

    uint32_t kid = 0;
    const tag_type* tagtype;

    for (tagtype = STREAM_TYPES; tagtype->tag != 0; tagtype++)
    {
      if (tagtype->tag == stream_type)
        kid = tagtype->kid;
    }

    if (isBluRay)
    {
      for (tagtype = HDMV_TYPES; tagtype->tag != 0; tagtype++)
      {
        if (tagtype->tag == stream_type)
          kid = tagtype->kid;
      }
    }

    const uint8_t* desc_list_end  = p + desc_list_len;

    if (desc_list_end > eod)
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "PMT descriptor length exceeds PMT length by %d bytes"
                                          , desc_list_end - eod);
      desc_list_end =  eod;
    }


    // For stream_type 0x6 PRIVATE DATA
    // tags 0x6a, 0x7a -> AC3
    // tags 0x7b -> DTS
    // tags 0x56 -> Teletext
    // tags 0x59 -> subtitle
    // tags 0x05 -> Registration -> FourCC

    for (; (desc_list_end - p) >= 2;) {
      int32_t desc_tag = *p++;
      int32_t desc_len = *p++;
      const uint8_t* desc_end = p + desc_len;
      if (desc_end > desc_list_end)
      {
        if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
          ka_log(ka_log_error | ka_log_demux, "PMT descriptor length exceeds desciptors end by %d bytes"
                                            , desc_end - desc_list_end);
        break;
      }

      if (stream_type == 0x06)
      {
        for (tagtype = DESC_TYPES; tagtype->tag != 0; tagtype++)
        {
          if (tagtype->tag == desc_tag)
            kid = tagtype->kid;
        }
      }

      switch(desc_tag)
      {
        case 0x05: // registration
        {
          if (desc_len >= 4)
          {
            ka_block_id reg_kid = 0;

            for (tagtype = REGD_TYPES; tagtype->tag != 0; tagtype++)
            {
              if (tagtype->tag == MKTAG(p[0], p[1], p[2], p[3]))
                reg_kid = tagtype->kid;
            }

            if (reg_kid)
            {
              if (kid && (kid != reg_kid))
              {
                if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
                  ka_log(ka_log_error | ka_log_demux, "Conflict between registration %c%c%c%c and stream type %08x"
                                                    , p[0], p[1], p[2], p[3], kid);
              }
              else
                kid = reg_kid;
            }
          }
        }
        break;
        case 0x0a: // ISO-639, language
        {
          if (desc_len >= 4)
          {

          }
        }
        break;
      }
      p = desc_end;
    }
    p = desc_list_end;

    if (kid)
    {
      kid |= packetid;
      ts_pes_t* pes = mpeg_ensure_pes(pdemux, stack, packetid, kid);
      if (!pes) return ERR_NOMEM;
      ka_stack_programMapId(stack, program->pmt_pid, kid);

      if (config.debug & cfg_printstatssummary)
        ka_log(ka_log_info | ka_log_demux, "TYPE %02x PID %04x DESC %02x -> KA_TYPE %x (%s)"
             , stream_type, packetid, desc_list_len, kid, ka_block_to_name(kid));
    }
    else
    {
      ts_pes_t* pes = mpeg_ensure_pes(pdemux, stack, packetid, packetid);
      if (!pes) return ERR_NOMEM;
      if (config.debug & cfg_printstatssummary)
        ka_log(ka_log_info | ka_log_demux, "TYPE %02x PID %04x DESC %02x ->"
             , stream_type, packetid, desc_list_len);
    }
  }

  return 0;
}

/**
 * Write packet data into section buffer.
 * Section starts with a header giving the useful size of the section.
 * We actually don't copy the data except enough to read the header.
 * Data past the header is just pushed directly on the stack.
 * Packet data behind the useful part is just filler (cf. fixed size of packets).
 */
static int ts_pes_section_handle_data(ka_demux_t* pdemux, ts_pes_t* pes, ka_stack_t* stack
                                    , const uint8_t* buf, const uint8_t* buf_end, bool is_start)
{
  data_t* data = (data_t*) pdemux;
  uint32_t len = buf_end - buf;

  if (is_start)
  {
    pes->hdrsize = 0;
    pes->pes_size = -1;
    pes->buf_pos = 0;
  }

  if (pes->pes_size < 0)
  {
    // not enough data yet for buffer
    if (pes->buf_pos + len <= pes->buf_size)
    {
      memcpy(pes->buf + pes->buf_pos, buf, len);
      pes->buf_pos += len;
      int32_t hdrsize = pes_decode_header(pdemux, stack, pes, pes->buf, pes->buf + pes->buf_pos);

      if (hdrsize > 0)
      {
        // skip start of bufffer overlapping header
        len = pes->buf_pos - hdrsize;
        buf = buf_end - len;
        pes->buf_pos = hdrsize; // need this info to determine when we reach waste
        pes->hdrsize = hdrsize;
      }
      else if (hdrsize == 0)
      {
        pes->pes_size = 0; // ignore pes
        return 0;
      }
      else if (hdrsize <= -2)
        return hdrsize; // error
      else return 0; // not enough data yet
    }
    else
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "PES %04x header exceeds %d bytes, ignoring pes"
                                          , pes->pid, pes->buf_size);
      pes->pes_size = 0; // pes is to be ignored
      return 0;
    }
  }
  else if (pes->pes_size < 6)
    return 0; // ignoring undecodable pes

  if (pes->pes_size != 6) // = meaning unlimited length
  {
    if (len > (pes->pes_size - pes->buf_pos))
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "PES %04x exceed pes_size %x pos %x, len %x"
                                          , pes->pid, pes->pes_size, pes->buf_pos, len);
      len = pes->pes_size - pes->buf_pos;
    }
  }

  if (len > 0)
  {
    if (pes->buf_pos == pes->hdrsize)
      ka_stack_push(stack, pes->kid, data->hdr.demuxSection, 0, pes->dts, pes->pts, buf, buf + len);
    else
      ka_stack_push(stack, pes->kid, data->hdr.demuxSection, 0, 0, 0, buf, buf + len);
    pes->buf_pos += len;
    return 1;
  }

  return 0;
}

static int ts_handle_packet(ka_demux_t* pdemux, ka_stack_t* stack, const uint8_t* buf, const uint8_t* eod)
{
  data_t* data = (data_t*) pdemux;
  const uint8_t* sod;
  uint32_t packetid = ((buf[1] << 8) + buf[2]) & 0x1fff;

  // seems like extras to standard size are extra timestamping
  eod = buf + TS_PACKET_SIZE;

  if (packetid >= 0x1fff)
    return 0; // Ignore padding and invalid packets
  if (buf[1] & 0x80)
    return 0; // Ignore packet mared as incorrect by transport layer

  bool is_start = (buf[1] & 0x40) != 0;
  // int cc = buf[3] & 0x0f;
  int cc_ok = true;
  int discontinuity = 0;

  // scrambled ?
  if (buf[3] & 0xc0)
    return 0;

  // has adaptation field ?
  if (buf[3] & 0x20)
  {
    sod = buf + 5 + buf[4];
    if (sod > eod)
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, "Packet adaptation field length exceeds packet length by %d bytes"
                                          , sod - eod);
      return ERR_CORRUPTED;
    }
    discontinuity = buf[5] & 0x80;
    if (buf[5] & 0x10)
    {
      // pcr (33 bits base, 6, reserved, 9 extension = base * 300 + extension)
    }
    if (buf[5] & 0x08)
    {
      // original pcr (see pcr)
    }
    // ignore the rest
  }
  else
  {
    sod = buf + 4;
  }

  // has payload ?
  if (buf[3] & 0x10)
  {
    int rc = 0;

    // Is Program Alloaction Table ?
    if (packetid == 0)
    {
/* Disable, may confuse decoder if no PMT follows
      if (is_start)
      {
        // pointer field is present
        int len = *sod++;
        if ((eod - sod) < len)
        {
          if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
            ka_log(ka_log_error | ka_log_demux, "Packet pointer field length exceeds packet length by %d bytes"
                                              , sod - eod - len);
          return ERR_CORRUPTED;
        }

        // Contains a remain from previous packet ?
        if (cc_ok && len)
        {
          rc = ts_pat_handle_data(data, pErrorBlock, stack, sod, sod + len, false);
          if (rc < 0) return rc;
          sod += len;
        }
        if (sod < eod)
        {
          rc = ts_pat_handle_data(data, pErrorBlock, stack, sod, eod, true);
          if (rc < 0) return rc;
        }
      }
      else
      {
        if (cc_ok)
        {
          rc = ts_pat_handle_data(data, pErrorBlock, stack, sod, eod, false);
          if (rc < 0) return rc;
        }
      }
*/
    }
    // Null packet ?
    else if (packetid == 0x1fff)
      ;
    else if (is_start)
    {
      // Program Elementary Stream ?
      if ((sod[0] == 0) && (sod[1] == 0) && (sod[2] == 1))
      {
        // Is Known Program Elementary Stream ?
        ts_pes_t* pes = mpeg_ensure_pes(pdemux, stack, packetid, 0);
        if (pes == NULL)
          return ERR_NOMEM;

        return ts_pes_section_handle_data(pdemux, pes, stack, sod, eod, is_start);
      }
      // Program Specific Information
      else
      {
        // pointer field is present
        int len = *sod++;
        if ((eod - sod) <= len)
        {
          if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
            ka_log(ka_log_error | ka_log_demux, "Packet %04x pointer field length exceeds packet length by %d bytes"
                                              , packetid, len - (eod - sod));
          return 0;
        }

        // Program Map Table ?
        if (sod[len] != 2)
          return 0;

        // Contains a remain from previous packet ?
        if (cc_ok && len)
        {
          // Is Known Program Map Table ?
          ts_program_t* program = ts_find_pmt(data, packetid);
          if (program != NULL)
          {
            rc = ts_pmt_handle_data(pdemux, stack, program, sod, sod + len, false);
            if (rc < 0) return rc;
          }
          else
          {
            // Ignore packet
          }
        }
        sod += len;
        if (sod < eod)
        {
          ts_program_t* program = mpeg_ensure_program(data, stack, packetid);
          if (program == NULL)
            return ERR_NOMEM;
          rc = ts_pmt_handle_data(pdemux, stack, program, sod, eod, true);
          if (rc < 0) return rc;
        }
      }
    }
    else
    {
      // Is Known Program Map Table ?
      ts_program_t* program = ts_find_pmt(data, packetid);
      if (program != NULL)
      {
        if (cc_ok)
        {
          rc = ts_pmt_handle_data(pdemux, stack, program, sod, eod, false);
          if (rc < 0) return rc;
        }
      }
      else
      {
        if ((sod < eod) && cc_ok)
        {
          // Is Known Program Elementary Stream ?
          ts_pes_t* pes = mpeg_find_pes_by_id(data, packetid);
          if (pes != NULL)
          {
            return ts_pes_section_handle_data(pdemux, pes, stack, sod, eod, is_start);
          }
          // else Ignore packet
        }
      }
    }
  }

  return 0;
}

/**
 * Analyses part of the input buffer and put it on stack.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack on which to push data block.
 * @param  buf     Pointer to start of data to parse.
 * @param  end     Pointer to end of data to parse.
 *
 * @returns Pointer to remaining unused data or ka_demux_error on error.
 *
 * Constructs pes stream from ts stream, on exit:
 * - buf points to unprocessed part of pes
 * - data->pes_len contains size of unprocessed pes stream
 * - unprocessed part of ts stream starts at buf + data->pes_len
 */
static const uint8_t* demux_mpeg_ts_push(ka_demux_t* pdemux, ka_stack_t* stack
                                       , const uint8_t* buf, const uint8_t* end)
{
  data_t* data = (data_t*) pdemux;
  uint32_t packetid;
  const uint8_t* eod;
  const uint8_t* start = buf;

  ka_stack_setProperties(stack, ps_stack_prop_allowvideo | ps_stack_prop_allowaudio);

  // First find all packets in buffer
  while (buf + data->ts_packetsize <= end)
  {
    if (*buf != 0x47)
    {
      buf++;
      continue;
    }

    // process only if fifo is not full
    if (ka_stack_isFull(stack))
      break;

    eod = buf + data->ts_packetsize;


    packetid = ((buf[1] << 8) + buf[2]) & 0x1fff;

    int rc = ts_handle_packet(pdemux, stack, buf, eod);
    if (rc < 0)
    {
      if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
        ka_log(ka_log_error | ka_log_demux, (rc == ERR_NOMEM) ? "Out of memory" : "Corrupted stream");

      if (rc != ERR_CORRUPTED)
        return ka_demux_error;

      // try to resync
      eod = buf + 1;
    }

    start = eod;
    buf = eod;
  }

  return buf;
}

/**
 * Returns audio/video stream parameters.
 */
static const void* demux_mpeg_params(const ka_demux_t* pdemux, ka_block_id kid)
{
  const data_t* data = (data_t*) pdemux;
  ts_pes_t* pes = mpeg_find_pes_by_kid(data, kid);
  if (pes)
  {
    if ((pes->kid & KA_BLOCK_TYPEMASK) == KA_BLOCK_TYPE_AUDIO)
    {
      pes->audioparams.typ = kid & ~KA_BLOCK_STREAMMASK;
      return &pes->audioparams;
    }
  }

  return NULL;
}

/**
 * Deletes a demuxer.
 *
 * @param  pdemux  Pointer to demuxer data.
 */
static void delete_demux_mpeg(ka_demux_t** ppdemux)
{
  data_t* pdata = (data_t*) *ppdemux;
  *ppdemux = NULL;

  if (!pdata)
    return;

  mpeg_clean_programs(pdata);

  if (pdata->pat_section.buf) ka_mem_free(pdata->pat_section.buf);
  ka_mem_free(pdata);
}

/**
 * Creates a new demuxer.
 *
 * @returns Pointer to demuxer data or NULL if creation failed.
 */
static ka_demux_t* new_demux_mpeg(const kav_demux_t* vptr, ka_error_t* pErrorBlock, ka_input_t* input, ka_stack_t* stack, uint32_t ts_packetsize)
{
  data_t* data = ka_mem_calloc(sizeof(*data));

  if (!data)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  data->hdr.vptr = vptr;
  data->hdr.pErrorBlock = pErrorBlock;

  data->input = input;
  data->angle = 1;
  data->max_angles = 0;
  data->ts_packetsize = ts_packetsize;
  if (data->ts_packetsize > 0)
  {
    // MPEG-TS
    data->pat_section.buf_size = 1024;
    data->pat_section.buf = ka_mem_calloc(data->pat_section.buf_size);
    if (!data->pat_section.buf)
    {
      delete_demux_mpeg((ka_demux_t**) &data);
      ka_error_fill(pErrorBlock, ka_error_nomem);
      return NULL;
    }
  }
  else
  {
    // MPEG-(P)ES
    if (mpeg_ensure_program(data, stack, 0) == NULL)
    {
      delete_demux_mpeg((ka_demux_t**) &data);
      return NULL;
    }
  }

  return &data->hdr;
}

static const kav_demux_t demux_mpeg_es =
{ .name = "MPEG Elementary Video Stream"
, .FNdelete = delete_demux_mpeg
, .FNreset  = demux_mpeg_reset
, .FNclear  = demux_mpeg_clear
, .FNgetPos = demux_mpeg_getPos
, .FNsetPos = demux_mpeg_setPos
, .FNgetAngle = demux_mpeg_getAngle
, .FNsetAngle = demux_mpeg_setAngle
, .FNfillBuffer = demux_mpeg_fillBuffer
, .FNpush   = demux_mpeg_es_push
, .FNparams = demux_mpeg_params
};

static const kav_demux_t demux_mpeg_ps =
{ .name = "MPEG Program Stream"
, .FNdelete = delete_demux_mpeg
, .FNreset  = demux_mpeg_reset
, .FNclear  = demux_mpeg_clear
, .FNgetPos = demux_mpeg_getPos
, .FNsetPos = demux_mpeg_setPos
, .FNgetAngle = demux_mpeg_getAngle
, .FNsetAngle = demux_mpeg_setAngle
, .FNfillBuffer = demux_mpeg_fillBuffer
, .FNpush   = demux_mpeg_ps_push
, .FNparams = demux_mpeg_params
};

static const kav_demux_t demux_mpeg_ts =
{ .name = "MPEG Transport Stream"
, .FNdelete = delete_demux_mpeg
, .FNreset  = demux_mpeg_reset
, .FNclear  = demux_mpeg_clear
, .FNgetPos = demux_mpeg_getPos
, .FNsetPos = demux_mpeg_setPos
, .FNgetAngle = demux_mpeg_getAngle
, .FNsetAngle = demux_mpeg_setAngle
, .FNfillBuffer = demux_mpeg_fillBuffer
, .FNpush   = demux_mpeg_ts_push
, .FNparams = demux_mpeg_params
};

/**
 * Searches the first ?kB of the buffer to see if it contains either
 * a sequence header (video stream) or a pack header (program stream)
 * and returns the appropriate demuxer.
 *
 * note. VCD formatted mpeg files from a VCD MPEGAV directory have about
 *       70k of stuff before the mpeg data
 *
 * @param  buf   Pointer to start of input data.
 * @param  end   Pointer to end of input data.
 * @param  last  1 if we reached the end of the input stream, 0 otherwise
 */
#define CHECK_BUF 0x20000 // 128k
#define NUM_CONSECUTIVE_TS_PACKETS 7 // was 32 but 01c56b0dc1/ts

static int get_tspacket_size(const unsigned char *buf, int size)
{
  int i;
  if (size < (TS_FEC_PACKET_SIZE * NUM_CONSECUTIVE_TS_PACKETS))
    return 0;

  for(i=0; i<NUM_CONSECUTIVE_TS_PACKETS; i++)
  {
    if (buf[i * TS_PACKET_SIZE] != 0x47)
      goto try_fec;
  }

  return TS_PACKET_SIZE;

try_fec:
  for(i=0; i<NUM_CONSECUTIVE_TS_PACKETS; i++)
  {
    if (buf[i * TS_FEC_PACKET_SIZE] != 0x47)
      goto try_philips;
  }

  return TS_FEC_PACKET_SIZE;

try_philips:
  for(i=0; i<NUM_CONSECUTIVE_TS_PACKETS; i++)
  {
    if (buf[i * TS_PH_PACKET_SIZE] != 0x47)
      return 0;
  }

  return TS_PH_PACKET_SIZE;
}

ka_demux_select demux_selector_mpeg_dvd(ka_demux_t** ppdemux, ka_demux_params_t* params)
{
  *ppdemux = new_demux_mpeg(&demux_mpeg_ps, params->pErrorBlock, params->pInput, params->pStack, 0);
  if (*ppdemux == NULL)
    return ka_demux_select_error;

  data_t* data = (data_t*) *ppdemux;
  data->isDVD = true;

  return ka_demux_select_found;
}

ka_demux_select demux_selector_scanForMpeg(ka_demux_t** ppdemux, ka_demux_params_t* params)
{
  const uint8_t *buf, *end;
  const uint8_t *p;
  int done;

  ka_input_seek(params->pInput, 0);
  ka_buffer_clear(params->pBuffer);

  // While not enough data
  do
  {
    if (ka_input_buffer(params->pInput, params->pBuffer, &done) < 0)
      return ka_demux_select_error;
    if (done)
      break; // Short file

    if (!ka_buffer_getUnstackedBlock(params->pBuffer, &buf, &end))
      continue;
  }
  while ((end - buf) < CHECK_BUF);

  // Scan the buffer for TS
  for (p = buf; p <= (end - 4); p++)
  {
    if (p[0] == 0x47)
    {
      int size = get_tspacket_size(p, end - p);
      if (size > 0)
      {
        *ppdemux = new_demux_mpeg(&demux_mpeg_ts, params->pErrorBlock, params->pInput, params->pStack, size);
        if (*ppdemux == NULL)
          return ka_demux_select_error;

        return ka_demux_select_found;
      }
    }
  }

  // Scan the buffer for PES, ES (only after TS cd. false positives)
  for (p = buf; p <= (end - 4); p++)
  {
    if ((p[0] == 0) && (p[1] == 0) && (p[2] == 1))
    {
      if (p[3] == 0xba)
      {
        *ppdemux = new_demux_mpeg(&demux_mpeg_ps, params->pErrorBlock, params->pInput, params->pStack, 0);
        if (*ppdemux == NULL)
          return ka_demux_select_error;

        return ka_demux_select_found;
      }
      else if (p[3] == 0xb3)
      {
        *ppdemux = new_demux_mpeg(&demux_mpeg_es, params->pErrorBlock, params->pInput, params->pStack, 0);
        if (*ppdemux == NULL)
          return ka_demux_select_error;

        return ka_demux_select_found;
      }
    }
  }

  return ka_demux_select_none;
}
