/*
**  Video 4 Linux 2 input
**
**  This file is part of MPlayer, see http://mplayerhq.hu/ for info.  
**
**  (c) 2003 Martin Olschewski <olschewski@zpr.uni-koeln.de>
**  (c) 2003 Jindrich Makovicka <makovick@kmlinux.fjfi.cvut.cz>
**  
**  File licensed under the GPL, see http://www.fsf.org/ for more info.
**
**  Some ideas are based on works from
**    Alex Beregszaszi <alex@fsn.hu>
**    Gerd Knorr <kraxel@bytesex.org>
**
**  CODE IS UNDER DEVELOPMENT, NO FEATURE REQUESTS PLEASE!
*/

/*

known issues:
- norm setting isn't consistent with tvi_v4l
- the same for volume/bass/treble/balance

*/


#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/types.h>
#include <linux/videodev2.h>

#include <sys/ipc.h>
#include <sys/shm.h>

#include "img_format.h"
#include "tv.h"

#include "tvi_v4l2.h"
#include "errormsg.h"

typedef struct smem_st {
  char mplayerid[2];
  int type;
  int size;
  int demuxer_type;
  int format;
  int width;
  int height;
  char data[0];
} smem_t;


struct map {
    struct v4l2_buffer buf;
    void   *addr;
    size_t len;
};

typedef struct {
    int 			video_fd;
    char*			video_dev;
    char*			name;
    struct v4l2_capability	capability;
    struct v4l2_input           input;
    struct v4l2_format		format;
    struct v4l2_standard	standard;
    struct v4l2_tuner		tuner;
    int				streamon;
    struct map			*map;
    int				mapcount;
    int                         video_buffer_size_max;
    volatile int                video_buffer_size_current;
    unsigned char		**video_ringbuffer;
    long long                   *video_timebuffer;
    volatile int		video_head;
    volatile int		video_tail;
    volatile int		video_cnt;
    pthread_t			video_grabber_thread;
    pthread_mutex_t             video_buffer_mutex;
    int				immediate_mode;
    int                         mp_format;
    char*			buffer;
    int				bufferlen;

    int 			smemid;

    int 			shmid;
    int 			smemsize;
    int 			smemmaxsize;
    smem_t*			shmptr;
    struct shmid_ds 		smem_ds;
    int				smemformat;
    int				fps;
    } priv_t;

char *vo_format_name(int format)
{
    switch(format)
    {
	case IMGFMT_RGB1: return("RGB 1-bit");
	case IMGFMT_RGB4: return("RGB 4-bit");
	case IMGFMT_RG4B: return("RGB 4-bit per byte");
	case IMGFMT_RGB8: return("RGB 8-bit");
	case IMGFMT_RGB15: return("RGB 15-bit");
	case IMGFMT_RGB16: return("RGB 16-bit");
	case IMGFMT_RGB24: return("RGB 24-bit");
//	case IMGFMT_RGB32: return("RGB 32-bit");
	case IMGFMT_BGR1: return("BGR 1-bit");
	case IMGFMT_BGR4: return("BGR 4-bit");
	case IMGFMT_BG4B: return("BGR 4-bit per byte");
	case IMGFMT_BGR8: return("BGR 8-bit");
	case IMGFMT_BGR15: return("BGR 15-bit");
	case IMGFMT_BGR16: return("BGR 16-bit");
	case IMGFMT_BGR24: return("BGR 24-bit");
//	case IMGFMT_BGR32: return("BGR 32-bit");
	case IMGFMT_ABGR: return("ABGR");
	case IMGFMT_BGRA: return("BGRA");
	case IMGFMT_ARGB: return("ARGB");
	case IMGFMT_RGBA: return("RGBA");
	case IMGFMT_YVU9: return("Planar YVU9");
	case IMGFMT_IF09: return("Planar IF09");
	case IMGFMT_YV12: return("Planar YV12");
	case IMGFMT_I420: return("Planar I420");
	case IMGFMT_IYUV: return("Planar IYUV");
	case IMGFMT_CLPL: return("Planar CLPL");
	case IMGFMT_Y800: return("Planar Y800");
	case IMGFMT_Y8: return("Planar Y8");
	case IMGFMT_444P: return("Planar 444P");
	case IMGFMT_422P: return("Planar 422P");
	case IMGFMT_411P: return("Planar 411P");
	case IMGFMT_NV12: return("Planar NV12");
	case IMGFMT_NV21: return("Planar NV21");
        case IMGFMT_HM12: return("Planar NV12 Macroblock");
	case IMGFMT_IUYV: return("Packed IUYV");
	case IMGFMT_IY41: return("Packed IY41");
	case IMGFMT_IYU1: return("Packed IYU1");
	case IMGFMT_IYU2: return("Packed IYU2");
	case IMGFMT_UYVY: return("Packed UYVY");
	case IMGFMT_UYNV: return("Packed UYNV");
	case IMGFMT_cyuv: return("Packed CYUV");
	case IMGFMT_Y422: return("Packed Y422");
	case IMGFMT_YUY2: return("Packed YUY2");
	case IMGFMT_YUNV: return("Packed YUNV");
	case IMGFMT_YVYU: return("Packed YVYU");
	case IMGFMT_Y41P: return("Packed Y41P");
	case IMGFMT_Y211: return("Packed Y211");
	case IMGFMT_Y41T: return("Packed Y41T");
	case IMGFMT_Y42T: return("Packed Y42T");
	case IMGFMT_V422: return("Packed V422");
	case IMGFMT_V655: return("Packed V655");
	case IMGFMT_CLJR: return("Packed CLJR");
	case IMGFMT_YUVP: return("Packed YUVP");
	case IMGFMT_UYVP: return("Packed UYVP");
	case IMGFMT_MPEGPES: return("Mpeg PES");
	case IMGFMT_ZRMJPEGNI: return("Zoran MJPEG non-interlaced");
	case IMGFMT_ZRMJPEGIT: return("Zoran MJPEG top field first");
	case IMGFMT_ZRMJPEGIB: return("Zoran MJPEG bottom field first");
	case IMGFMT_XVMC_MOCO_MPEG2: return("MPEG1/2 Motion Compensation");
	case IMGFMT_XVMC_IDCT_MPEG2: return("MPEG1/2 Motion Compensation and IDCT");
    }
    return("Unknown");
}

static int fcc_mp2vl(int fcc)
{
    switch (fcc) {
    case IMGFMT_RGB8:	return V4L2_PIX_FMT_RGB332;
    case IMGFMT_BGR15:	return V4L2_PIX_FMT_RGB555;
    case IMGFMT_BGR16:	return V4L2_PIX_FMT_RGB565;
    case IMGFMT_RGB24:	return V4L2_PIX_FMT_RGB24;
    case IMGFMT_RGB32:	return V4L2_PIX_FMT_RGB32;
    case IMGFMT_BGR24:	return V4L2_PIX_FMT_BGR24;
    case IMGFMT_BGR32:	return V4L2_PIX_FMT_BGR32;
    case IMGFMT_Y800:	return V4L2_PIX_FMT_GREY;
    case IMGFMT_IF09:	return V4L2_PIX_FMT_YUV410;
    case IMGFMT_I420:	return V4L2_PIX_FMT_YUV420;
    case IMGFMT_YUY2:	return V4L2_PIX_FMT_YUYV;
    case IMGFMT_YV12:	return V4L2_PIX_FMT_YVU420;
    case IMGFMT_UYVY:   return V4L2_PIX_FMT_UYVY;
    }
    return fcc;
}

static int fcc_vl2mp(int fcc)
{
    switch (fcc) {
    case V4L2_PIX_FMT_RGB332:	return IMGFMT_RGB8;
    case V4L2_PIX_FMT_RGB555:	return IMGFMT_BGR15;
    case V4L2_PIX_FMT_RGB565:	return IMGFMT_BGR16;
    case V4L2_PIX_FMT_RGB24:	return IMGFMT_RGB24;
    case V4L2_PIX_FMT_RGB32:	return IMGFMT_RGB32;
    case V4L2_PIX_FMT_BGR24:	return IMGFMT_BGR24;
    case V4L2_PIX_FMT_BGR32:	return IMGFMT_BGR32;
    case V4L2_PIX_FMT_GREY:		return IMGFMT_Y800;
    case V4L2_PIX_FMT_YUV410:	return IMGFMT_IF09;
    case V4L2_PIX_FMT_YUV420:	return IMGFMT_I420;
    case V4L2_PIX_FMT_YVU420:	return IMGFMT_YV12;
    case V4L2_PIX_FMT_YUYV:		return IMGFMT_YUY2;
    case V4L2_PIX_FMT_UYVY:     return IMGFMT_UYVY;
    }
    return fcc;
}


static int getfmt(priv_t *priv)
{
    int i;

    priv->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if ((i = ioctl(priv->video_fd, VIDIOC_G_FMT, &priv->format)) < 0) {
	logprint(LEVEL_ERR,"%s: ioctl get format failed: %s\n", priv->name,
	       strerror(errno));
    }
    return i;
}

static char *pixfmt2name(int pixfmt)
{
    static char unknown[24];

    switch (pixfmt) {
    case V4L2_PIX_FMT_RGB332:	return "RGB332";
    case V4L2_PIX_FMT_RGB555:	return "RGB555";
    case V4L2_PIX_FMT_RGB565:	return "RGB565";
    case V4L2_PIX_FMT_RGB555X:	return "RGB555X";
    case V4L2_PIX_FMT_RGB565X:	return "RGB565X";
    case V4L2_PIX_FMT_BGR24:	return "BGR24";
    case V4L2_PIX_FMT_RGB24:	return "RGB24";
    case V4L2_PIX_FMT_BGR32:	return "BGR32";
    case V4L2_PIX_FMT_RGB32:	return "RGB32";
    case V4L2_PIX_FMT_GREY:		return "GREY";
    case V4L2_PIX_FMT_YVU410:	return "YVU410";
    case V4L2_PIX_FMT_YVU420:	return "YVU420";
    case V4L2_PIX_FMT_YUYV:		return "YUYV";
    case V4L2_PIX_FMT_UYVY:		return "UYVY";
/*	case V4L2_PIX_FMT_YVU422P:	return "YVU422P"; */
/*	case V4L2_PIX_FMT_YVU411P:	return "YVU411P"; */
    case V4L2_PIX_FMT_YUV422P:	return "YUV422P";
    case V4L2_PIX_FMT_YUV411P:	return "YUV411P";
    case V4L2_PIX_FMT_Y41P:		return "Y41P";
    case V4L2_PIX_FMT_NV12:		return "NV12";
    case V4L2_PIX_FMT_NV21:		return "NV21";
    case V4L2_PIX_FMT_YUV410:	return "YUV410";
    case V4L2_PIX_FMT_YUV420:	return "YUV420";
    case V4L2_PIX_FMT_YYUV:		return "YYUV";
    case V4L2_PIX_FMT_HI240:	return "HI240";
    case V4L2_PIX_FMT_WNVA:		return "WNVA";
    }
    sprintf(unknown, "unknown (0x%x)", pixfmt);
    return unknown;
}


static int pixfmt2depth(int pixfmt)
{
    switch (pixfmt) {
    case V4L2_PIX_FMT_RGB332:
	return 8;
    case V4L2_PIX_FMT_RGB555:
    case V4L2_PIX_FMT_RGB565:
    case V4L2_PIX_FMT_RGB555X:
    case V4L2_PIX_FMT_RGB565X:
	return 16;
    case V4L2_PIX_FMT_BGR24:
    case V4L2_PIX_FMT_RGB24:
	return 24;
    case V4L2_PIX_FMT_BGR32:
    case V4L2_PIX_FMT_RGB32:
	return 32;
    case V4L2_PIX_FMT_GREY:
	return 8;
    case V4L2_PIX_FMT_YVU410:
	return 9;
    case V4L2_PIX_FMT_YVU420:
	return 12;
    case V4L2_PIX_FMT_YUYV:
    case V4L2_PIX_FMT_UYVY:
    case V4L2_PIX_FMT_YUV422P:
    case V4L2_PIX_FMT_YUV411P:
	return 16;
    case V4L2_PIX_FMT_Y41P:
    case V4L2_PIX_FMT_NV12:
    case V4L2_PIX_FMT_NV21:
	return 12;
    case V4L2_PIX_FMT_YUV410:
	return 9;
    case V4L2_PIX_FMT_YUV420:
	return 12;
    case V4L2_PIX_FMT_YYUV:
	return 16;
    case V4L2_PIX_FMT_HI240:
	return 8;

    }
    return 0;
}

static inline void copy_frame(priv_t *priv, unsigned char *dest, unsigned char *source)
{
    int w = priv->format.fmt.pix.width;
    int h = priv->format.fmt.pix.height;
    int d = pixfmt2depth(priv->format.fmt.pix.pixelformat);
    int bytesperline = w*d/8;
    memcpy(dest, source, bytesperline * h);
}



static int video_grab(priv_t *priv)
{
int framesize = priv->format.fmt.pix.height*priv->format.fmt.pix.width*
	pixfmt2depth(priv->format.fmt.pix.pixelformat)/8;
fd_set rdset;
struct timeval timeout;
struct v4l2_buffer buf;
int i, ret;

    if (!priv->streamon) {
	logprint(LEVEL_INFO, "%s: going to capture\n", priv->name);
	if (ioctl(priv->video_fd, VIDIOC_STREAMON, &(priv->format.type)) < 0) {
	    logprint(LEVEL_ERR, "%s: ioctl streamon failed: %s\n", priv->name,
	       strerror(errno));
	    return 0;
	}
    }
    priv->streamon = 1;

FD_ZERO (&rdset);
FD_SET (priv->video_fd, &rdset);

timeout.tv_sec = 1;
timeout.tv_usec = 0;

i = select(priv->video_fd + 1, &rdset, NULL, NULL, &timeout);
if (i < 0) {
    logprint(LEVEL_ERR, "%s: select failed: %s\n", priv->name,
	    strerror(errno));
    return 0;
    }
    else if (i == 0) {
	logprint(LEVEL_ERR, "%s: select timeout\n",priv->name);
	return 0;
    }
    else if (!FD_ISSET(priv->video_fd, &rdset)) {
	return 0;
    }

memset(&buf,0,sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(priv->video_fd, VIDIOC_DQBUF, &buf);
if (ret < 0) {
    logprint(LEVEL_ERR, "%s: ioctl dequeue buffer failed: %s, idx = %d\n", priv->name,
		   strerror(errno), buf.index);
    for (i = 0; i < priv->mapcount; i++) {
	memset(&buf,0,sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.index = i;
	ret = ioctl(priv->video_fd, VIDIOC_QUERYBUF, &buf);
	if (ret < 0) {
	    logprint(LEVEL_ERR, "%s: ioctl query buffer failed: %s, idx = %d\n", priv->name,
			   strerror(errno), buf.index);
	    return 0;
    	    }
	if ((buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE)) == V4L2_BUF_FLAG_MAPPED) {
	    if (ioctl(priv->video_fd, VIDIOC_QBUF, &(priv->map[i].buf)) < 0) {
		logprint(LEVEL_ERR, "%s: ioctl queue buffer failed: %s\n", priv->name,
		       strerror(errno));
		return 0;
		}		
	    }
	}
    return 1;
    }


	if (priv->video_buffer_size_current < priv->video_buffer_size_max) {
	    if (priv->video_cnt == priv->video_buffer_size_current) {
		unsigned char *newbuf = (unsigned char*)malloc(framesize);
		if (newbuf) {
		    memmove(priv->video_ringbuffer+priv->video_tail+1, priv->video_ringbuffer+priv->video_tail,
			    (priv->video_buffer_size_current-priv->video_tail)*sizeof(unsigned char *));
		    memmove(priv->video_timebuffer+priv->video_tail+1, priv->video_timebuffer+priv->video_tail,
			    (priv->video_buffer_size_current-priv->video_tail)*sizeof(long long));
		    priv->video_ringbuffer[priv->video_tail] = newbuf;
		    if ((priv->video_head >= priv->video_tail) && (priv->video_cnt > 0)) priv->video_head++;
		    priv->video_buffer_size_current++;
		}
	    }
	}


	    priv->video_cnt++;



	    copy_frame(priv, priv->video_ringbuffer[priv->video_tail], priv->map[buf.index].addr);
	    priv->video_tail = (priv->video_tail+1)%priv->video_buffer_size_current;
	    priv->video_cnt++;


if (ioctl(priv->video_fd, VIDIOC_QBUF, &buf) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl queue buffer failed: %s\n", priv->name,
	   strerror(errno));
    return 0;
    }


return 1;
}

static int get_capture_buffer_size(priv_t *priv)
{
    int w = priv->format.fmt.pix.width;
    int h = priv->format.fmt.pix.height;
    int d = pixfmt2depth(priv->format.fmt.pix.pixelformat);
    int bytesperline = w*d/8;
    return bytesperline*h;
}

int getformat(priv_t *priv)
{
int ret;
ret= fcc_vl2mp(priv->format.fmt.pix.pixelformat);
logprint(LEVEL_ERR, "%s: get format: %s\n", priv->name,
	       pixfmt2name(priv->format.fmt.pix.pixelformat));
return ret;
}

static int video_read(priv_t *priv)
{
if (priv->video_cnt == 0) return 0;
memcpy(priv->buffer, priv->video_ringbuffer[priv->video_head], priv->bufferlen);
priv->video_cnt--;
priv->video_head = (priv->video_head+1)%priv->video_buffer_size_current;
return 1;
}

static int video_write(priv_t *priv)
{
if (priv->smemid)
  {
  int smemopened=0;
  if (priv->shmid==0)
    {
    int msize;
    msize=sizeof(smem_t)+priv->bufferlen;
    priv->smemformat=getformat(priv);
    priv->smemmaxsize=priv->bufferlen;
    if((priv->shmid = shmget(priv->smemid, msize, IPC_CREAT|0666)) < 0) 
      logprint(LEVEL_ERR,"%s: Can't open shared memory! (%d) %s\n",priv->name, priv->smemid,strerror(errno));
      else smemopened = 1;
    }
  if (priv->shmid>0 && !priv->shmptr)
    {
    if((priv->shmptr=shmat(priv->shmid,0,0))==(smem_t*)-1)
      {
      logprint(LEVEL_ERR,"%s: Can't mount shared memory! %s\n", priv->name,strerror(errno));
      shmctl(priv->shmid, IPC_RMID, 0);
      priv->shmid=-1;
      smemopened=0;
      }
    }
  if (priv->shmid>0)
    {
    priv->smem_ds.shm_nattch=0;    
    if (shmctl(priv->shmid,IPC_STAT,&(priv->smem_ds))==0)
      {
//      printf("attach: %d \n",smem_ds.shm_nattch);
      }
    }
  if (priv->shmid>0 && priv->shmptr && (priv->smem_ds.shm_nattch>1 || smemopened))
    {
    smem_t *ptr;
    ptr=priv->shmptr;
    if (priv->bufferlen<priv->smemmaxsize) priv->smemsize=priv->bufferlen; else priv->smemsize=priv->smemmaxsize;
    ptr->mplayerid[0]='M';
    ptr->mplayerid[1]='P';
    ptr->type=0;
    ptr->size=priv->smemsize;
    ptr->demuxer_type=26;	// DEMUXER_TYPE_TV
    ptr->format=priv->smemformat;
    ptr->width=priv->format.fmt.pix.width;
    ptr->height=priv->format.fmt.pix.height;
    memcpy(ptr->data,priv->buffer, priv->smemsize);
    if (smemopened) logprint(LEVEL_INFO,"%s: Sahared memory open(%d): w=%i:h=%i:f=%x:l=%li\n",priv->name, priv->smemid,
	ptr->width, ptr->height, ptr->format, priv->bufferlen);
    }
  smemopened=0;
  }
return 1;
}



void* video_init(char *name, char *videodev, int smemid)
{
int i;
priv_t *priv;

if (NULL==(priv=malloc(sizeof(priv_t)))) return NULL;
memset(priv,0,sizeof(priv_t));

priv->name=strdup(name);
priv->video_dev=strdup(videodev);
priv->smemid=smemid;
priv->video_fd = open(priv->video_dev, O_RDWR);
if (priv->video_fd < 0) {
    logprint(LEVEL_ERR,"%s: unable to open '%s': %s\n", priv->name,
       priv->video_dev, strerror(errno));
    video_uninit((void**)&priv);
    return NULL;
    }
if (ioctl(priv->video_fd, VIDIOC_QUERYCAP, &priv->capability) < 0) {
    logprint(LEVEL_ERR,"%s: ioctl query capabilities failed: %s\n", priv->name,
	       strerror(errno));
    video_uninit((void**)&priv);
    return NULL;
    }
if (!(priv->capability.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
    logprint(LEVEL_ERR, "%s: Device %s is not a video capture device.\n", priv->name,
	       priv->video_dev);
    video_uninit((void**)&priv);
    return NULL;
    }
if (getfmt(priv) < 0) {
    video_uninit((void**)&priv);
    return NULL;
    } 
logprint(LEVEL_INFO, "%s: Shared memory id: %i\n", priv->name, priv->smemid);
logprint(LEVEL_INFO, "%s: Selected device: %s\n", priv->name, priv->capability.card);
if (priv->capability.capabilities & V4L2_CAP_TUNER) {
    logprint(LEVEL_VERBOSE, "%s: Tuner cap:%s%s%s\n", priv->name,
	(priv->tuner.capability & V4L2_TUNER_CAP_STEREO) ? " STEREO" : "",
	(priv->tuner.capability & V4L2_TUNER_CAP_LANG1)  ? " LANG1"  : "",
	(priv->tuner.capability & V4L2_TUNER_CAP_LANG2)  ? " LANG2"  : "");
    logprint(LEVEL_VERBOSE, "%s: Tuner rxs:%s%s%s%s\n", priv->name,
	(priv->tuner.rxsubchans & V4L2_TUNER_SUB_MONO)   ? " MONO"   : "",
	(priv->tuner.rxsubchans & V4L2_TUNER_SUB_STEREO) ? " STEREO" : "",
	(priv->tuner.rxsubchans & V4L2_TUNER_SUB_LANG1)  ? " LANG1"  : "",
	(priv->tuner.rxsubchans & V4L2_TUNER_SUB_LANG2)  ? " LANG2"  : "");
    }
logprint(LEVEL_VERBOSE, "%s: Capabilites:%s%s%s%s%s%s%s%s%s%s%s\n", priv->name,
    priv->capability.capabilities & V4L2_CAP_VIDEO_CAPTURE?
   "  video capture": "",
   priv->capability.capabilities & V4L2_CAP_VIDEO_OUTPUT?
   "  video output": "",
   priv->capability.capabilities & V4L2_CAP_VIDEO_OVERLAY?
   "  video overlay": "",
   priv->capability.capabilities & V4L2_CAP_VBI_CAPTURE?
   "  VBI capture device": "",
   priv->capability.capabilities & V4L2_CAP_VBI_OUTPUT?
   "  VBI output": "",
   priv->capability.capabilities & V4L2_CAP_RDS_CAPTURE?
   "  RDS data capture": "",
   priv->capability.capabilities & V4L2_CAP_TUNER?
   "  tuner": "",
   priv->capability.capabilities & V4L2_CAP_AUDIO?
   "  audio": "",
   priv->capability.capabilities & V4L2_CAP_READWRITE?
   "  read/write": "",
   priv->capability.capabilities & V4L2_CAP_ASYNCIO?
   "  async i/o": "",
   priv->capability.capabilities & V4L2_CAP_STREAMING?
   "  streaming": "");
logprint(LEVEL_VERBOSE, "%s: supported norms:", priv->name);
for (i = 0;; i++) {
    struct v4l2_standard standard;
    memset(&standard, 0, sizeof(standard));
    standard.index = i;
    if (-1 == ioctl(priv->video_fd, VIDIOC_ENUMSTD, &standard))
	    break;
    logprint(LEVEL_VERBOSE, " %d = %s;", i, standard.name);
    }
logprint(LEVEL_VERBOSE, "\n inputs:");
for (i = 0; 1; i++) {
    struct v4l2_input input;

    input.index = i;
    if (ioctl(priv->video_fd, VIDIOC_ENUMINPUT, &input) < 0) {
	    break;
	}
    logprint(LEVEL_VERBOSE, " %d = %s;", i, input.name);
    }
if (ioctl(priv->video_fd, VIDIOC_G_INPUT, &i) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl get input failed: %s\n", priv->name,
	       strerror(errno));
    }
logprint(LEVEL_INFO, "\n%s: Current input: %d\n", priv->name, i);
for (i = 0; ; i++) {
    struct v4l2_fmtdesc fmtdesc;

    fmtdesc.index = i;
    fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(priv->video_fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) {
	    break;
    }
logprint(LEVEL_VERBOSE, "%s: Format %-6s (%2d bits, %s): %s\n", priv->name,
	       pixfmt2name(fmtdesc.pixelformat), pixfmt2depth(fmtdesc.pixelformat),
	       fmtdesc.description, vo_format_name(fcc_vl2mp(fmtdesc.pixelformat)));
    }
logprint(LEVEL_VERBOSE, "%s: Current format: %s\n", priv->name,
	   pixfmt2name(priv->format.fmt.pix.pixelformat));

if (getfmt(priv) < 0) 
   {
   video_uninit((void**)&priv);
   return NULL;
   }
priv->format.fmt.pix.width  = 640;
priv->format.fmt.pix.height = 480;
if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl set format failed: %s\n", priv->name,
           strerror(errno));
    video_uninit((void**)&priv);
    return NULL;
    }
return (void*)priv;
}

int video_start(void *privptr)
{
if(!privptr) return 0;
priv_t* priv = privptr;
struct v4l2_requestbuffers request;
int i;

priv->video_buffer_size_max = 2;

int bytesperline = priv->format.fmt.pix.width*pixfmt2depth(priv->format.fmt.pix.pixelformat)/8;
	
logprint(LEVEL_INFO, "%s: Using a ring buffer for maximum %d frames, %d MB total size.\n", priv->name,
	       priv->video_buffer_size_max,
	       priv->video_buffer_size_max*priv->format.fmt.pix.height*bytesperline/(1024*1024));


priv->video_ringbuffer = (unsigned char**)malloc(priv->video_buffer_size_max*sizeof(unsigned char*));
if (!priv->video_ringbuffer) {
    logprint(LEVEL_ERR, "%s: cannot allocate video buffer: %s\n", priv->name, strerror(errno));
    return 0;
    }
for (i = 0; i < priv->video_buffer_size_max; i++)
	priv->video_ringbuffer[i] = NULL;
priv->video_timebuffer = (long long*)malloc(sizeof(long long) * priv->video_buffer_size_max);
if (!priv->video_timebuffer) {
    logprint(LEVEL_ERR, "%s: cannot allocate time buffer: %s\n", priv->name,strerror(errno));
    return 0;
    }
priv->video_head = 0;
priv->video_tail = 0;
priv->video_cnt = 0;
request.count = 2;

request.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
request.memory = V4L2_MEMORY_MMAP;
if (ioctl(priv->video_fd, VIDIOC_REQBUFS, &request) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl request buffers failed: %s\n", priv->name,
	       strerror(errno));
	return 0;
    }

    /* query buffers */
if (!(priv->map = malloc(sizeof(struct map) * request.count))) {
    logprint(LEVEL_ERR, "%s: malloc capture buffers failed: %s\n", priv->name,
	       strerror(errno));
	return 0;
    }
for (i = 0; i < request.count; i++) {
	memset(&priv->map[i].buf,0,sizeof(priv->map[i].buf));
	priv->map[i].buf.index = i;
	priv->map[i].buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	priv->map[i].buf.memory  = V4L2_MEMORY_MMAP;
	if (ioctl(priv->video_fd, VIDIOC_QUERYBUF, &(priv->map[i].buf)) < 0) {
	    logprint(LEVEL_ERR, "%s: ioctl query buffer failed: %s\n", priv->name,
		   strerror(errno));
	    free(priv->map);
	    priv->map = NULL;
	    return 0;
	}
	priv->map[i].addr = mmap (0, priv->map[i].buf.length, PROT_READ |
				  PROT_WRITE, MAP_SHARED, priv->video_fd, priv->map[i].buf.m.offset);
	if (priv->map[i].addr == MAP_FAILED) {
	    logprint(LEVEL_ERR, "%s: mmap capture buffer failed: %s\n", priv->name,
		   strerror(errno));
	    priv->map[i].len = 0;
	    return 0;
	}
	priv->map[i].len = priv->map[i].buf.length;
	/* count up to make sure this is correct everytime */
	priv->mapcount++;

	if (ioctl(priv->video_fd, VIDIOC_QBUF, &(priv->map[i].buf)) < 0) {
	    logprint(LEVEL_ERR, "%s: ioctl queue buffer failed: %s\n", priv->name,
		   strerror(errno));
	    return 0;
	}
    }

return 1;
}

int video_fps(void *privptr, int fps)
{
if(!privptr) return 0;
priv_t *priv = privptr;
priv->fps=fps;
return 1;
}

int video_size(void *privptr, int width, int height)
{
if(!privptr) return 0;
priv_t *priv = privptr;

priv->format.fmt.pix.width = width;
logprint(LEVEL_VERBOSE, "%s: set width: %d\n", priv->name,
	       width);
if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl set width failed: %s\n", priv->name,
		   strerror(errno));
	    return 0;
    }
priv->format.fmt.pix.height = height;
priv->format.fmt.pix.field = V4L2_FIELD_ANY;
logprint(LEVEL_ERR, "%s: set height: %d\n",  priv->name,
	       height);
if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl set height failed: %s\n", priv->name,
		   strerror(errno));
    return 0;
    }
return 1;
}

int video_format(void *privptr, int outfmt)
{
if(!privptr) return 0;
priv_t *priv = privptr;

priv->format.fmt.pix.pixelformat = fcc_mp2vl(outfmt);
priv->format.fmt.pix.field = V4L2_FIELD_ANY;
	    
priv->mp_format = outfmt;
logprint(LEVEL_ERR, "%s: set format: %s\n", priv->name,
	       pixfmt2name(priv->format.fmt.pix.pixelformat));
if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl set format failed: %s\n", priv->name,
		   strerror(errno));
	    return 0;
	}
priv->mp_format = fcc_vl2mp(priv->format.fmt.pix.pixelformat);
return 1;
}

int video_input(void *privptr, int input, float freq)
{
if(!privptr) return 0;
priv_t *priv = privptr;

struct v4l2_frequency frequency;

priv->input.index = input;
if (ioctl(priv->video_fd, VIDIOC_ENUMINPUT, &priv->input) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl enum input failed: %s\n", priv->name,
		   strerror(errno));
	    return 0;
    }
if (ioctl(priv->video_fd, VIDIOC_S_INPUT, &input) < 0) {
    logprint(LEVEL_ERR, "%s: ioctl set input failed: %s\n", priv->name,
		   strerror(errno));
	    return 0;
    }
if (freq>0.0) {
    frequency.tuner = 0;
    frequency.type  = V4L2_TUNER_ANALOG_TV;
    frequency.frequency = freq*16;
    if (ioctl(priv->video_fd, VIDIOC_S_FREQUENCY, &frequency) < 0) {
	logprint(LEVEL_ERR,"%s: ioctl set frequency failed: %s\n", priv->name,
		   strerror(errno));
        return 0;
	}
    unsigned int cfreq;

    frequency.tuner = 0;
    frequency.type  = V4L2_TUNER_ANALOG_TV;
    if (ioctl(priv->video_fd, VIDIOC_G_FREQUENCY, &frequency) < 0) {
	logprint(LEVEL_ERR,"%s: ioctl get frequency failed: %s\n", priv->name,
		  strerror(errno));
        return 0;
	}
    cfreq = frequency.frequency;
    logprint(LEVEL_ERR, "%s: Selected frequency: %u (%.3f)\n", priv->name,
	    cfreq, (float)cfreq/16);
    }
return 1;
}

int video_process(void *privptr)
{
int waittime;
if(!privptr) return 0;
priv_t *priv=privptr;
if(!priv) return 0;

if (!priv->buffer)
    {
    priv->bufferlen=get_capture_buffer_size(priv);
    if (NULL==(priv->buffer=malloc(priv->bufferlen)))
	{
	logprint(LEVEL_ERR,"%s: malloc grabbed buffers failed %s\n", priv->name,
	    strerror(errno));
	return 0;
	}
    memset(priv->buffer,0,priv->bufferlen);
    }
video_grab(priv);
video_read(priv);
video_write(priv);
if (priv->fps>0) {
  waittime=1000000/priv->fps;
  usleep(waittime);
  }
return 1;
}

void video_uninit(void** privptr)
{
if(!privptr) return;
if(!*privptr) return;
priv_t *priv = (priv_t*)*privptr;
priv->shmptr=NULL;
if (priv->shmid) shmctl(priv->shmid, IPC_RMID, 0);
priv->shmid=0;
if(priv->buffer) free(priv->buffer);
if(priv->shmptr) shmdt(priv->shmptr);
if(priv->video_fd) close(priv->video_fd);
if(priv->name) free(priv->name);
if(priv->video_dev) free(priv->video_dev);
free(priv);
*privptr=NULL;
return;
}
