MTK Android P Sensor架构(一)
需求场景:
本来如果只是给传感器写个驱动并提供能读取温湿度数据的节点,是一件比较轻松的事情,但是最近上层应用的同事要求我们按照安卓标准的流程来,这样他们就能通过注册一个服务直接读取传感器事件数据了。这样做的好处就是第三方的应用也能正常读取温湿度的数据并展示。
正文:
网上分析安卓9.0 sensor相关的资料不多,下面找到了一位大神对安卓9.0整个sensor框架总结的流程图:

虽然流程比较粗糙,但是也有助于我们跟踪代码。这里重点说一下,sensor架构中的HAL层分为两部分:
- 安卓官方实现部分:
hardware/libhardware/modules/sensors
- 芯片产商实现部分(MTK平台):
vendor/mediatek/proprietary/hardware/sensor
一般来讲,在适配一款新的sensor,改动只会涉及vendor层到kernel层,再往上都是安卓标准的,但是为了了解整个流程怎么走的,参考这位大神的博客,在这里我也稍微介绍一下framework层的部分。
代码路径:frameworks\base\services\java\com\android\server\SystemServer.java
private void startBootstrapServices() {...mSensorServiceStart = SystemServerInitThreadPool.get().submit(() -> {TimingsTraceLog traceLog = new TimingsTraceLog(SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);traceLog.traceBegin(START_SENSOR_SERVICE);startSensorService(); /* 调用JNI接口 */traceLog.traceEnd();}, START_SENSOR_SERVICE);...
}
system_server启动之后会通过JNI接口启动sensorService。
代码路径:frameworks\base\services\core\jni\com_android_server_SystemServer.cpp
static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) {char propBuf[PROPERTY_VALUE_MAX];property_get("system_init.startsensorservice", propBuf, "1");if (strcmp(propBuf, "1") == 0) {SensorService::instantiate();}}/** JNI registration.*/static const JNINativeMethod gMethods[] = {/* name, signature, funcPtr */{ "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },{ "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },};
从上面可以发现,最后调用到
android_server_SystemServer_startSensorService
函数,里面会判断属性
system_init.startsensorservice
是否为1,然后才会真正去启动
SensorService
服务。所以这里涉及到第一个改动,设置
system_init.startsensorservice
属性,这里我是直接在
build/make/tools/buildinfo.sh
里面写死为1。
用SensorService::instantiate()方式创建的sensorservice实例后,调用里面的SensorService::onFirstRef方法。
代码路径:frameworks\native\services\sensorservice\SensorService.cpp
void SensorService::onFirstRef() {ALOGD("nuSensorService starting...");SensorDevice& dev(SensorDevice::getInstance()); /* 创建并获取SensorDevice实例 */...if (dev.initCheck() == NO_ERROR) {sensor_t const* list;ssize_t count = dev.getSensorList(&list); /* 通过SensorDevice,并调用到vendor层去获取sensor的数目 */if (count > 0) {ssize_t orientationIndex = -1;bool hasGyro = false, hasAccel = false, hasMag = false;uint32_t virtualSensorsNeeds =(1<<SENSOR_TYPE_GRAVITY) |(1<<SENSOR_TYPE_LINEAR_ACCELERATION) |(1<<SENSOR_TYPE_ROTATION_VECTOR) |(1<<SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR) |(1<<SENSOR_TYPE_GAME_ROTATION_VECTOR);for (ssize_t i=0 ; i<count ; i++) {bool useThisSensor=true;switch (list[i].type) {case SENSOR_TYPE_ACCELEROMETER:hasAccel = true;break;case SENSOR_TYPE_MAGNETIC_FIELD:hasMag = true;break;case SENSOR_TYPE_ORIENTATION:orientationIndex = i;break;case SENSOR_TYPE_GYROSCOPE:case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:hasGyro = true;break;case SENSOR_TYPE_GRAVITY:case SENSOR_TYPE_LINEAR_ACCELERATION:case SENSOR_TYPE_ROTATION_VECTOR:case SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR:case SENSOR_TYPE_GAME_ROTATION_VECTOR:if (IGNORE_HARDWARE_FUSION) {useThisSensor = false;} else {virtualSensorsNeeds &= ~(1<<list[i].type);}break;}if (useThisSensor) {registerSensor( new HardwareSensor(list[i]) );}}// it's safe to instantiate the SensorFusion object here// (it wants to be instantiated after h/w sensors have been// registered)SensorFusion::getInstance();if (hasGyro && hasAccel && hasMag) {...}if (hasAccel && hasGyro) {...}if (hasAccel && hasMag) {...}...}}
}
我这次主要是增加温湿度传感器的功能,上面的流程中没有过多涉及温湿度的,有兴趣的可以参考大神的博客自行分析。不过这里重点关注一下SensorDevice这个类,它是连接上层应用和HAL层的中间枢纽:
代码路径:frameworks\native\services\sensorservice\SensorDevice.cpp
SensorDevice::SensorDevice(): mHidlTransportErrors(20), mRestartWaiter(new HidlServiceRegistrationWaiter()) {if (!connectHidlService()) {return;}float minPowerMa = 0.001; // 1 microAmpcheckReturn(mSensors->getSensorsList([&](const auto &list "&") {const size_t count = list.size();mActivationCount.setCapacity(count);Info model;for (size_t i=0 ; i < count; i++) {sensor_t sensor;convertToSensor(list[i], &sensor);// Sanity check and clamp power if it is 0 (or close)if (sensor.power < minPowerMa) {ALOGE("Reported power %f not deemed sane, clamping to %f",sensor.power, minPowerMa);sensor.power = minPowerMa;}mSensorList.push_back(sensor);mActivationCount.add(list[i].sensorHandle, model);checkReturn(mSensors->activate(list[i].sensorHandle, 0 /* enabled */));}}));mIsDirectReportSupported =(checkReturn(mSensors->unregisterDirectChannel(-1)) != Result::INVALID_OPERATION);
}
在SensorDevice构造函数中,通过调用connectHidlService()和安卓部分的HAL层服务建立连接。连接后,就可以调用已经在HAL层注册的sensor设备了,比如这里就调用getSensorsList()来获取sensor设备列表,并放回sensor的数目。然后就是通过mSensors->activate()来“激活”sensor设备,而每个sensor具体的activate()函数由驱动工程师实现。
激活sensor设备后,就可以开始获取sensor的数据了,在SensorService中会通过poll机制去查询底层sensor的数据:
代码路径:frameworks\native\services\sensorservice\SensorService.cpp
bool SensorService::threadLoop() {...SensorDevice& device(SensorDevice::getInstance());const int halVersion = device.getHalDeviceVersion();do {ssize_t count = device.poll(mSensorEventBuffer, numEventMax);if (count < 0) {ALOGE("sensor poll failed (%s)", strerror(-count));break;}...} while (!Thread::exitPending());ALOGW("Exiting SensorService::threadLoop => aborting...");abort();return false;
}
整个threadLoop函数里面内容挺多的,但是目前只关注读取数据的poll部分。可以看到device就是SensorDevice的一个实例,前面我们讲到上层都是通过SensorDevice和HAL层连接,这里也不例外,也是调用到了SensorDevice中的poll函数,这里我给出这个调用的流程:
1、frameworks\native\services\sensorservice\SensorDevice.cpp
SensorDevice::poll()2、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\sensors.cpppoll__poll()3、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorManager.cppSensorManager::pollEvent()4、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorContext.cppsensors_poll_context_t::pollEvent
上面简陋的流程展示了从framework层一路调用到vendor层:
int sensors_poll_context_t::pollEvent(sensors_event_t* data, int count) {int nbEvents = 0;int n = 0;int averageCount = 0, loop = 0, loopcount = 0;int backupcount = count, backuploop = 0;do {loopcount++;computeCountForEachFd(count, &averageCount, &loop);backuploop = loop;for (int i = 0; count && loop && i < numFds; i++) {SensorBase* const sensor(mSensors[i]);if (mPollFds[i].revents & POLLIN || sensor->pendingEvent()) {int nb = sensor->readEvents(data, averageCount);...}}// try to see if we can get some events immediately or just wait if// we don't have anything to return, important to update fd revents// which sensor data pending in buffer and aviod one sensor always// occupy poll bandwidth.n = TEMP_FAILURE_RETRY(poll(mPollFds, numFds, nbEvents ? 0 : -1));if (n < 0) {ALOGE("poll() failed (%s)", strerror(errno));return -errno;}} while (n && count);return nbEvents;
}
这里面我们重点关注三点
(1) mPollFds的定义如下
struct pollfd mPollFds[numFds];
其中,
struct pollfd {int fd; /* 文件描述符 */short events; /* 等待的事件 */short revents; /* 实际发生了的事件 */
};
所以mPollFds就是用来监听代表每个sensor是否有数据上报的文件描述符
enum {accel,magnetic,gyro,light,proximity,pressure,humidity,temperature,stepcounter,pedometer,activity,situation,scpfusion,apfusion,bio,wakeupset,numFds,
};
如果想自定义一种sensor就需要给这个枚举类型增加值
(2) mSensors的定义如下:
SensorBase* mSensors[numFds];
SensorBase是一个基类,所有的sensor类都继承于它,比如我这次实现的湿度传感器:
class HumiditySensor : public SensorBase {private:int mEnabled;sensors_event_t mPendingEvent;SensorEventCircularReader mSensorReader;int64_t mEnabledTime;char input_sysfs_path[PATH_MAX];int input_sysfs_path_len;int mDataDiv;int64_t m_hmdy_last_ts = 0;int64_t m_hmdy_delay = 0;void processEvent(struct sensor_event const *event);public:HumiditySensor();virtual ~HumiditySensor();virtual int readEvents(sensors_event_t* data, int count);virtual int setDelay(int32_t handle, int64_t ns);virtual int enable(int32_t handle, int enabled);virtual int batch(int handle, int flags, int64_t samplingPeriodNs, int64_t maxBatchReportLatencyNs);virtual int flush(int handle);virtual int getFd() {return mSensorReader.getReadFd();};
};
从类的声明来看,定义了很多函数,比如readEvents、enable和batch等等,这些最终都会和底层驱动联系起来,后面再细说。
(3)在sensors_poll_context_t的构造函数中会对上面两点讲到的数组进行初始化:
sensors_poll_context_t::sensors_poll_context_t()
{...mSensors[humidity] = new HumiditySensor(); /* 分配一个Humidity传感器的类 */mPollFds[humidity].fd = mSensors[humidity]->getFd(); /* 获取对应sensor的字符描述符 */mPollFds[humidity].events = POLLIN; /* 等待POLLIN类型的事件 */mPollFds[humidity].revents = 0;...
}
再回到上面的
sensors_poll_context_t::pollEvent()
函数,通过
mPollFds[i].revents
判断到如果发生了POLLIN事件,证明可以获取数据了,就调用对应sensor的readEvents()
函数去获取。接下来我们就进入到sensor设备对应的HAL层里面了,现在以湿度sensor为例:
代码路径:vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\Humidity.cpp
int HumiditySensor::readEvents(sensors_event_t* data, int count) {if (count < 1)return -EINVAL;ssize_t n = mSensorReader.fill();if (n < 0)return n;int numEventReceived = 0;struct sensor_event const* event;while (count && mSensorReader.readEvent(&event)) {processEvent(event);if (event->flush_action <= FLUSH_ACTION) {...}mSensorReader.next();}return numEventReceived;}
我们可以看到读取数据实际又是统一通过
SensorEventCircularReader
这个类来操作:
代码路径:vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorEventReader.cpp
SensorEventCircularReader::SensorEventCircularReader(size_t numEvents): mBuffer(new struct sensor_event[numEvents * 2]),mBufferEnd(mBuffer + numEvents),mHead(mBuffer),mCurr(mBuffer),mFreeSpace(numEvents) {mReadFd = -1;mWriteFd = -1;
}
构造函数里面分配了Buffer来存储接收的数据
ssize_t SensorEventCircularReader::fill() {size_t numEventsRead = 0;if (mFreeSpace) {const ssize_t nread = TEMP_FAILURE_RETRY(read(mReadFd, mHead, mFreeSpace * sizeof(struct sensor_event)));if (nread < 0 || nread % sizeof(struct sensor_event)) {return 0;}...}return numEventsRead;}
fill顾名思义就是往分配的buffer里面填充数据,通过我们熟悉的read()函数来获取数据。
ssize_t SensorEventCircularReader::readEvent(struct sensor_event const** events) {*events = mCurr;ssize_t available = (mBufferEnd - mBuffer) - mFreeSpace;return available ? 1 : 0;
}
readEvent()
只是判断buffer中是否有数据,然后就是调用
mSensorReader.next()
获取下一个buffer。再回到
HumiditySensor::readEvents()
在读取到数据后会调用
processEvent()
去处理数据:
void HumiditySensor::processEvent(struct sensor_event const *event) {mPendingEvent.relative_humidity = (float) event->word[0] / mDataDiv;
}
mPendingEvent.relative_humidity就是最终上报给上层应用的值了。
至此,framework层到vendor层的流程就分析完了,后面我们会分析kernel层的sensor框架。
相关文章:
MTK Android P Sensor架构(一)
需求场景: 本来如果只是给传感器写个驱动并提供能读取温湿度数据的节点,是一件比较轻松的事情,但是最近上层应用的同事要求我们按照安卓标准的流程来,这样他们就能通过注册一个服务直接读取传感器事件数据了。这样做的好处就是第…...
低代码开发与传统软件开发:未来趋势与竞争格局
近年来,低代码开发平台的快速发展引起了各行各业的广泛关注。低代码开发平台简化了软件开发的复杂性,提供了更快速、更灵活的开发方式。于是,许多人开始产生一个疑问:未来低代码开发是否会取代传统软件开发?今天这篇文…...
leetcode 股票问题全序列
1 只允许一次交易,121题,买卖股票的最佳时机 class Solution {/*给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票…...
SpringBoot中日志的使用log4j2
SpringBoot中日志的使用log4j2 1、log4j2介绍 Apache Log4j2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改 进,同时修复了 Logback 架构中的一些问题,主要有: 异常处理…...
机械设备企业网站建设的效果如何
机械设备涵盖的类目比较广,其市场需求也是稳增不减,也因此无论大小企业都有增长的机会,当然这也需要靠谱的工具及正确的决策。 对机械设备企业来说,产品品质自然是首位,而向外打造品牌、扩展信息及拓客转化自然也是非…...
设计模式之结构型设计模式(二):工厂模式 抽象工厂模式 建造者模式
工厂模式 Factory 1、什么是工厂模式 工厂模式旨在提供一种统一的接口来创建对象,而将具体的对象实例化的过程延迟到子类或者具体实现中。有助于降低客户端代码与被创建对象之间的耦合度,提高代码的灵活性和可维护性。 定义了一个创建对象的接口&…...
算法模板之单链表图文讲解
🌈个人主页:聆风吟 🔥系列专栏:算法模板、数据结构 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言一. ⛳️使用数组模拟单链表讲解1.1 🔔为什么我们要使用数组去模拟单链表…...
【强化学习-读书笔记】表格型问题的 Model-Free 方法
参考 Reinforcement Learning, Second Edition An Introduction By Richard S. Sutton and Andrew G. Barto无模型方法 在前面的文章中,我们介绍的是有模型方法(Model-Based)。在强化学习中,"Model"可以理解为算法…...
【手撕算法系列】k-means
k-means k-means算法介绍 k-means算法介绍 K-means算法是一种用于聚类的迭代算法,它将数据集划分为K个簇,其中每个数据点属于与其最近的簇的中心。这个算法的目标是最小化簇内的平方和误差(簇内数据点与簇中心的距离的平方和)。 …...
D33|动态规划!启程!
1.动态规划五部曲: 1)确定dp数组(dp table)以及下标的含义 2)确定递推公式 3)dp数组如何初始化 4)确定遍历顺序 5)举例推导dp数组 2.动态规划应该如何debug 找问题的最好方式就是把…...
C语言----文件操作(二)
在上一篇文章中我们简单介绍了在C语言中文件是什么以及文件的打开和关闭操作,在实际工作中,我们不仅仅是要打开和关闭文件,二是需要对文件进行增删改写。本文将详细介绍如果对文件进行安全读写。 一,以字符形式读写文件ÿ…...
oracle 10046事件跟踪
10046事件是一个很好的排查sql语句执行缓慢的内部事件,具体设置方式如下: 根据10046事件跟踪SQL语句 1、 alter session set events 10046 trace name context forever,level 12; 2、执行SQL语句 3、关闭10046事件 alter session set events 10046 trace…...
微软自带浏览器Edge,无法关闭“保存历史记录网站的屏幕截图”解决方案
微软自带浏览器Edge,无法关闭“保存历史记录网站的屏幕截图”解决方案 吐槽1:Windows自带的Chrome内核版本的浏览器Microsofg Edge刚发布时可谓一股清流,启动速度快,占用内存较小,相信很多人也开始抛弃正代Chrome&…...
讲座 | 颠覆传统摄像方式乃至计算机视觉的“脉冲视觉”
传统相机拍摄视频时其实是以一定帧率进行采样,视频其实还是一串图片的集合,因此低帧率时会觉得视频卡,拍摄高速运动物体时会有运动模糊等等问题。然而你能想象这一切都可以被“脉冲视觉”这一前沿技术改变吗? 今天下午听了北京大学…...
uniGUI学习之UniHTMLMemo1富文本编辑器
1]系统自带的富文本编辑器 2]jQueryBootstarp富文本编辑器插件summernote.js 1]系统自带的富文本编辑器 1、末尾增加<p> 2、增加字体 3、解决滚屏问题 4、输入长度限制问题 5、显示 并 编辑 HTML源代码(主要是图片处理) 1、末尾增加<p> UniHTMLMemo1.Lines…...
详细教程 - 从零开发 鸿蒙harmonyOS应用 第四节 (鸿蒙Stage模型 登录页面 ArkTS版 推荐使用)
在鸿蒙OS中,Ability是应用程序提供的抽象功能,可以理解为一种功能。在应用程序中,一个页面即一种能力,如登录页面,即具有登录功能的能力。以下是对鸿蒙新建项目的登录代码功能的详细解读和工作流程的描述: …...
uniapp怎么实现授权登录
在Uniapp中实现授权登录通常涉及以下几个步骤: 创建登录按钮:在页面中创建一个按钮,用于触发登录操作。 获取用户授权:当用户点击登录按钮时,调用uni.login或uni.getUserInfo等API获取用户授权。 处理授权回调&#…...
从零开始:前端架构师的基础建设和架构设计之路
文章目录 一、引言二、前端架构师的职责三、基础建设四、架构设计思想五、总结《前端架构师:基础建设与架构设计思想》编辑推荐内容简介作者简介目录获取方式 一、引言 在现代软件开发中,前端开发已经成为了一个不可或缺的部分。随着互联网的普及和移动…...
椋鸟C语言笔记#26:数据在内存中的存储(大小端字节序)、浮点数的存储(IEEE754)
萌新的学习笔记,写错了恳请斧正。 目录 大小端字节序 什么是大小端 写一个判断大小端的程序 浮点数在内存中的存储(IEEE 754规则) 引入 存储规则解释 读取规则解释 1.阶码不全为0或全为1(规格化数) 2.阶码全为…...
设计模式——组合模式(结构型)
引言 组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 问题 如果应用的核心模型能用树状结构表示, 在应用中使用组合模式才有价值。 例如, 你有两类对象: …...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
