#include "common.h"


cPvrDevice * PvrDevices[kMaxPvrDevices];

cPvrDevice::cPvrDevice(int DeviceNumber)
:  number(DeviceNumber),
  frequency(-1),
  VideoNorm(-1),
  VideoInput(-1),
  delivered(false),
  isTuning(false),
  driver(undef)
{ log(4,"new cPvrDevice (%d)", number);
  v4l2_fd = mpeg_fd = radio_fd = -1;
  v4l2_dev = mpeg_dev = radio_dev = -1;
  vpid = apid = tpid = -1;
  cString devName;
  struct v4l2_capability video_vcap;
  struct v4l2_capability capability;
  struct v4l2_input input;
  int i;
  // put all values here that are set only *once*
  v4l2_dev = number; 
  devName = cString::sprintf("/dev/video%d", v4l2_dev);
  v4l2_fd = open(devName, O_RDWR);
  if (v4l2_fd < 0) {
    log(0, "cPvrDevice::cPvrDevice(): error opening video device %s: %s",
      *devName, strerror(errno));
    }
  memset(&video_vcap, 0, sizeof(video_vcap));
  IOCTL(v4l2_fd, VIDIOC_QUERYCAP, &video_vcap);
  log(3, "%s = %s", *devName, video_vcap.card);
  BusID = cString::sprintf("%s", video_vcap.bus_info);
  log(3, "BusID = %s", *BusID);
  if      ((!memcmp(video_vcap.driver, "ivtv",4)) || (!memcmp(video_vcap.driver, "IVTV",4)))   driver = ivtv;
  else if  (!memcmp(video_vcap.driver, "pvrusb2",7))                                           driver = pvrusb2;
  else if  (!memcmp(video_vcap.driver, "cx88_blackbird",14))                                   driver = cx88_blackbird;
  else { driver = undef; log(3, "%s",video_vcap.driver); } 
  driver_apiversion = video_vcap.version;
  log(3, "%s Version 0x%06x", driver==ivtv?"ivtv": \
                              driver==pvrusb2?"pvrusb2": \
                              driver==cx88_blackbird?"cx88_blackbird":"undef", \
                              driver_apiversion);
  for (i=0; i<kMaxPvrDevices; i++) {
    if (radio_dev<0 && (video_vcap.capabilities & V4L2_CAP_RADIO)) { //searching matching radio dev
      devName = cString::sprintf("/dev/radio%d", i);
      radio_fd = open(devName, O_RDWR);
      if (radio_fd) {
        memset(&capability, 0, sizeof(capability));
        IOCTL(radio_fd, VIDIOC_QUERYCAP, &capability);
        if (!strncmp(*BusID,(const char *)  capability.bus_info, strlen(*BusID)-1)) {
          radio_dev=i; // store info for later
          log(3, "/dev/radio%d = FM radio dev",radio_dev);
          }
        close(radio_fd);
        radio_fd = -1;
        }
      }
    if (mpeg_dev<0 && (driver == cx88_blackbird)) { // the blackbird uses two (!) different devices, search the other one.
      close(v4l2_fd);
      v4l2_fd = -1;
      devName = cString::sprintf("/dev/video%d", i);
      mpeg_fd = open(devName, O_RDWR);
      if (mpeg_fd) {
        memset(&capability, 0, sizeof(capability));
        IOCTL(mpeg_fd, VIDIOC_QUERYCAP, &capability);
        if (!strncmp(*BusID ,(const char *) capability.bus_info, strlen(*BusID)-1) &&
            !strcmp("cx8800",(const char *)  capability.driver)) {
          mpeg_dev = v4l2_dev; //for this driver we found mpeg_dev up to now.
          v4l2_dev = i;        //reassigning, now with correct value.
          log(3, "/dev/video%d = v4l2 dev (analog properties: volume/hue/brightness/inputs..)",v4l2_dev);
          log(3, "/dev/video%d = mpeg dev (MPEG properties: bitrates/frame rate/filters..)",mpeg_dev);
          }
        close(mpeg_fd);
        mpeg_fd = -1;
        }
      }
    } // end device search loop
  switch (driver) {
    case ivtv:        //ivtv and pvrusb2 share the same device.
    case pvrusb2:
          mpeg_dev = v4l2_dev;
          mpeg_fd  = v4l2_fd;
          break;
    case cx88_blackbird:
                      //blackbird: reopen mpeg device.               //FIXME: WE SHOULD OPEN V4L2 DEV for inputs/picture properties/volume, mpeg for all other stuff 
          devName = cString::sprintf("/dev/video%d", mpeg_dev);
          v4l2_fd = open(devName, O_RDWR);                           //FIXME: FOR NOW THIS IS A WILD MIXTURE.
          break;
    default:;
    }
  QueryAllControls(); //we have to split in mpeg and v4l2 here.
  memset(&inputs, -1, sizeof(inputs)); 
  numInputs = 0;
  for (i = 0;; i++) {
    memset(&input, 0, sizeof(input));
    input.index = i;
    if (IOCTL(v4l2_fd, VIDIOC_ENUMINPUT, &input) == -1) {
      break;
      }
    else {
      log(3, "input %d = %s", i, input.name);
      numInputs++;
      }
    if      (!memcmp(input.name, "Tuner",        5)) { inputs[eTelevision]=inputs[eRadio]=i; continue; } //ivtv:   Radio and TV tuner are same input.
    else if (!memcmp(input.name, "television",  10)) { inputs[eTelevision]=i; continue; }                //pvrusb2
    else if (!memcmp(input.name, "Television",  10)) { inputs[eTelevision]=i; continue; }                //cx88_blackbird
    else if (!memcmp(input.name, "radio",        5)) { inputs[eRadio]     =i; continue; }                //pvrusb2
    else if (!memcmp(input.name, "Composite 0", 11)) { inputs[eComposite0]=i; continue; }
    else if (!memcmp(input.name, "Composite 1", 11)) { inputs[eComposite1]=i; continue; }
    else if (!memcmp(input.name, "Composite 2", 11)) { inputs[eComposite2]=i; continue; }
    else if (!memcmp(input.name, "Composite 3", 11)) { inputs[eComposite3]=i; continue; }
    else if (!memcmp(input.name, "Composite 4", 11)) { inputs[eComposite4]=i; continue; }
    else if (!memcmp(input.name, "Composite1",  10)) { inputs[eComposite1]=i; continue; }                //cx88_blackbird
    else if (!memcmp(input.name, "composite",    9)) { inputs[eComposite0]=i; continue; }                //pvrusb2
    else if (!memcmp(input.name, "S-Video 3",    9)) { inputs[eSVideo3]   =i; continue; }
    else if (!memcmp(input.name, "S-Video 0",    9)) { inputs[eSVideo0]   =i; continue; }
    else if (!memcmp(input.name, "S-Video 1",    9)) { inputs[eSVideo1]   =i; continue; }
    else if (!memcmp(input.name, "S-Video 2",    9)) { inputs[eSVideo2]   =i; continue; }
    else if (!memcmp(input.name, "S-Video",      7)) { inputs[eSVideo0]   =i; continue; }                //cx88_blackbird
    else if (!memcmp(input.name, "s-video",      7)) { inputs[eSVideo0]   =i; continue; }                //pvrusb2
    else log(0, "unknown input %s. PLEASE SEND LOG TO MAINTAINER.", input.name);
    }
  if (inputs[eTelevision] >= 0) 
    SetInput(inputs[eTelevision]);
  else  
    log(0, "device has no tuner");
  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);
  if (driver == ivtv) {
    SetVBImode(V4L2_STD_PAL, PvrSetup.VBIformat.value);
    }
  ReInit();
  readThread = new cPvrReadThread(v4l2_fd, tsBuffer, &mutex, this);
}

cPvrDevice::~cPvrDevice() {
  cRingBufferLinear * tsBuffer_tmp = tsBuffer;
  log(4,"~cPvrDevice()");
  tsBuffer = NULL;
  delete tsBuffer_tmp;
  close(radio_fd);
  close(v4l2_fd);
}

bool cPvrDevice::Probe(int DeviceNumber) {
  char device[256];
  struct v4l2_capability vcap;
  struct v4l2_format vfmt;
  int v4l2_fd = 0;
  bool found = false;
  memset(&vcap, 0, sizeof(vcap));
  memset(&vfmt, 0, sizeof(vfmt));
  sprintf(device, "/dev/video%d", DeviceNumber);
  v4l2_fd = open(device, O_RDONLY);
  if (v4l2_fd) {
    IOCTL(v4l2_fd, VIDIOC_QUERYCAP, &vcap);
    if (!memcmp(vcap.driver, "ivtv",4) ||
        !memcmp(vcap.driver, "pvrusb2", 7))
       found = true;
    if (!memcmp(vcap.driver, "cx88_blackbird", 14)) {
      vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
      if (! IOCTL(v4l2_fd, VIDIOC_G_FMT, &vfmt) &&
         (v4l2_fourcc('M','P','E','G') == vfmt.fmt.pix.pixelformat))      
          found=true;
       }
    close(v4l2_fd);
    if (found) log(3, "cPvrDevice::Probe():found %s", vcap.card);
    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, "cPvrDevice::Initialize(): found %d PVR device%s", found, found > 1 ? "s" : "");
  else
    log(1, "cPvrDevice::Initialize(): 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) {
        log(3,"cPvrDevice::Stop() for Device %i", i);
        PvrDevices[i]->StopReadThread();
        PvrDevices[i]->TurnOffSlicedVBI();
        }
      }
    }
}

void cPvrDevice::TurnOffSlicedVBI(void) {
  struct v4l2_format vbifmt;
  struct v4l2_ext_controls ctrls;
  struct v4l2_ext_control  ctrl;
  if (driver== ivtv && v4l2_fd > 0) {
    SetEncoderState(eStop);
    memset(&vbifmt, 0, sizeof(vbifmt));
    memset(&ctrls,  0, sizeof(ctrls));
    memset(&ctrl,   0, sizeof(ctrl));
    ctrl.id    = V4L2_CID_MPEG_STREAM_VBI_FMT;
    ctrl.value = 0;
    ctrls.ctrl_class=V4L2_CTRL_CLASS_MPEG;
    ctrls.controls=&ctrl;
    ctrls.count=1;
    if (IOCTL(v4l2_fd, VIDIOC_S_EXT_CTRLS, &ctrls) != 0) {
      log(0, "cPvrDevice::Stop(): error turning off Sliced VBI mode (ctrls), %d:%s",
        errno, strerror(errno));
      }
    }
}

void cPvrDevice::StopReadThread(void) {
  cPvrReadThread * readThread_tmp = readThread;
  readThread = NULL;
  delete readThread_tmp;
}

void cPvrDevice::ReInitAll(void) {
  log(3,"cPvrDevice::ReInitAll");
  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) {
  log(3,"cPvrDevice::ReInit");
  SetTunerAudioMode(PvrSetup.TunerAudioMode);
  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 ( ((driver == ivtv) && (radio_fd >= 0)) || ((driver == pvrusb2) && (VideoInput == inputs[eRadio])) ) {
    SetControlValue(&PvrSetup.AudioVolumeFM, \
                  PvrSetup.AudioVolumeFM.value);
    SetControlValue(&PvrSetup.AudioMute, \
                  (int) (PvrSetup.AudioVolumeFM.value == 0));
    SetControlValue(&PvrSetup.VideoBitrateFM, \
                  PvrSetup.VideoBitrateFM.value);
    }
  else { //no radio
    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) {
  double fac = 16;
  int freqaux = freq;
  struct v4l2_frequency vf;
  if (frequency == freq) return true;
  if ((EncoderInput == eRadio) || (EncoderInput == eTelevision)) {
    memset(&vf, 0, sizeof(vf));
    struct v4l2_tuner tuner;
    memset(&tuner, 0, sizeof(tuner));
    if (IOCTL(v4l2_fd, VIDIOC_G_TUNER,&tuner) == 0) {
      fac = (tuner.capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
      }
    vf.tuner = 0;
    vf.type = tuner.type;
    vf.frequency = (int) ((double) freqaux * fac / 1000.0);

    if (IOCTL(v4l2_fd, VIDIOC_S_FREQUENCY, &vf) == 0) {
      log(3, "cPvrDevice::Tune(): Frequency set to %.2f MHz (%d)", \
        vf.frequency / fac, vf.frequency);
      }
    else {
      log(0, "cPvrDevice::Tune(): error tuning to %.2f MHz (%d): %s", \
          vf.frequency / fac, vf.frequency, strerror(errno));
      return false;
      }
    }
  frequency = freq;
  return true;
}

bool cPvrDevice::SetInput(int input) {

  if (input == VideoInput) return true;
  log(3,"cPvrDevice::SetInput to %d", input);
  if (IOCTL(v4l2_fd, VIDIOC_S_INPUT, &input) !=0) {
    log(0, "VIDIOC_S_INPUT failed, %d:%s",
      errno, strerror(errno));
    return false;
    }
  VideoInput=input;
  if (driver == pvrusb2) {
    usleep(100000); /* 100msec */
    if (VideoInput == inputs[eRadio]) {
       SetControlValue(&PvrSetup.AudioVolumeFM, \
         PvrSetup.AudioVolumeFM.value);
       SetControlValue(&PvrSetup.VideoBitrateFM, \
         PvrSetup.VideoBitrateFM.value);
       }
    else { //television or extern
       SetControlValue(&PvrSetup.AudioVolumeTV, \
         PvrSetup.AudioVolumeTV.value);
       SetControlValue(&PvrSetup.VideoBitrateTV, \
         PvrSetup.VideoBitrateTV.value);
       }
    }
  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(v4l2_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) {
  log(3,"cPvrDevice::SetVideoSize");
  struct v4l2_format vfmt;

  vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (IOCTL(v4l2_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(v4l2_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::SetTunerAudioMode(int tuneraudiomode) {
  log(3, "cPvrDevice::SetTunerAudioMode(%s)",
     tuneraudiomode == V4L2_TUNER_MODE_MONO?"mono":
     tuneraudiomode == V4L2_TUNER_MODE_STEREO?"stereo":
     tuneraudiomode == V4L2_TUNER_MODE_LANG1?"lang1":
     tuneraudiomode == V4L2_TUNER_MODE_LANG2?"lang2":"bilingual");
  struct v4l2_tuner vt;
  memset(&vt, 0, sizeof(vt));
  vt.audmode = tuneraudiomode;
  if (IOCTL(v4l2_fd, VIDIOC_S_TUNER, &vt)) 
    log(0, "cPvrDevice::SetTunerAudioMode(%d): error %d:%s", \
          tuneraudiomode, errno, strerror(errno)); 
}

void cPvrDevice::SetEncoderState(eEncState state) {
  log(3, "cPvrDevice::SetEncoderState (%s)", state==eStop?"Stop":"Start");
  if ((driver == ivtv) && (driver_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(v4l2_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 or pvrusb2
    switch(state) {
      case eStop:   // VIDIOC_STREAMON/OFF doesn`t work properly according to Hans Verkuil
                    if (driver == pvrusb2) {
                      if (PvrSetup.Pvrusb2StopOnChange==0)
                        break;
                      }
                    close(v4l2_fd);
                    char * devName;
                    asprintf(&devName, "/dev/video%d", number);
                    v4l2_fd = open(devName, O_RDWR);  //reopen for tuning
                    if (v4l2_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;

  log(3, "SetVBImode(%s, %d)", \
      std==V4L2_STD_NTSC?"NTSC":std==V4L2_STD_SECAM?"SECAM":"PAL", \
      vbistatus);
  if (v4l2_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;


    ctrl.value = PvrSetup.VBIformat.value;
    ctrls.ctrl_class=V4L2_CTRL_CLASS_MPEG;
    ctrls.controls=&ctrl;
    ctrls.count=1;

    if (IOCTL(v4l2_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(v4l2_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, int *card,
                             eVideoInputs *EncoderInput, int *apid, int *vpid, int *tpid) const {

  *card  = 999;
  *norm  = V4L2_STD_PAL;
  *input = -1;

#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(3, "cPvrDevice::ParseChannel: using iptv channel syntax.");
      *vpid  = Channel->Vpid();
      *apid  = Channel->Apid(0);
      *tpid  = Channel->Tpid();
      if      (!strcasecmp (Input, "RADIO"))       *EncoderInput=eRadio;
      else if (!strcasecmp (Input, "TV"))          *EncoderInput=eTelevision;
      else if (!strncasecmp(Input, "COMPOSITE",9)) *EncoderInput=eExternalInput;
      else if (!strncasecmp(Input, "SVIDEO",6))    *EncoderInput=eExternalInput;
      else return false;
      if      (!strcasecmp (Input, "TV"))          *input = inputs[eTelevision];
      else if (!strcasecmp (Input, "RADIO"))       *input = inputs[eRadio];
      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];
      else return false; //unknown
      *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"))   *card = 0;
        else if (!strcasecmp (optArg1, "CARD1"))   *card = 1;
        else if (!strcasecmp (optArg1, "CARD2"))   *card = 2;
        else if (!strcasecmp (optArg1, "CARD3"))   *card = 3;
        else if (!strcasecmp (optArg1, "CARD4"))   *card = 4;
        else if (!strcasecmp (optArg1, "CARD5"))   *card = 5;
        else if (!strcasecmp (optArg1, "CARD6"))   *card = 6;
        else if (!strcasecmp (optArg1, "CARD7"))   *card = 7;
        }
      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"))   *card = 0;
        else if (!strcasecmp (optArg2, "CARD1"))   *card = 1;
        else if (!strcasecmp (optArg2, "CARD2"))   *card = 2;
        else if (!strcasecmp (optArg2, "CARD3"))   *card = 3;
        else if (!strcasecmp (optArg2, "CARD4"))   *card = 4;
        else if (!strcasecmp (optArg2, "CARD5"))   *card = 5;
        else if (!strcasecmp (optArg2, "CARD6"))   *card = 6;
        else if (!strcasecmp (optArg2, "CARD7"))   *card = 7;
        }
      log(4, "ParseChannel %s input %d, norm %s, card %d",
              (*EncoderInput==eRadio)?"Radio":(*EncoderInput==eTelevision)?"TV":"Ext",
              *input,
             (*norm==V4L2_STD_PAL)?"PAL":(*norm==V4L2_STD_NTSC)?"NTSC":"SECAM",
              *card);
      return true;
      }  
    }
#endif
  if (Channel->IsCable() && ProvidesCa(Channel)) {
    log(3, "cPvrDevice::ParseChannel: using old channel syntax.");
    *vpid   = Channel->Vpid();
    *apid   = Channel->Apid(0);
    *tpid   = Channel->Tpid();
    if (Channel->CoderateH() < 999)
      *input = Channel->CoderateH();
    else
      *input = inputs[eTelevision];
    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: *EncoderInput=eExternalInput; break;
      case 87500 ... 108000: *EncoderInput=eRadio;         break;
      default:               *EncoderInput=eTelevision;
      }
    return true;
    }
  return false;           
}

bool cPvrDevice::SetChannelDevice(const cChannel * Channel, bool LiveView) {
  log(3, "cPvrDevice::SetChannelDevice %d (%s) %3.2fMHz (/dev/video%d = %s)", \
    Channel->Number(), Channel->Name(), (double) Channel->Frequency()/1000,  number,\
    driver==ivtv?"ivtv":driver==ivtv?"ivtv":driver==pvrusb2?"pvrusb2": \
    driver==cx88_blackbird?"cx88_blackbird":"undef");
  int input, norm, card;
  if (! ParseChannel(Channel,&input,&norm,&card,&EncoderInput,&apid,&vpid,&tpid) || isTuning)
     return false;
  if ((Channel->Frequency() == frequency) && (input == VideoInput) && (norm == VideoNorm))
    return true;

  isTuning=true;
  StopReadThread();
  SetEncoderState(eStop);
  tsBuffer->Clear();
  if (driver == ivtv) {
    if (PvrSetup.repeat_ReInitAll_after_next_encoderstop) {
      ReInitAll(); //some settings require an encoder stop, so we repeat them now
      PvrSetup.repeat_ReInitAll_after_next_encoderstop = false;
      }
    }
  switch (EncoderInput) {
    case eComposite0 ... eComposite4:   //no break here, continuing at next case item
    case eSVideo0 ... eSVideo3:         //no break here, continuing at next case item
    case eExternalInput:
          {
          log(4,"channel is external input.");
          if (driver == ivtv && 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.");
          switch (driver) {
            case ivtv:
              if (radio_dev < 0) return false; //no hardware support.
              if (radio_fd < 0) {
                char * devName;
                asprintf(&devName, "/dev/radio%d", radio_dev);
                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);
                SetControlValue(&PvrSetup.AudioVolumeFM, \
                  PvrSetup.AudioVolumeFM.value);
                SetControlValue(&PvrSetup.VideoBitrateFM, \
                  PvrSetup.VideoBitrateFM.value);
                }
              if (! SetInput(inputs[eRadio]))   return false;
              break;
            case pvrusb2:
              if (! SetInput(inputs[eRadio]))   return false;
              break;
            case cx88_blackbird:
              break;
            case undef: log(0, "driver is unknown!!"); return false;
            }
          if (! Tune(Channel->Frequency())) return false;
          break;
          }
    case eTelevision :
          {
          log(4,"channel is television.");
          if ((driver == ivtv) && (radio_fd >= 0)) {
            close(radio_fd);
            radio_fd = -1;
            SetControlValue(&PvrSetup.AudioVolumeTV, \
                      PvrSetup.AudioVolumeTV.value);
            SetControlValue(&PvrSetup.VideoBitrateTV, \
                      PvrSetup.VideoBitrateTV.value);
            }
          if (! SetInput(inputs[eTelevision]))   return false;
          if (norm != VideoNorm) { // only set if videonorm changed.
            if (! SetVideoNorm(norm)) return false;
            if (driver == ivtv) {
              if (! SetVBImode(norm, PvrSetup.VBIformat.value)  ) return false;
              }
            }
          if (! Tune(Channel->Frequency())) return false;
          }
    }
  SetEncoderState(eStart);
  readThread = new cPvrReadThread(v4l2_fd, tsBuffer, &mutex, this);
  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;
  return true;
}

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

bool cPvrDevice::GetTSPacket(uchar *&Data) {
  int Count = 0;
  if (tsBuffer && readThread) {
    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;
    }
  return false;
}

int cPvrDevice::ProvidesCa(const cChannel *Channel) const {
  return (Channel->Ca() == kPvrCa);
}

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 not available' during records from ext inputs
  20080823 fix: all checks for valid channel here, not in SetChannelDevice!
  -wirbel-
*/
bool cPvrDevice::ProvidesChannel(const cChannel * Channel, int Priority, \
                                 bool * NeedsDetachReceivers) const {
  bool result = false;
  bool needsDetachReceivers = false;
  int input, norm, dev;
  eVideoInputs encoderInput;          //only dummy
  int audioPid,videoPid,teletextPid;  //only dummy

  if (! ParseChannel(Channel, &input, &norm, &dev, &encoderInput, &audioPid, &videoPid, &teletextPid))
    return false; // non-pvrinput channel or wrong syntax used, silently ignore this one
  if ((dev < 999) && (number != dev)) {
    log(3, "cPvrDevice::ProvidesChannel %s -> false (this is /dev/video%d, but CARD parameter says /dev/video%d only)",
        Channel->Name(), number, dev); 
    return false;
    }
  if ((PvrSetup.UseOnlyCard != 8) && (number != PvrSetup.UseOnlyCard)) {
    log(3, "cPvrDevice::ProvidesChannel %s -> false (this is /dev/video%d, but /dev/video%d only was selected in setup)",
        Channel->Name(), number, PvrSetup.UseOnlyCard); 
    return false;
    }
  if (Channel->Frequency() >= 87500 && Channel->Frequency() <= 108000) {
    if ((driver == ivtv) && (radio_dev < 0)) {
      log(3, "cPvrDevice::ProvidesChannel: /dev/video%d has no radio", number);
      return false;
      }
    }
  if ((input < 0) || (input >= numInputs)) {
    log(0, "cPvrDevice::ProvidesChannel: unknown input %d", input);
    return false;
    }
  if ((frequency == Channel->Frequency()) && \
      (VideoInput == input) && \
      (VideoNorm == norm)) {
    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: /dev/video%d: Channel %d (%s) %3.2fMHz, -> %s", \
    number, 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;
  if (driver == ivtv) {
    // 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(v4l2_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(v4l2_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;
  if (driver == ivtv) {
    // 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);
  if (driver == ivtv) {
    // 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;
}

