如何将Android传感器数据转换到地球直角坐标系坐标系

梳理一下传感器的数据流和框架是怎么样让屏幕旋转的
梳理一下传感器的数据流和框架是怎么样让屏幕旋转的
Android开发
本文章转载:http://blog.csdn.net/a/article/details/6592527
这篇文章写的传感器数据从驱动传递到应用程序的整个流程,还有数据校正的问题。
应用程序怎么样设置可以让自己随着设备的倾斜度变化而旋转方向呢?在AndroidManifest.xml文件中的android:screenOrientation就可以了。这里追踪一下它的内部机制。
先看一个最关键的部件:/frameworks/base/core/java/android/view/WindowOrientationListener.java
这个接口注册一个accelerator,并负责把accelerator的数据转化为orientation。这个API对应用程序不公开,我看Android2.3的源码时发现只有PhoneWindowManager使用到它了。
/frameworks/base/policy/../PhoneWindowManager.java
PhonwWindowManager注册了一个WindowOrientationListener,就可以异步获取当前设备的orientation了。再结合应用程序在AndroidManifest.xml中设置的值来管理着应用程序界面的旋转方向。以下是PhoneWindowManager.java中相关的两个代码片段。
让应用程序随屏幕方向自动旋转的实现原理就这么交待完了。我解决这一步时也没有费多少力气,在板子上打开SensorTest,对比一下XYZ三个轴和MileStone上面的数据,修改一下正负值就可以了。但要解决Teeter运行时Z轴反转的问题,还得深层次挖一挖。
PhoneWindowManager.java中有这么一句:
mWindowManager.setRotation(rotation, false, mFancyRotationAnimation);
当PhonewindowManager通过WindowOrientationListener这个监听器得知屏幕方向改变时,会通知给WindowManagerService(/frameworks/base/service/../WindowManagerService.java)
WindowManagerService中有这么一个监听器集合:mRotationWatchers,谁想监听屏幕方向改变,就会在这里注册一个监听器。SensorManager就这么干了。然后,通过下面这个异步方法获知当前的屏幕方向
SensorManager要这个值有什么作用呢?看看在哪里使用了SensorManager.getRotation()吧。只有一个方法:mapSensorDataToWindow
当一个Activity注册了一个Sensor事件监听器后,总是会通过接口来异步获取sensor事件的。在这里,新老版本出现了分化。老版本中,Android1.5以前,Sensor事件被分发给监听者(onSensorChanged)之前,总会先用这个方法处理一下。新版本的监听接口是SensorEventListener,分发前是没有处理方法的。看一下这个方法,原来是转换坐标系用的。应用程序的界面方向随屏幕发生变化以后,通过异步分发接口传递给它的传感器数据也要从传感器的坐标系转换到应用程序的坐标系。假设屏幕默认方向是竖屏,这个时候分发给它的SensorEvent里面的值与frameworks层从HAL的sensor.c中读到的数据是一样的。当设备右侧抬起,屏幕切换到横屏是,应用程序的界面也旋转了90度,这个时候,SensorEvent在分发给应用程序之前就需要先把自己的坐标系顺时针旋转90度。
新版本接口中,传感器数据直接通过SensorEventListener分发给应用程序。而老版本接口中,分发之前先要结合当前设备的旋转方向对传感器数据做一个坐标系转换。到这里,一切都清楚了。WindowOrientationListener借助SensorManager的accelerator数据制造了屏幕旋转方向,而屏幕旋转方向又被SensorManager用来兼容老版本的SensorListener接口。可以说,如果不考虑兼容老版本的接口的话,SensorManager是完全不用向WindowManagerService.java中注册监听器监听当前设备屏幕旋转方向的,直接分发下去就好了。SensorManager的代码恐怕要减少一半多。framework层是时候把SensorListener相关的一系列API扔到一边了。
还有一个地方,就是HAL层的sensor.c到SensorManager之间的部分。这个部分把sensor.c中读到的传感器数据整合成一个服务(SensorService)供SensorManager使用。很好地隔离了API层和HAL层。但从数据处理的角度来讲,只是扮演了一个数据传递者的角色,没有对数据进行任何的改变。
上面的写完了,接下来是HAL层了。各个厂商的写法都不一样,有的为了把所有传感器集成进来,还形成了自己的一个框架。让我们穿过HAL框架,直接进入sensor.c。这里有我最关心的最终如何与sensor的driver交互,向上层传递了哪些信息。再复杂的frameworks,也不过是把sensor.c提供的接口封装一下而己。sensor的数据从来没有被改变过。这里只是简述一下sensor.c的大致功能,更详细的分析可以参考一下这一篇文章(http://blog.csdn.net/a/article/details/6558401),那里我写了一个可以通过ADB或者串口运行的C++程序专门演示控制driver和读取数据的细节。
1、给上层提供一个获取sensor list的接口。这个是写死在sensor.c里面的。往一个设备上面移植frameworks时,这一部分是要根据设备上的sensor来修改这个文件的。
2、给上层提供控制接口:active/deactive某一个sensor。set某个sensor的delay值(即,获取sensor数据的频率,比如设置为200,000的话,就是驱动每隔200毫秒向上面发一次数据)。读取某个sensor的数据。
现在我们知道了,调试传感器时,只在两个地方下手就可以了:
1、/frameworks/base/core/java/android/view/WindowOrientation.java,校正accelerator数据与屏幕旋转方向的对应关系。
2、/hardware/libhardware/modules/sensor/sensor.c,对驱动递上来的数据进行初步校正。这一步可以参考一下已经的校正好的机器(我用的是自己的MileStone),然后再运行一下SensorTest(网上有的下),只要同一个摆放姿势下,我们读上来的数据和它的一样就可以了。因为前面说过,在新版本(1.5及以上)接口中,数据流经过sensor.c-&SensorService-&SensorManager,最后通过onSensorChanged分发给应用程序的整个过程中,是从来没有被改变过的。至于老版本中SensorManager部分做的校正,让他吃屎去吧。(我一开始在这里做了很多的工作,最后发现从1.5就已经deprecate这个接口了,请允许我再一次的Shit!!!)。
好了,这篇文章总算是写完了。别闲我啰嗦,我再说最后一次:sensor数据自从被sensor.c从driver读上来,一直到传递给应用程序的onSensorChanged接口,整个过程中,数据都没有被改变过。这很重要,因为这意味着frameworks层是不需要sensor校正的。我们只需要在WindowOrientationListener里面找到那两个数组(THRESHOLDS和ROTATE_TO),然后调调屏幕旋转方向就可以了。
兼容传感器老接口时出现的问题。
屏幕旋转后,传感器数据也要变的坐标系,这和以前的理解不一样,得纠正一下。
调试好sensor后,屏幕可以正常旋转了,但HTC手机自带的Teeter运行起来有问题。跟踪了一下,发现Teeter还在使用旧的传感器监听接口onSensorChanged(int sensor, float[] value)。因此,需要修改/frameworks/base/core/java/android/hardware/SensorManager.java中的mapSensorDataToWindow方法,这个方法负责把HAL读到的原始数据转化成旧接口的数据(利用onSensorChanged(int
sensor, float[] value)接收到的数据)。
目前只做了0度和90度两个方向,所以也修改了一下WindowOrientationListener,让所有API只能在这两个方向上旋转:
1、mAllow180Rotation变量永远设置为false。
2、修改ROTATE_TO数组,把270度全部修改为90度。
另外,sensor.c中poll data的接口会有一个int型返回值,表示读到的sensors_event_t的个数,这个值一定要等于实际个数。我自己的程序里面,实际读到了一个(accelerator),但返回时不管读到几个,都返回当前传感器的个数。这样的话,在应用程序中用老接口onSensorChanged(int sensor, float[] value)来监听时,除了一个正常数据之外,还会读到(sensor.c中的poll data函数返回值-1)个全部为0的冗余数据。
通过AndroidManifest.xml设置屏幕方向的话,安装后就不能改变,而程序内部设置屏幕方向就不会有这个限制。主要靠这两个API:getRequestedOrientation()和setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
这两个API通过ActivityManagerService.java的转换后,实际上都是调用的WindowManagerService的同名方法。每个Activity在WindowManagerService端都有一个AppWindowToken做代表,而屏幕的方向信息就存储在这里。
PhoneWindowManager会自动根据屏幕物理特性决定屏幕方向,看这段代码:
这里的d.getWidth() 和 d.getHeight()得到的是物理屏幕的宽高。一般来说,平板和手机的是不一样的。平板是宽比高大(0度时位于landscape模式,右转90度进入porit模式),手机是高比宽大(0度是位于porit模式,右转90度进入landscape模式)。如果应用程序只关心当前是横屏还是竖屏,而不直接使用传感器的话,没什么问题。如果像依靠重力感应的游戏那样直接使用传感器,就需要自己根据物理屏幕的坐标系对传感器数据做转化,否则就会出现坐标系混乱的问题。
我这里碰到的是Range Thunder和Teeter两个小游戏。它们都没有通过上面的d.getWidth()和d.getHeight()来检测设备的物理屏幕从确定哪个是landscape和porit模式,而是直接假设设备是和手机一样的模式。由于游戏运行在landscape模式下,它们都把传感器数据右转90度。这样做法在手机上是没有问题,但在平板电脑上是不应该转化的,这是因为物理屏幕宽比高大的情况下,默认就是landscape模式。
看到下面一楼读者提到的问题后,补充一下我针对他说的那个问题的解决方案。
拿新接口来说,我们可以在onSensorChangedLocked接口中,SensorEvent传递出去之前,对坐标系调整一下。但是,按照这种方法把使用新接口游戏调整正确后,发现使用老接口的游戏又乱了,屏幕的旋转方向也乱了。
我们已经知道,WindowOrientationListener使用SensorManager来确定屏幕的旋转方向,SensorManager再根据旋转方向对底层读上来的传感器做坐标系转换,然后传递给onSensorChanged(SensorEvent event)。而老接口onSensorChanged(int sensor,float[] value)是用onSensorChanged(SensorEvent event)的数据再做坐标系转换。
所以,你还需要在老接口中根据新接口中的坐标系转换也做相应地转换。这样,使用老接口的游戏也可以了。但屏幕旋转不对怎么办?这个问题陷入了一个怪圈。好吧,就到这,下面记录一下我的解决方案:
我们先为SensorEvent增加一个属性来记录传感器的原始数据:
有三个地方用到它,在onSensorChangedLocked中为新接口做坐标系转换,LegacyListener.onSensorChanged接口中为老接口做坐系转换,在WiindowOrientationListener中根据传感器数据计算屏幕旋转方向。
在这三个地方进行计算时都使用originalValue里面存放的原始数据进行计算,计算结果放到SensorEvent.value中。这样一来,哪个接口不对调整哪个接口,因为都是使用的原始数据,所以互不影响,再不会出现按下葫芦起来瓢的事情了。
不过呢,要是写游戏的人比较认真,不是只简单地考虑Landscape/Porit模式,而是使用Display.getRotation()来获取屏幕的旋转实际角度来做gsensor数据坐标系的转换,那他的程序在我们的板子上就悲剧了:
获取当前屏幕旋转角度:
很不幸,Gallery3D就是这样来做图片翻转特效的。下面代码段位于/packages/apps/Gallery3D/src/com/cooliris/media/GridInputProcessor.java文件中,根据屏幕旋转角度计算出图片的倾斜度。只好让它使用原始数据了,下面分别是修改前和修改后的代码。
修改后的代码:
我的热门文章
即使是一小步也想与你分享对加速度传感器的数据进行方向和坐标的转换
对加速度传感器的数据进行方向和坐标的转换
Android是面向智能手机和其他便携式设备的最受欢迎的操作系统(OS)之一。&它为多种传感器提供了标准的API接口,包括加速度计。加速度计的标准API定义了原始加速度数据的坐标系统。用户必须将从传感器中读取的原始数据转换为标准单位,并使其符合系统定义的坐标方向。本文介绍了Android中的坐标系统是如何定义的,以及如何在Android系统的驱动代码中对3轴加速度计数据的方向和坐标进行转换。本文讨论的示例代码基于飞思卡尔的Android&2.2和2.3驱动程序,加速度计则以飞思卡尔的MMA8452Q加速度传感器为例。 一部智能手机或便携设备应具有Wi-Fi和互联网功能,能够运行应用软件等诸多特征,而且一定会具有内置传感器。高端智能手机可能集成接近传感器,环境光传感器,3轴加速度计,以及磁力计等多种传感器。Android&2.3添加了一些支持多种新型传感器的API,包括陀螺仪、旋转向量、线性加速度、重力和气压传感器等。应用软件可以使用这些新型传感器,将它们组合起来,就可以实现高精确度的高级运动检测功能。 3轴加速度计或低g值传感器是Android&API支持的传感器之一,具有特定的坐标系统,可以给应用程序提供标准的接口数据。坐标空间的定义与手机屏幕的默认方向有关,如图1所示。&在Android坐标系统中,坐标原点位于屏幕的左下角,X轴水平指向右侧,Y轴垂直指向顶部,Z轴指向屏幕前方。&在该系统中,屏幕后方的坐标具有负的Z轴值。 Android加速度计数据定义为: Sensor.TYPE_ACCELEROMETER 所有数值都采用SI标准单位(m/s2),测量手机的加速度值,并减去重力加速度分量。& values[0]:x轴上的加速度值减去Gx values[1]:y轴上的加速度值减去Gy values[2]:z轴上的加速度值减去Gz 例如,当设备平放在桌上并推着其左侧向右移动时,x轴加速度值为正。当设备平放在桌上时,加速度值为+9.81,这是用设备的加速度值&(0&m/s2)&减去重力加速度值&(-9.81&m/s2)得到的。& 当设备平放在桌上放,并以加速度A&m/s2朝天空的方向推动时,加速度值等于A+9.81,这是用设备加速度值(+A&m/s2)减去重力加速度值(-9.81&m/s2)得到的。 表1列出了与设备的各个位置相对应的传感器的加速度值读数。用户可以用下表检查加速度计的方向与系统坐标是否一致。 表1. 不同位置上各轴的加速度值位置xyz朝上: 09.81m/s20朝左: 9.81m/s200朝下: 0-9.81m/s20朝右: -9.81m/s200正面朝上: 009.81m/s2背面朝上: 00-9.81m/s2通过加速度传感器读取3轴加速度值时,需要假设传感器的3轴方向与系统坐标是一致的。但是在实际的产品中,可能会使用不同的传感器芯片,或者采用不同的安装方向,因此数据方向也会不同。图2所示的是飞思卡尔MMA8452Q&3轴加速度传感器的方向定义。在图2中,我们可以看到当安装芯片时,必须让引脚1处于右下角的位置(PD),并安装在PCB的前方,这样才能与Android坐标系统的默认位置相符。这样安装后,用户可确定数据方向与系统坐标定义是一致的。在任何其他情形下,数据都无法与系统定义保持完全一致,所以需要更改数据方向和坐标。在某些情况下,X和Y轴必须交换,或者既要改变方向,也要交换X-Y轴。 判断是否需要改变方向或交换X-Y轴的方法如下所述: 1.&将设备放置在朝上(UP)的位置,如表1中所示。 2.&从传感器中读取3轴的数据。如果Y轴上的数据为&±1&g&(±9.81m/s2),其他两个轴上的数据大约为0,则不需要交换X-Y轴。否则,需要交换X和Y轴,请转至步骤3。 2.1.&在该位置上,如果Y轴上读取的数据为+1&g&(+9.81m/s2),则Y轴的方向不需要改变,如果数据为负,则Y轴的方向需要改变。 2.2.&将设备放置在朝左(LEFT)的位置,如表1中所示。X轴上读取的数据应为±1&g&(±9.81m/s2),其他两个轴上的数据应大约为0。如果X轴上的数据为正,则其方向不需要改变;否则X轴的方向需要改变。然后,执行第4步判断Z轴的方向。 3.&设备仍然放置在朝上(UP)的位置,并从传感器中读取3个轴的数据。此时X轴上的数据应为&±1&g&(±9.81m/s2),其他两个轴上的数据大约为0,需要X-Y交换。 3.1.&在该位置上,如果X轴的数据读取为+1&g&(+9.81m/s2),则X轴的方向不需要改变;否则需要改变。 3.2.&将设备放置在向左(LEFT)位置上,如表1中所示。Y轴上读取的数据应为±1&g&(±9.81m/s2),其他两个轴上的数据应大约为0。如果Y轴上的数据为正,则其方向不需要改变;否则需要改变。然后执行第4步判断Z轴的方向。 4.&将设备放置在正面朝上(FRONT-UP)的位置,并从传感器中读取3轴数据。&如果Z轴上的数据为+1&g&(+9.81m/s2),其他两个轴上的数据大约为0,则Z轴方向无需改变;如果Z轴数据为-1&g&(-9.81m/s2),则Z轴方向需要改变。 在Android系统中,传感器数据由内核空间中的Linux驱动读取,然后由HAL层驱动发送至API。分层结构如图3所示。因此,传感器数据可以在Linux驱动层或在HAL层上进行转换。 在Android&HAL文件中改变&X、Y和Z轴的方向 在HAL文件中,会有一组宏定义,用于把从传感器中读取的加速度数据转换为标准单位(m/s2)。如以下代码:& //&conversion&of&acceleration&data&to&SI&units&(m/s^2) #define&CONVERT_A&&&&&&&&&&&&&&&&&&&&&&&(GRAVITY_EARTH&/&LSG) #define&CONVERT_A_X&&&&&&&&&&&&&&&&&&&&&(-CONVERT_A) #define&CONVERT_A_Y&&&&&&&&&&&&&&&&&&&&&(CONVERT_A) #define&CONVERT_A_Z&&&&&&&&&&&&&&&&&&&&&(CONVERT_A) 在这个宏定义中,常量GRAVITY_EARTH&是一个标准重力加速度值,即9.81m/s2,LSG为一个重力加速度值的最小有效计数值,例如,MMA8452在正常模式下的读数为1024。因此,CONVERT_A用于把从加速度传感器中读取的数据,从数字读数转换为标准重力加速度单位。 通过分别修改CONVERT_A_X、CONVERT_A_Y和CONVERT_A_Z,我们可以轻松地改变X、Y和Z轴的方向。如果该轴的方向与系统定义相反,可以使用(-CONVERT_A)来改变其方向。如果方向一致,就使用(CONVERT_A),则保持方向不变。 这个宏定义位于FSL&Android&9&(Android&2.2)驱动程序的HAL文件sensor.c&中。&对于FSL&Android&10&(Android&2.3),您可以在’libsensors’文件夹的HAL文件Sensor.h中找到它。 在Android&2.2&HAL文件中交换X轴和Y轴 在某些情况下,X和Y轴必须进行交换,以便使传感器数据的坐标与系统坐标保持一致。 对于FSL&Android&9&(Android&2.2)驱动程序来说,X轴和Y轴的交换非常简单。首先,在HAL文件sensor.c中,在函数sensor_poll()&中找到以下代码:& &&&&&&&&&&&&&&&&switch&(event.code)&{ &&&&&&&&&&&&&&&&&&&&case&ABS_X: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.x&=&event.value&*&CONVERT_A_X; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&case&ABS_Y: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.y&=&event.value&*&CONVERT_A_Y; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&case&ABS_Z: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.z&=&event.value&*&CONVERT_A_Z; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&} 然后,根据如下所示修改代码: &&&&&&&&&&&&&&&&switch&(event.code)&{ &&&&&&&&&&&&&&&&&&&&case&ABS_X: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.y&=&event.value&*&CONVERT_A_Y; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&case&ABS_Y: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.x&=&event.value&*&CONVERT_A_X; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&case&ABS_Z: &&&&&&&&&&&&&&&&&&&&&&&&sSensors.acceleration.z&=&event.value&*&CONVERT_A_Z; &&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&} 在Android&2.3的HAL文件中交换X轴和Y轴 在Android&2.3的HAL文件中交换X轴和Y轴会更加复杂些,因为它具有更复杂的HAL文件结构。所有HAL文件都位于文件夹‘libsensors’中。文件AccelSensor.cpp中的两个函数需要修改。 &首先,修改函数AccelSensor()的代码,如下所示:& if&(accel_is_sensor_enabled(SENSOR_TYPE_ACCELEROMETER))&&{ &&&&mEnabled&|=&1&&A &&&&if&(!ioctl(data_fd,&EVIOCGABS(EVENT_TYPE_ACCEL_X),&&absinfo))&{ &&&&&&&mPendingEvents[Accelerometer].acceleration.y&=&absinfo.value&*&CONVERT_A_Y; &&&&} &&&&if&(!ioctl(data_fd,&EVIOCGABS(EVENT_TYPE_ACCEL_Y),&&absinfo))&{ &&&&&&&mPendingEvents[Accelerometer].acceleration.x&=&absinfo.value&*&CONVERT_A_X; &&&&} &&&&if&(!ioctl(data_fd,&EVIOCGABS(EVENT_TYPE_ACCEL_Z),&&absinfo))&{ &&&&&&&mPendingEvents[Accelerometer].acceleration.z&=&absinfo.value&*&CONVERT_A_Z; &&&&} &&&} 然后,修改函数processEvent()的代码,如下所示: void&AccelSensor::processEvent(int&code,&int&value) { &&switch&(code)&{ &&&&case&EVENT_TYPE_ACCEL_X: &&&&&&mPendingMask&|=&1&&A &&&&&&mPendingEvents[Accelerometer].acceleration.y&=&value&*&CONVERT_A_Y; &&&&&& &&&&case&EVENT_TYPE_ACCEL_Y: &&&&&&mPendingMask&|=&1&&A &&&&&&mPendingEvents[Accelerometer].acceleration.x&=&value&*&CONVERT_A_X; &&&&&& &&&&case&EVENT_TYPE_ACCEL_Z: &&&&&&mPendingMask&|=&1&&A &&&&&&mPendingEvents[Accelerometer].acceleration.z&=&value&*&CONVERT_A_Z; &&&&&& } } 完成后,X轴和Y轴的数据就互相交换了。 在Kernel驱动文件中交换X轴和Y轴 X轴和Y轴的数据交换可以在底层的Linux驱动中,在刚开始读取传感器数据时实施。&通过这种方法,无论传感器芯片以何种方式安装在PCB中,或者使用各种不同类型的传感器,HAL文件都可以保持一致。 对于Android&2.2和2.3来说,执行该操作的最便捷的方式是修改函数report_abs()中的代码。在该函数中,传感器数据通过调用函数&mma8452_read_data()读取,如下所示(当使用的传感器为MMA8452Q时): &&&&if&(mma8452_read_data(&x,&y,&z)&!=&0)&{ &&&&&&&&//DBG("mma8452&data&read&failed\n"); &&&&&&&&&&&&} X轴和Y轴可以通过以下方式轻松交换: &&&&if&(mma8452_read_data(&y,&x,&z)&!=&0)&{ &&&&&&&&//DBG("mma8452&data&read&failed\n"); &&&&&&&&&&&&} 对于Android&2.2,MMA8452的Kernel驱动文件为mma8452.c;对于Android&2.3,驱动文件是‘hwmon’文件夹中的mxc_mma8452.c。 在Kernel驱动文件中改变&X、Y和Z轴的方向 传感器数据的方向也可以在Kernel驱动文件中更改。以下带有注释的语句可以添加到函数report_abs()中,从而改变数据方向: &&&&if&(mma8452_read_data(&y,&x,&z)&!=&0)&{ &&&&&&&&//DBG("mma8452&data&read&failed\n"); &&&&&&&& &&&&} &&&&x&*=&-1;&&&&&&&&//Reverse&X&direction &&&&y&*=&-1;&&&&&&&&//Reverse&Y&direction &&&&z&*=&-1;&&&&&&&&//Reverse&Z&direction &&&&input_report_abs(mma8452_idev-&input,&ABS_X,&x); &&&&input_report_abs(mma8452_idev-&input,&ABS_Y,&y); &&&&input_report_abs(mma8452_idev-&input,&ABS_Z,&z); &&&&input_sync(mma8452_idev-&input); 总结 Android系统已经为加速度计定义了坐标系统,因此用户必须转换从实际传感器中读取的数据,从而与其保持一致。无论是否需要转换,都应检查X、Y和Z轴的方向以及X-Y轴坐标。我们可以更改HAL文件或Kernel驱动文件来改变轴的方向,或交换X和Y轴,但是不要同时修改HAL文件和Kernel驱动。
发表评论:
TA的最新馆藏[转]&11633人阅读
Android Framework(73)
这篇文章写的传感器数据从驱动传递到应用程序的整个流程,还有数据校正的问题。应用程序怎么样设置可以让自己随着设备的倾斜度变化而旋转方向呢?在AndroidManifest.xml文件中的android:screenOrientation就可以了。这里追踪一下它的内部机制。先看一个最关键的部件:/frameworks/base/core/java/android/view/WindowOrientationListener.java这个接口注册一个accelerator,并负责把accelerator的数据转化为orientation。这个API对应用程序不公开,我看Android2.3的源码时发现只有PhoneWindowManager使用到它了。/frameworks/base/policy/../PhoneWindowManager.javaPhonwWindowManager注册了一个WindowOrientationListener,就可以异步获取当前设备的orientation了。再结合应用程序在AndroidManifest.xml中设置的值来管理着应用程序界面的旋转方向。以下是PhoneWindowManager.java中相关的两个代码片段。public void onOrientationChanged(int rotation) {
// Send updates based on orientation value
if (localLOGV) Log.v(TAG, &onOrientationChanged, rotation changed to & +rotation);
mWindowManager.setRotation(rotation, false,
mFancyRotationAnimation);
} catch (RemoteException e) {
switch (orientation) {//这个值就是当前设备屏幕的旋转方向,再结合应用程序设置的android:configChanges属性值就可以确定应用程序界面的旋转方向了。应用程序设置值的优先级大于传感器确定的优先级。
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
//always return portrait if orientation set to portrait
return mPortraitR
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
//always return landscape if orientation set to landscape
return mLandscapeR
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
//always return portrait if orientation set to portrait
return mUpsideDownR
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
//always return seascape if orientation set to reverse landscape
return mSeascapeR
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
//return either landscape rotation based on the sensor
mOrientationListener.setAllow180Rotation(
isLandscapeOrSeascape(Surface.ROTATION_180));
return getCurrentLandscapeRotation(lastRotation);
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
mOrientationListener.setAllow180Rotation(
!isLandscapeOrSeascape(Surface.ROTATION_180));
return getCurrentPortraitRotation(lastRotation);
}让应用程序随屏幕方向自动旋转的实现原理就这么交待完了。我解决这一步时也没有费多少力气,在板子上打开SensorTest,对比一下XYZ三个轴和MileStone上面的数据,修改一下正负值就可以了。但要解决Teeter运行时Z轴反转的问题,还得深层次挖一挖。PhoneWindowManager.java中有这么一句:mWindowManager.setRotation(rotation, false, mFancyRotationAnimation);当PhonewindowManager通过WindowOrientationListener这个监听器得知屏幕方向改变时,会通知给WindowManagerService(/frameworks/base/service/../WindowManagerService.java)WindowManagerService中有这么一个监听器集合:mRotationWatchers,谁想监听屏幕方向改变,就会在这里注册一个监听器。SensorManager就这么干了。然后,通过下面这个异步方法获知当前的屏幕方向public void onRotationChanged(int rotation) {
synchronized(sListeners) {
static int getRotation() {
synchronized(sListeners) {
}SensorManager要这个值有什么作用呢?看看在哪里使用了SensorManager.getRotation()吧。只有一个方法:mapSensorDataToWindow当一个Activity注册了一个Sensor事件监听器后,总是会通过接口来异步获取sensor事件的。在这里,新老版本出现了分化。老版本中,Android1.5以前,Sensor事件被分发给监听者(onSensorChanged)之前,总会先用这个方法处理一下。新版本的监听接口是SensorEventListener,分发前是没有处理方法的。看一下这个方法,原来是转换坐标系用的。应用程序的界面方向随屏幕发生变化以后,通过异步分发接口传递给它的传感器数据也要从传感器的坐标系转换到应用程序的坐标系。假设屏幕默认方向是竖屏,这个时候分发给它的SensorEvent里面的值与frameworks层从HAL的sensor.c中读到的数据是一样的。当设备右侧抬起,屏幕切换到横屏是,应用程序的界面也旋转了90度,这个时候,SensorEvent在分发给应用程序之前就需要先把自己的坐标系顺时针旋转90度。新版本接口中,传感器数据直接通过SensorEventListener分发给应用程序。而老版本接口中,分发之前先要结合当前设备的旋转方向对传感器数据做一个坐标系转换。到这里,一切都清楚了。WindowOrientationListener借助SensorManager的accelerator数据制造了屏幕旋转方向,而屏幕旋转方向又被SensorManager用来兼容老版本的SensorListener接口。可以说,如果不考虑兼容老版本的接口的话,SensorManager是完全不用向WindowManagerService.java中注册监听器监听当前设备屏幕旋转方向的,直接分发下去就好了。SensorManager的代码恐怕要减少一半多。framework层是时候把SensorListener相关的一系列API扔到一边了。还有一个地方,就是HAL层的sensor.c到SensorManager之间的部分。这个部分把sensor.c中读到的传感器数据整合成一个服务(SensorService)供SensorManager使用。很好地隔离了API层和HAL层。但从数据处理的角度来讲,只是扮演了一个数据传递者的角色,没有对数据进行任何的改变。上面的写完了,接下来是HAL层了。各个厂商的写法都不一样,有的为了把所有传感器集成进来,还形成了自己的一个框架。让我们穿过HAL框架,直接进入sensor.c。这里有我最关心的最终如何与sensor的driver交互,向上层传递了哪些信息。再复杂的frameworks,也不过是把sensor.c提供的接口封装一下而己。sensor的数据从来没有被改变过。这里只是简述一下sensor.c的大致功能,更详细的分析可以参考一下这一篇文章(http://blog.csdn.net/a/article/details/6558401),那里我写了一个可以通过ADB或者串口运行的C++程序专门演示控制driver和读取数据的细节。1、给上层提供一个获取sensor list的接口。这个是写死在sensor.c里面的。往一个设备上面移植frameworks时,这一部分是要根据设备上的sensor来修改这个文件的。2、给上层提供控制接口:active/deactive某一个sensor。set某个sensor的delay值(即,获取sensor数据的频率,比如设置为200,000的话,就是驱动每隔200毫秒向上面发一次数据)。读取某个sensor的数据。现在我们知道了,调试传感器时,只在两个地方下手就可以了:1、/frameworks/base/core/java/android/view/WindowOrientation.java,校正accelerator数据与屏幕旋转方向的对应关系。2、/hardware/libhardware/modules/sensor/sensor.c,对驱动递上来的数据进行初步校正。这一步可以参考一下已经的校正好的机器(我用的是自己的MileStone),然后再运行一下SensorTest(网上有的下),只要同一个摆放姿势下,我们读上来的数据和它的一样就可以了。因为前面说过,在新版本(1.5及以上)接口中,数据流经过sensor.c-&SensorService-&SensorManager,最后通过onSensorChanged分发给应用程序的整个过程中,是从来没有被改变过的。至于老版本中SensorManager部分做的校正,让他吃屎去吧。(我一开始在这里做了很多的工作,最后发现从1.5就已经deprecate这个接口了,请允许我再一次的Shit!!!)。好了,这篇文章总算是写完了。别闲我啰嗦,我再说最后一次:sensor数据自从被sensor.c从driver读上来,一直到传递给应用程序的onSensorChanged接口,整个过程中,数据都没有被改变过。这很重要,因为这意味着frameworks层是不需要sensor校正的。我们只需要在WindowOrientationListener里面找到那两个数组(THRESHOLDS和ROTATE_TO),然后调调屏幕旋转方向就可以了。补充于兼容传感器老接口时出现的问题。屏幕旋转后,传感器数据也要变的坐标系,这和以前的理解不一样,得纠正一下。调试好sensor后,屏幕可以正常旋转了,但HTC手机自带的Teeter运行起来有问题。跟踪了一下,发现Teeter还在使用旧的传感器监听接口onSensorChanged(int sensor, float[] value)。因此,需要修改/frameworks/base/core/java/android/hardware/SensorManager.java中的mapSensorDataToWindow方法,这个方法负责把HAL读到的原始数据转化成旧接口的数据(利用onSensorChanged(int sensor, float[] value)接收到的数据)。目前只做了0度和90度两个方向,所以也修改了一下WindowOrientationListener,让所有API只能在这两个方向上旋转:1、mAllow180Rotation变量永远设置为false。2、修改ROTATE_TO数组,把270度全部修改为90度。另外,sensor.c中poll data的接口会有一个int型返回值,表示读到的sensors_event_t的个数,这个值一定要等于实际个数。我自己的程序里面,实际读到了一个(accelerator),但返回时不管读到几个,都返回当前传感器的个数。这样的话,在应用程序中用老接口onSensorChanged(int sensor, float[] value)来监听时,除了一个正常数据之外,还会读到(sensor.c中的poll data函数返回值-1)个全部为0的冗余数据。补充于通过AndroidManifest.xml设置屏幕方向的话,安装后就不能改变,而程序内部设置屏幕方向就不会有这个限制。主要靠这两个API:getRequestedOrientation()和setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)这两个API通过ActivityManagerService.java的转换后,实际上都是调用的WindowManagerService的同名方法。每个Activity在WindowManagerService端都有一个AppWindowToken做代表,而屏幕的方向信息就存储在这里。PhoneWindowManager会自动根据屏幕物理特性决定屏幕方向,看这段代码:if (mPortraitRotation & 0) {
// Initialize the rotation angles for each orientation once.
Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
if (d.getWidth() & d.getHeight()) {
mPortraitRotation = Surface.ROTATION_90;
mLandscapeRotation = Surface.ROTATION_0;
mUpsideDownRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_180;
mPortraitRotation = Surface.ROTATION_0;
mLandscapeRotation = Surface.ROTATION_90;
mUpsideDownRotation = Surface.ROTATION_180;
mSeascapeRotation = Surface.ROTATION_270;
}这里的d.getWidth() 和 d.getHeight()得到的是物理屏幕的宽高。一般来说,平板和手机的是不一样的。平板是宽比高大(0度时位于landscape模式,右转90度进入porit模式),手机是高比宽大(0度是位于porit模式,右转90度进入landscape模式)。如果应用程序只关心当前是横屏还是竖屏,而不直接使用传感器的话,没什么问题。如果像依靠重力感应的游戏那样直接使用传感器,就需要自己根据物理屏幕的坐标系对传感器数据做转化,否则就会出现坐标系混乱的问题。我这里碰到的是Range Thunder和Teeter两个小游戏。它们都没有通过上面的d.getWidth()和d.getHeight()来检测设备的物理屏幕从确定哪个是landscape和porit模式,而是直接假设设备是和手机一样的模式。由于游戏运行在landscape模式下,它们都把传感器数据右转90度。这样做法在手机上是没有问题,但在平板电脑上是不应该转化的,这是因为物理屏幕宽比高大的情况下,默认就是landscape模式。补充于看到下面一楼读者提到的问题后,补充一下我针对他说的那个问题的解决方案。拿新接口来说,我们可以在onSensorChangedLocked接口中,SensorEvent传递出去之前,对坐标系调整一下。但是,按照这种方法把使用新接口游戏调整正确后,发现使用老接口的游戏又乱了,屏幕的旋转方向也乱了。我们已经知道,WindowOrientationListener使用SensorManager来确定屏幕的旋转方向,SensorManager再根据旋转方向对底层读上来的传感器做坐标系转换,然后传递给onSensorChanged(SensorEvent event)。而老接口onSensorChanged(int sensor,float[] value)是用onSensorChanged(SensorEvent event)的数据再做坐标系转换。所以,你还需要在老接口中根据新接口中的坐标系转换也做相应地转换。这样,使用老接口的游戏也可以了。但屏幕旋转不对怎么办?这个问题陷入了一个怪圈。好吧,就到这,下面记录一下我的解决方案:我们先为SensorEvent增加一个属性来记录传感器的原始数据:/**
* 这个注释一定要加上,要不你编译时还要先update-api一下。
float[] originalValue=new float[3];有三个地方用到它,在onSensorChangedLocked中为新接口做坐标系转换,LegacyListener.onSensorChanged接口中为老接口做坐系转换,在WiindowOrientationListener中根据传感器数据计算屏幕旋转方向。在这三个地方进行计算时都使用originalValue里面存放的原始数据进行计算,计算结果放到SensorEvent.value中。这样一来,哪个接口不对调整哪个接口,因为都是使用的原始数据,所以互不影响,再不会出现按下葫芦起来瓢的事情了。补充于不过呢,要是写游戏的人比较认真,不是只简单地考虑Landscape/Porit模式,而是使用Display.getRotation()来获取屏幕的旋转实际角度来做gsensor数据坐标系的转换,那他的程序在我们的板子上就悲剧了:获取当前屏幕旋转角度:WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display mDisplay = windowManager.getDefaultDisplay();
mDisplay.getRotation();很不幸,Gallery3D就是这样来做图片翻转特效的。下面代码段位于/packages/apps/Gallery3D/src/com/cooliris/media/GridInputProcessor.java文件中,根据屏幕旋转角度计算出图片的倾斜度。只好让它使用原始数据了,下面分别是修改前和修改后的代码。修改前:
public void onSensorChanged(RenderView view, SensorEvent event, int state) {
if (mZoomGesture)
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
float[] values = event.
float valueToU
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
valueToUse = values[0];
case Surface.ROTATION_90:
valueToUse = -event.values[1];
case Surface.ROTATION_180:
valueToUse = -event.values[0];
case Surface.ROTATION_270:
valueToUse =
event.values[1];
valueToUse = 0.0f;
}修改后的代码:&
public void onSensorChanged(RenderView view, SensorEvent event, int state) {
if (mZoomGesture)
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
float[] values = event.
float valueToU
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
valueToUse = values[0];
case Surface.ROTATION_90:
valueToUse = -values[1];
case Surface.ROTATION_180:
valueToUse = -values[0];
case Surface.ROTATION_270:
valueToUse =
values[1];
valueToUse = 0.0f;
}期待那个不分平板和手机的系统早早降临吧。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1388829次
积分:13417
积分:13417
排名:第673名
原创:166篇
译文:37篇
评论:438条
农场老马,与CSDN博客同步更新
基于ffmpeg的mp4视频文件压缩工具

我要回帖

更多关于 手机传感器坐标系 的文章

 

随机推荐