获得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 capturedevice 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。