一、概述

Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。

二、编程思路

在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。

而摄像头所用的主要是capature了,视频的捕捉,具体linux的调用可以参考下图。

三、应用程序通过V4L2进行视频采集的原理

V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集,本文重点讨论内存映射方式的视频采集。
应用程序通过V4L2接口采集视频数据分为五个步骤:

  1. 打开视频设备文件,进程视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式。
  2. 申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据。
  3. 将申请到的帧缓冲区在视频采集输入队列排队,,并启动视频采集
  4. 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据
  5. 停止视频采集

具体的程序实现流程可以参考下面的流程图:

其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。

启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

应用程序从视频采集输出队列中取出含有视频塑化剂的帧缓冲区,处理帧缓冲区中的视频数据,如压缩或存储。

最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示:

每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态。

V4L2_BUF_FLAG_UNMAPPED       0B0000
V4L2_BUF_FLAG_MAPPED        0B0001
V4L2_BUF_FLAG_ENQUEUED      0B0010
V4L2_BUF_FLAG_DONE          0B0100

缓冲区的状态转化如图所示。

四、核心命令字和结构体(参见/usr/include/linux/videodev2.h)

一、VIDIOC_ENUM_FMT
含义:枚举出当前摄像头(驱动)所支持的所有数据格式

具体用法如下:

ioctl(fd,VIDIOC_ENUM_FMT,struct v4l2_fmtdesc *argp);

通过迭代结构体struct v4l2_fmtdesc 中的index 成员,来枚举罗列支持的所有格式,该结构体的详细信息如下:

struct v4l2_fmtdesc
{__u32 index; // 数据格式的索引
__u32 type; // 一般设置为 V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 flags;
__u8 description[32];
__u32 pixelformat;
__u32 reserved[4];
};

其中 typev4l2_format中的type设置要一致。在成功调用ioctl之后,description将保存对当前获取的数据格式的描述。

二、VIDIOC_G_FMT / VIDIOC_S_FMT / VIDIOC_TRY_FMT
含义:
1.获取当前摄像头驱动数据格式
2.设置摄像头驱动数据格式
3.尝试设置格式

具体用法:

ioctl(fd,VIDIOC_G_FMT ,struct v4l2_format     *argp)
ioctl(fd,VIDIOC_S_FMT ,struct v4l2_format      *argp)
ioctl(fd,VIDIOC_TRY_FMT,struct v4l2_format  *argp)

涉及数据结构:

struct v4l2_format
{__u32 type;
union
{struct v4l2_pix_format pix;struct v4l2_pix_format_mplane pix_mp;struct v4l2_window win;struct v4l2_vbi_format vbi;struct v4l2_sliced_vbi_format sliced;__u8 raw_data[200];
} fmt;
}

V4l2_format中的fmt是一个union,其中哪个成员有效取决于type的取值,一般较常用的是取类型typeV4L2_BUF_TYPE_VIDEO_CAPTURE,此时pix生效。该成员的详细内部细节如下:

struct v4l2_pix_format
{__u32 width;__u32 height;__u32 pixelformat;__u32 field;__u32 bytesperline;__u32 sizeimage;__u32 colorspace;__u32 priv;
};

该结构体中的成员 pixelformat 代表视频输入驱动所使用的像素格式,常见的有V4L2_PIX_FMT_JPEGV4L2_PIX_FMT_YUVV4L2_PIX_FMT_MJPG等。而成员field代表视频帧传输的方式,选择 V4L2_FIELD_INTERLACED 为交错式。

三、VIDIOC_REQBUFS
含义:向内核申请视频缓存
具体用法如下:

ioctl(fd,VIDIOC_REQBUFS,v4l2_requestbuffers *argp)

该命令字所申请的缓存就是如下图所示的内核中处理视频数据的队列缓存,这些缓存的具体配置参数用如下结构体来指定:

struct v4l2_requestbuffers
{__u32 count; // 申请缓存总个数__u32 type; // 与 struct v4l2_format 中的 type 一致__u32 memory;__u32 reserved[2];
};

其中 memory 的取值为 V4L2_MEMORY_MMAP V4L2_MEMORY_USERPTR,取决于,当该字段被设置为 V4L2_MEMORY_MMAP 时,count 字段才有效。

四、VIDIOC_QUERYBUF
含义:内核成功分配了缓存后,取得这些缓存的具体参数
具体用法如下:

ioctl(fd, VIDIOC_QUERYBUF, v4l2_buffer *argp);

之所以需要取得这些缓存的具体参数的一个目的是,这些缓存都是处在内核空间的,我们并不能直接操作他们,因此需要将他们通过mmap映射到用法空间,这就要求必须知道他们的大小、偏移等信息。这些信息统一被储存到如下结构体中:

struct v4l2_buffer
{__u32 index; // 内核缓存索引号,由用户指定,范围是[0 ~ count-1]__u32 type; // 与 v4l2_format 中的 type 一致__u32 bytesused;__u32 flags;__u32 field;struct timeval timestamp;struct v4l2_timecode timecode;__u32 sequence;__u32 memory; // 与 v4l2_requestbuffers 中的 memory 一致union{__u32 offset; // 缓存相对于设备内存的偏移unsigned long userptr;struct v4l2_plane *planes;__s32 fd;} m;__u32 length; // 缓存大小__u32 reserved2;__u32 reserved;};

五、VIDIOC_QBUF / VIDIOC_DQBUF
含义:
1.使一个空的(视频输入时)或者一个满的(视频输出时)缓存入队
2.使一个满的(视频输入时)或者一个空的(视频输出时)缓存出队

具体用法如下:

ioctl(fd, VIDIOC_QBUF, v4l2_buffer *argp);
ioctl(fd, VIDIOC_DQBUF, v4l2_buffer *argp);


这两个命令字是捕捉视频帧最常用的动作,通过 v4l2_bufferindex 字段,将指定缓存出队或者入队,这里需要澄清的几个要点是:
1、在尚未开启摄像头取像之前,需要将空的缓存一一入队
2、针对视频输入,出队的时候如果缓存没有数据,那么出队将阻塞
3、虽然内核对这些内存的定义时“队列”,但实际上不按顺序“插队”也是可以的,但一般不那么做

六、VIDIOC_STREAMON / VIDIOC_STREAMOFF
含义:
1、开启I/O流
2、关闭I/O流

具体用法如下:

ioctl(fd, VIDIOC_STREAMON, const int *argp);
ioctl(fd, VIDIOC_STREAMOFF, const int *argp);

不管 I/O 方式被设定为内存映射(MMAP)方式还是用户指针(USERPTR)方式,都可以使用 VIDIOC_STREAMONVIDIOC_STREAMOFF 来启停 I/O 流。事实上,在使用 ioctl 调用 VIDIOC_STREAMON 之前,物理硬件将暂时被禁用且没有缓存被填充数据。

VIDIOC_STREAMOFF 除了终止进程的 DMA 操作(如果有的话)之外,还将解锁用户指针指向的物理内存,队列中的所有缓存都将被移除,这意味着如果是视频输入,那么那些没来得及读取的视频帧将被丢弃,如果是视频输出,那么那写没来及传输的视频帧也同样会被丢弃。

五、V4L2 API及数据结构

V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。

1.常用的结构体在内核目录include/linux/videodev2.h中定义

 struct v4l2_requestbuffers      //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_capability          //视频设备的功能,对应命令VIDIOC_QUERYCAP
struct v4l2_input               //视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard            //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format              //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer              //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop                //视频信号矩形边框
struct v4l2_std_id              //视频制式

2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

VIDIOC_REQBUFS                   //分配内存
VIDIOC_QUERYBUF                 //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP                 //查询驱动功能
VIDIOC_ENUM_FMT                 //获取当前驱动支持的视频格式
VIDIOC_S_FMT                    //设置当前驱动的频捕获格式
VIDIOC_G_FMT                    //读取当前驱动的频捕获格式
VIDIOC_TRY_FMT                  //验证当前驱动的显示格式
VIDIOC_CROPCAP                  //查询驱动的修剪能力
VIDIOC_S_CROP                   //设置视频信号的矩形边框
VIDIOC_G_CROP                   //读取视频信号的矩形边框
VIDIOC_QBUF                     //把数据从缓存中读取出来
VIDIOC_DQBUF                    //把数据放回缓存队列
VIDIOC_STREAMON                 //开始视频显示函数
VIDIOC_STREAMOFF                //结束视频显示函数
VIDIOC_QUERYSTD                 //检查当前视频设备支持的标准,例如PAL或NTSC。

3、操作流程
V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。下面列举出一种操作的流程,供参考。

(1)打开设备文件

int fd = open(Devicename,mode);
Devicename:/dev/video0、/dev/video1 ……
Mode:O_RDWR | O_NONBLOCK

如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

(2)获取摄像头设备的基本参数

    struct v4l2_capability cap;bzero(&cap, sizeof(cap));if(ioctl(camfd, VIDIOC_QUERYCAP, &cap) == -1){printf("获取摄像头基本信息失败: %s\n", strerror(errno));exit(0);}printf("驱动:%s\n", cap.driver);printf("显卡:%s\n", cap.card);printf("总线:%s\n", cap.bus_info);printf("版本:%d\n", cap.version);if((cap.capabilities&V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){printf("该设备为视频采集设备\n");}if((cap.capabilities&V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){printf("该设备支持流IO操作\n\n");}

(3)获取摄像头格式信息(固定)

    struct v4l2_fmtdesc fmtdesc;fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;int ret;printf("像素格式: \n");while((ret=ioctl(camfd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0){printf("[%d]", fmtdesc.index);sprintf(formats[fmtdesc.index]+0, "%c", (fmtdesc.pixelformat>>8*0)&0xFF);sprintf(formats[fmtdesc.index]+1, "%c", (fmtdesc.pixelformat>>8*1)&0xFF);sprintf(formats[fmtdesc.index]+2, "%c", (fmtdesc.pixelformat>>8*2)&0xFF);sprintf(formats[fmtdesc.index]+3, "%c", (fmtdesc.pixelformat>>8*3)&0xFF);printf("\"%s\"", formats[fmtdesc.index]);printf("(详细描述: %s)\n", fmtdesc.description);fmtdesc.index++;}

(4)获取摄像头格式信息(可调)

   struct v4l2_format  fmt;bzero(&fmt, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(ioctl(camfd, VIDIOC_G_FMT, &fmt) == -1){printf("获取摄像头格式信息失败: %s\n", strerror(errno));exit(0);}printf("分辨率: %d×%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);printf("像素格式: ");switch(fmt.fmt.pix.pixelformat){case V4L2_PIX_FMT_MJPEG:printf("V4L2_PIX_FMT_MJPEG\n");break;case V4L2_PIX_FMT_JPEG:printf("V4L2_PIX_FMT_JPEG\n");break;case V4L2_PIX_FMT_MPEG:printf("V4L2_PIX_FMT_MPEG\n");break;case V4L2_PIX_FMT_MPEG1:printf("V4L2_PIX_FMT_MPEG1\n");break;case V4L2_PIX_FMT_MPEG2:printf("V4L2_PIX_FMT_MPEG2\n");break;case V4L2_PIX_FMT_MPEG4:printf("V4L2_PIX_FMT_MPEG4\n");break;case V4L2_PIX_FMT_H264:printf("V4L2_PIX_FMT_H264\n");break;case V4L2_PIX_FMT_XVID:printf("V4L2_PIX_FMT_XVID\n");break;case V4L2_PIX_FMT_RGB24:printf("V4L2_PIX_FMT_RGB24\n");break;case V4L2_PIX_FMT_BGR24:printf("V4L2_PIX_FMT_BGR24\n");break;case V4L2_PIX_FMT_YUYV:printf("V4L2_PIX_FMT_YUYV\n");break;case V4L2_PIX_FMT_YYUV:printf("V4L2_PIX_FMT_YYUV\n");break;case V4L2_PIX_FMT_YVYU:printf("V4L2_PIX_FMT_YVYU\n");break;case V4L2_PIX_FMT_YUV444:printf("V4L2_PIX_FMT_YUV444\n");break;case V4L2_PIX_FMT_YUV410:printf("V4L2_PIX_FMT_YUV410\n");break;case V4L2_PIX_FMT_YUV420:printf("V4L2_PIX_FMT_YUV420\n");break;case V4L2_PIX_FMT_YVU420:printf("V4L2_PIX_FMT_YVU420\n");break;case V4L2_PIX_FMT_YUV422P:printf("V4L2_PIX_FMT_YUV422P\n");break;default:printf("未知\n");}

(5)配置摄像头像素格式

   struct v4l2_format *tmp = calloc(1, sizeof(*tmp));bzero(tmp, sizeof(*tmp));tmp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tmp->fmt.pix.width  = 800;tmp->fmt.pix.height = 448;printf("\n请选择要配置的像素格式:");int n; scanf("%d", &n);if(!strcmp(formats[n], "JPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;else if(!strcmp(formats[n], "MJPG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;else if(!strcmp(formats[n], "MPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;else if(!strcmp(formats[n], "YUYV")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;else if(!strcmp(formats[n], "YVYU")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YVYU;else if(!strcmp(formats[n], "H264")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_H264;else{printf("对不起,所选格式无法配置.\n");exit(0);} tmp->fmt.pix.field = V4L2_FIELD_INTERLACED;if(ioctl(camfd, VIDIOC_S_FMT, tmp) == -1){printf("ioctl() VIDIOC_S_FMT 失败了: %s\n", strerror(errno));}

(6)向驱动申请帧缓存

   int nbuf = 3;struct v4l2_requestbuffers reqbuf;bzero(&reqbuf, sizeof (reqbuf));reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;reqbuf.count = nbuf;// 使用该参数reqbuf来申请缓存ioctl(camfd, VIDIOC_REQBUFS, &reqbuf);
}

v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。

(7)获取每个缓存的信息,并mmap到用户空间

    // 根据刚刚设置的reqbuf.count的值,来定义相应数量的struct v4l2_buffer// 每一个struct v4l2_buffer对应内核摄像头驱动中的一个缓存struct v4l2_buffer buffer[nbuf];int length[nbuf];uint8_t *start[nbuf];for(int i=0; i<nbuf; i++){bzero(&buffer[i], sizeof(buffer[i]));buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buffer[i].memory = V4L2_MEMORY_MMAP;buffer[i].index = i;ioctl(camfd, VIDIOC_QUERYBUF, &buffer[i]);length[i] = buffer[i].length;start[i] = mmap(NULL, buffer[i].length, PROT_READ | PROT_WRITE,MAP_SHARED,  camfd, buffer[i].m.offset);ioctl(camfd , VIDIOC_QBUF, &buffer[i]);}


(8)启动摄像头数据采集

    enum v4l2_buf_type vtype= V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(camfd, VIDIOC_STREAMON, &vtype);

(9)取出FIFO缓存中已经采样的帧缓存

    struct v4l2_buffer v4lbuf;bzero(&v4lbuf, sizeof(v4lbuf));v4lbuf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4lbuf.memory= V4L2_MEMORY_MMAP;// 从队列中取出填满数据的缓存v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_DQBUF, &v4lbuf);display(start[i%nbuf]);    //显示取出数据的缓存

根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。

(10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

// 将已经读取过数据的缓存块重新置入队列中v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_QBUF, &v4lbuf);}

(11)停止视频的采集

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

(12)关闭视频设备

close(fd);

六、YUV格式转RGB

点击查看详细的信息

七、摄像头快速编程

caminfo.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>#include <linux/fb.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#include <sys/ioctl.h>#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <setjmp.h>char formats[5][16] = {0};
struct v4l2_fmtdesc fmtdesc;
struct v4l2_format  fmt;
struct v4l2_capability cap;// 获取摄像头格式信息(固定)
void get_caminfo(int camfd)
{fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;int ret;printf("像素格式: \n");while((ret=ioctl(camfd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0){printf("[%d]", fmtdesc.index);sprintf(formats[fmtdesc.index]+0, "%c", (fmtdesc.pixelformat>>8*0)&0xFF);sprintf(formats[fmtdesc.index]+1, "%c", (fmtdesc.pixelformat>>8*1)&0xFF);sprintf(formats[fmtdesc.index]+2, "%c", (fmtdesc.pixelformat>>8*2)&0xFF);sprintf(formats[fmtdesc.index]+3, "%c", (fmtdesc.pixelformat>>8*3)&0xFF);printf("\"%s\"", formats[fmtdesc.index]);printf("(详细描述: %s)\n", fmtdesc.description);fmtdesc.index++;}
}// 获取摄像头格式信息(可调)
void get_camfmt(int camfd)
{bzero(&fmt, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(ioctl(camfd, VIDIOC_G_FMT, &fmt) == -1){printf("获取摄像头格式信息失败: %s\n", strerror(errno));exit(0);}printf("分辨率: %d×%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);printf("像素格式: ");switch(fmt.fmt.pix.pixelformat){case V4L2_PIX_FMT_MJPEG:printf("V4L2_PIX_FMT_MJPEG\n");break;case V4L2_PIX_FMT_JPEG:printf("V4L2_PIX_FMT_JPEG\n");break;case V4L2_PIX_FMT_MPEG:printf("V4L2_PIX_FMT_MPEG\n");break;case V4L2_PIX_FMT_MPEG1:printf("V4L2_PIX_FMT_MPEG1\n");break;case V4L2_PIX_FMT_MPEG2:printf("V4L2_PIX_FMT_MPEG2\n");break;case V4L2_PIX_FMT_MPEG4:printf("V4L2_PIX_FMT_MPEG4\n");break;case V4L2_PIX_FMT_H264:printf("V4L2_PIX_FMT_H264\n");break;case V4L2_PIX_FMT_XVID:printf("V4L2_PIX_FMT_XVID\n");break;case V4L2_PIX_FMT_RGB24:printf("V4L2_PIX_FMT_RGB24\n");break;case V4L2_PIX_FMT_BGR24:printf("V4L2_PIX_FMT_BGR24\n");break;case V4L2_PIX_FMT_YUYV:printf("V4L2_PIX_FMT_YUYV\n");break;case V4L2_PIX_FMT_YYUV:printf("V4L2_PIX_FMT_YYUV\n");break;case V4L2_PIX_FMT_YVYU:printf("V4L2_PIX_FMT_YVYU\n");break;case V4L2_PIX_FMT_YUV444:printf("V4L2_PIX_FMT_YUV444\n");break;case V4L2_PIX_FMT_YUV410:printf("V4L2_PIX_FMT_YUV410\n");break;case V4L2_PIX_FMT_YUV420:printf("V4L2_PIX_FMT_YUV420\n");break;case V4L2_PIX_FMT_YVU420:printf("V4L2_PIX_FMT_YVU420\n");break;case V4L2_PIX_FMT_YUV422P:printf("V4L2_PIX_FMT_YUV422P\n");break;default:printf("未知\n");}
}// 获取摄像头设备的基本参数
void get_camcap(int camfd)
{bzero(&cap, sizeof(cap));if(ioctl(camfd, VIDIOC_QUERYCAP, &cap) == -1){printf("获取摄像头基本信息失败: %s\n", strerror(errno));exit(0);}printf("驱动:%s\n", cap.driver);printf("显卡:%s\n", cap.card);printf("总线:%s\n", cap.bus_info);printf("版本:%d\n", cap.version);if((cap.capabilities&V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){printf("该设备为视频采集设备\n");}if((cap.capabilities&V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){printf("该设备支持流IO操作\n\n");}
}// 配置摄像头像素格式
void set_camfmt(int camfd)
{struct v4l2_format *tmp = calloc(1, sizeof(*tmp));bzero(tmp, sizeof(*tmp));tmp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tmp->fmt.pix.width  = 800;tmp->fmt.pix.height = 448;printf("\n请选择要配置的像素格式:");int n; scanf("%d", &n);if(!strcmp(formats[n], "JPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;else if(!strcmp(formats[n], "MJPG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;else if(!strcmp(formats[n], "MPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;else if(!strcmp(formats[n], "YUYV")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;else if(!strcmp(formats[n], "YVYU")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YVYU;else if(!strcmp(formats[n], "H264")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_H264;else{printf("对不起,所选格式无法配置.\n");exit(0);}  tmp->fmt.pix.field = V4L2_FIELD_INTERLACED;if(ioctl(camfd, VIDIOC_S_FMT, tmp) == -1){printf("ioctl() VIDIOC_S_FMT 失败了: %s\n", strerror(errno));}
}

common.h

///#ifndef __COMMON_H
#define __COMMON_H#include <stdio.h>
#include <stdint.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>#include <linux/fb.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#include <sys/ioctl.h>#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <setjmp.h>
#include <pthread.h>
#include <semaphore.h>extern char formats[5][16];
extern struct v4l2_fmtdesc fmtdesc;
extern struct v4l2_format  fmt;
extern struct v4l2_capabilities cap;// 获取摄像头格式信息(固定)
void get_caminfo(int camfd);// 获取/设置摄像头格式信息(可调)
void get_camfmt(int camfd);
void set_camfmt(int camfd, char *pixfmt);// 获取摄像头设备的基本参数
void get_camcap(int camfd);#define MIN(a, b) \({ \typeof(a) _a = a; \typeof(b) _b = b; \(void)(&_a==&_b); \_a < _b ? _a : _b;\})#endif

main.c

///#include "common.h"#define SCREENSIZE 800*480*4#define MIN(a, b) \({ \typeof(a) _a = a; \typeof(b) _b = b; \(void)(&_a==&_b); \_a < _b ? _a : _b; \})int redoffset  ;
int greenoffset;
int blueoffset ;int lcd;
struct fb_var_screeninfo lcdinfo;
uint8_t *fb;int SCREEN_W, SCREEN_H;
int CAMERA_W, CAMERA_H;int R[256][256];
int G[256][256][256];
int B[256][256];sem_t s;void *convert(void *arg)
{/*******************************R = Y + 1.042*(V-128);G = Y - 0.344*(U-128)-0.714*(V-128);B = Y + 1.772*(U-128);*******************************/pthread_detach(pthread_self());for(int i=0; i<256; i++){for(int j=0; j<256; j++){R[i][j] = i + 1.042*(j-128);R[i][j] = R[i][j]>255 ? 255 : R[i][j];R[i][j] = R[i][j]<0   ? 0   : R[i][j];B[i][j] = i + 1.772*(j-128);B[i][j] = B[i][j]>255 ? 255 : B[i][j];B[i][j] = B[i][j]<0   ? 0   : B[i][j];for(int k=0; k<256; k++){G[i][j][k] = i - 0.344*(j-128)-0.714*(k-128);G[i][j][k] = G[i][j][k]>255 ? 255 : G[i][j][k];G[i][j][k] = G[i][j][k]<0   ? 0   : G[i][j][k];}}}sem_post(&s);
}void display(uint8_t *yuv)
{static uint32_t shown = 0;int R0, G0, B0;int R1, G1, B1;uint8_t Y0, U;uint8_t Y1, V;int w = MIN(SCREEN_W, CAMERA_W);int h = MIN(SCREEN_H, CAMERA_H);// 画显存之前,先把LCD移动到不可见区域lcdinfo.xoffset = 0;lcdinfo.yoffset = 480 * ((shown+1)%2);ioctl(lcd, FBIOPAN_DISPLAY, &lcdinfo);uint8_t *fbtmp = fb;fbtmp += (shown%2) * SCREENSIZE;int yuv_offset, lcd_offset;for(int y=0; y<h; y++){for(int x=0; x<w; x+=2){yuv_offset = ( CAMERA_W*y + x ) * 2;lcd_offset = ( SCREEN_W*y + x ) * 4;Y0 = *(yuv + yuv_offset + 0);U  = *(yuv + yuv_offset + 1);Y1 = *(yuv + yuv_offset + 2);V  = *(yuv + yuv_offset + 3);*(fbtmp + lcd_offset + redoffset  +0) = R[Y0][V];*(fbtmp + lcd_offset + greenoffset+0) = G[Y0][U][V];*(fbtmp + lcd_offset + blueoffset +0) = B[Y0][U];*(fbtmp + lcd_offset + redoffset  +4) = R[Y1][V];*(fbtmp + lcd_offset + greenoffset+4) = G[Y1][U][V];*(fbtmp + lcd_offset + blueoffset +4) = B[Y1][U];}}shown++;
}void usage(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s </dev/videoX>\n", argv[0]);exit(0);}
}int main(int argc, char *argv[])
{usage(argc, argv);sem_init(&s, 0, 0);// 打开LCD设备lcd = open("/dev/fb0", O_RDWR);if(lcd == -1){perror("open \"/dev/fb0\" failed");exit(0);}// 获取LCD显示器的设备参数ioctl(lcd, FBIOGET_VSCREENINFO, &lcdinfo);SCREEN_W = lcdinfo.xres;SCREEN_H = lcdinfo.yres;fb = mmap(NULL, lcdinfo.xres* lcdinfo.yres_virtual* lcdinfo.bits_per_pixel/8,PROT_READ | PROT_WRITE, MAP_SHARED, lcd, 0);if(fb == MAP_FAILED){perror("mmap failed");exit(0);}// 清屏bzero(fb, 2 * lcdinfo.xres * lcdinfo.yres * 4);// 获取RGB偏移量redoffset  = lcdinfo.red.offset/8;greenoffset= lcdinfo.green.offset/8;blueoffset = lcdinfo.blue.offset/8;lcdinfo.xoffset = 0;lcdinfo.yoffset = 0;ioctl(lcd, FBIOPAN_DISPLAY, &lcdinfo);// ************************************************** //// 准备好YUV-RGB映射表pthread_t tid;pthread_create(&tid, NULL, convert, NULL);// 打开摄像头设备文件int camfd = open(argv[1],O_RDWR);if(camfd == -1){printf("open %s faield: %s\n", argv[1], strerror(errno));exit(0);}printf("\n摄像头的基本参数:\n");get_camcap(camfd);get_camfmt(camfd);get_caminfo(camfd);// 配置摄像头的采集格式set_camfmt(camfd, "YUYV");get_camfmt(camfd);CAMERA_W = fmt.fmt.pix.width;CAMERA_H = fmt.fmt.pix.height;// 设置即将要申请的摄像头缓存的参数int nbuf = 3;struct v4l2_requestbuffers reqbuf;bzero(&reqbuf, sizeof (reqbuf));reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;reqbuf.count = nbuf;// 使用该参数reqbuf来申请缓存ioctl(camfd, VIDIOC_REQBUFS, &reqbuf);// 根据刚刚设置的reqbuf.count的值,来定义相应数量的struct v4l2_buffer// 每一个struct v4l2_buffer对应内核摄像头驱动中的一个缓存struct v4l2_buffer buffer[nbuf];int length[nbuf];uint8_t *start[nbuf];for(int i=0; i<nbuf; i++){bzero(&buffer[i], sizeof(buffer[i]));buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buffer[i].memory = V4L2_MEMORY_MMAP;buffer[i].index = i;ioctl(camfd, VIDIOC_QUERYBUF, &buffer[i]);length[i] = buffer[i].length;start[i] = mmap(NULL, buffer[i].length,   PROT_READ | PROT_WRITE,MAP_SHARED,  camfd, buffer[i].m.offset);ioctl(camfd , VIDIOC_QBUF, &buffer[i]);}// 启动摄像头数据采集enum v4l2_buf_type vtype= V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(camfd, VIDIOC_STREAMON, &vtype);struct v4l2_buffer v4lbuf;bzero(&v4lbuf, sizeof(v4lbuf));v4lbuf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4lbuf.memory= V4L2_MEMORY_MMAP;// 开始抓取摄像头数据并在屏幕播放视频sem_wait(&s);int i=0;while(1){// 从队列中取出填满数据的缓存v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_DQBUF, &v4lbuf);display(start[i%nbuf]);// 将已经读取过数据的缓存块重新置入队列中v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_QBUF, &v4lbuf);i++;}return 0;
}

通用Makefile


CROSS_COMPILE ?=arm-none-linux-gnueabi-
AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nmSTRIP      = $(CROSS_COMPILE)strip
OBJCOPY     = $(CROSS_COMPILE)objcopy
OBJDUMP     = $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g
CFLAGS += LDFLAGS := -lpthreadexport CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := testobj-y += main.o
obj-y += caminfo.oall : start_recursive_build $(TARGET)@echo $(TARGET) has been built!start_recursive_build:make -C ./ -f $(TOPDIR)/Makefile.build$(TARGET) : built-in.o$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)distclean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)

八、实验效果

[root@GEC6818 /mnt/v4l2]#./test  /dev/video7摄像头的基本参数:
驱动:uvcvideo
显卡:USB2.0 PC CAMERA
总线:usb-nxp-ehci-1.3
版本:197671
该设备为视频采集设备
该设备支持流IO操作分辨率: 640×480
像素格式: V4L2_PIX_FMT_YUYV
像素格式:
[0]"YUYV"(详细描述: YUV 4:2:2 (YUYV))请选择要配置的像素格式:0
分辨率: 640×480
像素格式: V4L2_PIX_FMT_YUYV


参考博文:
原文地址

和菜鸟一起学linux之V4L2摄像头应用流程相关推荐

  1. 和菜鸟一起学linux之V4L2摄像头应用流程【转】

    转自:http://blog.csdn.net/eastmoon502136/article/details/8190262/ 上篇文章,知道了,C代码编译后存放在内存中的位置,那么C代码的整个编译过 ...

  2. 我们一起学linux之V4L2摄像头应用流程

    一.概述 Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备 ...

  3. linux之V4L2摄像头应用流程

    对于v4l2,上次是在调试收音机驱动的时候用过,其他也就只是用i2c配置一些寄存器就可以了.那时只是粗粗的了解了,把收音机当作v4l2的设备后会在/dev目录下生成一个radio的节点.然后就可以操作 ...

  4. C语言高级应用---操作linux下V4L2摄像头应用程序

    目录(?)[-]采集方式V4L2操作流程点击这个网址说得很详细了这里不多说httpbaikebaiducomview5494174htm我们都知道,想要驱动Linux下的摄像头,其实很简单,照着V4L ...

  5. 和菜鸟一起学linux内核源码之启动篇

    又是一个周末,日子过得比较散,虽然期间也有不断地看书学习,总觉得有点小盲目.想想毕业也快要1年了,从事嵌入式linux的研发工作也1年多了.这1年多的从实习到正式工作到现在的自己,进步有,也很大,但是 ...

  6. 和菜鸟一起学linux内核源码之基础准备篇

    注:以下大部分内容摘自Linux内核编程入门篇和linux内核完全注释 在工作的这段时间,发现我的visio画图熟悉了点点,总喜欢把什么源码啊,结构啊之类的就当作流程图来画来理解,因为对于图,有一个很 ...

  7. 和菜鸟一起学linux之DBUS基础学习记录

    转自:http://blog.csdn.net/eastmoon502136/article/details/10044993 D-Bus三层架构 D-Bus是一个为应用程序间通信的消息总线系统, 用 ...

  8. 和菜鸟一起学linux之bluez学习记录2

    这里主要摘取对于hci,l2cap,sdp和rfcomm的一些应用编程. 关于hci 一.HCI层协议概述 1.HCI Command Packets 详见bluez源码:lib/hci.h /* L ...

  9. 和菜鸟一起学linux总线驱动之初识spi驱动数据传输流程【转】

    转自:http://blog.csdn.net/eastmoon502136/article/details/7921846 对于SPI的一些结构体都有所了解之后呢,那么再去瞧瞧SPI的那些长见的操作 ...

最新文章

  1. Android性能优化——使用 APK Analyzer 分析你的 APK
  2. 循环神经网络应用案例
  3. XiaoKL学Python(C)__future__
  4. 正则表达式的运算符优先级
  5. Express+Socket.IO 实现简易聊天室
  6. 海量图片去重算法-局部分块Hash算法
  7. 7x android 8,内测开启 华为荣耀畅玩7X升级Android 8.0
  8. Ubuntu18.04安装和卸载teamviewer
  9. 马士兵oracle视频教程笔记
  10. mx350显卡天梯图_不可错过的2020显卡天梯图,选卡详解
  11. 菲律宾 软件测试,一个中国学生,2个月的菲律宾游学失败经历自白
  12. 编写程序,求柱体的体积:
  13. 小程序左滑删除,可上下滑动
  14. mysql 索引选择原则 07
  15. java迭代器遍历json,批量替换内容
  16. 【oracle】oracle11g 搭建
  17. 区块链学习2-合约开发
  18. Spring Cloud Alibaba Sentinel - - > 容错机制
  19. MySQL 数据表主键设计,选择自增 id 还是 UUID 还是雪花 id?
  20. 一起学Pandas系列基础篇---loc和iloc

热门文章

  1. 【selenoid】selenoid的配置、安装、使用、Docker + Selenoid 搭建Selenium UI自动化运行环境、Docker中的Selenium、Selenium Grid
  2. iOS-iPad强制竖屏
  3. HCIA(计算机网络概念、网络协议模型意义)
  4. CoInitialize函数的使用注意
  5. 对于每天在微信群发类似早上好、祝福的一类人,你会怎么处理?
  6. python 获取硬盘信息失败请谨慎操作_【裸机装系统】获取硬盘信息失败,请谨慎操作!(示例代码)...
  7. JavaScript的历史价值
  8. Ka-CHOCO国产士力架的测评
  9. python文字游戏循环3次_文字游戏 用and 只能回答三次,满足需求的了但是次数的计算我没搞明白!...
  10. kbmMW均衡负载与容灾(2)(转载红鱼儿)