/*
 * device.c: The basic device interface
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: device.c 1.2 2002/06/16 13:23:31 kls Exp $
 */

#include "device.h"
#include <errno.h>
extern "C" {
#define HAVE_BOOLEAN
#include <jpeglib.h>
}
#include <linux/videodev.h>
#include <ost/sec.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "player.h"
#include "receiver.h"
#include "status.h"

#define DEV_VIDEO         "/dev/video"
#define DEV_OST_OSD       "/dev/ost/osd"
#define DEV_OST_FRONTEND  "/dev/ost/frontend"
#define DEV_OST_SEC       "/dev/ost/sec"
#define DEV_OST_DVR       "/dev/ost/dvr"
#define DEV_OST_DEMUX     "/dev/ost/demux"
#define DEV_OST_VIDEO     "/dev/ost/video"
#define DEV_OST_AUDIO     "/dev/ost/audio"

// The default priority for non-primary DVB cards:
#define DEFAULTPRIORITY  -2

#define TS_SIZE          188
#define TS_SYNC_BYTE     0x47
#define PID_MASK_HI      0x1F

// The maximum time we wait before assuming that a recorded video data stream
// is broken:
#define MAXBROKENTIMEOUT 30 // seconds

static const char *OstName(const char *Name, int n)
{
  static char buffer[_POSIX_PATH_MAX];
  snprintf(buffer, sizeof(buffer), "%s%d", Name, n);
  return buffer;
}

static int OstOpen(const char *Name, int n, int Mode, bool ReportError = false)
{
  const char *FileName = OstName(Name, n);
  int fd = open(FileName, Mode);
  if (fd < 0 && ReportError)
     LOG_ERROR_STR(FileName);
  return fd;
}

int cDevice::numDevices = 0;
int cDevice::useDevice = 0;
cDevice *cDevice::device[MAXDEVICES] = { NULL };
cDevice *cDevice::primaryDevice = NULL;

cDevice::cDevice(int n)
{
  frontendType = FrontendType(-1); // don't know how else to initialize this - there is no FE_UNKNOWN
  siProcessor = NULL;
  cardIndex = n;

  // Devices that are present on all card types:

  fd_frontend = OstOpen(DEV_OST_FRONTEND, n, O_RDWR);

  // Devices that are only present on DVB-S cards:

  fd_sec      = OstOpen(DEV_OST_SEC,      n, O_RDWR);

  // Devices that are only present on cards with decoders:

  fd_osd      = OstOpen(DEV_OST_OSD,    n, O_RDWR);
  fd_video    = OstOpen(DEV_OST_VIDEO,  n, O_RDWR | O_NONBLOCK);
  fd_audio    = OstOpen(DEV_OST_AUDIO,  n, O_RDWR | O_NONBLOCK);

  // Video format:

  SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3);

  // We only check the devices that must be present - the others will be checked before accessing them://XXX

  if (fd_frontend >= 0) {
     siProcessor = new cSIProcessor(OstName(DEV_OST_DEMUX, n));
     FrontendInfo feinfo;
     if (ioctl(fd_frontend, FE_GET_INFO, &feinfo) >= 0)
        frontendType = feinfo.type;
     else
        LOG_ERROR;
     }
  else
     esyslog("ERROR: can't open video device %d", n);

  dvrFileName = strdup(OstName(DEV_OST_DVR, CardIndex()));
  active = false;

  currentChannel = 0;
  frequency = 0;

  mute = false;
  volume = Setup.CurrentVolume;

  player = NULL;

  for (int i = 0; i < MAXRECEIVERS; i++)
      receiver[i] = NULL;
  ca = -1;
}

cDevice::~cDevice()
{
  delete dvrFileName;
  delete siProcessor;
  Detach(player);
  for (int i = 0; i < MAXRECEIVERS; i++)
      Detach(receiver[i]);
  // We're not explicitly closing any device files here, since this sometimes
  // caused segfaults. Besides, the program is about to terminate anyway...
}

void cDevice::SetUseDevice(int n)
{
  if (n < MAXDEVICES)
     useDevice |= (1 << n);
}

bool cDevice::SetPrimaryDevice(int n)
{
  n--;
  if (0 <= n && n < numDevices && device[n]) {
     isyslog("setting primary device to %d", n + 1);
     primaryDevice = device[n];
     return true;
     }
  esyslog("invalid devive number: %d", n + 1);
  return false;
}

cDevice *cDevice::GetDevice(int Ca, int Priority, int Frequency, int Vpid, bool *ReUse)
{
  if (ReUse)
     *ReUse = false;
  cDevice *d = NULL;
  int Provides[MAXDEVICES];
  // Check which devices provide Ca:
  for (int i = 0; i < numDevices; i++) {
      if ((Provides[i] = device[i]->ProvidesCa(Ca)) != 0) { // this device is basicly able to do the job
         //XXX+ dsyslog("GetDevice: %d %d %d %5d %5d", i, device[i]->HasDecoder(), device[i]->Receiving(), Frequency, device[i]->frequency);//XXX
         if (  (!device[i]->HasDecoder() // it's a "budget card" which can receive multiple channels...
               && device[i]->frequency == Frequency // ...and it is tuned to the requested frequency...
               && device[i]->Receiving() // ...and is already receiving
               // need not check priority - if a budget card is already receiving on the requested
               // frequency, we can attach another receiver regardless of priority
               )
            || (device[i]->HasDecoder() // it's a "full featured card" which can receive only one channel...
               && device[i]->frequency == Frequency // ...and it is tuned to the requested frequency...
               && device[i]->pidHandles[ptVideo].pid == Vpid // ...and the requested video PID...
               && device[i]->Receiving() // ...and is already receiving
               // need not check priority - if a full featured card is already receiving the requested
               // frequency and video PID, we can attach another receiver regardless of priority
               )
            ) {
            d = device[i];
            if (ReUse)
               *ReUse = true;
            break;
            }
         if (Priority > device[i]->Priority() // Priority is high enough to use this device
            && (!d // we don't have a device yet, or...
               || device[i]->Priority() < d->Priority() // ...this one has an even lower Priority
               || (device[i]->Priority() == d->Priority() // ...same Priority...
                  && Provides[i] < Provides[d->CardIndex()] // ...but this one provides fewer Ca values
                  )
               )
            )
            d = device[i];
         }
      }
  /*XXX+ too complex with multiple recordings per device
  if (!d && Ca > MAXDEVICES) {
     // We didn't find one the easy way, so now we have to try harder:
     int ShiftLevel = -1;
     for (int i = 0; i < numDevices; i++) {
         if (Provides[i]) { // this device is basicly able to do the job, but for some reason we didn't get it above
            int sl = device[i]->CanShift(Ca, Priority); // asks this device to shift its job to another device
            if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) {
               d = device[i]; // found one that can be shifted with the fewest number of subsequent shifts
               ShiftLevel = sl;
               }
            }
         }
     }
  XXX*/
  return d;
}

void cDevice::SetCaCaps(void)
{
  for (int d = 0; d < numDevices; d++) {
      for (int i = 0; i < MAXCACAPS; i++)
          device[d]->caCaps[i] = Setup.CaCaps[device[d]->CardIndex()][i];
      }
}

bool cDevice::Probe(const char *FileName)
{
  if (access(FileName, F_OK) == 0) {
     dsyslog("probing %s", FileName);
     int f = open(FileName, O_RDONLY);
     if (f >= 0) {
        close(f);
        return true;
        }
     else if (errno != ENODEV && errno != EINVAL)
        LOG_ERROR_STR(FileName);
     }
  else if (errno != ENOENT)
     LOG_ERROR_STR(FileName);
  return false;
}

bool cDevice::Initialize(void)
{
  numDevices = 0;
  for (int i = 0; i < MAXDEVICES; i++) {
      if (useDevice == 0 || (useDevice & (1 << i)) != 0) {
         if (Probe(OstName(DEV_OST_FRONTEND, i)))
            device[numDevices++] = new cDevice(i);
         else
            break;
         }
      }
  primaryDevice = device[0];
  if (numDevices > 0) {
     isyslog("found %d video device%s", numDevices, numDevices > 1 ? "s" : "");
     SetCaCaps();
     }
  else
     esyslog("ERROR: no video device found, giving up!");
  return numDevices > 0;
}

void cDevice::Shutdown(void)
{
  for (int i = 0; i < numDevices; i++) {
      delete device[i];
      device[i] = NULL;
      }
  primaryDevice = NULL;
}

bool cDevice::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
{
  int videoDev = OstOpen(DEV_VIDEO, CardIndex(), O_RDWR, true);
  if (videoDev >= 0) {
     int result = 0;
     struct video_mbuf mbuf;
     result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf);
     if (result == 0) {
        int msize = mbuf.size;
        unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0);
        if (mem && mem != (unsigned char *)-1) {
           // set up the size and RGB
           struct video_capability vc;
           result |= ioctl(videoDev, VIDIOCGCAP, &vc);
           struct video_mmap vm;
           vm.frame = 0;
           if ((SizeX > 0) && (SizeX <= vc.maxwidth) &&
               (SizeY > 0) && (SizeY <= vc.maxheight)) {
              vm.width = SizeX;
              vm.height = SizeY;
              }
           else {
              vm.width = vc.maxwidth;
              vm.height = vc.maxheight;
              }
           vm.format = VIDEO_PALETTE_RGB24;
           result |= ioctl(videoDev, VIDIOCMCAPTURE, &vm);
           result |= ioctl(videoDev, VIDIOCSYNC, &vm.frame);
           // make RGB out of BGR:
           int memsize = vm.width * vm.height;
           unsigned char *mem1 = mem;
           for (int i = 0; i < memsize; i++) {
               unsigned char tmp = mem1[2];
               mem1[2] = mem1[0];
               mem1[0] = tmp;
               mem1 += 3;
               }
         
           if (Quality < 0)
              Quality = 255; //XXX is this 'best'???
         
           isyslog("grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height);
           FILE *f = fopen(FileName, "wb");
           if (f) {
              if (Jpeg) {
                 // write JPEG file:
                 struct jpeg_compress_struct cinfo;
                 struct jpeg_error_mgr jerr;
                 cinfo.err = jpeg_std_error(&jerr);
                 jpeg_create_compress(&cinfo);
                 jpeg_stdio_dest(&cinfo, f);
                 cinfo.image_width = vm.width;
                 cinfo.image_height = vm.height;
                 cinfo.input_components = 3;
                 cinfo.in_color_space = JCS_RGB;
         
                 jpeg_set_defaults(&cinfo);
                 jpeg_set_quality(&cinfo, Quality, true);
                 jpeg_start_compress(&cinfo, true);
         
                 int rs = vm.width * 3;
                 JSAMPROW rp[vm.height];
                 for (int k = 0; k < vm.height; k++)
                     rp[k] = &mem[rs * k];
                 jpeg_write_scanlines(&cinfo, rp, vm.height);
                 jpeg_finish_compress(&cinfo);
                 jpeg_destroy_compress(&cinfo);
                 }
              else {
                 // write PNM file:
                 if (fprintf(f, "P6\n%d\n%d\n255\n", vm.width, vm.height) < 0 ||
                     fwrite(mem, vm.width * vm.height * 3, 1, f) < 0) {
                    LOG_ERROR_STR(FileName);
                    result |= 1;
                    }
                 }
              fclose(f);
              }
           else {
              LOG_ERROR_STR(FileName);
              result |= 1;
              }
           munmap(mem, msize);
           }
        else
           result |= 1;
        }
     close(videoDev);
     return result == 0;
     }
  return false;
}

void cDevice::SetVideoFormat(videoFormat_t Format)
{
  if (HasDecoder())
     CHECK(ioctl(fd_video, VIDEO_SET_FORMAT, Format));
}

//                          ptVideo        ptAudio        ptTeletext        ptDolby        ptOther
dmxPesType_t PesTypes[] = { DMX_PES_VIDEO, DMX_PES_AUDIO, DMX_PES_TELETEXT, DMX_PES_OTHER, DMX_PES_OTHER };

//#define PRINTPIDS(s) { char b[500]; char *q = b; q += sprintf(q, "%d %s ", CardIndex(), s); for (int i = 0; i < MAXPIDHANDLES; i++) q += sprintf(q, " %s%4d %d", i == ptOther ? "* " : "", pidHandles[i].pid, pidHandles[i].used); dsyslog(b); } //XXX+
#define PRINTPIDS(s)

bool cDevice::AddPid(int Pid, ePidType PidType)
{
  if (Pid) {
     int n = -1;
     int a = -1;
     for (int i = 0; i < MAXPIDHANDLES; i++) {
         if (pidHandles[i].pid == Pid)
            n = i;
         else if (a < 0 && i >= ptOther && !pidHandles[i].used)
            a = i;
         }
     dmxPesType_t PesType = PesTypes[ptOther];
     if (n >= 0) {
        // The Pid is already in use
        if (++pidHandles[n].used == 2 && n <= ptTeletext) {
           // It's a special PID that has to be switched into "tap" mode
           PRINTPIDS("A");//XXX+
           return SetPid(pidHandles[n].fd, PesTypes[n], Pid, DMX_OUT_TS_TAP);
           }
        PRINTPIDS("a");//XXX+
        return true;
        }
     else if (PidType < ptOther) {
        // The Pid is not yet in use and it is a special one
        n = PidType;
        PesType = PesTypes[PidType];
        PRINTPIDS("B");//XXX+
        }
     else if (a >= 0) {
        // The Pid is not yet in use and we have a free slot
        n = a;
        }
     else
        esyslog("ERROR: no free slot for PID %d", Pid);
     if (n >= 0) {
        pidHandles[n].pid = Pid;
        pidHandles[n].fd = OstOpen(DEV_OST_DEMUX, CardIndex(), O_RDWR | O_NONBLOCK, true);
        pidHandles[n].used = 1;
        PRINTPIDS("C");//XXX+
        return SetPid(pidHandles[n].fd, PesType, Pid, PidType <= ptTeletext ? DMX_OUT_DECODER : DMX_OUT_TS_TAP);
        }
     }
  return true;
}

bool cDevice::DelPid(int Pid)
{
  if (Pid) {
     for (int i = 0; i < MAXPIDHANDLES; i++) {
         if (pidHandles[i].pid == Pid) {
            switch (--pidHandles[i].used) {
              case 0: CHECK(ioctl(pidHandles[i].fd, DMX_STOP));//XXX+ is this necessary???
                      close(pidHandles[i].fd);
                      pidHandles[i].fd = -1;
                      pidHandles[i].pid = 0;
                      break;
              case 1: if (i <= ptTeletext)
                         SetPid(pidHandles[i].fd, PesTypes[i], Pid, DMX_OUT_DECODER);
                      break;
              }
            PRINTPIDS("D");//XXX+
            return pidHandles[i].used;
            }
         }
     }
  return false;
}

bool cDevice::SetPid(int fd, dmxPesType_t PesType, int Pid, dmxOutput_t Output)
{
  if (Pid) {
     CHECK(ioctl(fd, DMX_STOP));
     if (Pid != 0x1FFF) {
        dmxPesFilterParams pesFilterParams;
        pesFilterParams.pid     = Pid;
        pesFilterParams.input   = DMX_IN_FRONTEND;
        pesFilterParams.output  = Output;
        pesFilterParams.pesType = PesType;
        pesFilterParams.flags   = DMX_IMMEDIATE_START;
        //XXX+ pesFilterParams.flags   = DMX_CHECK_CRC;//XXX
        if (ioctl(fd, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
           LOG_ERROR;
           return false;
           }
        //XXX+ CHECK(ioctl(fd, DMX_SET_BUFFER_SIZE, KILOBYTE(32)));//XXX
        //XXX+ CHECK(ioctl(fd, DMX_START));//XXX
        }
     }
  return true;
}

eSetChannelResult cDevice::SetChannel(int ChannelNumber, int Frequency, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Tpid, int Ca, int Pnr)
{
  //XXX+StopTransfer();
  //XXX+StopReplay();

  cStatus::MsgChannelSwitch(this, 0);

  // Must set this anyway to avoid getting stuck when switching through
  // channels with 'Up' and 'Down' keys:
  currentChannel = ChannelNumber;

  // Avoid noise while switching:

  if (HasDecoder()) {
     CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true));
     CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
     CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
     CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
     }

  // Stop setting system time:

  if (siProcessor)
     siProcessor->SetCurrentTransponder(0);

  // If this card can't receive this channel, we must not actually switch
  // the channel here, because that would irritate the driver when we
  // start replaying in Transfer Mode immediately after switching the channel:
  bool NeedsTransferMode = (IsPrimaryDevice() && !ProvidesCa(Ca));

  if (!NeedsTransferMode) {

     // Turn off current PIDs:

     if (HasDecoder()) {
        DelPid(pidHandles[ptVideo].pid);
        DelPid(pidHandles[ptAudio].pid);
        DelPid(pidHandles[ptTeletext].pid);
        DelPid(pidHandles[ptDolby].pid);
        }

     FrontendParameters Frontend;

     switch (frontendType) {
       case FE_QPSK: { // DVB-S

            // Frequency offsets:

            unsigned int freq = Frequency;
            int tone = SEC_TONE_OFF;

            if (freq < (unsigned int)Setup.LnbSLOF) {
               freq -= Setup.LnbFrequLo;
               tone = SEC_TONE_OFF;
               }
            else {
               freq -= Setup.LnbFrequHi;
               tone = SEC_TONE_ON;
               }

            Frontend.Frequency = freq * 1000UL;
            Frontend.Inversion = INVERSION_AUTO;
            Frontend.u.qpsk.SymbolRate = Srate * 1000UL;
            Frontend.u.qpsk.FEC_inner = FEC_AUTO;

            int volt = (Polarization == 'v' || Polarization == 'V') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18;

            // DiseqC:

            secCommand scmd;
            scmd.type = 0;
            scmd.u.diseqc.addr = 0x10;
            scmd.u.diseqc.cmd = 0x38;
            scmd.u.diseqc.numParams = 1;
            scmd.u.diseqc.params[0] = 0xF0 | ((Diseqc * 4) & 0x0F) | (tone == SEC_TONE_ON ? 1 : 0) | (volt == SEC_VOLTAGE_18 ? 2 : 0);

            secCmdSequence scmds;
            scmds.voltage = volt;
            scmds.miniCommand = SEC_MINI_NONE;
            scmds.continuousTone = tone;
            scmds.numCommands = Setup.DiSEqC ? 1 : 0;
            scmds.commands = &scmd;

            CHECK(ioctl(fd_sec, SEC_SEND_SEQUENCE, &scmds));
            }
            break;
       case FE_QAM: { // DVB-C

            // Frequency and symbol rate:

            Frontend.Frequency = Frequency * 1000000UL;
            Frontend.Inversion = INVERSION_AUTO;
            Frontend.u.qam.SymbolRate = Srate * 1000UL;
            Frontend.u.qam.FEC_inner = FEC_AUTO;
            Frontend.u.qam.QAM = QAM_64;
            }
            break;
       case FE_OFDM: { // DVB-T

            // Frequency and OFDM paramaters:

            Frontend.Frequency = Frequency * 1000UL;
            Frontend.Inversion = INVERSION_AUTO;
            Frontend.u.ofdm.bandWidth=BANDWIDTH_8_MHZ;
            Frontend.u.ofdm.HP_CodeRate=FEC_2_3;
            Frontend.u.ofdm.LP_CodeRate=FEC_1_2;
            Frontend.u.ofdm.Constellation=QAM_64;
            Frontend.u.ofdm.TransmissionMode=TRANSMISSION_MODE_2K;
            Frontend.u.ofdm.guardInterval=GUARD_INTERVAL_1_32;
            Frontend.u.ofdm.HierarchyInformation=HIERARCHY_NONE;
            }
            break;
       default:
            esyslog("ERROR: attempt to set channel with unknown DVB frontend type");
            return scrFailed;
       }

     // Tuning:

     CHECK(ioctl(fd_frontend, FE_SET_FRONTEND, &Frontend));

     // Wait for channel sync:

     if (cFile::FileReady(fd_frontend, 5000)) {
        FrontendEvent event;
        int res = ioctl(fd_frontend, FE_GET_EVENT, &event);
        if (res >= 0) {
           if (event.type != FE_COMPLETION_EV) {
              esyslog("ERROR: channel %d not sync'ed on DVB card %d!", ChannelNumber, CardIndex() + 1);
              if (IsPrimaryDevice())
                 cThread::RaisePanic();
              return scrFailed;
              }
           }
        else
           esyslog("ERROR %d in frontend get event (channel %d, card %d)", res, ChannelNumber, CardIndex() + 1);
        }
     else
        esyslog("ERROR: timeout while tuning");

     frequency = Frequency;

     // PID settings:

     if (HasDecoder()) {
        if (!(AddPid(Vpid, ptVideo) && AddPid(Apid, ptAudio))) {//XXX+ dolby Dpid1!!! (if audio plugins are attached)
           esyslog("ERROR: failed to set PIDs for channel %d", ChannelNumber);
           return scrFailed;
           }
        if (IsPrimaryDevice())
           AddPid(Tpid, ptTeletext);
        CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
        }
     }

  if (IsPrimaryDevice() && siProcessor)
     siProcessor->SetCurrentServiceID(Pnr);

  eSetChannelResult Result = scrOk;

  // If this DVB card can't receive this channel, let's see if we can
  // use the card that actually can receive it and transfer data from there:

  if (NeedsTransferMode) {
     cDevice *CaDevice = GetDevice(Ca, 0);
     if (CaDevice && !CaDevice->Receiving()) {
        if ((Result = CaDevice->SetChannel(ChannelNumber, Frequency, Polarization, Diseqc, Srate, Vpid, Apid, Tpid, Ca, Pnr)) == scrOk) {
           //XXX+SetModeReplay();
           //XXX+transferringFromDevice = CaDevice->StartTransfer(fd_video);
           }
        }
     else
        Result = scrNoTransfer;
     }

  if (HasDecoder()) {
     CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false));
     CHECK(ioctl(fd_video, VIDEO_SET_BLANK, false));
     }

  // Start setting system time:

  if (Result == scrOk && siProcessor)
     siProcessor->SetCurrentTransponder(Frequency);

  cStatus::MsgChannelSwitch(this, ChannelNumber);

  return Result;
}

bool cDevice::ToggleMute(void)
{
  int OldVolume = volume;
  mute = !mute;
  SetVolume(0, mute);
  volume = OldVolume;
  return mute;
}

void cDevice::SetVolume(int Volume, bool Absolute)
{
  if (HasDecoder()) {
     volume = min(max(Absolute ? Volume : volume + Volume, 0), MAXVOLUME);
     audioMixer_t am;
     am.volume_left = am.volume_right = volume;
     CHECK(ioctl(fd_audio, AUDIO_SET_MIXER, &am));
     cStatus::MsgSetVolume(volume, Absolute);
     if (volume > 0)
        mute = false;
     }
}

void cDevice::TrickSpeed(int Speed)
{
  if (fd_video >= 0)
     CHECK(ioctl(fd_video, VIDEO_SLOWMOTION, Speed));
}

void cDevice::Clear(void)
{
  if (fd_video >= 0)
     CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
  if (fd_audio >= 0)
     CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
}

void cDevice::Play(void)
{
  if (fd_audio >= 0)
     CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
  if (fd_video >= 0)
     CHECK(ioctl(fd_video, VIDEO_CONTINUE));
}

void cDevice::Freeze(void)
{
  if (fd_audio >= 0)
     CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
  if (fd_video >= 0)
     CHECK(ioctl(fd_video, VIDEO_FREEZE));
}

void cDevice::Mute(void)
{
  if (fd_audio >= 0) {
     CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
     CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true));
     }
}

void cDevice::StillPicture(const uchar *Data, int Length)
{
  Mute();
/* Using the VIDEO_STILLPICTURE ioctl call would be the
   correct way to display a still frame, but unfortunately this
   doesn't work with frames from VDR. So let's do pretty much the
   same here as in DVB/driver/dvb.c's play_iframe() - I have absolutely
   no idea why it works this way, but doesn't work with VIDEO_STILLPICTURE.
   If anybody ever finds out what could be changed so that VIDEO_STILLPICTURE
   could be used, please let me know!
   kls 2002-03-23
*/
//#define VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES
#ifdef VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES
  videoDisplayStillPicture sp = { (char *)Data, Length };
  CHECK(ioctl(fd_video, VIDEO_STILLPICTURE, &sp));
#else
#define MIN_IFRAME 400000
  for (int i = MIN_IFRAME / Length + 1; i > 0; i--) {
      safe_write(fd_video, Data, Length);
      usleep(1); // allows the buffer to be displayed in case the progress display is active
      }
#endif
}

bool cDevice::Replaying(void)
{
  /*XXX+
  if (replayBuffer && !replayBuffer->Active())
     StopReplay();
  return replayBuffer != NULL;
  XXX*/
  return player != NULL;
}

bool cDevice::Attach(cPlayer *Player)
{
  if (Receiving()) {
     esyslog("ERROR: attempt to attach a cPlayer while receiving on device %d - ignored", CardIndex() + 1);
     return false;
     }
  if (HasDecoder()) {
     if (player)
        Detach(player);

     if (siProcessor)
        siProcessor->SetStatus(false);
     CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
     CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY));
     CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
     CHECK(ioctl(fd_audio, AUDIO_PLAY));
     CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY));
     CHECK(ioctl(fd_video, VIDEO_PLAY));

     player = Player;
     player->device = this;
     player->deviceFileHandle = fd_video;
     player->Activate(true);
     return true;
     }
  return false;
}

void cDevice::Detach(cPlayer *Player)
{
  if (Player && player == Player) {
     player->Activate(false);
     player->deviceFileHandle = -1;
     player->device = NULL;
     player = NULL;

     CHECK(ioctl(fd_video, VIDEO_STOP, true));
     CHECK(ioctl(fd_audio, AUDIO_STOP, true));
     CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
     CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
     CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX));
     CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX));
     CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
     CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false));
     if (siProcessor)
        siProcessor->SetStatus(true);
     }
}

void cDevice::StopReplay(void)
{
  if (player) {
     Detach(player);
     /*XXX+
     if (IsPrimaryDevice()) {
        // let's explicitly switch the channel back in case it was in Transfer Mode:
        cChannel *Channel = Channels.GetByNumber(currentChannel);
        if (Channel) {
           Channel->Switch(this, false);
           usleep(100000); // allow driver to sync in case a new replay will start immediately
           }
        }
        XXX*/
     }
}

int cDevice::PlayVideo(const uchar *Data, int Length)
{
  if (fd_video >= 0)
     return write(fd_video, Data, Length);
  return -1;
}

int cDevice::PlayAudio(const uchar *Data, int Length)
{
  //XXX+
  return -1;
}

int cDevice::Priority(void)
{
  if (IsPrimaryDevice() && !Receiving())
     return Setup.PrimaryLimit - 1;
  int priority = DEFAULTPRIORITY;
  for (int i = 0; i < MAXRECEIVERS; i++) {
      if (receiver[i])
         priority = max(receiver[i]->priority, priority);
      }
  return priority;
}

int cDevice::CanShift(int Ca, int Priority, int UsedCards)
{
  return -1;//XXX+ too complex with multiple recordings per device
  // Test whether a receiving on this DVB device can be shifted to another one
  // in order to perform a new receiving with the given Ca and Priority on this device:
  int ShiftLevel = -1; // default means this device can't be shifted
  if (UsedCards & (1 << CardIndex()) != 0)
     return ShiftLevel; // otherwise we would get into a loop
  if (Receiving()) {
     if (ProvidesCa(Ca) // this device provides the requested Ca
        && (Ca != this->Ca() // the requested Ca is different from the one currently used...
           || Priority > this->Priority())) { // ...or the request comes from a higher priority
        cDevice *d = NULL;
        int Provides[MAXDEVICES];
        UsedCards |= (1 << CardIndex());
        for (int i = 0; i < numDevices; i++) {
            if ((Provides[i] = device[i]->ProvidesCa(this->Ca())) != 0) { // this device is basicly able to do the job
               if (device[i] != this) { // it is not _this_ device
                  int sl = device[i]->CanShift(this->Ca(), Priority, UsedCards); // this is the original Priority!
                  if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) {
                     d = device[i];
                     ShiftLevel = sl;
                     }
                  }
               }
            }
        if (ShiftLevel >= 0)
           ShiftLevel++; // adds the device's own shift
        }
     }
  else if (Priority > this->Priority())
     ShiftLevel = 0; // no shifting necessary, this device can do the job
  return ShiftLevel;
}

int cDevice::ProvidesCa(int Ca)
{
  if (Ca == CardIndex() + 1)
     return 1; // exactly _this_ card was requested
  if (Ca && Ca <= MAXDEVICES)
     return 0; // a specific card was requested, but not _this_ one
  int result = Ca ? 0 : 1; // by default every card can provide FTA
  int others = Ca ? 1 : 0;
  for (int i = 0; i < MAXCACAPS; i++) {
      if (caCaps[i]) {
         if (caCaps[i] == Ca)
            result = 1;
         else
            others++;
         }
      }
  return result ? result + others : 0;
}

bool cDevice::Receiving(void)
{
  for (int i = 0; i < MAXRECEIVERS; i++) {
      if (receiver[i])
         return true;
      }
  return false;
}

void cDevice::Action(void)
{
  dsyslog("receiver thread started on device %d (pid=%d)", CardIndex() + 1, getpid());

  int fd_dvr = open(dvrFileName, O_RDONLY | O_NONBLOCK);
  if (fd_dvr >= 0) {
     pollfd pfd;
     pfd.fd = fd_dvr;
     pfd.events = pfd.revents = POLLIN;
     uchar b[TS_SIZE];
     time_t t = time(NULL);
     active = true;
     for (; active;) {

         // Read data from the DVR device:

         if (pfd.revents & POLLIN != 0) {
            int r = read(fd_dvr, b, sizeof(b));
            if (r == TS_SIZE) {
               if (*b == TS_SYNC_BYTE) {
                  // We're locked on to a TS packet
                  int Pid = (((uint16_t)b[1] & PID_MASK_HI) << 8) | b[2];
                  // Distribute the packet to all attached receivers:
                  Lock();
                  for (int i = 0; i < MAXRECEIVERS; i++) {
                      if (receiver[i] && receiver[i]->WantsPid(Pid))
                         receiver[i]->Receive(b, TS_SIZE);
                      }
                  Unlock();
                  }
               t = time(NULL);
               }
            else if (r > 0)
               esyslog("ERROR: got incomplete TS packet (%d bytes)", r);//XXX+ TODO do we have to read the rest???
            else if (r < 0) {
               if (FATALERRNO) {
                  if (errno == EBUFFEROVERFLOW) // this error code is not defined in the library
                     esyslog("ERROR: DVB driver buffer overflow on device %d", CardIndex() + 1);
                  else {
                     LOG_ERROR;
                     break;
                     }
                  }
               }
            }

         // Wait for more data to become available:

         poll(&pfd, 1, 100);

         //XXX+ put this into the recorder??? or give the receiver a flag whether it wants this?
         if (time(NULL) - t > MAXBROKENTIMEOUT) {
            esyslog("ERROR: video data stream broken on device %d", CardIndex() + 1);
            cThread::EmergencyExit(true);
            t = time(NULL);
            }
         }
     close(fd_dvr);
     }
  else
     LOG_ERROR_STR(dvrFileName);

  dsyslog("receiver thread ended on device %d (pid=%d)", CardIndex() + 1, getpid());
}

bool cDevice::Attach(cReceiver *Receiver)
{
  //XXX+ check for same transponder???
  if (!Receiver)
     return false;
  if (Receiver->device == this)
     return true;
  StopReplay();
  for (int i = 0; i < MAXRECEIVERS; i++) {
      if (!receiver[i]) {
         //siProcessor->SetStatus(false);//XXX+
         for (int n = 0; n < MAXRECEIVEPIDS; n++) {
             if (Receiver->pids[n])
                AddPid(Receiver->pids[n]);//XXX+ retval!
             else
                break;
             }
         Receiver->Activate(true);
         Lock();
         Receiver->device = this;
         receiver[i] = Receiver;
         Unlock();
         Start();
         return true;
         }
      }
  esyslog("ERROR: no free receiver slot!");
  return false;
}

void cDevice::Detach(cReceiver *Receiver)
{
  if (!Receiver || Receiver->device != this)
     return;
  bool receiversLeft = false;
  for (int i = 0; i < MAXRECEIVERS; i++) {
      if (receiver[i] == Receiver) {
         Receiver->Activate(false);
         Lock();
         receiver[i] = NULL;
         Receiver->device = NULL;
         Unlock();
         for (int n = 0; n < MAXRECEIVEPIDS; n++) {
             if (Receiver->pids[n])
                DelPid(Receiver->pids[n]);
             else
                break;
             }
         }
      else if (receiver[i])
         receiversLeft = true;
      }
  if (!receiversLeft) {
     active = false;
     Cancel(3);
     }
}