文章目录
1、V4L2简介
V4L2是Video for Linux 2的简称,是Linux中关于视频设备的内核驱动框架。视频设备驱动主要可以分为V4L2框架和硬件相关层两层。V4L2主要是将视频相关的共性操作框架提取出来,硬件相关层来对接各自的设备差异部分。在Linux系统中,视频捕获设备节点通常为/dev/videoX,例如/dev/video0、/dev/video1…
V4L2不仅仅是支持视频捕获设备,它还支持其他相关的设备,通常情况下它的主设备号为81,次设备号0~63对应grabber(视频捕捉)设备、次设备号64~127对应radio设备、次设备号224~255对应VBI设备、次设备号128~191对应其他设备,这点可以在后面驱动层的代码中验证。
1.1 来自内核文档的介绍
由于硬件的复杂性,V4L2驱动程序往往非常复杂:大多数设备有多个IC,在/dev中导出多个设备节点,还创建非V4L2设备,如DVB、ALSA、FB、I2C和输入(IR)设备。特别是V4L2驱动程序必须设置支持集成电路来做音频/视频混合/编码/解码,这使得它比大多数更加复杂。通常这些集成电路通过一个或多个I2C总线连接到主桥接驱动器,但也可以使用其他总线。这样的设备称为“子设备”。
很长一段时间以来,框架被限制为用于创建V4L设备节点的video_device结构和用于处理视频缓冲区的video_buf结构。这意味着所有驱动程序必须自己设置设备实例并连接到子设备。这其中的一些是相当复杂的正确做法,但许多驱动从来没有做到准确。
由于缺乏框架,还有许多公共代码永远无法重构。因此,这个框架(V4L2)建立了所有驱动程序需要的基本构建块,并且这个相同的框架应该使将公共代码重构为所有驱动程序共享的实用函数变得更加容易。可以参考的一个很好的示例是这个目录中的v4l2-pci-skeleton.c
源代码。它是一种PCI捕获卡的骨架驱动程序,并演示了如何使用V4L2驱动程序框架。它可以作为一个模板为真正的PCI视频捕获驱动程序。
翻译自:Linux内核源码/Documentation/video4linux/v4l2-framework.txt
1.2 V4L2支持设备
V4L2支持多种设备,但它们之中也只有一部分在本质上是真正的视频设备:
- video capture interface:视频捕获接口,从调谐器或摄像头上获取视频数据,视频捕获是V4L2的基本应用;
- video output interface:视频输出接口,允许应用程序驱动外设提供视频图像–可能是以电视信号的形式;
- video overlay interface:视频覆盖接口,是捕获接口的一个变体,其工作是便于捕获设备直接显示到显示器,无需经过CPU;
- VBI interfaces:Vertical blanking interval interface,垂直消隐接口,对视频消隐期间传输的数据访问,有两种接口:“原始”接口和“切片”接口,区别在于硬件中执行VBI数据的处理量有所不同;
radio interface:无线电接口,从AM和FM调谐器设备访问音频流。
翻译自:The Video4Linux2 API: an introduction - LWN
1.3 V4L2驱动框架提供的ioctl选项
先预览内核include/uapi/linux/videodev2.h
以下选项,相关功能看使用示例。
VIDIOC_QUERYCAP 查询设备能力、属性
VIDIOC_ENUM_FMT 枚举支持的视频格式
VIDIOC_G_FMT 获得视频格式
VIDIOC_S_FMT 设置视频格式
VIDIOC_TRY_FMT 尝试视频格式,但不会改变
VIDIOC_REQBUFS 申请内存
VIDIOC_QUERYBUF 将VIDIOC_REQBUFS申请的数据缓存转为物理地址
VIDIOC_QBUF 将缓存加入队列
VIDIOC_DQBUF 从队列中取出缓存
VIDIOC_OVERLAY 设置开始或停止Overlay
VIDIOC_G_FBUF 获得Overlay设备的Framebuffer参数
VIDIOC_S_FBUF 设置Framebuffer
VIDIOC_STREAMON 启动流
VIDIOC_STREAMOFF 关闭流
VIDIOC_ENUMINPUT 枚举设备所有input端口
VIDIOC_G_INPUT 获取当前使用的input端口
VIDIOC_S_INPUT 设置将要使用的input端口
VIDIOC_ENUMOUTPUT 枚举设备所有output端口
VIDIOC_G_OUTPUT 获取当前使用的output端口
VIDIOC_S_OUTPUT 设置将要使用的output端口
VIDIOC_ENUMAUDIO 枚举设备所有audio input端口
VIDIOC_G_AUDIO 获取当前使用的audio input端口
VIDIOC_S_AUDIO 设置将要使用的audio input端口
VIDIOC_ENUMAUDOUT 枚举设备所有audio output端口
VIDIOC_G_AUDOUT 获取当前使用的audio output端口
VIDIOC_S_AUDOUT 设置将要使用的audio output端口
VIDIOC_QUERYCTRL 查询指定control详细信息
VIDIOC_G_CTRL 获取control信息
VIDIOC_S_CTRL 设置control信息
VIDIOC_G_EXT_CTRLS 获取多个control信息
VIDIOC_S_EXT_CTRLS 设置多个control信息
VIDIOC_TRY_EXT_CTRLS 尝试多个control信息,但不会改变
VIDIOC_QUERYMENU 查询MENU
VIDIOC_G_CROP 获取视频信号的边框
VIDIOC_S_CROP 设置视频信号的边框
VIDIOC_ENUMSTD 枚举支持的所有电视标准
VIDIOC_G_STD 获取当前正在使用的标准
VIDIOC_S_STD 设置视频标准
VIDIOC_QUERYSTD 查询支持的视频标准
VIDIOC_RESERVED 保留
...
其中,只需要以下几个ioctl选项即可实现最基本的视频捕获功能:
VIDIOC_QUERYCAP
VIDIOC_ENUM_FMT
VIDIOC_S_FMT
VIDIOC_REQBUFS
VIDIOC_QUERYBUF
VIDIOC_QBUF
VIDIOC_DQBUF
VIDIOC_STREAMON
VIDIOC_STREAMOFF
2、从应用层看V4L2视频捕获功能
2.1 相关操作步骤
2.1.1 打开设备:open
操作一个设备之前必须先打开,并且获得它的文件句柄。
int v4l2_fd;
v4l2_fd = open("/dev/video0", O_RDWR);
if(v4l2_fd < 0){
printf("Open device error!\n");
return -1;
}
2.1.2 查询设备性能:VIDIOC_QUERYCAP
struct v4l2_capability V4L2_Cap;
if(ioctl(v4l2_fd, VIDIOC_QUERYCAP, &V4L2_Cap)){
printf("VIDIOC_QUERYCAP error!\n");
close(v4l2_fd);
return -1;
}
printf("Device Driver: %s, Card: %s, Bus info: %s\n",
V4L2_Cap.driver, V4L2_Cap.card, V4L2_Cap.bus_info);
if (!(V4L2_Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
printf("Device is not a video capture device!\n");
return -1;
}
if (V4L2_Cap.capabilities & V4L2_CAP_STREAMING) {
printf("Device supports streaming i/o\n");
}
if(V4L2_Cap.capabilities & V4L2_CAP_READWRITE) {
printf("Device supports read i/o\n");
}
2.1.3 枚举格式:VIDIOC_ENUM_FMT
struct v4l2_fmtdesc catFmt;
memset(&catFmt, 0, sizeof(struct v4l2_fmtdesc));
catFmt.index = 0; // 想查询哪个属性就设置索引值
catFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &catFmt))
{
if(ucatFmt.pixelformat == V4L2_PIX_FMT_YUYV){
printf("Device pixel format: YUYV!\n");
break;
}else{
/* 如果不支持则继续往下枚举查询 */
catFmt.index++;
}
}
2.1.4 设置格式:VIDIOC_S_FMT
填充结构体的格式,但是如果设备不支持,ioctl之后会将根据参数进行纠正。
struct v4l2_format setFmt;
memset(&setFmt, 0, sizeof(struct v4l2_format));
setFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setFmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; /* 指定YUYV格式 */
setFmt.fmt.pix.width = 640;
setFmt.fmt.pix.height = 480;
setFmt.fmt.pix.field = V4L2_FIELD_ANY;
if(ioctl(v4l2_fd, VIDIOC_S_FMT, &setFmt)){
printf("VIDIOC_S_FMT error!\n");
goto fatal;
}
2.1.5 “请求”缓存:VIDIOC_REQBUFS
这里还没有分配,只是获取缓存的一些相关信息,下一步才分配。
#define NB_BUFFER 4
struct v4l2_requestbuffers reqBufs;
memset(&reqBufs, 0, sizeof(struct v4l2_requestbuffers));
reqBufs.count = NB_BUFFER;
reqBufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqBufs.memory = V4L2_MEMORY_MMAP;
if (ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqBufs)) {
printf("VIDIOC_REQBUFS error!\n");
goto fatal;
}
2.1.6 查询/映射缓存:VIDIOC_QUERYBUF
上一步只是得到缓存的信息,在这一步才映射。
struct v4l2_buffer videoBuf;
char *videoBufAddr[NB_BUFFER];
for(i = 0; i < reqBufs.count; i++){
memset(&videoBuf, 0, sizeof(struct v4l2_buffer));
videoBuf.index = i;
videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(v4l2_fd, VIDIOC_QUERYBUF, &videoBuf)){
printf("VIDIOC_QUERYBUF error!\n");
goto fatal;
}
/* 保存地址 */
videoBufAddr[i] = mmap(0, videoBuf.length,
PROT_READ, MAP_SHARED, v4l2_fd,
videoBuf.m.offset);
if(videoBufAddr[i] == MAP_FAILED){
printf("Mmap error!\n");
goto fatal;
}
}
2.1.7 将缓存放入队列:VIDIOC_QBUF
for(i = 0; i < reqBufs.count; i++){
memset(&videoBuf, 0, sizeof(struct v4l2_buffer));
videoBuf.index = i;
videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(v4l2_fd, VIDIOC_QBUF, &uvideoBuf)){
printf("VIDIOC_QBUF error!\n");
goto fatal;
}
}
2.1.8 将缓存从队列取出:VIDIOC_DQBUF
memset(&videoBuf, 0, sizeof(struct v4l2_buffer));
videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(v4l2_fd, VIDIOC_DQBUF, &videoBuf)){
printf("VIDIOC_DQBUF error!\n");
goto fatal;
}
2.1.9 启动传输:VIDIOC_STREAMON
if(ioctl(v4l2_fd, VIDIOC_STREAMON, &setFmt.type)){
printf("VIDIOC_STREAMON error!\n");
goto fatal;
}
2.1.10 停止传输:VIDIOC_STREAMOFF
if(ioctl(v4l2_fd, VIDIOC_STREAMOFF, &setFmt.type)){
printf("VIDIOC_STREAMOFF error!\n");
goto fatal;
}
2.2 示例程序
程序不严谨,仅以了解V4L2应用程序的框架为目的,主要参考Github项目luvcview,文末附地址。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <poll.h>
#include <sys/mman.h>
#define VIDEO_DBG printf
#define NB_BUFFER 4
typedef struct VideoDev{
int fd; // 文件句柄
char *videoBufAddr[NB_BUFFER]; // 用于保存视频缓存地址
struct v4l2_capability cap; // 用于查询设备属性
struct v4l2_fmtdesc catFmt; // 查看设备格式
struct v4l2_format setFmt; // 设置设备格式
struct v4l2_requestbuffers reqBufs; // 请求缓存时的信息
struct v4l2_buffer videoBuf; // 视频缓存的信息
struct pollfd pollfds[1]; // V4L2需要用到的poll机制
}tVideoDev;
int main(int argc, char **argv)
{
int i;
tVideoDev usb_camera;
if(argc != 2){
printf("Usage: ./videoApp /dev/videoX\n");
return -1;
}
/* 1、打开设备 */
usb_camera.fd = open(argv[1], O_RDWR);
if(usb_camera.fd < 0){
printf("Open device %s error!\n", argv[1]);
return -1;
}
/* 2、查询设备性能 */
if(ioctl(usb_camera.fd, VIDIOC_QUERYCAP, &usb_camera.cap)){
printf("VIDIOC_QUERYCAP error!\n");
goto fatal;
}
printf("*****************************************\n"
"* Device : %s\n"
"* Type : %s\n"
"* Driver : %s\n"
"* Card : %s\n"
"* Bus info: %s\n"
"* Format : %s\n"
"*****************************************\n",
argv[1],
usb_camera.cap.capabilities & V4L2_CAP_VIDEO_CAPTURE ? "Video Capture" : "Other",
usb_camera.cap.driver,
usb_camera.cap.card,
usb_camera.cap.bus_info,
usb_camera.cap.capabilities & V4L2_CAP_STREAMING ? "Streaming":"Other");
/* 3、枚举格式 */
memset(&usb_camera.catFmt, 0, sizeof(struct v4l2_fmtdesc));
usb_camera.catFmt.index = 0; // 想查询哪个属性就设置索引值
usb_camera.catFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(0 == ioctl(usb_camera.fd, VIDIOC_ENUM_FMT, &usb_camera.catFmt))
{
if(usb_camera.catFmt.pixelformat == V4L2_PIX_FMT_YUYV){
printf("Device pixel format: YUYV!\n");
break;
}else{
/* 如果不支持则继续往下枚举查询 */
usb_camera.catFmt.index++;
}
}
if(!usb_camera.catFmt.pixelformat){
/* 如果都没有匹配的格式则退出 */
printf("No pixel format supported!\n");
goto fatal;
}
/* 4、设置格式 */
memset(&usb_camera.setFmt, 0, sizeof(struct v4l2_format));
usb_camera.setFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.setFmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; /* 指定YUYV格式 */
usb_camera.setFmt.fmt.pix.width = 640;
usb_camera.setFmt.fmt.pix.height = 480;
usb_camera.setFmt.fmt.pix.field = V4L2_FIELD_ANY;
if(ioctl(usb_camera.fd, VIDIOC_S_FMT, &usb_camera.setFmt)){
printf("VIDIOC_S_FMT error!\n");
goto fatal;
}
/* 设备会根据提供的格式返回调整后的格式 */
printf("Video resolution: %dx%d !\n",
usb_camera.setFmt.fmt.pix.width,
usb_camera.setFmt.fmt.pix.height);
/* 5、“请求”缓存(设置缓存信息,还没分配) */
memset(&usb_camera.reqBufs, 0, sizeof(struct v4l2_requestbuffers));
usb_camera.reqBufs.count = NB_BUFFER;
usb_camera.reqBufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.reqBufs.memory = V4L2_MEMORY_MMAP;
if (ioctl(usb_camera.fd, VIDIOC_REQBUFS, &usb_camera.reqBufs)) {
printf("VIDIOC_REQBUFS error!\n");
goto fatal;
}
/* 6、根据缓存信息分配缓存 */
for(i = 0; i < usb_camera.reqBufs.count; i++){
memset(&usb_camera.videoBuf, 0, sizeof(struct v4l2_buffer));
usb_camera.videoBuf.index = i;
usb_camera.videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(usb_camera.fd, VIDIOC_QUERYBUF, &usb_camera.videoBuf)){
printf("VIDIOC_QUERYBUF error!\n");
goto fatal;
}
/* 保存地址 */
usb_camera.videoBufAddr[i] = mmap(0, usb_camera.videoBuf.length,
PROT_READ, MAP_SHARED, usb_camera.fd,
usb_camera.videoBuf.m.offset);
if(usb_camera.videoBufAddr[i] == MAP_FAILED){
printf("Mmap error!\n");
goto fatal;
}
}
/* 7、将缓存放入队列 */
for(i = 0; i < usb_camera.reqBufs.count; i++){
memset(&usb_camera.videoBuf, 0, sizeof(struct v4l2_buffer));
usb_camera.videoBuf.index = i;
usb_camera.videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(usb_camera.fd, VIDIOC_QBUF, &usb_camera.videoBuf)){
printf("VIDIOC_QBUF error!\n");
goto fatal;
}
}
/* 8、启动传输 */
if(ioctl(usb_camera.fd, VIDIOC_STREAMON, &usb_camera.setFmt.type)){
printf("VIDIOC_STREAMON error!\n");
goto fatal;
}
/* 循环读取数据 */
while(1){
/* 9、等待数据可读 */
usb_camera.pollfds[0].fd = usb_camera.fd;
usb_camera.pollfds[0].events = POLLIN;
poll(usb_camera.pollfds, 1, -1);
/* 10、将缓存从队列取出(传出给索引值,去读取对应索引值的地址即可) */
memset(&usb_camera.videoBuf, 0, sizeof(struct v4l2_buffer));
usb_camera.videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(usb_camera.fd, VIDIOC_DQBUF, &usb_camera.videoBuf)){
printf("VIDIOC_DQBUF error!\n");
goto fatal;
}
/* 11、处理视频图像原始缓存数据,这里简单打印即可 */
VIDEO_DBG("Get the video raw data!\n"
" - index : %d\n"
" - start : 0x%x\n"
" - size : %d\n",
usb_camera.videoBuf.index,
usb_camera.videoBufAddr[usb_camera.videoBuf.index],
usb_camera.videoBuf.bytesused);
/* 12、将缓存放回队列 */
usb_camera.videoBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usb_camera.videoBuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(usb_camera.fd, VIDIOC_QBUF, &usb_camera.videoBuf)){
printf("VIDIOC_QBUF error!\n");
goto fatal;
}
}
/* 13、停止传输 */
if(ioctl(usb_camera.fd, VIDIOC_STREAMOFF, &usb_camera.setFmt.type)){
printf("VIDIOC_STREAMOFF error!\n");
goto fatal;
}
return 0;
fatal:
close(usb_camera.fd);
return -1;
}
2.3 测试效果
/drivers # ./videoApp /dev/video1
*****************************************
* Device : /dev/video1
* Type : Video Capture
* Driver : uvcvideo
* Card : USB2.0 UVC PC Camera
* Bus info: usb-ci_hdrc.1-1
* Format : Streaming
*****************************************
Device pixel format: YUYV!
Video resolution: 640x480 !
Get the video raw data!
- index : 0
- start : 0x76e35000
- size : 614400
Get the video raw data!
- index : 1
- start : 0x76d9f000
- size : 614400
...
3、从驱动层看V4L2视频捕获设备框架
从上面内核介绍的文档推荐使用v4l2-pci-skeleton.c入手,查看V4L2的框架。
3.1 重要的数据结构
3.1.1 V4L2向内核注册的驱动操作结构体:v4l2_fops(file_operations类型)
该结构体类型与常规的驱动是一样的,都是file_operations结构体类型。在V4L2框架中的定义为v4l2_fops,它对应的open/read/write函数其实就是一个框架,查看函数的实现就会发现里面会通过vdev->fops->open/read/write继续往下调用“硬件相关层”的v4l2_file_operations结构体的open/read/write函数,关于硬件相关层的v4l2_file_operations后面会列出。
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
/* 以v4l2_open为例查看调用 */
static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
...
if (vdev->fops->open) {
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);
...
}
3.1.2 硬件相关层的操作结构体:skel_fops(v4l2_file_operations类型)
这部分对应硬件部分的操作,是视频设备video_device结构体的一个成员。正如上面所说,这部分会被V4L2的框架调用。
static const struct v4l2_file_operations skel_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.unlocked_ioctl = video_ioctl2,
.read = vb2_fop_read,
.mmap = vb2_fop_mmap,
.poll = vb2_fop_poll,
};
/* 结构体类型定义 */
struct v4l2_file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*ioctl) (struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
#ifdef CONFIG_COMPAT
long (*compat_ioctl32) (struct file *, unsigned int, unsigned long);
#endif
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct file *);
int (*release) (struct file *);
};
3.1.3 ioctl函数操作集合的结构体:skel_ioctl_ops(v4l2_ioctl_ops类型)
这部分是V4L2框架的核心之一,提供ioctl选项的集合,同样是视频设备video_device结构体的一个成员。不需要全部填充,只需要填充需要使用到的函数即可。
static const struct v4l2_ioctl_ops skel_ioctl_ops = {
.vidioc_querycap = skeleton_querycap,
.vidioc_try_fmt_vid_cap = skeleton_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = skeleton_s_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = skeleton_g_fmt_vid_cap,
// 省略...
};
/* 结构体类型定义 */
struct v4l2_ioctl_ops {
/* ioctl callbacks */
/* VIDIOC_QUERYCAP handler */
int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
/* VIDIOC_ENUM_FMT handlers */
int (*vidioc_enum_fmt_vid_cap) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_overlay) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_cap_mplane)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out_mplane)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
// 省略...
}
3.1.4 视频设备抽象成结构体:vdev(video_device类型)
在V4L2的框架下,每一个视频设备都可以抽象成一个video_device结构体,其中有两个在分析框架时比较重要的成员:fops和ioctl_ops。这两个就是上面所列出的结构体类型,每个设备的“硬件相关层”的驱动根据硬件的差异性各自填充,然后再调用video_register_device函数注册设备,后面应用程序调用函数时经过V4L2的框架后调用到该结构体里的函数。
static int skeleton_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct video_device *vdev;
// 省略...
}
/* 结构体类型定义 */
struct video_device
{
const struct v4l2_file_operations *fops; /* 重要:常规的驱动操作open/read/write结构体指针 */
struct device dev;
struct cdev *cdev;
struct v4l2_device *v4l2_dev;
struct device *dev_parent;
struct v4l2_ctrl_handler *ctrl_handler;
struct vb2_queue *queue;
struct v4l2_prio_state *prio;
char name[32];
int vfl_type;
int vfl_dir;
int minor;
u16 num;
unsigned long flags;
int index;
spinlock_t fh_lock;
struct list_head fh_list;
int dev_debug;
v4l2_std_id tvnorms;
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops; /* 重要:ioctl函数选项的结构体指针 */
DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);
struct mutex *lock;
};
3.2 重要的函数调用
3.2.1 注册视频设备函数:video_register_device/__video_register_device
该函数一般是提供给“硬件相关层”去调用注册,在调用之前,会先设置好video_device的fops成员(v4l2_file_operations类型)和ioctl_ops成员(v4l2_ioctl_ops类型),而video_register_device函数注册最终只需要配置cdev的ops成员为v4l2_fops(file_operations类型)。
static inline int __must_check video_register_device(struct video_device *vdev, int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
// ... 省略
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
case VFL_TYPE_RADIO:
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
case VFL_TYPE_SDR:
/* Use device name 'swradio' because 'sdr' was already taken. */
name_base = "swradio";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
// ... 省略
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
#endif
// ... 省略
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc(); /* 分配 */
vdev->cdev->ops = &v4l2_fops; /* 设置v4l2驱动操作结构体 */
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
// ... 省略
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num); /* 设置设备节点名称 */
ret = device_register(&vdev->dev); /* 注册设备 */
vdev->dev.release = v4l2_device_release;
// ... 省略
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Part 5: Register the entity. */
if (vdev->v4l2_dev->mdev &&
vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.dev.major = VIDEO_MAJOR;
vdev->entity.info.dev.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,
&vdev->entity);
// ... 省略
#endif
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
cleanup:
// ... 省略
}
3.2.2 函数调用过程(以ioctl为例)
ioctl(fd, ...
一系列系统调用之后...
v4l2_fops.unlocked_ioctl /* v4l2_ioctl */
vdev->fops->unlocked_ioctl(filp, cmd, arg); /* video_ioctl2 */
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl /* 回调函数 */
info = &v4l2_ioctls[_IOC_NR(cmd)];
info->u.func(ops, file, fh, arg);
应用层调用ioctl函数经过一系列的系统调用之后,到达V4L2所配置的file_operations结构体中的unlocked_ioctl函数指针所指的函数v4l2_ioctl,函数里面继续调用vdev->fops->unlocked_ioctl(filp, cmd, arg);调用到硬件相关层的v4l2_file_operations结构体中的unlocked_ioctl函数指针所指的函数video_ioctl2,而该函数继续通过video_usercopy函数的回调函数__video_do_ioctl去从V4L2框架中的结构体数组v4l2_ioctls中得到相应的结构体,最终调用该结构体中的函数指针func所指向的实际的函数。具体还可以结合以下框架图及源码进行分析。
3.3 视频设备在内核V4L2中的关系
3.4 基于v4l2-pci-skeleton.c分析V4L2框架
4、参考资料
- 韦东山第三期项目实战视频 - 项目2视频监控
- Linux内核源码/Documentation/video4linux/v4l2-framework.txt
- Linux内核源码/drivers/media/platform/omap3isp/
- luvcview(v4l2uvc.c) - Github
- The Video4Linux2 API: an introduction - LWN
- 和菜鸟一起学linux之V4L2摄像头应用流程 - CSDN
- V4L2框架概述 - CSDN