最近学习了v4l2(video for linux two),主要是关于linux中对视频(还有其他)方面的支持。这里就分析一下内核自带的vivi.c文件,因为完全是虚拟的不涉及任何物理硬件(摄像头等),所以适合分析和了解整个v4l2框架和流程。
说明:
1)/* */ 为代码注释,很多时候,有一些函数比较复杂,我就在调用函数后面加上函数的实现
2)后一行比前一行缩进 代表后一行是在前一个代码里面的,被调用关系 ,有时候也用{ 、、、}
module_init(vivi_init);
vivi_init
vivi_create_instance(i)
{
struct vivi_dev *dev;//申明一个vivi_dev结构体类型的变量
/*struct vivi_dev
{
struct list_head vivi_devlist;
struct v4l2_device v4l2_dev;//是整个v4l2树形架构的父设备(根)
struct v4l2_ctrl_handler ctrl_handler;//v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等
/* controls */
struct v4l2_ctrl *brightness;
struct {
/* autogain/gain cluster */
struct v4l2_ctrl *autogain;
struct v4l2_ctrl *gain;
};
struct v4l2_ctrl *volume;
/* Input Number */
int input;
/* video capture */
struct vivi_fmt *fmt;
unsigned int width, height;
struct vb2_queue vb_vidq;
enum v4l2_field field;
};*/
struct video_device *vfd;// video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间
struct v4l2_ctrl_handler *hdl;//v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等
struct vb2_queue *q;//支持流IO方式(后面会分析),驱动需要实现struct vb2_queue
v4l2_device_register(NULL, &dev->v4l2_dev);//这里面没干啥活,就是跟着链表初始化
int v4l2_device_register(struct device *dev(NULL), struct v4l2_device *v4l2_dev)
{
v4l2_dev->dev = dev(NULL);//这个后面会用到
}
/*
static struct vivi_fmt formats[] = {
{
.name = "4:2:2, packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
.depth = 16,
},
*/
dev->fmt = &formats[0];//设置格式
dev->width = 640;//宽
dev->height = 480;//高
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);//11代表支持11种控制,该函数就是把hdl加入各种链表
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->v4l2_dev.ctrl_handler = hdl;
/* initialize queue */
q = &dev->vb_vidq;
memset(q, 0, sizeof(dev->vb_vidq));
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;//三种方式都支持,流IO,帧IO
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct vivi_buffer);
q->ops = &vivi_video_qops;
q->mem_ops = &vb2_vmalloc_memops;
vb2_queue_init(q);
vfd = video_device_alloc();//动态分配一个video_device
*vfd = vivi_template;//赋初值,vivi_template是一个实体
vfd->debug = debug;//开调试打印
vfd->v4l2_dev = &dev->v4l2_dev;//获取父节点,存储
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);//设置优先级
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);//注册video_device结构体,类型是VFL_TYPE_GRABBER, video_nr = -1,系统自动分配子设备号
video_set_drvdata(vfd, dev);//把vivi_dev类型的dev存入video_device类型的vfd中的私有数据区中,方便后续可以找到
dev->vfd = vfd;//作用和上面的差不多,相互记录,可以找到对方
}
/* 分析video_register_device函数的具体实现 */
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr(-1));
static inline int video_register_device(struct video_device *vdev,int type(VFL_TYPE_GRABBER), int nr(-1))
{
return __video_register_device(vdev, type(VFL_TYPE_GRABBER), nr(-1), 1, vdev->fops->owner);
}
int __video_register_device(struct video_device *vdev, int type(VFL_TYPE_GRABBER), int nr(-1),
int warn_if_nr_in_use(1), struct module *owner)
{
int minor_offset = 0;
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
vdev->minor = -1;
switch (type)
{
case VFL_TYPE_GRABBER:
name_base = "video";
break;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->v4l2_dev) //vdev->v4l2_dev = vfd->v4l2_dev = &dev->v4l2_dev;其中dev->v4l2_dev.ctrl_handler = hdl;
{
if (vdev->v4l2_dev->dev) //在v4l2_device_register函数中v4l2_dev->dev = dev(NULL);
{
vdev->parent = vdev->v4l2_dev->dev;
}
if (vdev->ctrl_handler == NULL)//dev->v4l2_dev.ctrl_handler = hdl;
{
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
}
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
{
vdev->prio = &vdev->v4l2_dev->prio;
}
}
/* Pick a device node number */
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);//minor_cnt = VIDEO_NUM_DEVICES(256)
上面一行代码等价于nr = devnode_find(vdev, 0 , 256);//找一个未用过的节点
/* The device node number and minor numbers are independent, so we just find the first free minor number. */
/* 设备节点和次设备号都独立的,所以来寻找第一个未用的设备号 */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)//在VIDEO_NUM_DEVICES(256)中找一个未用过的
if (video_device[i] == NULL) //找到了
break;
vdev->minor = i + minor_offset;//记录次设备号,minor_offset = 0;
vdev->num = nr;
/* Part 3: Initialize the character device */
/* 初始化字符设备 */
vdev->cdev = cdev_alloc();//动态分配一个cdev结构体
vdev->cdev->ops = &v4l2_fops;//操作函数集,在应用程序中调用
/*
v4l2_fops是在v4l2_dev.c(v4l2核心)实现
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,
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
这里举例说明一个函数,以read为例,
static ssize_t v4l2_read(struct file *filp, char __user *buf,size_t sz, loff_t *off)
{
struct video_device *vdev = video_devdata(filp);//根据应用程序传入的文件找到对应的节点,
然后在节点中找到对应的次设备号,最后再根据次设备号在video_device全局数据中找到之前存入的项
/* struct video_device *video_devdata(struct file *file)//代码如下:
{
return video_device[iminor(file->f_path.dentry->d_inode)];//其中video_device 在Part 6部分初始化的
}
*/
if (!vdev->fops->read)
return -EINVAL;
if (vdev->lock && mutex_lock_interruptible(vdev->lock))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->read(filp, buf, sz, off);
if (vdev->lock)
mutex_unlock(vdev->lock);
return ret;
}
*/
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);
if (vdev->parent)
vdev->dev.parent = vdev->parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);//name_base = "video"(part 1部分设置),vdev->num = nr(系统自动分配的);
ret = device_register(&vdev->dev); //在/dev下创建设备节点,因此在应用层就可以使用open read write等系统调用了,操作函数集:就是v4l2_fops里面的(vdev->cdev->ops = &v4l2_fops;)
#if defined(CONFIG_MEDIA_CONTROLLER)//这个宏没有被调用,所以,part 5不起作用
/* Part 5: Register the entity. */
#endif
/* Part 6: Activate this minor. The char device can now be used. */
/* 激活次设备号的设备,这个字符设备可以用了 */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);//标志着以及被用过了
video_device[vdev->minor] = vdev;//把一个video_device实体存入video_device这个全局数组中,
后面在应用层使用open read write等操作就是根据次设备号找到数组中对应的项。例如在 Part 3中
vdev->cdev->ops = &v4l2_fops;中的v4l2_fops操作函数集,里面的函数就是从filp中的inode结构体中找到次设备号,
然后取出来,就是之前根据次设备号存进去的内容
}
========================================================================================================================
分析应用层到内核驱动的调用(以read和ioctl为例分析)
应用层:调用read()
-------------------------------------------------------------
内核层
根据__video_register_device函数中代码 part 3部分有 vdev->cdev->ops = &v4l2_fops;
为此就调用v4l2_fops中的read
/* v4l2_fops原型如下
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,
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
*/
v4l2_fops中的read函数
static ssize_t v4l2_read(struct file *filp, char __user *buf,size_t sz, loff_t *off)
{
struct video_device *vdev = video_devdata(filp);
/*
struct video_device *video_devdata(struct file *file)
{
return video_device[iminor(file->f_path.dentry->d_inode)];//分解如下
iminor_my = iminor(file->f_path.dentry->d_inode);//根据filp找到inode,在根据inode使用函数iminor直接找到次设备号
return video_device[iminor_my];//根据次设备号,找到之前初始化填入的内容
}
*/
if (!vdev->fops->read)//判断是否为空,之前初始化了,不为空
return -EINVAL;
if (vdev->lock && mutex_lock_interruptible(vdev->lock))//在open调用的时候,已经解锁了
return -ERESTARTSYS;
if (video_is_registered(vdev))//在前面分析过了,在part6 部分被标志成已使用,set_bit(V4L2_FL_REGISTERED, &vdev->flags);//标志着以及被用过了
/*
static inline int video_is_registered(struct video_device *vdev)
{
return test_bit(V4L2_FL_REGISTERED, &vdev->flags);
}
*/
ret = vdev->fops->read(filp, buf, sz, off);
/* 这里是重点,到底是如何嵌套调用的呢? */
1.vdev 是从本函数内上代码中video_device[iminor_my]找到,也就是之前存入的
2.之前存入的是什么内容?
答:是函数 __video_register_device中的vdev,也就是__video_register_device中的参数,__video_register_device(vdev,...)被
video_register_device(vdev,....)函数调用,video_register_device在vivi_create_instance函数中 ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);这么调用
因此,__video_register_device中的vdev就是 vfd,vivi_create_instance中vfd按照下面方式初始化
*vfd = vivi_template;
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
重点看一下:*vfd = vivi_template,也就是说或vdev=vivi_template,因此ret = vdev->fops->read(filp, buf, sz, off);等同于vivi_template.fops->read(filp, buf, sz, off)
3.vivi_template.fops提供了open read write等操作函数吗?
static struct video_device vivi_template =
{
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
//vivi_fops操作函数集合如下
static const struct v4l2_file_operations vivi_fops =
{
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
所以vdev->fops->read(filp, buf, sz, off)最终就是调用下面的函数
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
static ssize_t vivi_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
{
struct vivi_dev *dev = video_drvdata(file);
dprintk(dev, 1, "read called\n");
return vb2_read(&dev->vb_vidq, data, count, ppos,file->f_flags & O_NONBLOCK);//vb2_read是内核框架实现的关于缓冲的操作
}
}
为此,应用层调用read函数是如何调用到内核层代码分析完毕
----------------------------------------------------------------------------------------------------------------------------------
应用层:调用ioctl()
-------------------------------------------------------------
内核层(过程和read差不多从简分析了,重点分析不一样的)
调用到 v4l2_fops->unlocked_ioctl
//在贴一遍v4l2_fops结构体,至于为什么不是调用v4l2_fops->ioctl而是v4l2_fops->unlocked_ioctl,还没搞清楚,应该就是没有ioctl,然后就只能调unlocked_ioctl了
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,
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
unlocked_ioctl = v4l2_ioctl;而v4l2_ioctl是内核实现的一个函数
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);//同read分析,根据filp找到次设备号,然后根据次设备号在video_device[]全局数组中找到之前根据次设备填充的内容
if (vdev->fops->unlocked_ioctl) //vdev->fops->unlocked_ioctl等价于vivi_template.vivi_fops->unlocked_ioctl
{
if (vdev->lock && mutex_lock_interruptible(vdev->lock))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
if (vdev->lock)
mutex_unlock(vdev->lock);
}
else if (vdev->fops->ioctl) //vivi_template.vivi_fops里没有ioctl
{
static DEFINE_MUTEX(v4l2_ioctl_mutex);
struct mutex *m = vdev->v4l2_dev ?
&vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;
if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->ioctl(filp, cmd, arg);
if (cmd != VIDIOC_DQBUF)
mutex_unlock(m);
}
else
ret = -ENOTTY;
/* vivi_template.vivi_fops中函数集合如下
static const struct v4l2_file_operations vivi_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
*/
其中video_ioctl2是内核实现的,原型如下
long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);//根据传入的cmd(命令),arg(参数),间接调用内核实现的另外一个函数__video_do_ioctl
}
在video_usercopy中核心点在于把应用程序的数据cmd和arg拷贝至内核空间,然后调用__video_do_ioctl函数
long video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,v4l2_kioctl func)
{
/* Handles IOCTL */
err = func(file, cmd, parg);//func = __video_do_ioctl
}
//__video_do_ioctl原型如下
static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg)
{//这里只展示核心代码(太多了)
//在此之前说一下:capabilities、priority、capture ioctls三者;
capabilities是应用程序使用ioctl(fd,VIDIOC_QUERYBUF),用于查询摄像设备的能力
priority是用ioctl(fd,VIDIOC_G_PRIORITY,用于查询优先级,但是一般应用层不调用
capture ioctls是用 例如ioctl(fd,VIDIOC_ENUM_FMT),VIDIOC_ENUM_FMT是不唯一的,用于对摄像设备进行各种设置
struct video_device *vfd = video_devdata(file);//vfd = vivi_template
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;//vfd->ioctl_ops也就是vivi_template.vivi_fops
/*
static struct video_device vivi_template =
{
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
static const struct v4l2_ioctl_ops vivi_ioctl_ops = //这里面的函数全是具有实际意义上的操作,太多不分析了
{
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
*/
switch (cmd)
{
/* --- capabilities ------------------------------------------ */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = (struct v4l2_capability *)arg;
if (!ops->vidioc_querycap)
break;
cap->version = LINUX_VERSION_CODE;
ret = ops->vidioc_querycap(file, fh, cap);
break;
}
/* --- priority ------------------------------------------ */
case VIDIOC_G_PRIORITY:
{
enum v4l2_priority *p = arg;
if (ops->vidioc_g_priority) {
ret = ops->vidioc_g_priority(file, fh, p);
} else if (use_fh_prio) {
*p = v4l2_prio_max(&vfd->v4l2_dev->prio);
ret = 0;
}
break;
}
case VIDIOC_S_PRIORITY:
{
enum v4l2_priority *p = arg;
if (!ops->vidioc_s_priority && !use_fh_prio)
break;
if (ops->vidioc_s_priority)
ret = ops->vidioc_s_priority(file, fh, *p);
else
ret = ret_prio ? ret_prio :
v4l2_prio_change(&vfd->v4l2_dev->prio,
&vfh->prio, *p);
break;
}
/* --- capture ioctls ---------------------------------------- */
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc *f = arg;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
if (likely(ops->vidioc_enum_fmt_vid_cap))
ret = ops->vidioc_enum_fmt_vid_cap(file, fh, f);
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
if (likely(ops->vidioc_enum_fmt_vid_cap_mplane))
ret = ops->vidioc_enum_fmt_vid_cap_mplane(file,
fh, f);
break;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
if (likely(ops->vidioc_enum_fmt_vid_overlay))
ret = ops->vidioc_enum_fmt_vid_overlay(file,
fh, f);
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
if (likely(ops->vidioc_enum_fmt_vid_out))
ret = ops->vidioc_enum_fmt_vid_out(file, fh, f);
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
if (likely(ops->vidioc_enum_fmt_vid_out_mplane))
ret = ops->vidioc_enum_fmt_vid_out_mplane(file,
fh, f);
break;
case V4L2_BUF_TYPE_PRIVATE:
if (likely(ops->vidioc_enum_fmt_type_private))
ret = ops->vidioc_enum_fmt_type_private(file,
fh, f);
break;
default:
break;
}
}
}
}
=================================================================================================================================