Linux添加uvc摄像头上的按键拍照

有的uvc镜头上带有按键按钮来实现拍照功能,本篇将讲述如何在Linux系统中获取uvc镜头的按键消息,实现拍照。

一、配置menuconfig

1、打开menuconfig, 在 -> Device Drivers -> Input device support ->
选中 < * > Generic input layer (needed for keyboard, mouse, …) 输入子系统核心层和 < * > Event interface 事件上报处理层
想支持poll轮询选中<> Polled input device skeleton ,其余的不需要的可以不勾选减少体积,如图 。
Linux添加uvc摄像头上的按键拍照_第1张图片
2、在 -> Device Drivers -> Multimedia support -> Media USB Adapters 除选中 <
> USB Video Class (UVC) 外还需选中[*] UVC input events device support ,以支持uvc输入设备的支持,如图。Linux添加uvc摄像头上的按键拍照_第2张图片

二、创建设备节点

配置好内核选项后编译烧录,如果没有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的按键值,想想可能是这个原因。但是为什么呢?我们一探究竟。

五 、 分析事件上报代码

1、入口
	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,
......
};
2、关键函数

我们在上诉流程的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中上报过去的。

3、对于disposition值 。

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;

4、回看

我在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行修改上述代码即可。

你可能感兴趣的:(驱动杂文,输入子系统input,Linux,uvc镜头,uvc按键拍照)