V4L2框架分析学习
Author:CJOK
Contact:cjok.liao#gmail.com
SinaWeibo:@廖野cjok
1、概述
Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。就像公司的老板一般都不会直接找底层的员工谈话,而是找部门经理了解情况,一个是因为底层屌丝人数多,意见各有不同,措辞也不准,部门经理会把情况汇总后再向上汇报;二个是老板时间宝贵。
V4L2支持三类设备:视频输入输出设备、VBI设备和radio设备(其实还支持更多类型的设备,暂不讨论),分别会在/dev目录下产生videoX、radioX和vbiX设备节点。我们常见的视频输入设备主要是摄像头,也是本文主要分析对象。下图V4L2在Linux系统中的结构图:
Linux系统中视频输入设备主要包括以下四个部分:
字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev。
具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。
V4L2的核心源码位于drivers/media/v4l2-core,源码以实现的功能可以划分为四类:
核心模块实现:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数;
V4L2框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件实现,构建V4L2框架;
Videobuf管理:由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。
2、V4L2框架
结构体v4l2_device、video_device、v4l2_subdev和v4l2_fh是搭建框架的主要元素。下图是V4L2框架的结构图:
从上图V4L2框架是一个标准的树形结构,v4l2_device充当了父设备,通过链表把所有注册到其下的子设备管理起来,这些设备可以是GRABBER、VBI或RADIO。V4l2_subdev是子设备,v4l2_subdev结构体包含了对设备操作的ops和ctrls,这部分代码和硬件相关,需要驱动工程师根据硬件实现,像摄像头设备需要实现控制上下电、读取ID、饱和度、对比度和视频数据流打开关闭的接口函数。Video_device用于创建子设备节点,把操作设备的接口暴露给用户空间。V4l2_fh是每个子设备的文件句柄,在打开设备节点文件时设置,方便上层索引到v4l2_ctrl_handler,v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等。
v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设备。以下是v4l2_device结构体原型(去掉了无关的成员):
structlist_head subdevs; //用链表管理注册的subdev
charname[V4L2_DEVICE_NAME_SIZE]; //device 名字
可以看出v4l2_device的主要作用是管理注册在其下的子设备,方便系统查找引用到。
intv4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev)
static void v4l2_device_release(struct kref *ref)
V4l2_subdev代表子设备,包含了子设备的相关属性和操作。先来看下结构体原型:
structv4l2_device *v4l2_dev; //指向父设备
conststruct v4l2_subdev_ops *ops;
conststruct v4l2_subdev_internal_ops *internal_ops;
structv4l2_ctrl_handler *ctrl_handler;
charname[V4L2_SUBDEV_NAME_SIZE];
每个子设备驱动都需要实现一个v4l2_subdev结构体,v4l2_subdev可以内嵌到其它结构体中,也可以独立使用。结构体中包含了对子设备操作的成员v4l2_subdev_ops和v4l2_subdev_internal_ops。
//视频设备通用的操作:初始化、加载FW、上电和RESET等
conststruct v4l2_subdev_core_ops *core;
conststruct v4l2_subdev_tuner_ops *tuner;
conststruct v4l2_subdev_audio_ops *audio;
conststructv4l2_subdev_video_ops *video;
视频设备通常需要实现core和video成员,这两个OPS中的操作都是可选的,但是对于视频流设备video->s_stream(开启或关闭流IO)必须要实现。
v4l2_subdev_internal_ops结构体原型如下:
structv4l2_subdev_internal_ops {
int(*registered)(struct v4l2_subdev *sd);
void(*unregistered)(struct v4l2_subdev *sd);
//当设备节点被打开时调用,通常会给设备上电和设置视频捕捉FMT
int(*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
int(*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
v4l2_subdev_internal_ops是向V4L2框架提供的接口,只能被V4L2框架层调用。在注册或打开子设备时,进行一些辅助性操作。
当我们把v4l2_subdev需要实现的成员都已经实现,就可以调用以下函数把子设备注册到V4L2核心层:
int v4l2_device_register_subdev(struct v4l2_device*v4l2_dev, struct v4l2_subdev *sd)
void v4l2_device_unregister_subdev(struct v4l2_subdev*sd)
video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间。
conststruct v4l2_file_operations *fops; //V4L2设备操作集合
structdevice dev; /* v4l device */
/* Seteither parent or v4l2_dev if your driver uses v4l2_device */
structdevice *parent; /* deviceparent */
structv4l2_device *v4l2_dev; /*v4l2_device parent */
/*Control handler associated with this device node. May be NULL. */
structv4l2_ctrl_handler *ctrl_handler;
intvfl_type; /* device type */
spinlock_t fh_lock; /* Lock for allv4l2_fhs */
structlist_head fh_list; /* List ofstruct v4l2_fh */
/*ioctl回调函数集,提供file_operations中的ioctl调用 */
conststruct v4l2_ioctl_ops *ioctl_ops;
Video_device分配和释放,用于分配和释放video_device结构体:
struct video_device *video_device_alloc(void)
void video_device_release(struct video_device *vdev)
video_device注册和注销,实现video_device结构体的相关成员后,就可以调用下面的接口进行注册:
static inline int __must_checkvideo_register_device(struct video_device *vdev,
void video_unregister_device(struct video_device*vdev);
type:设备类型,包括VFL_TYPE_GRABBER、VFL_TYPE_VBI、VFL_TYPE_RADIO和VFL_TYPE_SUBDEV。
v4l2_fh是用来保存子设备的特有操作方法,也就是下面要分析到的v4l2_ctrl_handler,内核提供一组v4l2_fh的操作方法,通常在打开设备节点时进行v4l2_fh注册。
初始化v4l2_fh,添加v4l2_ctrl_handler到v4l2_fh:
void v4l2_fh_init(struct v4l2_fh *fh, structvideo_device *vdev)
添加v4l2_fh到video_device,方便核心层调用到:
void v4l2_fh_add(struct v4l2_fh *fh)
struct v4l2_ctrl *v4l2_ctrl_new_std(structv4l2_ctrl_handler *hdl,
conststruct v4l2_ctrl_ops *ops,
u32id, s32 min, s32 max, u32 step, s32 def)
hdl是初始化好的v4l2_ctrl_handler结构体;
ops是v4l2_ctrl_ops结构体,包含ctrls的具体实现;
id是通过IOCTL的arg参数传过来的指令,定义在v4l2-controls.h文件;
min、max用来定义某操作对象的范围。如:
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS,-208, 127, 1, 0);
用户空间可以通过ioctl的VIDIOC_S_CTRL指令调用到v4l2_ctrl_handler,id透过arg参数传递。
3、ioctl框架
你可能观察到用户空间对V4L2设备的操作基本都是ioctl来实现的,V4L2设备都有大量可操作的功能(配置寄存器),所以V4L2的ioctl也是十分庞大的。它是一个怎样的框架,是怎么实现的呢?
Ioctl框架是由v4l2_ioctl.c文件实现,文件中定义结构体数组v4l2_ioctls,可以看做是ioctl指令和回调函数的关系表。用户空间调用系统调用ioctl,传递下来ioctl指令,然后通过查找此关系表找到对应回调函数。
以下是截取数组的两项:
IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf,v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf,v4l_print_framebuffer, 0),
内核提供两个宏(IOCTL_INFO_FNC和IOCTL_INFO_STD)来初始化结构体,参数依次是ioctl指令、回调函数或者v4l2_ioctl_ops结构体成员、debug函数、flag。如果回调函数是v4l2_ioctl_ops结构体成员,则使用IOCTL_INFO_STD;如果回调函数是v4l2_ioctl.c自己实现的,则使用IOCTL_INFO_FNC。
V4L2支持三种不同IO访问方式(内核中还支持了其它的访问方式,暂不讨论):
read和write,是基本帧IO访问方式,通过read读取每一帧数据,数据需要在内核和用户之间拷贝,这种方式访问速度可能会非常慢;
Read和write方式属于帧IO访问方式,每一帧都要通过IO操作,需要用户和内核之间数据拷贝,而后两种是流IO访问方式,不需要内存拷贝,访问速度比较快。内存映射缓冲区访问方式是比较常用的方式。
待图像数据传输到DMA缓冲区之后,mmap操作把缓冲区映射到用户空间,应用就可以直接访问缓冲区的数据。
为了使设备支持流IO这种方式,驱动需要实现struct vb2_queue,来看下这个结构体:
enumv4l2_buf_type type; //buffer类型
unsignedint io_modes; //访问IO的方式:mmap、userptr etc
conststruct vb2_ops *ops; //buffer队列操作函数集合
conststruct vb2_mem_ops *mem_ops; //buffer memory操作集合
structvb2_buffer *bufs[VIDEO_MAX_FRAME]; //代表每个buffer
unsignedint num_buffers; //分配的buffer个数
Vb2_queue代表一个videobuffer队列,vb2_buffer是这个队列中的成员,vb2_mem_ops是缓冲内存的操作函数集,vb2_ops用来管理队列。
vb2_mem_ops包含了内存映射缓冲区、用户空间缓冲区的内存操作方法:
void *(*alloc)(void *alloc_ctx, unsignedlong size); //分配视频缓存
void (*put)(void *buf_priv); //释放视频缓存
void *(*get_userptr)(void *alloc_ctx,unsigned long vaddr,
unsignedlong size, int write);
void (*put_userptr)(void *buf_priv); //释放用户空间视频缓冲区指针
void (*prepare)(void *buf_priv);
void (*finish)(void *buf_priv);
void *(*vaddr)(void *buf_priv);
void *(*cookie)(void *buf_priv);
unsignedint (*num_users)(void *buf_priv); //返回当期在用户空间的buffer数
int (*mmap)(void *buf_priv, structvm_area_struct *vma); //把缓冲区映射到用户空间
vb2_ops是用来管理buffer队列的函数集合,包括队列和缓冲区初始化
int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
unsigned int *num_buffers, unsigned int*num_planes,
unsigned int sizes[], void *alloc_ctxs[]);
void(*wait_prepare)(struct vb2_queue *q);
void(*wait_finish)(struct vb2_queue *q);
int(*buf_init)(struct vb2_buffer *vb);
int(*buf_prepare)(struct vb2_buffer *vb);
int(*buf_finish)(struct vb2_buffer *vb);
void(*buf_cleanup)(struct vb2_buffer *vb);
int(*start_streaming)(struct vb2_queue *q, unsigned int count);
int(*stop_streaming)(struct vb2_queue *q);
void(*buf_queue)(struct vb2_buffer *vb);
vb2_buffer是缓存队列的基本单位,内嵌在其中v4l2_buffer是核心成员。当开始流IO时,帧以v4l2_buffer的格式在应用和驱动之间传输。一个缓冲区可以有三种状态:
在驱动的传入队列中,驱动程序将会对此队列中的缓冲区进行处理,用户空间通过IOCTL:VIDIOC_QBUF把缓冲区放入到队列。对于一个视频捕获设备,传入队列中的缓冲区是空的,驱动会往其中填充数据;
在驱动的传出队列中,这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领;
用户空间状态的队列,已经通过IOCTL:VIDIOC_DQBUF传出到用户空间的缓冲区,此时缓冲区由用户空间拥有,驱动无法访问。
structtimeval timestamp; //时间戳,代表帧捕获的时间
__u32 memory; //表示缓冲区是内存映射缓冲区还是用户空间缓冲区
unsignedlong userptr; //缓冲区的用户空间地址
下面通过内核映射缓冲区方式访问视频设备(capturedevice)的流程。
fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
ioctl(fd, VIDIOC_QUERYCAP, &cap)
fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat= V4L2_PIX_FMT_YUYV; //像素格式
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
Struct v4l2_requestbuffers req;
req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
buffers = calloc(req.count, sizeof(*buffers));
for (n_buffers= 0; n_buffers < req.count; ++n_buffers) {
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 ==xioctl(fd, VIDIOC_QUERYBUF, & buf))
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length= buf.length;
mmap(NULL /* start anywhere */,
PROT_READ | PROT_WRITE /* required */,
for (i =0; i < n_buffers; ++i) {
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, & type))
7> 调用select监测文件描述符,缓冲区的数据是否填充好,然后对视频数据
r = select(fd + 1,& fds, NULL, NULL, & tv);
fprintf(stderr,"select timeout\n");
/* EAGAIN - continueselect loop. */
8> 取出已经填充好的缓冲,获取到视频数据的大小,然后对数据进行处理。这里取出的缓冲只包含缓冲区的信息,并没有进行视频数据拷贝。
buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 ==ioctl(fd, VIDIOC_DQBUF, & buf)) //取出缓冲
process_image(buffers[buf.index].start,buf.bytesused); //视频数据处理
if (-1 ==xioctl(fd, VIDIOC_QBUF, & buf)) //然后又放入到传入队列
type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd,VIDIOC_STREAMOff, & type);
http://lxr.linux.no/linux+v3.8.8/Documentation/video4linux/v4l2-framework.txt
http://lxr.linux.no/linux+v3.9/Documentation/DocBook/media/v4l/capture.c.xml
http://linuxtv.org/downloads/v4l-dvb-apis/vidioc-reqbufs.html
http://lwn.net/Articles/203924/
http://lxr.linux.no/linux+v3.9.1/drivers/media/platform/vivi.c
V4L2框架分析学习相关推荐
- V4L2框架分析学习二
转载于:http://www.techbulo.com/1198.html v4l2_device v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设 ...
- 深入理解l内核v4l2框架之video for linux 2(一)
在看了很多关于v4l2驱动的例程之后,想深入研究下linux内核的v4l2框架,顺便把这些记录下来,以备查用. Video for Linux 2 随着一些视频或者图像硬件的复杂化,V4L2驱动也越来 ...
- 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】
转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...
- 深入理解l内核v4l2框架之video for linux 2(转载)
在看了很多关于v4l2驱动的例程之后,想深入研究下linux内核的v4l2框架,顺便把这些记录下来,以备查用. Video for Linux 2 随着一些视频或者图像硬件的复杂化,V4L2驱动也越来 ...
- v4l2框架—申请缓存(VIDIOC_REQBUFS)
1.前言 本文对学习V4L2框架缓存管理做一个笔记. 2.v4l2中关于缓存管理的结构体 在v4l2中,使用vb2_queue结构体作为缓存管理的结构体 struct vb2_queue {enum ...
- V4L2框架-videobuf2
阅读原文 本文介绍在 v4l2 框架之下的数据流交互的实现与使用,主要目的是实现一个能够进行用户空间与内核空间进行数据交互.数据流格式设置.数据流 buffer 申请与释放.数据流开启与关闭的 vid ...
- Linux下V4L2框架基于SDL库本地USB摄像头监控
Linux下V4L2框架基于SDL库本地USB摄像头监控 1.摄像头框架编程步骤 (1)打开摄像头设备(/dev/video0 ./dev/video1 ) (2)设置图像格式:VIDIOC_S_FM ...
- 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写
一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...
- 通过V4L2框架获取UVC摄像头的MJPEG格式数据
UVC摄像头一般支持YUV,MJPEG,H264等格式的输出,下面以MJPEG输出为例,介绍如何通过V4L2框架抓取摄像头MJPEG数据. #include <stdio.h> #incl ...
最新文章
- CentOS6.8下安装JDK1.8
- 新手也能看懂的监控报警系统架构设计
- 20亿参数,大型视觉Transformer来了,刷新ImageNet Top1
- 微信小游戏视频激励广告onClose接口叠加回调的问题解决方法
- ccf a类期刊_喜报:我院2篇学生论文被CCFA类会议AAAI(2020)接收
- Spring Boot 打成war包部署到tomcat8.5.20报无法访问
- HDU 4281 Judges' response [MTSP]
- ubuntu中忘记root密码的解决方法
- 孤荷凌寒自学python第五十四天使用python来删除Firebase数据库中的文档
- 互联网红利消退,下一个机会在哪?
- airflow 進行後端大數據中ETL處理(草稿)
- 程序员拒带电脑回家被开除获赔 19.4 万;库克称,很多功能来自中国消费者反馈;谷歌开源1.6万亿参数语言模型 | 极客头条...
- yii2 advanced版基础部分
- windows内核——基石
- 四.驱动框架入门之LED(中)
- wannacry作者捉到了吗_Wannacry事件解读
- braft-editor 富文本编辑器在谷歌复制图片出现两张
- 用c语言编写打印机输出程序,C语言编写银行打印程序实例参考
- Dep包管理的主要机制
- ssis-状态为在执行中,组件的颜色一直为黄色
热门文章
- Spring Boot 最核心的 3 个注解详解
- matlab调用kmeans_K_Means算法的MATLAB实现
- mysql半复制_mysql半同步复制
- 你知道铅酸蓄电池的常见失效模式吗?
- Python才是世界上最好的语言
- 如何优化UPS的工作模式为数据中心节省运营成本
- matlab模拟gpd,如何用ARMA模型预测中国GDP
- Python之Pandas:利用Pandas函数实现对表格文件的查之高级查询(类似sql,分组查询等)之详细攻略
- Algorithm:C++语言实现之分治法相关问题(给定实数x和整数n,分治法求xn)
- NLP:基于nltk和jieba库对文本实现提取文本摘要(两种方法实现:top_n_summary和mean_scored_summary)