V4L2源代码之旅七:controls

时间:2021-01-05 05:33:11

  通过上两篇文章,我们已经成功的建立了/dev/video0这个字符设备,此时,在UserSpace就可以打开该设备,完成相应的调用。

  总结如何使用V4L2架构建立我们自己的设备驱动,其实就是以下3个结构体的设置及注册:

    1. struct v4l2_device

    2. struct v4l2_subdev

    3. struct video_device

  在使用V4L2设备时,即使用Camera时,必然用户希望可以调整Camera的参数,这就是通过Ctrl实现的(v4l2-ctrls.c)。

一. 内核文档阅读

Introduction
============
  V4L2 control API看起来好像很简单,但是如何快速的驱动程序中正确实现是非常困难的。大量的处理controls的代码实际上不是和驱动相关的,而且可以移动到V4L的core framework.

  毕竟,一个驱动开发者唯一关心的是:

  1)如何添加一个control?

  2)如何设置controls's value? (s_ctrl)

  偶尔会关心:

  3)如何获取control's value?(g_volatile_ctrl)

  4)如何验证用户的建议的control value?(try_ctrl)

  其他的事情集中处理。

  control framework是为了实现V4L2的集中控制规范所有规则而建立的。并且可以使驱动开发者尽可能简单。
  control framework依赖于struct v4l2_device和struct v4l2_subdev。

Objects in the framework
========================

两个重要的对象:

1. v4l2_ctrl描述控制属性并且跟踪control's value(current value和proposed new value)

2. v4l2_ctrl_handler是跟踪controls。维护了一个v4l2_ctrl对象链表和另一个控件的引用列表,也可能是其他handles所拥有的controls。

Basic usage for V4L2 and sub-device drivers
===========================================

1)准备:

1.1)添加handler到你的驱动的顶层结构体:

struct foo_dev {
...
struct v4l2_ctrl_handler ctrl_handler;
...
};

1.2)初始化handler:

v4l2_ctrl_handler_init(&foo->ctrl_handler, nr_of_controls);

  第二个参数是提示该函数有多少个该handler的controls。它会根据这个信息分配一个hastable。它仅仅是一个提示。

1.3) Hook the control handler into the driver:

1.3.1) For V4L2 drivers do this:

struct foo_dev {
...
struct v4l2_device v4l2_dev;
...
struct v4l2_ctrl_handler ctrl_handler;
...
};

foo
->v4l2_dev.ctrl_handler = &foo->ctrl_handler;

  最后,从v4l2_ioctl_ops删除所有的control函数:vidioc_queryctrl, vidioc_querymenu, vidioc_g_ctrl, vidioc_s_ctrl,  vidioc_g_ext_ctrls, vidioc_try_ext_ctrls and vidioc_s_ext_ctrls。

1.3.2) For sub-device drivers do this:

struct foo_dev {
...
struct v4l2_subdev sd;
...
struct v4l2_ctrl_handler ctrl_handler;
...
};
foo->sd.ctrl_handler = &foo->ctrl_handler

  设置core control ops在v4l2_subdev_core_ops:

    .queryctrl = v4l2_subdev_queryctrl,
.querymenu
= v4l2_subdev_querymenu,
.g_ctrl
= v4l2_subdev_g_ctrl,
.s_ctrl
= v4l2_subdev_s_ctrl,
.g_ext_ctrls
= v4l2_subdev_g_ext_ctrls,
.try_ext_ctrls
= v4l2_subdev_try_ext_ctrls,
.s_ext_ctrls
= v4l2_subdev_s_ext_ctrls,

  这只是一个临时的解决方案!!一旦所有的依赖于subdev驱动的V4L2驱动转换到control framework后,这些函数就不在需要。

1.4) Clean up the handler at the end:

v4l2_ctrl_handler_free(&foo->ctrl_handler);

 

2) Add controls:

  添加non-menu controls通过调用v4l2_ctrl_new_std:

struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl,
              
const struct v4l2_ctrl_ops *ops,
              u32 id, s32 min, s32 max, u32 step, s32 def);

  添加menu controls通过调用v4l2_ctrl_new_std_menu:

struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl,
              
const struct v4l2_ctrl_ops *ops,
                 u32 id, s32 max, s32 skip_mask, s32 def);

这些函数在调用v4l2_ctrl_handler_init之后调用:

    v4l2_ctrl_handler_init(&foo->ctrl_handler, nr_of_controls);
v4l2_ctrl_new_std(
&foo->ctrl_handler, &foo_ctrl_ops,
V4L2_CID_BRIGHTNESS,
0, 255, 1, 128);
v4l2_ctrl_new_std(
&foo->ctrl_handler, &foo_ctrl_ops,
V4L2_CID_CONTRAST,
0, 255, 1, 128);
v4l2_ctrl_new_std_menu(
&foo->ctrl_handler, &foo_ctrl_ops,
V4L2_CID_POWER_LINE_FREQUENCY,
V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
0,
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED);
...
if (foo->ctrl_handler.error) {
int err = foo->ctrl_handler.error;

v4l2_ctrl_handler_free(
&foo->ctrl_handler);
return err;
}

  v4l2_ctrl_new_std函数返回了指向新的controlde v4l2_ctrl指针,如果不需要可以不存储。

  v4l2_ctrl_new_std函数将会根据control ID(min,max,step,defaule values)填充大部分成员。这些值是由驱动的control attributes(type,name,flags)指定。control's的current value将会设置为default value.

  v4l2_ctrl_new_std_menu函数非常类似,但是是用来设置menu controls。不存在最小参数,对于menu controls总是为0,且存在skip_mask参数:如果bit X为1,那么menu item X将会被跳过。 

3) Optionally force initial control setup:

v4l2_ctrl_handler_setup(&foo->ctrl_handler);

  这就要求所有的controls无条件地调用s_ctrl。有小弟初始化hardware到默认的control values。建议这么做,因为这确保内部数据结构和硬件都是同步的。

4) Finally: implement the v4l2_ctrl_ops

    static const struct v4l2_ctrl_ops foo_ctrl_ops = {
.s_ctrl
= foo_s_ctrl,
};

 通常你需要的仅仅是s_ctrl:

  static int foo_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct foo *state = container_of(ctrl->handler, struct foo, ctrl_handler);

switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
write_reg(
0x123, ctrl->val);
break;
case V4L2_CID_CONTRAST:
write_reg(
0x456, ctrl->val);
break;
}
return 0;
}

  control ops被调用以v4l2_ctrl指针作为参数。新的控制值已经被验证,所以你需要做的是要实际更新硬件寄存器。

 

You're done!对于大多数的驱动是足够的!不需要做任何对于control values的确认,也不腰实现QUERYCTRL/QUERYMENU。并且G/S_CTRL和G/TRY/S_EXT_CTRLS被自动的支持。

2.  理解

  用户存在这样的需求:设置摄像头的参数,例如亮度,对比度等。这些参数可在视频应用中调整,有时也的确也会这样做,但是当硬件支持时,在硬件在调整有其优势。比如当亮度调整,如果在应用中调整而不调整硬件,可能会丢失动态范围,但是基于硬件的调整可以完整保持传感器可传递的动态范围。而且,基于硬件调整可以较少CPU的压力。

  现代硬件通常可以在运行时调整很多参数。然而,现在在不同设备之间这些参数差别很大。简单的亮度调整可以直观地设置一个寄存器,也可能需要处理一个非常复杂的矩阵变换。最好是尽可能把诸多细节对应用隐藏,但能隐藏到什么程度却收到很多限制。一个过于抽象的接口会使硬件的控制无法发挥到极限。

  V4L2的控制接口试图使事情尽可能简化,同时还能完全的发挥硬件的性能。它始于定义一个标准的控制名的集合,包括V4L2_CID_BRIGHTNESS,V4L2_CID_CONTRAST,V4L2_CID_SATURATION,还有许多其他定义。对于白平衡,水平/垂直镜像等特定,还提供了一些布尔类型的控制。

/* kernel/include/linux/videodev2.h */
/* User-class control IDs defined by V4L2 */
#define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900)
#define V4L2_CID_USER_BASE V4L2_CID_BASE
/* IDs reserved for driver specific controls */
#define V4L2_CID_PRIVATE_BASE 0x08000000

#define V4L2_CID_USER_CLASS (V4L2_CTRL_CLASS_USER | 1)
#define V4L2_CID_BRIGHTNESS (V4L2_CID_BASE+0)
#define V4L2_CID_CONTRAST (V4L2_CID_BASE+1)
#define V4L2_CID_SATURATION (V4L2_CID_BASE+2)
#define V4L2_CID_HUE (V4L2_CID_BASE+3)
#define V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5)
#define V4L2_CID_AUDIO_BALANCE (V4L2_CID_BASE+6)
#define V4L2_CID_AUDIO_BASS (V4L2_CID_BASE+7)
#define V4L2_CID_AUDIO_TREBLE (V4L2_CID_BASE+8)
#define V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9)
#define V4L2_CID_AUDIO_LOUDNESS (V4L2_CID_BASE+10)
#define V4L2_CID_BLACK_LEVEL (V4L2_CID_BASE+11) /* Deprecated */
#define V4L2_CID_AUTO_WHITE_BALANCE (V4L2_CID_BASE+12)
#define V4L2_CID_DO_WHITE_BALANCE (V4L2_CID_BASE+13)
#define V4L2_CID_RED_BALANCE (V4L2_CID_BASE+14)
#define V4L2_CID_BLUE_BALANCE (V4L2_CID_BASE+15)
#define V4L2_CID_GAMMA (V4L2_CID_BASE+16)
#define V4L2_CID_WHITENESS (V4L2_CID_GAMMA) /* Deprecated */
#define V4L2_CID_EXPOSURE (V4L2_CID_BASE+17)
#define V4L2_CID_AUTOGAIN (V4L2_CID_BASE+18)
#define V4L2_CID_GAIN (V4L2_CID_BASE+19)
#define V4L2_CID_HFLIP (V4L2_CID_BASE+20)
#define V4L2_CID_VFLIP (V4L2_CID_BASE+21)

  应用如何知道设备支持那些特性的控制?一种典型的做法,V4L2 API提供了一种机制可以让应用能枚举可用的控制操作。为此,他们要发出最终由驱动videoc_queryctrl()方法实现的ioctl()调用。

int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc);

  驱动通常会用所关心的控制信息来填充qc结构体,或当控制操作不支持时返回-EINVAL,这个结构体如下:

/*  Used in the VIDIOC_QUERYCTRL ioctl for querying controls */
struct v4l2_queryctrl {
__u32 id;
enum v4l2_ctrl_type type;
__u8 name[
32]; /* Whatever */
__s32 minimum;
/* Note signedness */
__s32 maximum;
__s32 step;
__s32 default_value;
__u32 flags;
__u32 reserved[
2];
};

  被查询的控制操作将会通过id传送。作为一种特殊的情况,应用可以通过设定V4L2_CTRL_FLAG_NEXT_CTRL位的方式传递控制id。当这种情况发生时,驱动会返回关于下一个所支持的控制id的信息,这比应用给出的ID要高。无论在任何情况下,id都应设为实际上被控制操作的id。

  其他所有的字段均由驱动设定,用来描述所选的控制操作。控制的数据类型在type字段中设定。可以是:

enum v4l2_ctrl_type {
V4L2_CTRL_TYPE_INTEGER
= 1,
V4L2_CTRL_TYPE_BOOLEAN
= 2,
V4L2_CTRL_TYPE_MENU
= 3,
V4L2_CTRL_TYPE_BUTTON
= 4,
V4L2_CTRL_TYPE_INTEGER64
= 5,
V4L2_CTRL_TYPE_CTRL_CLASS
= 6,
V4L2_CTRL_TYPE_STRING
= 7,
};

  name用来描述控制操作。它可以在展现给用户的应用接口中使用。

  对于整型的控制来说,minimum和maximum描述的是控制所实现的范围,setp给出的是再次范围下的粒度大小。default_value顾名思义就是默认值——尽管它只对整型,布尔型和菜单控制使用。驱动仅应在初始化时将参数设置为默认。至于其他设备参数,他们应该从open到close爆出不变。结果,default_value很可能不是现在的控制参数值。

  还有一组定义值进一步描述控制操作:

/*  Control flags  */
#define V4L2_CTRL_FLAG_DISABLED 0x0001
#define V4L2_CTRL_FLAG_GRABBED 0x0002
#define V4L2_CTRL_FLAG_READ_ONLY 0x0004
#define V4L2_CTRL_FLAG_UPDATE 0x0008
#define V4L2_CTRL_FLAG_INACTIVE 0x0010
#define V4L2_CTRL_FLAG_SLIDER 0x0020
#define V4L2_CTRL_FLAG_WRITE_ONLY 0x0040

  应用可以只查询几个特定的编程过的控制操作,或者枚举整个集合。对于后者来说,他们会从V4L2_CID_BASE开始至V4L2_CID_LASTP1结束,过程可能会用到V4L2_CTRL_FLAG_NEXT_CTRL标签。对于菜单型的诸多控制操作(type == V4L2_CTRL_TYPE_MENU)而言,应用很可能希望枚举可能的值,相关的回调函数:

int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm);
/*  Used in the VIDIOC_QUERYMENU ioctl for querying menu items */
struct v4l2_querymenu {
__u32 id;
__u32 index;
__u8 name[
32]; /* Whatever */
__u32 reserved;
};

  在输入中,id是相关菜单控制操作的ID值,index为某特定菜单ID值的索引值。索引值为0开始,依次递增到videoc_queryctrl()返回的最大值。驱动会填充菜单项的name字段。

reserved字段恒为0.

  一旦应用知道了可用的控制操作,它就开始查询并改变其值。这种情况下相关的结构体是:

/*
* C O N T R O L S
*/
struct v4l2_control {
__u32 id;
__s32 value;
};

  要查询某一给定控制操作,应用应将id字段设为对应的控制的ID,并发出一个调用,这个调用最终实现为:

int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *ctrl);

  驱动应将值设为当前控制的设定,还要保证它知道这个特定的控制操作并在应用师徒查询不存在的控制操作时返回-EINVAL,师徒访问按键控制时也应返回-EINVAL。

一个试图改变控制操作的请求实现为:

int v4l2_s_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *ctrl);

  驱动应验证id,保证其值在允许的区间。如果一切都没有问题的话,就将新值写入硬件。

  最后值得注意的是:V4L2还支持一个独立的扩展控制接口。这个API是一组相当复杂的控制操作。实际上,它的主要引用是MPEG编码参数。扩展控制可以分门归类,而且支持64为整型值。其接口与常规的控制接口类似。

 

  后记:由于我们的系统没有使用Ctrl,因为我们不是一个标准的Android手机,所有不需要调整亮度,对比度这些参数。所以,关于Controls就说这么多了,以后遇到再详细了解吧。