有的uvc镜头上带有按键按钮来实现拍照功能,本篇将讲述如何在Linux系统中获取uvc镜头的按键消息,实现拍照。
1、打开menuconfig, 在 -> Device Drivers -> Input device support ->
选中 < * > Generic input layer (needed for keyboard, mouse, …) 输入子系统核心层和 < * > Event interface 事件上报处理层
想支持poll轮询选中<> Polled input device skeleton ,其余的不需要的可以不勾选减少体积,如图 。
2、在 -> Device Drivers -> Multimedia support -> Media USB Adapters 除选中 < > USB Video Class (UVC) 外还需选中[*] UVC input events device support ,以支持uvc输入设备的支持,如图。
配置好内核选项后编译烧录,如果没有mdev系统创建节点就得自己手动了。
1、找到当前设备对用的event事件编号。cat /proc/bus/input/devices文件可以看到当前的输入设备信息,如下:
#cat /proc/bus/input/devices
I: Bus=0003 Vendor=eb1a Product=299f Version=0001
N: Name=“supereyes”
P: Phys=usb-ehci-platform-1/button
S: Sysfs=/devices/platform/ehci-platform/usb1/1-1/1-1:1.0/input/input2
U: Uniq=
H: Handlers=event0
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0 0 0 0
这是一个MJPEG格式的uvc镜头,在H字段后面可以看到该设备对应的是event0。
2、找到设备号
查看目录下/sys/class/input/event0/的dev文件找到主设备号和次设备号。
#cat /sys/class/input/event0/dev
13:64
可以发现系统分给该设备的主设备号为13,次设备号为64。
3、创建设备节点
使用命令
mknod /dev/event0 c 13 64
创建节点。
创建好设备消息后就可以在应用层读取消息了,单独创建一个线程读取消息,代码如下。
#include
void *camera_key_thread( void *arg )
{
struct input_event camera_key={0};
int ret;
int event0_fd ;
while(1){
event0_fd = open("/dev/event0",O_RDWR);
if(event0_fd < 0){
dprintf ("open /dev/event0 err: %d \n",event0_fd);
}
else {
dprintf("open /dev/event0 success \n");
while(1){
memset(&camera_key, 0, sizeof(struct input_event));
ret = read( event0_fd ,&camera_key,sizeof(camera_key));
if( ret == sizeof(camera_key)){
if(camera_key.type == EV_KEY && camera_key.code == KEY_CAMERA && camera_key.value == 1 ){
//dprintf("type %d, code %d, value %d \n",camera_key.type ,camera_key.code, camera_key.value );
/* 按键拍照处理*/
}
}else {
dprintf("read err : ret %d \n", ret);
close(event0_fd);
break;
}
}
}
usleep(1000*500);
}
return NULL;
}
为了支持热插拔使用了两层循环, 获得按键消息后就可抓取一帧图像数据保存即可实现拍照了。
1、按键按的第一下read函数有反应,之后的按键read函数卡死,没有发回。(有的镜头可能没有这个bug)
2、查找问题
打开源码drivers\media\usb\uvc\uvc_driver.c 文件,从probe函数开始依次调用顺序依次为
uvc_probe() ( 该函数在插入uvc设备时调,初始化各类信息 ) -> uvc_status_init() (初始化中断urb传输,uvc设备的按键,控制等消息)-> usb_fill_int_urb() (填充一个中断urb传输,该函数uvc_status_complete为urb传输完成时回调)。
在 uvc_status_complete()(中断传输urb完成回调) -> uvc_event_streaming() (处理中断端点传输的消息) ->uvc_input_report_key()(上报按键消息)函数如下:
static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,int value)
{
if (dev->input) {
input_report_key(dev->input, code, value);
input_sync(dev->input);
}
}
该函数为标准的输入按键事件上报,到此流程都没有问题,但是为什么按键第二次没有反应呢?我添加打印发现 uvc_input_report_key() 这个函数每按一次按键就会打印一次,上报一次按键消息。在这个函数的上一个函数uvc_event_streaming()打开打印消息。
uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n",
data[1], data[3] ? "pressed" : "released", len);
每按一次按键我们发现,只有Button (intf 1) pressed len 4的消息,也就是按键value值为1的 pressed消息。没有value为0的release消息,有人说输入子系统的按键消息一定要加按键抬起消息,也就是上报value为0的按键值,想想可能是这个原因。但是为什么呢?我们一探究竟。
input_report_key(dev->input, code, value);
input_sync(dev->input);
函数开始,这两个函数都是调用的input_event()函数 (drivers\input\input.c),注意参数不同。唤醒read函数的流程如下:
(input_report_key() / input_sync()) -> input_event() -> input_handle_event() -> input_pass_values() -> input_to_handler() -> handler->event() -> evdev_events -> evdev_pass_values 唤醒read函数。
handler->event() 函数在 input_register_handler()注册。
static int __init evdev_init(void)// 该函数为输入事件层起始函数。
{
return input_register_handler(&evdev_handler);
}
结构体如下:
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
......
};
我们在上诉流程的input_handle_event()函数中可以找到答案。
可以发现能不能唤醒read函数的步骤就是调不调用input_pass_values()函数,代码如下。
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition;
disposition = input_get_disposition(dev, type, code, value);
// printk("disposition %x \n" , disposition);
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (!dev->vals)
return;
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
// printk("num_vals %d \n" , dev->num_vals);
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)// 2
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
我们先看在哪里调用input_pass_values函数, 总共两个地方。
第一个if条件为disposition & INPUT_FLUSH 条件为真,并且dev->num_vals 的值大于2,注意在判断之后dev->num_vals = 0;给清零了,dev->num_vals这个值是在哪里增加的呢?就在代码上面
v = &dev->vals[dev->num_vals++];增加的。
第二个条件为(dev->num_vals >= dev->max_vals - 2) ,改判断可以发现固定上班按键类型为宏input_value_sync,只是个同步按键值,并不是实际按键值,我猜是为了同一时间按键值泛滥的情况,将按键次数清零。
所以按键值是在第一个if中上报过去的。
disposition 这个变量是重点,dev->num_vals 的自增也需要这个变量disposition & INPUT_PASS_TO_HANDLERS成立。我们看看这个变量是如何赋值的。
disposition = input_get_disposition(dev, type, code, value);
函数源码如下:
static int input_get_disposition(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
break;
case SYN_MT_REPORT:
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX)) {
/* auto-repeat bypasses state updates */
if (value == 2 ) {
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
if (!!test_bit(code, dev->key) != !!value) {
__change_bit(code, dev->key);
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
...........................
return disposition;
}
在上报按键消息的时候我们总共调用了两个函数分别为:
input_report_key -> 内敛为 input_event(dev, EV_KEY, code, !!value);
input_sync -> 内敛为 input_event(dev, EV_SYN, SYN_REPORT, 0);
该内敛函数的变量type,code 传递到了函数 input_get_disposition()中,当type为EV_KEY时,也就是我们填写上报的按键值的函数调用中,我们可以发现:
① 第一个判断 (is_event_supported(code, dev->keybit, KEY_MAX) ,的目的是为了检查我们的参数code在系统的定义范围之内,我们的按键code值为宏 KEY_CAMERA 值为212 ,所以这个判断基本为一。
② 我们先看!!test_bit(code, dev->key) != !!value这个条件。
!!test_bit(code, dev->key) dev->key 的第code位是否为1, !!value 位我们上报的value,两次非运算是为了将值变成二值量。只有不等于值disposition = INPUT_PASS_TO_HANDLERS 才会赋值,后续的 dev->num_vals++ 才会执行到。read函数除了第一次按按键有返回,后面都没有返回也是因为这个条件不成立。为什么!!test_bit(code, dev->key) != !!value这个条件第二次进不来了呢?
我们看看里面干了什么,里面就两句话。
__change_bit(code, dev->key);
disposition = INPUT_PASS_TO_HANDLERS;
第二句作用我们知道,第一句__change_bit的作用是将dev->key的code位取反,这就可以解释为什么第一次可以进来而第二次、第三、四次进不来的原因。因为我们的value固定为1,要想不等于成立,只有改变value的值,这就是为什么我们一定要上报value为0的抬起消息。
③ value == 2 直接可以得到我们想要的 disposition = INPUT_PASS_TO_HANDLERS;
当我们调用input_sync()函数时type为EV_SYN,code为SYN_REPORT,所以disposition 为,
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
我在urb中断回调里上报按键值总共回调了两个上报函数。
① 调用 input_report_key ()函数,如果我们的value一直是0的话disposition除了第一次返回INPUT_PASS_TO_HANDLERS之后都是返回0,在input_handle_event函数按键赋值和dev->num_vals++也不会运行到,就唤不醒read函数,所以应用层就会一直在等待。假设正常流程都带按键抬起0上报,则每次都会返回disposition = INPUT_PASS_TO_HANDLERS,按键赋值和dev->num_vals++都会运行,这时候dev->num_vals的值会变成1;
② 调用 input_sync()函数,该函数调用后值disposition 为
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;这样
disposition & INPUT_PASS_TO_HANDLERS会再次成立,指针dev->vals保留按键值,dev->num_vals会再次加一,出来后dev->num_vals会等于2。又因为disposition & INPUT_FLUSH也成立所以read函数被唤醒,dev->vals指针有两个按键值所以read函数会返回两次,一次为按键值一次为0的同步事件上报。
大家以为我们只要这样,将我们上报的value值固定为2就可以了。
input_report_key(dev->input, code, 2);
跳进去看一下。
input_event(dev, EV_KEY, code, !!value);
哈哈,不管你写什么只要不是0,你的value都会是1,想要用这种方式改代码吧,不过不建议。
用这种方式
static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
int value)
{
if (dev->input) {
input_report_key(dev->input, code, value);
input_sync(dev->input);
input_report_key(dev->input, code, 0);
input_sync(dev->input);
}
}
强制上报抬起事件,可以不用看过程直接修改drivers\media\usb\uvc\uvc_status.c 的大约第70行修改上述代码即可。