精华内容
下载资源
问答
  • V4L2的使用及运行机制

    2017-09-28 22:12:12
    v4l2 操作实际上就是 open() 设备, close() 设备,以及中间过程 ioctl() 操作。对于 ioctl 调用,要注意对 errno 判断,如果调用被其他信号中断,即 errno 等于 EINTR 时候,要重新调用。 ...

    v4l2 操作实际上就是 open() 设备, close() 设备,以及中间过程的 ioctl() 操作。对于 ioctl 的调用,要注意对 errno 的判断,如果调用被其他信号中断,即 errno 等于 EINTR 的时候,要重新调用。

    Video capture device 的实际功能就是采集视频信号,并将数字化的图像保存在 memory 中,现在几乎上所有的相关设备都能采集 25/30  /s 。在下面的讨论中,我只列举出一些和camera 密切相关的一些属性和方法。

    1  open_device

    打开设备一般都是使用 open() 打开 /dev 下的 video 设备文件 ,比如说 /dev/video1, 打开之前首先要对相应的设备文件进行检查 ,比如说使用 stat() 获得文件属性,并判断是否为字符设备文件。

    驱动通过主设备号 81  0  255 之间的次设备号来注册 device note 系统 管理 员通过设备的主次设备号在 /dev 目录下创建相应的字符设备文件。应用 程序不能通过设备的主次设备号来打开设备,而必须通过适当的 device name ,即 /dev 目录下的设备文件来打开设备。

    v4l2 支持一个设备文件可以被多次打开,却只允许其中一个应用程序与设备进行数据 交换 ,其他应用程序只能用来设定一些设备参数,对设备进行一些控制。

    //====== 相关 spec  http://v4l2spec.bytesex.org/spec/c174.htm#OPEN

    2  init_device

    对设备进行初始化是一个很复杂的过程,其中要进行一系列参数的协商,其中重要的包括 v4l2_capability, v4l2_cropcap, v4l2_format 等等。

    首先使用 VIDIOC_QUERYCAP 命令 来获得当前设备的各个属性,查看设备对各项功能的支持程度:

    int ioctl(int fd, int request, struct v4l2_capability *argp);

    所有的 v4l2 驱动都必须支持 VIDIOC_QUERYCAP , 而且在打开设备以后,这个 ioctl 必须是被首先调用的。

    v4l2_capability 的各项参数可以查 API ,其中比较重要的是下面的成员变量:

    _u32 capabilities

    这个 32 位无符号整型定义了当前设备对一些关键属性的支持:

    V4L2_CAP_VIDEO_CAPTURE 0x00000001

    // 这个设备支持 video capture 的接口,即这个设备具备 video capture 的功能

    V4L2_CAP_VIDEO_OUTPUT 0x00000002

    // 这个设备支持 video output 的接口,即这个设备具备 video output 的功能

    V4L2_CAP_VIDEO_OVERLAY 0x00000004

    // 这个设备支持 video overlay 的接口,即这个设备具备 video overlay 的功能 ,在这个功能下会将采集到的 imag 方在视频设备的 meomory 中保存,并直接在屏幕上显示,而不需要经过其他的处理。

    V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200

    // 这个设备支持 video output overlay( 又名 On-Screen Display) ,这是一个实验性的功能, spec 说明他在将来可能会改变,如果打开这个功能必须将 video overlay 功能给关闭,反之亦然

    V4L2_CAP_READWRITE 0x01000000

    // 这个设备是否支持 read()  write() I/O 操作函数

    V4L2_CAP_STREAMING 0x04000000

    // 这个设备是否支持 streaming I/O 操作函数

    在实际操作过程中,可以将取得的 capabilites 与这些宏进行与远算来判断设备是否支持相应的功能。

    除了 VIDIOC_QUERYCAP 之外,设备其他属性的获得可以通过其他的命令,比如说 VIDIOC_ENUMINPUT  VIDIOC_ENUMOUTPUT 可以枚举出设备的输入输出物理连接。

    //============== 相关 spec  http://v4l2spec.bytesex.org/spec/x282.htm

     

    获得 device  capability 以后,可以根据应用程序的功能要求对设备参数进行一系列的设置 ,这些参数又分为两部分,一个是 user contrl ,还一个是 extended control ,下面先来讲对 user contrl 的一些参数进行设置。

    User control 参数包含一个 ID ,以及相应的 Type ,下面对各个 type 进行简单的列举:

    ID Type

    V4L2_CID_BASE

    // 第一个预定义的 ID ,实际等于 V4L2_CID_BRIGHTNESS ,因为 V4L2_CID_BRIGHTNESS 是第一个预定义的 ID

    V4L2_CID_USER_BASE

    // 实际上等同于 V4L2_CID_BASE

    V4L2_CID_BRIGHTNESS integer

    // 图片的亮度,或者说黑色位准

    V4L2_CID_AUTO_WHITE_BALANCE boolean

    //camera 的自动白平衡

    V4L2_CID_EXPOSURE integer

    //camera 的爆光时间

    V4L2_CID_LASTP1

    // 最后一个预定义的 ID ,实际等于上一个 ID  1

    V4L2_CID_PRIVATE_BASE

    // 第一个 driver 定义的一般 control ID

    可以通过 VIDIOC_QUERYCTRL  VIDIOC_QUERYMENU ioctls 来枚举出有效的 control ID ,及其属性,比如说 ID 值,类型,是否有效,是否可修改,最大值,最小值,步长等等 ,主要的数据结构是 v4l2_queryctrl  v4l2_querymenu ,他们的结构可以参考 spec 。另外可以通过 V4L2_CID_BASE  V4L2_CID_LASTP1 可以枚举出所有的预定义 control ID ,可以通过 V4L2_CID_PRIVATE_BASE 来枚举出所有的驱动定义的 control ID  Menu 实际上是同一个 ID 可能具有多个选项的目录。

    int ioctl(int fd, int request,struct v4l2_queryctrl *argp);

    int ioctl(int fd, int request, struct v4l2_querymenu *argp);

    获得 user control ID 以后,可以对其中可以修改的 ID 按照应用程序的要求进行修改 VIDIOC_G_CTRL, VIDIOC_S_CTRL 

    int ioctl(int fd, int request, struct v4l2_control *argp);

    v4l2_control 的结构比较简单,就是相应的 ID 及其 value 

    //========== 相关 spec  http://v4l2spec.bytesex.org/spec/x542.htm

     

    除了 user control 之外还有一个就是扩展控制,扩展控制可以同时原子的对多个 ID 进行 control ,相关命令是三个: VIDIOC_G_EXT_CTRLS, VIDIOC_S_EXT_CTRLS VIDIOC_TRY_EXT_CTRLS 

    int ioctl(int fd, int request, struct v4l2_ext_controls *argp);

    其中最重要的是 v4l2_ext_controls 这个数据结构,它包含几个内容:

    __u32 ctrl_class

    // 现在 spec 中只定义了两种类型的 class  V4L2_CTRL_CLASS_USER  V4L2_CTRL_CLASS_MPEG

    __u32 count

    //ctrl 数组中的 control ,即 v4l2_ext_control 的个数

    struct v4l2_ext_control * controls

    //control 数组, v4l2_ext_control 包含要设定的 ID ,以及 value

    应用程序可以使用 V4L2_CTRL_FLAG_NEXT_CTRL 来对扩展 control 进行枚举, V4L2_CTRL_FLAG_NEXT_CTRL 返回下一个 ID 更高的 control ID 

    struct v4l2_queryctrl qctrl;

    qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;

    while (0 == ioctl (fd, VIDIOC_QUERYCTRL, &qctrl)) {

    /* ... */

    qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;

    }

    要枚举指定的 control class 中的 control 可以使用下面的方法:

    qctrl.id = V4L2_CTRL_CLASS_MPEG | V4L2_CTRL_FLAG_NEXT_CTRL;

    while (0 == ioctl (fd, VIDIOC_QUERYCTRL, &qctrl)) {

    if (V4L2_CTRL_ID2CLASS (qctrl.id) != V4L2_CTRL_CLASS_MPEG)

    break;

    /* ... */

    qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;

    }

    当然前提是驱动必须提供对 V4L2_CTRL_FLAG_NEXT_CTRL 的支持。

    应用程序可以为创建一个控制面板,其中包含一系列控制,每个 control class 用一个 V4L2_CTRL_TYPE_CTRL_CLASS 类型开始,当使用 VIDIOC_QUERYCTRL 的时候将返回这个control class  name ,下面我们来看 camera control class 的一些 control 

    ID Type

    V4L2_CID_CAMERA_CLASS class

    //camera class 的描述符,当调用 VIDIOC_QUERYCTRL 的时候将返回一个对这个 class 的描述

    V4L2_CID_EXPOSURE_AUTO integer

    // 自动爆光

    V4L2_CID_FOCUS_AUTO boolean

    // 自动对焦

    //============== 相关 spec  http://v4l2spec.bytesex.org/spec/x802.htm

     

    对各种控制参数进行设置以后,下面要进行的就是要获得设备对 Image Cropping  Scaling 的支持,即对图像的取景范围以及图片的比例缩放的支持。 Image Crop 的功能通俗一点讲就是他对 camera 镜头能捕捉的图像,截取一个范围来保存,而这个要截取的范围就是最终保存下来的图像。

    对于一个视频捕捉或者视频直接播放的设备来说,源是视频信号,而 cropping ioctl 决定视频信号的哪部分被采样,目标则是应用程序或者屏幕上读到的图片。对于视频输出设备来说,输入是应用程序传入的图片,而输出则是视频流, cropping ioctl 此时则决定图片的哪部分会被插入视频信号,所有的视频捕捉和视频输出设备都必须支持 VIDIOC_CROPCAP ioctl:

    Int ioctl(int fd, int request, struct v4l2_cropcap *argp)

    其中数据结构 v4l2_cropcap 的几个要重要的成员变量是下面这些:

    enum v4l2_buf_type type

    // 数据流的类型,在 VIDIOC_CROPCAP 这个控制中只有 V4L2_BUF_TYPE_CAPTURE, V4L2_BUF_TYPE_OUTPUT, V4L2_BUF_TYPE_OVERLAY 以及驱动定义的一些一般类型V4L2_BUF_TYPE_PRIVATE 是有用的

    struct v4l2_rect bounds

    // 这是 camera 的镜头能捕捉到的窗口大小的局限,在应用程序设置窗口参数的时候要注意,不能超过这个长宽限制

    struct v4l2_rect defrect

    // 定义了默认的窗口大小,包括起点的位置以及长宽的大小,大小以像素为单位

    struct v4l2_fract pixelaspect

    // 定义了图片的宽高比

    应用程序可以使用 VIDIOC_G_CROP  VIDIOC_S_CROP 来获得对这些窗口参数并对其进行设置,也就是所谓的 Scaling Adjustments ,因为硬件可能在这些窗口参数设置上具有很多限制,当需要对窗口参数进行设置的时候,驱动会按照自身的规律在应用程序要求和设备限制上决定一个平衡值,一般应用程序应该先使用 VIDIOC_CROPCAP 来获得硬件限制,并使设定的参数在 bound 范围以内:

    int ioctl(int fd, int request, struct v4l2_crop *argp);

    int ioctl(int fd, int request, const struct v4l2_crop *argp);

    //==== 相关 spec  http://v4l2spec.bytesex.org/spec/x1904.htm

     

    设置好取景窗口参数以后,下面要进行的设置就是对图形格式的协商,这个 Data Format 的协商通过 VIDIOC_G_FMT  VIDIOC_S_FMT 来实现。另外 VIDIOC_TRY_FMT 的功能等同与 VIDIOC_S_FMT ,唯一的不同就是他不会改变驱动的状态,它在任何时候都可以被调用,主要用来获得硬件的限制,从而对参数进行协商。如果驱动需要与应用程序交换数据,则必须支持 VIDIOC_G_FMT  VIDIOC_S_FMT  VIDIOC_TRY_FMT 是可选的,但是是强烈推荐实现的。

    Int ioctl(int fd, int requeset, struct v4l2_format *argp) ;

    前面讲过,虽然一个设备文件可以支持多打开,但是只允许一个能与驱动进行数据交换,因此在设备的初始化过程中对 VIDIOC_S_FMT ioctl 的调用是一个转折点,第一个调用VIDIOC_S_FMT ioctl 的文件描述符会打开一个逻辑的流 ,如果此时其他的文件描述符对设备进行的操作有可能破坏这个流的时候是会被禁止的,比如说如果另外一个应用程序想修改video standard ,只有对流拥有所有权的文件描述符才能修改这方面的属性。再比如当 overlay 已经开始的时候, video capture 就会被限制在和 overlay 相同的 cropping  image size 

    一般来说只允许同一个文件描述符拥有一个逻辑流,唯一的例外是 video capture  video overlay 可以使用同一个文件描述符。

    下面来看看 v4l2_format 这个数据结构,它包含几个重要内容:

    enum v4l2_buf_type type

    //buf 的类型,比如说 V4L2_BUF_TYPE_VIDEO_CAPTURE

    union fmt

    struct v4l2_pix_format

    //used for video capture and output

    struct v4l2_window

    //used for video overlay

    …………

    其中最重要的是 union 中的两个结构体, v4l2_window  overlay interface 的内容,将在 overlay 中再讨论,先看一下 v4l2_pix_format 的结构:

    __u32 width

    __u32 height

    // 分别是 image 的宽度和高度,以像素为单位,应用程序可以设置这些参数,驱动会返回一个最靠近这些参数的值,为什么是最靠近的值呢,因为图像格式以及硬件限制的原因,可能应用程序要求的值无法得到满足。[在这里普及一个基础 知识  YUV 格式有两种存储方式,一种就是将其 3 个分量存在同一个数组中,然后几个像素组成一个宏块,这种方式叫packed ;另外一种就是 3 个分量分别存放在不同的数组中,这种方式叫做 planar 。]

    __u32 pixelformat

    // 这就是图像格式了,可以是 RGB ,也可以是 YUV ,还可以是压缩格式 MPEG 或者 JPEG ,这个值是通过一个 4 字母宏来计算出来的: #define v4l2_fourcc(a,b,c,d)(((__u32)(a)<<0) | ((__u32)(b)<<8)| ((__u32)(c)<<16) | ((__u32)(a)<<24)) ,具体格式的标准宏可以参照 spec 

    enum v4l2_field field

    // 这个定义了视频信号的场的顺序,比如视频信号可能是顺序扫描的,也可能是隔行扫描的。一般将场分为 top 场和 bottom 场,一个 video camera 不会在一个时间内暴光一个整帧,而是将其分成场分别传输。所有的 video capture  output 装置都必须指定其场的传输顺序,即是 top 场在前还是 bottom 场在时间上和空间 上的顺序 。具体的可以从参考 spec 关于Field Order 的描述,一般采用的是 V4L2_FIILED_INTERLACED ,在这个模式下 image 包含交叉存取的帧,场的顺序由当前的视频标准来决定。

    __u32 bytesperline

    // 即每行像素所占的 byte 数,应用程序和驱动都可以设置这个参数,但驱动可以忽略应用程序的参数,而返回一个硬件要求的参数,应用程序可以设置这个参数为 0 来让驱动返回一个默认值。 Image 在内存中还是按照每行像素这样来存储的,每一行像素后面都有一个衬垫来代表该行像素的结束。

    __u32 sizeimage

    // 要保存一个完整的 Image 需要的 buffer 空间,单位是 byte ,由驱动来设定,是保存一个图像所需要的最大 byte 数,而不是图像被压缩的 byte 数。

    如果驱动需要与应用程序交换 image data 则必须支持 VIDIOC_ENUM_FMT 来列出所有驱动支持的 FMT 格式 :

     

    实际上, crop 是对取景进行限制,而 fmt 则是对最终保存下来的图片属性进行设置 ,如果取景后的图片和要求的图像属性有冲突,就要将取景后的图片进行相应的调整,比如放大,缩小等等 ]

    看下面的例子:

    Resetting the cropping parameters

    (A video capture device is assumed; change V4L2_BUF_TYPE_VIDEO_CAPTURE for other devices.)

    struct v4l2_cropcap cropcap;

    struct v4l2_crop crop;

    memset (&cropcap, 0, sizeof (cropcap));

    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == ioctl (fd, VIDIOC_CROPCAP, &cropcap)) {

    perror ("VIDIOC_CROPCAP");

    exit (EXIT_FAILURE);

    }

    memset (&crop, 0, sizeof (crop));

    crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    crop.c = cropcap.defrect;

     

    /* Ignore if cropping is not supported (EINVAL). */

    if (-1 == ioctl (fd, VIDIOC_S_CROP, &crop)

    && errno != EINVAL) {

    perror ("VIDIOC_S_CROP");

    exit (EXIT_FAILURE);

    }

    Simple downscaling

    (A video capture device is assumed.)

    struct v4l2_cropcap cropcap;

    struct v4l2_format format;

    reset_cropping_parameters ();

    /* Scale down to 1/4 size of full picture. */

    memset (&format, 0, sizeof (format)); /* defaults */

    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    format.fmt.pix.width = cropcap.defrect.width >> 1;

    format.fmt.pix.height = cropcap.defrect.height >> 1;

    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

    if (-1 == ioctl (fd, VIDIOC_S_FMT, &format)) {

    perror ("VIDIOC_S_FORMAT");

    exit (EXIT_FAILURE);

    }

    /* We could check the actual image size now, the actual scaling factor

    or if the driver can scale at all. */

    另外还有一个可选的选项,就是如果采用 read/write 模式,还可以通过设置流参数属性来优化 capture 的性能,在这里就不讨论了,具体的可以去参照 spec 

     

    完成这一系列参数的初始化以后,最后一个要协商的就是 I/0 模式的选择:主要分为两种,一种是 Read/Write ,这也是打开 video device 之后默认选择的 I/O 方法,其他的方法如果使用必须经过协商;还有一个就是 stream ,其中 stream 中根据实现方式的不同又可以分为 Memory Mapping  User Pointers  Driver 可以决定是否支持对 I/O  switch ,这不是必须的,如果 driver 不支持,则只有通过 open/close device 来实现 I/0 的切换。

    首先来看 Read/Write ,如果 VIDIOC_QUERYCAP 调用返回的 v4l2_capability 参数中, V4L2_CAP_READWRITE 被设置成真了的话,就说明支持 Read/Write I/O 。这是最简单最原始的方法,它需要进行数据 的拷贝 ( 而不是像 memory map 那样只需要进行指针的交换 ) ,而且不会交换元数据 ( 比如说帧计数器和时间戳之类的可用于识别帧丢失和进行帧同步 ) ,虽然它是最原始的方法,但因为其简单,所以对于简单的应用 程序比如只需要 capture 静态图像是很有用的 。

    如果使用 Read/Write 方法支持的话,必须同时支持另外两个函数 select()  poll() ,这两个函数用来进行 I/0 的多路复用。

    对于 streaming 它有两种方式, driver 对两种方式的支持要使用 VIDIOC_REQBUFS 来确定:

    int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);

    对于 memory mapped 方式, Memory mapped buffers 是通过 VIDIOC_REQBUFS  device memory 中申请的,而且必须在 map 进应用程序虚拟地址空间 之前就申请好。而对于User pointers  User buffers 是在应用程序自己开辟的,只是通过 VIDIOC_REQBUFS 将驱动转化到 user pointer  I/O 模式下。这两种方式都不会拷贝数据,而只是 buffer 指针的交互。

    首先来看一下 v4l2_requestbuffers 这个数据结构:

    __u32 count

    // 要申请的 buffer 的数量,只有当 memory 被设置成 V4L2_MEMORY_MMAP 的时候才会设置这个参数

    enum v4l2_buf_type type

    enum v4l2_memory memory

    // 要么是 V4L2_MEMORY_MMAP ,要么是 V4L2_MEMORY_USERPTR

    对于 memory mapped 模式,要在 device memory 下申请 buffer ,应用程序必须初始化上面的 3 个参数,驱动最后返回的 buffer 的个数可能等于 count ,也可能少于或者多于 count,少于可能是因为内存不足,多于则可能是驱动为更好地完成相应功能增加的 buffer 。如果 driver 不支持 memory mapped 调用这个 ioctl 就会返回 EINVAL 

    因为 memory map 模式下分配的是实实在在的物理内存,不是虚拟内存,所以使用完以后一定要使用 munmap() 释放。

    应用程序可以重新调用 VIDICO_REQBUFS 来改变 buffer 的个数,但前提是必须先释放已经 mapped  buffer ,可以先 munmap ,然后设置参数 count  0 来释放所有的 buffer 

    对于 User pointer I/O ,应用程序只需设置上面的 type  memory 类型就可以了。

    申请好 buffer 后在进行 memory mapped 之前,首先要使用 VIDIOC_QUERYBUF 来获得分配的 buffer 信息,以传给函数 mmap() 来进行 map 

    int ioctl(int fd, int request, struct v4l2_buffer *argp);

    VIDIOC_QUERYBUF  memory mapped 这种模式下使用的方法,在 User pointer 模式下不需要使用这个函数,在调用之前应用程序需要设定 v4l2_buffer 中的两个参数,一个是buffer 类型,另外一个是 index number( 有效值从 0 到申请的 buffer 数目减 1) ,调用这个 ioctl 会将相应 buffer 中的 flag  V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED  V4L2_BUF_FLAG_DONE 设置为有效。下面我们来仔细看看 v4l2_buffer 这个数据结构:

    __u32 index

    // 应用程序来设定,仅仅用来申明是哪个 buffer

    enum v4l2_buf_type type

    __u32 bytesused

    //buffer 中已经使用的 byte 数,如果是 input stream  driver 来设定,相反则由应用程序来设定

    __u32 flags

    // 定义了 buffer 的一些标志位,来表明这个 buffer 处在哪个队列,比如输入队列或者输出队列 (V4L2_BUF_FLAG_QUEUED V4L2_BUF_FLAG_DONE) ,是否关键帧等等,具体可以参照 spec

    enum v4l2_memory memory

    //V4L2_MEOMORY_MMAP  V4L2_MEMORY_USERPTR  V4L2_MEMORY_OVERLAY

    union m

    __u32 offset

    //  memory 类型是 V4L2_MEOMORY_MMAP 的时候,主要用来表明 buffer  device momory 中相对起始位置的偏移,主要用在 mmap() 参数中,对应用程序没有左右

    unsigned long userptr

    //  memory 类型是 V4L2_MEMORY_USERPTR 的时候,这是一个指向虚拟内存中 buffer 的指针,由应用程序来设定。

    __u32 length

    //buffer  size

     driver 内部管理 着两个 buffer queues ,一个输入队列,一个输出队列。对于 capture device 来说,当输入队列中的 buffer 被塞满数据以后会自动变为输出队列,等待调用VIDIOC_DQBUF 将数据进行处理以后重新调用 VIDIOC_QBUF  buffer 重新放进输入队列;对于 output device 来说 buffer 被显示以后自动变为输出队列。

    刚初始化的所有 map 过的 buffer 开始都处于 dequeced 的状态,由 driver 来管理对应用程序是不可访问的。对于 capture 应用程序来说,首先是通过 VIDIOC_QBUF 将所有 map 过的buffer 加入队列,然后通过 VIDIOC_STREAMON 开始 capture ,并进入 read loop ,在这里应用程序会等待直到有一个 buffer 被填满可以从队列中 dequeued ,当数据使用完后再enqueue 进输入队列;对于 output 应用程序来说,首先应用程序会 buffer 装满数据然后 enqueued ,当足够的 buffer 进入队列以后就调用 VIDIOC_STREAMON 将数据输出。

    有两种方法来阻塞应用程序的执行,直到有 buffer 能被 dequeued ,默认的是当调用 VIDIOC_DQBUF 的时候会被阻塞,直到有数据在 outgoing queue ,但是如果打开设备文件 的时候使用了 O_NONBLOCK ,则当调用 VIDIOC_DQBUF 而又没有数据可读的时候就会立即返回。另外一种方法是调用 select  poll 来对文件描述符进行监听是否有数据可读。

    VIDIOC_STREAMON  VIDIOC_STREAMOFF 两个 ioctl 用来开始和停止 capturing 或者 output ,而且 VIDIOC_STREAMOFF 会删除输入和输出队列中的所有 buffer 

    因此 drvier 如果要实现 memory mapping I/O 必须支持 VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON  VIDIOC_STREAMOFF ioctl, the mmap(), munmap(), select()  poll() 函数 。

     

    User Pointers 是一种综合了 Read/Write  memory mappded 优势的 I/O 方法, buffer 是由应用程序自己申请的,可以是在虚拟内存或者共享内存中。在 capture  output 方面基本来说和 memory mapped 方式是相同的,在这里只提一下它申请内存的方式。

    User pointer 方式下,申请的内存也 memory page size 为单位对齐,而且 buffersize 也有一定限制,例示代码中是这样计算 buffer size 的,暂时还不知道这样分配 buffer size 的依据是什么,先简单地这样用就好了:

    page_size = getpagesize ();

    buffer_size = (buffer_size + page_size - 1) & ~(page_size – 1);

    buffers[n_buffers].start = memalign (/* boundary */ page_size,

    buffer_size);

     

    3  start_capturing

    经过上面的一系列的数据协商已经 buffer 的分配以后就可以调用 VIDIOC_QBUF  buffer 全部加入输入队列中,并调用 VIDIOC_STREAM0N 开始捕获数据了:

    int ioctl(int fd, int request, struct v4l2_buffer *argp);

    //VIDIOC_QBUF VIDIOC_DQBUF

    int ioctl(int fd, int request, const int *argp);

    //VIDIOC_STREAM0N VIDIOC_STREAMOFF  int 参数是 buffer 类型)

     

    4  mainloop

    开始捕获数据以后就会进入一个主循环,可以使用 select 或者 poll 来监听文件描述符的状态,一旦有数据可读,就调用函数来读取数据。

     

    5  read_frame

    读取数据根据 I/O 方式的不同而不同:

    Read/Write 方式直接从文件描述符中读一个帧大小的数据;

    Memory mapped 方式下先从输出队列中 dequeued 一个 buffer ,然后对帧数据进行处理,处理完成以后再放入输入队列。

    User pointer 方式下也是首先从输出队列中 dequeued 一个 buffer ,然后对这个 buffer 进行判断,看是否是应用程序开始申请的 buffer ,然后再对这个 buffer 进行处理,最后放入输入队列。

     

    6  stop_capturing / uninit_device / close device

    最后就是捕捉以及资源释放并关闭 device 

    展开全文
  • Linux 下V4L2的使用例子

    2012-05-08 11:13:39
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret ) { LOG("VIDIOC_STREAMON failed (%d)\n", ret); return ret; } // Get frame ...

    #include
    #include
    #include
    #include
    #include
    #include

    //#define Android_ENV
    #ifdef Android_ENV
    #define LOG LOGV

     

    #else
    #define LOG printf
    #endif

    #define CAMERA_DEVICE "/dev/video0"
    #define CAPTURE_FILE "frame.jpg"

    #define VIDEO_WIDTH 640
    #define VIDEO_HEIGHT 480
    #define VIDEO_FORMAT V4L2_PIX_FMT_YUYV
    #define BUFFER_COUNT 4

    struct fimc_buffer {
    int length;
    void *start;
    } framebuf[BUFFER_COUNT];


    int main()
    {
    int i, ret;

    // Open Device
    int fd;
    fd = open(CAMERA_DEVICE, O_RDWR, 0);
    if (fd < 0) {
    LOG("Open %s failed\n", CAMERA_DEVICE);
    return -1;
    }

    // Query Capability
    struct v4l2_capability cap;
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if (ret < 0) {
    LOG("VIDIOC_QUERYCAP failed (%d)\n", ret);
    return ret;
    }

    // Print capability infomations
    LOG("Capability Informations:\n");
    LOG(" driver: %s\n", cap.driver);
    LOG(" card: %s\n", cap.card);
    LOG(" bus_info: %s\n", cap.bus_info);
    LOG(" version: %08X\n", cap.version);
    LOG(" capabilities: %08X\n", cap.capabilities);

    // Set Stream Format
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = VIDEO_WIDTH;
    fmt.fmt.pix.height = VIDEO_HEIGHT;
    fmt.fmt.pix.pixelformat = VIDEO_FORMAT;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if (ret < 0) {
    LOG("VIDIOC_S_FMT failed (%d)\n", ret);
    return ret;
    }

    // Get Stream Format
    ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
    if (ret < 0) {
    LOG("VIDIOC_G_FMT failed (%d)\n", ret);
    return ret;
    }

    // Print Stream Format
    LOG("Stream Format Informations:\n");
    LOG(" type: %d\n", fmt.type);
    LOG(" width: %d\n", fmt.fmt.pix.width);
    LOG(" height: %d\n", fmt.fmt.pix.height);
    char fmtstr[8];
    memset(fmtstr, 0, 8);
    memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);
    LOG(" pixelformat: %s\n", fmtstr);
    LOG(" field: %d\n", fmt.fmt.pix.field);
    LOG(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);
    LOG(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);
    LOG(" colorspace: %d\n", fmt.fmt.pix.colorspace);
    LOG(" priv: %d\n", fmt.fmt.pix.priv);
    LOG(" raw_date: %s\n", fmt.fmt.raw_data);

    // Request buffers
    struct v4l2_requestbuffers reqbuf;
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    reqbuf.count = BUFFER_COUNT;
    ret = ioctl(fd , VIDIOC_REQBUFS, &reqbuf);
    if(ret < 0) {
    LOG("VIDIOC_REQBUFS failed (%d)\n", ret);
    return ret;
    }

    // Queen buffers
    struct v4l2_buffer buf;
    for(i=0; i // Query buffer
    buf.index = i;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd , VIDIOC_QUERYBUF, &buf);
    if(ret < 0) {
    LOG("VIDIOC_QUERYBUF (%d) failed (%d)\n", i, ret);
    return ret;
    }

    // mmap buffer
    framebuf[i].length = buf.length;
    framebuf[i].start = (char *) mmap(0, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (framebuf[i].start == MAP_FAILED) {
    LOG("mmap (%d) failed: %s\n", i, strerror(errno));
    return -1;
    }

    // Queen buffer
    ret = ioctl(fd , VIDIOC_QBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_QBUF (%d) failed (%d)\n", i, ret);
    return -1;
    }

    LOG("Frame buffer %d: address=0x%x, length=%d\n", i, (unsigned int)framebuf[i].start, framebuf[i].length);
    }

    // Stream On
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0) {
    LOG("VIDIOC_STREAMON failed (%d)\n", ret);
    return ret;
    }

    // Get frame
    ret = ioctl(fd, VIDIOC_DQBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_DQBUF failed (%d)\n", ret);
    return ret;
    }

    // Process the frame
    FILE *fp = fopen(CAPTURE_FILE, "wb");
    if (fp < 0) {
    LOG("open frame data file failed\n");
    return -1;
    }
    fwrite(framebuf[buf.index].start, 1, buf.length, fp);
    fclose(fp);
    LOG("Capture one frame saved in %s\n", CAPTURE_FILE);

    // Re-queen buffer
    ret = ioctl(fd, VIDIOC_QBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_QBUF failed (%d)\n", ret);
    return ret;
    }

    // Release the resource
    for (i=0; i munmap(framebuf[i].start, framebuf[i].length);
    }

    close(fd);
    LOG("Camera test Done.\n");
    return 0;
    }


    展开全文
  • linux 下V4L2的使用例子

    千次阅读 2011-05-07 16:06:00
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret LOG("VIDIOC_STREAMON failed (%d)/n", ret); return ret; } // Get frame ret = ioctl...

    转自:http://blog.csdn.net/jaylin2013/archive/2010/07/01/5707331.aspx

    #include
    #include
    #include
    #include
    #include
    #include

    //#define ANDROID_ENV
    #ifdef ANDROID_ENV
    #define LOG LOGV
    #else
    #define LOG printf
    #endif

    #define CAMERA_DEVICE "/dev/video0"
    #define CAPTURE_FILE "frame.jpg"

    #define VIDEO_WIDTH 640
    #define VIDEO_HEIGHT 480
    #define VIDEO_FORMAT V4L2_PIX_FMT_YUYV
    #define BUFFER_COUNT 4

    struct fimc_buffer {
    int length;
    void *start;
    } framebuf[BUFFER_COUNT];


    int main()
    {
    int i, ret;

    // Open Device
    int fd;
    fd = open(CAMERA_DEVICE, O_RDWR, 0);
    if (fd < 0) {
    LOG("Open %s failed/n", CAMERA_DEVICE);
    return -1;
    }

    // Query Capability
    struct v4l2_capability cap;
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if (ret < 0) {
    LOG("VIDIOC_QUERYCAP failed (%d)/n", ret);
    return ret;
    }

    // Print capability infomations
    LOG("Capability Informations:/n");
    LOG(" driver: %s/n", cap.driver);
    LOG(" card: %s/n", cap.card);
    LOG(" bus_info: %s/n", cap.bus_info);
    LOG(" version: %08X/n", cap.version);
    LOG(" capabilities: %08X/n", cap.capabilities);

    // Set Stream Format
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = VIDEO_WIDTH;
    fmt.fmt.pix.height = VIDEO_HEIGHT;
    fmt.fmt.pix.pixelformat = VIDEO_FORMAT;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if (ret < 0) {
    LOG("VIDIOC_S_FMT failed (%d)/n", ret);
    return ret;
    }

    // Get Stream Format
    ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
    if (ret < 0) {
    LOG("VIDIOC_G_FMT failed (%d)/n", ret);
    return ret;
    }

    // Print Stream Format
    LOG("Stream Format Informations:/n");
    LOG(" type: %d/n", fmt.type);
    LOG(" width: %d/n", fmt.fmt.pix.width);
    LOG(" height: %d/n", fmt.fmt.pix.height);
    char fmtstr[8];
    memset(fmtstr, 0, 8);
    memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);
    LOG(" pixelformat: %s/n", fmtstr);
    LOG(" field: %d/n", fmt.fmt.pix.field);
    LOG(" bytesperline: %d/n", fmt.fmt.pix.bytesperline);
    LOG(" sizeimage: %d/n", fmt.fmt.pix.sizeimage);
    LOG(" colorspace: %d/n", fmt.fmt.pix.colorspace);
    LOG(" priv: %d/n", fmt.fmt.pix.priv);
    LOG(" raw_date: %s/n", fmt.fmt.raw_data);

    // Request buffers
    struct v4l2_requestbuffers reqbuf;
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    reqbuf.count = BUFFER_COUNT;
    ret = ioctl(fd , VIDIOC_REQBUFS, &reqbuf);
    if(ret < 0) {
    LOG("VIDIOC_REQBUFS failed (%d)/n", ret);
    return ret;
    }

    // Queen buffers
    struct v4l2_buffer buf;
    for(i=0; i // Query buffer
    buf.index = i;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd , VIDIOC_QUERYBUF, &buf);
    if(ret < 0) {
    LOG("VIDIOC_QUERYBUF (%d) failed (%d)/n", i, ret);
    return ret;
    }

    // mmap buffer
    framebuf[i].length = buf.length;
    framebuf[i].start = (char *) mmap(0, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (framebuf[i].start == MAP_FAILED) {
    LOG("mmap (%d) failed: %s/n", i, strerror(errno));
    return -1;
    }

    // Queen buffer
    ret = ioctl(fd , VIDIOC_QBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_QBUF (%d) failed (%d)/n", i, ret);
    return -1;
    }

    LOG("Frame buffer %d: address=0x%x, length=%d/n", i, (unsigned int)framebuf[i].start, framebuf[i].length);
    }

    // Stream On
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0) {
    LOG("VIDIOC_STREAMON failed (%d)/n", ret);
    return ret;
    }

    // Get frame
    ret = ioctl(fd, VIDIOC_DQBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_DQBUF failed (%d)/n", ret);
    return ret;
    }

    // Process the frame
    FILE *fp = fopen(CAPTURE_FILE, "wb");
    if (fp < 0) {
    LOG("open frame data file failed/n");
    return -1;
    }
    fwrite(framebuf[buf.index].start, 1, buf.length, fp);
    fclose(fp);
    LOG("Capture one frame saved in %s/n", CAPTURE_FILE);

    // Re-queen buffer
    ret = ioctl(fd, VIDIOC_QBUF, &buf);
    if (ret < 0) {
    LOG("VIDIOC_QBUF failed (%d)/n", ret);
    return ret;
    }

    // Release the resource
    for (i=0; i munmap(framebuf[i].start, framebuf[i].length);
    }

    close(fd);
    LOG("Camera test Done./n");
    return 0;
    }

    Powered by Zoundry Raven

    展开全文
  • 1. USERPTR, 顾名思义是用户空间指针意思,应用层负责分配需要内存空间,然后以指针形式传递给V4L2驱动层,V4L2驱动会把capture内容保存到指针所指空间 一般来说,应用层需要确保这个内存空间物理上是...

    视频应用可以通过两种方式从V4L2驱动申请buffer

    1. USERPTR, 顾名思义是用户空间指针的意思,应用层负责分配需要的内存空间,然后以指针的形式传递给V4L2驱动层,V4L2驱动会把capture的内容保存到指针所指的空间

    一般来说,应用层需要确保这个内存空间物理上是连续的(IPU处理单元的需求),在android系统可以通过PMEM驱动来分配大块的连续物理内存。应用层在不需要的时候要负责释放申请的PMEM内存。

    2. MMAP方式,内存映射模式,应用调用VIDIOC_REQBUFS ioctl分配设备buffers,参数标识需要的数目和类型。这个ioctl也可以用来改变buffers的数据以及释放分配的内存,当然这个内存空间一般也是连续的。在应用空间能够访问这些物理地址之前,必须调用mmap函数把这些物理空间映射为用户虚拟地址空间。

    虚拟地址空间是通过munmap函数释放的; 而物理内存的释放是通过VIDIOC_REQBUFS来实现的(设置参数buf count为(0)),物理内存的释放是实现特定的,mx51 v4l2是在关闭设备时进行释放的。

    所以二者都是申请连续的物理内存,只是申请和释放的方式不同

    转载于:https://www.cnblogs.com/Lxk0825/p/10382149.html

    展开全文
  • V4L2使用

    2020-08-06 15:47:59
    v4l2,一开始听到这个名词时候,以为又是一个很难很难模块,涉及到视频处理,后来在网上各种找资料后,才发现其实v4l2已经分装好了驱动程序,只要我们根据需要调用相应接口和函数,从而实现视频获取和处理...
  • v4l2基本使用

    2020-10-14 10:21:02
    struct v4l2_capability cap; ioctl(camrea_fd, VIDIOC_QUERYCAP, &amp;cap); 2获取设备支持标准制式 std struct v4l2_standard std; ioctl(camrea_fd, VIDIOC_ENUMSTD, &amp;std) 3显示所有支持帧...
  • 1,probe ...   ... v4l2_i2c_subdev_init(&s_ctrl->sensor_v4l2_subdev, client,s_ctrl->sensor_v4l2_subdev_ops);...这里v4l2的注册需要几个参数 s_ctrl->sensor_v4l2_subdev 这个参数主要是v
  • v4l2 api 使用介绍

    2010-10-26 01:15:55
    v4l2 api v4l2 api v4l2 api v4l2 api 很好一个介绍文档!!!!!!!
  • 使用live555 直播来自v4l2的摄像头数据

    万次阅读 多人点赞 2018-04-24 11:08:44
    使用live555 直播来自v4l2的摄像头数据,在我的这个工程中,基本思路是:使用V4L2采集摄像头数据,然后使用x264库对摄像头数据进行编码,编成H264数据格式,然后把数据写入到命名管道中。最后使用live555 从管道中...
  • v4l2使用技巧

    2017-11-01 12:36:23
    转载时请注明出处和作者联系方式 ... V4L2 较 V4L 有较大改动,并已成为 2.6 标准接口,函盖 video/ dvb /FM… ,多数驱动都在向 V4l2 迁移 。 V4L2 采用流水线方式,操作更简单直观。 一般来
  • 本文就V4L2的使用方式做简易说明。 视频采集的基本流程 一般的,视频采集都有如下流程: 打开视频设备 在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备: // 用非阻塞模式打开摄像头设备int ...
  • V4L2接口简单使用

    千次阅读 2018-04-11 17:26:31
     V4L2就是video for linux 2,也就是linux下针对视频采集一种编程接口,主体编程模式如下:案例:采集USB摄像头视频帧头文件:#include &lt;libv4l2.h&gt; #include &lt;linux/video...
  • 基于Streaming I/O的V4L2设备使用

    千次阅读 2017-04-12 11:32:47
    基于V4L2的应用,通常面临着大块数据的读取与拷贝等问题。尤其在嵌入式系统中,对于实时性能要求较高的应用,拷贝会花上几十个ms的时间,这通常轻则造成用户体验差,重则导致产品质量不达标。V4L2 Framework定义了几...
  • 基于V4L2驱动程序USB摄像头Android(JNI)编写 Linux环境下使用V4L2+opencv以MJPEG格式读取USB摄像头并实时显示 Linux之V4L2基础编程_内存映射 Linux内核中的V4L2核心框架分析
  • 趁任务完成间隙,来简单总结下V4L2的使用。(文章只主要写了过程,完整程序已经附在最后) 有读者要问,opencv已经有相关的读取摄像头的函数,为什么要使用V4L2这么麻烦呢。其实主要是因为后面要将程序移植到板子上...
  • 使用方法:解压,当然是第一步,有用文件就两个:v4l2test.c和gm_video.h, 直接gcc v4l2test.c 就行了,生成一个a.out可执行文件,运行后就会输出一些 提示信息,至于是什么意思,自己看看代码。运行完成后就会...
  • V4L2使用技巧

    2012-02-10 21:40:56
     V4L2 较 V4L 有较大改动,并已成为 2.6 标准接口,函盖 video/ dvb /FM… ,多数驱动都在向 V4l2 迁移 。 V4L2 采用流水线方式,操作更简单直观。 一般来说,需要用到函数就是 open() , clo
  • V4L2

    2021-04-12 17:58:10
    1、V4L和V4L2。 V4L是Linux环境下开发视频采集设备驱动程序一套规范(API),它为驱动程序编写提供...V4L2是V4L升级版,由于我们使用的OOB是3.3内核,不再支持V4L,因而编程不再考虑V4Lapi和参数定义。 ...
  • v4l2loopback-创建V4L2回送设备内核模块 该模块允许您创建“虚拟视频设备”。 普通(v4l2)应用程序将读取这些设备,就像它们是普通视频设备一样,但是不会从例如捕获卡中读取视频,而是由另一个应用程序生成。 ...
  • 使用RTMPdump(libRTMP)直播来自v4l2的摄像头数据》 通过RTMPdump库将v4l2采集到的摄像头数据推送到流媒体服务器实现直播的功能 rtmp/rtmp_push_aac 通过rtmp将aac音频文件推送到流媒体服务器 rtmp/rtmp_push_h...
  • V4L2框架概述

    万次阅读 多人点赞 2018-06-17 15:25:07
    本文开启 linux 内核 V4L2 框架部分学习之旅,本文仅先对 V4L2 框架做一个综述性概括介绍,然后接下来文章中会对 V4L2 框架各个子模块进行一个全面介绍,包括每一部分实现原理,如何使用,用在什么...
  • 最近在Linux3.0.1内核下采用V4L2接口使用zc301摄像头进行图像采集,可是接上去之后写应用程序运行后出现段错误,即segment fault。查看系统回溯信息对内核进行反汇编,查看V4L2相关内核代码得以解决。首先上错误...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 933
精华内容 373
关键字:

v4l2的使用