/*
 * Analogue TV Player plugin for VDR
 *
 * Copyright (C) 2003 .. 2006 Andreas Kool <akool@gmx.de>
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * Version: $Id: player-analogtv.c,v 1.98 2005/01/13 20:04:09 akool Exp $
 * Release: $Name:  $
 *
 */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <sys/soundcard.h>

#include </usr/include/libdvb/transform.h>
#undef MIN
#undef MAX

#include "player-analogtv.h"
#include "msg.h"
#include "ivtv.h"

#define MAXWARN           5
#define MAXRETRIES        3

static p2p p2t;

static UC *rbuffer[NUM_BUFS];

static UI  buf_read;
static UI  buf_write;
static UI  buf_read_pos;
static UI  buf_write_pos;

static int full_buffers;
static int buffered_bytes;
static int max_used;
static int mutex, ts, pes;
static long long unsigned int sts, spes;
static int ringbuffer_warned, ready_warned, read_warned;

static char *snddev[] = SOUND_DEVICE_NAMES;

void d(int level, const char *fmt, ...)
{
  auto char    t[BUFSIZ];
  auto va_list ap;
  auto time_t  now;


  va_start(ap, fmt);

  if (s.debug >= level)
    switch (s.logger) {
      case STDOUT : time(&now);
#if 0                    
      	   	    vsnprintf(t + 19, sizeof t - 19, fmt, ap);
                    strftime(t, sizeof t, "analogTV: %H:%M:%S", localtime(&now));
                    t[18] = ' ';
#else
      	   	    vsnprintf(t + 9, sizeof t - 9, fmt, ap);
                    strftime(t, sizeof t, "%H:%M:%S", localtime(&now));
                    t[8] = ' ';
#endif
                    
                    if (lv == 6) { // Linux 2.6
                      printf("\r%s\033[K\r\n", t);
                      fflush(stdout);
                    }
                    else
                      printf("%s\033[K\n", t);

                    break;

      case SYSLOG : vsnprintf(t + 10, sizeof t - 10, fmt, ap);
                    memcpy(t, "analogTV: ", 10);
                    syslog(LOG_DEBUG, "%s", t);
                    break;

      case ASFILE : if (lf == (FILE *)NULL)
                      lf = fopen("/tmp/analogTV.log", "a");
                    if (lf != (FILE *)NULL) {
                      vsnprintf(t + 20, sizeof t - 20, fmt, ap);
                      time(&now);
                      strftime(t, sizeof t, "%d.%m.%Y %H:%M:%S", localtime(&now));
                      t[19] = ' ';
                      fprintf(lf, "%s\n", t);
                    }
                    break;
    }

  va_end(ap);
}

void write_buffer(UC *data, int len, void *p)
{
  register int len2 = 0, x;

  ts++;
  sts += len;

  if (!ReaderLoopRunning) {
    if (++ringbuffer_warned < MAXWARN)
      d(2, "%d. TS frame dropped - encoder is shutting down", ringbuffer_warned);
    return;
  }

  while (mutex)
    usleep(USLEEP);

  mutex = 1;

  while (len > 0) {
    if (full_buffers == NUM_BUFS) {
      if (++ringbuffer_warned < MAXWARN)
        d(2, "%d. TS frame dropped (ringbuffer full)", ringbuffer_warned);

      break;
    }

    x = BUFFSIZE - buf_write_pos;

    if(x > len)
      x = len;

    fast_memcpy(rbuffer[buf_write] + buf_write_pos, data + len2, x);
    len2 += x;
    len -= x;

    buffered_bytes += x;

    if (buffered_bytes > max_used)
      max_used = buffered_bytes;

    buf_write_pos += x;

    if (buf_write_pos >= BUFFSIZE) { // block is full, find next!
      buf_write = (buf_write + 1) % NUM_BUFS;
      ++full_buffers;
      buf_write_pos = 0;
    }
  }

  mutex = 0;
}

int read_buffer(UC *data, int len)
{
  register int len2 = 0, x;


  if (!ReaderLoopRunning) {
    d(2, "read_buffer() no more data available -- encoder is shutting down!");
    return 0;
  }

  if (!rbuffer[0]) {
    if (!read_warned) {
      d(2, "read_buffer() no data available yet -- encoder is still starting up!");
      read_warned = 1;
    }
    return 0;
  }

  while (mutex)
    usleep(USLEEP);

  mutex = 1;

  while (len > 0) {
    if (!full_buffers) // no more data buffered!
      break;

    x = BUFFSIZE - buf_read_pos;

    if (x > len)
      x = len;

    fast_memcpy(data + len2, rbuffer[buf_read] + buf_read_pos, x);

    len2 += x;
    len -= x;

    buffered_bytes -= x;
    buf_read_pos += x;

    if (buf_read_pos >= BUFFSIZE) { // block is empty, find next!
      buf_read = (buf_read + 1) % NUM_BUFS;
      --full_buffers;
      buf_read_pos = 0;
    }
  }

  mutex = 0;
  return len2;
}

void flush_buffer()
{
  d(1, "flush_buffer()");

  while (mutex)
    usleep(USLEEP);

  mutex = 1;

  buf_read = buf_write = buf_read_pos = buf_write_pos = 0;
  full_buffers = buffered_bytes = max_used = 0;
  ringbuffer_warned = ready_warned = read_warned = ts = sts = pes = spes = 0;

  mutex = 0;
}

int v4l_version()
{
  register int    i, fd, cc = -1, amodule = 0;
  auto     struct v4l2_capability vcap;


  // maybe, the last instance of the encoder is still going down ...

  for (i = 0; i < 100; i++) {
    if ((fd = open(video_device, O_RDWR)) >= 0)
      break;

    if (errno != EBUSY)
      return errno == ENODEV ? -2 : -1;

    usleep(USLEEP);
  }

  if (i)
    d(2, "usleep'ed %d us before %s was ready", i * USLEEP, video_device);

  if (IOCTL(fd, VIDIOC_QUERYCAP, &vcap) == -1) // Supposedly no V4L2 device, we'll assume V4L
    cc = 1;
  else
    cc = 2;

  close(fd);

  return cc;
}

int get_inputs()
{
  register int    i, fd, cc = -1, amodule = 0;
  auto     struct v4l2_input input;

  if ((fd = open(video_device, O_RDWR)) >= 0) {
    for (i = 0;; i++) {
      memset(&input, 0, sizeof(input));
      input.index = i;

      if (IOCTL(fd, VIDIOC_ENUMINPUT, &input) == -1)
        break;

      switch (i) {
        case 0 : if (!memcmp(input.name, "DVB", 3))
                   amodule++;
                 break;

        case 1 : if (!memcmp(input.name, "Television", 10) ||
               	     !memcmp(input.name, "ANALOG", 6))
                   amodule++;
        	 break;
      }

      if (i < MAXSOURCES)
        strcpy(sources[i], input.name);

      d(1, "Detected Input#%d: %s", i, input.name);
    }

    if ((i == 2) && (amodule == 2)) {
      d(1, "Detected Siemens DVB-C analog module");
      analogModule = 1;
    }

    close(fd);
  }
}

int audio_free()
{
  register int i, fd;

  if (*s.audio_device == '/') { // this is'nt possible with ALSA yet
    // maybe, the last instance of the encoder is still going down ...

    for (i = 0; i < 100; i++) {
      if ((fd = open(s.audio_device, O_RDWR)) >= 0)
        break;

      if (errno != EBUSY)
        return -1;

      usleep(USLEEP);
    }

    if (i)
      d(2, "usleep'ed %d us before %s was ready", i * USLEEP, s.audio_device);
  }

  close(fd);
  return 0;
}

// Checks:
#define REQUIRED_ANALOGTVPATCHLEVEL 9
#define REQUIRED_VIDEO4LINUX        2
#define ALLOWED_CPU_LOAD           70
#define ALLOWED_FRAME_DROP_RATE     1
//

static void report(int rmax, int dropped)
{
  auto   FILE       *f, *fo;
  auto   int         n;
  auto   double      n1, n2, n3;
  auto   long long   n0, n4, n5;
  auto   time_t      now;
  auto   struct sysinfo info;
  extern const char *VERSION;


  if (s.report) {

    if ((fo = fopen("/tmp/analogTV.rep", "a")) != (FILE *)NULL) {
      now = time((time_t)NULL);
      fprintf(fo, "\nvdr-analogTV report %s\n", ctime(&now));

      fprintf(fo, "VDR version:%s", vdrver);

#ifdef HAVE_BEAUTYPATCH
      fprintf(fo, ", with BEAUTYPATCH");
#endif

      if (*ac3ver)
        fprintf(fo, ", with AC3overDVB patch version %s", ac3ver);

      fprintf(fo, "\n\tanalogTV plugin version:%s\n", VERSION);

      fprintf(fo, "Hardware:\n");
      fprintf(fo, "\tCPU:%s\n", CPU);
      fprintf(fo, "\tCPUspeed:%.3f MHz\n", CPUspeed);
      fprintf(fo, "\tVideo4Linux:V4L%d\n", v4l);

      if (v4l != REQUIRED_VIDEO4LINUX)
        fprintf(fo, "MISUSE: You are using V4L %d, required is V4L %d\n", v4l, REQUIRED_VIDEO4LINUX);

      fprintf(fo, "\tCapture device:%s\n", capdev);

      fprintf(fo, "MPEG-Encoder parameters:\n");

      fprintf(fo, "\tVideo device:%s\n", video_device);

      fprintf(fo, "\tImage size:%s\n", imagesizes[s.image_size]);
      fprintf(fo, "\tVideo bit rate:%.3f Mbit/s\n", (double)s.video_bit_rate / 10.0);
      fprintf(fo, "\tAudio bit rate:%d kbit/s\n", s.audio_mode ? s.audio_bit_rate : s.audio_bit_rate * 2);

      fprintf(fo, "\tVolume:%d\n", s.mixer_volume);
      fprintf(fo, "\tVideo source:%s\n", sources[videosource]);

      if (isPVR) {
        fprintf(fo, "\tFrames per GOP:%d\n", s.framespergop);
        fprintf(fo, "\tGOP closed:%s\n", s.gop_closure ? "YES" : "NO");
        fprintf(fo, "\tB frames/GOP:%d\n", s.bframes);
        fprintf(fo, "\tDNR mode:%s\n", dnr_modes[s.dnr_mode]);
        fprintf(fo, "\tDNR spatial:%d\n", s.dnr_spatial);
        fprintf(fo, "\tDNR temporal:%d\n", s.dnr_temporal);
        fprintf(fo, "\tDNR type:%d\n", s.dnr_type);

        fprintf(fo, "\tsync on next GOP hack:%s\n", s.syncHack ? "YES" : "NO");
        fprintf(fo, "\tRemove wining hack:%s\n", s.winingHack ? "YES" : "NO");
        fprintf(fo, "\tA/V sync hack:%s\n", s.avHack ? "YES" : "NO");
        fprintf(fo, "\tpicture properties hack:%s\n", s.propHack ? "YES" : "NO");
      }
      else {
        fprintf(fo, "\tclipping:%d\n", s.clipping);
      	if (s.clipping)
          fprintf(fo, "\tGrab size:%s\n", grabsizes[s.image_size]);

        fprintf(fo, "\tFilter:%s\n", filters[s.filter]);
      	if (s.motion[0])
          fprintf(fo, "\tMotion compensation:%d,%d\n", s.motion[0], s.motion[1]);
        else
          fprintf(fo, "\tMotion compensation disabled\n");

        fprintf(fo, "\tAudio mode:%s\n", audiomodes[s.audio_mode]);
        fprintf(fo, "\tAudio sample rate:%s\n", samplerates[s.sample_rate]);

      	fprintf(fo, "\tAdditional options:%s\n", MP1EOPTS);
      	fprintf(fo, "\tGOP sequence:%s\n", gops[s.gop]);
        fprintf(fo, "\tAudio device:%s\n", s.audio_device);
        fprintf(fo, "\tMixer device:%s\n", s.mixer_device);
      	fprintf(fo, "\tMixer line:%s\n", SoundDevices[s.mixer_line]);

      	if (s.test) {
          fprintf(fo, "\tTest:%s\n", tests[s.test]);
          fprintf(fo, "MISUSE: Please disable Testmode (in the OSD-Setup)\n");
        }

        fprintf(fo, "\tLink:%s()\n", s.link ? "select" : "read");
      }

      fprintf(fo, "\tdebug:%d\n", s.debug);

      if (wide)
        fprintf(fo, "\twide\n");

      fprintf(fo, "Misc settings:\n");
      fprintf(fo, "\tBrightness:%d\n", s.brightness);
      fprintf(fo, "\tContrast:%d\n", s.contrast);
      fprintf(fo, "\tColour:%d\n", s.colour);
      fprintf(fo, "\tHue:%d\n", s.hue);

      if (s.mpeg_encoder == MP1E) {
        if ((f = fopen("/tmp/mp1e.log", "r")) != (FILE *)NULL) {
          if (fscanf(f, "%d %llu %lf %lf %lf %llu %llu\n", &n, &n0, &n1, &n2, &n3, &n4, &n5) == 7) {
            fprintf(fo, "Encoder statistics:\n");
            fprintf(fo, "\tmp1e patchlevel:%d\n", n);

            if (n != REQUIRED_ANALOGTVPATCHLEVEL)
              fprintf(fo, "\n\tMISUSE: You are using mp1e, analogTV-Patchlevel #%d, required is analogTV-Patchlevel #%d\n", n, REQUIRED_ANALOGTVPATCHLEVEL);

            fprintf(fo, "\t%llu bytes out (%.1f MB)\n", n0, n0 / (double)(1 << 20));
            fprintf(fo, "\tsystem load: min=%4.1f%% max=%4.1f%% avg=%4.1f%%\n", n1, n2, n3);

            if (n3 > ALLOWED_CPU_LOAD)
              fprintf(fo, "\n\tMISUSE: Your system load is far too high, please stop unimportant processes or lower the video bit rate!\n");

            fprintf(fo, "\t%llu video frames total, %llu video frames dropped (%.2f %%)\n", n4, n5, 100.0 * n5 / n4);

            if (100.0 * n5 / n4 > ALLOWED_FRAME_DROP_RATE)
              fprintf(fo, "\n\tMISUSE: Too many frame drops! Try to finetune these station\n");

          }
          fclose(f);
        }
        else
          fprintf(fo, "MISUSE: Encoder statistics not available - wrong (or unpatched) mp1e-version?\n");
      }

      if ((s.mpeg_encoder != FFMPEGTS) && (s.mpeg_encoder != PVRTS) && (s.mpeg_encoder != NVRECTS)) {
        fprintf(fo, "\tRingbuffer max usage: %d byte (out of %d byte) = %.1f%%\n",
          max_used, NUM_BUFS * BUFFSIZE, (double)(max_used * 100.0 / (NUM_BUFS * BUFFSIZE)));
        fprintf(fo, "\tLargest data packet from %s was %d byte long\n", (s.mpeg_encoder == PVRPES) ? "PVR-card" : "MPEG-Encoder", rmax);
        fprintf(fo, "\t%d PES frame(s), %d TS frame(s) processed (total, incl. dropped)\n", pes, ts);
        fprintf(fo, "\t%llu byte PES, %llu byte TS (total, incl. dropped)\n", spes, sts);
        fprintf(fo, "\t%d TS frame(s) dropped (ringbuffer was full)\n", ringbuffer_warned);
        fprintf(fo, "\t%d PES frame(s) dropped\n", ready_warned);
        fprintf(fo, "\t%d PES frame(s) dropped while not on a analogue channel\n", dropped);
      }

      if (!sysinfo(&info)) {
        fprintf(fo, "Host statistics:\n");

        fprintf(fo, "\tUptime = %ds\n", info.uptime);
        fprintf(fo, "\tLoad: 1 min %d / 5 min %d / 15 min %d\n", info.loads[0], info.loads[1], info.loads[2]);
        fprintf(fo, "\tRAM: total %d / free %d / shared %d\n", info.totalram, info.freeram, info.sharedram);
        fprintf(fo, "\tMemory in buffers = %d\n", info.bufferram);
        fprintf(fo, "\tSwap: total %d / free %d\n", info.totalswap, info.freeswap);
        fprintf(fo, "\tNumber of processes = %d\n", info.procs);
      }

      fclose(fo);
    }
  }

  if (s.mpeg_encoder == MP1E) {
    if ((f = fopen("/tmp/mp1e.log", "r")) != (FILE *)NULL) {
      if (fscanf(f, "%d %llu %lf %lf %lf %llu %llu\n", &n, &n0, &n1, &n2, &n3, &n4, &n5) == 7) {
        d(1, "%llu bytes out (%.1f MB)", n0, n0 / (double)(1 << 20));
        d(1, "system load: min=%4.1f%% max=%4.1f%% avg=%4.1f%%", n1, n2, n3);

        if (!sysinfo(&info))
          d(1, "Load: 1 min %d / 5 min %d / 15 min %d", info.loads[0], info.loads[1], info.loads[2]);

        d(1, "%llu video frames total, %llu video frames dropped (%.2f %%)", n4, n5, 100.0 * n5 / n4);
      }
      fclose(f);
    }
  }

  if ((s.mpeg_encoder != FFMPEGTS) && (s.mpeg_encoder != PVRTS) && (s.mpeg_encoder != NVRECTS)) {
    d(1, "Ringbuffer max usage: %d/%d byte = %.1f%%",
      max_used, NUM_BUFS * BUFFSIZE, (double)(max_used * 100.0 / (NUM_BUFS * BUFFSIZE)));
    d(1, "Largest data packet from %s was %d byte long", (s.mpeg_encoder == PVRPES) ? "PVR-card" : "MPEG-Encoder", rmax);
    d(1, "%d data packets, %d TS frames processed", pes, ts);
    d(1, "%llu byte PES, %llu byte TS", spes, sts);
    d(1, "%d TS frame(s) dropped (ringbuffer was full)", ringbuffer_warned);
    d(1, "%d data packets dropped", ready_warned);
    d(1, "%d data packets dropped while not on a analogue channel", dropped);
  }
}

static int action(int halt, int Apid, int Vpid)
{
  register int    a, b, f, cc;
  auto     float  ar;
  auto     char   cmd[512], s0[64], s1[64], s3[64];

  switch (s.sample_rate) {
    case 0 : ar = 22.05; break;
    case 1 : ar = 32; break;
    case 2 : ar = 44.1; break;
    case 3 : ar = 48; break;
    case 4 : ar = 96; break;
  }

  switch (s.mpeg_encoder) {
    case MP1E      : *s0 = 0;
                     if (s.debug)
                       // FIXME: More than 1 is really too much for stdout :-(
                       // for (i = 0; i < s.debug; i++)
                       strcat(s0, " -v");

                     *s0 = 0; //FIXME

                     *s1 = 0;
                     if (s.clipping)
                       sprintf(s1, " -G %s", grabsizes[s.image_size]);

                     b = s.video_bit_rate;

                     f = s.filter + 1;

                     if ((f == 4) || // is no longer available
                         (f == 6) || // Sorry, progressive mode out of order
                         (f == 7))   // Sorry, progressive mode out of order
                       f = 8;        // CM_YUV_VERTICAL_DECIMATION

                     if ((f == 6) || (f == 7)) {

                       if (v4l == 1)
                         f = 8; // YUYV progressive not possible with V4L1
                       else
                         b *= 2;     // double the bit rate
                     }

                     *s3 = 0;
                     if (s.motion[0])
                       sprintf(s3, " -R %d,%d", s.motion[0], s.motion[1]);

                     a = s.audio_mode;

                     switch (s.analysis) {
                       case 1 : a += 10; break;
                       case 2 : a += 20; break;
                     }

                     sprintf(cmd, "mp1e -m %d %s%s%s%s -t %d -g %s -p %s -c %s -x %s -d %d -a %d -b %d -B %d%s -r %d,%d -s %s -S %2.1f -F %d%s%s%s%s -o %s &",
                      /* -m */ VIDEO | AUDIO, // s.encode,
                      /* -j */ halt ? "-j " : "",
                      /* -l */ wide ? "-l " : "",
                               MP1EOPTS,
                               extraOpts,
                      /* -t */ 0x100 + (s.test ? (1 << (s.test - 1)) : 0),
                      /* -g */ gops[s.gop],
                      /* -p */ s.audio_device,
                      /* -c */ video_device,
                      /* -x */ s.mixer_device,
                      /* -d */ videosource,
                      /* -a */ a,
                      /* -b */ b * 100000,
                      /* -B */ s.audio_mode ? s.audio_bit_rate : s.audio_bit_rate * 2,
                      /* -v */ s0,
                      /* -r */ s.mixer_line, s.mixer_volume,
                      /* -s */ imagesizes[s.image_size],
                      /* -S */ ar,
                      /* -F */ f,
                      /* -G */ s1,
                      /* -R */ s3,
                      /* -X */ s.mpeg ? " -0 -X 3" : "",
                      /* -K */ s.deinterlace ? " -K1" : "",
                      /* -o */ FIFO);
                     break;

    case FFMPEGPES :
    case FFMPEGTS  : sprintf(cmd, "ffmpeg -f %s -s %s -ad %s:%d -ar %2.1f -vd %s -ac %d -b %d -ab %d -y -hq -re -title %s -author %s -copyright %s -comment %s -aspect %s -tvstd %s %s%s &",
                      /* -f           */ (s.mpeg_encoder == FFMPEGTS) ? "mpegts" : "mpeg1video",
                      /* -s           */ imagesizes[s.image_size],
                      /* -ad          */ s.audio_device, s.mixer_line,
                      /* -ar          */ ar,
                      /* -vd          */ video_device,
                      /* -ac          */ !s.audio_mode ? 2 : 1,
                      /* -b           */ s.video_bit_rate * 100,
                      /* -ab          */ s.audio_bit_rate,
                      /* -title       */ "analogTV",
                      /* -author      */ "analogTV",
                      /* -copyright   */ "GPL",
                      /* -comment     */ "analogTV",
                      /* -aspect      */ wide ? "16:9" : "4:3",
                      /* -tvstd       */ tvnorms[videonorm],
                      /* -deinterlace */ s.deinterlace ? "-deinterlace " : "",
                                         FIFO);
                     break;

    case FAME      : sprintf(cmd, "fame -d %s -b %d -u %d %s &",
                      /* -d */ video_device,
                      /* -b */ s.video_bit_rate * 100000,
                      /* -u */ s.audio_bit_rate,
                               FIFO);
                     break;

    case TEST      : sprintf(cmd, "cat /tmp/test.mpeg >> %s &", FIFO);
                     break;

    case LAVREC    : sprintf(cmd, "lavrec -f a -i p -g 352x288 -q 50 -B --software-encoding --file-flush 0 -r 44100 -s -l 80 -R l -c 2 %s &",
                      /* -o */ FIFO);
                     break;

    case MENCODER  : sprintf(cmd, "mencoder -of mpeg -tv driver=v4l:width=352:height=288 -ovc lavc -lavcopts vcodec=mpeg1video:vbitrate=2300 -oac mp3lame -lameopts cbr:br=64 -vf crop=304:256:24:18,pp=lb -o %s tv:// &",
                      /* -o */ FIFO);
                     break;

    case NVRECPES  :
    case NVRECTS   : sprintf(cmd, "ffmpegrec%s -d %s%s -mixvol %s:%s:%d -v %s -w %d -h %d -norm %s -input %s -ff %s -vc mpeg1video -ac mp2 -vb %d -ab %d -ar %2.1f -vg %d -o %s &",
                      /* -deint     */ s.deinterlace ? " -deint bob" : "",
                      /* -d         */ s.audio_device,
                      /* -s         */ s.audio_mode ? "" : " -s",
                      /* -mixvol    */ s.mixer_device, snddev[s.mixer_line], s.mixer_volume,
                      /* -v         */ video_device,
                      /* -w         */ atoi(imagesizes[s.image_size]),
                      /* -h         */ atoi(imagesizes[s.image_size] + 4),
                      /* -norm      */ tvnorms[videonorm],
                      /* -input     */ sources[videosource],
                      /* -ff        */ "vcd", // FIXME (s.mpeg_encoder == NVRECTS) ? "mpegts" : "mpeg1video",
                      /* -vb        */ s.video_bit_rate * 100,
                      /* -ab        */ s.audio_mode ? s.audio_bit_rate : s.audio_bit_rate * 2,
                      /* -ar        */ ar,
                      /* -vg        */ (s.gop == 1) ? 0 : 25,
                      /* -o         */ FIFO);

                      /* -N 24 */

#if 0
    case PVRTS     : sprintf(cmd, "pes2ts2 %s %d %d 2>/dev/null >> %s", video_device, Apid, Vpid, FIFO);
                     break;
#endif
  }

  d(1, cmd);

  if (s.mpeg_encoder == MP1E)
    unlink("/tmp/mp1e.log");

  pid = fork();

  if (pid == -1)
    return -1;

  if (!pid) { // child
    register int i, mx = getdtablesize();

    for (i = STDERR_FILENO + 1; i < mx; i++)
      close(i); // close all dup()'ed filedescriptors

    if (s.mpeg_encoder != PVRTS) {
      if ((cc = nice(PRIORITY)) == -1)
        d(0, "nice() failed, %d:%s", errno, strerror(errno));
      else
        d(1, "Sucessfully set encoder priority to %d", PRIORITY);
    }

    execle("/bin/sh", "sh", "-c", cmd, 0, environ);
    d(0, "exec(%s) failed, %d:%s", cmd, errno, strerror(errno));
    exit(127);
  }
  else // parent
    d(2, "encoder started, pid=%d", pid);

  return 0;
}

int runEncoder(int Apid, int Vpid)
{
  register int    fifo, r, rmax, n, cc, i;
  register int    dropped, dropwarned;
  register UC    *p;
  extern   char **environ;
  auto     UC     buffer[BUFFSIZE];
  auto     fd_set readmask;
  auto     int    retries = 0, status;


  cc = mknod(FIFO, S_IFIFO | S_IRUSR | S_IWUSR, 0);

  if (cc && (errno == EEXIST)) // named pipe existiert bereits
    cc = 0;

  if (!cc) {

    if (!action(1, Apid, Vpid)) {

      if ((s.mpeg_encoder != FFMPEGTS) && (s.mpeg_encoder != NVRECTS)) { // ffmpeg/TS und nvrec/TS geben direkt TS aus :-)
        for (i = 0; i < NUM_BUFS; i++)
          rbuffer[i] = (UC *)malloc(BUFFSIZE);

        flush_buffer();
        r = 0;
        dropped = 0;
        dropwarned = 0;

        init_p2p(&p2t, write_buffer, 2048);
        p2t.pida = Apid;
        p2t.pidv = Vpid;
        p2t.acounter = 0;
        p2t.vcounter = 0;
        p2t.count1 = 0;
        p2t.count0 = 0;

retry:
        fifo = open(FIFO, O_RDONLY | s.link ? O_NONBLOCK : 0);

        if (fifo >= 0) {
          rmax = 0;

          FD_ZERO(&readmask);
          FD_SET(fifo, &readmask);

          n = fifo + 1;

          while (ReaderLoopRunning) {

            if (s.link)
              if (select(n, &readmask, NULL, NULL, NULL) == -1) {
                if (errno == EINTR)
                  d(1, "Encoder has stopped");
                else
                  d(1, "select(%d) failed, %d:%s", n, errno, strerror(errno));
                break;
              }

            r = read(fifo, buffer, BUFFSIZE);

            if (r == -1) {
              d(1, "read(%d) failed, %d:%s", fifo, errno, strerror(errno));
              break;
            }

            if (Restart) {
              Restart = 0;

              close(fifo);

              status = kill(pid, SIGINT);
              d(1, "kill(%d)=%d %s", pid, status, status ? strerror(errno) : "");

              if ((i = waitpid(pid, &status, 0)) >= 0) {

                d(1, "waitpid(%d)=%d, %d:%s", pid, i, errno, strerror(errno));

                if (v4l_version() > 0) {
                  if (!audio_free()) {

                    // FIXME: flush_buffer();

                    if (!action(1, Apid, Vpid))
                      goto retry;
                    else {
                      d(0, "fork() failed, %d:%s", errno, strerror(errno));
                      cc = -1;
                    }
                  }
                  else
                    d(0, "Can't open \"%s\", %d:%s", s.audio_device, errno, strerror(errno));
                }
                else
                  d(0, "Can't open \"%s\", %d:%s", video_device, errno, strerror(errno));
              }
              else {
                d(0, "waitpid(%d) failed, %d:%s", pid, errno, strerror(errno));
                cc = -1;
              }
            }

            if (!r) { // EOF - Encoder died or stopped
              if (++retries < MAXRETRIES) {
                d(1, "Encoder died - restarting");

                close(fifo);

                if (!action(retries < MAXRETRIES - 1, Apid, Vpid))
                  goto retry;
              }
              else {
                d(0, "MAXRETRIES (%d) reached - cannot continue", MAXRETRIES);
                cc = -1;
                break;
              }
            }
            else {
              if (dropPES) {
                if (!dropwarned) {
                  dropwarned++;
                  d(2, "Beginning to drop PES frames - not tuned to a analogue channel");
                }

                dropped++;
              }
              else {
                if (dropwarned) {
                  dropwarned = 0;
                  d(2, "Ending to drop PES frames - now tuned to a analogue channel");
                }

                if (r > rmax)
                  rmax = r;

                pes++;
                spes += r;

                kpes_to_ts(&p2t, buffer, r);
              }
            }
          }

          close(fifo);

          report(rmax, dropped);
        }
        else {
          d(0, "Can't open \"%s\", %d:%s", FIFO, errno, strerror(errno));
          cc = -1;
        }

        for (i = 0; i < NUM_BUFS; i++) {
          if (rbuffer[i])
            free(rbuffer[i]);

          rbuffer[i] = NULL;
        }
      }
    }
    else {
      d(0, "fork() failed, %d:%s", errno, strerror(errno));
      cc = -1;
    }
  }
  else {
    d(0, "mknod(%s) failed, %d:%s", FIFO, errno, strerror(errno));
    cc = -1;
  }

  return cc;
}

int readPVR(int Apid, int Vpid)
{
  register int  f, r = 0, rv, rmax = 0, cc = 0, i;
  register int  dropped = 0, dropwarned = 1, skipped = 0;
  auto     UC   buffer[BUFFSIZE], sx[128], cmd[256];
  auto	   FILE *fi;

  for (i = 0; i < NUM_BUFS; i++)
    rbuffer[i] = (UC *)malloc(BUFFSIZE);

  init_p2p(&p2t, write_buffer, 2048);
  p2t.pida = Apid;
  p2t.pidv = Vpid;
  p2t.acounter = 0;
  p2t.vcounter = 0;
  p2t.count1 = 0;
  p2t.count0 = 0;

  while (ReaderLoopRunning) {
    if (dropPES) {
      if (!dropwarned) {
        dropwarned++;

        if (s.remux)
          pclose(fi);
        else
          close(f);

        d(0, "PVR reader suspended");
      }

      dropped++;

      usleep(999999);
    }
    else {
      if (dropwarned) {
        dropwarned = 0;

	if (s.remux) {
          sprintf(cmd, "replex%s -t MPEG2 -i %s %s", ((s.remux == 2) ? " -f" : ""), (s.mpeg_encoder == PVRTS) ? "TS" : "PS", video_device);

          if ((fi = popen(cmd, "r")) == (FILE *)NULL)
            f = -1;
          else
            f = fileno(fi);
	}
        else
          f = open(video_device, O_RDONLY);

        if (f < 0) {
          d(0, "cannot open %s, %d:%s", video_device, errno, strerror(errno));
          cc = -2;
          break;
        }

        d(0, "PVR reader continuing");
      }

      r = read(f, buffer, BUFFSIZE);

      if (r == -1) {
        d(1, "cannot read %s, %d:%s", video_device, errno, strerror(errno));
        cc = -1;
        break;
      }

      if (r > rmax)
        rmax = r;

      pes++;
      spes += r;

      if (r > 0)
        kpes_to_ts(&p2t, buffer, r); // PES -> TS
    }
  }

  if (s.remux)
    pclose(fi);
  else
    close(f);

  report(rmax, dropped);

  for (i = 0; i < NUM_BUFS; i++) {
    if (rbuffer[i])
      free(rbuffer[i]);

    rbuffer[i] = NULL;
  }

  return cc;
}

int tune(int frequency, int freqOffset)
{
  register int fd, cc = 0;
  int      freq = frequency, ofs = freqOffset;
  struct   v4l2_frequency f;

  ofs = (ofs - 16) * FREQDELTA; // 16 is "ground zero"

  freq = (freq + ofs) * 16 / 1000;

  if ((fd = open(video_device, O_RDWR)) >= 0) {
    if (v4l == 2) {
      memset(&f, 0, sizeof(f));
      f.tuner = analogModule; // FIXME??
      f.type = V4L2_TUNER_ANALOG_TV;
      f.frequency = freq;

      cc = IOCTL(fd, VIDIOC_S_FREQUENCY, &f);
    }
    else
      cc = IOCTL(fd, VIDIOCSFREQ, &freq);

    if (cc)
      d(0, "tune(%d, %d) failed, %d:%s", frequency, freqOffset, errno, strerror(errno));
    else
      d(1, "tune(%d) [frequency=%d, freqOffset=%d, ofs=%d]",
        freq, frequency, freqOffset, ofs);

    close(fd);
  }

  return cc;
}

int setNorm(int norm)
{
  register int   fd, cc = 1;
  auto     v4l2_std_id id = V4L2_STD_PAL;

  if ((fd = open(video_device, O_RDWR)) >= 0) {
    cc = 0;

    switch (norm) {
      case 0 : id = V4L2_STD_PAL; break;
      case 1 : id = V4L2_STD_NTSC; break;
      case 2 : id = V4L2_STD_SECAM; break;
    }

    if (v4l == 2)
      cc = IOCTL(fd, VIDIOC_S_STD, &id);
    else
      d(0, "setNorm() does not support V4L - Sorry");

    close(fd);
  }

  if (cc)
    d(0, "setNorm(fd=%d, %d:%s (0x%lx)) failed, %d:%s",
      fd, norm, tvnorms[norm], id, errno, strerror(errno));
  else
    d(1, "setNorm(%d:%s (0x%lx))", norm, tvnorms[norm], id);

  return cc;
}

// FIXME: The following routine isnt used at the moment.
//        The values must be converted to analogTV's internal
//        representation:
//
//          s.contrast = (int)(control.value / 1927.5);

int getPictureProperties(void)
{
  register int fd, cc = 1;
  struct v4l2_control control;
  struct video_picture pict;

  if ((fd = open(video_device, O_RDWR)) >= 0) {
    cc = 0;

    if (v4l == 2) {
      control.id = V4L2_CID_BRIGHTNESS;
      cc += IOCTL(fd, VIDIOC_G_CTRL, &control);
      s.brightness = control.value;

      control.id = V4L2_CID_HUE;
      cc += IOCTL(fd, VIDIOC_G_CTRL, &control);
      s.hue = control.value;

      control.id = V4L2_CID_SATURATION;
      cc += IOCTL(fd, VIDIOC_G_CTRL, &control);
      s.colour = control.value;

      control.id = V4L2_CID_CONTRAST;
      cc += IOCTL(fd, VIDIOC_G_CTRL, &control);
      s.contrast = control.value;
    }
    else {
      memset(&pict, 0, sizeof(pict));

      if (!(cc = IOCTL(fd, VIDIOCGPICT, &pict))) {
        s.brightness = pict.brightness;
        s.hue        = pict.hue;
        s.colour     = pict.colour;
        s.contrast   = pict.contrast;
      }
    }

    close(fd);
  }

  if (cc)
    d(0, "getPictureProperties() failed, %d:%s", errno, strerror(errno));
  else
    d(1, "getPictureProperties(%d, %d, %d, %d)", s.brightness, s.hue, s.colour, s.contrast);

  return cc;
}

int setPictureProperties(int brightness, int contrast, int colour, int hue)
{
  register int fd, cc = 1;
  sigset_t set, old;
  struct v4l2_control control;
  struct video_picture pict;

  if ((fd = open(video_device, O_RDWR)) >= 0) {
    cc = 0;

    if (v4l == 2) {
      control.id = V4L2_CID_BRIGHTNESS;

      control.value = (int)(brightness * 1927.5);
      cc += IOCTL(fd, VIDIOC_S_CTRL, &control);

      control.id = V4L2_CID_CONTRAST;

      control.value = (int)(contrast * 1927.5);
      cc += IOCTL(fd, VIDIOC_S_CTRL, &control);

      control.id = V4L2_CID_SATURATION;

      control.value = (int)(colour * 1927.5);
      cc += IOCTL(fd, VIDIOC_S_CTRL, &control);

      control.id = V4L2_CID_HUE;

      control.value = (int)(hue * 1927.5);
      cc += IOCTL(fd, VIDIOC_S_CTRL, &control);
    }
    else {
      pict.brightness = (int)(brightness * 1927.5);
      pict.contrast = (int)(contrast * 1927.5);
      pict.colour = (int)(colour * 1927.5);
      pict.hue = (int)(hue * 1927.5);
      pict.depth = 24;
      pict.palette = VIDEO_PALETTE_RGB24;

      sigemptyset(&set);          // BTTV hates signals during IOCTL
      sigaddset(&set, SIGCHLD);   // block SIGCHLD & SIGALRM
      sigaddset(&set, SIGALRM);   // for the time of ioctls
      sigprocmask(SIG_BLOCK, &set, &old);

      cc = IOCTL(fd, VIDIOCSPICT, &pict);

      sigprocmask(SIG_UNBLOCK, &old, NULL); // undo the signal blocking
    }

    close(fd);
  }

  if (cc)
    d(0, "setPictureProperties(fd=%d) failed, %d:%s", fd, errno, strerror(errno));
  else {
    d(1, "setPictureProperties(brightness=%d (%.1f%%), contrast=%d (%.1f%%), colour=%d (%.1f%%), hue=%d (%.1f%%))",
      brightness, (brightness / 34.0 * 100.0), contrast, (contrast / 34.0 * 100.0), colour, (colour / 34.0 * 100.0), hue, (hue / 34.0 * 100));

    Assign(0);
  }

  return cc;
}

void Assign(int mode)
{
  switch (mode) {
    case 0 : sc.brightness   = s.brightness;
             sc.contrast     = s.contrast;
             sc.colour       = s.colour;
             sc.hue          = s.hue;
             break;

    case 1 : sc.mpeg_encoder   = s.mpeg_encoder;
             sc.video_bit_rate = s.video_bit_rate;
             sc.audio_bit_rate = s.audio_bit_rate;
             sc.image_size     = s.image_size;
             sc.clipping       = s.clipping;
             sc.gop            = s.gop;
             sc.filter         = s.filter;
             sc.motion[0]      = s.motion[0];
             sc.motion[1]      = s.motion[1];
             sc.profile        = s.profile;
             sc.test           = s.test;
             strcpy(sc.audio_device, s.audio_device);
             strcpy(sc.mixer_device, s.mixer_device);
             sc.mixer_volume   = s.mixer_volume;
             sc.audio_mode     = s.audio_mode;
             sc.sample_rate    = s.sample_rate;
             break;

    case 2 : sc.mpeg_encoder   = s.mpeg_encoder;
             sc.video_bit_rate = s.video_bit_rate;
             sc.framespergop   = s.framespergop;
             sc.bframes        = s.bframes;
             sc.gop_closure    = s.gop_closure;
             sc.dnr_mode       = s.dnr_mode;
             sc.dnr_type       = s.dnr_type;
             sc.dnr_spatial    = s.dnr_spatial;
             sc.dnr_temporal   = s.dnr_temporal;
             sc.audio_bit_rate = s.audio_bit_rate;
             sc.mixer_line     = s.mixer_line;
             break;

    case 3 : sc.image_size     = s.image_size;
             break;

    case 4 : sc.mixer_volume   = s.mixer_volume;
             break;
  }
}

int PVRencoderSettingsChanged()
{
  return((s.mpeg_encoder   != sc.mpeg_encoder)   ||
         (s.video_bit_rate != sc.video_bit_rate) ||
         (s.framespergop   != sc.framespergop)   ||
         (s.bframes        != sc.bframes)        ||
         (s.gop_closure    != sc.gop_closure)    ||
         (s.dnr_mode       != sc.dnr_mode)       ||
         (s.dnr_type       != sc.dnr_type)       ||
         (s.dnr_spatial    != sc.dnr_spatial)    ||
         (s.dnr_temporal   != sc.dnr_temporal)   ||
         (s.audio_bit_rate != sc.audio_bit_rate) ||
         // (s.image_size     != sc.image_size)     ||
         (s.mixer_volume   != sc.mixer_volume)   ||
         (videonorm        != lastvideonorm));
}

int EncoderSettingsChanged()
{
  return((s.mpeg_encoder   != sc.mpeg_encoder)     ||
         (s.video_bit_rate != sc.video_bit_rate)   ||
         (s.audio_bit_rate != sc.audio_bit_rate)   ||
         (s.image_size     != sc.image_size)       ||
         (s.clipping       != sc.clipping)         ||
         (s.gop            != sc.gop)              ||
         (s.filter         != sc.filter)           ||
         (s.motion[0]      != sc.motion[0])        ||
         (s.motion[1]      != sc.motion[1])        ||
         (s.profile        != sc.profile)          ||
         (s.test           != sc.test)             ||
         (strcmp(s.audio_device, sc.audio_device)) ||
         (strcmp(s.mixer_device, sc.mixer_device)) ||
         (s.mixer_line     != sc.mixer_line)       ||
         (s.mixer_volume   != sc.mixer_volume)     ||
         (s.audio_mode     != sc.audio_mode)       ||
         (s.sample_rate    != sc.sample_rate));
}
