/*
* V4L2 video capture example
*
* This program can be used and distributed without restrictions.
*/
#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 <malloc.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 <asm/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#include <opencv2/opencv.hpp>
#include <jpeglib.h>
#include <iostream>
#include <boost/thread/thread.hpp>
#define CLEAR(x) memset (&(x), 0, sizeof (x))
#define Img_WIDTH 1504
#define Img_HEIGHT 1504
#define YUYV //YUYV MJEGP
#define ShowImg false //YUYV MJEGP
#define FrameRate 30
typedef enum {
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR,
} io_method;
struct buffer {
void * start;
size_t length;
};
static char * dev_name = NULL;
static io_method io = IO_METHOD_MMAP;
static int fd = -1;
struct buffer * buffers = NULL;
static unsigned int n_buffers = 0;
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 fd,int request,void * arg)
{
int r;
do r = ioctl (fd, request, arg);// ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下
while (-1 == r && EINTR == errno);
return r;
}
static void process_image(const void * p)
{
fputc('.',stdout);
fputc('.',stdout);
fflush(stdout);
}
double t_old;
static void process_image(const void * p,int length)
{
// std::cout<<"length = "<<length<<std::endl;
double t = ((double)cv::getTickCount()-t_old)/cv::getTickFrequency();
// std::cout<<" t = "<<t*1000<<" ms"<<std::endl;
t_old = (double)cv::getTickCount();
#ifdef YUYV
cv::Mat M(Img_HEIGHT,Img_WIDTH,CV_8U);
uchar *data = (uchar*)M.data;
for(int i=0;i<Img_HEIGHT*Img_WIDTH;i++)
{
data[i] = *((uchar*)p+2*i);
}
if(ShowImg)
{
cv::imshow("img_gray",M);
cv::waitKey(1);
}
#endif
#ifdef MJPEG
cv::Mat M(Img_HEIGHT,Img_WIDTH*2,CV_8U);
uchar *data = (uchar*)M.data;
for(int i=0;i<Img_HEIGHT*Img_WIDTH*2;i++)
{
data[i] = *((uchar*)p+i);
}
cv::Mat A = cv::imdecode(M,cv::IMREAD_GRAYSCALE);
cv::imshow("img_gray",A);
cv::waitKey(1);
if(ShowImg)
{
cv::imshow("img_gray",M);
cv::waitKey(1);
}
#endif
}
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,buffers[buf.index].length);
{
FILE *fp = NULL;
// fp = fopen("/home/huajun/hj/test.jpg", "w");
if (fp != NULL)
{
// fwrite(buffers[buf.index].start, 1, buffers[buf.index].length, fp);
sync();
// fclose(fp);
}
}
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);
if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
break;
}
return 1;
}
static void mainloop(void)
{
unsigned int count;
count = 100;
while (1) {
for (;;) {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds); //将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET (fd, &fds); //用于在文件描述符集合中增加一个新的文件描述符。
// std::cout<<" fd = "<<fd<<std::endl;
/* Timeout. */
tv.tv_sec = 4;
tv.tv_usec = 0;
// t_old = (double)cv::getTickCount();
r = select (fd + 1, &fds, NULL, NULL, &tv); //用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型
// double t = ((double)cv::getTickCount()-t_old)/cv::getTickFrequency();
// std::cout<<" t = "<<t*1000<<" ms"<<std::endl;
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.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 = (buffer*)calloc (1, sizeof (*buffers));//内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
buffers[0].length = buffer_size;
buffers[0].start = malloc (buffer_size); //malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型
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 mapping\n", 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 = (buffer*)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 , buf.length,PROT_READ | PROT_WRITE , MAP_SHARED , 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/o\n", dev_name);
exit (EXIT_FAILURE);
}
else {
errno_exit ("VIDIOC_REQBUFS");
}
}
buffers = (buffer*)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)) { //使用ioctl VIDIOC_QUERYCAP 来查询当前driver是否合乎规范 因为V4L2要求所有driver 和Device都支持这个Ioctl
if (EINVAL == errno) { /* Invalid argument */
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 */ //c是表示采集窗口的大小的结构体,
if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
switch (errno) {
case EINVAL:
/* Cropping not supported. */
break;
default:
/* Errors ignored. */
break;
}
}
}
else {
/* Errors ignored. */
}
int ret;
struct v4l2_fmtdesc fmt_my;
memset(&fmt_my, 0, sizeof(fmt_my));
fmt_my.index = 0;
fmt_my.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt_my)) == 0) { //查询支持的格式
printf("{ pixelformat = '%c%c%c%c', description = '%s' }\n",
fmt_my.pixelformat & 0xFF, (fmt_my.pixelformat >> 8) & 0xFF,
(fmt_my.pixelformat >> 16) & 0xFF, (fmt_my.pixelformat >> 24) & 0xFF,
fmt_my.description);
fmt_my.index++;
}
CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = Img_WIDTH;
fmt.fmt.pix.height = Img_HEIGHT;
#ifdef YUYV
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_MJPEG;//
#endif
#ifdef MJPEG
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//V4L2_PIX_FMT_YUYV;//
#endif
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. */
/* 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;
struct v4l2_streamparm Stream_Parm;
memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));
Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
Stream_Parm.parm.capture.timeperframe.denominator =FrameRate; //分母
Stream_Parm.parm.capture.timeperframe.numerator = 1; //分子
if(-1 == xioctl(fd, VIDIOC_S_PARM, &Stream_Parm)) //设置帧数
{
printf("Set frame rate failed\n");
}
memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));
Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1 == xioctl(fd, VIDIOC_G_PARM, &Stream_Parm)) //获取帧数
{
printf("Get frame rate failed\n");
}
int capability = Stream_Parm.parm.capture.capability;
int capturemode = Stream_Parm.parm.capture.capturemode;
int numerator = Stream_Parm.parm.capture.timeperframe.numerator;
int denominator = Stream_Parm.parm.capture.timeperframe.denominator;
std::cout<<" numerator = "<<numerator<<std::endl;
std::cout<<" denominator = "<<denominator<<std::endl;
std::cout<<" capability = "<<capability<<std::endl; //4096 表示能设置帧率
std::cout<<" capturemode = "<<capturemode<<std::endl; //
struct v4l2_queryctrl Setting;
Setting.id = V4L2_CID_GAIN;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的增益设置
{
printf(" Query GAIN settings failed \n");
}
else
printf(" V4L2_CID_GAIN Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_GAIN;
ctrl.value = 4;
if( -1 == ioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置增益
{
printf(" Set GAIN failed \n");
}
Setting.id = V4L2_CID_EXPOSURE_ABSOLUTE;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的曝光时间
{
printf(" Query EXPOSURE settings failed \n");
}
else
printf(" V4L2_CID_EXPOSURE Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
ctrl.id = V4L2_CID_EXPOSURE_AUTO;
ctrl.value = V4L2_EXPOSURE_MANUAL;
if(-1 == ioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置曝光模式
{
printf(" set EXPOSURE Mode failed\n");
}
ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
ctrl.value = 300;
if( -1 == ioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置曝光时间
{
printf(" set EXPOSURE Time failed\n");
}
Setting.id = V4L2_CID_BRIGHTNESS;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的亮度
{
printf(" Query BRIGHTNESS settings failed \n");
}
else
printf(" V4L2_CID_BRIGHTNESS Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
ctrl.id = V4L2_CID_BRIGHTNESS;
ctrl.value = 0;
if( -1 == ioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置亮度
{
printf("set BRIGHTNESS failed\n");
}
Setting.id = V4L2_CID_CONTRAST;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的对比度
{
printf(" Query CONTRAST settings failed \n");
}
else
printf(" V4L2_CID_CONTRAST Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
ctrl.id = V4L2_CID_CONTRAST;
ctrl.value = 0;
if( -1 == ioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置对比度
{
printf("set BRIGHTNESS failed\n");
}
Setting.id = V4L2_CID_SATURATION;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的饱和度
{
printf(" Query SATURATION settings failed \n");
}
else
printf(" V4L2_CID_SATURATION Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
ctrl.id = V4L2_CID_SATURATION;
ctrl.value = 0;
if( -1 == xioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置饱和度
{
printf("set BRIGHTNESS failed\n");
}
Setting.id = V4L2_CID_HUE;
if(-1 == xioctl(fd, VIDIOC_QUERYCTRL, &Setting)) //查询支持的色彩
{
printf(" Query HUE settings failed \n");
}
else
printf(" V4L2_CID_HUE Setting \n minimum = '%d' maximum = '%d' step = '%d' default_value = '%d' \n",
Setting.minimum, Setting.maximum,Setting.step,Setting.default_value);
ctrl.id = V4L2_CID_HUE;
ctrl.value = 0;
if( -1 == xioctl(fd, VIDIOC_S_CTRL, &ctrl)) //设置色彩
{
printf("set HUE failed\n");
}
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)) { //将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 device\n", dev_name);
exit (EXIT_FAILURE);
}
fd = open (dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);//打开设备 成功返回新分配的文件描述符 可读可写打开 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/O)。
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"
"Options:\n"
"-d | --device name Video device name [/dev/video]\n"
"-h | --help Print this message\n"
"-m | --mmap Use memory mapped buffers\n"
"-r | --read Use read() calls\n"
"-u | --userp Use application allocated buffers\n"
"",
argv[0]);
}
static const char short_options [] = "d:hmru";
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' },
{ 0, 0, 0, 0 }
};
int main (int argc, char ** argv)
{
dev_name = "/dev/video";
for (;;) {
int index;
int c;
c = getopt_long (argc, argv,short_options, long_options,&index);
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;
default:
usage (stderr, argc, argv);
exit (EXIT_FAILURE);
}
}
open_device ();
init_device ();
start_capturing ();
mainloop ();
stop_capturing ();
uninit_device ();
close_device ();
exit (EXIT_SUCCESS);
return 0;
}
V4L2 + opencv3 Linux获取摄像头数据显示 程序源码
最新推荐文章于 2024-04-20 15:03:32 发布