Ubutun 安装V4L库
sudo apt-get install libv4l-dev
直接使用如上命令即可安装。
V4L2介绍 V4L与V4L2的区别
V4L是 Video for Linux的缩写,它是Linux 内核中关于视频设备的子系统,它为linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。
由于早期的 V4L 有很多缺陷,Bill Dirks 等人对其进行了重新设计,并取名为Video for Linux 2(V4L2使用),最早出现于Linux2.5.x 版本。V4L2 相比于V4L 有更好的扩展性和灵活性,并且支持的硬件设备更多。
因此在应用程序V4L编程实际是指v4l2,我们这个系列的以V4L2为主,但由于历史的原因,V4L2一般兼容V4L.所以很多程序可以用V4L接口。
V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。
V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备:
Video capture device : 从摄像头等设备上获取视频数据。对很多人来讲,video capture是V4L2的基本应用。设备名称为/dev/video,主设备号81,子设备号0~63
Video output device : 将视频数据编码为模拟信号输出。与video capture设备名相同。
Video overlay device : 将同步锁相视频数据(如TV)转换为VGA信号,或者将抓取的视频数据直接存放到视频卡的显存中。
Video output overlay device :也被称为OSD(On-Screen Display)
VBI device : 提供对VBI(Vertical Blanking Interval)数据的控制,发送VBI数据或抓取VBI数据。设备名/dev/vbi0~vbi31,主设备号81,子设备号224~255
Radio device : FM/AM发送和接收设备。设备名/dev/radio0~radio63,主设备号81,子设备号64~127
V4L2应用层
V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。
可以将这些ioctl分类如下:
Query Capability:查询设备支持的功能,只有VIDIOC_QUERY_CAP一个。
优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。
capture相关:视频捕获相关Ioctl。
V4L2采集需要使用的ID
接下来我们就开始写代码,开始使用V4L2采集摄像头数据。
一、重写ioctl函数
这样保证设置IO不会因为中断而停止,并且保证了IO设置。
static inline int camera_ioctl(int fd, int request, void *arg)
{
int r = -1;
do
{
r = ioctl(fd, request, arg);
} while (r < 0 && EINTR == errno);
return r;
}
二、打开文件
在linux下‘一点哲学’,所以我们此时需要去打开摄像头对应虚拟化后的文件(注意,要将虚拟机的摄像头连接上,在虚拟机->可移动设备->USB Camera 断开与主机的连接)此时可以在/dev下找到video0(针对不同主机对应可能为video1、video0);
int open_camear_device(const char *path)
{
int fd = open(path, O_RDWR, 0);
if (-1 == fd)
{
perror("Open camera device:");
return -1;
}
return fd;
}
三、我们需要初始化虚拟设备
//初始化
int init_camera(int fd)
{
//检查摄像头设备,获取信息。
v4l2_capability cap;
if (-1 == camera_ioctl(fd, VIDIOC_QUERYCAP, &cap))
{
perror("ictol cap!");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
fprintf(stderr, "is no video capture device\n");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
fprintf(stderr, "does not support streaming i/o\n");
return -1;
}
//打印摄像头相关信息
printf("\nVIDOOC_QUERYCAP\n");
printf("the camera driver is %s\n", cap.driver);
printf("the camera card is %s\n", cap.card);
printf("the camera bus info is %s\n", cap.bus_info);
printf("the version is %d\n", cap.version);
//摄像头所支持的像素格式
v4l2_fmtdesc fmtdesc;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
printf("Support format:\n");
while (camera_ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)!= -1)
{
printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
fmtdesc.index++;
}
//设置像素格式
v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //yuv422
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; //隔行扫描
//应用设置格式
if (camera_ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
printf("VIDIOC_S_FMT\n");
return -1;
}
//读出设置查看是否设置格式成功
if (camera_ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) {
printf("VIDIOC_S_FMT\n");
return -1;
}
printf("fmt.type:\t\t%d\n",fmt.type);
printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
//设置fps
struct v4l2_streamparm parm;
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 10;
parm.parm.capture.timeperframe.denominator = 10;
if (ioctl(fd, VIDIOC_S_PARM, &parm) == -1)
{
perror("VIDIOC_S_PARM failed\n");
return false;
}
printf("init camera success!\n");
init_memm(fd);
}
四、虚拟化缓存,创建虚拟缓存映射到用户态
这个时候我们需要自定义一个用户态缓存的结构体
typedef struct __video_buffer
{
void *start;
size_t length;
} video_buf_t;
然后开始虚拟缓存,建立映射
int init_memm(int fd)
{
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
memset(&req, 0, sizeof(struct v4l2_requestbuffers));
memset(&buf, 0, sizeof(struct v4l2_buffer));
req.count = 4;
req.memory = V4L2_MEMORY_MMAP;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//分配内存
if (camera_ioctl(fd, VIDIOC_REQBUFS, &req) == -1)
{
perror("fail to request buffer");
}
if (req.count < 2) {
printf("Insufficient buffer memory\n");
return -1;
}
cout << "frame buffer request done" << endl;
//分配用户态内存
framebuf = (video_buf_t *)calloc(req.count, sizeof(video_buf_t));
if(!framebuf)
{
printf("frambuf err calloc");
return -1;
}
for (int i = 0; i < req.count; i++)
{
buf.index = i;
//回收缓冲
if (camera_ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1)
{
perror("VIDIOC_QUERYBUF failed!\n");
return -1;
}
//mmap buffer
framebuf[i].length = buf.length;
framebuf[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
if (framebuf[i].start == MAP_FAILED)
{
perror("mmap failed!\n");
return -1;
}
}
cout << "init memm success" << endl;
}
五、设置IO开始采集
注意在开始采集之前我们需要将缓存回收,然后开始采集,此时已经设置IO开始采集了,之后我们只需要从缓存中读取数据即可
int start_capture(int fd)
{
unsigned int i;
v4l2_buffer buf;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//放回缓冲区
for (i = 0; i < 4; i++)
{
buf.index = i;
if (camera_ioctl(fd, VIDIOC_QBUF, &buf) < 0)
{
perror("VIDIOC_DQBUF err\n");
return -1;
}
}
//开始采集
if (camera_ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
printf("VIDIOC_STREAMON\n");
return -1;
}
cout << "satart capture....." << endl;
}
六、读取采集好的数据
在这个过程中,process_data 代表你自己得到采集的数据需要如何处理,我就不列举出来了。你可以对其进行RGB转换显示成图片。注意这是读取一帧数据,如果需要一直读取视频,则需要进行while循环,即数据为framebuf[buf.index].start
长度为buf.length;
int read_frame(int fd, int fd_yuv)
{
v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (camera_ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
return -1;
}
}
//处理数据
process_data(fd_yuv, (unsigned char *)framebuf[buf.index].start, buf.length);
//放回缓冲
if (camera_ioctl(fd, VIDIOC_QBUF, &buf) < 0)
{
printf("VIDIOC_DQBUF ERROR\n");
return -1;
}
printf("read frame done\n");
}
七、收尾工作,取消缓存、停止采集
int camera_uninit(int fd)
{
unsigned int i;
for (i = 0; i < 4; i++)
{
if (munmap(framebuf[i].start, framebuf[i].length) < 0)
{
printf("munmap\n");
break;
}
}
printf("ummp buf....\n");
free(framebuf);
return 1;
}
int stop_caputer(int fd)
{
//停止采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (camera_ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
perror("VIDIOC_STREAMOFF");
}
printf("Stop caputer...\n");
return -1;
}
这样整个采集过程就完成了