V4L2源代码之旅七:controls

转自:http://www.cnblogs.com/ronnydm/p/5787182.html

通过上两篇文章,我们已经成功的建立了/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就说这么多了,以后遇到再详细了解吧。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值