/*
 * pidscanner.c: IPTV plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: pidscanner.c,v 1.6 2008/02/02 21:06:14 rahrenbe Exp $
 */

#include "common.h"
#include "pidscanner.h"

#define PIDSCANNER_TIMEOUT_IN_MS   15000 /* 15s timeout for detection */
#define PIDSCANNER_APID_COUNT      5     /* minimum count of audio pid samples for pid detection */
#define PIDSCANNER_VPID_COUNT      10    /* minimum count of video pid samples for pid detection */
#define PIDSCANNER_PID_DELTA_COUNT 50    /* minimum count of pid samples for audio/video only pid detection */

cPidScanner::cPidScanner(void) 
: timeout(0),
  process(true),
  Vpid(0xFFFF),
  Apid(0xFFFF),
  numVpids(0),
  numApids(0)
{
  debug("cPidScanner::cPidScanner()\n");
  channel = cChannel();
}

cPidScanner::~cPidScanner() 
{
  debug("cPidScanner::~cPidScanner()\n");
}

void cPidScanner::SetChannel(const cChannel *Channel)
{
  if (Channel) {
     debug("cPidScanner::SetChannel(): %s\n", Channel->PluginParam());
     channel = *Channel;
     }
  else {
     debug("cPidScanner::SetChannel()\n");
     channel = cChannel();
     }
  Vpid = 0xFFFF;
  numVpids = 0;
  Apid = 0xFFFF;
  numApids = 0;
  process = true;
  timeout.Set(PIDSCANNER_TIMEOUT_IN_MS);
}

void cPidScanner::Process(const uint8_t* buf)
{
  //debug("cPidScanner::Process()\n");
  if (!process)
     return;

  // Stop scanning after defined timeout
  if (timeout.TimedOut()) {
     debug("cPidScanner::Process: Timed out determining pids\n");
     process = false;
  }

  // Verify TS packet
  if (buf[0] != 0x47) {
     error("Not TS packet: 0x%X\n", buf[0]);
     return;
     }

  // Found TS packet
  int pid = ts_pid(buf);
  int xpid = (buf[1] << 8 | buf[2]);

  // Check if payload available
  uint8_t count = payload(buf);
  if (count == 0)
     return;

  if (xpid & 0x4000) {
     // Stream start (Payload Unit Start Indicator)
     uchar *d = (uint8_t*)buf;
     d += 4;
     // pointer to payload
     if (buf[3] & 0x20)
        d += d[0] + 1;
     // Skip adaption field
     if (buf[3] & 0x10) {
        // Payload present
        if ((d[0] == 0) && (d[1] == 0) && (d[2] == 1)) {
           // PES packet start
           int sid = d[3];
           // Stream ID
           if ((sid >= 0xC0) && (sid <= 0xDF)) {
              if (pid < Apid) {
                 debug("Found lower Apid: 0x%X instead of 0x%X\n", pid, Apid);
                 Apid = pid;
                 numApids = 1;
                 }
              else if (pid == Apid) {
                 ++numApids;
                 debug("Incrementing Apids, now at %d\n", numApids);
                 }
              }
           else if ((sid >= 0xE0) && (sid <= 0xEF)) {
              if (pid < Vpid) {
                 debug("Found lower Vpid: 0x%X instead of 0x%X\n", pid, Vpid);
                 Vpid = pid;
                 numVpids = 1;
                 }
              else if (pid == Vpid) {
                 ++numVpids;
                 debug("Incrementing Vpids, now at %d\n", numVpids);
                 }
              }
           }
        if (((numVpids > PIDSCANNER_VPID_COUNT) && (numApids > PIDSCANNER_APID_COUNT)) ||
            (abs(numApids - numVpids) > PIDSCANNER_PID_DELTA_COUNT)) {
           // Lock channels for pid updates
           if (!Channels.Lock(true, 10)) {
              timeout.Set(PIDSCANNER_TIMEOUT_IN_MS);
              return;
              }
           cChannel *IptvChannel = Channels.GetByChannelID(channel.GetChannelID());
           int Apids[MAXAPIDS + 1] = { 0 }; // these lists are zero-terminated
           int Dpids[MAXDPIDS + 1] = { 0 };
           int Spids[MAXSPIDS + 1] = { 0 };
           char ALangs[MAXAPIDS][MAXLANGCODE2] = { "" };
           char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" };
           char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" };
           int Ppid = IptvChannel->Ppid();
           int Tpid = IptvChannel->Tpid();
           bool foundApid = false;
           if (numVpids <= PIDSCANNER_VPID_COUNT)
              Vpid = 0; // No detected video pid
           else if (numApids <= PIDSCANNER_APID_COUNT)
              Apid = 0; // No detected audio pid
           for (unsigned int i = 1; i < MAXAPIDS; ++i) {
               Apids[i] = IptvChannel->Apid(i);
               if (Apids[i] && (Apids[i] == Apid))
                  foundApid = true;
               }
           if (!foundApid)
              Apids[0] = Apid;
           for (unsigned int i = 0; i < MAXDPIDS; ++i)
               Dpids[i] = IptvChannel->Dpid(i);
           for (unsigned int i = 0; i < MAXSPIDS; ++i)
               Spids[i] = IptvChannel->Spid(i);
           debug("cPidScanner::Process(): Vpid=0x%04X, Apid=0x%04X\n", Vpid, Apid);
           IptvChannel->SetPids(Vpid, Ppid, Apids, ALangs, Dpids, DLangs, Spids, SLangs, Tpid);
           Channels.Unlock();
           process = false;
           }
        }
     }
}
