概述
Video4Linux2是Linux系统下进行音影图像开发的应用编程接口,他比Video4Linux具有更好的扩张性和灵活性,支持更多的硬件设备。该框架遵循驱动框架设计理念:它具有用于表示设备数据实例的struct v4l2_device,用于引用子设备实例的struct v4l2_subdev,用于存储v4l2设备节点信息的struct video_device,以及用于描述v4l2文件句柄的struct v4l2_fh。v4l2框架可以选择与media framework结合变得更加灵活,方便运行时控制。linux源码中有提供一个基于v4l2框架的编程实例v4l2-pci-skeleton.c,通过这个实例可以去学会使用v4l2 driver api。
Stream I/O
v4l2有三个Stream I/O 方式:Memory Mapping、User Pointers、DMA buffer importing。这三种方式都可以在应用层获取到driver填充的camera图像数据。
1 、Memory Mapping
内存映射的方式有两种情况,一种是单平面的内存映射,一种是多平面的内存映射。
在多平面映射的时候,需要定义一个struct v4l2_plane数组然后把首地址赋值给m.planes,并且把plane的数量告知给v4l2_buffer。在VIDIOC_QUERYBUF成功后调用mmap映射每一个buffer、每一个plane,映射次数为请求的buffer数量*plane数量。从概念上来说,流驱动程序需要维护两个队列,一个是传入队列,一个是传出队列。这两个队列都以FIFO的形式运作。其实v4l2 core层已经帮你实现了出队入队的工作,在你VIDIOC_QBUF的时候帮你把buffer放进vb2_queue的queued_list中 ,在你VIDIOC_DQBUF的时候帮你从vb2_queue的done_list中取出已经填充好数据的buffer。需要注意的是,当4412接受完图像后会产生一个硬件中断,在中断处理函数中,需要调用vb2_buffer_done把填充完的buffer放进vb2_queue的done_wq中并唤醒等待队列。调用vb2_buffer_done的这个操作一般是平台驱动完成的,比如三星平台,会在fimc驱动程序中完成。
关于多平面的映射方法如下:
struct v4l2_requestbuffers reqbuf;
/* Our current format uses 3 planes per buffer */
#define FMT_NUM_PLANES = 3struct {void *start[FMT_NUM_PLANES];size_t length[FMT_NUM_PLANES];
} *buffers;
unsigned int i, j;memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = 20;if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {if (errno == EINVAL)printf("Video capturing or mmap-streaming is not supported\\n");elseperror("VIDIOC_REQBUFS");exit(EXIT_FAILURE);
}/* We want at least five buffers. */if (reqbuf.count < 5) {/* You may need to free the buffers here. */printf("Not enough buffer memory\\n");exit(EXIT_FAILURE);
}buffers = calloc(reqbuf.count, sizeof(*buffers));
assert(buffers != NULL);for (i = 0; i < reqbuf.count; i++) {struct v4l2_buffer buffer;struct v4l2_plane planes[FMT_NUM_PLANES];memset(&buffer, 0, sizeof(buffer));buffer.type = reqbuf.type;buffer.memory = V4L2_MEMORY_MMAP;buffer.index = i;/* length in struct v4l2_buffer in multi-planar API stores the size* of planes array. */buffer.length = FMT_NUM_PLANES;buffer.m.planes = planes;if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) {perror("VIDIOC_QUERYBUF");exit(EXIT_FAILURE);}/* Every plane has to be mapped separately */for (j = 0; j < FMT_NUM_PLANES; j++) {buffers[i].length[j] = buffer.m.planes[j].length; /* remember for munmap() */buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length,PROT_READ | PROT_WRITE, /* recommended */MAP_SHARED, /* recommended */fd, buffer.m.planes[j].m.offset);if (MAP_FAILED == buffers[i].start[j]) {/* If you do not exit here you should unmap() and free()the buffers and planes mapped so far. */perror("mmap");exit(EXIT_FAILURE);}}
}/* Cleanup. */for (i = 0; i < reqbuf.count; i++)for (j = 0; j < FMT_NUM_PLANES; j++)munmap(buffers[i].start[j], buffers[i].length[j]);
2 、User Pointers
顾名思义,这种方式就是用户空间指针的意思,由应用层去分配连续的地址空间,然后以指针的形式传递给v4l2驱动程序,v4l2驱动程序会把获取到的图像数据保存到这个地址上面。在调用VIDIOC_REQBUFS的时候指定内存方式为V4L2_MEMORY_USERPTR。
struct v4l2_requestbuffers reqbuf;memset (&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_USERPTR;
if (ioctl (fd, VIDIOC_REQBUFS, &reqbuf) == -1) {if (errno == EINVAL)printf ("Video capturing or user pointer streaming is not supported\\n");elseperror ("VIDIOC_REQBUFS");exit (EXIT_FAILURE);
}
struct v4l2_buffer结构体中有一个union,里面有个userptr成员,通过设置这个成员可以把用户层分配的地址空间的首地址给v4l2驱动程序。
union {__u32 offset;unsigned long userptr;struct v4l2_plane *planes;__s32 fd;} m;
3 、DMA buffer importing
DMABUF框架提供了一种用于多个设备间共享缓冲区的通用方法。支持DMABUF的设备驱动程序可以把DMA缓冲区以文件描述符的形式导出到用户空间(导出者角色);也可以从用户空间把从不同设备或者同一个设备导出来的文件描述符导入到DMA缓冲区(导入者角色)。这里讲下导入者这种方式的应用。
在VIDIOC_REQBUFS的时候告知v4l2要使用DMA buffer importing。
struct v4l2_requestbuffers reqbuf;memset(&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_DMABUF;
reqbuf.count = 1;if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {if (errno == EINVAL)printf("Video capturing or DMABUF streaming is not supported\\n");elseperror("VIDIOC_REQBUFS");exit(EXIT_FAILURE);
}
使用单平面的api入队dma buffer
int buffer_queue(int v4lfd, int index, int dmafd)
{struct v4l2_buffer buf;memset(&buf, 0, sizeof buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_DMABUF;buf.index = index;buf.m.fd = dmafd;if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {perror("VIDIOC_QBUF");return -1;}return 0;
}
使用多平面的api入队dma buffer
int buffer_queue_mp(int v4lfd, int index, int dmafd[], int n_planes)
{struct v4l2_buffer buf;struct v4l2_plane planes[VIDEO_MAX_PLANES];int i;memset(&buf, 0, sizeof buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;buf.memory = V4L2_MEMORY_DMABUF;buf.index = index;buf.m.planes = planes;buf.length = n_planes;memset(&planes, 0, sizeof planes);for (i = 0; i < n_planes; ++i)buf.m.planes[i].m.fd = dmafd[i];if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {perror("VIDIOC_QBUF");return -1;}return 0;
}
其中的dma可以是这样创建的
mIonFd = ion_open();for (uint32_t i = 0; i < mNumBuffers; i++) {ionHandle = -1;int32_t err = ion_alloc(mIonFd, ionSize, 8, 1, 0, &ionHandle);if (err) {ALOGE("ion_alloc failed.");return BAD_VALUE;}err = ion_map(mIonFd, ionHandle,ionSize,PROT_READ | PROT_WRITE,MAP_SHARED,0, &ptr,&sharedFd);if (err) {ALOGE("ion_map failed.");ion_free(mIonFd, ionHandle);if (ptr != MAP_FAILED) {munmap(ptr, ionSize);}if (sharedFd > 0) {close(sharedFd);}goto err;}phyAddr = ion_phys(mIonFd, ionHandle);if (phyAddr == 0) {ALOGE("ion_phys failed.");ion_free(mIonFd, ionHandle);if (ptr != MAP_FAILED) {munmap(ptr, ionSize);}close(sharedFd);goto err;}ALOGI("phyalloc ptr:0x%p, phy:0x%x, ionSize:%d", ptr, phyAddr, ionSize);mBuffers[i] = new StreamBuffer();mBuffers[i]->mVirtAddr = ptr;mBuffers[i]->mPhyAddr = phyAddr;mBuffers[i]->mSize = ionSize;mBuffers[i]->mBufHandle = (buffer_handle_t*)(uintptr_t)ionHandle;mBuffers[i]->mFd = sharedFd;mBuffers[i]->mStream = this;for (uint32_t i = 0; i < mNumBuffers; i++) { //使用多平面的api入队dma buffermemset(&cfilledbuffer, 0, sizeof (struct v4l2_buffer));cfilledbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;cfilledbuffer.memory = V4L2_MEMORY_DMABUF;cfilledbuffer.m.fd = mBuffers[i]->mFd;cfilledbuffer.index = i;cfilledbuffer.length = mStreamSize;ALOGI("buf[%d] length:%d", i, cfilledbuffer.length);ret = ioctl(mDev, VIDIOC_QBUF, &cfilledbuffer);if (ret < 0) {ALOGE("%s VIDIOC_QBUF Failed: %s", __func__, strerror(errno));return BAD_VALUE;}}
}
v4l2 capture例子
使用v4l2去采集摄像头图像,一般就是下面几个操作:
1、以O_RDWR | O_NONBLOCK的方式open设备节点,为啥是O_NONBLOCK,因为在VIDIOC_DQBUF的时候如果在输出队列中没有数据,那默认是阻塞的。
2、通过VIDIOC_QUERYCAP获取摄像头的相关信息。
3、通过VIDIOC_S_FMT设置摄像头格式
4、通过VIDIOC_REQBUFS请求buffer,一般是4个buffer。
5、通过VIDIOC_QUERYBUF查询buffer,如果buffer已请求成功,则调用mmap映射驱动分配的buffer到应用层
6、通过VIDIOC_QBUF把buffer放到传入队列
7、通过VIDIOC_STREAMON启动流传输
8、在文件描述符上poll或者select等待数据
9、被唤醒后通过VIDIOC_DQBUF从传出队列中取出数据,同时会告诉你数据放在哪个buffer。
10、处理图像、显示图像,然后回到6操作继续。
下面是一个capture例子。
/** V4L2 video capture example** This program can be used and distributed without restrictions.** This program is provided with the V4L2 API* see https://linuxtv.org/docs.php for more information*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>#include <getopt.h> /* getopt_long() */#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>#include <linux/videodev2.h>#define CLEAR(x) memset(&(x), 0, sizeof(x))enum io_method {IO_METHOD_READ,IO_METHOD_MMAP,IO_METHOD_USERPTR,
};struct buffer {void *start;size_t length;
};static char *dev_name;
static enum io_method io = IO_METHOD_MMAP;
static int fd = -1;
struct buffer *buffers;
static unsigned int n_buffers;
static int out_buf;
static int force_format;
static int frame_count = 70;static void errno_exit(const char *s)
{fprintf(stderr, "%s error %d, %s\\n", s, errno, strerror(errno));exit(EXIT_FAILURE);
}static int xioctl(int fh, int request, void *arg)
{int r;do {r = ioctl(fh, request, arg);} while (-1 == r && EINTR == errno);return r;
}static void process_image(const void *p, int size)
{if (out_buf)fwrite(p, size, 1, stdout);fflush(stderr);fprintf(stderr, ".");fflush(stdout);
}static int read_frame(void)
{struct v4l2_buffer buf;unsigned int i;switch (io) {case IO_METHOD_READ:if (-1 == read(fd, buffers[0].start, buffers[0].length)) {switch (errno) {case EAGAIN:return 0;case EIO:/* Could ignore EIO, see spec. *//* fall through */default:errno_exit("read");}}process_image(buffers[0].start, buffers[0].length);break;case IO_METHOD_MMAP:CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {switch (errno) {case EAGAIN:return 0;case EIO:/* Could ignore EIO, see spec. *//* fall through */default:errno_exit("VIDIOC_DQBUF");}}assert(buf.index < n_buffers);process_image(buffers[buf.index].start, buf.bytesused);if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))errno_exit("VIDIOC_QBUF");break;case IO_METHOD_USERPTR:CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_USERPTR;if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {switch (errno) {case EAGAIN:return 0;case EIO:/* Could ignore EIO, see spec. *//* fall through */default:errno_exit("VIDIOC_DQBUF");}}for (i = 0; i < n_buffers; ++i)if (buf.m.userptr == (unsigned long)buffers[i].start&& buf.length == buffers[i].length)break;assert(i < n_buffers);process_image((void *)buf.m.userptr, buf.bytesused);if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))errno_exit("VIDIOC_QBUF");break;}return 1;
}static void mainloop(void)
{unsigned int count;count = frame_count;while (count-- > 0) {for (;;) {fd_set fds;struct timeval tv;int r;FD_ZERO(&fds);FD_SET(fd, &fds);/* Timeout. */tv.tv_sec = 2;tv.tv_usec = 0;r = select(fd + 1, &fds, NULL, NULL, &tv);if (-1 == r) {if (EINTR == errno)continue;errno_exit("select");}if (0 == r) {fprintf(stderr, "select timeout\\n");exit(EXIT_FAILURE);}if (read_frame())break;/* EAGAIN - continue select loop. */}}
}static void stop_capturing(void)
{enum v4l2_buf_type type;switch (io) {case IO_METHOD_READ:/* Nothing to do. */break;case IO_METHOD_MMAP:case IO_METHOD_USERPTR:type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))errno_exit("VIDIOC_STREAMOFF");break;}
}static void start_capturing(void)
{unsigned int i;enum v4l2_buf_type type;switch (io) {case IO_METHOD_READ:/* Nothing to do. */break;case IO_METHOD_MMAP:for (i = 0; i < n_buffers; ++i) {struct v4l2_buffer buf;CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))errno_exit("VIDIOC_QBUF");}type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))errno_exit("VIDIOC_STREAMON");break;case IO_METHOD_USERPTR:for (i = 0; i < n_buffers; ++i) {struct v4l2_buffer buf;CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_USERPTR;buf.index = i;buf.m.userptr = (unsigned long)buffers[i].start;buf.length = buffers[i].length;if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))errno_exit("VIDIOC_QBUF");}type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))errno_exit("VIDIOC_STREAMON");break;}
}static void uninit_device(void)
{unsigned int i;switch (io) {case IO_METHOD_READ:free(buffers[0].start);break;case IO_METHOD_MMAP:for (i = 0; i < n_buffers; ++i)if (-1 == munmap(buffers[i].start, buffers[i].length))errno_exit("munmap");break;case IO_METHOD_USERPTR:for (i = 0; i < n_buffers; ++i)free(buffers[i].start);break;}free(buffers);
}static void init_read(unsigned int buffer_size)
{buffers = calloc(1, sizeof(*buffers));if (!buffers) {fprintf(stderr, "Out of memory\\n");exit(EXIT_FAILURE);}buffers[0].length = buffer_size;buffers[0].start = malloc(buffer_size);if (!buffers[0].start) {fprintf(stderr, "Out of memory\\n");exit(EXIT_FAILURE);}
}static void init_mmap(void)
{struct v4l2_requestbuffers req;CLEAR(req);req.count = 4;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_MMAP;if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {if (EINVAL == errno) {fprintf(stderr, "%s does not support ""memory mappingn", dev_name);exit(EXIT_FAILURE);} else {errno_exit("VIDIOC_REQBUFS");}}if (req.count < 2) {fprintf(stderr, "Insufficient buffer memory on %s\\n",dev_name);exit(EXIT_FAILURE);}buffers = calloc(req.count, sizeof(*buffers));if (!buffers) {fprintf(stderr, "Out of memory\\n");exit(EXIT_FAILURE);}for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {struct v4l2_buffer buf;CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = n_buffers;if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))errno_exit("VIDIOC_QUERYBUF");buffers[n_buffers].length = buf.length;buffers[n_buffers].start =mmap(NULL /* start anywhere */,buf.length,PROT_READ | PROT_WRITE /* required */,MAP_SHARED /* recommended */,fd, buf.m.offset);if (MAP_FAILED == buffers[n_buffers].start)errno_exit("mmap");}
}static void init_userp(unsigned int buffer_size)
{struct v4l2_requestbuffers req;CLEAR(req);req.count = 4;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_USERPTR;if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {if (EINVAL == errno) {fprintf(stderr, "%s does not support ""user pointer i/on", dev_name);exit(EXIT_FAILURE);} else {errno_exit("VIDIOC_REQBUFS");}}buffers = calloc(4, sizeof(*buffers));if (!buffers) {fprintf(stderr, "Out of memory\\n");exit(EXIT_FAILURE);}for (n_buffers = 0; n_buffers < 4; ++n_buffers) {buffers[n_buffers].length = buffer_size;buffers[n_buffers].start = malloc(buffer_size);if (!buffers[n_buffers].start) {fprintf(stderr, "Out of memory\\n");exit(EXIT_FAILURE);}}
}static void init_device(void)
{struct v4l2_capability cap;struct v4l2_cropcap cropcap;struct v4l2_crop crop;struct v4l2_format fmt;unsigned int min;if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {if (EINVAL == errno) {fprintf(stderr, "%s is no V4L2 device\\n",dev_name);exit(EXIT_FAILURE);} else {errno_exit("VIDIOC_QUERYCAP");}}if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {fprintf(stderr, "%s is no video capture device\\n",dev_name);exit(EXIT_FAILURE);}switch (io) {case IO_METHOD_READ:if (!(cap.capabilities & V4L2_CAP_READWRITE)) {fprintf(stderr, "%s does not support read i/o\\n",dev_name);exit(EXIT_FAILURE);}break;case IO_METHOD_MMAP:case IO_METHOD_USERPTR:if (!(cap.capabilities & V4L2_CAP_STREAMING)) {fprintf(stderr, "%s does not support streaming i/o\\n",dev_name);exit(EXIT_FAILURE);}break;}/* Select video input, video standard and tune here. */CLEAR(cropcap);cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;crop.c = cropcap.defrect; /* reset to default */if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {switch (errno) {case EINVAL:/* Cropping not supported. */break;default:/* Errors ignored. */break;}}} else {/* Errors ignored. */}CLEAR(fmt);fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (force_format) {fmt.fmt.pix.width = 640;fmt.fmt.pix.height = 480;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))errno_exit("VIDIOC_S_FMT");/* Note VIDIOC_S_FMT may change width and height. */} else {/* Preserve original settings as set by v4l2-ctl for example */if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))errno_exit("VIDIOC_G_FMT");}/* Buggy driver paranoia. */min = fmt.fmt.pix.width * 2;if (fmt.fmt.pix.bytesperline < min)fmt.fmt.pix.bytesperline = min;min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;if (fmt.fmt.pix.sizeimage < min)fmt.fmt.pix.sizeimage = min;switch (io) {case IO_METHOD_READ:init_read(fmt.fmt.pix.sizeimage);break;case IO_METHOD_MMAP:init_mmap();break;case IO_METHOD_USERPTR:init_userp(fmt.fmt.pix.sizeimage);break;}
}static void close_device(void)
{if (-1 == close(fd))errno_exit("close");fd = -1;
}static void open_device(void)
{struct stat st;if (-1 == stat(dev_name, &st)) {fprintf(stderr, "Cannot identify '%s': %d, %s\\n",dev_name, errno, strerror(errno));exit(EXIT_FAILURE);}if (!S_ISCHR(st.st_mode)) {fprintf(stderr, "%s is no devicen", dev_name);exit(EXIT_FAILURE);}fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);if (-1 == fd) {fprintf(stderr, "Cannot open '%s': %d, %s\\n",dev_name, errno, strerror(errno));exit(EXIT_FAILURE);}
}static void usage(FILE *fp, int argc, char **argv)
{fprintf(fp,"Usage: %s [options]\\n\\n""Version 1.3\\n""Options:\\n""-d | --device name Video device name [%s]n""-h | --help Print this messagen""-m | --mmap Use memory mapped buffers [default]n""-r | --read Use read() callsn""-u | --userp Use application allocated buffersn""-o | --output Outputs stream to stdoutn""-f | --format Force format to 640x480 YUYVn""-c | --count Number of frames to grab [%i]n""",argv[0], dev_name, frame_count);
}static const char short_options[] = "d:hmruofc:";static const struct option
long_options[] = {{ "device", required_argument, NULL, 'd' },{ "help", no_argument, NULL, 'h' },{ "mmap", no_argument, NULL, 'm' },{ "read", no_argument, NULL, 'r' },{ "userp", no_argument, NULL, 'u' },{ "output", no_argument, NULL, 'o' },{ "format", no_argument, NULL, 'f' },{ "count", required_argument, NULL, 'c' },{ 0, 0, 0, 0 }
};int main(int argc, char **argv)
{dev_name = "/dev/video0";for (;;) {int idx;int c;c = getopt_long(argc, argv,short_options, long_options, &idx);if (-1 == c)break;switch (c) {case 0: /* getopt_long() flag */break;case 'd':dev_name = optarg;break;case 'h':usage(stdout, argc, argv);exit(EXIT_SUCCESS);case 'm':io = IO_METHOD_MMAP;break;case 'r':io = IO_METHOD_READ;break;case 'u':io = IO_METHOD_USERPTR;break;case 'o':out_buf++;break;case 'f':force_format++;break;case 'c':errno = 0;frame_count = strtol(optarg, NULL, 0);if (errno)errno_exit(optarg);break;default:usage(stderr, argc, argv);exit(EXIT_FAILURE);}}open_device();init_device();start_capturing();mainloop();stop_capturing();uninit_device();close_device();fprintf(stderr, "\\n");return 0;
}