最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

android实现触摸屏校准

IT圈 admin 59浏览 0评论

2024年8月24日发(作者:范姜驰婷)

android实现触摸屏校准

1. 触摸校准算法

a.

触摸屏校准通用方法。

(XL, YL是显示屏坐标,XT, YT是触摸屏坐标,)

XL = XT*A+YT*B+C

YL = YT*D+YT*E+F

b.

由于具体计算是希望是整数运算,所以实际中保存的ABCDEF

为整数,而增加一个参数Div

XL = (XT*A+YT*B+C) / Div

YL = (YT*D+YT*E+F) / Div

c. TSLIB

把以上的

7

个参数

ABCDEF Div

保存在

pointercal

文件中。

不校准的数据: A=1, B=0, C=0, D=0, E=1, F=0, Div=1

A

-411

B

37818

C

-3636780

D

-51325

E

39

F

47065584

Div

65536

2.

Android 事件处理机制

a.

android 事件的传入是从EventHub开始的,EventHub是 事件的抽象结构,

维护着系统设备的运行情况(设备文件放在/dev/input里),设备类型包括

Keyboard、TouchScreen、TraceBall。它在系统启动的时候会通过 open_device

方法将系统提供的输入设备都增加到这个抽象结构中,并维护一个所有输入

设备的文件描述符,如果输入设备是键盘的话还会读取 /system/usr/keylayout/

目录下对应键盘设备的映射文件(修

改./development/emulator/keymaps/来改变键值的映射关系),另外

getEvent方法是对EventHub中的设备文件描述符使用poll操作等侍驱动层事

件的发生,如果发生的事件是键盘事件,则调用Map函数按照映射文件转换

成相应的键值并将扫描码和键码返回给KeyInputQueue.

b.

frameworks/base/services/jni/com_android_server_

根据事件的类型以及事件值进行判断处理,从而确定这个事件对应的设备状态是否发生了改变并

相应的改变对这个设备的描述结构InputDevice。

c.

Windowmanager会创建一个线程(InputDispatcherThread),在这个线程里从

事件队列中读取发生的事件 (QueuedEvent ev = nt()),并

根据读取到事件类型的不同分成三类(KEYBOARD、TOUCHSCREEN、

TRACKBALL),分别进 行处理,例如键盘事件会调用

dispatchKey((KeyEvent), 0, 0)以将事件通过Binder发送给具有焦点的

窗口应用程序,然后调用 eEvent(ev)继续等侍键盘事件的发生;

如果是触摸屏事件则调用dispatchPointer(ev, (MotionEvent), 0, 0),这

里会根据事件的种类(UP、DOWN、MOVE、OUT_SIDE等)进行判断并处

理,比如Cancel或将事件发送到具有权限的指定的窗口中 去;

3. 移植方案

a.

b.

c.

d.

e.

Android本身并不带触摸屏校准。Android获取到的数据就是驱动

上报的原始数据。

方案一

:

移植

TSLIB

,通过

TSLIB

产生

pointercal

校准参数文件。

方案二

:

Android

框架层获取

OnTouch

事件产生

pointercal

校准参

数文件

方案一

:

数据的校准在驱动中完成。 即把

pointercal

的参数数据通

过某种方式

(sysfs)

传递给驱动程序进行校准。

方案二

:

驱动上报原始点,原始点在框架层拦截后进行校验处理。

4. TSLIB移植过程

a. 修改源码以适应android的文件结构。

b. 设定 编译选项,生成库即应用。

i. etc/ module_raw input

ii. src/ts_config.c #define TS_CONF "/system/etc/"

iii. src/ts_load_module.c

char *plugin_directory="/system/lib/ts/plugins/";

iv. tests/fbutils.c

char *defaultfbdevice = "/dev/graphics/fb0";

v. COPY 到 /system/etc/

vi. . mkdir /data/etc/pointercal

vii.

通过

ts_calibrate

产生

pointercal

数据文件。

5. 框架内获取参数文件

a. 制作APK 应用,仿效ts_calibrate

采点并计算出各参数

,产生 pointercal

6. 框架内

实现触摸屏校准

a.

b.

中 拦截触摸屏原始数据

进行

pointercal

参数校验后再分发

7. 驱动内实现触摸屏校准

a. 在 中添加event,在触摸屏加载后把 pointercal

参数输送给驱动。

8. 结果-效果

实现细节:

1. 扩展init - proper_serivce 系统支持的属性权限,对自定义的特殊系统属性进行权限开放。

2. 使用自定义系统属性在 中 on property 事件中处理 pointercal的读写权限。

3. 使用自定义系统属性 触摸屏校准程序.apk 和 中的输入事件的同步。

(在触摸屏校准期间 inputDevice 在输入事件中不能采用算法。 校准程序完成有

inputDevice重新启用校准算法)

4. 模拟器中至今无法进入 /Y != null 的代码, 需要了解以下 inputDevice 被调

用的步骤。

触摸屏的时间流程:

驱动层:

/*

* Touchscreen absolute values

*

* These parameters are used to help the input layer discard out of

* range readings and reduce jitter etc.

*

* o min, max:- indicate the min and max values your touch screen

returns

* o fuzz:- use a higher number to reduce jitter

*

* The default values correspond to Mainstone II in QVGA mode

*

* Please read

* Documentation/input/ for more details.

*/

static int abs_x[3] = {350, 3900, 5};

module_param_array(abs_x, int, NULL, 0);

MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz");

static int abs_y[3] = {320, 3750, 40};

module_param_array(abs_y, int, NULL, 0);

MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz");

static int abs_p[3] = {0, 150, 4};

module_param_array(abs_p, int, NULL, 0);

MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max,

fuzz");

/*

* 对设备进行初始化设置

*/

set_bit(EV_ABS, wm->input_dev->evbit);

set_bit(ABS_X, wm->input_dev->absbit);

set_bit(ABS_Y, wm->input_dev->absbit);

set_bit(ABS_PRESSURE, wm->input_dev->absbit);

input_set_abs_params(wm->input_dev, ABS_X, abs_x[0],

abs_x[1],

abs_x[2], 0);

input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0],

abs_y[1],

abs_y[2], 0);

input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0],

abs_p[1],

abs_p[2], 0);

/*

* 事件发生时,提供原始点

*/

input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff);

input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff);

input_report_abs(wm->input_dev, ABS_PRESSURE, data.p &

0xfff);

/*

* 提供给驱动外查询input_dev 的接口

* struct input_absinfo info;

* ioctl(fd, EVIOCGABS(axis), &info)

* src file: evDev.c

*/

if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {

t = _IOC_NR(cmd) & ABS_MAX;

= dev->abs[t];

m = dev->absmin[t];

m = dev->absmax[t];

= dev->absfuzz[t];

= dev->absflat[t];

Android 底层驱动

static const char *device_path = "/dev/input";

openPlatformInput(void)

scan_dir(device_path);

open_device(devname);

fd = open(deviceName, O_RDWR);

/*

* 对外接口,

getEvent,

* inotify 监控device_path目录, 使用poll机制轮询 inotify 和各个

输入设备的可用状态。 解析事件或输入信息,放入各个传出参数中。

*/

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,

int32_t* outScancode, int32_t* outKeycode, uint32_t

*outFlags,

int32_t* outValue, nsecs_t* outWhen)

JNI 部分: com_android_server_. 提供接口

static JNINativeMethod gInputMethods[] = {

/* name, signature, funcPtr */

{ "readEvent", "(Landroid/view/RawInputEvent;)Z",

(void*) android_server_KeyInputQueue_readEvent },

{ "getDeviceClasses", "(I)I",

(void*) android_server_KeyInputQueue_getDeviceClasses },

{ "getDeviceName", "(I)Ljava/lang/String;",

(void*) android_server_KeyInputQueue_getDeviceName },

{ "getAbsoluteInfo",

"(IILcom/android/server/InputDevice$AbsoluteInfo;)Z",

(void*) android_server_KeyInputQueue_getAbsoluteInfo },

{ "getSwitchState", "(I)I",

(void*) android_server_KeyInputQueue_getSwitchState },

{ "getSwitchState", "(II)I",

(void*)

android_server_KeyInputQueue_getSwitchStateDevice },

{ "getScancodeState", "(I)I",

(void*) android_server_KeyInputQueue_getScancodeState },

{ "getScancodeState", "(II)I",

(void*)

android_server_KeyInputQueue_getScancodeStateDevice },

{ "getKeycodeState", "(I)I",

(void*) android_server_KeyInputQueue_getKeycodeState },

{ "getKeycodeState", "(II)I",

(void*)

android_server_KeyInputQueue_getKeycodeStateDevice },

{ "hasKeys", "([I[Z)Z",

(void*) android_server_KeyInputQueue_hasKeys },

};

java service 部分: . 循环查询输入设备信息或目录状态并处理

Thread mThread = new Thread("InputDeviceReader") {

public void run() {

eadPriority(

_PRIORITY_URGENT_DISPL

AY);

try {

RawInputEvent ev = new RawInputEvent();

while (true) {

InputDevice di;

// block, doesn't release the monitor

readEvent(ev);

boolean send = false;

boolean configChanged = false;

......

//检测到新设备后

if ( == _DEVICE_ADDED) {

synchronized (mFirst) {

di = newInputDevice(Id);

(Id, di);

configChanged = true;

}

}

//对触摸屏设备

teInfo absX;

teInfo absY;

teInfo absPressure;

teInfo absSize;

if ((classes&_TOUCHSCREEN) != 0) {

absX = loadAbsoluteInfo(deviceId,

_X, "X");

absY = loadAbsoluteInfo(deviceId,

_Y, "Y");

absPressure = loadAbsoluteInfo(deviceId,

_PRESSURE, "Pressure");

absSize = loadAbsoluteInfo(deviceId,

_TOOL_WIDTH, "Size");

} else {

absX = null;

absY = null;

absPressure = null;

absSize = null;

}

return new InputDevice(deviceId, classes, name, absX, absY,

absPressure, absSize);

我们对触摸屏的数据修订是在 中基于

absX, absY, absPressure != null

的状态下的,当绝对原始点

数据从驱动报上来之后,传递到 经过我们的校准

后再dispatch出去到windowManager -> activity 。 这样就是起到了校准效果。

需要注意的补助说明

EventHub 中有使用IOCTL 对触摸屏的EVIOCGABS(axis)进行了采样,取出内容struct

input_absinfo info;

struct input_absinfo {

__s32 value;

__s32 minimum;

__s32 maximum;

__s32 fuzz;

__s32 flat;

__s32 resolution;

};

#define EVIOCGABS(abs) _IOR('E', 0x40 + abs, struct

input_absinfo) /* get abs value/limits */

取出的是触摸屏的最大值,最小值等,这些值在com_android_server_ 中会把

这些值传递给中使用,即把报上来的位置通过算法进行计算成绝对坐标值

scaledX = ((ue)

/ ) * w;

当然如果出现了X,Y轴相反或者坐标反向等问题都可通过改写这条语句来进行实际操作。

2024年8月24日发(作者:范姜驰婷)

android实现触摸屏校准

1. 触摸校准算法

a.

触摸屏校准通用方法。

(XL, YL是显示屏坐标,XT, YT是触摸屏坐标,)

XL = XT*A+YT*B+C

YL = YT*D+YT*E+F

b.

由于具体计算是希望是整数运算,所以实际中保存的ABCDEF

为整数,而增加一个参数Div

XL = (XT*A+YT*B+C) / Div

YL = (YT*D+YT*E+F) / Div

c. TSLIB

把以上的

7

个参数

ABCDEF Div

保存在

pointercal

文件中。

不校准的数据: A=1, B=0, C=0, D=0, E=1, F=0, Div=1

A

-411

B

37818

C

-3636780

D

-51325

E

39

F

47065584

Div

65536

2.

Android 事件处理机制

a.

android 事件的传入是从EventHub开始的,EventHub是 事件的抽象结构,

维护着系统设备的运行情况(设备文件放在/dev/input里),设备类型包括

Keyboard、TouchScreen、TraceBall。它在系统启动的时候会通过 open_device

方法将系统提供的输入设备都增加到这个抽象结构中,并维护一个所有输入

设备的文件描述符,如果输入设备是键盘的话还会读取 /system/usr/keylayout/

目录下对应键盘设备的映射文件(修

改./development/emulator/keymaps/来改变键值的映射关系),另外

getEvent方法是对EventHub中的设备文件描述符使用poll操作等侍驱动层事

件的发生,如果发生的事件是键盘事件,则调用Map函数按照映射文件转换

成相应的键值并将扫描码和键码返回给KeyInputQueue.

b.

frameworks/base/services/jni/com_android_server_

根据事件的类型以及事件值进行判断处理,从而确定这个事件对应的设备状态是否发生了改变并

相应的改变对这个设备的描述结构InputDevice。

c.

Windowmanager会创建一个线程(InputDispatcherThread),在这个线程里从

事件队列中读取发生的事件 (QueuedEvent ev = nt()),并

根据读取到事件类型的不同分成三类(KEYBOARD、TOUCHSCREEN、

TRACKBALL),分别进 行处理,例如键盘事件会调用

dispatchKey((KeyEvent), 0, 0)以将事件通过Binder发送给具有焦点的

窗口应用程序,然后调用 eEvent(ev)继续等侍键盘事件的发生;

如果是触摸屏事件则调用dispatchPointer(ev, (MotionEvent), 0, 0),这

里会根据事件的种类(UP、DOWN、MOVE、OUT_SIDE等)进行判断并处

理,比如Cancel或将事件发送到具有权限的指定的窗口中 去;

3. 移植方案

a.

b.

c.

d.

e.

Android本身并不带触摸屏校准。Android获取到的数据就是驱动

上报的原始数据。

方案一

:

移植

TSLIB

,通过

TSLIB

产生

pointercal

校准参数文件。

方案二

:

Android

框架层获取

OnTouch

事件产生

pointercal

校准参

数文件

方案一

:

数据的校准在驱动中完成。 即把

pointercal

的参数数据通

过某种方式

(sysfs)

传递给驱动程序进行校准。

方案二

:

驱动上报原始点,原始点在框架层拦截后进行校验处理。

4. TSLIB移植过程

a. 修改源码以适应android的文件结构。

b. 设定 编译选项,生成库即应用。

i. etc/ module_raw input

ii. src/ts_config.c #define TS_CONF "/system/etc/"

iii. src/ts_load_module.c

char *plugin_directory="/system/lib/ts/plugins/";

iv. tests/fbutils.c

char *defaultfbdevice = "/dev/graphics/fb0";

v. COPY 到 /system/etc/

vi. . mkdir /data/etc/pointercal

vii.

通过

ts_calibrate

产生

pointercal

数据文件。

5. 框架内获取参数文件

a. 制作APK 应用,仿效ts_calibrate

采点并计算出各参数

,产生 pointercal

6. 框架内

实现触摸屏校准

a.

b.

中 拦截触摸屏原始数据

进行

pointercal

参数校验后再分发

7. 驱动内实现触摸屏校准

a. 在 中添加event,在触摸屏加载后把 pointercal

参数输送给驱动。

8. 结果-效果

实现细节:

1. 扩展init - proper_serivce 系统支持的属性权限,对自定义的特殊系统属性进行权限开放。

2. 使用自定义系统属性在 中 on property 事件中处理 pointercal的读写权限。

3. 使用自定义系统属性 触摸屏校准程序.apk 和 中的输入事件的同步。

(在触摸屏校准期间 inputDevice 在输入事件中不能采用算法。 校准程序完成有

inputDevice重新启用校准算法)

4. 模拟器中至今无法进入 /Y != null 的代码, 需要了解以下 inputDevice 被调

用的步骤。

触摸屏的时间流程:

驱动层:

/*

* Touchscreen absolute values

*

* These parameters are used to help the input layer discard out of

* range readings and reduce jitter etc.

*

* o min, max:- indicate the min and max values your touch screen

returns

* o fuzz:- use a higher number to reduce jitter

*

* The default values correspond to Mainstone II in QVGA mode

*

* Please read

* Documentation/input/ for more details.

*/

static int abs_x[3] = {350, 3900, 5};

module_param_array(abs_x, int, NULL, 0);

MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz");

static int abs_y[3] = {320, 3750, 40};

module_param_array(abs_y, int, NULL, 0);

MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz");

static int abs_p[3] = {0, 150, 4};

module_param_array(abs_p, int, NULL, 0);

MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max,

fuzz");

/*

* 对设备进行初始化设置

*/

set_bit(EV_ABS, wm->input_dev->evbit);

set_bit(ABS_X, wm->input_dev->absbit);

set_bit(ABS_Y, wm->input_dev->absbit);

set_bit(ABS_PRESSURE, wm->input_dev->absbit);

input_set_abs_params(wm->input_dev, ABS_X, abs_x[0],

abs_x[1],

abs_x[2], 0);

input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0],

abs_y[1],

abs_y[2], 0);

input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0],

abs_p[1],

abs_p[2], 0);

/*

* 事件发生时,提供原始点

*/

input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff);

input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff);

input_report_abs(wm->input_dev, ABS_PRESSURE, data.p &

0xfff);

/*

* 提供给驱动外查询input_dev 的接口

* struct input_absinfo info;

* ioctl(fd, EVIOCGABS(axis), &info)

* src file: evDev.c

*/

if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {

t = _IOC_NR(cmd) & ABS_MAX;

= dev->abs[t];

m = dev->absmin[t];

m = dev->absmax[t];

= dev->absfuzz[t];

= dev->absflat[t];

Android 底层驱动

static const char *device_path = "/dev/input";

openPlatformInput(void)

scan_dir(device_path);

open_device(devname);

fd = open(deviceName, O_RDWR);

/*

* 对外接口,

getEvent,

* inotify 监控device_path目录, 使用poll机制轮询 inotify 和各个

输入设备的可用状态。 解析事件或输入信息,放入各个传出参数中。

*/

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,

int32_t* outScancode, int32_t* outKeycode, uint32_t

*outFlags,

int32_t* outValue, nsecs_t* outWhen)

JNI 部分: com_android_server_. 提供接口

static JNINativeMethod gInputMethods[] = {

/* name, signature, funcPtr */

{ "readEvent", "(Landroid/view/RawInputEvent;)Z",

(void*) android_server_KeyInputQueue_readEvent },

{ "getDeviceClasses", "(I)I",

(void*) android_server_KeyInputQueue_getDeviceClasses },

{ "getDeviceName", "(I)Ljava/lang/String;",

(void*) android_server_KeyInputQueue_getDeviceName },

{ "getAbsoluteInfo",

"(IILcom/android/server/InputDevice$AbsoluteInfo;)Z",

(void*) android_server_KeyInputQueue_getAbsoluteInfo },

{ "getSwitchState", "(I)I",

(void*) android_server_KeyInputQueue_getSwitchState },

{ "getSwitchState", "(II)I",

(void*)

android_server_KeyInputQueue_getSwitchStateDevice },

{ "getScancodeState", "(I)I",

(void*) android_server_KeyInputQueue_getScancodeState },

{ "getScancodeState", "(II)I",

(void*)

android_server_KeyInputQueue_getScancodeStateDevice },

{ "getKeycodeState", "(I)I",

(void*) android_server_KeyInputQueue_getKeycodeState },

{ "getKeycodeState", "(II)I",

(void*)

android_server_KeyInputQueue_getKeycodeStateDevice },

{ "hasKeys", "([I[Z)Z",

(void*) android_server_KeyInputQueue_hasKeys },

};

java service 部分: . 循环查询输入设备信息或目录状态并处理

Thread mThread = new Thread("InputDeviceReader") {

public void run() {

eadPriority(

_PRIORITY_URGENT_DISPL

AY);

try {

RawInputEvent ev = new RawInputEvent();

while (true) {

InputDevice di;

// block, doesn't release the monitor

readEvent(ev);

boolean send = false;

boolean configChanged = false;

......

//检测到新设备后

if ( == _DEVICE_ADDED) {

synchronized (mFirst) {

di = newInputDevice(Id);

(Id, di);

configChanged = true;

}

}

//对触摸屏设备

teInfo absX;

teInfo absY;

teInfo absPressure;

teInfo absSize;

if ((classes&_TOUCHSCREEN) != 0) {

absX = loadAbsoluteInfo(deviceId,

_X, "X");

absY = loadAbsoluteInfo(deviceId,

_Y, "Y");

absPressure = loadAbsoluteInfo(deviceId,

_PRESSURE, "Pressure");

absSize = loadAbsoluteInfo(deviceId,

_TOOL_WIDTH, "Size");

} else {

absX = null;

absY = null;

absPressure = null;

absSize = null;

}

return new InputDevice(deviceId, classes, name, absX, absY,

absPressure, absSize);

我们对触摸屏的数据修订是在 中基于

absX, absY, absPressure != null

的状态下的,当绝对原始点

数据从驱动报上来之后,传递到 经过我们的校准

后再dispatch出去到windowManager -> activity 。 这样就是起到了校准效果。

需要注意的补助说明

EventHub 中有使用IOCTL 对触摸屏的EVIOCGABS(axis)进行了采样,取出内容struct

input_absinfo info;

struct input_absinfo {

__s32 value;

__s32 minimum;

__s32 maximum;

__s32 fuzz;

__s32 flat;

__s32 resolution;

};

#define EVIOCGABS(abs) _IOR('E', 0x40 + abs, struct

input_absinfo) /* get abs value/limits */

取出的是触摸屏的最大值,最小值等,这些值在com_android_server_ 中会把

这些值传递给中使用,即把报上来的位置通过算法进行计算成绝对坐标值

scaledX = ((ue)

/ ) * w;

当然如果出现了X,Y轴相反或者坐标反向等问题都可通过改写这条语句来进行实际操作。

发布评论

评论列表 (0)

  1. 暂无评论