公开课简介
详细介绍EAIDK-610产品基本功能,硬件特性,软件特点及软件调试操作方法,旨在帮助开发人员更快、更好熟悉EAIDK-610硬件平台(EAIDK-610)和使用EAIDK-610开发套件,并使用EAIDK开发AIoT应用。
- 原生c/c++通用接口控制编程(GPIO、SPI、IIC、USB等),如何使用IO接口采集不同类型传感器数据,及进行数据分析处理;
- Linux标准视频驱动V4L2介绍,进行视频采集、及图像基本处理;
- 音频数据采集与处理。
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),这些接口主要有:
- 视频采集接口——Video Capture Interface;
- 视频输出接口—— Video Output Interface;
- 视频覆盖/预览接口——Video Overlay Interface;
- 视频输出覆盖接口——Video Output Overlay Interface;
- 编解码接口——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接口采集视频数据分为五个步骤:
- 打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
- 其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
- 将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
- 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
- 停止视频采集。
V4L2视频采集过程
打开设备
打开视频设备非常简单,在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来进行视频流的读写。
- 内存映射:V4L2_MEMORY_MMAP
在应用程序设计中通过调VIDIOC_QUERYBUF来获取内核空间的视频缓冲区信息(视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等),然后调用函数mmap将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incoming queues)和视频采集输出队列(outgoing queues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集。 - 用户指针:V4L2_MEMORY_USERPTR
此I/O方法结合了高级读写以及内存映射方法。缓存通过应用程序本身进行申请,可以贮存在虚拟或共享内存中,需要交换的只是数据指针。这些指针和元数据在struct v4l2_buffer(对多平面API来说是struct v4l2_plane)中。若调用VIDIOC_REQBUFS且带有相应缓存类型,则驱动必须切换到用户指针I/O模式。不需要预先分配缓存,因此他们没有索引,也不能像映射缓存那样通过VIDIOC_QUERYBUF ioctl进行查询。 - 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
附件:
- 公开教程文档;
- 教程所有C/C++源代码;
文件名 | 大小 | 下载次数 | 操作 |
---|---|---|---|
EAIDK-610公开教程C版.pdf | 2.67MB | 26 | 下载 |
cases.tar.gz | 847.35KB | 19 | 下载 |