blob: 98345e8d7237289dc740225f5e7f1cec7a5079b0 [file] [log] [blame]
/*
Copyright (C) 2006 Mauro Carvalho Chehab <mchehab@kernel.org>
The libv4l2util Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The libv4l2util Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include "v4l2_driver.h"
/****************************************************************************
Auxiliary routines
****************************************************************************/
static int xioctl (int fd, unsigned long int request, void *arg)
{
int r;
do r = ioctl (fd, request, arg);
while (-1 == r && EINTR == errno);
return r;
}
static void free_list(struct drv_list **list_ptr)
{
struct drv_list *prev,*cur;
if (list_ptr==NULL)
return;
prev=*list_ptr;
if (prev==NULL)
return;
do {
cur=prev->next;
if (prev->curr)
free (prev->curr); // Free data
free (prev); // Free list
prev=cur;
} while (prev);
*list_ptr=NULL;
}
/****************************************************************************
Auxiliary Arrays to aid debug messages
****************************************************************************/
char *v4l2_field_names[] = {
[V4L2_FIELD_ANY] = "any",
[V4L2_FIELD_NONE] = "none",
[V4L2_FIELD_TOP] = "top",
[V4L2_FIELD_BOTTOM] = "bottom",
[V4L2_FIELD_INTERLACED] = "interlaced",
[V4L2_FIELD_SEQ_TB] = "seq-tb",
[V4L2_FIELD_SEQ_BT] = "seq-bt",
[V4L2_FIELD_ALTERNATE] = "alternate",
};
char *v4l2_type_names[] = {
[V4L2_BUF_TYPE_VIDEO_CAPTURE] = "video-cap",
[V4L2_BUF_TYPE_VIDEO_OVERLAY] = "video-over",
[V4L2_BUF_TYPE_VIDEO_OUTPUT] = "video-out",
[V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap",
[V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out",
[V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap",
[V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "slicec-vbi-out",
};
static char *v4l2_memory_names[] = {
[V4L2_MEMORY_MMAP] = "mmap",
[V4L2_MEMORY_USERPTR] = "userptr",
[V4L2_MEMORY_OVERLAY] = "overlay",
};
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr))
#define prt_names(a,arr) (((a)<ARRAY_SIZE(arr))?arr[a]:"unknown")
static char *prt_caps(uint32_t caps)
{
static char s[4096]="";
s[0] = '\0';
if (V4L2_CAP_VIDEO_CAPTURE & caps)
strcat (s,"CAPTURE ");
if (V4L2_CAP_VIDEO_CAPTURE_MPLANE & caps)
strcat (s,"CAPTURE_MPLANE ");
if (V4L2_CAP_VIDEO_OUTPUT & caps)
strcat (s,"OUTPUT ");
if (V4L2_CAP_VIDEO_OUTPUT_MPLANE & caps)
strcat (s,"OUTPUT_MPLANE ");
if (V4L2_CAP_VIDEO_M2M & caps)
strcat (s,"M2M ");
if (V4L2_CAP_VIDEO_M2M_MPLANE & caps)
strcat (s,"M2M_MPLANE ");
if (V4L2_CAP_VIDEO_OVERLAY & caps)
strcat (s,"OVERLAY ");
if (V4L2_CAP_VBI_CAPTURE & caps)
strcat (s,"VBI_CAPTURE ");
if (V4L2_CAP_VBI_OUTPUT & caps)
strcat (s,"VBI_OUTPUT ");
if (V4L2_CAP_SLICED_VBI_CAPTURE & caps)
strcat (s,"SLICED_VBI_CAPTURE ");
if (V4L2_CAP_SLICED_VBI_OUTPUT & caps)
strcat (s,"SLICED_VBI_OUTPUT ");
if (V4L2_CAP_RDS_CAPTURE & caps)
strcat (s,"RDS_CAPTURE ");
if (V4L2_CAP_RDS_OUTPUT & caps)
strcat (s,"RDS_OUTPUT ");
if (V4L2_CAP_SDR_CAPTURE & caps)
strcat (s,"SDR_CAPTURE ");
if (V4L2_CAP_TUNER & caps)
strcat (s,"TUNER ");
if (V4L2_CAP_HW_FREQ_SEEK & caps)
strcat (s,"HW_FREQ_SEEK ");
if (V4L2_CAP_MODULATOR & caps)
strcat (s,"MODULATOR ");
if (V4L2_CAP_AUDIO & caps)
strcat (s,"AUDIO ");
if (V4L2_CAP_RADIO & caps)
strcat (s,"RADIO ");
if (V4L2_CAP_READWRITE & caps)
strcat (s,"READWRITE ");
if (V4L2_CAP_ASYNCIO & caps)
strcat (s,"ASYNCIO ");
if (V4L2_CAP_STREAMING & caps)
strcat (s,"STREAMING ");
if (V4L2_CAP_EXT_PIX_FORMAT & caps)
strcat (s,"EXT_PIX_FORMAT ");
if (V4L2_CAP_DEVICE_CAPS & caps)
strcat (s,"DEVICE_CAPS ");
if(V4L2_CAP_VIDEO_OUTPUT_OVERLAY & caps)
strcat (s,"VIDEO_OUTPUT_OVERLAY ");
if(V4L2_CAP_HW_FREQ_SEEK & caps)
strcat (s,"HW_FREQ_SEEK ");
if(V4L2_CAP_VIDEO_M2M_MPLANE & caps)
strcat (s,"VIDEO_M2M_MPLANE ");
if(V4L2_CAP_VIDEO_M2M & caps)
strcat (s,"VIDEO_M2M ");
if(V4L2_CAP_SDR_OUTPUT & caps)
strcat (s,"SDR_OUTPUT ");
if(V4L2_CAP_META_CAPTURE & caps)
strcat (s,"META_CAPTURE ");
if(V4L2_CAP_READWRITE & caps)
strcat (s,"READWRITE ");
if(V4L2_CAP_ASYNCIO & caps)
strcat (s,"ASYNCIO ");
if(V4L2_CAP_STREAMING & caps)
strcat (s,"STREAMING ");
if(V4L2_CAP_META_OUTPUT & caps)
strcat (s,"META_OUTPUT ");
if(V4L2_CAP_TOUCH & caps)
strcat (s,"TOUCH ");
if(V4L2_CAP_IO_MC & caps)
strcat (s,"IO_MC ");
return s;
}
static void prt_buf_info(char *name,struct v4l2_buffer *p)
{
struct v4l2_timecode *tc=&p->timecode;
printf ("%s: %02ld:%02d:%02d.%08ld index=%d, type=%s, "
"bytesused=%d, flags=0x%08x, "
"field=%s, sequence=%d, memory=%s, offset=0x%08x, length=%d\n",
name, (p->timestamp.tv_sec/3600),
(int)(p->timestamp.tv_sec/60)%60,
(int)(p->timestamp.tv_sec%60),
p->timestamp.tv_usec,
p->index,
prt_names(p->type,v4l2_type_names),
p->bytesused,p->flags,
prt_names(p->field,v4l2_field_names),
p->sequence,
prt_names(p->memory,v4l2_memory_names),
p->m.offset,
p->length);
tc=&p->timecode;
printf ("\tTIMECODE: %02d:%02d:%02d type=%d, "
"flags=0x%08x, frames=%d, userbits=0x%02x%02x%02x%02x\n",
tc->hours,tc->minutes,tc->seconds,
tc->type, tc->flags, tc->frames,
tc->userbits[0],
tc->userbits[1],
tc->userbits[2],
tc->userbits[3]);
}
/****************************************************************************
Open V4L2 devices
****************************************************************************/
int v4l2_open (char *device, int debug, struct v4l2_driver *drv)
{
int ret;
memset(drv,0,sizeof(*drv));
drv->debug=debug;
if ((drv->fd = open(device, O_RDWR )) < 0) {
return(-errno);
}
ret=xioctl(drv->fd,VIDIOC_QUERYCAP,(void *) &drv->cap);
if (!ret && drv->debug) {
printf ("driver=%s, card=%s, bus=%s, version=%d.%d.%d, "
"capabilities=%s, device_caps=%s\n",
drv->cap.driver,drv->cap.card,drv->cap.bus_info,
(drv->cap.version >> 16) & 0xff,
(drv->cap.version >> 8) & 0xff,
drv->cap.version & 0xff,
prt_caps(drv->cap.capabilities),
(drv->cap.capabilities & V4L2_CAP_DEVICE_CAPS) ?
prt_caps(drv->cap.device_caps) : "N/A");
}
return ret;
}
/****************************************************************************
V4L2 Eumberations
****************************************************************************/
int v4l2_enum_stds (struct v4l2_driver *drv)
{
struct v4l2_standard *p=NULL;
struct drv_list *list;
int ok=0,i;
free_list(&drv->stds);
list=drv->stds=calloc(1,sizeof(*drv->stds));
assert (list!=NULL);
for (i=0; ok==0; i++) {
p=calloc(1,sizeof(*p));
assert (p);
p->index=i;
ok=xioctl(drv->fd,VIDIOC_ENUMSTD,p);
if (ok<0) {
ok=-errno;
free(p);
break;
}
if (drv->debug) {
printf ("STANDARD: index=%d, id=0x%08x, name=%s, fps=%.3f, "
"framelines=%d\n", p->index,
(unsigned int)p->id, p->name,
1.*p->frameperiod.denominator/p->frameperiod.numerator,
p->framelines);
}
if (list->curr) {
list->next=calloc(1,sizeof(*list->next));
list=list->next;
assert (list!=NULL);
}
list->curr=p;
}
if (i>0 && ok==-EINVAL)
return 0;
return ok;
}
int v4l2_enum_input (struct v4l2_driver *drv)
{
struct v4l2_input *p=NULL;
struct drv_list *list;
int ok=0,i;
free_list(&drv->inputs);
list=drv->inputs=calloc(1,sizeof(*drv->inputs));
assert (list!=NULL);
for (i=0; ok==0; i++) {
p=calloc(1,sizeof(*p));
assert (p);
p->index=i;
ok=xioctl(drv->fd,VIDIOC_ENUMINPUT,p);
if (ok<0) {
ok=errno;
free(p);
break;
}
if (drv->debug) {
printf ("INPUT: index=%d, name=%s, type=%d, audioset=%d, "
"tuner=%d, std=%08x, status=%d\n",
p->index,p->name,p->type,p->audioset, p->tuner,
(unsigned int)p->std, p->status);
}
if (list->curr) {
list->next=calloc(1,sizeof(*list->next));
list=list->next;
assert (list!=NULL);
}
list->curr=p;
}
if (i>0 && ok==-EINVAL)
return 0;
return ok;
}
int v4l2_enum_fmt (struct v4l2_driver *drv, enum v4l2_buf_type type)
{
struct v4l2_fmtdesc *p=NULL;
struct drv_list *list;
int ok=0,i;
free_list(&drv->fmt_caps);
list=drv->fmt_caps=calloc(1,sizeof(*drv->fmt_caps));
assert (list!=NULL);
for (i=0; ok==0; i++) {
p=calloc(1,sizeof(*p));
assert (p!=NULL);
p->index=i;
p->type =type;
ok=xioctl(drv->fd,VIDIOC_ENUM_FMT,p);
if (ok<0) {
ok=errno;
free(p);
break;
}
if (drv->debug) {
printf ("FORMAT: index=%d, type=%d, flags=%d, description='%s'\n\t"
"fourcc=%c%c%c%c\n",
p->index, p->type, p->flags,p->description,
p->pixelformat & 0xff,
(p->pixelformat >> 8) & 0xff,
(p->pixelformat >> 16) & 0xff,
(p->pixelformat >> 24) & 0xff
);
}
if (list->curr) {
list->next=calloc(1,sizeof(*list->next));
list=list->next;
assert (list!=NULL);
}
list->curr=p;
}
if (i>0 && ok==-EINVAL)
return 0;
return ok;
}
/****************************************************************************
Set routines - currently, it also checks results with Get
****************************************************************************/
int v4l2_setget_std (struct v4l2_driver *drv, enum v4l2_direction dir, v4l2_std_id *id)
{
v4l2_std_id s_id=*id;
int ret=0;
char s[256];
if (dir & V4L2_SET) {
ret=xioctl(drv->fd,VIDIOC_S_STD,&s_id);
if (ret<0) {
ret=errno;
sprintf (s,"while trying to set STD to %08x",
(unsigned int) *id);
perror(s);
}
}
if (dir & V4L2_GET) {
ret=xioctl(drv->fd,VIDIOC_G_STD,&s_id);
if (ret<0) {
ret=errno;
perror ("while trying to get STD id");
}
}
if (dir == V4L2_SET_GET) {
if (*id & s_id) {
if (*id != s_id) {
printf ("Warning: Received a std subset (%08x"
" std) while trying to adjust to %08x\n",
(unsigned int) s_id,(unsigned int) *id);
}
} else {
fprintf (stderr,"Error: Received %08x std while trying"
" to adjust to %08x\n",
(unsigned int) s_id,(unsigned int) *id);
}
}
return ret;
}
int v4l2_setget_input (struct v4l2_driver *drv, enum v4l2_direction dir, struct v4l2_input *input)
{
int ok=0,ret;
unsigned int inp=input->index;
char s[256];
if (dir & V4L2_SET) {
ret=xioctl(drv->fd,VIDIOC_S_INPUT,input);
if (ret<0) {
ret=errno;
sprintf (s,"while trying to set INPUT to %d\n", inp);
perror(s);
}
}
if (dir & V4L2_GET) {
ret=xioctl(drv->fd,VIDIOC_G_INPUT,input);
if (ret<0) {
perror ("while trying to get INPUT id\n");
}
}
if (dir & V4L2_SET_GET) {
if (input->index != inp) {
printf ("Input is different than expected (received %i, set %i)\n",
inp, input->index);
}
}
return ok;
}
int v4l2_gettryset_fmt_cap (struct v4l2_driver *drv, enum v4l2_direction dir,
struct v4l2_format *fmt,uint32_t width, uint32_t height,
uint32_t pixelformat, enum v4l2_field field)
{
struct v4l2_pix_format *pix=&(fmt->fmt.pix);
int ret=0;
fmt->type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (dir == V4L2_GET) {
ret=xioctl(drv->fd,VIDIOC_G_FMT,fmt);
if (ret < 0) {
ret=errno;
perror("VIDIOC_G_FMT failed\n");
}
return ret;
}
if (dir & (~(V4L2_TRY | V4L2_SET)) ) {
perror ("Invalid direction\n");
return EINVAL;
}
if (dir & (V4L2_TRY|V4L2_SET)) {
pix->width = width;
pix->height = height;
pix->pixelformat = pixelformat;
pix->field = field;
/*
enum v4l2_colorspace colorspace;
*/
if (dir & V4L2_TRY) {
ret=xioctl(drv->fd,VIDIOC_TRY_FMT,fmt);
if (ret < 0) {
perror("VIDIOC_TRY_FMT failed\n");
}
}
if (dir & V4L2_SET) {
ret=xioctl(drv->fd,VIDIOC_S_FMT,fmt);
if (ret < 0) {
perror("VIDIOC_S_FMT failed\n");
}
drv->sizeimage=pix->sizeimage;
}
if (pix->pixelformat != pixelformat) {
fprintf(stderr,"Error: asked for format %d, received %d",pixelformat,
pix->pixelformat);
}
if (pix->width != width) {
fprintf(stderr,"Error: asked for format %d, received %d\n",width,
pix->width);
}
if (pix->height != height) {
fprintf(stderr,"Error: asked for format %d, received %d\n",height,
pix->height);
}
if (pix->bytesperline == 0 ) {
fprintf(stderr,"Error: bytesperline = 0\n");
}
if (pix->sizeimage == 0 ) {
fprintf(stderr,"Error: sizeimage = 0\n");
}
}
if (drv->debug)
printf( "FMT SET: %dx%d, fourcc=%c%c%c%c, %d bytes/line,"
" %d bytes/frame, colorspace=0x%08x\n",
pix->width,pix->height,
pix->pixelformat & 0xff,
(pix->pixelformat >> 8) & 0xff,
(pix->pixelformat >> 16) & 0xff,
(pix->pixelformat >> 24) & 0xff,
pix->bytesperline,
pix->sizeimage,
pix->colorspace);
return 0;
}
/****************************************************************************
Get routines
****************************************************************************/
int v4l2_get_parm (struct v4l2_driver *drv)
{
int ret;
struct v4l2_captureparm *c;
drv->parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ((ret=xioctl(drv->fd,VIDIOC_G_PARM,&drv->parm))>=0) {
c=&drv->parm.parm.capture;
printf ("PARM: capability=%d, capturemode=%d, %.3f fps "
"ext=%x, readbuf=%d\n",
c->capability,
c->capturemode,
c->timeperframe.denominator*1./c->timeperframe.numerator,
c->extendedmode, c->readbuffers);
} else {
ret=errno;
perror ("VIDIOC_G_PARM");
}
return ret;
}
/****************************************************************************
Queue and stream control
****************************************************************************/
int v4l2_free_bufs(struct v4l2_driver *drv)
{
unsigned int i;
if (!drv->n_bufs)
return 0;
/* Requests the driver to free all buffers */
drv->reqbuf.count = 0;
drv->reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
drv->reqbuf.memory = V4L2_MEMORY_MMAP;
/* stop capture */
if (xioctl(drv->fd,VIDIOC_STREAMOFF,&drv->reqbuf.type)<0)
return errno;
sleep (1); // FIXME: Should check if all buffers are stopped
/* V4L2 API says REQBUFS with count=0 should be used to release buffer.
However, video-buf.c doesn't implement it.
*/
#if 0
if (xioctl(drv->fd,VIDIOC_REQBUFS,&drv->reqbuf)<0) {
perror("reqbufs while freeing buffers");
return errno;
}
#endif
if (drv->reqbuf.count != 0) {
fprintf(stderr,"REQBUFS returned %d buffers while asking for freeing it!\n",
drv->reqbuf.count);
}
for (i = 0; i < drv->n_bufs; i++) {
if (drv->bufs[i].length)
munmap(drv->bufs[i].start, drv->bufs[i].length);
if (drv->v4l2_bufs[i])
free (drv->v4l2_bufs[i]);
}
free(drv->v4l2_bufs);
free(drv->bufs);
drv->v4l2_bufs=NULL;
drv->bufs=NULL;
drv->n_bufs=0;
return 0;
}
int v4l2_mmap_bufs(struct v4l2_driver *drv, unsigned int num_buffers)
{
/* Frees previous allocations, if required */
v4l2_free_bufs(drv);
if (drv->sizeimage==0) {
fprintf(stderr,"Image size is zero! Can't proceed\n");
return -1;
}
/* Requests the specified number of buffers */
drv->reqbuf.count = num_buffers;
drv->reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
drv->reqbuf.memory = V4L2_MEMORY_MMAP;
if (xioctl(drv->fd,VIDIOC_REQBUFS,&drv->reqbuf)<0) {
perror("reqbufs");
return errno;
}
if (drv->debug)
printf ("REQBUFS: count=%d, type=%s, memory=%s\n",
drv->reqbuf.count,
prt_names(drv->reqbuf.type,v4l2_type_names),
prt_names(drv->reqbuf.memory,v4l2_memory_names));
/* Allocates the required number of buffers */
drv->v4l2_bufs=calloc(drv->reqbuf.count, sizeof(*drv->v4l2_bufs));
assert(drv->v4l2_bufs!=NULL);
drv->bufs=calloc(drv->reqbuf.count, sizeof(*drv->bufs));
assert(drv->bufs!=NULL);
for (drv->n_bufs = 0; drv->n_bufs < drv->reqbuf.count; drv->n_bufs++) {
struct v4l2_buffer *p;
/* Requests kernel buffers to be mmapped */
p=drv->v4l2_bufs[drv->n_bufs]=calloc(1,sizeof(*p));
assert (p!=NULL);
p->index = drv->n_bufs;
p->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
p->memory = V4L2_MEMORY_MMAP;
if (xioctl(drv->fd,VIDIOC_QUERYBUF,p)<0) {
int ret=errno;
perror("querybuf");
free (drv->v4l2_bufs[drv->n_bufs]);
v4l2_free_bufs(drv);
return ret;
}
if (drv->debug)
prt_buf_info("QUERYBUF",p);
if (drv->sizeimage != p->length) {
if (drv->sizeimage < p->length) {
fprintf (stderr, "QUERYBUF: ERROR: VIDIOC_S_FMT said buffer should have %d size, but received %d from QUERYBUF!\n",
drv->sizeimage, p->length);
} else {
fprintf (stderr, "QUERYBUF: Expecting %d size, received %d buff length\n",
drv->sizeimage, p->length);
}
}
drv->bufs[drv->n_bufs].length = p->length;
drv->bufs[drv->n_bufs].start = mmap (NULL, /* start anywhere */
p->length,
PROT_READ | PROT_WRITE, /* required */
MAP_SHARED, /* recommended */
drv->fd, p->m.offset);
if (MAP_FAILED == drv->bufs[drv->n_bufs].start) {
perror("mmap");
free (drv->v4l2_bufs[drv->n_bufs]);
v4l2_free_bufs(drv);
return errno;
}
}
return 0;
}
/* Returns <0, if error, 0 if nothing to read and <size>, if something
read
*/
int v4l2_rcvbuf(struct v4l2_driver *drv, v4l2_recebe_buffer *rec_buf)
{
int ret;
struct v4l2_buffer buf;
memset (&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl (drv->fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
perror ("dqbuf");
return -errno;
}
}
prt_buf_info("DQBUF",&buf);
assert (buf.index < drv->n_bufs);
ret = rec_buf (&buf,&drv->bufs[buf.index]);
if (ret<0) {
v4l2_free_bufs(drv);
return ret;
}
if (-1 == xioctl (drv->fd, VIDIOC_QBUF, &buf)) {
perror ("qbuf");
return -errno;
}
return ret;
}
int v4l2_start_streaming(struct v4l2_driver *drv)
{
uint32_t i;
struct v4l2_buffer buf;
if (drv->debug)
printf("Activating %d queues\n", drv->n_bufs);
for (i = 0; i < drv->n_bufs; i++) {
int res;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
res = xioctl (drv->fd, VIDIOC_QBUF, &buf);
if (!res)
prt_buf_info("QBUF",&buf);
else {
perror("qbuf");
return errno;
}
}
/* Activates stream */
if (drv->debug)
printf("Enabling streaming\n");
if (xioctl(drv->fd,VIDIOC_STREAMON,&drv->reqbuf.type)<0)
return errno;
return 0;
}
int v4l2_stop_streaming(struct v4l2_driver *drv)
{
v4l2_free_bufs(drv);
return 0;
}
/****************************************************************************
Close V4L2, disallocating all structs
****************************************************************************/
int v4l2_close (struct v4l2_driver *drv)
{
v4l2_free_bufs(drv);
free_list(&drv->stds);
free_list(&drv->inputs);
free_list(&drv->fmt_caps);
return (close(drv->fd));
}
/****************************************************************************
Get/Set frequency
****************************************************************************/
int v4l2_getset_freq (struct v4l2_driver *drv, enum v4l2_direction dir,
double *freq)
{
struct v4l2_tuner tun;
struct v4l2_frequency frq;
double d = 62500;
memset(&tun, 0, sizeof(tun));
if (-1 == xioctl (drv->fd, VIDIOC_G_TUNER, &tun)) {
perror ("g_tuner");
printf("Assuming 62.5 kHz step\n");
} else {
if (tun.capability & V4L2_TUNER_CAP_LOW)
d=62.5;
}
if (drv->debug) {
if (tun.capability & V4L2_TUNER_CAP_LOW)
printf("62.5 Hz step\n");
else
printf("62.5 kHz step\n");
}
memset(&frq, 0, sizeof(frq));
frq.type = V4L2_TUNER_ANALOG_TV;
if (dir & V4L2_GET) {
if (-1 == xioctl (drv->fd, VIDIOC_G_FREQUENCY, &frq)) {
perror ("s_frequency");
return errno;
}
*freq = frq.frequency * d;
if (drv->debug)
printf("board is at freq %4.3f MHz (%d)\n",
*freq/1000000, frq.frequency);
} else {
frq.frequency = (uint32_t)(((*freq)+d/2) / d);
if (-1 == xioctl (drv->fd, VIDIOC_S_FREQUENCY, &frq)) {
perror ("s_frequency");
return errno;
}
if (drv->debug)
printf("board set to freq %4.3f MHz (%d)\n",
*freq/1000000, frq.frequency);
}
return 0;
}