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

    文章

    1309

    浏览量

    39862
  • 接口
    +关注

    关注

    33

    文章

    7644

    浏览量

    148520
  • Linux
    +关注

    关注

    87

    文章

    10992

    浏览量

    206742
  • 摄像头
    +关注

    关注

    59

    文章

    4612

    浏览量

    92909

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

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

收藏 人收藏

    评论

    相关推荐

    Linux摄像头应用编程

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

    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

    CSI摄像头接口及在英创主板上的应用

    加载相应的驱动,加载驱动后会自动生成设备节点:“/dev/video0",应用程序可以操作该设备节点对摄像头进行图像的采集和控制。  CSI摄像头都是用了V4L2驱动提供的标准API来操作
    发表于 10-20 13:36

    【100ASK_IMX6ULL(带屏) 开发板试用体验】基于samba v4l2库和libjpeg远程摄像头图像读取

    的特点是生成的文件小,需要使用CPU调用libjpeg软件编解码库占用资源,在不使用多线程并发操作的前提下,保存一张图片会有毫秒级的延迟,我实测,使用v4l2库抓取摄像头换成并保存成jpg文件,间隔为
    发表于 11-07 16:33

    香橙派OrangePi PC Plus开发板连接5MP摄像头测试

    排线接到开发板的 CIS 摄像头接口上,接好摄 像后再启动 linux 系统(不要在上电后再插入摄像头)3) 进入系统后查看下 ov5640 4
    发表于 05-20 15:34

    全志H2芯片香橙派Zero开发板连接USB摄像头的使用方法

    使用 lsmod 查看系统是否自动加载了 uvcvideo 内核模块3) 然后通过 v4l2-ctl(注意 v4l2 中的 l 是小写字母 l,不是数字 1)命令查看下 USB
    发表于 10-28 17:23

    全志H5芯片开发板香橙派PC2Linux系统下连接USB摄像头的使用方法

    v4l2 中的 l 是小写字母 l,不是数字 1)命令查看下 USB 摄像头的设备节点,从下面的输出可知 USB 摄像头对应的设备节点为/
    发表于 11-16 11:41

    运行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

    【米尔-STM32MP135开发板-入门级MPU试用】UVC摄像头基于V4L2拍照

    基于V4L2使用usb摄像头(UVC)拍照。 1 查看内核对USB摄像头 当插入UVC摄像头就会以下打印信息。 在dev目录下也会有相应的设备。 如果插入多个
    发表于 08-12 13:47

    【ELF 1开发板试用】板载资源测试3:OV5640 摄像头测试

    )OV5640 摄像头在开发板的位置如图。 (2)命令行执行v4l2-ctl-d /dev/video0 --all,显示摄像头参数(如下图)。这里,
    发表于 12-15 22:49

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

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