精华内容
参与话题
问答
  • BCGControlBar Pro 22.1 完美破解版V4

    千次下载 热门讨论 2014-03-19 18:10:20
    BCGControlBar Pro 22.1 完美破解版V4 by hsluoyz veotax akisn0w 2014.03.19 BCGControlBar Pro 22.1破解版本V4介绍: 1)12个dll,分为60、70、71、80、90、100六个版本,分别支持VC6、VC2002、VC2003、VC2005...
  • linux V4l2 摄像头问题

    2015-05-17 19:30:42
    stream_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if(-1 == ioctl(fd,VIDIOC_S_FMT,&stream_fmt)) { perror("Fail to ioctl"); exit(EXIT_...
  • V4L2

    千次阅读 2017-08-31 10:15:16
    v4L2是针对uvc免驱usb设备的编程框架 ,主要用于采集usb摄像头等 HIGHGUI ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV Unable to stop the stream.: Bad file ...

    video for linux 2


    v4L2是针对uvc免驱usb设备的编程框架 ,主要用于采集usb摄像头等




    HIGHGUI ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV
    Unable to stop the stream.: Bad file descriptor



    遇到问题的解释链接:https://www.linuxquestions.org/questions/programming-9/opencv-pixel-format-of-incoming-image-is-unsupported-by-opencv-842801/

    Make sure that you have video for linux control panel installed (v4l2ucp) and also ensure that you have the v4l2convert.so in /usr/lib/libv4l/ (it should all be part of the video for linux package I think?

    root@localhost~# aptitude install v4l2ucp

    then run v4l2ucp, go to preview->configure preview in the video for linux control panel (v4l2ucp) application it should have as the enviroment and application to launch for the view, eg. mplayer or whatever you set it to.

    LD_PRELOAD=/usr/lib/libv4l/v4l2convert.so as the environment variable
    and tv:// as the argument

    the key here is setting v4l2convert.so as a preloader to the viewer, which i think converts the device stream from the camera to a format suitable for v4l2 devices. If you do this before running your opencv application it should fix the problem.

    just cd to the directory of your application and type

    LD_PRELOAD=/usr/lib/libv4l/v4l2convert.so ./youropencvapp

    or you can add it as an environment variable in your launch configuration if you are working with an IDE such as eclipse

    Hope this helps someone!

    展开全文
  • V4L2框架-v4l2 device

    千次阅读 2018-06-23 11:22:00
    本文对 V4L2 中比较容易理解的骨干结构进行介绍,涉及两个核心结构体:v4l2_device, v4l2_subdev。文章围绕这两个结构体以 Linux-4.4 内核的 omap3isp 代码为例进行相关的介绍,所谓介绍还是起到辅助作用,真真儿的...

    阅读原文

    本文对 V4L2 中比较容易理解的骨干结构进行介绍,涉及两个核心结构体:v4l2_devicev4l2_subdev。文章围绕这两个结构体以 Linux-4.4 内核的 omap3isp 代码为例进行相关的介绍,所谓介绍还是起到辅助作用,真真儿的还是要靠 RTFSC、WTFSC。

    00 - V4L2框架概述

    下面「该例程」均指的是 omap3isp 这个例程。

    V4L2 框架补充

    首先看图:
    V4L2 子模块划分

    V4L2 子模块划分

    这并不是官方的划分,是我自己根据自己感觉划分出来的,也就是那么回事儿吧。那么这篇文章里面秀的主题就是子设备系统这一块,就是上面图中的第三个「subdev」。由图也可知,本文主要是讲如何管理众多的输入设备。注意:是所有的设备,并不是运行时的数据流通路上面的设备。

    主设备

    主设备使用 v4l2_device 进行抽象化表示。该例程使用了设备树来进行设备解析,使用平台驱动进行相应的驱动 probe。在文件 drivers/media/platform/omap3isp/isp.c 中有 isp_probe 函数,该函数的第一个参数定义即是 isp_device,回忆前面的文章,里面有提到大多数情况下,需要将 v4l2_device 结构体嵌入到一个更大的结构体里面使用,此为驱动自定义的结构体,那就是它了,那么它的定义如下(删去了与分析无关的部分):

    struct isp_device {
    	struct v4l2_device v4l2_dev;
    	struct v4l2_async_notifier notifier;
    	struct media_device media_dev;
    
    	... ...
    }
    

    其中与本节有关的就属于是 struct v4l2_device v4l2_dev 这个了,它将作为串联管理整个 omap3isp 的管理者的存在。

    同时它的用法契合前面讲的「嵌入式」,struct isp_device 是驱动自定义的结构体,这个结构体可以看作是整个 omap3isp 的设备抽象化结构体,也就是说:它就代表了 omap3isp 这个大的设备。下面将看到它的用法。

    注册 v4l2_device

    在 isp.c 的 isp_probe 函数中有调用 isp_register_entities 函数,里面开头的内容大致如下:

    ... ...
    	isp->v4l2_dev.mdev = &isp->media_dev;
    	ret = v4l2_device_register(isp->dev, &isp->v4l2_dev);
    	if (ret < 0) {
    		dev_err(isp->dev, "%s: V4L2 device registration failed (%d)\n",
    			__func__, ret);
    		goto done;
    	}
    ... ...
    

    其实可以看到注册函数里面并没有把高设备加入到一个链表里面什么的,而是初始化结构体成员,比如子设备链表头初始化,增加 dev 的引用等。在一切子设备被串联起来之前首先要初始化注册 v4l2_device 这个总的设备。

    v4l2_subdev

    该结构体是抽象化的子设备,用于子设备管理之用。它有「初始化」、「注册子设备」、「注册子设备节点」等几个操作。通常情况下「初始化」的函数实例被定义在一个个的子设备驱动模块内部,「注册子设备」与「注册子设备节点」这两个函数的实体被定义在父设备模块内部,比如: isp.c 中。

    上代码(isp_probe 函数中):

    ret = isp_initialize_modules(isp);
    if (ret < 0)
    	goto error_iommu;
    ret = isp_register_entities(isp);
    if (ret < 0)
    	goto error_modules;
    

    下面首先看下 isp_initialize_modules 函数中的实现:

    static int isp_initialize_modules(struct isp_device *isp)
    {
    	int ret;
    
    	ret = omap3isp_csiphy_init(isp);
    
    	ret = omap3isp_csi2_init(isp);
    
    ... ...
    
    	ret = omap3isp_h3a_aewb_init(isp);
    
    	ret = omap3isp_h3a_af_init(isp);
    ... ...
    	return 0;
    ... ...
    

    再进去 omap3isp_xxxx_init 函数里面就可以看到有类似 v4l2_subdev_init 的函数调用了,文章尽量少贴代码,多讲实现、使用机理,相信你一定能自己找到相关代码的。

    所以结构大体上是这样子的:

    1. 你有一个输入设备 omap3isp,它的管理者列在一个单独的代码文件里面名为 isp.c
    2. 定义一个自定义的抽象化结构体代表 omap3isp 这个设备,名为 isp_device,并把 v4l2_device 嵌入内部作为子设备管理工具;
    3. 把子设备-类似 csi、preview、3a 等抽象化为一个个子设备,每个子设备一个代码文件,名为 ispxxx.c,分别有自己的抽象化结构体,名为 isp_xxx_dev,内部嵌入了 v4l2_subdev 作为子设备的抽象工具使用。同时实现自己的设备初始化函数,名为 xxx_init_eneities
    4. 在管理者 isp.c 的 probe 函数里面调用子设备的 xxx_init_entities,子设备初始化函数里面会做好 v4l2_subdev 的初始化工作;
    5. 管理者的 probe 函数里面注册 v4l2_device,注册子设备,必要时注册子设备节点在用户空间生成 /dev/nodeX
    6. 大功告成,此时你就可以通过 v4l2_device 来管理所有的子设备了,框架本身提供了很好用的管理方式与相关的回调函数、结构体成员等等。

    设备的管理

    设备的管理必然需要主设备与子设备之间能够互联互通,否则的话谈何去管理,本节就介绍如何实现主设备与子设备之间的数据互联互通。

    主设备子设备互通

    通过上面的步骤建立了连接之后怎么从主设备找到子设备呢?如何从子设备找到主设备?如何从 v4l2_device 到自定义的主设备抽象结构体?如何从 v4l2_subdev 到子设备自定义的结构体?

    • 如何从主设备找到子设备
      首先需要获取 v4l2_device 结构体,然后可以使用 list_for_each_entry 来对子设备进行遍历,其中子设备的结构体内部有一个 name 成员,长度为32个字节,这个字段要求是整个 v4l2_device 下属唯一的,所以要想找到某一个指定的子设备完全可以在遍历的时候对比子设备的 name 字段看是不是自己想要找的。
    • 如何从子设备找到主设备
      v4l2_subdev 的结构体里面有一个 v4l2_dev 的指针成员,该成员会在子设备被注册的时候指向 v4l2_device 成员,注册函数为 v4l2_device_register_subdev。在该步骤执行完毕之后就可以通过获取子设备结构体内部的 v4l2_dev 成员来获得主设备结构体。
    • 如何从主设备到主设备实例化结构体
      可以看到 v4l2_device 内部并没有什么私有指针之类的东西,那怎么去找到主设备的实例化结构体呢,此时可以通过另一种偏门方法获取,比如在定义结构体的时候把 v4l2_device 放在结构体成员的第一个,之后通过 v4l2_subdev 获取到 v4l2_device 之后就可以把其地址强制转换为主设备自定义的实例化结构体来实现访问。
    • 如何从子设备到子设备实例化结构体
      子设备内部有两个私有的指针:dev_privhost_priv。前一个好理解也很好使用,使用的时候就调用 v4l2_set_subdevdata 函数将 dev_priv 指向子设备实例化结构体即可,然后就可以用 v4l2_get_subdevdata 来从 v4l2_subdev 获取到子设备结构体实例化的结构体数据了。后一个不是很好理解其用处,但是也可以通过 v4l2_set_subdev_hostdata/v4l2_get_subdev_hostdata 来进行设置/获取,host 也即主控端,比如一个 camera sensor 的 SOC 端的控制器就可以作为主控端,再比如使用 I2C 进行通信的 camera sensor 的 SOC 端的 I2C 控制器就可以作为 host_priv,必要时通过 I2C 来控制子设备的行为。或者干脆把主设备实例化的结构体作为 host data 也可以。

    主子设备信息交流

    本节使用多个实际的用例来深入解释下各种信息交流方式与情景。比如:如何控制访问指定类型的子设备?子设备如何向主设备回返通知?

    • 访问所有的 sensor 设备并关闭其数据流
    1. 子设备注册的时候应该要提供了相关的操作函数,那就是 v4l2_subdev_ops 这个结构体了,在此例中我们就仅仅设置其 video 成员的 s_stream 成员。
    2. 提供子设备组 id,也就是 v4l2_subdevgrp_id 成员,此处我们设置为一个我自己假定的枚举类型(你只要保证这个枚举类型是整个 v4l2_device 下属唯一的就行),我假定为 OMAP3ISP_CAMSENSOR
    3. 初始化并注册子设备,就不再多说了,初始化以及注册的方式前面都有提到过了。
    4. 执行 v4l2_device_call_all(v4l2_device, OMAP3ISP_CAMSENSOR, video, s_stream, 0);,此时会遍历挂在 v4l2_device 名下的所有的 OMAP3ISP_CAMSENSOR 组的子设备,调用其 s_stream 的模块函数进行数据流的关闭。
    • 子设备数据流关闭后向主设备回返通知
    1. 需要提供主设备的 notify 成员操作函数。
    2. 定义好 notification 的格式,比如我自己的定义,高8位表示哪个子设备,次8位表示哪种类型的操作(此处是 video 类型的 ops),再次8位表示具体的操作函数(s_stream),低8位表示操作值(0关闭)。
    3. 子设备调用 v4l2_subdev_notify 函数进行正式通知的发送,此时也可以带一些参数,只需要传递其地址就可以了,主子设备端商定好数据的格式即可。
    4. 主设备收到通知之后进行相关的操作。

    交通枢纽 video_device

    该结构体整合了数据流管理的终端模块功能,负责提供从内核空间到用户空间的数据交流,属于非常重要的一个功能了,必不可少的那种。

    通常情况下所有的子设备都可以注册一个 video_device 结构体来在用户空间生成一个设备节点以供用户进行操作,但是区别在于只有负责真正传递视频数据的那个模块用得着注册 video 类型的设备节点名称(比如内核输入设备数据链的 DMA 数据终端),其它的使用 v4l-subdev 类型的就可以了。

    video_device 只与 v4l2_device 进行绑定关联,通过后者这层关系可以访问到整个子设备网络的资源。

    如何注册 video 类型节点

    使用 video_register_device 配合 VFL_TYPE_GRABBER 参数进行注册,此时该函数执行完毕并返回的时候就可以在用户空间看到形如 /dev/videoX 的设备节点了。

    注意需要提供其操作函数,类似下面的:

    static struct v4l2_file_operations isp_video_fops = {
    	.owner = THIS_MODULE,
    	.unlocked_ioctl = video_ioctl2,
    	.open = isp_video_open,
    	.release = isp_video_release,
    	.poll = isp_video_poll,
    	.mmap = isp_video_mmap,
    };
    

    关于其成员如何实现本节不详细介绍,在后面的 videobuf2 一文中会进行详细介绍。

    如何注册其它类型节点

    对于 v4l2 输入设备来说,使用 v4l2_device_register_subdev_nodes 来进行批量的设备节点注册,它内部依然会调用 video_register_device 函数,只不过会使用 VFL_TYPE_SUBDEV 类型来代替上面 VFL_TYPE_GRABBER,那么在用户空间生成的设备节点名称就是 v4l-subdevX 了。

    这种情况下注册的设备节点的操作函数是在 v4l2-device.c 里面定义好的默认操作函数,它的定义如下:

    const struct v4l2_file_operations v4l2_subdev_fops = {
    	.owner = THIS_MODULE,
    	.open = subdev_open,
    	.unlocked_ioctl = subdev_ioctl,
    #ifdef CONFIG_COMPAT
    	.compat_ioctl32 = subdev_compat_ioctl32,
    #endif
    	.release = subdev_close,
    	.poll = subdev_poll,
    };
    

    结束语

    到这里为止,基本上关于设备这块的基础操作已经介绍完毕,至于还有一些比较高级的操作就放在后续的文章里面进行介绍了。预告一下,下一篇文章是讲 media framework 的,循序渐进。该篇文章读完并实践之后就可以在用户空间看到有 /dev/video 设备节点了,可以写一个小的测试用例,当打开设备节点的时候遍历一遍子设备把子设备的名字打出来,也可以增加更详细的信息,总之,本文的目的是实现一个 v4l2_device 管理框架下的设备拓扑,你可以把这个拓扑结构打印出来就算是完美完成任务了。

    以防万一有滴同学不知道:
    「RTFSC」:Read The Fxxking Source Code
    「WTFSC」:Dao Li Tong Shang


    想做的事情就去做吧
    展开全文
  • V4L2框架概述

    万次阅读 多人点赞 2018-06-17 15:25:07
    本文开启 linux 内核 V4L2 框架部分的学习之旅,本文仅先对 V4L2 的框架做一个综述性的概括介绍,然后接下来的文章中会对 V4L2 框架的各个子模块进行一个全面的介绍,包括每一部分的实现原理,如何使用,用在什么...

    原文链接

    本文开启 linux 内核 V4L2 框架部分的学习之旅,本文仅先对 V4L2 的框架做一个综述性的概括介绍,然后接下来的文章中会对 V4L2 框架的各个子模块进行一个全面的介绍,包括每一部分的实现原理,如何使用,用在什么地方等等。预计接下来的文章大概有5篇(不带本篇)。坑已经挖好了,开始吧。

    导读:V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架。其广泛应用在嵌入式设备以及移动端、个人电脑设备上面,市面上的编码产品类如:SDV、手机、IPC、行车记录仪都会用到这个框架来进行视频采集,当然,有比较厉害的厂家直接就使用自己实现的一套视频采集框架,这种属于是厂家中战斗机了。下文主要参考linux-4.4内核文档对V4L2框架进行一次全局的介绍。

    V4L2框架简介

    几乎所有的设备都有多个 IC 模块,它们可能是实体的(例如 USB 摄像头里面包含 ISP、sensor 等)、也可能是抽象的(如 USB 设备里面的抽象拓扑结构),它们在 /dev 目录下面生成了多个设备节点,并且这些 IC 模块还创建了一些非 v4l2 设备:DVB、ALSA、FB、I2C 和输入设备。正是由于硬件的复杂性,v4l2 的驱动也变得非常复杂。

    特别是 v4l2 驱动要支持 IC 模块来进行音/视频的混合/编解码操作,这就更加使得 v4l2 驱动变得异常复杂。通常情况下,有些IC模块通过一个或者多个 I2C 总线连接到主桥驱动上面,同时其它的总线仍然可用,这些 IC 就称为 ‘sub-devices’,比如摄像头设备里面的 sensor 传感器就是使用 I2C 来进行命令沟通,同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。

    在很长一段时间内,该框架(指老旧的 V4L2 框架)仅限于通过 video_device 结构体创建 v4l 设备节点和 video_buf 来处理视频数据。这意味着所有的驱动都必须对设备实例进行设置并将其映射到子设备上。有些时候这些操作步骤十分复杂,很难正确完成,并且有些驱动程序从来没有正确的按照这些操作步骤编写。由于缺少一个框架,有很多通用代码就没有办法被重构,从而导致这部分代码被重复编写,效率比较低下。

    因此,本框架抽象构建了所有驱动都需要的代码并封装为一个个的模块,简化了设备驱动通用代码的重构。v4l2-pci-skeleton.c 是个非常好的参考例程,它是一个PCI采集卡的驱动框架。该例程演示了如何使用 v4l2 驱动框架,并且该例程可以作为一个 PCI 视频采集卡的驱动模板使用。在最开始的时候也可以参照这个代码编写方式进行联系,当然最适合的代码还是 drivers/media/video/omap3isp 文件夹里面的代码,这个代码基本上可以作为一个完整的输入设备实例代码(因为它包含了 ISP、CSI、video 等设备,并且有着一个完整的数据流 pipeline,几乎用到了 V4L2 框架的方方面面,参考价值极大)来进行参考编写自己的设备驱动代码。

    V4L2框架蓝图

    蓝图解构

    这是一张非常大的图,但是我只选取了其中的一个,这张图对 V4L2 里面的子模块进行简化(简化到只有子模块的名字,没有内部实现的介绍),大图如下:
    V4L2 设备拓扑

    V4L2 设备拓扑

    这张图怎么看呢?它有以下几个关键因素:

    • v4l2_device:这个是整个输入设备的总结构体,可以认为它是整个 V4L2 框架的入口,充当驱动的管理者以及入口监护人。由该结构体引申出来 v4l2_subdev。用于视频输入设备整体的管理,有多少输入设备就有多少个v4l2_device抽象(比如一个USB摄像头整体就可以看作是一个 V4L2 device)。再往下分是输入子设备,对应的是例如 ISP、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的。
    • media_device:用于运行时数据流的管理,嵌入在 V4L2 device 内部,运行时的意思就是:一个 V4L2 device 下属可能有非常多同类型的子设备(两个或者多个 sensor、ISP 等),那么在设备运行的时候我怎么知道我的数据流需要用到哪一个类型的哪一个子设备呢。这个时候就轮到 media_device 出手了,它为这一坨的子设备建立一条虚拟的连线,建立起来一个运行时的 pipeline(管道),并且可以在运行时动态改变、管理接入的设备。
    • v4l2_ctrl_handler:控制模块,提供子设备(主要是 video 和 ISP 设备)在用户空间的特效操作接口,比如你想改变下输出图像的亮度、对比度、饱和度等等,都可以通过这个来完成。
    • vb2_queue:提供内核与用户空间的 buffer 流转接口,输入设备产生了一坨图像数据,在内核里面应该放在哪里呢?能放几个呢?是整段连续的还是还是分段连续的又或者是物理不连续的?用户怎么去取用呢?都是它在管理。

    层级解构

    1. 可以看到图中的入口 custom_v4l2_dev,它是由用户定义的一个结构体,重要的不是它怎么定义的,重要的是它里面有一个 v4l2_device 结构体,上文说到,这个结构体总览全局,运筹帷幄,相当于中央管理处的位置,那么中央决定了,它就是整个输入设备整体的抽象(比如整个 USB 摄像头输入设备,比如整个 IPC 摄像头输入设备)。它还有一个 media_device 结构体,上文也说道,,它是管数据流线路的,属于搞结构路线规划管理的。
    2. 往后 v4l2_device 里面有一个链表,它维护了一个巨大的子设备链,所有的子设备都通过内核的双向循环链表结构以 v4l2_device 为中心紧紧团结在一起。另外 media_device 在往里面去就是一个个的 media_entity(现在不需要了解它的具体含义,只需要知道它就是类似电路板上面的元器件一样的抽象体),media_entity 之间建立了自己的小圈子,在它们这个小圈子里面数据流按照一定的顺序畅通无阻,恣意遨游。
    3. 到结尾处,抽象出来了 /dev/videoX 设备节点,这个就是外交部的角色,它负责提供了一个内核与用户空间的交流枢纽。需要注意的是,该设备节点的本质还是一个字符设备,其内部的一套操作与字符设备是一样的,只不过是进行了一层封装而已。
    4. 到此为止,一个 V4L2 大概的四层结构就抽象出来了,如下图所示:
      V4L2 层次结构
    V4L2 层次结构

    驱动结构体

    所有的 V4L2 驱动都有以下结构体类型:

    • 每个设备都有一个设备实例结构体(上面的 custom_v4l2_dev),里面包含了设备的状态;
    • 一种初始化以及控制子设备(v4l2_subdev)的方法;
    • 创建v4l2设备节点并且对设备节点的特定数据(media_device)保持跟踪;
    • 含有文件句柄的文件句柄结构体(v4l2_fh 文件句柄与句柄结构体一一对应);
    • 视频数据处理(vb2_queue);

    结构体实例

    • 框架结构体(media_device
      与驱动结构体非常类似,参考上面的解释,这里不再赘述。v4l2 框架也可以整合到 media framework 里面。如果驱动程序设置了 v4l2_devicemdev 成员,那么子设备与 video 节点都会被自动当作 media framework 里的 entitiy 抽象。

    • v4l2_device 结构体
      每一个设备实例都被抽象为一个 v4l2_device 结构体。一些简单的设备可以仅分配一个 v4l2_device 结构体即可,但是
      大多数情况下需要将该结构体嵌入到一个更大的结构体(custom_v4l2_dev)里面。必须用 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); 来注册设备实例。该函数会初始化传入的 v4l2_device 结构体,如果 dev->driver_data 成员为空的话,该函数就会设置其指向传入的 v4l2_dev 参数。

    • 集成 media framework
      如果驱动想要集成 media framework 的话,就需要人为地设置 dev->driver_data 指向驱动适配的结构体(该结构体由
      驱动自定义- custom_v4l2_dev,里面嵌入 v4l2_device 结构体)。在注册 v4l2_device 之前就需要调用 dev_set_drvdata 来完成设置。并且必须设置 v4l2_decicemdev 成员指向注册的 media_device 结构体实例。

    • 设备节点的命名
      如果 v4l2_devicename 成员为空的话,就按照 dev 成员的名称来命名,如果 dev 成员也为空的话,就必须在注册 v4l2_device 之前设置它的 name 成员。可以使用 v4l2_device_set_name 函数来设置 name 成员,该函数会基于驱动名以及驱动实例的索引号来生成 name 成员的名称,类似于 ivtv0、ivtv1 等等,如果驱动名的最后一个字母是整数的话,生成的名称就类似于cx18-0、cx18-1等等,该函数的返回值是驱动实例的索引号。

    • 回调函数与设备卸载
      还可以提供一个 notify() 回调函数给 v4l2_device 接收来自子设备的事件通知。当然,是否需要设置该回调函数取决于子设备是否有向主设备发送通知事件的需求。v4l2_device 的卸载需调用到 v4l2_device_unregister 函数。在该函数被调用之后,如果 dev->driver_data 指向 v4l2_device 的话,该指针将会被设置为NULL。该函数会将所有的子设备全部卸载掉。如果设备是热拔插属性的话,当 disconnect 发生的时候,父设备就会失效,同时 v4l2_device 指向父设备的指针也必须被清除,可以调用 v4l2_device_disconnect 函数来清除指针,该函数并不卸载子设备,子设备的卸载还是需要调用到 v4l2_device_unregister 来完成。如果不是热拔插设备的话,就不必关注这些。

    驱动设备使用

    有些时候需要对驱动的所有设备进行迭代,这种情况通常发生在多个设备驱动使用同一个硬件设备的情况下,比如 ivtvfb 驱动就是个 framebuffer 驱动,它用到了 ivtv 这个硬件设备。可以使用以下方法来迭代所有的已注册设备:

    static int callback(struct device *dev, void *p)
    {
    	struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
    
    	/* test if this device was inited */
    	if (v4l2_dev == NULL)
    		return 0;
    	...
    	return 0;
    }
    
    int iterate(void *p)
    {
    	struct device_driver *drv;
    	int err;
    
        /* Find driver 'ivtv' on the PCI bus.
        * pci_bus_type is a global. For USB busses use usb_bus_type.
        */
    	drv = driver_find("ivtv", &pci_bus_type);
    	/* iterate over all ivtv device instances */
    	err = driver_for_each_device(drv, NULL, p, callback);
    	put_driver(drv);
    	return err;
    }
    

    有时候需要对设备实例进行计数以将设备实例映射到模块的全局数组里面,可以使用以下步骤来完成计数操作:

    static atomic_t drv_instance = ATOMIC_INIT(0);
    
    static int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
    {
    	...
    	state->instance = atomic_inc_return(&drv_instance) - 1;
    }
    

    如果一个热拔插设备有很多个设备节点(比如一个USB摄像头可以产生多路视频输出,虽然它的视频源是一个),那么很难知道在什么时候才能够安全地卸载 v4l2_device 设备。基于以上问题, v4l2_device 引入了引用计数机制,当 video_register_device 函数被调用的时候,引用计数会加一,当 video_device 被释放的时候,引用计数会减一,直到 v4l2_device 的引用计数到0的时候,v4l2_devicerelease 回调函数就会被调用,可以在该回调函数里面做一些清理工作。当其它的设备(alsa,因为这个不属于 video 设备,所以也就不能使用上面的 video 函数进行计数的加减操作)节点被创建的时候,可以人为调用以下函数对引用计数进行增减操作:

    	void v4l2_device_get(struct v4l2_device *v4l2_dev);
    	int v4l2_device_put(struct v4l2_device *v4l2_dev);
    

    需要注意的是,v4l2_device_register 函数将引用计数初始化为1,所以需要在 remove 或者 disconnect 回调方法里面调用 v4l2_device_put 来减少引用计数,否则引用计数将永远不会达到0。


    v4l2_subdev 结构体

    很多设备都需要与子设备进行交互,通常情况下子设备用于音视频的编解码以及混合处理,对于网络摄像机来说子设备就是 sensors 和 camera 控制器。通常情况下它们都是 I2C 设备,但也有例外。v4l2_subdev 结构体被用于子设备管理。

    每一个子设备驱动都必须有一个 v4l2_subdev 结构体,这个结构体可以作为独立的简单子设备存在,也可以嵌入到更大的结构体(自定义的子设备结构体)里面。通常会有一个由内核设置的低层次结构体(i2c_client,也就是上面说的 i2c 设备),它包含了一些设备数据,要调用 v4l2_set_subdevdata 来设置子设备私有数据指针指向它,这样的话就可以很方便的从 subdev 找到相关的 I2C 设备数据(这个要编程实现的时候才能够了解它的用意)。另外也需要设置低级别结构的私有数据指针指向 v4l2_subdev 结构体,方便从低级别的结构体访问 v4l2_subdev 结构体,达到双向访问的目的,对于 i2c_client 来说,可以用 i2c_set_clientdata 函数来设置,其它的需使用与之相应的函数来完成设置。

    桥驱动器需要存储每一个子设备的私有数据,v4l2_subdev 结构体提供了主机私有数据指针成员来实现此目的,使用以下函数可以对主机私有数据进行访问控制:

    	v4l2_get_subdev_hostdata();
    	v4l2_set_subdev_hostdata();
    

    从桥驱动器的角度来看,我们加载子设备模块之后可以用某种方式获取子设备指针。对于 i2c 设备来说,调用 i2c_get_clientdata 函数即可完成,其它类型的设备也有与之相似的操作,在内核里面提供了不少的帮助函数来协助完成这部分工作,编程时可以多多使用。

    每个 v4l2_subdev 结构体都包含有一些函数指针,指向驱动实现的回调函数,内核对这些回调函数进行了分类以避免出现定义了一个巨大的回调函数集,但是里面只有那么几个用得上的尴尬情况。最顶层的操作函数结构体内部包含指向各个不同类别操作函数结构体的指针成员,如下所示:

    	struct v4l2_subdev_core_ops {
    		int (*log_status)(struct v4l2_subdev *sd);
    		int (*init)(struct v4l2_subdev *sd, u32 val);
    		...
    	};
    
    	struct v4l2_subdev_tuner_ops {
    		...
    	};
    
    	struct v4l2_subdev_audio_ops {
    		...
    	};
    
    	struct v4l2_subdev_video_ops {
    		...
    	};
    
    	struct v4l2_subdev_pad_ops {
    		...
    	};
    
    	struct v4l2_subdev_ops {
    		const struct v4l2_subdev_core_ops	*core;
    		const struct v4l2_subdev_tuner_ops	*tuner;
    		const struct v4l2_subdev_audio_ops	*audio;
    		const struct v4l2_subdev_video_ops	*video;
    		const struct v4l2_subdev_vbi_ops	*vbi;
    		const struct v4l2_subdev_ir_ops		*ir;
    		const struct v4l2_subdev_sensor_ops	*sensor;
    		const struct v4l2_subdev_pad_ops	*pad;
    	};
    

    这部分的设计我个人觉得是非常实用的,linux 要想支持大量的设备的同时又要保持代码的精简就必须得这样去实现。core ops成员对于所有的子设备来说都是通用的,其余的成员不同的驱动会有选择的去使用,例如:video 设备就不需要支持 audio 这个 ops 成员。子设备驱动的初始化使用 v4l2_subdev_init 函数来完成(该函数只是初始化一些 v4l2_subdev 的成员变量,内容比较简单),在初始化之后需要设置子设备结构体的 nameowner 成员(如果是 i2c 设备的话,这个在 i2c helper 函数里面就会被设置)。该部分 ioctl 可以直接通过用户空间的 ioctl 命令访问到(前提是该子设备在用户空间生成了子设备节点,这样的话就可以操作子设备节点来进行 ioctl)。内核里面可以使用 v4l2_subdev_call 函数来对这些回调函数进行调用,这个在 pipeline 管理的时候十分受用。

    如果需要与 media framework 进行集成,必须初始化 media_entity 结构体并将其嵌入到 v4l2_subdev 结构体里面,操作如下所示:

    	struct media_pad *pads = &my_sd->pads;
    	int err;
    
    	err = media_entity_init(&sd->entity, npads, pads, 0);
    

    其中 pads 结构体变量必须提前初始化,media_entityflagsnametypeops 成员需要设置。entity 的引用计数在子设备节点被打开/关闭的时候会自动地增减。在销毁子设备的时候需使用 media_entity_cleanup 函数对 entity 进行清理。如果子设备需要处理 video 数据,就需要实现 v4l2_subdev_video_ops 成员,如果要集成到 media_framework 里面,就必须要实现 v4l2_subdev_pad_ops 成员,此时使用 pad_ops 中与 format 有关的成员代替 v4l2_subdev_video_ops 中的相关成员。

    子设备驱动需要设置 link_validation 成员来提供自己的 link validation 函数,该回调函数用来检查 pipeline 上面的所有的 link 是否有效(是否有效由自己来做决定),该回调函数在 media_entity_pipeline_start 函数里面被循环调用。如果该成员没有被设置,那么 v4l2_subdev_link_validate_default 将会作为默认的回调函数被使用,该函数确保 link 的 source pad 和 sink pad 的宽、高、media 总线像素码是一致的,否则就会返回错误。

    有两种方法可以注册子设备(注意是设备,不是设备驱动,常用的方式是通过设备树来注册),第一种(旧的方法,比如使用 platform_device_register 来进行注册)是使用桥驱动去注册设备。这种情况下,桥驱动拥有连接到它的子设备的完整信息,并且知道何时去注册子设备,内部子设备通常属于这种情况。比如 SOC 内部的 video 数据处理单元,连接到 USB 或 SOC 的相机传感器。另一种情况是子设备必须异步地被注册到桥驱动上,比如基于设备树的系统,此时所有的子设备信息都独立于桥驱动器。使用这两种方法注册子设备的区别是 probing 的处理方式不同。也就是一种是设备信息结构体由驱动本身持有并注册,一种是设备信息结构体由设备树持有并注册。

    设备驱动需要用 v4l2_device 信息来注册 v4l2_subdev ,如下所示:

    	int err = v4l2_device_register_subdev(v4l2_dev, sd);
    

    如果子设备模块在注册之前消失的话,该操作就会失败,如果成功的话就会使得 subdev->dev 指向 v4l2_device。如果 v4l2_device 父设备的 mdev 成员不为空的话,子设备的 entity 就会自动地被注册到 mdev 指向的 media_device 里面。在子设备需要被卸载并且 sd->dev 变为NULL之后,使用如下函数来卸载子设备:

    	v4l2_device_unregister_subdev(sd);
    

    如果子设备被注册到上层的 v4l2_device 父设备中,那么 v4l2_device_unregister 函数就会自动地把所有子设备卸载掉。但为了以防万一以及保持代码的风格统一,需要注册与卸载结对使用。可以用以下方式直接调用ops成员:err = sd->ops->core->g_std(sd, &norm); 使用下面的宏定义可以简化书写:err = v4l2_subdev_call(sd, core, g_std, &norm);该操作会检查 sd->dev 指针是否为空,如果是,返回 -ENODEV,同时如果 ops->core 或者 ops->core->g_std 为空,则返回 -ENOIOCTLCMD 。也可以通过以下函数调用来对 V4l2 下面挂载的所有子设备进行回调:

    	v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);
    

    该函数会跳过所有不支持该 ops 的子设备,并且所有的错误信息也被忽略,如果想捕获错误信息,可以使用下面的函数:

    	err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);
    

    该函数的第二个参数如果为 0,则所有的子设备都会被访问,如果非 0,则指定组的子设备会被访问。

    组ID使得桥驱动能够更加精确的去调用子设备操作函数,例如:在一个单板上面有很多个声卡,每个都能够改变音量,但是通常情况下只访问一个,这时就可以设置子设备的组 ID 为 AUDIO_CONTROLLER 并指定它的值,这时 v4l2_device_call_all 函数就会只去访问指定组的子设备,提高效率。

    如果子设备需要向 v4l2_device 父设备发送事件通知的话,就可以调用 v4l2_subdev_notify 宏定义来回调 v4l2->notify 成员(前文有提到过)。

    使用 v4l2_subdev 的优点是不包含任何底层硬件的信息,它是对底层硬件的一个抽象,因此一个驱动可能包含多个使用同一条 I2C 总线的子设备,也可能只包含一个使用 GPIO 管脚控制的子设备,只有在驱动设置的时候才有这些差别,而一旦子设备被注册之后,底层硬件对驱动来说就是完全透明的。

    不清楚异步模式的用途
    在异步模式下,子设备 probing 可以被独立地被调用以检查桥驱动是否可用,子设备驱动必须确认所有的 probing 请求是否成功,如果有任意一个请求条件没有满足,驱动就会返回 -EPROBE_DEFER 来继续下一次尝试,一旦所有的请求条件都被满足,子设备就需要调用 v4l2_async_register_subdev 函数来进行注册(用 v4l2_async_unregister_subdev 卸载)。桥驱动反过来得注册一个 notifier 对象(v4l2_async_notifier_register),该函数的第二个参数类型是 v4l2_async_notifier 类型的结构体,里面包含有一个指向指针数组的指针成员,指针数组每一个成员都指向 v4l2_async_subdev 类型结构体。v4l2 核心层会利用上述的异步子设备结构体描述符来进行子设备的匹配,如果成功匹配,.bound()notifier回调函数将会被调用,当所有的子设备全部被加载完毕之后,.complete() 回调函数就会被调用,子设备被移除的时候 .unbind() 函数就会被调用。

    另外子设备还提供了一组内部操作函数,该内部函数的调用时机在下面有描述,原型如下所示:

    struct v4l2_subdev_internal_ops {
    	int (*registered)(struct v4l2_subdev *sd);
    	void (*unregistered)(struct v4l2_subdev *sd);
    	int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
    	int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
    };
    

    这些函数仅供 v4l2 framework 使用,驱动程序不应该显式的去调用这些回调

    • registered/unregister:在子设备被注册(v4l2_device_register_subdev)/反注册的时候被调用。
    • open/close:如果子设备在用户空间创建了设备节点,那么这两个函数就会在用户空间的设备节点被打开/关闭的时候调用到,主要是用来创建/关闭v4l2_fh以供v4l2_ctrl_handler等的使用。

    v4l2子设备用户空间API

    可以在 /dev 文件夹下创建 v4l-subdevX 设备节点以供用户直接操作子设备硬件。如果需要在用户空间创建设备节点的话,就需要在子设备节点注册之前设置 V4L2_SUBDEV_FL_HAS_DEVNODE 标志,然后调用 v4l2_device_register_subdev_nodes() 函数,就可以在用户空间创建设备节点,设备节点会在子设备卸载的时候自动地被销毁。

    	VIDIOC_QUERYCTRL
    	VIDIOC_QUERYMENU
    	VIDIOC_G_CTRL
    	VIDIOC_S_CTRL
    	VIDIOC_G_EXT_CTRLS
    	VIDIOC_S_EXT_CTRLS
    	VIDIOC_TRY_EXT_CTRLS
    

    上述 ioctls 可以通过设备节点访问,也可以直接在子设备驱动里面调用。

    	VIDIOC_DQEVENT
    	VIDIOC_SUBSCRIBE_EVENT
    	VIDIOC_UNSUBSCRIBE_EVENT
    

    要使用上述事件,就必须设置 v4l2_subdevV4L2_SUBDEV_USES_EVENTS 标志位,实现 core_opssubscribe 相关的回调函数,回调函数里面需要初始化 events,然后注册 v4l2_subdev。一些私有的 ioctls 可以在 v4l2_subdevops->core->ioctl 里面实现。

    I2C子设备驱动

    要想在 I2C 驱动里面添加 v4l2_subdev 支持,就需要把 v4l2_subdev 结构体嵌入到每个 I2C 实例结构体里面,有一些比较简单的 I2C 设备不需要自定义的状态结构体,此时只需要创建一个单独的 v4l2_subdev 结构体即可。一个典型的驱动自定义状态结构体如下所示:

    	struct chipname_state {
    		struct v4l2_subdev sd;
    		...  /* additional state fields */
    	};
    

    使用 v4l2_i2c_subdev_init 去初始化一个 I2C 子设备,该函数会填充 v4l2_subdev 的所有成员并确保 v4l2_subdevi2c_client 互相指向对方。也可以添加内联函数来从 v4l2_subdev 的指针获取到 i2c_client 结构体:

    struct i2c_client *client = v4l2_get_subdevdata(sd);
    也可以从i2c_client结构体指针获取到v4l2_subdev结构体:
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    桥驱动可以使用以下帮助函数来创建一个I2C子设备:
    struct v4l2_subdev *sd = v4l2_i2c_new_subdev
    	(v4l2_dev, adapter,"module_foo", "chipid", 0x36, NULL);
    

    该函数会加载给定的模块(可以为空)并且调用 i2c_new_device 根据传入的参数创建子设备结构体,最后注册 v4l2_subdev

    video_device 结构体

    video_device 可以动态的分配:

    struct video_device *vdev = video_device_alloc();
    if (vdev == NULL)
    	return -ENOMEM;
    vdev->release = video_device_release;
    

    如果需要将 video_device 结构体嵌入到更大的结构体里面的话,就需要设置 vdevrelease 成员。内核提供了两个默认的 release 回调函数,如下:

    video_device_release()       // 仅仅调用kfree释放分配的内存,用于动态分配情况下
    video_device_release_empty() // 不做任何事情,静态变量
    

    以下的函数成员必须被设置:

    • v4l2_dev:必须指向v4l2_device父设备
    • vfl_dir:VFL_DIR_RX(capture设备)、VFL_DIR_TX(输出设备)、VFL_DIR_M2M(codec设备)
    • fops:设置v4l2_file_operations结构体
    • ioctl_ops:ioctls,可以通过设备节点被用户空间程序访问,需设置fops的.unlocked_ioctl指向video_ioctl2
    • lock:如果想要在驱动空间里做锁操作,可以设置为NULL。否则需要指向一个已经初始化的mutex_lock结构体
    • queue:指向一个vb2_queue结构体,如果queue->lock不为空,那么与队列相关的ioctls就会使用queue内部的锁,这样的话就不用等待其它类型的ioctls操作
    • prio:对优先级进行跟踪,用在VIDIOC_G/S_PRIORITY上,如果为空的话就会使用v4l2_device里面的v4l2_prio_state
    • dev_parent:指向v4l2_device即可

    如果想忽略 ioctl_ops 中某个 ioctls 的话可以调用下面的函数:

    	void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd);
    

    如果要集成到 media_framework 里面,就需要设置 video_device 里面的 media_entity 成员,同时需要提供 media_pad

    	struct media_pad *pad = &my_vdev->pad;
    	int err;
    	err = media_entity_init(&vdev->entity, 1, pad, 0);
    
    • video_device 的注册
      video_device 的注册函数如下:
    	err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    

    该段代码会注册一个字符设备驱动程序并在用户空间生成一个设备节点。如果 v4l2_device 父设备的 mdev 成员不为空的话,video_deviceentity 会被自动的注册到 media framework 里面。函数最后一个参数是设备节点索引号,如果是 -1 的话就取用第一个内核中可用的索引号值。注册的设备类型以及用户空间中的节点名称取决于以下标识:

    	VFL_TYPE_GRABBER: videoX 输入输出设备
    	VFL_TYPE_VBI: vbiX 
    	VFL_TYPE_RADIO: radioX 硬件定义的音频调谐设备
    	VFL_TYPE_SDR: swradioX 软件定义的音频调谐设备
    

    当一个设备节点被创建时,相关属性也会被创建,可以在 /sys/class/video4linux 里面看到这些设备文件夹,在文件夹里面可以看到 'name','dev_debug','index','uevent'等属性,可以使用 cat 命令查看。‘dev_debug’ 可以用于 video 设备调试,每个 video 设备都会创建一个 ‘dev_debug’ 属性,该属性以文件夹的形式存在与 /sys/class/video4linux/<devX>/ 下面以供使能 log file operation。'dev_debug’是一个位掩码,以下位可以被设置:

    	0x01:记录ioctl名字与错误码。设置0x08位可以只记录VIDIOC_(D)QBUF
    	0x02:记录ioctl的参数与错误码。设置0x08位可以只记录VIDIOC_(D)QBUF
    	0x04:记录file ops操作。设置0x08位可以只记录read&write成员的操作
    	0x08:如上所示
    	0x10:记录poll操作
    

    当以上的位被设置的时候,发生相关的调用或者操作的时候内核就会打印出来相关的调用信息到终端上面。类似于

    [173881.402120] video4: VIDIOC_DQEVENT: error -2
    [173884.906633] video4: VIDIOC_UNSUBSCRIBE_EVENT
    
    • video设备的清理
      当 video 设备节点需要被移除或者USB设备断开时,需要执行以下函数:
    	video_unregister_device(vdev);
    

    来进行设备的卸载,该函数会移除 /dev 下的设备节点文件,同时不要忘记调用 media_entity_cleanup 来清理 entity。

    ioctls 与 locking

    V4L 核心层提供了可选的锁服务,最主要的就是 video_device 里面的锁,用来进行 ioctls 的同步。如果使用了 videobuf2 框架,那么 video_device->queue->lock 锁也会被用来做 queue 相关的 ioctls 同步。使用不同的锁有很多优点,比如一些设置相关的 ioctls 花费的时间比较长,如果使用独立的锁,VIDIOC_DQBUF就不用等待设置操作的完成就可以执行,这个在网络摄像机驱动中很常见。当然,也可以完全由驱动本身去完成锁操作,这时可以设置所有的锁成员为NULL并实现一个驱动自己的锁。

    如果使用旧的 videobuf,需要将 video_device 的锁传递给 videobuf queue 初始化函数,如果 videobuf 正在等待一帧数据的到达,此时会将锁暂时释放,等数据到达之后再次加锁,否则别的处理程序就无法访问。所以不推荐使用旧的 videobuf。如果是在 videobuf2 框架下,需要实现 wait_preparewait_finish 回调函数去释放或者获取锁,如果使用了 queue->lock,可以使用 V4L2 提供的回调 vb2_ops_wait_prepare/finish 帮助函数来完成加锁与解锁的操作,它们会使用 queue->lock这个锁(此时一定要将该锁初始化)。

    v4l2_fh 结构体

    该结构体提供了一种简单的保存文件句柄特定数据的方法。v4l2_fh 的使用者-v4l2 framework 可以通过检查 video_device->flagsV4L2_FL_USES_V4L2_FH 位来知道驱动是否使用 v4l2_fh 作为 file->private_data 指针,该标志位通过调用函数 v4l2_fh_init 来设置。

    v4l2_fh 结构体作为驱动自己的文件句柄存在,并且在驱动的 open 函数里面设置 file->private_data 指向它,v4l2_fh 有多个的时候会作为一个链表存在于 file->private_data 中,可以遍历访问。在大多数情况下 v4l2_fh 结构体都被嵌入到更大的结构体里面,此时需要在 open 函数里面调用
    v4l2_fh_init+v4l2_fh_add 进行添加,在 release 函数里面调用 v4l2_fh_del+v4l2_fh_exit 进行退出。驱动可以使用 container_of 来访问自己的文件句柄结构体,如下所示:

    struct my_fh {
    	int blah;
    	struct v4l2_fh fh;
    };
    
    int my_open(struct file *file)
    {
    	struct my_fh *my_fh;
    	struct video_device *vfd;
    	int ret;
    
    	my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
    
    	v4l2_fh_init(&my_fh->fh, vfd);
    
    	file->private_data = &my_fh->fh;
    	v4l2_fh_add(&my_fh->fh);
    	return 0;
    }
    
    int my_release(struct file *file)
    {
    	struct v4l2_fh *fh = file->private_data;
    	struct my_fh *my_fh = container_of(fh, struct my_fh, fh);
    
    	v4l2_fh_del(&my_fh->fh);
    	v4l2_fh_exit(&my_fh->fh);
    	kfree(my_fh);
    	return 0;
    }
    

    如以上代码所示,由于 open 函数可能会被多个应用 app 所调用,所以 fh 也会有多个,但是 file->private 永远指向最新的一个 v4l2_fh ,通过这个 v4l2_fh 可以找到整个 v4l2_fh 链表中的所有元素。一些驱动需要在第一个文件句柄打开后以及最后一个文件句柄关闭前的时候做一些其它的工作,下面两个帮助函数可以检查 v4l2_fh 结构体是否只剩下一个 entry:

    int v4l2_fh_is_singular(struct v4l2_fh *fh)
    如果是只有一个entry,返回1,否则返回0,如果fh为空也返回0int v4l2_fh_is_singular_file(struct file *filp)
    和上面差不多,但是使用 filp->private_data 这一数据源,实际上它是指向最新的一个v4l2_fh的。
    

    V4L2 events

    V4L2 events 提供一种通用的方法来传递 events 到用户空间,驱动程序必须使用 v4l2_fh(设置 video_deviceflags 位)才能够实现对 V4L2 events 的支持。events 用类型和 ID 作为区分标识,没有使用到的 events 的ID就是0。

    当用户订阅 event 时,用户空间会相应地为每个 event 分配一个 kevent 结构体(如果 elems 参数为0的话只有一个,不为0就按照指定的数量分配),所以每个 event 都有一个或多个属于自己的 kevent 结构体,这就保证了如果驱动短时间内生成了非常多的 events 也不会覆盖到其它的同类型 events,可以看作是分了好几个篮子来放不同类型的水果。event 结构体是 v4l2_subscribed_event 结构体的最后一个成员,以数组的形式存在,并且是一个柔性数组(struct v4l2_kevent events[]),也就是说在分配 v4l2_subscribed_event 结构体空间的时候,events 并不占用空间,需要额外为指定数量的 events 分配空间,kzalloc(siezof(struct v4l2_subscribed_event) + sizeof(struct v4l2_kevent) * num, GFP_KERNEL);在使用的时候,完全可以按照数组的方式去对 kevent 进行寻址,很方便。

    如果获得的 event 数量比 kevent 的还要多,那么旧的 events 就会被丢弃。可以设置结构体 v4l2_subscribed_eventmerge、replace 回调函数(其实默认的函数就足够用了),它们会在 event 被捕获并且没有更多的空间来存放 event 时被调用。在 v4l2_event.c 里面有一个很好的关于 replace/merge 的例子,ctrls_replace()与ctrls_merge() 被作为回调函数使用。由于这两个函数可以在中断上下文被调用,因此必须得快速执行完毕并返回。

    关于events的循环是一个比较有意思的操作,入队时:三个变量(first-下一个准备被dequeue的eventm,elems-总kevent数量,in_use-已经使用的kevent数量)

    1. 若elems == in_use,说明队列成员已经用完。
    2. 取出第一个kevent,从available队列中删掉,first指向数组的下一个成员,in_use --。
    3. 找到上一步中(first指向数组的下一个成员),将上一步(取出第一个kevent)的changes位进行合并赋值给前者。
      因为后者比前者更新,所以数值完全可以覆盖前者,同时又保留了前者的变化。
    4. 取出第in_use + first >= elems ? in_use + first - elems : in_use + first;个数组kevent项作为新的填充项。
    5. in_use ++

    一些有用的函数:

    int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub, 
    		unsigned elems, const struct v4l2_subscribed_event_ops *ops)
    

    当用户空间通过 ioctl 发起订阅请求之后,video_device->ioctl_ops->vidioc_subscribe_event
    需要检查是否支持请求的 event,如果支持的话就调用上面的函数进行订阅。一般可以将 video 的相关 ioctl 指向内核默认的 v4l2_ctrl_subscribe_event() 函数。

    int v4l2_event_unsubscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub)
    取消一个事件的订阅,V4L2_EVENT_ALL类型可以用于取消所有事件的订阅。一般可以将video的相关ioctl指向该函数。
    
    void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
    该函数用作events入队操作(由驱动完成),驱动只需要设置type以及data成员,其余的交由V4L2来完成。
    
    int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
    		       int nonblocking)
    events出队操作,发生于用户空间的VIDIOC_DQEVENT调用,作用是从available队列中取出一个events。
    

    v4l2_subscribed_event_ops 参数允许驱动程序设置以下四个回调函数成员:

    • add:添加一个事件订阅时被调用
    • del:取消一个事件订阅时被调用
    • replace:event以新换旧,队列满时被调用,下同,常用于只有一个elems的情况下,拷贝kevent.u.ctrl项。
    • merge:将旧的event合并到新的event中,用于多个elems的情况下,只合并changes项,原因见上面event循环过程描述。

    events 通过 poll 系统调用传递到用户空间,驱动可以将 v4l2_fh->wait 作为 poll_wait() 的参数。子设备可以直接通过 notify 函数向 v4l2_device 发送 events(使用V4L2_DEVICE_NOTIFY_EVENT)。drivers/media/platform/omap3isp给出了如何使用event的实例。

    注意事项:

    • 注意v4l2_event_subscribe的elems参数,如果为0,则内核就默认分配为1,否则按照指定的参数值分配。
    • 最好不要使用内核默认的v4l2_subscribed_event_ops,因为它的add函数会尝试在v4l2_ctrl里面查找相应id的ctrl,如果
      是自定义的event id的话,有可能找不到相关的ctrl项,这样的话用户空间的VIDIOC_SUBSCRIBE_EVENT就会返回失败。
    • 用户空间dqevent之后不必关心还回的操作,因为内核会自动获取用过的kevent,用柔性数组去管理而不是分散的链表。
    • 子设备可以通过v4l2_subdev_notify_event函数调用来入队一个event并通知v4l2设备的notify回调。
    • v4l2_event_queue函数会遍历video_device上面所有的v4l2_fh,将event入队到每一个fh的列表当中。fh由用户打开video
      设备节点的时候产生,每一个用户打开video节点时都会为其分配一个单独的v4l2_fh。
    • file->private永远指向最新的一个v4l2_fh,通过这个v4l2_fh可以找到整个v4l2_fh链表中的所有元素。
    • v4l2_fh_release函数会将所有挂载该fh上面的事件全部取消订阅。

    写到这里,本文就算结束了,这部分会发现很多东西都是点到即撤,没有深入去解释,深入的这部分放在后面来完成,还有一个就是可能会感觉里面有很多东西看着可能知道是什么,但是反应到实际代码里面,实际应用里面就不知道是什么了,这个时候就必须结合代码来进行实际操作实验才能够确切了解。还有一种情况就是可能需求比较简单,一些特性永远用不到,这个时候也没关系,那就用到的时候再去翻看就好。


    想做的事就去做吧
    展开全文
  • V4L 简介及其与V4L2区别

    千次阅读 2017-10-17 16:30:45
    V4L是 Video for Linux的缩写,它是Linux 内核中关于视频设备的子系统,它为linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。...
     V4L是 Video for Linux的缩写,它是Linux 内核中关于视频设备的子系统,它为linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。
    
    由于早期的 V4L 有很多缺陷,Bill Dirks 等人对其进行了重新设计,并取名为Video for Linux 2(V4L2使用),最早出现于Linux2.5.x 版本。V4L2 相比于V4L 有更好的扩展性和灵活性,并且支持的硬件设备更多。
    因此在应用程序V4L编程实际是指v4l2,我们这个系列的以V4L2为主,但由于历史的原因,V4L2一般兼容V4L.所以很多程序可以用V4L接口.

    1.V4L支持设备

    V4L2(video for linux) 可以支持多种设备,它可以有以下几种接口:
    1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.下面也是着重讲解这种应用.
    2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.

    3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.
    4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.
    5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.

    2.V4L处理基本流程

    跟一般设备处理一样,大体上V4L处理有四个流程.
    2.1 打开V4L设备结点
    一般V4L设备结点名是 /dev/videoN.如第一个V4L设备是/dev/video0.

    int fd = open("/dev/video0",O_RDWR |O_NONBLOCK);

    2.2配置设备/查询设备属性
    主要通过ioctl来操作,象V4L2 常见的的命令有
    格式
    int ioctl (int __fd, unsigned long int __request, .../*args*/) ;

    __request是V4L2一些ioctl命令,常见如下.
      1. VIDIOC_REQBUFS:分配内存
      2. VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
      3. VIDIOC_QUERYCAP:查询驱动功能
      4. VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
      5. VIDIOC_S_FMT:设置当前驱动的频捕获格式
      6. VIDIOC_G_FMT:读取当前驱动的频捕获格式
      7. VIDIOC_TRY_FMT:验证当前驱动的显示格式
      8. VIDIOC_CROPCAP:查询驱动的修剪能力
      9. VIDIOC_S_CROP:设置视频信号的边框
      10. VIDIOC_G_CROP:读取视频信号的边框
      11. VIDIOC_QBUF:把数据从缓存中读取出来
      12. VIDIOC_DQBUF:把数据放回缓存队列
      13. VIDIOC_STREAMON:开始视频显示函数
      14. VIDIOC_STREAMOFF:结束视频显示函数
      15. VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

    2.3 处理V4L视频数据
    V4L设备中,有的设备从硬件取出,送到应用程序处理,比如摄像头硬件取得视频数据后,通过V4L接口把视频数据发送应用程序, 比如显示屏幕或保存成为文件.
    有的设备是从应用发往硬件处理,如电视接口.
    在V4L接口,设定了三种应用程序与驱动的交互方式,分别是
    直接读取设备文件方式(read/write)、用户指针方式(userptr)以及mmap 映射方式。

    1)mmap方式,驱动将内部数据空间映射到应用程序空间上,双方直接在这个空间进行数据交换,是效果最高的方法,这也是最常用的方式之一
    2) 
    直接读取设备文件方式 直接调用 read()、write()函数进行数据的读入和输出,该方法一般配合select()使用。

    3)用户指针方式 首先由应用程序申请一段缓冲区,然后将缓冲区传给驱动,驱动将其作为缓冲区,从而实现了内存共享。这一方法用的较少.

    2.4 关闭设备
    调用close();如果是内存映射方式,在关闭前还需要调用munmap解除映射.

    3.V4L两个版本区别

    1.头文件不一样 V4L使用#include <linux/videodevice.h>
    V4L2使用 #include <linux/videodevice2.h>

    2.IOCTL命令编号 ,V4L使用 VIDIOCXXXX的形式,而V4L2使用VIDIOC_XXXX 或 VIDIOC_G_XXXX形式.
    如V4L中取设备属性命令是VIDIOCGCAP,而V4L2对应的是VIDIOC_QUERYCAP.

    3.两者数据结构不一样,V4L以Video_为前缀,而V4L以v4l2_为前缀.如设备属性
    V4l1--> struct video_capability video_cap
    V4l2-->struct v4l2_capability


    4.检测V4L设备版本
    在V4L2中,规定必须实现 VIDIOC_QUERYCAP命令,而V4L1,规定必须实现VIDIOCGCAP,用这个方法可以判断设备的版本.参见如下代码.
     
     
    [html] view plain copy
    1. /*  
    2.  * Author: Andrew Huang <bluedrum@163.com>  
    3.  * detectd v4l2 device version  
    4.  *  
    5.  */  
    6. #include <stdio.h>  
    7. #include <string.h>  
    8. #include <errno.h>  
    9.   
    10. #include <sys/types.h>  
    11. #include <sys/stat.h>  
    12. #include <sys/ioctl.h>  
    13. #include <fcntl.h>  
    14.   
    15. #include <linux/videodev2.h>  
    16. #include <linux/videodev.h>  
    17.   
    18. /*  
    19.      0    -- 不是v4l设备  
    20.   1 -- v4l 设备  
    21.   2 -- v4l2 设备  
    22. */  
    23.   
    24. int test_v4l_version(int fd)  
    25. {  
    26.    int ret = 0;  
    27.    char dummy[256];   
    28.   
    29.    if (-1 != ioctl(fd,VIDIOC_QUERYCAP,dummy)) {   
    30.         ret = 2;  
    31.           }   
    32.     else if (-1 != ioctl(fd,VIDIOCGCAP,dummy)) {   
    33.        ret = 1;  
    34.      }   
    35.   
    36.     return ret;  
    37. }  
    38.   
    39.   
    40. int main(int argc,char * argv[])  
    41. {  
    42.    char dev_name[64] = "/dev/video2";  
    43.    int cam_fd =-1;  
    44.   
    45.    if(argc>1)  
    46.        {  
    47.         strncpy(dev_name,argv[1],sizeof(dev_name)-1);  
    48.        }  
    49.   
    50.    printf("open device %s\n",dev_name);  
    51.    cam_fd = open(dev_name,O_RDWR|O_NONBLOCK);  
    52.     if(cam_fd == -1)  
    53.         {  
    54.          printf("open failure \n");  
    55.          return -1;  
    56.         }  
    57.   
    58.    switch(test_v4l_version(cam_fd))  
    59.        {  
    60.       case 0:  
    61.         printf("%s:fd %d isn't v4l deivce\n",dev_name,cam_fd);  
    62.         return -1;  
    63.         break;  
    64.         case 1:  
    65.         printf("\n### video4linux device info [%s] ###\n",dev_name);   
    66.         return -2;  
    67.         break;  
    68.         case 2:  
    69.         printf("\n### v4l2 device info [%s] ###\n",dev_name);   
    70.         break;  
    71.        }  
    72.       
    73.    close(cam_fd);  
    74.   
    75.    return 0;  
    76.       
    77. }  

    作者:Andrew Huang bluedrum@163.com
     
    PS:
    错误:expected expression before ‘struct’
    解决:#include<sys/ioctl.h>
    展开全文
  • v4l2框架v4l2-device API分析

    千次阅读 2017-05-19 15:23:32
    涉及到的结构体: struct v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备...struct v4l2_device { /* dev->driver_data points to this struct. Note: dev might be NULL if there is no parent device as
  • webcam_v4l2_x264

    千次下载 热门讨论 2011-07-07 15:25:57
    一个webcam_v4l2的演示:包含两部分: 1. webcam_server: 抓v4l2, 压缩,发送 2. webcam_shower: 接收,解压,回放
  • V4L2 CONTROLS

    2014-07-05 17:30:23
    下面是Linux内核文档中的一篇---videobuf的翻译,只是大概意思的翻译,有不对的地方请指正。 欢迎转载,转载请注明出处。 An introduction to the videobuf layer ...The videobuf layer functions as ...
  • 程序抛出错误: 不兼容的类型: android.app.Fragment无法转换为android.support.v4.app.Fragment Error:(34, 47) 错误: 不兼容的类型: android.app.Fragment无法转换为android.support.v4.app.Fragment Error:...
  • 知乎 API v4 整理

    万次阅读 2019-02-20 22:17:01
    URL:https://www.zhihu.com/api/v4/questions/{qid}/answers 参数: limit:数量,最大 20 offset:起始位置,从零开始 sort_by:{default, created},表示默认排序或者时间排序 include:额外信息,包括is_normal...
  • V4L2视频采集与H264编码2—v4l2采集YUV数据

    万次阅读 热门讨论 2018-04-24 11:01:59
    本以为代码从PC机移植到开发板是很简单的一个事,谁知因为平台或是V4L2底层驱动的不同,最终也是花了九牛二虎之力才把问题给解了。话不多说,直接上代码:/*=============================================...
  • V4L V4L2简介

    2015-09-24 09:44:53
    V4L是 Video for Linux的缩写,它是Linux 内核中关于视频设备的子系统,它为linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。...
  • 网上找的android camera HAL资料及V4L2资料,V4L2一个文档中含V4L2的简单实现例程
  • V4L2视频采集与H264编码1—V4L2采集JPEG数据

    千次阅读 热门讨论 2018-04-24 11:01:53
    最近在做视频编码,经过一段时间的折腾,终于可以把V4L2采集到的图片数据编码成H264视频,并且成功将工程移植到了ARM开发板上。无奈开发板性能太低,每秒钟只能编码一帧数据,查看CPU已经跑到100%,计划换另外一种...
  • 安卓ActionBar.TabListener的onTabSelected方法不支持v4包的FragmentTransaction,怎么解决 在ActionBar里添加Tab时,class TabListener implements ActionBar.OnTabListener{ } 里面要复写一个onTabSelected(Tab ...
  • android v4兼容包

    千次阅读 2016-09-23 16:29:44
    我们都知道Android一些SDK比较分裂,为此google官方提供了Android Support Library package 系列的包来保证高版本sdk开发的向下兼容性, 所以你可能经常看到v4,v7,v13这些数字,首先我们就来理清楚这些数字的含义,...
  • E/V4L2CameraDevice( 66): camera(0) select timeout W/V4L2CameraDevice( 66): camera(0) wait v4l2 buffer time out, mUsedBufCnt(0) E/V4L2CameraDevice( 66): camera(0) select timeout W/V4L2CameraDevice( 66)...
  • 我在linux下使用V4L2对免驱摄像头进行操作,由于项目需求,需要通过USB线获取 摄像头板的GPI的状态,这个属于扩展功能,摄像头工程师是这么做的:由于我们的摄像头的GAMMA值不会被用到,因此当摄像头板检测到GPI...
  • v4l2-controls

    千次阅读 2018-03-18 23:45:48
    V4L2 control API似乎很简单,然而正确实现地驱动程序很快就变得很难。因为大部分需要处理control的代码实际上是不特定的驱动和可移动的V4L核心框架。 毕竟,驱动程序开发人员感兴趣的惟一部分是: 1)如何添加...
  • V4L2学习记录

    千次阅读 2016-08-24 15:28:59
    V4L2学习记录  这个还没有分析完,先在这放着,防止电脑坏掉丢了,以后再完善 V4L2
  • linux下v4l2视频采集源代码

    热门讨论 2012-03-02 09:51:21
    1、在linux下使用v4l2框架采集视频 2、使用socket tcp发送采集到的视频数据,下载一个接收yuv数据的显示软件就可以播放 3、使用select、多线程、线程锁等技术,值得参考 4、程序逻辑清晰,代码简单 包含文件video.c
  • V4L2开发要点

    千次阅读 2012-04-09 15:18:27
    首先来看 Read/Write ,如果 VIDIOC_QUERYCAP 调用返回的 v4l2_capability 参数中, V4L2_CAP_READWRITE 被设置成真了的话,就说明支持 Read/Write I/O 。这是最简单最原始的方法,它需要进行数据 的...
  • YOLO v4

    千次阅读 2020-04-28 00:42:12
    源权重文件在Google driver上:https://drive.google.com/open?id=1cewMfusmPjYWbrnuJRuKhPMwRe_b9PaT,有245MB。 速度简单测试了下转keras后的速度,输入大小为416*416,GTX1060 要260ms左右,CPU I7-7700要1s左右...
  • V4L

    2013-04-01 18:40:02
    第一个部分介绍一些v4l的基本概念和基本方法,利用系统API完成一系列函数以方便后续应用程序的开发和使用。 第二个部分一些说明如何使用v4l,用一个示例程序说明。 第三个部分想简单说一说对获取和处理图像相关...
  • > Could not find support-v4.jar (com.android.support:support-v4:24.0.0). Searched in the following locations: https://jcenter.bintray.com/com/android/support/support-v4/24.0.0/support-v4-24.0.0.jar ...

空空如也

1 2 3 4 5 ... 20
收藏数 35,428
精华内容 14,171
关键字:

v4