/*
 *  written by Edgard Lima <edgard.lima@indt.org.br>
 *  Use it for free.
 */


#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define DEVNAME "/dev/video0"
//#define DEVNAME "/dev/video1"
//#define DEVNAME "/dev/radio0"

void print_v4l2_std(const v4l2_std_id std) {
  
  //  fprintf(stderr, "std = 0x%08x%08x = 0", std >> 32, std & 0xFFFFFFFF);
  fprintf(stderr, "std = 0x%016llx = 0", std);

  /* PAL */
  if ( std & V4L2_STD_PAL_B ) {
    fprintf(stderr, " | PAL_B");
  }
  if ( std & V4L2_STD_PAL_B1 ) {
    fprintf(stderr, " | PAL_B1");
  }
  if ( std & V4L2_STD_PAL_G ) {
    fprintf(stderr, " | PAL_G");
  }
  if ( std & V4L2_STD_PAL_H ) {
    fprintf(stderr, " | PAL_H");
  }
  if ( std & V4L2_STD_PAL_I ) {
    fprintf(stderr, " | PAL_I");
  }
  if ( std & V4L2_STD_PAL_D ) {
    fprintf(stderr, " | PAL_D");
  }
  if ( std & V4L2_STD_PAL_D1 ) {
    fprintf(stderr, " | PAL_D1");
  }
  if ( std & V4L2_STD_PAL_K ) {
    fprintf(stderr, " | PAL_K");
  }
  if ( std & V4L2_STD_PAL_M ) {
    fprintf(stderr, " | PAL_M");
  }
  if ( std & V4L2_STD_PAL_N ) {
    fprintf(stderr, " | PAL_N");
  }
  if ( std & V4L2_STD_PAL_Nc ) {
    fprintf(stderr, " | PAL_Nc");
  }
  if ( std & V4L2_STD_PAL_60 ) {
    fprintf(stderr, " | PAL_60");
  }


  /* NTSC */
  if ( std & V4L2_STD_NTSC_M ) {
    fprintf(stderr, " | NTSC_M");
  }
  if ( std & V4L2_STD_NTSC_M_JP ) {
    fprintf(stderr, " | NTSC_M_JP");
  }

#if 0
  if ( std & V4L2_STD_NTSC_443 ) {
    fprintf(stderr, " | NTSC_443");
  }
#endif

  /* SECAM */
  if ( std & V4L2_STD_SECAM_B ) {
    fprintf(stderr, " | SECAM_B");
  }
  if ( std & V4L2_STD_SECAM_D ) {
    fprintf(stderr, " | SECAM_D");
  }
  if ( std & V4L2_STD_SECAM_G ) {
    fprintf(stderr, " | SECAM_G");
  }
  if ( std & V4L2_STD_SECAM_H ) {
    fprintf(stderr, " | SECAM_H");
  }
  if ( std & V4L2_STD_SECAM_K ) {
    fprintf(stderr, " | SECAM_K");
  }
  if ( std & V4L2_STD_SECAM_K1 ) {
    fprintf(stderr, " | SECAM_K1");
  }
  if ( std & V4L2_STD_SECAM_L ) {
    fprintf(stderr, " | SECAM_L");
  }

#if 0
  if ( std & V4L2_STD_SECAM_LC ) {
    fprintf(stderr, " | SECAM_LC");
  }
#endif

  if ( std & V4L2_STD_ATSC_8_VSB ) {
    fprintf(stderr, " | V4L2_STD_ATSC_8_VSB");
  }
  if ( std & V4L2_STD_ATSC_16_VSB ) {
    fprintf(stderr, " | V4L2_STD_ATSC_16_VSB");
  }


  fprintf(stderr, "\n");

}

void print_v4l2_input_status(const __u32 status) {

  fprintf(stderr, "status = 0x%08x = 0", status);

  /* general */
  if ( status & V4L2_IN_ST_NO_POWER ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_POWER");
  }
  if ( status & V4L2_IN_ST_NO_SIGNAL ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_SIGNAL");
  }
  if ( status & V4L2_IN_ST_NO_COLOR ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_COLOR");
  }

  /* Analog video */
  if ( status & V4L2_IN_ST_NO_H_LOCK ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_H_LOCK");
  }
  if ( status & V4L2_IN_ST_COLOR_KILL ) {
    fprintf(stderr, " | V4L2_IN_ST_COLOR_KILL");
  }

  /* Digital video */
  if ( status & V4L2_IN_ST_NO_SYNC ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_SYNC");
  }
  if ( status & V4L2_IN_ST_NO_EQU ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_EQU");
  }
  if ( status & V4L2_IN_ST_NO_CARRIER ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_CARRIER");
  }

  /* VCR and Set-Top Box */
  if ( status & V4L2_IN_ST_MACROVISION ) {
    fprintf(stderr, " | V4L2_IN_ST_MACROVISION");
  }
  if ( status & V4L2_IN_ST_NO_ACCESS ) {
    fprintf(stderr, " | V4L2_IN_ST_NO_ACCESS");
  }
  if ( status & V4L2_IN_ST_VTR ) {
    fprintf(stderr, " | V4L2_IN_ST_VTR");
  }

  fprintf(stderr, "\n");

}

int print_v4l2_standard(int fd, const struct v4l2_input* input, unsigned index) {

  struct v4l2_standard standard;

  memset(&standard, 0x00, sizeof(standard));

  standard.index = index;
    
  if ( -1 == ioctl(fd, VIDIOC_ENUMSTD, &standard) ) {
    return EINVAL;
  } else {

    if ( standard.id & input->std ) { // print only standards of current input
      
      fprintf(stderr, "index = %u\n", standard.index);
      
      print_v4l2_std(standard.id);
      
      fprintf(stderr, "name = %s\n", standard.name);
      
      fprintf(stderr, "frameperiod = %u/%u\n", standard.frameperiod.numerator, standard.frameperiod.denominator);
      
      fprintf(stderr, "framelines = %u\n", standard.framelines);

    }
      
    return ~EINVAL; // something different from EINVAL so (~EINVAL)

  }

}

void enum_v4l2_standard(int fd, const struct v4l2_input* input) {
  unsigned index;

  fprintf(stderr, "\n *** enum_v4l2_standard ***\n");

  for (index =0; ; ++index) {
    if ( EINVAL == print_v4l2_standard(fd, input, index) ) {
      if ( index == 0 ) {
	perror ("VIDIOC_ENUMSTD"); // cannot be 0 unless it is a USB exception
	fflush(stderr);
      }
      break;
    }
  }
}

void print_current_standard(int fd) {

  v4l2_std_id std_id;

  memset(&std_id, 0x00, sizeof(std_id));
    
  if ( -1 == ioctl(fd, VIDIOC_G_STD, &std_id) ) {
    perror ("VIDIOC_G_STD");
    fflush(stderr);
  } else {
    fprintf(stderr, "current standard: ");
    print_v4l2_std(std_id);
  }

}

void print_v4l2_queryctrl (const struct v4l2_queryctrl *queryctrl) {
  fprintf(stderr, "\n *** v4l2_queryctrl ***\n");
  fprintf(stderr, "id = %u\n", queryctrl->id);

  fprintf(stderr, "type = 0x%08x == 0", queryctrl->type);

  if ( queryctrl->type & V4L2_CTRL_TYPE_INTEGER ) {
    fprintf(stderr, " | V4L2_CTRL_TYPE_INTEGER");
  }
  if ( queryctrl->type & V4L2_CTRL_TYPE_BOOLEAN ) {
    fprintf(stderr, " | V4L2_CTRL_TYPE_BOOLEAN");
  }
  if ( queryctrl->type & V4L2_CTRL_TYPE_MENU ) {
    fprintf(stderr, " | V4L2_CTRL_TYPE_MENU");
  }
  if ( queryctrl->type & V4L2_CTRL_TYPE_BUTTON ) {
    fprintf(stderr, " | V4L2_CTRL_TYPE_BUTTON");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "name = %s\n", queryctrl->name);
  fprintf(stderr, "minimum = %d\n", queryctrl->minimum);
  fprintf(stderr, "maximum = %d\n", queryctrl->maximum);
  fprintf(stderr, "step = %d\n", queryctrl->step);
  fprintf(stderr, "default_value = %d\n", queryctrl->default_value);

  fprintf(stderr, "flags = 0x%08x == 0", queryctrl->flags);

  if ( queryctrl->flags & V4L2_CTRL_FLAG_DISABLED ) {
    fprintf(stderr, " | V4L2_CTRL_FLAG_DISABLED");
  }
  if ( queryctrl->flags & V4L2_CTRL_FLAG_GRABBED ) {
    fprintf(stderr, " | V4L2_CTRL_FLAG_GRABBED");
  }
  fprintf(stderr, "\n");

}

void print_v4l2_querymenu (const struct v4l2_querymenu *querymenu) {
  fprintf(stderr, "\n *** v4l2_querymenu ***\n");
  fprintf(stderr, "id = %u\n", querymenu->id);
  fprintf(stderr, "index = %u\n", querymenu->index);
  fprintf(stderr, "name = %s\n", querymenu->name);
}

void enumerate_menu (int fd, const struct v4l2_queryctrl *queryctrl)
{

  unsigned index;
  struct v4l2_querymenu querymenu;

  fprintf(stderr, "\n *** enumerate_menu ***\n");
  
  for (index = queryctrl->minimum; index <= queryctrl->maximum; ++index) {

    memset (&querymenu, 0, sizeof (querymenu));
    querymenu.id = queryctrl->id;
    querymenu.index = index;

    if (0 != ioctl (fd, VIDIOC_QUERYMENU, &querymenu)) {
      perror ("VIDIOC_QUERYMENU");
      fflush(stderr);
      break;
    }

    print_v4l2_querymenu (&querymenu);

  }
}

void enumerate_controls (int fd) {

  unsigned id;
  struct v4l2_queryctrl queryctrl;
  memset (&queryctrl, 0, sizeof (queryctrl));
  
  for (id = V4L2_CID_BASE;
       id < V4L2_CID_LASTP1;
       id++) {

    queryctrl.id = id;

    if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) {
      if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
	continue;
      
      print_v4l2_queryctrl (&queryctrl);

      if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
	enumerate_menu (fd, &queryctrl);
    } else {
      if (errno == EINVAL)
	continue;
      
      perror ("VIDIOC_QUERYCTRL");
      fflush(stderr);
      return;
    }
  }
  
  for (id = V4L2_CID_PRIVATE_BASE; ; id++) {

    queryctrl.id = id;

    if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) {
      if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
	continue;
      
      print_v4l2_queryctrl (&queryctrl);
      
      if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
	enumerate_menu (fd, &queryctrl);
    } else {
      if (errno == EINVAL)
	break;

      perror ("VIDIOC_QUERYCTRL");
      fflush(stderr);
      return;
    }
  }
}

void print_v4l2_buf_type(const enum v4l2_buf_type buf_type) {

  switch(buf_type) {
  case V4L2_BUF_TYPE_VIDEO_CAPTURE:
    fprintf(stderr, "type = V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
    break;
  case V4L2_BUF_TYPE_VIDEO_OUTPUT:
    fprintf(stderr, "type = V4L2_BUF_TYPE_VIDEO_OUTPUT\n");
    break;
  case V4L2_BUF_TYPE_VIDEO_OVERLAY:
    fprintf(stderr, "type = V4L2_BUF_TYPE_VIDEO_OVERLAY\n");
    break;
  case V4L2_BUF_TYPE_VBI_CAPTURE:
    fprintf(stderr, "type = V4L2_BUF_TYPE_VBI_CAPTURE\n");
    break;
  case V4L2_BUF_TYPE_VBI_OUTPUT:
    fprintf(stderr, "type = V4L2_BUF_TYPE_VBI_OUTPUT\n");
    break;
  case V4L2_BUF_TYPE_PRIVATE:
    fprintf(stderr, "type = V4L2_BUF_TYPE_PRIVATE\n");
    break;
  default:
    fprintf(stderr, "type = private:%u\n", buf_type);
    break;
  }
}

void print_pixelformat(const unsigned pixelformat) {
  fprintf(stderr, "pixelformat = ('%c','%c','%c','%c')\n",
	  pixelformat & 0xFF,
	  pixelformat >> 8 & 0xFF,
	  pixelformat >> 16 & 0xFF,
	  pixelformat >> 24 & 0xFF);
}

void print_v4l2_fmtdesc(const struct v4l2_fmtdesc * fmtdesc) {

  fprintf(stderr, "\n *** v4l2_fmtdesc ***\n");
  fprintf(stderr, "index = %u\n", fmtdesc->index);

  print_v4l2_buf_type(fmtdesc->type);

  fprintf(stderr, "flags = 0x%08x == 0", fmtdesc->flags);
  if ( fmtdesc->flags & V4L2_FMT_FLAG_COMPRESSED ) {
    fprintf(stderr, " | V4L2_FMT_FLAG_COMPRESSED");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "description = %s\n", fmtdesc->description);

  print_pixelformat(fmtdesc->pixelformat);

}

void print_v4l2_field(const enum v4l2_field field) {
  switch(field) {
  case V4L2_FIELD_ANY:
    fprintf(stderr, "field = V4L2_FIELD_ANY\n");
    break;
  case V4L2_FIELD_NONE:
    fprintf(stderr, "field = V4L2_FIELD_NONE\n");
    break;
  case V4L2_FIELD_TOP:
    fprintf(stderr, "field = V4L2_FIELD_TOP\n");
    break;
  case V4L2_FIELD_BOTTOM:
    fprintf(stderr, "field = V4L2_FIELD_BOTTOM\n");
    break;
  case V4L2_FIELD_INTERLACED:
    fprintf(stderr, "field = V4L2_FIELD_INTERLACED\n");
    break;
  case V4L2_FIELD_SEQ_TB:
    fprintf(stderr, "field = V4L2_FIELD_SEQ_TB\n");
    break;
  case V4L2_FIELD_SEQ_BT:
    fprintf(stderr, "field = V4L2_FIELD_SEQ_BT\n");
    break;
  case V4L2_FIELD_ALTERNATE:
    fprintf(stderr, "field = V4L2_FIELD_ALTERNATE\n");
    break;
  default:
    fprintf(stderr, "field = unexpected:%u\n", field);
    break;
  }
}

void print_v4l2_colorspace(const enum v4l2_colorspace colorspace) {
  switch(colorspace) {
  case V4L2_COLORSPACE_SMPTE170M:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_SMPTE170M\n");
    break;
  case V4L2_COLORSPACE_SMPTE240M:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_SMPTE240M\n");
    break;
  case V4L2_COLORSPACE_REC709:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_REC709\n");
    break;
  case V4L2_COLORSPACE_BT878:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_BT878\n");
    break;
  case V4L2_COLORSPACE_470_SYSTEM_M:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_470_SYSTEM_M\n");
    break;
  case V4L2_COLORSPACE_470_SYSTEM_BG:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_470_SYSTEM_BG\n");
    break;
  case V4L2_COLORSPACE_JPEG:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_JPEG\n");
    break;
  case V4L2_COLORSPACE_SRGB:
    fprintf(stderr, "colorspace = V4L2_COLORSPACE_SRGB\n");
    break;
  default:
    fprintf(stderr, "colorspace = unexpected:%u\n", colorspace);
    break;
  }
}

void print_v4l2_pix_format(const struct v4l2_pix_format * pix_format) {
  fprintf(stderr, "\n *** v4l2_pix_format ***\n");
  fprintf(stderr, "width = %u\n", pix_format->width);
  fprintf(stderr, "height = %u\n", pix_format->height);
  print_pixelformat(pix_format->pixelformat);
  print_v4l2_field(pix_format->field);
  fprintf(stderr, "bytesperline = %u\n", pix_format->bytesperline);
  fprintf(stderr, "sizeimage = %u\n", pix_format->sizeimage);
  print_v4l2_colorspace(pix_format->colorspace);
  fprintf(stderr, "priv = %u\n", pix_format->priv);
}

void print_v4l2_format(const struct v4l2_format * format) {
  print_v4l2_buf_type(format->type);
  print_v4l2_pix_format(&format->fmt.pix);

}

void print_v4l2_rect(const struct v4l2_rect * rect) {
  fprintf(stderr, "left = %d, top = %d, width = %d, height = %d\n",
	  rect->left, rect->top, rect->width, rect->height);
}

void print_v4l2_cropcap(const struct v4l2_cropcap * cropcap) {
  print_v4l2_buf_type(cropcap->type);
  fprintf(stderr, "bounds"); print_v4l2_rect(&cropcap->bounds);
  fprintf(stderr, "defrect"); print_v4l2_rect(&cropcap->defrect);
  fprintf(stderr, "pixelaspect = %u/%u (y/x)\n", cropcap->pixelaspect.numerator, cropcap->pixelaspect.denominator);
}

int enum_crop_scal_caps(int fd, const enum v4l2_buf_type buf_type) {

  struct v4l2_cropcap cropcap;
  int ret = -1;

  memset(&cropcap, 0x00, sizeof(cropcap));
  cropcap.type = buf_type;

  if ( -1 == ioctl(fd, VIDIOC_CROPCAP, &cropcap) ) {
    if ( cropcap.type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
	 cropcap.type == V4L2_BUF_TYPE_VIDEO_OUTPUT  ||
	 cropcap.type == V4L2_BUF_TYPE_VIDEO_OVERLAY ||
	 errno != EINVAL ) {
      perror("VIDIOC_CROPCAP");
      fflush(stderr);
    }
  } else {
    ret = 0;
    print_v4l2_cropcap(&cropcap);
  }

  return ret;

}


void print_all_crop_scal_caps(int fd) {

  unsigned type;

  fprintf(stderr, "\nCrop_Scal for V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
  enum_crop_scal_caps(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE);

  fprintf(stderr, "\nCrop_Scal for V4L2_BUF_TYPE_VIDEO_OUTPUT\n");
  enum_crop_scal_caps(fd, V4L2_BUF_TYPE_VIDEO_OUTPUT);

  fprintf(stderr, "\nCrop_Scal for V4L2_BUF_TYPE_VIDEO_OVERLAY\n");
  enum_crop_scal_caps(fd, V4L2_BUF_TYPE_VIDEO_OVERLAY);

  fprintf(stderr, "\nCrop_Scal for V4L2_BUF_TYPE_PRIVATE\n");
  if ( 0 == enum_crop_scal_caps(fd, V4L2_BUF_TYPE_PRIVATE) ) {

    for (type = V4L2_BUF_TYPE_PRIVATE+1; ; ++type) {
      
      fprintf(stderr, "\nCrop_Scal for %u\n", type);
      if ( -1 == enum_crop_scal_caps(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE) ) {
	break;
      }

    }

  }  
}

void print_v4l2_captureparam(const struct v4l2_captureparm* capparam) {

  fprintf(stderr, "\n *** v4l2_captureparam ***\n");

  fprintf(stderr, "capability = 0x%08x", capparam->capability);
  if ( capparam->capability & V4L2_CAP_TIMEPERFRAME ) {
    fprintf(stderr, " | V4L2_CAP_TIMEPERFRAME");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "capturemode = 0x%08x", capparam->capturemode);
  if ( capparam->capturemode & V4L2_MODE_HIGHQUALITY ) {
    fprintf(stderr, " | V4L2_MODE_HIGHQUALITY");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "timeperframe = %u/%u\n", capparam->timeperframe.numerator, capparam->timeperframe.denominator);

  fprintf(stderr, "extendedmode = %u\n", capparam->extendedmode);
  fprintf(stderr, "readbuffers = %u\n", capparam->readbuffers);

  fprintf(stderr, "\n");

}

void print_v4l2_outputparam(const struct v4l2_outputparm* capparam) {

  fprintf(stderr, "\n *** v4l2_outputparam ***\n");

  fprintf(stderr, "capability = 0x%08x", capparam->capability);
  if ( capparam->capability & V4L2_CAP_TIMEPERFRAME ) {
    fprintf(stderr, " | V4L2_CAP_TIMEPERFRAME");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "outputmode = 0x%08x", capparam->outputmode);
  if ( capparam->outputmode & V4L2_MODE_HIGHQUALITY ) {
    fprintf(stderr, " | V4L2_MODE_HIGHQUALITY");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "timeperframe = %u/%u\n", capparam->timeperframe.numerator, capparam->timeperframe.denominator);

  fprintf(stderr, "extendedmode = %u\n", capparam->extendedmode);
  fprintf(stderr, "writebuffers = %u\n", capparam->writebuffers);

  fprintf(stderr, "\n");

}



void print_stream_param(int fd, const enum v4l2_buf_type buf_type) {
  struct v4l2_streamparm streamparam;

  memset(&streamparam, 0, sizeof(streamparam));
  streamparam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  if ( -1 == ioctl(fd, VIDIOC_G_PARM, &streamparam) ) {
    perror("VIDIOC_G_PARM");
    fflush(stderr);
  } else {
    if ( V4L2_BUF_TYPE_VIDEO_CAPTURE == streamparam.type ) {
      print_v4l2_captureparam(&streamparam.parm.capture);
    } else if ( V4L2_BUF_TYPE_VIDEO_OUTPUT == streamparam.type ) {
      print_v4l2_outputparam(&streamparam.parm.output);
    } else {
      fprintf(stderr, "invalid buf_type = %d (%d)\n", buf_type, streamparam.type);
    }
  }
  
}

int print_current_format(int fd, const enum v4l2_buf_type buf_type) {

  struct v4l2_format format;
  int ret = -1;

  memset(&format, 0x00, sizeof(format));
  format.type = buf_type;
    
  if ( -1 == ioctl(fd, VIDIOC_G_FMT, &format) ) {
    if ( errno != EINVAL ) {
      perror("VIDIOC_G_FMT");
      fflush(stderr);
    }
  } else {
    fprintf(stderr, "current format: ");
    print_v4l2_format(&format);
    print_stream_param(fd, format.type);
    ret = 0;
  }

  return ret;

}

int enum_image_formats(int fd, const enum v4l2_buf_type type) {

  struct v4l2_fmtdesc fmtdesc;
  unsigned index;
  int ret = -1;

  for (index = 0; ; ++index) {

    memset(&fmtdesc, 0x00, sizeof(fmtdesc));
    fmtdesc.index = index;
    fmtdesc.type = type;
    
    if ( -1 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) ) {
      if ( errno != EINVAL ) {
	perror("VIDIOC_ENUM_FMT");
	fflush(stderr);
      } else {
	if ( index > 0 ) {
	  ret = 0;
	}
      }
      break;
    } else {
      print_v4l2_fmtdesc(&fmtdesc);
    }
  }

  return ret;
}

void print_all_current_format(int fd) {

  unsigned type;

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
  print_current_format(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_OUTPUT\n");
  print_current_format(fd, V4L2_BUF_TYPE_VIDEO_OUTPUT);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_OVERLAY\n");
  print_current_format(fd, V4L2_BUF_TYPE_VIDEO_OVERLAY);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_PRIVATE\n");
  if ( 0 == print_current_format(fd, V4L2_BUF_TYPE_PRIVATE) ) {

    for (type = V4L2_BUF_TYPE_PRIVATE+1; ; ++type) {
      
      fprintf(stderr, "\nformats for %u\n", type);
      if ( -1 == print_current_format(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE) ) {
	break;
      }

    }

  }  
}


void enum_image_allformats(int fd) {

  unsigned type;

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
  enum_image_formats(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_OUTPUT\n");
  enum_image_formats(fd, V4L2_BUF_TYPE_VIDEO_OUTPUT);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_VIDEO_OVERLAY\n");
  enum_image_formats(fd, V4L2_BUF_TYPE_VIDEO_OVERLAY);

  fprintf(stderr, "\nformats for V4L2_BUF_TYPE_PRIVATE\n");
  if ( 0 == enum_image_formats(fd, V4L2_BUF_TYPE_PRIVATE) ) {

    for (type = V4L2_BUF_TYPE_PRIVATE+1; ; ++type) {
      
      fprintf(stderr, "\nformats for %u\n", type);
      if ( -1 == enum_image_formats(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE) ) {
	break;
      }

    }

  }

}

void print_v4l2_input(int fd, const struct v4l2_input* input) {

  fprintf(stderr, "\n *** v4l2_input ***\n");
  fprintf(stderr, "index = %u\n", input->index);
  fprintf(stderr, "name = %s\n", input->name);

  switch(input->type) {
  case V4L2_INPUT_TYPE_TUNER:
    fprintf(stderr, "type = INPUT_TYPE_TUNER\n");
    break;
  case V4L2_INPUT_TYPE_CAMERA:
    fprintf(stderr, "type = INPUT_TYPE_CAMERA\n");
    break;
  default:
    fprintf(stderr, "type = unexpected:%u\n", input->type);
    break;
  }

  fprintf(stderr, "audioset = 0x%08x\n", input->audioset);

  fprintf(stderr, "tuner = %d\n", input->tuner);  

  print_current_standard(fd);

  print_v4l2_std(input->std);

  enum_v4l2_standard(fd, input);

  print_v4l2_input_status(input->status);

  enumerate_controls(fd);

  enum_image_allformats(fd);

  print_all_current_format(fd);

  print_all_crop_scal_caps(fd);

  fprintf(stderr, "\n");  
  
}

void print_v4l2_capability_caps(__u32 caps) {

  fprintf(stderr, "capabilities = 0x%08x == 0", caps);

  if ( caps & V4L2_CAP_VIDEO_CAPTURE ) {
    fprintf(stderr, " | V4L2_CAP_VIDEO_CAPTURE");
  }
  if ( caps & V4L2_CAP_VIDEO_OUTPUT ) {
    fprintf(stderr, " | V4L2_CAP_VIDEO_OUTPUT");
  }
  if ( caps & V4L2_CAP_VIDEO_OVERLAY ) {
    fprintf(stderr, " | V4L2_CAP_VIDEO_OVERLAY");
  }

  if ( caps & V4L2_CAP_VBI_CAPTURE ) {
    fprintf(stderr, " | V4L2_CAP_VBI_CAPTURE");
  }
  if ( caps & V4L2_CAP_VBI_OUTPUT ) {
    fprintf(stderr, " | V4L2_CAP_VBI_OUTPUT");
  }

  if ( caps & V4L2_CAP_RDS_CAPTURE ) {
    fprintf(stderr, " | V4L2_CAP_RDS_CAPTURE");
  }

  if ( caps & V4L2_CAP_TUNER ) {
    fprintf(stderr, " | V4L2_CAP_TUNER");
  }

  if ( caps & V4L2_CAP_AUDIO ) {
    fprintf(stderr, " | V4L2_CAP_AUDIO");
  }

  if ( caps & V4L2_CAP_RADIO ) {
    fprintf(stderr, " | V4L2_CAP_RADIO");
  }

  if ( caps & V4L2_CAP_READWRITE ) {
    fprintf(stderr, " | V4L2_CAP_READWRITE");
  }
  if ( caps & V4L2_CAP_ASYNCIO ) {
    fprintf(stderr, " | V4L2_CAP_ASYNCIO");
  }
  if ( caps & V4L2_CAP_STREAMING ) {
    fprintf(stderr, " | V4L2_CAP_STREAMING");
  }

  fprintf(stderr, "\n");

}

void print_v4l2_capability(const struct v4l2_capability* cap) {


  fprintf(stderr, "\n *** v4l2_capability ***\n");
  fprintf(stderr, "driver = %s\n", cap->driver);
  fprintf(stderr, "card = %s\n", cap->card);
  fprintf(stderr, "bus_info = %s\n", cap->bus_info);
  fprintf(stderr, "version: %u.%u.%u\n",
	  (cap->version >> 16) & 0xFF,
	  (cap->version >> 8) & 0xFF,
	  cap->version & 0xFF);

  print_v4l2_capability_caps(cap->capabilities);

  fprintf(stderr, "\n");
}

void print_v4l2_audio(const struct v4l2_audio* audio) {

  fprintf(stderr, "\n *** v4l2_audio ***\n");

  fprintf(stderr, "index = %u\n", audio->index);

  fprintf(stderr, "name = %s\n", audio->name);

  fprintf(stderr, "capability = 0x%08x = 0", audio->capability);
  if ( audio->capability & V4L2_AUDCAP_STEREO ) {
    fprintf(stderr, " | V4L2_AUDCAP_STEREO");
  }
  if ( audio->capability & V4L2_AUDCAP_AVL ) {
    fprintf(stderr, " | V4L2_AUDCAP_AVL");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "mode = 0x%08x = 0", audio->mode);
  if ( audio->capability & V4L2_AUDMODE_AVL ) {
    fprintf(stderr, " | V4L2_AUDMODE_AVL");
  }
  fprintf(stderr, "\n");

  fprintf(stderr, "\n");

}

void print_v4l2_frequency(int fd, const struct v4l2_tuner * tuner, const struct v4l2_input * input) {

  struct v4l2_frequency frequency;

  memset(&frequency, 0x00, sizeof(frequency));

  if ( tuner->index != input->tuner ) {
    fprintf(stderr, "Error tuner->index != input->tunner in print_v4l2_frequency\n");
    return;
  }

  frequency.tuner = tuner->index;

  if ( -1 == ioctl(fd, VIDIOC_G_FREQUENCY, &frequency) ) {
    perror("VIDIOC_G_TUNER");
    fflush(stderr);
    // _Exit(errno);
  }

  if ( frequency.type != tuner->type ) {
    fprintf(stderr, "Error frequency.type != tunner->type in print_v4l2_frequency\n");
    fprintf(stderr, "\tfrequency.type (%d) == %s\n",
	    frequency.type,
	    frequency.type == V4L2_TUNER_RADIO ? "V4L2_TUNER_RADIO" :
	    frequency.type == V4L2_TUNER_ANALOG_TV ? "V4L2_TUNER_ANALOG_TV" : "INVALID must be 1 or 2");
    fprintf(stderr, "\ttuner->type (%d) == %s\n",
	    tuner->type,
	    tuner->type == V4L2_TUNER_RADIO ? "V4L2_TUNER_RADIO" :
	    tuner->type == V4L2_TUNER_ANALOG_TV ? "V4L2_TUNER_ANALOG_TV" : "INVALID must be 1 or 2");
  }

  if ( tuner->capability | V4L2_TUNER_CAP_LOW ) {
    fprintf(stderr, "frequency = %u x 62.5 Hz == %lu\n", frequency.frequency, (unsigned long)(frequency.frequency) * 625 / 10);
  } else {
    fprintf(stderr, "frequency = %u x 62.5 x 1000 Hz == %lu\n", frequency.frequency, (unsigned long)(frequency.frequency) * 62500);
  }
  
}

void print_v4l2_tuner(const struct v4l2_tuner * tuner, const struct v4l2_input * input) {
  fprintf(stderr, "\n *** v4l2_tuner ***\n");

  fprintf(stderr, "index = %u\n", tuner->index);

  fprintf(stderr, "name = %s\n", tuner->name);

  switch(tuner->type) {
  case V4L2_TUNER_RADIO:
    fprintf(stderr, "type = TUNER_RADIO\n");
    break;
  case V4L2_TUNER_ANALOG_TV:
    fprintf(stderr, "type = TUNER_ANALOG_TV\n");
    break;
  case V4L2_TUNER_DIGITAL_TV:
    fprintf(stderr, "type = TUNER_DIGITAL_TV\n");
    break;
  default:
    fprintf(stderr, "type = Unexpected Value !!!\n");
    break;
  }

  fprintf(stderr, "capability = 0x%08x = 0", tuner->capability);
  if ( tuner->capability & V4L2_TUNER_CAP_LOW ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_LOW");
  }
  if ( tuner->capability & V4L2_TUNER_CAP_NORM  ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_NORM");
  }
  if ( tuner->capability & V4L2_TUNER_CAP_STEREO ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_STEREO");
  }
  if ( tuner->capability & V4L2_TUNER_CAP_LANG2 ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_LANG2");
  }
  if ( tuner->capability & V4L2_TUNER_CAP_SAP ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_SAP");
  }
  if ( tuner->capability & V4L2_TUNER_CAP_LANG1 ) {
    fprintf(stderr, " | V4L2_TUNER_CAP_LANG1");
  }
  fprintf(stderr, "\n");    
    
  if ( tuner->capability & V4L2_TUNER_CAP_LOW ) {
    fprintf(stderr, "rangelow = %u (x 62.5 Hz) = %llu\n", tuner->rangelow,
	    (unsigned long long)(tuner->rangelow) * 625 / 10);
    fprintf(stderr, "rangehigh = %u (x 62.5 Hz) = %llu\n", tuner->rangehigh,
	    (unsigned long long)(tuner->rangehigh) * 625 / 10);
  } else {
    fprintf(stderr, "rangelow = %u (x 62.5 kHz) = %llu\n", tuner->rangelow,
	    (unsigned long long)(tuner->rangelow) * 62500);
    fprintf(stderr, "rangehigh = %u (x 62.5 kHz) = %llu\n", tuner->rangehigh,
	    (unsigned long long)(tuner->rangehigh) * 62500);
  }

  /* The rxsubchans field is valid only if this is the tuner of the current video input,
     or when the structure refers to a radio tuner. */
  fprintf(stderr, "rxsubchans = 0x%08x = 0", tuner->rxsubchans);
  if ( tuner->rxsubchans & V4L2_TUNER_SUB_MONO ) {
    fprintf(stderr, " | V4L2_TUNER_SUB_MONO");
  }
  if ( tuner->rxsubchans & V4L2_TUNER_SUB_STEREO ) {
    fprintf(stderr, " | V4L2_TUNER_SUB_STEREO");
  }
  if ( tuner->rxsubchans & V4L2_TUNER_SUB_LANG2 ) {
    fprintf(stderr, " | V4L2_TUNER_SUB_LANG2");
  }
  if ( tuner->rxsubchans & V4L2_TUNER_SUB_SAP ) {
    fprintf(stderr, " | V4L2_TUNER_SUB_SAP");
  }
  if ( tuner->rxsubchans & V4L2_TUNER_SUB_LANG1 ) {
    fprintf(stderr, " | V4L2_TUNER_SUB_LANG1");
  }
  fprintf(stderr, "\n");    

  fprintf(stderr, "audmode = 0x%08x = 0", tuner->audmode);
  if ( tuner->audmode & V4L2_TUNER_MODE_MONO ) {
    fprintf(stderr, " | V4L2_TUNER_MODE_MONO");
  }
  if ( tuner->audmode & V4L2_TUNER_MODE_STEREO ) {
    fprintf(stderr, " | V4L2_TUNER_MODE_STEREO");
  }
  if ( tuner->audmode & V4L2_TUNER_MODE_LANG2 ) {
    fprintf(stderr, " | V4L2_TUNER_MODE_LANG2");
  }
  if ( tuner->audmode & V4L2_TUNER_MODE_SAP && input->std & V4L2_STD_NTSC_M ) {
    fprintf(stderr, " | V4L2_TUNER_MODE_SAP");
  }
  if ( tuner->audmode & V4L2_TUNER_MODE_LANG1 ) {
    fprintf(stderr, " | V4L2_TUNER_MODE_LANG1");
  }
  fprintf(stderr, "\n");   

  fprintf(stderr, "signal = %u\n", tuner->signal);

  fprintf(stderr, "afc = %d\n", tuner->afc);

  fprintf(stderr, "\n");

}

void enumerate_tunner(int fd, const struct v4l2_input * input) {

  struct v4l2_tuner tuner;

  /* show info tunners */

  if ( !(input->type & V4L2_INPUT_TYPE_TUNER) ) {
    return; /* there is no tuner for this input */
  }

  memset(&tuner, 0, sizeof(tuner));
  tuner.index = input->tuner;

  /*
    if ( -1 == ioctl(fd, VIDIOC_S_TUNER, &tuner) ) {
    perror("VIDIOC_G_TUNER");
    fflush(stderr);
    // _Exit(errno);          
    }
  */
  
  if ( -1 == ioctl(fd, VIDIOC_G_TUNER, &tuner) ) {
    perror("VIDIOC_G_TUNER");
    fflush(stderr);
    // _Exit(errno);          
  } else {
    print_v4l2_tuner(&tuner, input);
    print_v4l2_frequency(fd, &tuner, input);
  }
  
}

void enumerate_audio(int fd, const struct v4l2_capability * capability, int enuminput) {

  int audioindex;
  struct v4l2_audio audio;

  /* show info about audio inputs */
  // it should be only need if device capabilities has V4L2_CAP_AUDIO flag
  // capability->capabilities & V4L2_CAP_AUDIO
  
  for (audioindex = 0; /* capability->capabilities & V4L2_CAP_AUDIO */ ; ++audioindex) {
    
    memset (&audio, 0, sizeof (audio));
    audio.index = audioindex;
    
    if (-1 == ioctl (fd, enuminput ? VIDIOC_S_AUDIO : VIDIOC_S_AUDOUT, &audio)) {
      perror (enuminput ? "VIDIOC_S_AUDIO" : "VIDIOC_S_AUDOUT");
      fflush(stderr);
      break;
      //_Exit (errno);	
    }
    
    if (-1 == ioctl (fd, enuminput ? VIDIOC_G_AUDIO : VIDIOC_G_AUDOUT, &audio)) {
      perror (enuminput ? "VIDIOC_G_AUDIO" : "VIDIOC_G_AUDOUT");
      fflush(stderr);
      break;
      //_Exit (errno);
    } else {
      if ( audio.index != audioindex ) {
	// there is a bug in my driver, it doens return error
	// when my /dev/radio0 is selected and I try to open
	// another audio input
	break;
      }
      print_v4l2_audio(&audio);
    }
    
  }

}


void print_v4l2_output(const struct v4l2_output* output) {

  fprintf(stderr, "\n *** v4l2_output ***\n");
  fprintf(stderr, "index = %u\n", output->index);
  fprintf(stderr, "name = %s\n", output->name);

  switch(output->type) {
  case V4L2_OUTPUT_TYPE_MODULATOR:
    fprintf(stderr, "type = V4L2_OUTPUT_TYPE_MODULATOR\n");
    break;
  case V4L2_OUTPUT_TYPE_ANALOG:
    fprintf(stderr, "type = V4L2_OUTPUT_TYPE_ANALOG\n");
    break;
  case V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY:
    fprintf(stderr, "type = V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY\n");
    break;    
  default:
    fprintf(stderr, "type = unexpected:%u\n", output->type);
    break;
  }

  fprintf(stderr, "audioset = 0x%08x\n", output->audioset);

  fprintf(stderr, "modulator = %d\n", output->modulator);  

  print_v4l2_std(output->std);

  fprintf(stderr, "\n");  
  
}


int main(int argc, char *argv[]) {

  int fd;
  int index;
  struct v4l2_capability capability;
  struct v4l2_input input;
  struct v4l2_output output;

  if ( argc == 2 )
    fd = open(argv[1], O_RDWR);
  else
    fd = open(DEVNAME, O_RDWR);

  if ( -1 == fd ) {
    perror("open");
    fflush(stderr);
    _Exit(errno);
  }

  /* just query the current selected input */
  if ( -1 == ioctl(fd, VIDIOC_G_INPUT, &index) ) {
    perror("VIDIOC_G_INPUT");
    fflush(stderr);
    _Exit(errno);
  }

  if ( -1 == ioctl(fd, VIDIOC_QUERYCAP, &capability) ) {
    perror("VIDIOC_QUERYCAP");
    fflush(stderr);
    _Exit(errno);
  }

  print_v4l2_capability(&capability);

  /* ENUM INPUTS start from input 0 */
  for(index=0; ; ++index ) {

    fprintf(stderr, "\n\n ##### INPUT INDEX = %d #####\n", index);

    if ( -1 == ioctl(fd, VIDIOC_S_INPUT, &index) ) {
      perror("VIDIOC_S_INPUT");
      fflush(stderr);
      break;
      //_Exit(errno);
    }

    /* show info about current input */

    memset(&input, 0, sizeof(input));
    input.index = index;

    if ( -1 == ioctl(fd, VIDIOC_ENUMINPUT, &input) ) {
      perror("VIDIOC_ENUMINPUT");
      fflush(stderr);
      break;
      //_Exit(errno);          
    }

    print_v4l2_input(fd, &input);

    enumerate_audio(fd, &capability, 1);

    enumerate_audio(fd, &capability, 0);

    enumerate_tunner(fd, &input);


  }



  /* ENUM OUTPUTS start from output 0 */
  for(index=0; ; ++index ) {

    fprintf(stderr, "\n\n ##### OUTPUT INDEX = %d #####\n", index);

    if ( -1 == ioctl(fd, VIDIOC_S_OUTPUT, &index) ) {
      perror("VIDIOC_S_OUTPUT");
      fflush(stderr);
      break;
      //_Exit(errno);
    }

    /* show info about current output */

    memset(&output, 0, sizeof(output));
    output.index = index;

    if ( -1 == ioctl(fd, VIDIOC_ENUMOUTPUT, &output) ) {
      perror("VIDIOC_ENUMOUTPUT");
      fflush(stderr);
      break;
      //_Exit(errno);          
    }

    print_v4l2_output(&output);

  }

  if ( -1 == close(fd) ) {
    perror("close");
    fflush(stderr);
    _Exit(errno);      
  }

  return 0;

}
