风雨者 · 2022年11月07日 · 陕西

EAIDK-610公开教程(接口编程、数据采集、视觉分析)c/c++版

公开课简介

详细介绍EAIDK-610产品基本功能,硬件特性,软件特点及软件调试操作方法,旨在帮助开发人员更快、更好熟悉EAIDK-610硬件平台(EAIDK-610)和使用EAIDK-610开发套件,并使用EAIDK开发AIoT应用。

  1. 原生c/c++通用接口控制编程(GPIO、SPI、IIC、USB等),如何使用IO接口采集不同类型传感器数据,及进行数据分析处理;
  2. Linux标准视频驱动V4L2介绍,进行视频采集、及图像基本处理;
  3. 音频数据采集与处理。

c/c++公开课内容涵盖Python公开课内容,增加了Linux视频设备驱动的框架V4l2(Video for Linux 2)接口规范的介绍和使用。提供基于EAIDK-610的视频采集源代码,可完整熟悉Linux环境下使用V4L2处理图片及视频。同时V4L2也是OpenCV媒体处理的底层标准API。

V4L2介绍

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

V4L2规范中不仅定义了通用API元素(Common API Elements),图像的格式(Image Formats),输入/输出方法(Input/Output),还定义了Linux内核驱动处理视频信息的一系列接口(Interfaces),这些接口主要有:

  1. 视频采集接口——Video Capture Interface;
  2. 视频输出接口—— Video Output Interface;
  3. 视频覆盖/预览接口——Video Overlay Interface;
  4. 视频输出覆盖接口——Video Output Overlay Interface;
  5. 编解码接口——Codec Interface。
    在应用程序获取视频数据的流程中,都是通过 ioctl 命令与驱动程序进行交互,常见的 ioctl 命令有:
ioctl命令描述
VIDIOC_QUERYCAP获取设备支持的操作
VIDIOC_G_FMT获取设置支持的视频格式
VIDIOC_S_FMT设置捕获视频的格式
VIDIOC_REQBUFS向驱动提出申请内存的请求
VIDIOC_QUERYBUF向驱动查询申请到的内存
VIDIOC_QBUF将空闲的内存加入可捕获视频的队列
VIDIOC_DQBUF将已经捕获好视频的内存拉出已捕获视频的队列
VIDIOC_STREAMON打开视频流
VIDIOC_STREAMOFF关闭视频流
VIDIOC_QUERYCTRL查询驱动是否支持该命令
VIDIOC_G_CTRL获取当前命令值
VIDIOC_S_CTRL设置新的命令值
VIDIOC_G_TUNER获取调谐器信息
VIDIOC_S_TUNER设置调谐器信息
VIDIOC_G_FREQUENCY获取调谐器频率
VIDIOC_S_FREQUENCY设置调谐器频率

V4L2视频采集原理

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

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

V4L2视频采集过程

图片1.png

打开设备

打开视频设备非常简单,在V4L2中,视频设备被看做一个文件。可以使用open函数打开这个设备。应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。
非阻塞模式打开摄像头设备, 示例代码如下:

int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK);

用阻塞模式打开摄像头设备,上述代码变为:

cameraFd = open("/dev/video0", O_RDWR);
查询设备能力:VIDIOC_QUERYCAP

函数示例代码如下:

struct v4l2_capability capability;
int ret = ioctl(cameraFd, VIDIOC_QUERYCAP, &capability);

所有V4L2设备都支持 VIDIOC_QUERYCAP。它用于识别与此规范兼容的内核设备,并获取有关驱动程序和硬件功能的信息。ioctl获取指向由驱动程序填充的capability 的指针 。当驱动程序与此规范不兼容时,ioctl将返回 EINVAL错误代码。
struct v4l2_capability结构体内容如下:

struct v4l2_capability
{
    u8 driver[16];     // 驱动名字
    u8 card[32];         // 设备名字
    u8 bus_info[32];     // 设备在系统中的位置
    u32 version;         // 驱动版本号
    u32 capabilities;     // 设备支持的操作
    u32 reserved[4];     // 保留字段
};

其中capabilities代表设备支持的操作模式,详细参考附件文档表8-2。

获取当前驱动支持的视频格式:VIDIOC_ENUM_FMT

示例代码如下:

struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(cameraFd, VIDIOC_ENUM_FMT, &fmtdesc);

首先初始化v4l2_fmtdesc的index和type字段,调用VIDIOC_ENUM_FMT后,驱动将会填充v4l2_fmtdesc剩余部分,否则返回-1。Index从0开始,依次增加,直到返回-1。
struct v4l2_fmtdesc 具体内容如下:

struct v4l2_fmtdesc
{
    __u32 index;        // 需要填充,从0开始,依次上升。
    enum v4l2_buf_type type;    //应用程序设置的数据流的类型
    __u32 flags;     //视频格式标志,见下表
    __u8 description[32];// 格式说明,以null结尾的ASCII字符串
    __u32 pixelformat;  //图像格式标识符,由v4l2_fourcc()计算的四字符代码
    __u32 reserved[4]; //保留字段
};

flags支持的格式,详细参考附件文档表8-3:

获取和设置视频制式:VIDIOC_G_FMT, VIDIOC_S_FMT

利用 VIDIOC_G_FMT得到当前设置。因为Camera为CAPTURE设备,所以需要设置type为: V4L2_BUF_TYPE_VIDEO_CAPTURE,然后Driver会填充其它内容。然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式。v4l2_format结构体用来设置摄像头的视频制式、帧格式等,在设置这个参数时应先填好v4l2_format的各个字段,如 type(传输流类型),fmt.pix.width(宽),fmt.pix.heigth(高),fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2)。
示例代码如下:

struct v4l2_format fmt;
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cameraFd, VIDIOC_G_FMT, &fmt);
memset(&fmt, 0, sizeof(fmt));
fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = g_display_width;
fmt.fmt.pix.height      = g_display_height;
fmt.fmt.pix.pixelformat = g_fmt;
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
ret = ioctl(cameraFd, VIDIOC_S_FMT, &fmt);

struct v4l2_format结构体所包含的信息如下:

struct v4l2_format
{
    enum v4l2_buf_type type; //数据流类型
    union
    {
       struct v4l2_pix_format pix;   // 视频捕获/输出设备的图像格式定义
       struct v4l2_window win; //视频叠加设备的定义
       struct v4l2_vbi_format vbi; //原始VBI捕获/输出参数
       struct v4l2_sliced_vbi_format sliced;//限幅VBI捕获/输出参数
       __u8 raw_data[200];    //保留字段
     }fmt;
};

因为是Camera, 所以采用像素格式,其结构如下:

struct v4l2_pix_format
{
    __u32 width;    //图像像素的宽
    __u32 height;   // 图像像素的宽
    __u32 pixelformat;  // 像素格式或者压缩类型
    enum v4l2_field field; //是否逐行扫描,是否隔行扫描
    __u32 bytesperline; //每行的byte数
    __u32 sizeimage;    //总共的byte数,bytesperline * height
    enum v4l2_colorspace colorspace; 
    __u32 priv;
};

为了在驱动和应用程序之间交换图像数据,在两边都有标准图像数据是很有必要的。Pixelformat一般是指V4L2中支持的标准图像格式。其中包括RGB格式,YUV格式,压缩格式等,详细描述请参考附件文档8.3.4章节,图8-2、8-3,以及表8-4。

申请缓存区:VIDIOC_REQBUFS

请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存),V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。示例代码如下:

struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4;  //申请一个拥有四个缓冲帧的缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ret = ioctl(cameraFd, VIDIOC_REQBUFS, &req);

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

struct v4l2_requestbuffers
{
    __u32 count; //请求的缓存区数量
    enum v4l2_buf_type type;  //缓冲区的格式
    enum v4l2_memory memory; //采集图像的方式
    __u32 reserved[2];  //保留字段
};

V4L2支持使用内存映射,用户指针和DMABUF来进行视频流的读写。

  1. 内存映射:V4L2_MEMORY_MMAP
    在应用程序设计中通过调VIDIOC_QUERYBUF来获取内核空间的视频缓冲区信息(视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等),然后调用函数mmap将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incoming queues)和视频采集输出队列(outgoing queues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集。
  2. 用户指针:V4L2_MEMORY_USERPTR
    此I/O方法结合了高级读写以及内存映射方法。缓存通过应用程序本身进行申请,可以贮存在虚拟或共享内存中,需要交换的只是数据指针。这些指针和元数据在struct v4l2_buffer(对多平面API来说是struct v4l2_plane)中。若调用VIDIOC_REQBUFS且带有相应缓存类型,则驱动必须切换到用户指针I/O模式。不需要预先分配缓存,因此他们没有索引,也不能像映射缓存那样通过VIDIOC_QUERYBUF ioctl进行查询。
  3. DMA缓存引用:V4L2_MEMORY_DMABUF
    DMABUF框架提供了在多设备见共享缓存的通用方法,支持DMABUF的设备驱动可以将一个DMA缓存以文件句柄的方式输出到用户空间(输出者规则),以文件句柄的方式从用户空间获取一个DMA缓存,这个文件句柄是之前其他或相同的设备所输出的(引入者规则),或都是。此章节描述在V4L2中DMABUF的引入者规则。V4L2缓存以DMABUF文件句柄方式进行DMABUF输出。支持I/O流方法的输入和输出设备在通过VIDIOC_QUERYCAP ioctl查询时,返回的struct v4l2_capability中capabilities成员会包含V4L2_CAP_STREAMING标签。是否支持通过DMABUF文件句柄引入DMABUF缓存决定了在调用VIDIOC_REQBUFS时内存类型是否要设定为V4L2_MEMORY_DMABUF。这种I/O方法专用于在不同设备间共享DMA缓存,这些设备可以是V4L设备或是其他视频相关设备(如DRM)。缓存通过应用程序控制驱动来申请。然后,这些缓存通过使用特殊API以文件句柄的方式输出给应用程序,交换的只有文件句柄。句柄和信息存在于struct v4l2_buffer。在VIDIOC_REQBUFS ioctl执行后驱动必须切换到DMABUF I/O模式中。
开始/停止采集:VIDIOC_STREAMON,VIDIOC_STREAMOFF

VIDIOC_STREAMON和VIDIOC_STREAMOFF在视频流(内存映射、用户指针或DMABUF) I/O期间启动和停止捕获/输出进程。在调用VIDIOC_STREAMON之前,视频捕获是禁用的,并且没有输入缓冲区被填充,而且输出设备也是禁用的,并且不产生任何视频信号。直到捕获流和输出流类型都调用了VIDIOC_STREAMON,设备才会启动。如果VIDIOC_STREAMON失败,那么输入缓冲区将保持不变。VIDIOC_STREAMOFF除了中止或完成正在进行的任何DMA之外,还会解锁物理内存中锁定的任何用户指针缓冲区,并从传入和传出队列中删除所有缓冲区。这意味着所有捕获但未脱队列的图像将丢失,同样地,所有排队等待输出但尚未传输的图像也将丢失。

完整的V4L2视频采集代码请参考公开课源代码,可使用通用USB摄像头(无需特殊驱动)进行实验。

使用Linux V4L2,配合Linux Framebuffer驱动,可不依赖OpenCV或其他第三方库情况下,实现在无图形桌面界面系统下的视频处理和显示(RGB格式)。

EAIDK-610购买 | 淘宝链接
EAIDK-610标准版文档下载 | 提取密码:NZDg7
EAIDK-610公开课内容下载 | 提取密码:j904a

附件:

  1. 公开教程文档;
  2. 教程所有C/C++源代码;
文件名 大小 下载次数 操作
EAIDK-610公开教程C版.pdf 2.67MB 26 下载
cases.tar.gz 847.35KB 19 下载
推荐阅读
关注数
3510
内容数
57
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息