一、v4l2 controls介绍:
V4L2 控制 API 看起来足够简单,但在驱动程序中正确实现时会变得非常困难。然而,处理控制所需的大部分代码实际上并非特定于驱动程序,可以移到 V4L2 核心框架中 。毕竟,驱动开发者关心的唯一部分是:
● 1、我如何添加一个控制?
● 2、我如何设置控制的值?(即 s_ctrl)
偶尔还会有:
● 1、我如何获取控制的值?(即 g_volatile_ctrl)
● 2、我如何验证用户提议的控制值?(即 try_ctrl)
其余的部分可以集中处理。
控制框架的创建是为了在一个中心位置实现与控制相关的所有 V4L2 规范规则,并尽可能简化驱动开发者的工作。
请注意,控制框架依赖于 V4L2 驱动中的 struct v4l2_device 和子设备驱动中的 struct v4l2_subdev 的存在。
二、框架中的对象 :
有两个主要对象:
● v4l2_ctrl 对象描述了控制的属性,并跟踪控制的值(包括当前值和提议的新值)。
● v4l2_ctrl_handler 是跟踪控制的对象。它维护一个由自己拥有的 v4l2_ctrl 对象的列表,以及一个包含对控制的引用的列表,这些控制可能是由其他处理器拥有的。
三、V4L2 和子设备驱动的基本使用步骤:
1、Prepare the driver:
1.1) Add the handler to your driver’s top-level struct:
struct foo_dev {
...
struct v4l2_ctrl_handler ctrl_handler;
...
};
struct foo_dev *foo;
1.2) Initialize the handler:
v4l2_ctrl_handler_init(&foo->ctrl_handler, nr_of_controls);
第二个参数是一个提示,告诉函数这个处理器预计要处理多少个控制。它将根据这个信息分配一个哈希表。这个参数仅仅是一个提示
1.3) Hook the control handler into the driver( 将控制处理器挂接到驱动程序中):
unsetunset1.3.1) For V4L2 drivers do this:unsetunset
struct foo_dev {
...
struct v4l2_device v4l2_dev;
...
struct v4l2_ctrl_handler ctrl_handler;
...
};
foo->v4l2_dev.ctrl_handler = &foo->ctrl_handler;
其中,foo->v4l2_dev 的类型是 struct v4l2_device。
最后,从你的 v4l2_ioctl_ops 中移除所有控制函数(如果有的话):vidioc_queryctrl、vidioc_query_ext_ctrl、vidioc_querymenu、vidioc_g_ctrl、vidioc_s_ctrl、vidioc_g_ext_ctrls、vidioc_try_ext_ctrls 和 vidioc_s_ext_ctrls。这些现在不再需要了。
unsetunset1.3.2) For sub-device drivers do this:unsetunset
struct foo_dev {
...
struct v4l2_subdev sd;
...
struct v4l2_ctrl_handler ctrl_handler;
...
};
foo->sd.ctrl_handler = &foo->ctrl_handler;
其中,foo->sd 的类型是 struct v4l2_subdev.
1.4) Clean up the handler at the end:
v4l2_ctrl_handler_free(&foo->ctrl_handler);
2、Add controls:
通过调用 v4l2_ctrl_new_std 来添加非菜单控制:
struct v4l2_ctrl * v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl,\
const struct v4l2_ctrl_ops *ops, u32 id , s32 min,\
s32 max , u32 step, s32 def);
通过调用 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_new_std_menu_items 来添加具有驱动特定菜单的菜单控制:
struct v4l2_ctrl *v4l2_ctrl_new_std_menu_items(
struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops, u32 id, s32 max,
s32 skip_mask, s32 def, const char * const *qmenu);
通过调用 v4l2_ctrl_new_int_menu 可以添加具有驱动特定菜单的整数菜单控制:
struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops,
u32 id, s32 max, s32 def, const s64 *qmenu_int);
这些函数通常在 v4l2_ctrl_handler_init 之后立即调用
static const s64 exp_bias_qmenu[] = {
-2, -1, 0, 1, 2
};
static const char * const test_pattern[] = {
"Disabled",
"Vertical Bars",
"Solid Black",
"Solid White",
};
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);
v4l2_ctrl_new_int_menu(&foo->ctrl_handler, &foo_ctrl_ops,
V4L2_CID_EXPOSURE_BIAS,
ARRAY_SIZE(exp_bias_qmenu) - 1,
ARRAY_SIZE(exp_bias_qmenu) / 2 - 1,
exp_bias_qmenu);
v4l2_ctrl_new_std_menu_items(&foo->ctrl_handler, &foo_ctrl_ops,
V4L2_CID_TEST_PATTERN, ARRAY_SIZE(test_pattern) - 1, 0,
0, test_pattern);
...
if (foo->ctrl_handler.error) {
int err = foo->ctrl_handler.error;
v4l2_ctrl_handler_free(&foo->ctrl_handler);
return err;
}
v4l2_ctrl_new_std 函数返回指向新控制的 v4l2_ctrl 指针,但如果你不需要在控制操作之外访问该指针,则不需要存储它。
v4l2_ctrl_new_std 函数将根据控制 ID 填充大部分字段,除了最小值、最大值、步长和默认值。这些值通过最后四个参数传入。这些值是驱动特定的,而控制的属性,如类型、名称、标志等则是全局的。控制的当前值将设置为默认值。
v4l2_ctrl_new_std_menu 函数与 v4l2_ctrl_new_std 非常相似,但它用于菜单控制。由于菜单控制的最小值总是 0,因此没有 min 参数,而是有一个 skip_mask 参数:如果第 X 位为 1,则跳过菜单项 X。
v4l2_ctrl_new_int_menu 函数创建一个新的标准整数菜单控制,并在菜单中添加驱动特定的项。它与 v4l2_ctrl_new_std_menu 的不同之处在于,它没有 mask 参数,最后一个参数是一个包含有符号 64 位整数的数组,构成了精确的菜单项列表。
v4l2_ctrl_new_std_menu_items 函数与 v4l2_ctrl_new_std_menu 非常相似,但它多了一个额外的参数 qmenu,这是一个驱动特定的菜单,用于标准菜单控制的特殊情况。一个好的例子是捕捉/显示/传感器设备的测试模式控制,这些设备具有生成测试模式的能力。由于这些测试模式是硬件特定的,因此菜单的内容会因设备而异。
请注意,如果发生错误,函数将返回 NULL 或错误,并将 ctrl_handler->error 设置为错误代码。如果 ctrl_handler->error 已经被设置,那么它将直接返回并不执行任何操作。如果 v4l2_ctrl_handler_init 无法分配内部数据结构,也会如此。
这使得初始化处理器并添加所有控制变得简单,最后只需要检查错误代码,避免了大量重复的错误检查。
建议按控制 ID 的升序添加控制:这样会稍微提高一些性能。
3、Optionally force initial control setup( 可选地强制初始控制设置):
v4l2_ctrl_handler_setup(&foo->ctrl_handler);
这将无条件地为所有控制调用 s_ctrl。实际上,这会将硬件初始化为默认控制值。建议这样做,因为这可以确保内部数据结构和硬件保持同步。
4、Finally: implement the v4l2_ctrl_ops( 最后:实现v4l2_ctrl_ops)
static const struct v4l2_ctrl_ops foo_ctrl_ops = {
.s_ctrl = foo_s_ctrl,
};
Usually all you need is 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;
}
控制操作会以 v4l2_ctrl 指针作为参数被调用。新的控制值已经过验证,因此你需要做的就是实际更新硬件寄存器。
完成了!对于我们大多数驱动程序来说,这已经足够了。无需进行控制值的验证,也无需实现 QUERYCTRL、QUERY_EXT_CTRL 和 QUERYMENU。G/S_CTRL 以及 G/TRY/S_EXT_CTRLS 也会自动得到支持。
其余部分涉及更高级的控制话题和场景。实际上,上述描述的基本使用方法对于大多数驱动程序来说已经足够。
四、Inheriting Controls(继承控制 ):
当通过调用 v4l2_device_register_subdev() 将子设备注册到 V4L2 驱动程序,并且同时设置了 v4l2_subdev 和 v4l2_device 的 ctrl_handler 字段时,子设备的控制将自动在 V4L2 驱动程序中变得可用。如果子设备驱动程序包含的控制在 V4L2 驱动程序中已经存在,那么这些控制将被跳过(因此 V4L2 驱动程序始终可以覆盖子设备控制)。
这里发生的事情是,v4l2_device_register_subdev() 调用 v4l2_ctrl_add_handler(),将子设备的控制添加到 v4l2_device 的控制中。
五、(Accessing Control Values)访问控制值 :
以下联合体在控制框架中用于访问控制值:
union v4l2_ctrl_ptr {
s32 *p_s32;
s64 *p_s64;
char *p_char;
void *p;
};
v4l2_ctrl 结构体包含以下字段,可以用来访问当前值和新值:
s32 val;
struct {
s32 val;
} cur;
union v4l2_ctrl_ptr p_new;
union v4l2_ctrl_ptr p_cur;
如果控制具有简单的 s32 类型,那么:
&ctrl->val == ctrl->p_new.p_s32
&ctrl->cur.val == ctrl->p_cur.p_s32
对于所有其他类型,使用 ctrl->p_cur.p。基本上,val 和 cur.val 字段可以视为别名,因为它们被频繁使用。
在控制操作中,你可以自由地使用这些字段。val 和 cur.val 的含义显而易见。p_char 指针指向长度为 ctrl->maximum + 1 的字符缓冲区,并且总是以 0 结尾。
除非该控制被标记为易失性(volatile),否则 p_cur 字段指向当前的缓存控制值。当你创建一个新的控制时,这个值会与默认值相同。调用 v4l2_ctrl_handler_setup() 后,这个值会被传递到硬件。通常,调用这个函数是一个好主意。
每当设置一个新值时,这个新值会自动缓存。这意味着大多数驱动程序不需要实现 g_volatile_ctrl() 操作。唯一的例外是对于那些返回易失性寄存器的控制,比如持续变化的信号强度读数。在这种情况下,你需要像下面这样实现 g_volatile_ctrl:
static int foo_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
ctrl->val = read_reg(0x123);
break;
}
}
请注意,在 g_volatile_ctrl 中也使用了“新值”联合体。通常,需要实现 g_volatile_ctrl 的控制是只读控制。如果它们不是只读的,那么当控制变化时,将不会生成 V4L2_EVENT_CTRL_CH_VALUE 事件。要将一个控制标记为易失性(volatile),你必须设置 V4L2_CTRL_FLAG_VOLATILE:
ctrl = v4l2_ctrl_new_std(&sd->ctrl_handler, ...);
if (ctrl)
ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
对于 try/s_ctrl,新的值(即用户传递的值)会被填充,你可以在 try_ctrl 中修改它们,或者在 s_ctrl 中设置它们。cur 联合体包含当前值,你可以使用它(但不能更改!)。
如果 s_ctrl 返回 0(表示成功),那么控制框架会将新的最终值复制到 cur 联合体中。
在 g_volatile、s_ctrl 或 try_ctrl 中,你可以访问所有由同一处理器拥有的控制的值,因为处理器的锁已经被持有。如果需要访问由其他处理器拥有的控制值,那么你必须非常小心,以避免引入死锁。
在控制操作外部,你必须通过帮助函数来安全地获取或设置驱动中的单个控制值:
s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl);
int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val);
这些函数通过控制框架,就像 VIDIOC_G/S_CTRL ioctls 一样。但是不要在控制操作 g_volatile、s_ctrl 或 try_ctrl 中使用这些函数,因为这会导致死锁,因为这些帮助函数也会锁定处理器。
你也可以自己手动获取处理器的锁:
mutex_lock(&state->ctrl_handler.lock);
pr_info("String value is '%s'\n", ctrl1->p_cur.p_char);
pr_info("Integer value is '%s'\n", ctrl2->cur.val);
mutex_unlock(&state->ctrl_handler.lock);
六、Menu Controls:
The v4l2_ctrl struct contains this union:
union {
u32 step;
u32 menu_skip_mask;
};
对于菜单控制,使用 menu_skip_mask。它的作用是允许你轻松地排除某些菜单项。这在 VIDIOC_QUERYMENU 实现中使用,你可以在某个菜单项不存在时返回 -EINVAL。需要注意的是,VIDIOC_QUERYCTRL 始终返回菜单控制的步长值为 1。
一个很好的例子是 MPEG 音频 Layer II 比特率菜单控制,其中菜单是一个标准化的可能比特率列表。但实际上,硬件实现只会支持其中的一部分。通过设置跳过掩码,你可以告诉框架应该跳过哪些菜单项。将其设置为 0 表示所有菜单项都被支持。
你可以通过 v4l2_ctrl_config 结构体为自定义控制设置此掩码,或者通过调用 v4l2_ctrl_new_std_menu() 来设置。
七、Custom Controls:
可以使用 v4l2_ctrl_new_custom() 创建驱动特定的控制:
static const struct v4l2_ctrl_config ctrl_filter = {
.ops = &ctrl_custom_ops,
.id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER,
.name = "Spatial Filter",
.type = V4L2_CTRL_TYPE_INTEGER,
.flags = V4L2_CTRL_FLAG_SLIDER,
.max = 15,
.step = 1,
};
ctrl = v4l2_ctrl_new_custom(&foo->ctrl_handler, &ctrl_filter, NULL);
最后一个参数是 priv 指针,可以设置为驱动特定的私有数据。v4l2_ctrl_config 结构体中也有一个字段,用来设置 is_private 标志。如果没有设置 name 字段,框架将假定这是一个标准控制,并相应地填充 name、type 和 flags 字段。
八、活动控制和抓取控制(Active and Grabbed Controls) :
如果控制之间有更复杂的关系,那么你可能需要激活和停用一些控制。例如,如果色度自动增益控制(Chroma AGC)打开,则色度增益控制(Chroma Gain)将变为非活动状态。也就是说,你可以设置它,但只要自动增益控制开启,硬件将不会使用该值。通常用户界面会禁用这些输入字段。
你可以使用 v4l2_ctrl_activate() 设置“活动”状态。默认情况下,所有控制都是活动的。请注意,框架并不会检查这个标志,它仅用于图形用户界面(GUI)。该函数通常在 s_ctrl 中调用。
另一个标志是“抓取”标志(‘grabbed’)。一个被抓取的控制意味着你无法更改它,因为它正在被某些资源使用。典型的例子是 MPEG 比特率控制,在捕获进行时无法更改。如果一个控制被设置为“抓取”状态(使用 v4l2_ctrl_grab()),那么如果尝试设置该控制,框架将返回 -EBUSY。v4l2_ctrl_grab() 函数通常在驱动程序开始或停止流媒体时调用。
九、控制集群 (Control Clusters):
默认情况下,所有控制是彼此独立的。但在更复杂的场景中,可能会出现一个控制依赖于另一个控制的情况。在这种情况下,你需要将它们“聚集”在一起:
struct foo {
struct v4l2_ctrl_handler ctrl_handler;
#define AUDIO_CL_VOLUME (0)
#define AUDIO_CL_MUTE (1)
struct v4l2_ctrl *audio_cluster[2];
...
};
state->audio_cluster[AUDIO_CL_VOLUME] =
v4l2_ctrl_new_std(&state->ctrl_handler, ...);
state->audio_cluster[AUDIO_CL_MUTE] =
v4l2_ctrl_new_std(&state->ctrl_handler, ...);
v4l2_ctrl_cluster(ARRAY_SIZE(state->audio_cluster), state->audio_cluster);
从现在开始,每当同一集群中一个或多个控制被设置(或“获取”,或“尝试”)时,只会调用第一个控制(在这个例子中是“音量”)的控制操作。你实际上创建了一个新的复合控制,类似于 C 中的 struct。
因此,当 s_ctrl 被调用,参数为 V4L2_CID_AUDIO_VOLUME 时,你应该设置属于 audio_cluster 的所有两个控制:
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_AUDIO_VOLUME: {
struct v4l2_ctrl *mute = ctrl->cluster[AUDIO_CL_MUTE];
write_reg(0x123, mute->val ? 0 : ctrl->val);
break;
}
case V4L2_CID_CONTRAST:
write_reg(0x456, ctrl->val);
break;
}
return 0;
}
在上面的例子中,以下对于音量(VOLUME)情况是等价的:
ctrl == ctrl->cluster[AUDIO_CL_VOLUME] == state->audio_cluster[AUDIO_CL_VOLUME]
ctrl->cluster[AUDIO_CL_MUTE] == state->audio_cluster[AUDIO_CL_MUTE]
实际上,像这样使用集群数组会变得非常繁琐。因此,通常使用以下等效的方法:
struct {
/* audio cluster */
struct v4l2_ctrl *volume;
struct v4l2_ctrl *mute;
};
匿名 struct 被用来清晰地“聚集”这两个控制指针,但它没有其他用途。其效果与创建一个包含两个控制指针的数组相同。因此,你可以直接这样做:
state->volume = v4l2_ctrl_new_std(&state->ctrl_handler, ...);
state->mute = v4l2_ctrl_new_std(&state->ctrl_handler, ...);
v4l2_ctrl_cluster(2, &state->volume);
在 foo_s_ctrl 中,你可以直接使用这些指针:state->mute->val。
需要注意的是,集群中的控制可能是 NULL。例如,如果由于某种原因音量静音控制(mute)从未添加(因为硬件不支持该特定功能),那么 mute 将是 NULL。所以,在这种情况下,我们有一个包含两个控制的集群,其中只有一个控制是实际实例化的。唯一的限制是集群中的第一个控制必须始终存在,因为它是集群的“主控”。主控是识别集群并提供指向用于该集群的 v4l2_ctrl_ops 结构体指针的控制。
显然,集群数组中的所有控制必须初始化为有效控制或 NULL。
在少数情况下,你可能想知道集群中哪些控制是用户明确设置的。为此,你可以检查每个控制的 is_new 标志。例如,在音量/静音集群的情况下,如果用户只为静音调用了 VIDIOC_S_CTRL,则静音控制的 is_new 标志会被设置。如果用户为音量和静音都调用了 VIDIOC_S_EXT_CTRLS,则两个控制的 is_new 标志都会被设置为 1。
在从 v4l2_ctrl_handler_setup() 调用时,is_new 标志始终为 1。
十、处理自动增益/增益类型控制与自动集群:
一种常见的控制集群类型是处理“自动-foo/foo”类型的控制。典型的例子有自动增益/增益、自动曝光/曝光、自动白平衡/红色平衡/蓝色平衡。在所有这些情况下,你有一个控制决定另一个控制是否由硬件自动处理,还是由用户手动控制。
如果集群处于自动模式,则手动控制应标记为非活动(inactive)和易变(volatile)。当读取这些易变控制时,g_volatile_ctrl 操作应返回由硬件自动模式自动设置的值。如果集群切换到手动模式,则手动控制应重新变为活动(active),并且清除易变标志(这样在手动模式下不再调用 g_volatile_ctrl)。此外,在切换到手动模式之前,自动模式确定的当前值将作为新的手动值进行复制。
最后,应该为自动控制设置 V4L2_CTRL_FLAG_UPDATE 标志,因为更改该控制会影响手动控制的控制标志。
为了简化这一过程,引入了 v4l2_ctrl_cluster 的一个特殊变种:
void v4l2_ctrl_auto_cluster(unsigned ncontrols, struct v4l2_ctrl **controls,
u8 manual_val, bool set_volatile);
前两个参数与 v4l2_ctrl_cluster 相同。第三个参数告诉框架哪个值将集群切换到手动模式。最后一个参数将可选地为非自动控制设置 V4L2_CTRL_FLAG_VOLATILE 标志。如果它为 false,则手动控制永远不会是易变的。通常在硬件不允许你读取自动模式下确定的值时使用这个选项(例如,如果启用了自动增益,硬件不允许你获取当前增益值)。
集群中的第一个控制被假定为“自动”控制。
使用此函数将确保你不需要处理所有复杂的标志和易变处理逻辑。
十一、支持 VIDIOC_LOG_STATUS
这个 ioctl 允许你将驱动程序的当前状态转储到内核日志中。v4l2_ctrl_handler_log_status(ctrl_handler, prefix) 可以用来将由给定控制处理程序(handler)拥有的控制值转储到日志中。你还可以提供一个前缀。如果前缀没有以空格结尾,系统会自动为你添加 。
十二、不同视频节点使用不同的控制处理程序:
通常,V4L2 驱动只有一个全局的控制处理程序,适用于所有的视频节点。但你也可以为不同的视频节点指定不同的控制处理程序。可以通过手动设置 struct video_device 中的 ctrl_handler 字段来实现这一点。
如果没有涉及子设备(subdev),这没有问题;但如果有子设备,那么你需要阻止自动将子设备的控制合并到全局控制处理程序中。你可以通过简单地将 struct v4l2_device 中的 ctrl_handler 字段设置为 NULL 来做到这一点。这样,v4l2_device_register_subdev() 就不会再自动合并子设备的控制了。
在添加了每个子设备后,你需要手动调用 v4l2_ctrl_add_handler 将子设备的控制处理程序(sd->ctrl_handler)添加到所需的控制处理程序中。这个控制处理程序可以是特定于 video_device 的,也可以是某些 video_device 的子集。例如:广播设备节点只包含音频控制,而视频和 VBI 设备节点共享音频和视频控制的同一个控制处理程序。
如果你希望让一个控制处理程序(例如,用于广播设备节点的)包含另一个控制处理程序(例如,用于视频设备节点的一个子集),那么你应该首先将控制添加到第一个处理程序,然后将其他控制添加到第二个处理程序,最后将第一个处理程序添加到第二个处理程序。例如:
v4l2_ctrl_new_std(&radio_ctrl_handler, &radio_ops, V4L2_CID_AUDIO_VOLUME, ...);
v4l2_ctrl_new_std(&radio_ctrl_handler, &radio_ops, V4L2_CID_AUDIO_MUTE, ...);
v4l2_ctrl_new_std(&video_ctrl_handler, &video_ops, V4L2_CID_BRIGHTNESS, ...);
v4l2_ctrl_new_std(&video_ctrl_handler, &video_ops, V4L2_CID_CONTRAST, ...);
v4l2_ctrl_add_handler(&video_ctrl_handler, &radio_ctrl_handler, NULL);
v4l2_ctrl_add_handler() 的最后一个参数是一个过滤函数,它允许你筛选将要添加的控制。如果你希望添加所有控制,可以将这个参数设置为 NULL。或者,你也可以将特定的控制添加到一个控制处理程序中:
volume = v4l2_ctrl_new_std(&video_ctrl_handler, &ops, V4L2_CID_AUDIO_VOLUME, ...);
v4l2_ctrl_new_std(&video_ctrl_handler, &ops, V4L2_CID_BRIGHTNESS, ...);
v4l2_ctrl_new_std(&video_ctrl_handler, &ops, V4L2_CID_CONTRAST, ...);
你不应该为两个控制处理程序创建两个相同的控制。例如:
v4l2_ctrl_new_std(&radio_ctrl_handler, &radio_ops, V4L2_CID_AUDIO_MUTE, ...);
v4l2_ctrl_new_std(&video_ctrl_handler, &video_ops, V4L2_CID_AUDIO_MUTE, ...);
这样做不好,因为静音收音机不会改变视频静音控制。规则是,每个硬件“旋钮”应该只有一个控制项可以调节。
十二、查找控制:
通常,你自己创建了控制项,并可以将 v4l2_ctrl 结构体指针存储到你自己的结构体中。
但有时你需要从另一个控制处理程序中查找一个控制项,而你并不拥有该处理程序。例如,如果你需要从子设备(subdev)中查找一个音量控制项。你可以通过调用 v4l2_ctrl_find 来实现这一点:
struct v4l2_ctrl *volume;
volume = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_AUDIO_VOLUME);
由于 v4l2_ctrl_find 会锁定控制处理程序(handler),因此你需要小心使用它。例如,以下这种做法不是一个好主意:
...and in video_ops.s_ctrl:
case V4L2_CID_BRIGHTNESS:
contrast = v4l2_find_ctrl(&ctrl_handler, V4L2_CID_CONTRAST);
...
当框架调用 s_ctrl 时,ctrl_handler.lock 已经被获取,因此尝试从同一个处理程序中查找另一个控制项将导致死锁。因此,不建议在控制操作中使用此函数。
参考:
https://www.kernel.org/doc/html/v4.10/media/kapi/v4l2-controls.html