#include <stdio.h>
#include <sys/ioctl.h>

#include <string>

#include <linux/types.h>
#include <vdr/channels.h>
#include <vdr/sources.h>
#include "common.h"
#include "device.h"
#include "global.h"
#include "setup.h"

#define BUFFSIZE_INPUT 4096 * 32
#define IVTV_IOC_PAUSE_ENCODE      _IO  ('@', 56)
#define IVTV_IOC_RESUME_ENCODE     _IO  ('@', 57)

const short kVideoPid    = 301;
const short kAudioPid    = 300;
const short kTeletextPid = 305;

typedef enum {
  videoNormNTSC,
  videoNormSECAM,
  videoNormPAL
  } eVideoNorm;


const unsigned char kInvTab[256] =
{
  0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
  0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
  0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
  0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
  0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
  0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
  0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
  0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
  0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
  0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
  0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
  0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
  0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
  0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
  0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
  0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
  0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
  0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
  0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
  0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
  0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
  0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
  0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
  0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
  0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
  0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
  0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
  0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
  0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
  0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
  0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
  0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};


class cPvrReadThread : public cThread {
private:
  bool active;
  int video_fd;
  int vbi_fd;
  cRingBufferLinear * tsBuffer;
  cMutex * mutex;

  uint8_t  ts_buffer[188];
  uint8_t  video_counter;
  uint8_t  audio_counter;
  uint8_t  text_counter;
  uint8_t  pes_buffer[64 * 1024];
  uint8_t  pes_stream_id;
  uint32_t pes_offset;
  uint32_t pes_length;
  uint32_t pes_tmp;

  void ParseProgramStream(uint8_t * Data, uint32_t Length);
  void PesToTs(uint8_t * Data, uint32_t Length);
  int  PutData(const unsigned char * Data, int Count);
protected:
  virtual void Action(void);
public:
  cPvrReadThread(int VideoDevice, int VbiDevice, cRingBufferLinear * TsBuffer,\
                 cMutex * Mutex);
  void Stop(void) {active=false;};
  virtual ~cPvrReadThread(void);
};

cPvrReadThread::cPvrReadThread(int VideoDevice, int VbiDevice, \
                               cRingBufferLinear * TsBuffer, cMutex * Mutex)
: active(false),
  video_fd(VideoDevice),
  vbi_fd(VbiDevice),
  tsBuffer(TsBuffer),
  mutex(Mutex),
  video_counter(0),
  audio_counter(0),
  text_counter(0),
  pes_stream_id(0),
  pes_offset(0),
  pes_length(0),
  pes_tmp(0)
{
  Start();
}

cPvrReadThread::~cPvrReadThread(void) {
  active = false;
  Cancel(2);
}


int cPvrReadThread::PutData(const unsigned char * Data, int Count) {
  cMutexLock mutexLock(mutex);
  if (tsBuffer->Free() < Count)
    return 0;
  return tsBuffer->Put(Data, Count);
}

void cPvrReadThread::PesToTs(uint8_t * Data, uint32_t Length) {
  uint8_t stream_id;
  bool first = true;
  uint32_t i;
  const short PayloadSize = TS_SIZE - 4;    
  uint32_t Payload_Count  = Length / PayloadSize;
  uint32_t Payload_Rest   = Length % PayloadSize;
  stream_id = Data[3];
  switch (stream_id) {
    case 0xE0 ... 0xEF:
      for (i = 0; i < Payload_Count; i++) {
        ts_buffer[0] = TS_SYNC_BYTE;
        ts_buffer[1] = (first ? 0x40 : 0x00) || (kVideoPid >> 8);
        ts_buffer[2] = kVideoPid & 0xFF;
        ts_buffer[3] = 0x10 | video_counter;
        memcpy(ts_buffer + 4, Data + i * PayloadSize, PayloadSize);
        PutData(ts_buffer, TS_SIZE);
        video_counter = (video_counter + 1) & 15; //uint8_t
        first = false;
        } // end: for (i = 0; i < Payload_Count; i++)
      if (Payload_Rest > 0) {
        ts_buffer[0] = TS_SYNC_BYTE;
        ts_buffer[1] = (first ? 0x40 : 0x00) || (kVideoPid >> 8);
        ts_buffer[2] = kVideoPid & 0xFF;
        ts_buffer[3] = 0x30 | video_counter;
        ts_buffer[4] = 183 - Payload_Rest;
        if (ts_buffer[4] > 0) {
          ts_buffer[5] = 0x00;
          memset(ts_buffer + 6, 0xFF, ts_buffer[4] - 1);
          } //end: if (ts_buffer[4] > 0)
        memcpy(ts_buffer + 5 + ts_buffer[4], Data + i * 184, Payload_Rest);
        PutData(ts_buffer, TS_SIZE);
        video_counter = (video_counter + 1) & 15;
        first = false;
        } // end: if (Payload_Rest > 0)
      break; // end: case 0xE0..0xEF:
    case 0xBD: {
      uint8_t * payload_data;
      uint16_t payload_length;
      uint32_t pos = 0;
      uint32_t ts_pos = 0;
      memset(ts_buffer, 0, TS_SIZE);
      ts_buffer[0] = TS_SYNC_BYTE;
      ts_buffer[1] = kTeletextPid >> 8;
      ts_buffer[2] = kTeletextPid & 0xFF;
      ts_buffer[3] = 0x10 | text_counter;

      payload_length = Length - 9 - Data[8];
      payload_data = Data + 9 + Data[8];
      if (memcmp(payload_data, "itv0", 4) == 0)
        pos = 12;
      else
        if (memcmp(payload_data, "ITV0", 4) == 0)
          pos = 4;
        else
          return;
      while (pos + 43 <= payload_length) {
        if ((payload_data[pos] & 0x0F) == 0x01)  {
          ts_buffer[4+ts_pos*46] = 0x02; // data_unit_id
          ts_buffer[5+ts_pos*46] = 0x2C; // data_unit_length
          ts_buffer[6+ts_pos*46] = 0x00; // field_parity, line_offset
          ts_buffer[7+ts_pos*46] = 0xE4; // framing_code
          for (int j = 0; j < 42; j++) {
            ts_buffer[8 + ts_pos * 46 + j] = kInvTab[payload_data[pos + 1 + j]];
            }
          ts_pos++;
          if (ts_pos == 4) {
            ts_pos = 0;
            PutData(ts_buffer, TS_SIZE);
            text_counter = (text_counter + 1) & 15;
            memset(ts_buffer, 0, TS_SIZE);
            ts_buffer[0] = TS_SYNC_BYTE;
            ts_buffer[1] = kTeletextPid >> 8;
            ts_buffer[2] = kTeletextPid & 0xFF;
            ts_buffer[3] = 0x10 | text_counter;
            }
          }
        pos += 43;
        }
      if (ts_pos > 0) {
        PutData(ts_buffer, TS_SIZE);
        text_counter = (text_counter + 1) & 15;
        }
      break; // end: case 0xBD:
      }
    case 0xC0 ... 0xDF: 
      for (i = 0; i < Payload_Count; i++) {
        ts_buffer[0] = TS_SYNC_BYTE;
        ts_buffer[1] = (first ? 0x40 : 0x00) || (kAudioPid >> 8);
        ts_buffer[2] = kAudioPid & 0xFF;
        ts_buffer[3] = 0x10 | audio_counter;
        memcpy(ts_buffer + 4, Data + i * PayloadSize, PayloadSize);
        PutData(ts_buffer, TS_SIZE);
        audio_counter = (audio_counter + 1) & 15;
        first = false;
        } // end: for (i = 0; i < Payload_Count; i++)
      if (Payload_Rest > 0) {
        ts_buffer[0] = TS_SYNC_BYTE;
        ts_buffer[1] = (first ? 0x40 : 0x00) || (kAudioPid >> 8);
        ts_buffer[2] = kAudioPid & 0xFF;
        ts_buffer[3] = 0x30 | audio_counter;
        ts_buffer[4] = 183 - Payload_Rest;
        if (ts_buffer[4] > 0) {
          ts_buffer[5] = 0x00;
          memset(ts_buffer + 6, 0xFF, ts_buffer[4] - 1);
          } //end: if (ts_buffer[4] > 0)
        memcpy(ts_buffer + 5 + ts_buffer[4], Data + i * PayloadSize, Payload_Rest);
        PutData(ts_buffer, TS_SIZE);
        audio_counter = (audio_counter + 1) & 15;
        first = false;
        } // end: if (Payload_Rest > 0)
      break; // end: case 0xC0..0xDF:
    } // end: switch (stream_id)
}

void cPvrReadThread::ParseProgramStream(uint8_t * Data, uint32_t Length) {
  uint32_t pos = 0;
  while (pos < Length) {
    switch(pes_offset)  {
      case 0:
      case 1:
        if (Data[pos] == 0x00)
          pes_offset++;
        else
          pes_offset = 0;
        pos++;
        break;
      case 2:
        if (Data[pos] == 0x01)  {
          pes_offset++;
          pos++;
          }
        else
          pes_offset = 0;
        break;
      case 3:
        pes_stream_id = Data[pos];
        pes_offset++;
        pos++;
        break;
      default:
        switch (pes_stream_id) {
          case 0xBA:
            switch (pes_offset) {
              case 4 ... 12:
                pes_offset++;
                pos++;
                break;
              case 13:
                pes_tmp = Data[pos] & 7;
                pes_offset++;
                pos++;
                break;
              default:
                if (pes_tmp > 0) {
                  pes_tmp--;
                  pos++;
                  }
                else
                  pes_offset = 0;
                break;              
              }
            break;
          case 0xBB:
            switch (pes_offset) {
              case 4 ... 11:
                pes_offset++;
                pos++;
                break;
              default:
                if (pes_tmp > 0) {
                  pes_tmp--;
                  pos++;
                  }
                else
                  if (Data[pos] & 0x80) {
                    pes_tmp = 3;
                    }
                  else {
                    pes_offset = 0;
                    }
                break; 
              } // end: switch (pes_offset) 
            break; // end: case 0xBB
          case 0xBD ... 0xEF:
            switch (pes_offset) {
              case 4:
                pes_length = Data[pos] << 8;
                pes_offset++;
                pos++;
                break;
              case 5:
                pes_length += Data[pos];
                pes_buffer[0] = 0x00;
                pes_buffer[1] = 0x00;
                pes_buffer[2] = 0x01;
                pes_buffer[3] = pes_stream_id;
                pes_buffer[4] = pes_length >> 8;
                pes_buffer[5] = pes_length & 0xFF;
                pes_length += 6;
                pes_offset++;
                pos++;
                break;
              default: {
                uint32_t rest = pes_length - pes_offset;
                if (pos + rest <= Length) {
                  memcpy(pes_buffer + pes_offset, Data + pos, rest);
                  pos += rest;
                  PesToTs(pes_buffer, pes_length);
                  pes_offset = 0;
                  }
                else {
                  memcpy(pes_buffer + pes_offset, Data + pos, Length - pos);
                  pes_offset += Length - pos;
                  pos += Length - pos;
                  }
                }
                break;
              }
            break;
          default:
            // unexpected PES Stream id, most probably garbage data.
            pes_offset = 0;
            return;
            break;
          } // end: switch (pes_stream_id)
        } // end: switch(pes_offset)
    }  // end: while (pos < Length)
}


void cPvrReadThread::Action(void) {
  unsigned char buffer[BUFFSIZE_INPUT];
  int r;
  active = true;
  log(3,"cPvrReadThread::Action(): Entering Action()"); 
  // A derived cThread class must check Running()
  // repeatedly to see whether it's time to stop.
  // see VDR/thread.h
  while (active && Running()) {
    r = read(video_fd, buffer, BUFFSIZE_INPUT);
    if (r == -1) {
      log(0, "cPvrReadThread::Action():error reading from video device, %d:%s",
        errno, strerror(errno));
      break;
      }
    if (r > 0)  {
      ParseProgramStream(buffer, r);
      }
    }
  log(3,"cPvrReadThread::Action(): leaving Action()"); 
}


/*
function IOCTL
retries the ioctl given six times before giving up,
improves stability if device/driver is actually busy
*/
int IOCTL(int fd, int cmd, void *data) {
  if (fd<=0) return (-1); 
  for (int retry = 5; retry >= 0; ) {
    if (ioctl(fd, cmd, data) != 0) {
      if (retry){
        usleep(20000); /* 20msec */
        retry--;
        continue;
        }
      return -1;
      }
    else
      return 0;  /* all went okay :) */
    }
  return 0;  /* should never reach this */
}


cPvrDevice * PvrDevices[kMaxPvrDevices];

cPvrDevice::cPvrDevice(int DeviceNumber)
:  number(DeviceNumber),
  video_fd(-1),
  radio_fd(-1),
  vbi_fd(-1),
  radioDevice(-1),
  frequency(-1),
  vpid(-1),
  apid(-1),
  tpid(-1),
  VideoNorm(-1),
  VideoInput(-1),
  dvrOpen(false),
  delivered(false),
  isTuning(false)
{
  char devName[64];
  char fileName[256];
  FILE * f;
  struct v4l2_input input;
  struct v4l2_capability vcap;
  int i;

  // put all values here that are set only *once* 
  memset(&vcap, 0, sizeof(vcap));
  sprintf(devName, "/dev/video%d", DeviceNumber);
  video_fd = open(devName, O_RDWR);
  if (video_fd < 0) {
    log(0, "cPvrDevice::cPvrDevice(): error opening video device %s: %s",
      devName, strerror(errno));
    }
  IOCTL(video_fd, VIDIOC_QUERYCAP, &vcap);
  log(3, "Device is %s (%s)", vcap.card, devName);
  if (vcap.capabilities & V4L2_CAP_RADIO) {
    for (i=0; i<kMaxPvrDevices; i++) {
      // sysfs is needed here, what about old 2.4 kernels?          
      sprintf(fileName, \
        "/sys/class/video4linux/video%d/device/video4linux:radio%d/name", \
        number, i);
      f = fopen(fileName, "r");
      if (f != NULL) { // found radio dev for /dev/video%number
        radioDevice=i; // store info for later
        fclose(f);
        log(3, "Device has FM Radio support (/dev/radio%d)",i);
        break;
        }
      }
    }
  IVTV_apiversion = vcap.version;
  log(3, "ivtv Version 0x%06x", IVTV_apiversion);
  QueryAllControls();
  memset(&inputs, -1, sizeof(inputs)); 
  for (i = 0;; i++) {
    memset(&input, 0, sizeof(input));
    input.index = i;
    if (IOCTL(video_fd, VIDIOC_ENUMINPUT, &input) == -1)
      break;
    log(3, "Detected input %d: %s", i, input.name);
    if (!memcmp(input.name, "Tuner",        5)) { inputs[eTuner]     =i; continue; }
    if (!memcmp(input.name, "Composite 0", 11)) { inputs[eComposite0]=i; continue; }
    if (!memcmp(input.name, "Composite 1", 11)) { inputs[eComposite1]=i; continue; }
    if (!memcmp(input.name, "Composite 2", 11)) { inputs[eComposite2]=i; continue; }
    if (!memcmp(input.name, "Composite 3", 11)) { inputs[eComposite3]=i; continue; }
    if (!memcmp(input.name, "Composite 4", 11)) { inputs[eComposite4]=i; continue; }
    if (!memcmp(input.name, "S-Video 3",    9)) { inputs[eSVideo3]   =i; continue; }
    if (!memcmp(input.name, "S-Video 0",    9)) { inputs[eSVideo0]   =i; continue; }
    if (!memcmp(input.name, "S-Video 1",    9)) { inputs[eSVideo1]   =i; continue; }
    if (!memcmp(input.name, "S-Video 2",    9)) { inputs[eSVideo2]   =i; continue; }
    }   
  if (inputs[eTuner] >= 0) 
    SetInput(inputs[eTuner]);
  else  
    log(0, "device has no tuner");
  sprintf(devName, "/dev/vbi%d", DeviceNumber);
  vbi_fd = open(devName, O_RDONLY);
  if (vbi_fd < 0) {
    log(0, "error opening vbi device %s: %s",
      devName, strerror(errno));
    }
  tsBuffer = new cRingBufferLinear(MEGABYTE(3), TS_SIZE, false, "PVRTS");
  tsBuffer->SetTimeouts(100, 100);
  SetControlValue(&PvrSetup.StreamType, \
                 (__s32) V4L2_MPEG_STREAM_TYPE_MPEG2_PS);
  SetControlValue(&PvrSetup.AudioEncoding,\
                  V4L2_MPEG_AUDIO_ENCODING_LAYER_2);
  SetControlValue(&PvrSetup.VideoBitratePeak, \
                  15000000);
  SetVideoSize(720, 576);
  //
  SetVideoNorm(V4L2_STD_PAL);
  SetVBImode(V4L2_STD_PAL, PvrSetup.SliceVBI);
  ReInit();
  readThread = new cPvrReadThread(video_fd, vbi_fd, tsBuffer, &mutex);
}

cPvrDevice::~cPvrDevice() {
  delete readThread;
  delete tsBuffer;
  close(vbi_fd);
  close(radio_fd);
  close(video_fd);
}

bool cPvrDevice::Probe(int DeviceNumber) {
  char device[256];
  struct v4l2_capability vcap;
  int video_fd = 0;
  bool found = false;
  memset(&vcap, 0, sizeof(vcap));
  sprintf(device, "/dev/video%d", DeviceNumber);
  video_fd = open(device, O_RDONLY);
  if (video_fd) {
    IOCTL(video_fd, VIDIOC_QUERYCAP, &vcap);
    if (!memcmp(vcap.driver, "ivtv", 4)) found=true;
    if (!memcmp(vcap.driver, "IVTV", 4)) found=true;
    close(video_fd);
    return found;
    }
  return false;
}

bool cPvrDevice::Initialize(void) {
  int found = 0;
  for (int i = 0; i < kMaxPvrDevices; i++) {
    PvrDevices[i] = NULL;
    if (Probe(i)) {
      PvrDevices[i] = new cPvrDevice(i);
      found++;
      }
    }
  if (found)
    log(1, "found %d PVR device%s", found, found > 1 ? "s" : "");
  else
    log(1, "no PVR device found");
  return found > 0;
}

void cPvrDevice::Stop(void) {
/* recursively stop all threads inside pvrinputs devices */
  for (int i = 0; i < kMaxPvrDevices; i++) {
    if (PvrDevices[i]) {
      if (PvrDevices[i]->readThread) {
        PvrDevices[i]->readThread->Stop();
        }
      }
    }
}


void cPvrDevice::ReInitAll(void) {
  int i;
  for (i = 0; i < kMaxPvrDevices; i++) {
    if (PvrDevices[i]) {
      PvrDevices[i]->ReInit();
      }
    }
}

int cPvrDevice::Count() {
  int count = 0;
  for (int i = 0; i < kMaxPvrDevices; i++) {
    if (PvrDevices[i])
      count++;
    }
  return count;
}

cPvrDevice * cPvrDevice::Get(int index) {
  int count = 0;
  for (int i = 0; i < kMaxPvrDevices; i++) {
    if (PvrDevices[i]) {
      if (count == index)
        return PvrDevices[i];
      count++;
    }
  }
  return NULL;
}

void cPvrDevice::ReInit(void) {
  SetInput(VideoInput);
  /* begin new v4l2 ivtv controls */
  SetControlValue(&PvrSetup.AspectRatio, \
                  PvrSetup.AspectRatio.value);
  SetControlValue(&PvrSetup.AudioBitrate, \
                  PvrSetup.AudioBitrate.value);
  SetControlValue(&PvrSetup.AudioSampling, \
                  PvrSetup.AudioSampling.value);
  SetControlValue(&PvrSetup.VideoBitrateTV, \
                  PvrSetup.VideoBitrateTV.value);
  SetControlValue(&PvrSetup.BitrateMode, \
                  PvrSetup.BitrateMode.value);
  SetControlValue(&PvrSetup.GopSize, \
                  PvrSetup.GopSize.queryctrl.default_value);
  SetControlValue(&PvrSetup.GopClosure, \
                  PvrSetup.GopClosure.queryctrl.default_value);
  SetControlValue(&PvrSetup.BFrames, \
                  PvrSetup.BFrames.queryctrl.default_value);
  SetControlValue(&PvrSetup.FilterSpatialMode, \
                  PvrSetup.FilterSpatialMode.value);
  SetControlValue(&PvrSetup.FilterSpatial, \
                  PvrSetup.FilterSpatial.value);
  SetControlValue(&PvrSetup.FilterLumaSpatialType, \
                  PvrSetup.FilterLumaSpatialType.value);
  SetControlValue(&PvrSetup.FilterChromaSpatialType, \
                  PvrSetup.FilterChromaSpatialType.value);
  SetControlValue(&PvrSetup.FilterTemporalMode, \
                  PvrSetup.FilterTemporalMode.value);
  SetControlValue(&PvrSetup.FilterTemporal, \
                  PvrSetup.FilterTemporal.value);
  SetControlValue(&PvrSetup.FilterMedianType, \
                  PvrSetup.FilterMedianType.value);
  SetControlValue(&PvrSetup.FilterLumaMedianBottom, \
                  PvrSetup.FilterLumaMedianBottom.value);
  SetControlValue(&PvrSetup.FilterLumaMedianTop, \
                  PvrSetup.FilterLumaMedianTop.value);
  SetControlValue(&PvrSetup.FilterChromaMedianBottom, \
                  PvrSetup.FilterChromaMedianBottom.value);
  SetControlValue(&PvrSetup.FilterChromaMedianTop, \
                  PvrSetup.FilterChromaMedianTop.value);
  SetControlValue(&PvrSetup.Brightness, \
                  PvrSetup.Brightness.value);
  SetControlValue(&PvrSetup.Contrast, \
                  PvrSetup.Contrast.value);
  SetControlValue(&PvrSetup.Saturation, \
                  PvrSetup.Saturation.value);
  SetControlValue(&PvrSetup.Hue, \
                  PvrSetup.Hue.value);
  if (radio_fd >= 0) { 
    SetControlValue(&PvrSetup.AudioVolumeFM, \
                  PvrSetup.AudioVolumeFM.value);
    SetControlValue(&PvrSetup.AudioMute, \
                  (int) (PvrSetup.AudioVolumeFM.value == 0));
    SetControlValue(&PvrSetup.VideoBitrateFM, \
                  PvrSetup.VideoBitrateFM.value);
    }
  else {
    SetControlValue(&PvrSetup.AudioVolumeTV, \
                  PvrSetup.AudioVolumeTV.value);   
    SetControlValue(&PvrSetup.AudioMute, \
                  (int) (PvrSetup.AudioVolumeTV.value == 0));
    SetControlValue(&PvrSetup.VideoBitrateTV, \
                  PvrSetup.VideoBitrateTV.value);
    }
    /* end new v4l2 ivtv controls */
}


bool cPvrDevice::Tune(int freq) {
  struct v4l2_frequency vf;
  int freqaux = freq;
  if (frequency == freq) return true;
  memset(&vf, 0, sizeof(vf));
  vf.tuner = 0;
  if (radio_fd >= 0) {
    struct v4l2_tuner tuner;
    tuner.index = 0;
    IOCTL(video_fd, VIDIOC_G_TUNER, &tuner);
    if (tuner.capability & V4L2_TUNER_CAP_LOW)
      freqaux = freq * 1000;
    vf.type = V4L2_TUNER_RADIO;
    }
  else
    vf.type = V4L2_TUNER_ANALOG_TV;
  vf.frequency = (int) ((double) freqaux * 16.0 / 1000.0 + 0.5);

  if (IOCTL(video_fd, VIDIOC_S_FREQUENCY, &vf) != 0) {
    log(0, "cPvrDevice::Tune(): error tuning to %d: %s", \
        freq, strerror(errno));
    return false;
    }
  frequency = freq;
  return true;
}

bool cPvrDevice::SetInput(int input) {
  if (input == VideoInput) return true;
  if (IOCTL(video_fd, VIDIOC_S_INPUT, &input)) {
    log(0, "VIDIOC_S_INPUT failed, %d:%s",
      errno, strerror(errno));
    return false;
    }
  VideoInput=input;
  return true;
}

bool cPvrDevice::SetVideoNorm(int norm) {
  if (norm == VideoNorm) return true;
  log(3, "SetVideoNorm(%s)", \
      norm==V4L2_STD_NTSC?"NTSC":norm==V4L2_STD_SECAM?"SECAM":"PAL");
  if (IOCTL(video_fd, VIDIOC_S_STD, &norm) != 0) {
    log(0, "cPvrDevice::SetVideoNorm() failed, %d:%s", 
      errno, strerror(errno));
    return false;
    }
  VideoNorm=norm;
  return true;
}

bool cPvrDevice::SetVideoSize(int width, int height) {
  struct v4l2_format vfmt;

  vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (IOCTL(video_fd, VIDIOC_G_FMT, &vfmt) != 0) {
    log(0, "cPvrDevice::SetVideoSize(): VIDIOC_G_FMT failed, %d:%s",
      errno, strerror(errno));
    return false;
    }
  vfmt.fmt.pix.width  = width;
  vfmt.fmt.pix.height = height;
  if (IOCTL(video_fd, VIDIOC_S_FMT, &vfmt) != 0) {
    log(0, "cPvrDevice::SetVideoSize(): VIDIOC_S_FMT failed, %d:%s",
      errno, strerror(errno));
    return false;
    }
  return true;
}

void cPvrDevice::SetEncoderState(eEncState state) {
  log(3, "cPvrDevice::SetEncoderState (%s)", state==eStop?"Stop":"Start");
  if (IVTV_apiversion >= 0x01000) { // ivtv 1.0.0
    struct v4l2_encoder_cmd encoderCommand;
    memset(&encoderCommand, 0, sizeof(encoderCommand));
    switch(state) {
      case eStop  :  encoderCommand.cmd = V4L2_ENC_CMD_STOP;   break;
      case eStart :  encoderCommand.cmd = V4L2_ENC_CMD_START;  break;
      }
    if (IOCTL(video_fd, VIDIOC_ENCODER_CMD, &encoderCommand)) 
      log(0, "cPvrDevice::SetEncoderState(%s): error %d:%s", \
          state==eStop?"Stop":"Start", errno, strerror(errno)); 
    }
  else { // old API from ivtv<1.0.0
   switch(state) {
      case eStop:   // VIDIOC_STREAMON/OFF doesn`t work properly according to Hans Verkuil
                    close(video_fd);
                    char * devName;
                    asprintf(&devName, "/dev/video%d", number);
                    video_fd = open(devName, O_RDWR);  //reopen for tuning
                    if (video_fd < 0) log(0, "Error opening %s: %s", devName, strerror(errno));
                    free(devName);
                    break;
      case eStart:  break;//encoder will start automatically with new cPvrReadThread        
      }
   }
}

bool cPvrDevice::SetVBImode(int std, int vbistatus) {
  struct v4l2_format vbifmt;
  struct v4l2_ext_controls ctrls;
  struct v4l2_ext_control  ctrl;

  vbistatus = PvrSetup.SliceVBI;

  log(3, "SetVBImode(%s, %d)", \
      std==V4L2_STD_NTSC?"NTSC":std==V4L2_STD_SECAM?"SECAM":"PAL", \
      vbistatus);
  if (vbi_fd > 0) {

    memset(&vbifmt, 0, sizeof(vbifmt));
    memset(&ctrls,  0, sizeof(ctrls));
    memset(&ctrl,   0, sizeof(ctrl));
    ctrl.id    = V4L2_CID_MPEG_STREAM_VBI_FMT;

    switch(vbistatus) {
      case 0: ctrl.value = 0; break;
      case 1: ctrl.value = V4L2_MPEG_STREAM_VBI_FMT_IVTV; break;
    }
    ctrls.ctrl_class=V4L2_CTRL_CLASS_MPEG;
    ctrls.controls=&ctrl;
    ctrls.count=1;

    if (IOCTL(vbi_fd, VIDIOC_S_EXT_CTRLS, &ctrls) != 0) {
      log(0, "cPvrDevice::SetVBImode(): error setting vbi mode (ctrls), %d:%s",
        errno, strerror(errno));
      return false; 
      }

    if (ctrl.value == V4L2_MPEG_STREAM_VBI_FMT_IVTV) {
        vbifmt.fmt.sliced.service_set = (std & V4L2_STD_NTSC)?V4L2_SLICED_VBI_525:V4L2_SLICED_VBI_625;
        vbifmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
        vbifmt.fmt.sliced.reserved[0] = 0;
        vbifmt.fmt.sliced.reserved[1] = 0;

        if (IOCTL(vbi_fd, VIDIOC_S_FMT, &vbifmt) < 0) {
          log(0, "cPvrDevice::SetVBImode():error setting vbi mode (fmt), %d:%s",
              errno, strerror(errno));
          return false;
          }
        }
    }
  return true;
}

bool cPvrDevice::ParseChannel(const cChannel * Channel, int * input, int * norm) {

#ifdef PLUGINPARAMPATCHVERSNUM
  if (Channel->IsPlug()) {
    const char * str = Channel->PluginParam();
    char * PluginId = NULL;
    char * Input    = NULL;
    char * optArg1  = NULL;
    char * optArg2  = NULL;
    int NumArgs = 0;

    NumArgs = sscanf(str, "%a[^|]|%a[^|]|%a[^|]|%a[^:\n]",
              &PluginId, &Input, &optArg1, &optArg2);    
    if (NumArgs >= 2) {     
      if (strcasecmp(PluginId, "PVRINPUT")) return false;
      log(0, "**************new syntax!*****************");      
      vpid  = Channel->Vpid();
      apid  = Channel->Apid(0);
      tpid  = Channel->Tpid();
      if      (!strcasecmp (Input, "RADIO"))       EncoderSwitch=eRadio;
      else if (!strcasecmp (Input, "TV"))          EncoderSwitch=eTelevision;
      else if (!strncasecmp(Input, "COMPOSITE",9)) EncoderSwitch=eExternalInput;
      else if (!strncasecmp(Input, "SVIDEO",6))    EncoderSwitch=eExternalInput;
      else return false;
      if      (!strcasecmp (Input, "TUNER"))       *input = inputs[eTuner];
      else if (!strcasecmp (Input, "COMPOSITE0"))  *input = inputs[eComposite0];
      else if (!strcasecmp (Input, "COMPOSITE1"))  *input = inputs[eComposite1];
      else if (!strcasecmp (Input, "COMPOSITE2"))  *input = inputs[eComposite2];
      else if (!strcasecmp (Input, "COMPOSITE3"))  *input = inputs[eComposite3];
      else if (!strcasecmp (Input, "COMPOSITE4"))  *input = inputs[eComposite4];
      else if (!strcasecmp (Input, "SVIDEO0"))     *input = inputs[eSVideo0];
      else if (!strcasecmp (Input, "SVIDEO1"))     *input = inputs[eSVideo1];
      else if (!strcasecmp (Input, "SVIDEO2"))     *input = inputs[eSVideo2];
      else if (!strcasecmp (Input, "SVIDEO3"))     *input = inputs[eSVideo3];
      *norm = V4L2_STD_PAL;
      if (NumArgs > 2) { /* only if optional arg norm or card given */
        if      (!strcasecmp (optArg1, "SECAM"))  *norm = V4L2_STD_SECAM;
        else if (!strcasecmp (optArg1, "NTSC" ))  *norm = V4L2_STD_NTSC;
        if      (!strcasecmp (optArg1, "CARD0"))  if (number != 0) return false;
        else if (!strcasecmp (optArg1, "CARD1"))  if (number != 1) return false;
        else if (!strcasecmp (optArg1, "CARD2"))  if (number != 2) return false;
        else if (!strcasecmp (optArg1, "CARD3"))  if (number != 3) return false;
        }
      if (NumArgs > 3) { /* only if optional arg norm or card given */
        if      (!strcasecmp (optArg2, "SECAM"))  *norm = V4L2_STD_SECAM;
        else if (!strcasecmp (optArg2, "NTSC" ))  *norm = V4L2_STD_NTSC;
        if      (!strcasecmp (optArg2, "CARD0"))  if (number != 0) return false;
        else if (!strcasecmp (optArg2, "CARD1"))  if (number != 1) return false;
        else if (!strcasecmp (optArg2, "CARD2"))  if (number != 2) return false;
        else if (!strcasecmp (optArg2, "CARD3"))  if (number != 3) return false;
        }
      log(4, "ParseChannel %s input %d, norm %s",
              (EncoderSwitch==eRadio)?"Radio":(EncoderSwitch==eTelevision)?"TV":"Ext",
              *input,
             (*norm==V4L2_STD_PAL)?"PAL":(*norm==V4L2_STD_NTSC)?"NTSC":"SECAM");
      return true;
      }  
    }
#endif
  if (Channel->IsCable()) {
    vpid   = Channel->Vpid();
    apid   = Channel->Apid(0);
    tpid   = Channel->Tpid();
    *input = Channel->CoderateH();
    switch (Channel->Inversion()) {
      case 0:  *norm = V4L2_STD_NTSC;  break;
      case 1:  *norm = V4L2_STD_SECAM; break;
      default: *norm = V4L2_STD_PAL;   break;
      }
    switch (Channel->Frequency()) {
      case 0     ...   9999: EncoderSwitch=eExternalInput; break;
      case 87500 ... 108000: EncoderSwitch=eRadio;         break;
      default:               EncoderSwitch=eTelevision;
      }
    return true;
    }
  return false;           
}

bool cPvrDevice::SetChannelDevice(const cChannel * Channel, bool LiveView) {
  log(3, "cPvrDevice::SetChannelDevice %d (%s) %3.2fMHz", \
    Channel->Number(), Channel->Name(), (double) Channel->Frequency()/1000);
  int input, norm;

  if (! ParseChannel(Channel,&input,&norm) || isTuning) return false;
  if ((Channel->Frequency() == frequency) && (input == VideoInput)) {
    return true;
    }

  isTuning=true;
  delete readThread;
  SetEncoderState(eStop);
  tsBuffer->Clear();
  if (PvrSetup.repeat_ReInitAll_after_next_encoderstop) {
    ReInitAll();
    PvrSetup.repeat_ReInitAll_after_next_encoderstop = false;
    }

  switch (EncoderSwitch) {
    case eExternalInput:
          {
          log(4,"channel is external input.");
          if (radio_fd >= 0) {
            close(radio_fd);
            radio_fd = -1;
            SetControlValue(&PvrSetup.AudioVolumeTV, \
                      PvrSetup.AudioVolumeTV.value);
            SetControlValue(&PvrSetup.VideoBitrateTV, \
                      PvrSetup.VideoBitrateTV.value);
            }
          if (! SetInput(input))    return false;
          if (! SetVideoNorm(norm)) return false;
          frequency=Channel->Frequency(); // since we don't tune: set it here          
          break;
          }
    case eRadio   :
          {
          log(4,"channel is FM radio.");
          if (radioDevice < 0) return false; //no hardware support.
          if (radio_fd < 0) {
            char * devName;
            asprintf(&devName, "/dev/radio%d", radioDevice);
            radio_fd = open(devName, O_RDONLY);
            if (radio_fd < 0) {
              log(0, "Error opening FM radio device %s: %s",\
                  devName, strerror(errno));
              return false;
              }
            free(devName);
            }
          if (! SetInput(inputs[eTuner]))   return false;
          if (! Tune(Channel->Frequency())) return false;
          SetControlValue(&PvrSetup.AudioVolumeFM, \
            PvrSetup.AudioVolumeFM.value);
          SetControlValue(&PvrSetup.VideoBitrateFM, \
            PvrSetup.VideoBitrateFM.value);
          break;
          }
    case eTelevision :
          {
          log(4,"channel is television.");
          if (radio_fd >= 0) {
            close(radio_fd);
            radio_fd = -1;
            SetControlValue(&PvrSetup.AudioVolumeTV, \
                      PvrSetup.AudioVolumeTV.value);
            SetControlValue(&PvrSetup.VideoBitrateTV, \
                      PvrSetup.VideoBitrateTV.value);
            }
          if (! SetInput(inputs[eTuner]))   return false;
          if (norm != VideoNorm) { // only set if videonorm changed.
            if (! SetVideoNorm(norm)) return false;
            if (! SetVBImode(norm, PvrSetup.SliceVBI)  ) return false;
            }
          if (! Tune(Channel->Frequency())) return false;
          }
     }
  SetEncoderState(eStart);
  readThread = new cPvrReadThread(video_fd, vbi_fd, tsBuffer, &mutex);
  isTuning=false;   
  return true;
}


bool cPvrDevice::SetPid(cPidHandle * Handle, int Type, bool On) {
  log(4, "cPvrDevice::SetPid %d = %s", Handle->pid, On?"On":"Off");
  return true;
}

bool cPvrDevice::OpenDvr(void) {
  log(3, "cPvrDevice::OpenDvr");
  delivered = false;
  dvrOpen = true;
  return true;
}

void cPvrDevice::CloseDvr(void) {
  log(3, "cPvrDevice::CloseDvr");
  dvrOpen = false;
}

bool cPvrDevice::GetTSPacket(uchar *&Data) {

  int Count = 0;
  if (delivered) {
    tsBuffer->Del(TS_SIZE);
    delivered = false;
    }

  uchar *p = tsBuffer->Get(Count);
  if (p && Count >= TS_SIZE) {
    if (*p != TS_SYNC_BYTE) {
      for (int i = 1; i < Count; i++) {
        if (p[i] == TS_SYNC_BYTE) {
          Count = i;
          break;
          } //end: if
        } //end: for
      tsBuffer->Del(Count);
      log(1, "ERROR: skipped %d bytes to sync on TS packet\n", Count);
      return false;
      }
    delivered = true;
    Data = p;
    return true;
    }
  Data = NULL;
  return true;
}

int cPvrDevice::ProvidesCa(const cChannel *Channel) const {

  if (Channel->Ca() == kPvrCa) {
    log(3, "cPvrDevice::ProvidesCa %d (%s) CaID=0x%02x -> true", \
        Channel->Number(), Channel->Name(), Channel->Ca());
    return true;
    }
  return false;
}

bool cPvrDevice::ProvidesSource(int Source) const {

 if (cSource::IsCable(Source)
 #ifdef PLUGINPARAMPATCHVERSNUM
     || cSource::IsPlug(Source)
 #endif
      ) {
    log(3, "cPvrDevice::ProvidesSource Source=%s -> true", \
        (const char *) cSource::ToString(Source));
    return true;
    }
  return false;
}

bool cPvrDevice::ProvidesTransponder(const cChannel * Channel) const {
  return false;
}

/*
  see <vdr>/dvbdevice.c
  20070523 fix: 'Channel no available' during records from ext inputs
  -wirbel-
*/
bool cPvrDevice::ProvidesChannel(const cChannel * Channel, int Priority, \
                                 bool * NeedsDetachReceivers) const {
  bool result = false;
  bool needsDetachReceivers = false;

  if (! ProvidesSource(Channel->Source()) ||
     (Channel->IsCable() && ! ProvidesCa(Channel)))
    return false; // non-pvrinput channel

  if ((frequency == Channel->Frequency()) && \
      (VideoInput == Channel->CoderateH())) {
    log(3, "cPvrDevice::ProvidesChannel: %s already set -> true.", \
        Channel->Name());
    result = true; // dont need to detach receivers
    }
  else { // is other channel or actual no channel set
    result = Priority < 0 ||                /* actual channel w/o prio or    */
             Priority > this->Priority();   /* new channel with higher prio? */
    if (Priority >= 0 && Receiving(true)) { 
      needsDetachReceivers = true;          /* have to detach act. receiver  */
      }                                     /* with lower priority           */
    }
  if (NeedsDetachReceivers) *NeedsDetachReceivers = needsDetachReceivers;
  log(3, "cPvrDevice::ProvidesChannel %d (%s) %3.2fMHz, -> %s", \
    Channel->Number(), Channel->Name(), (double) Channel->Frequency()/1000, \
    result?"true":"false");
  return result;
}

/* 
Sets a V4L2 control.
If the value Val is out of range, the ivtv default value will be
used. The value set to V4L2 will be returned, either Val or the
default.
Has to be used *after* calling cPvrDevice::QueryAllControls()
20070506
If possible, use THIS function for setting VIDIOC_S_EXT_CTRLS.
-wirbel-
*/

int cPvrDevice::SetControlValue(struct valSet * vs, __s32 Val) {

 if (SetControlValue(vs->ctrl_class, vs->queryctrl.id, Val, \
 vs->queryctrl) == 0) {
   vs->value = Val;
   return 0;
   }
 else
   return -1;
}

/*
functionality as above, but no valSet needed.
*/

int cPvrDevice::SetControlValue(__u32 control, __s32 Val) {

__u32 ctrl_class=V4L2_CTRL_CLASS_MPEG;
struct v4l2_queryctrl * query; 

switch (control) {
  // picture properties
  case V4L2_CID_BRIGHTNESS:    ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.Brightness.queryctrl;
                               break;
  case V4L2_CID_CONTRAST:      ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.Contrast.queryctrl;
                               break;
  case V4L2_CID_SATURATION:    ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.Saturation.queryctrl;
                               break;
  case V4L2_CID_HUE:           ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.Hue.queryctrl;
                               break;
  // Audio
  case V4L2_CID_AUDIO_VOLUME:  ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.AudioVolumeTV.queryctrl;
                               break;
  case V4L2_CID_AUDIO_MUTE:    ctrl_class=V4L2_CTRL_CLASS_USER;
                               query=&PvrSetup.AudioMute.queryctrl;
                               break;
  case V4L2_CID_MPEG_AUDIO_L2_BITRATE:
                               query=&PvrSetup.AudioBitrate.queryctrl;
                               break;
  case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ:
                               query=&PvrSetup.AudioSampling.queryctrl;
                               break;
  case V4L2_CID_MPEG_AUDIO_ENCODING:
                               query=&PvrSetup.AudioEncoding.queryctrl;
                               break;
  // Video
  case V4L2_CID_MPEG_VIDEO_BITRATE:
                               query=&PvrSetup.VideoBitrateTV.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK:
                               query=&PvrSetup.VideoBitratePeak.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_ASPECT:
                               query=&PvrSetup.AspectRatio.queryctrl;
                               break;
  // MPEG
  case V4L2_CID_MPEG_STREAM_TYPE:
                               query=&PvrSetup.StreamType.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
                               query=&PvrSetup.BitrateMode.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_B_FRAMES:
                               query=&PvrSetup.BFrames.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
                               query=&PvrSetup.GopSize.queryctrl;
                               break;
  case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
                               query=&PvrSetup.GopClosure.queryctrl;
                               break;
  // Video Filters
  case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE:
                               query=&PvrSetup.FilterSpatialMode.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER:
                               query=&PvrSetup.FilterSpatial.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE:
                               query=&PvrSetup.FilterLumaSpatialType.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE:
                               query=&PvrSetup.FilterChromaSpatialType.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE:
                               query=&PvrSetup.FilterTemporalMode.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER:
                               query=&PvrSetup.FilterTemporal.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE:
                               query=&PvrSetup.FilterMedianType.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM:
                               query=&PvrSetup.FilterLumaMedianBottom.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP:
                               query=&PvrSetup.FilterLumaMedianTop.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM:
                               query=&PvrSetup.FilterChromaMedianBottom.queryctrl;
                               break;
  case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP:
                               query=&PvrSetup.FilterChromaMedianTop.queryctrl;
                               break;
  // VBI
  case V4L2_CID_MPEG_STREAM_VBI_FMT:
                               query=&PvrSetup.VBIformat.queryctrl;
                               break;
  default:
    log(0, "SetControlValue(__u32 control, __s32 Val): ERROR: control %d not included in switch(control)",\
       control);
    return -1;
  }

return SetControlValue(ctrl_class, control, Val, *query);
}


/* 
Sets a V4L2 control.
If the value Val is out of range, the ivtv default value will be
used. The value set to V4L2 will be returned, either Val or the
default.
Has to be used *after* calling cPvrDevice::QueryAllControls().
-wirbel-
*/

int cPvrDevice::SetControlValue(__u32 control_class, __u32 control, __s32 Val, \
                                struct v4l2_queryctrl queryctrl) {

  struct v4l2_ext_controls ctrls;
  struct v4l2_ext_control  ctrl;
  
  log (3, "SetControlValue(%s, %d)",queryctrl.name, Val);
  if ((queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) == V4L2_CTRL_FLAG_DISABLED){
    log(1, " Error: this control is disabled by driver. Skipped.");
    return Val;
    }    
  if ((Val>queryctrl.maximum) || (Val<queryctrl.minimum)) {
    log(1, "  Info: setting value to default(%d)",queryctrl.default_value);
    Val=queryctrl.default_value;
    }
  ctrl.id    = control;
  ctrl.value = Val;

  ctrls.ctrl_class=control_class;
  ctrls.controls=&ctrl;
  ctrls.count=1;

  if (IOCTL(video_fd, VIDIOC_S_EXT_CTRLS, &ctrls) != 0) {
    log(0, "cPvrDevice::SetControlValue(): setting control value: %d:%s",
        errno, strerror(errno));
      }
  return Val;
}


/*
Queries the properties of a given valSet
20070510
-> Before using function, valSet's queryctrl.id has to be set.
-wirbel-
*/

int cPvrDevice::QueryControl(struct valSet * vs) {
  if (vs->queryctrl.id == 0) {
    log(0, "cPvrDevice::QueryControl(): valSet not initialized.");
    return -1;
    }
  if (IOCTL(video_fd, VIDIOC_QUERYCTRL, &vs->queryctrl) != 0) {
    log(0, "QueryControl(): quering control %d failed.", vs->queryctrl.id);
    return -1;
    }
  return 0;
}


/*
Queries the properties of all valSets used in SetControlValue
20070510
-wirbel-
*/

bool cPvrDevice::QueryAllControls(void) {
  int err=0;
  log(3, "QueryAllControls");

  PvrSetup.Brightness.ctrl_class                = V4L2_CTRL_CLASS_USER;
  PvrSetup.Contrast.ctrl_class                  = V4L2_CTRL_CLASS_USER;
  PvrSetup.Saturation.ctrl_class                = V4L2_CTRL_CLASS_USER;
  PvrSetup.Hue.ctrl_class                       = V4L2_CTRL_CLASS_USER;
  // Audio
  PvrSetup.AudioVolumeTV.ctrl_class             = V4L2_CTRL_CLASS_USER;
  PvrSetup.AudioVolumeFM.ctrl_class             = V4L2_CTRL_CLASS_USER;
  PvrSetup.AudioMute.ctrl_class                 = V4L2_CTRL_CLASS_USER;
  PvrSetup.AudioBitrate.ctrl_class              = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.AudioSampling.ctrl_class             = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.AudioEncoding.ctrl_class             = V4L2_CTRL_CLASS_MPEG;
  // Video
  PvrSetup.VideoBitrateTV.ctrl_class            = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.VideoBitrateFM.ctrl_class            = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.VideoBitratePeak.ctrl_class          = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.AspectRatio.ctrl_class               = V4L2_CTRL_CLASS_MPEG;
  // MPEG
  PvrSetup.StreamType.ctrl_class                = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.BitrateMode.ctrl_class               = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.BFrames.ctrl_class                   = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.GopSize.ctrl_class                   = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.GopClosure.ctrl_class                = V4L2_CTRL_CLASS_MPEG;
  // Video Filters
  PvrSetup.FilterSpatialMode.ctrl_class         = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterSpatial.ctrl_class             = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterLumaSpatialType.ctrl_class     = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterChromaSpatialType.ctrl_class   = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterTemporalMode.ctrl_class        = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterTemporal.ctrl_class            = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterMedianType.ctrl_class          = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterLumaMedianBottom.ctrl_class    = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterLumaMedianTop.ctrl_class       = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterChromaMedianBottom.ctrl_class  = V4L2_CTRL_CLASS_MPEG;
  PvrSetup.FilterChromaMedianTop.ctrl_class     = V4L2_CTRL_CLASS_MPEG;

  PvrSetup.Brightness.queryctrl.id              = V4L2_CID_BRIGHTNESS;
  PvrSetup.Contrast.queryctrl.id                = V4L2_CID_CONTRAST;
  PvrSetup.Saturation.queryctrl.id              = V4L2_CID_SATURATION;
  PvrSetup.Hue.queryctrl.id                     = V4L2_CID_HUE;
  // Audio
  PvrSetup.AudioVolumeTV.queryctrl.id           = V4L2_CID_AUDIO_VOLUME;
  PvrSetup.AudioVolumeFM.queryctrl.id           = V4L2_CID_AUDIO_VOLUME;
  PvrSetup.AudioMute.queryctrl.id               = V4L2_CID_AUDIO_MUTE;
  PvrSetup.AudioBitrate.queryctrl.id            = V4L2_CID_MPEG_AUDIO_L2_BITRATE;
  PvrSetup.AudioSampling.queryctrl.id           = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ;
  PvrSetup.AudioEncoding.queryctrl.id           = V4L2_CID_MPEG_AUDIO_ENCODING;
  // Video
  PvrSetup.VideoBitrateTV.queryctrl.id          = V4L2_CID_MPEG_VIDEO_BITRATE;
  PvrSetup.VideoBitrateFM.queryctrl.id          = V4L2_CID_MPEG_VIDEO_BITRATE;
  PvrSetup.VideoBitratePeak.queryctrl.id        = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK;
  PvrSetup.AspectRatio.queryctrl.id             = V4L2_CID_MPEG_VIDEO_ASPECT;
  // MPEG
  PvrSetup.StreamType.queryctrl.id              = V4L2_CID_MPEG_STREAM_TYPE;
  PvrSetup.BitrateMode.queryctrl.id             = V4L2_CID_MPEG_VIDEO_BITRATE_MODE;
  PvrSetup.BFrames.queryctrl.id                 = V4L2_CID_MPEG_VIDEO_B_FRAMES;
  PvrSetup.GopSize.queryctrl.id                 = V4L2_CID_MPEG_VIDEO_GOP_SIZE;
  PvrSetup.GopClosure.queryctrl.id              = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE;
  // Video Filters
  PvrSetup.FilterSpatialMode.queryctrl.id       = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE;
  PvrSetup.FilterSpatial.queryctrl.id           = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER;
  PvrSetup.FilterLumaSpatialType.queryctrl.id   = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE;
  PvrSetup.FilterChromaSpatialType.queryctrl.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE;
  PvrSetup.FilterTemporalMode.queryctrl.id      = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE;
  PvrSetup.FilterTemporal.queryctrl.id          = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER;
  PvrSetup.FilterMedianType.queryctrl.id        = V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE;
  PvrSetup.FilterLumaMedianBottom.queryctrl.id  = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM;
  PvrSetup.FilterLumaMedianTop.queryctrl.id     = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP;
  PvrSetup.FilterChromaMedianBottom.queryctrl.id= V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM;
  PvrSetup.FilterChromaMedianTop.queryctrl.id   = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP;
  // VBI
  PvrSetup.VBIformat.queryctrl.id               = V4L2_CID_MPEG_STREAM_VBI_FMT;
  /* now quering min, max, default */
  // picture properties
  err += QueryControl(&PvrSetup.Brightness);
  err += QueryControl(&PvrSetup.Contrast);
  err += QueryControl(&PvrSetup.Saturation);
  err += QueryControl(&PvrSetup.Hue);
  // Audio
  err += QueryControl(&PvrSetup.AudioVolumeTV);
  err += QueryControl(&PvrSetup.AudioVolumeFM);
  err += QueryControl(&PvrSetup.AudioMute);
  err += QueryControl(&PvrSetup.AudioBitrate);
  err += QueryControl(&PvrSetup.AudioSampling);
  err += QueryControl(&PvrSetup.AudioEncoding);
  // Video
  err += QueryControl(&PvrSetup.VideoBitrateTV);
  err += QueryControl(&PvrSetup.VideoBitrateFM);
  err += QueryControl(&PvrSetup.VideoBitratePeak);
  err += QueryControl(&PvrSetup.AspectRatio);
  // MPEG
  err += QueryControl(&PvrSetup.StreamType);
  err += QueryControl(&PvrSetup.BitrateMode);
  err += QueryControl(&PvrSetup.BFrames);
  err += QueryControl(&PvrSetup.GopSize);
  err += QueryControl(&PvrSetup.GopClosure);
  // Video Filters
  err += QueryControl(&PvrSetup.FilterSpatialMode);
  err += QueryControl(&PvrSetup.FilterSpatial);
  err += QueryControl(&PvrSetup.FilterLumaSpatialType);
  err += QueryControl(&PvrSetup.FilterChromaSpatialType);
  err += QueryControl(&PvrSetup.FilterTemporalMode);
  err += QueryControl(&PvrSetup.FilterTemporal);
  err += QueryControl(&PvrSetup.FilterMedianType);
  err += QueryControl(&PvrSetup.FilterLumaMedianBottom);
  err += QueryControl(&PvrSetup.FilterLumaMedianTop);
  err += QueryControl(&PvrSetup.FilterChromaMedianBottom);
  err += QueryControl(&PvrSetup.FilterChromaMedianTop);
  // VBI
  err += QueryControl(&PvrSetup.VBIformat);
  if (err)
    return false;
  // The following code checks wether a valSet.value is 
  // INVALID_VALUE and if so -> set it to its queryctrl.default_value.
  // The macro INIT(v) is a abbreviation for the 'if .. then' comparison.
  #define INIT(v) if (v.value == INVALID_VALUE) v.value=v.queryctrl.default_value
  // picture properties
  INIT(PvrSetup.Brightness);
  INIT(PvrSetup.Contrast);
  INIT(PvrSetup.Saturation);
  INIT(PvrSetup.Hue);
  // Audio
  if (PvrSetup.AudioVolumeTV.value == INVALID_VALUE)
    PvrSetup.AudioVolumeTV.value = \
         (int) (0.95 * PvrSetup.AudioVolumeTV.queryctrl.maximum);
  if (PvrSetup.AudioVolumeFM.value == INVALID_VALUE)
    PvrSetup.AudioVolumeFM.value = \
         PvrSetup.AudioVolumeFM.queryctrl.maximum;
  INIT(PvrSetup.AudioBitrate);
  INIT(PvrSetup.AudioSampling);
  // Video
  INIT(PvrSetup.VideoBitrateTV);
  if (PvrSetup.VideoBitrateFM.value == INVALID_VALUE)
    PvrSetup.VideoBitrateFM.value = \
         (int) (0.25 * PvrSetup.VideoBitrateFM.queryctrl.default_value);
  INIT(PvrSetup.AspectRatio);
  // MPEG
  INIT(PvrSetup.BitrateMode);
  INIT(PvrSetup.BFrames);
  INIT(PvrSetup.GopSize);
  INIT(PvrSetup.GopClosure);
  // Video Filters
  INIT(PvrSetup.FilterSpatialMode);
  INIT(PvrSetup.FilterSpatial);
  INIT(PvrSetup.FilterLumaSpatialType);
  INIT(PvrSetup.FilterChromaSpatialType);
  INIT(PvrSetup.FilterTemporalMode);
  INIT(PvrSetup.FilterTemporal);
  INIT(PvrSetup.FilterMedianType);
  INIT(PvrSetup.FilterLumaMedianBottom);
  INIT(PvrSetup.FilterLumaMedianTop);
  INIT(PvrSetup.FilterChromaMedianBottom);
  INIT(PvrSetup.FilterChromaMedianTop);
  return true;
}

