0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入学习Linux摄像头v4l2应用编程

嵌入式应用研究院 来源: 嵌入式应用研究院 2023-11-15 09:28 次阅读

一、什么是v4l2

vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)

V4L2在设计时,是要支持很多广泛的设备的,它们之中只有一部分在本质上是真正的视频设备,可以支持多种设备,它可以有以下几种接口

video capture interface:视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能

video output interface:视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程并将图像从用户空间移动到驱动程序

video overlay interface:视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图像的效率比其他方式高得多

其他接口这里就不介绍了,下面来看一下v4l2的API

二、v4l2 API介绍

对V4L2设备进行编程包括以下步骤

打开设备

更改设备属性,选择视频和音频输入,视频标准,图片亮度等

设置数据格式

设置输入/输出方法

输入/输出缓存队列循环

关闭设备

其中大多数操作都是通过应用层调用ioctl实现的,可以将这些ioctl分为下面几类

2.1 查询设备的功能

由于V4L2涵盖了各种各样的设备,因此并非API的所有方面都适用于所有类型的设备,在使用v4l2设备时,必须调用此API,获得设备支持的功能(capture、output、overlay…)

注:可以点击名称查看API讲解

bede8272-830e-11ee-939d-92fbcf53809c.png

2.2 应用优先级

当多个应用程序共享设备时,可能需要为它们分配不同的优先级。视频录制应用程序可以例如阻止其他应用程序改变视频控制或切换当前的电视频道。另一个目标是允许在后台工作的低优先级应用程序,这些应用程序可以被用户控制的应用程序抢占,并在以后自动重新获得对设备的控制

beed130a-830e-11ee-939d-92fbcf53809c.png

2.3 输入和输出设备

bef6ba0e-830e-11ee-939d-92fbcf53809c.png

2.4 视频标准

bf009f74-830e-11ee-939d-92fbcf53809c.png

2.5 控制属性

bf0a7a94-830e-11ee-939d-92fbcf53809c.png

2.6 图像格式

图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…所以在使用设备时,需要对格式进行设置

bf150964-830e-11ee-939d-92fbcf53809c.png

2.7 图像裁剪、插入与缩放

bf1f801a-830e-11ee-939d-92fbcf53809c.png

2.8 数据的输入和输出

内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP获取设备支持哪种方式

ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference;下面来讲一讲如何使用这些接口

bf29c188-830e-11ee-939d-92fbcf53809c.png

三、v4l2设备操作流程

V4L2支持多种接口:capture(捕获)、output(输出)、overlay(预览)等等 这里讲解如何使用capture功能,下面讲解操作流程

step1:打开设备 在Linux中,视频设备节点为/dev/videox,使用open函数将其打开

intfd=open(name,flag);
if(fd< 0)
{
    printf("ERR(%s):failed to open %s
", __func__, name);
    return -1;
}
return fd;

step 2:查询设备功能

if(ioctl(fd,VIDIOC_QUERYCAP,cap)< 0)
{
    printf("ERR(%s):VIDIOC_QUERYCAP failed
", __func__);
    return -1;
}

看一看v4l2_capability:

structv4l2_capability{
__u8driver[16];/*i.e."bttv"*/
__u8card[32];/*i.e."HauppaugeWinTV"*/
__u8bus_info[32];/*"PCI:"+pci_name(pci_dev)*/
__u32version;/*shoulduseKERNEL_VERSION()*/
__u32capabilities;/*Devicecapabilities*/
__u32reserved[4];
};

其中最重要的是capabilities字段,这个字段标记着v4l2设备的功能,capabilities有以下部分标记位:

bf34211e-830e-11ee-939d-92fbcf53809c.png

我们可以通过这样子去判断设备的功能:

step 3:设置输入设备 一个设备可能有多个输入,比如:在芯片上,摄像头控制器和摄像头接口是分离的,需要选择哪一个摄像头接口作为摄像头控制器的输入源

当然,并不是所有的设备都需要设置输入,比如:uvc摄像头,一般只有一个输入,默认就会选择,不需要设置

下面介绍如何设置输入设备

1.枚举输入设备 下面这段程序枚举了该设备所有的输入源,并打印输入源的名称:

structv4l2_inputinput;

input.index=0;
while(!ioctl(fd,VIDIOC_ENUMINPUT,&input))
{
printf("input:%s
",input.name);
++input.index;
}

2.设置输入设备

structv4l2_inputinput;
input.index=index;//指定输入设备

if(ioctl(fd,VIDIOC_S_INPUT,&input)< 0)
{
    printf("ERR(%s):VIDIOC_S_INPUT failed
", __func__);
    return -1;
}

step 4:设置图像格式 有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置

1.枚举支持的像素格式

structv4l2_fmtdescfmtdesc;

fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index=0;

while(!ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc))
{
printf("fmt:%s
",fmtdesc.description);

fmtdesc.index++;
}

2.设置像素格式

structv4l2_formatv4l2_fmt;

memset(&v4l2_fmt,0,sizeof(structv4l2_format));
v4l2_fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fmt.fmt.pix.width=width;//宽度
v4l2_fmt.fmt.pix.height=height;//高度
v4l2_fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;//像素格式
v4l2_fmt.fmt.pix.field=V4L2_FIELD_ANY;

if(ioctl(fd,VIDIOC_S_FMT,&v4l2_fmt)< 0)
{
    printf("ERR(%s):VIDIOC_S_FMT failed
", __func__);
    return -1;
}

step 5:设置缓存 v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2的返回结果是支持V4L2_CAP_READWRITE还是V4L2_CAP_STREAMING

read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?

streaming就是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断地出队列和入队列的过程,如下图所示:

bf3f4aa8-830e-11ee-939d-92fbcf53809c.png

下面讲解如何去申请和映射缓存:

1.申请缓存

structv4l2_requestbuffersreq;

req.count=nr_bufs;//缓存数量
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;

if(ioctl(fd,VIDIOC_REQBUFS,&req)< 0)
{
    printf("ERR(%s):VIDIOC_REQBUFS failed
", __func__);
    return -1;
}

2.映射缓存 为什么要映射缓存?

因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率

映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子:

structv4l2_bufferv4l2_buffer;
void*addr;

memset(&v4l2_buffer,0,sizeof(structv4l2_buffer));
v4l2_buffer.index=i;//想要查询的缓存
v4l2_buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory=V4L2_MEMORY_MMAP;

/*查询缓存信息*/
ret=ioctl(fd,VIDIOC_QUERYBUF,&v4l2_buffer);
if(ret< 0)
{
    printf("Unable to query buffer.
");
    return -1;
}

/* 映射 */
addr = mmap(NULL /* start anywhere */ ,
            v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, v4l2_buffer.m.offset);

注:需要将所有申请的缓存使用上述方法进行映射

3.将所有的缓存放入队列

structv4l2_bufferv4l2_buffer;

for(i=0;i< nr_bufs; i++)
{
 memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
 v4l2_buffer.index = i; //想要放入队列的缓存
 v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 v4l2_buffer.memory = V4L2_MEMORY_MMAP; 

    ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
    if(ret < 0)
    {
        printf("Unable to queue buffer.
");
        return -1;
    }
}

step 6:打开设备

enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE;

if(ioctl(fd,VIDIOC_STREAMON,&type)< 0)
{
    printf("ERR(%s):VIDIOC_STREAMON failed
", __func__);
    return -1;
}

step 7:读取数据 获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll等待数据准备完成

1.poll

structpollfdpoll_fds[1];

poll_fds[0].fd=fd;
poll_fds[0].events=POLLIN;//等待可读

poll(poll_fds,1,10000);

2.出队列

structv4l2_bufferbuffer;

buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory=V4L2_MEMORY_MMAP;

if(ioctl(fd,VIDIOC_DQBUF,&buffer)< 0)
{
    printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame
", __func__);
    return -1;
}

出队列后得到了缓存的下标buffer.index,然后找到对饮的缓存,通过映射过后的地址进行数据的读取

3.入队列 再数据读取完成后,要将buf重新放入队列中:

structv4l2_bufferv4l2_buf;

v4l2_buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory=V4L2_MEMORY_MMAP;
v4l2_buf.index=i;//指定buf

if(ioctl(fd,VIDIOC_QBUF,&v4l2_buf)< 0)
{
    printf("ERR(%s):VIDIOC_QBUF failed
", __func__);
    return -1;
}

读取数据就是在上面这三步一直不断地循环

step 8:关闭设备

1.关闭设备

enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE;

if(ioctl(fd,VIDIOC_STREAMOFF,&type)< 0)
{
    printf("ERR(%s):VIDIOC_STREAMOFF failed
", __func__);
    return -1;
}

2.取消映射

for(i=0;i< nr_bufs; ++i)
    munmap(buf[i].addr, buf[i]->length);

关闭文件描述符

close(fd);

libv4l2 v4l2设备操作起来还是比较繁琐的,为此我对其进行了封装,写了一套库,使用起来更加方便,可以从这里libv4l2获取

其中附带一个实例example_cature,通过capture /dev/video0运行程序采集一张YUYV格式的图片,采集后得到了pic.yuv,可以通过ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv,效果图如下

四、v4l2采集图像在frame buffer显示

如何将采集图像在frame buff上显示?

1.转换图像格式,将yuv格式转换成frame buff可以接收的rgb格式

2.操作frame buff,通过映射frame buff的显存到用户空间,直接写显存就可以显示图像

具体的实现过程这里就不详细说了,下面给出一个例子。

执行make编译后可以得到video2lcd,执行video2lcd /dev/video0

运行效果如下:

五、v4l2采集图像使用Qt显示

如何使用qt显示,道理跟在frame buff上显示是一样的,都是采集,转化格式,显示,只是在显示部分不同而已,这里给出一个例子。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1362

    浏览量

    40216
  • 接口
    +关注

    关注

    33

    文章

    8486

    浏览量

    150804
  • Linux
    +关注

    关注

    87

    文章

    11219

    浏览量

    208872
  • 摄像头
    +关注

    关注

    59

    文章

    4806

    浏览量

    95389

原文标题:深入学习Linux摄像头v4l2应用编程

文章出处:【微信号:嵌入式应用研究院,微信公众号:嵌入式应用研究院】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux摄像头应用编程

    V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,
    的头像 发表于 08-26 21:39 2779次阅读
    <b class='flag-5'>Linux</b>下<b class='flag-5'>摄像头</b>应用<b class='flag-5'>编程</b>

    百问网全志系列开发板摄像头V4L2编程步骤详解

    7 摄像头V4L2编程 7.1 V4L2简介 ​Video for Linux two(Video4Li
    发表于 08-13 09:45

    v4l2编程经典_

    v4l2编程经典_
    发表于 11-05 12:41

    如何在Raspberry Pi(树莓派)上调用V4L2来操纵摄像头拍照

    如何在Raspberry Pi(树莓派)上调用V4L2来操纵摄像头拍照简单地说,它就是一个基于ARM CPU的、信用卡那么大的迷你计算机。我曾经写过一篇教程,展示了如何调用OpenCV,来控制摄像头
    发表于 06-30 21:39

    【WRTnode2R试用体验】使用V4L2获取摄像头的信息

    V4L2全称是Video for Linux 2,通过它可以驱动摄像头。在Ubuntu中,已经内置了V4L2,因此不需要安装多余的东西。在W
    发表于 10-26 20:36

    运行linuxtv官网的v4l2代码,capture摄像头时select超时怎么解决?

    编译,运行linuxtv官网的v4l2代码,capture 摄像头时select超时,这怎么搞?
    发表于 12-31 06:12

    运行linuxtv官网的v4l2代码,capture摄像头时select超时怎么解决?

    [td]编译,运行linuxtv官网的v4l2代码,capture 摄像头时select超时,这怎么搞?
    发表于 01-14 07:02

    什么是V4L2?有何作用

    1.什么是V4L2Video for(4) Linux 2 的简称,V4L的升级版。V4L2
    发表于 01-17 09:05

    需要了解Linux V4L2的驱动架构

    video4linux2(V4L2)是Linux内核中关于视频设备的中间驱动层,向上为Linux应用程序访问视频设备提供了通用接口,向下为linux
    发表于 04-28 17:29 1115次阅读
    需要了解<b class='flag-5'>Linux</b> <b class='flag-5'>V4L2</b>的驱动架构

    Linux视频设备驱动编程(v4l2编程)

    一.什么是video4linuxVideo4linux2(简称V4L2),是linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一
    发表于 04-02 14:35 887次阅读

    Linux应用开发【第七章】摄像头V4L2编程应用开发

    文章目录 7 摄像头V4L2编程应用开发 7.1 V4L2简介 7.2 V4L2视频采集原理 7.3 V
    的头像 发表于 12-10 19:23 3042次阅读
    <b class='flag-5'>Linux</b>应用开发【第七章】<b class='flag-5'>摄像头</b><b class='flag-5'>V4L2</b><b class='flag-5'>编程</b>应用开发

    V4L2学习笔记

    1.什么是V4L2Video for(4) Linux 2 的简称,V4L的升级版。V4L2
    发表于 01-17 13:43 12次下载
    <b class='flag-5'>V4L2</b><b class='flag-5'>学习</b>笔记

    如何使用v4l2 API读取摄像头

    V4L2是Video For Linux的第二个版本,它是Linux的视频捕获的API。在这里,您可以找到有关的文档。它提供了很方便的c,c++和python接口
    发表于 02-07 11:16 4次下载
    如何使用<b class='flag-5'>v4l2</b> API读取<b class='flag-5'>摄像头</b>

    Linux开发_摄像头编程(实现拍照功能)

    这篇文章主要介绍Linux下UVC免驱摄像头操作流程,介绍V4L2框架、完成摄像头拍照保存为BMP图像到本地,最后贴出了利用CJSON库解析天气预报、北京时间接口返回的数据例子代码(上
    的头像 发表于 09-17 15:34 1774次阅读

    v4l2编程

    v4l2编程
    发表于 10-28 11:10 0次下载