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.阶码全为…...

设计模式——组合模式(结构型)
引言 组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 问题 如果应用的核心模型能用树状结构表示, 在应用中使用组合模式才有价值。 例如, 你有两类对象: …...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...