V4L2框架分析

       V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。v4L2是针对uvc(USB Video Class)免驱usb设备的编程框架,主要用于采集usb摄像头等。

      下图是V4L2的框架,首先系统核心层分配设置注册一个名为cdev结构体变量(cdev结构体是video_device结构体里的一部分),并设置cdev->ops = v4l2_fops;在硬件层我们分配设置注册了一个名为vfd结构体变量(video_device结构体),并设置vfd->fops = &vivi_fops,vfd->ioctl_ops  = &vivi_ioctl_ops;当应用程序(APP)调用read、open等函数时,会调用到v4l2_fops里的read、open函数,然后v4l2_fops里的read、open函数会再调用到硬件层相关的vfd->fops里的read、open函数。ioctl函数也类似。

        下面我们从程序入手来分析V4L2的框架,本文借助Linux内核目录下的drivers\medio\video里的虚拟视频驱动程序vivi.c(这段代码使用v4l2 api模拟真实的视频设备)来分析V4L2的框架。它的总体框架如下所示:

vivi_init
    vivi_create_instance
        v4l2_device_register   // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数
        video_device_alloc
        // 设置
          1. vfd:
            .fops           = &vivi_fops,
            .ioctl_ops 	= &vivi_ioctl_ops,
            .release	= video_device_release,
          2.
            vfd->v4l2_dev = &dev->v4l2_dev;
          3. 设置"ctrl属性"(用于APP的ioctl):
            	v4l2_ctrl_handler_init(hdl, 11);
            	dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
            	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
            	dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_CONTRAST, 0, 255, 1, 16);                        
        video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
            __video_register_device
                vdev->cdev = cdev_alloc();
                vdev->cdev->ops = &v4l2_fops;
                cdev_add
                
                video_device[vdev->minor] = vdev;

        		if (vdev->ctrl_handler == NULL)
        			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

①我们从vivi.c里的vivi_init函数入手发现它调用了v4l2_device_register,该函数用于初始化一些东西,比如自旋锁、引用计数,这个并不是必需的;②调用了video_device_alloc分配video_device结构体并对其进行相应的设置,例如

.fops             = &vivi_fops,
.ioctl_ops     = &vivi_ioctl_ops,
.release       = video_device_release,

等设置,然后video_register_device注册该结构体;

③video_register_device函数调用了__video_register_device实现了如下操作:

vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
cdev_add
                
video_device[vdev->minor] = vdev;

if (vdev->ctrl_handler == NULL)
    vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

上图是vivi_create_instance函数的一部分,首先分配一个video_device结构体的变量vfd,然后设置*vfd = vivi_template;其中vivi_template是一个video_device的结构体变量,它本身设置好了一些如.fops之类信息(如下图),此操作便相当于设置

 1. vfd:

.fops             = &vivi_fops,
.ioctl_ops     = &vivi_ioctl_ops,
.release       = video_device_release,

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,
};

然后进入video_register_device函数,下面是video_register_device里的一部分源码,首先分配一个cdev结构体

然后设置cdev->ops = &v4l2_fops;v4l2_fops本身指向了一些函数(如下图),这样cdev便也指向了这些函数,当APP调用read函数时,便会调用cdev里面的read函数

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,
};

cdev里面的read函数如下图,首先根据filp获取到video_device结构体,然后判断该video_device结构体里的read函数是否存在,若存在则调用它,所以最后便调用到了前面我们设置的vfd.fops里的read函数。

相比open、read函数,ioctl的调用过程更复杂一些,下面我们来看一下(我们以VIDIOC_QUERYCAP为例)。下图是v4l2_fops里的.unlocked_ioctl指向的v4l2_ioctl函数。

它调用了前面vivi_template的fops里面的ioctl。

vivi_template的fops里面的ioctl里调用到下图的__video_do_ioctl函数,该函数最终调用到vfd里的ioctl_ops成员里面的函数,即vivi_ioctl_ops里的函数

比如调用的是 VIDIOC_QUERYCAP,则最终会调用到下面的函数。

/* ------------------------------------------------------------------
	IOCTL vidioc handling
   ------------------------------------------------------------------*/
static int vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	struct vivi_dev *dev = video_drvdata(file);

	strcpy(cap->driver, "vivi");
	strcpy(cap->card, "vivi");
	strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
	cap->version = VIVI_VERSION;
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \
			    V4L2_CAP_READWRITE;
	return 0;
}

总结:ioctl的调用比open、read多了一层,当APP调用ioctl函数时,便会调用cdev里面的ioctl函数,然后调用到了前面我们设置的vfd.fops里的ioctl函数,即和read、open函数同一结构体里的v4l2_ioctl,然后最终再去调用到和  

.fops           = &vivi_fops,同一结构体里的
.ioctl_ops     = &vivi_ioctl_ops,里对应的函数。

相关文章:

https://blog.csdn.net/qingkongyeyue/article/details/53447331

https://blog.csdn.net/qingkongyeyue/article/details/52372960

发布了42 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_37659294/article/details/104139839
今日推荐