您好,欢迎来到好走旅游网。
搜索
您的当前位置:首页V4l2拍照API调用详解

V4l2拍照API调用详解

来源:好走旅游网
V4l2拍照API调⽤详解

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 :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 :

获得 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 是第⼀个预定义的 IDV4L2_CID_USER_BASE// 实际上等同于 V4L2_CID_BASEV4L2_CID_BRIGHTNESS integer// 图⽚的亮度,或者说⿊⾊位准

V4L2_CID_AUTO_WHITE_BALANCE boolean//camera 的⾃动⽩平衡

V4L2_CID_EXPOSURE integer//camera 的爆光时间V4L2_CID_LASTP1

// 最后⼀个预定义的 ID ,实际等于上⼀个 ID + 1V4L2_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 :

除了 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 :

对各种控制参数进⾏设置以后,下⾯要进⾏的就是要获得设备对 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 :

设置好取景窗⼝参数以后,下⾯要进⾏的设置就是对图形格式的协商,这个 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_FMTioctl 的调⽤是⼀个转折点,第⼀个调⽤ 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_CAPTUREunion fmt

struct v4l2_pix_format

//used for video capture and outputstruct 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 场,⼀个 videocamera 不会在⼀个时间内暴光⼀个整帧,⽽是将其分成场分别传输。所有的 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 factoror 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 typeenum 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_QUEUEDV4L2_BUF_FLAG_DONE) ,是否关键帧等等,具体可以参照 specenum v4l2_memory memory

//V4L2_MEOMORY_MMAP / V4L2_MEMORY_USERPTR / V4L2_MEMORY_OVERLAYunion 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 。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- haog.cn 版权所有 赣ICP备2024042798号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务