title: UVC调节亮度
date: 2019/4/23 20:30:00
toc: true
UVC调节亮度
引入
摄像头的参数比如亮度等是通过VC接口控制的,具体可以参考APP的调用流程,这里暂时不分析了
xawtv.c:
grabber_scan
ng_vid_open
v4l2_driver.open // v4l2_open
get_device_capabilities(h);
// 调用VIDIOC_QUERYCTRL ioctl确定是否支持某个属性
/* controls */
for (i = 0; i < MAX_CTRL; i++) {
h->ctl[i].id = V4L2_CID_BASE+i;
if (-1 == xioctl(h->fd, VIDIOC_QUERYCTRL, &h->ctl[i], EINVAL) ||
(h->ctl[i].flags & V4L2_CTRL_FLAG_DISABLED))
h->ctl[i].id = -1;
}
怎么去获得/设置属性?
看drv0-v4l2.c
可见这2个函数:
v4l2_read_attr : VIDIOC_G_CTRL
v4l2_write_attr : VIDIOC_S_CTRL
直接说结论
ioctl
中的VIDIOC_QUERYCTRL
来查询是支持的属性,VIDIOC_G_CTRL,VIDIOC_S_CTRL
来读取设置具体的属性
硬件协议速览
亮度设置等属性是归属于PU
的,我们找到uvc规范中的Processing Unit Descriptor
这里有一个3字节的bmControls
指示了所有的属性,1表示支持这个属性,0表示不支持.我们之前读取联合接口描述符可以分析出PU
是支持亮度调节的
代码框架
对于硬件的描述,程序在uvc_ctrl.c > uvc_ctrls
也描述了这个
- 将
SU,PU,CT,IT
等都抽象为entity
,这使用16个字节的GUID标识 selector
标识了具体的属性,index
则指出了在实际的数据位,在亮度中为bit0
,这里index=0
size
标识了数据的大小,单位为字节,亮度为两个字节,有些属性可能为11位,那么他也占据两个字节,具体有11位在下面另外一个结构体uvc_ctrl_mappings
指出flags
表示可读可写等
static struct uvc_control_info uvc_ctrls[] = {
{
.entity = UVC_GUID_UVC_PROCESSING,
.selector = UVC_PU_BRIGHTNESS_CONTROL,
.index = 0,
.size = 2,
.flags = UVC_CTRL_FLAG_SET_CUR
| UVC_CTRL_FLAG_GET_RANGE
| UVC_CTRL_FLAG_RESTORE,
},
.....
{
.entity = UVC_GUID_UVC_PROCESSING,
.selector = UVC_PU_ANALOG_LOCK_STATUS_CONTROL,
.index = 17,
.size = 1,
.flags = UVC_CTRL_FLAG_GET_CUR,
},
//===================================================================//
{
.entity = UVC_GUID_UVC_CAMERA,
.selector = UVC_CT_SCANNING_MODE_CONTROL,
.index = 0,
.size = 1,
.flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
| UVC_CTRL_FLAG_RESTORE,
},
这里是另外更详细的一些描述
size
标识了使用了上述字节数中的多少位offset
则是偏移量v4l2_type
提供给APP这是什么数据类型,比如菜单,滑动条等data_type
则是数据的类型
static struct uvc_control_mapping uvc_ctrl_mappings[] = {
{
.id = V4L2_CID_BRIGHTNESS,
.name = "Brightness",
.entity = UVC_GUID_UVC_PROCESSING,
.selector = UVC_PU_BRIGHTNESS_CONTROL,
.size = 16,
.offset = 0,
.v4l2_type = V4L2_CTRL_TYPE_INTEGER,
.data_type = UVC_CTRL_DATA_TYPE_SIGNED,
},
....
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,
V4L2_CTRL_TYPE_BITMASK = 8,
};
/* Data types for UVC control data */
#define UVC_CTRL_DATA_TYPE_RAW 0
#define UVC_CTRL_DATA_TYPE_SIGNED 1
#define UVC_CTRL_DATA_TYPE_UNSIGNED 2
#define UVC_CTRL_DATA_TYPE_BOOLEAN 3
#define UVC_CTRL_DATA_TYPE_ENUM 4
#define UVC_CTRL_DATA_TYPE_BITMASK 5
属性初始化
在uvc_drvier.c > uvc_ctrl_init_device
中会通过USB读取到具体支持哪些属性,并构造一个结构去管理.
最终目的就是实现
读取具体的属性支持,构造
uvc_control
将之前统一设置的
uvc_ctrls
(包含了具体的属性是否可读的信息等),挂载到上述构造的结构中memcpy(&ctrl->info, info, sizeof(*info))
//>ctrl->info = 某个uvc_control_info数组项
详细的流程如下
uvc_ctrl_init_device
// 遍历所有的 pu/eu/ct
list_for_each_entry(entity, &dev->entities, list) {
struct uvc_control *ctrl;
// 取出具体的数据,数据size
bmControls = entity->processing.bmControls;
bControlSize = entity->processing.bControlSize;
// 统计支持的属性个数
for (i = 0; i < bControlSize; ++i)
ncontrols += hweight8(bmControls[i]);
// 一次性分配n个属性控制结构,到 uvc_control
entity->controls = kcalloc(ncontrols, sizeof(*ctrl),GFP_KERNEL);
ctrl = entity->controls;
// 关联这个 uvc_control(这个刚构造的属性描述) 和 uvc_control_info类型的 uvc_ctrls (统一的描述)
uvc_ctrl_init_ctrl
// 查询guid 和 index 一致 则关联
if (uvc_entity_match_guid(ctrl->entity, info->entity) &&ctrl->index == info->index) {
uvc_ctrl_add_info(dev, ctrl, info);
//这里有个关键函数 uvc_control_mapping.get/set 为这个 control 结构具体的数据转换函数
__uvc_ctrl_add_mapping(dev, ctrl, mapping);
if (map->get == NULL)
map->get = uvc_get_le_value;
if (map->set == NULL)
map->set = uvc_set_le_value;
属性支持查询
从ioctl
入手
uvc_v4l2_do_ioctl
case VIDIOC_QUERYCTRL:
uvc_query_v4l2_ctrl(chain, arg)
uvc_find_control(chain, v4l2_ctrl->id, &mapping)
//遍历 entity
list_for_each_entry(entity, &chain->entities, chain)
//寻找到一个详细的描述
// 这里有指示这个 unit 的详细信息,这个mapping就是
// static struct uvc_control_mapping uvc_ctrl_mappings[]
__uvc_find_control(entity, v4l2_id, mapping, &ctrl, next)
也就是说存在这么个流程
- 设备插入后初始化属性管理结构,这个管理结构会挂载
uvc_control_info uvc_ctrls
,指示了可读可写等信息 - 查询属性的时候,可以根据这个找到更详细的
uvc_control_mapping uvc_ctrl_mappings[]
,指示了具体的数据位宽和默认值等
具体属性值获取
VIDIOC_G_CTRL
uvc_ctrl_get(chain, &xctrl);
// 属性查询,了解具体的操作格式
uvc_find_control
// 发起usb传输 获取最大最小值,存到 ctrl->uvc_data 这个是在初始化后分配的内存
uvc_ctrl_populate_cache (UVC_GET_DEF,UVC_GET_MIN,UVC_GET_MAX)
uvc_query_ctrl
usb_control_msg
// 传递给usb
uvc_query_ctrl
__uvc_query_ctrl
usb_control_msg
// ...
uvc_ctrl_rollback(chain);
具体属性值设置
VIDIOC_S_CTRL
uvc_ctrl_set
// 属性查询
uvc_find_control
// 发起usb传输 获取最大最小值,存到 ctrl->uvc_data 这个是在初始化后分配的内存
if (!ctrl->cached) { //这里如果在读属性的时候获取过就不再发起usb传输获取了
ret = uvc_ctrl_populate_cache(chain, ctrl);
//数据转换
min,max,step=get(uvc_ctrl_data..)
// 传输usb
uvc_query_ctrl
__uvc_query_ctrl
usb_control_msg
uvc_ctrl_commit
代码实现
这里代码的实现难点还是在usb_control_msg
的参数确定了,摘录自这里
实例代码如下
/* 查询/获得/设置属性
.vidioc_queryctrl = myuvc_vidioc_queryctrl,
.vidioc_g_ctrl = myuvc_vidioc_g_ctrl,
.vidioc_s_ctrl = myuvc_vidioc_s_ctrl,
*/
// uvc_get_le_value uvc_set_le_value
static void myuvc_set_le_value(__s32 value, __u8 *data);
static __s32 myuvc_get_le_value(const __u8 *data);
/* 参考:uvc_query_v4l2_ctrl */
int myuvc_vidioc_queryctrl (struct file *file, void *fh,
struct v4l2_queryctrl *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
memset(ctrl, 0, sizeof *ctrl);
ctrl->id = V4L2_CID_BRIGHTNESS;
ctrl->type = V4L2_CTRL_TYPE_INTEGER;
strcpy(ctrl->name, "MyUVC_BRIGHTNESS");
ctrl->flags = 0;
pipe = usb_rcvctrlpipe(myuvc_udev, 0);
type |= USB_DIR_IN;
/* 发起USB传输确定这些值 */
ret = usb_control_msg(myuvc_udev, pipe, GET_MIN, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->minimum = myuvc_get_le_value(data); /* Note signedness */
ret = usb_control_msg(myuvc_udev, pipe, GET_MAX, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->maximum = myuvc_get_le_value(data); /* Note signedness */
ret = usb_control_msg(myuvc_udev, pipe, GET_RES, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->step = myuvc_get_le_value(data); /* Note signedness */
ret = usb_control_msg(myuvc_udev, pipe, GET_DEF, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->default_value = myuvc_get_le_value(data); /* Note signedness */
printk("Brightness: min =%d, max = %d, step = %d, default = %d\n", ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);
return 0;
}
/* 参考 : uvc_ctrl_get */
int myuvc_vidioc_g_ctrl (struct file *file, void *fh,
struct v4l2_control *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
pipe = usb_rcvctrlpipe(myuvc_udev, 0);
type |= USB_DIR_IN;
ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->value = myuvc_get_le_value(data); /* Note signedness */
return 0;
}
/* 参考: uvc_ctrl_set/uvc_ctrl_commit */
int myuvc_vidioc_s_ctrl (struct file *file, void *fh,
struct v4l2_control *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
myuvc_set_le_value(ctrl->value, data);
pipe = usb_sndctrlpipe(myuvc_udev, 0);
type |= USB_DIR_OUT;
ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
return 0;
}