小阳心健康测量 Android SDK
安装 SDK
导入 SDK
在管理控制台/许可证管理/SDK许可证处添加最新版本 Android SDK 即可获得 SDK 下载链接。
将 SDK 压缩包解压并保持目录结构拷贝到您的 Android 项目中即可。
项目配置
build.gradle
groovyandroid { defaultConfig { //... ndk { abiFilters "arm64-v8a", "armeabi-v7a", "armeabi" } } packagingOptions { jniLibs { pickFirsts += ['lib/x86/libc++_shared.so', 'lib/arm64-v8a/libc++_shared.so', 'lib/armeabi-v7a/libc++_shared.so', 'lib/x86_64/libc++_shared.so'] } resources { excludes += ['META-INF/DEPENDENCIES', 'META-INF/library_release.kotlin_module'] } } } implementation(files('libs/measurement_android_sdk_v2.0.0beta0.aar')) implementation 'com.squareup.okhttp3:okhttp:3.12.0' implementation 'com.google.code.gson:gson:2.6.2' //协议缓冲区 implementation 'com.google.protobuf:protobuf-java:3.13.0' // bzmedia implementation 'io.github.bookzhan:bzyuv:1.1.17@aar' implementation 'io.github.bookzhan:bzcommon:1.1.14@aar' implementation 'io.github.bookzhan:bzcamera:1.0.22@aar' implementation 'io.github.bookzhan:bzmedia:1.0.8@aar' //websocket implementation 'com.microsoft.signalr:signalr:5.0.15' //messagepack implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.15' implementation 'com.blankj:utilcodex:1.30.2' //阿里巴巴人脸识别 implementation 'com.alibaba.android.mnnkit:facedetection:0.0.5' implementation ('com.google.mediapipe:tasks-vision:0.10.0'){ exclude group: 'com.google.protobuf' } //libyuv图像分析库 implementation 'com.github.GitLqr:LQRLibyuv:1.0.0' //google mediaPipe人脸识别库 implementation ('com.google.mediapipe:tasks-vision:0.10.0'){ exclude group: 'com.google.protobuf' }
manifest.xml
xml<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-featureandroid:glEsVersion="0x00020000"android:required="true" />
可根据自己设备硬件性能,在
application
节点或activity
节点开启加速配置。
初始化
初始化测量对象以进行后续健康测量。
测量配置
初始化测量前需要先创建一个MeasurementConfig
对象,用于配置测量的相关参数。
MeasurementConfig(Context context, String appId, String sdkKey)
IMPORTANT
建议将配置对象缓存以供每次测量共享。
参数说明
名称 | 类型 | 必需 | 示例值 | 描述 |
---|---|---|---|---|
context | Context | 是 | - | Application 上下文 |
appId | str | 是 | - | APP_ID 从管理控制台获取 |
sdkKey | str | 是 | - | SDK_KEY 从管理控制台获取 |
高级配置
如果需要自定义测量相关其它高级参数,可以通过如下方式进行配置,如不确定,请勿修改。
方法名称 | 参数说明 | 描述 |
---|---|---|
setMeasurementCategory(Category.MeasurementCategory... measurementCategory) | 测量类型 | 设置单次测量关注的所有测量类型指标 |
setMinChunkTimeSpan(int minChunkTimeSpan) | 阶段测量最小时长 | 设置阶段测量最小时长(毫秒) |
setMeasurementDuration(int measurementDuration) | 测量时长 | 设置测量时长(毫秒) |
setServer(@NonNull String measurementUrl, @NonNull String authUrl,@NonNull boolean isIntranet) | 测量服务URL,认证服务URL,是否为内网部署环境 | 设置服务地址(仅供私有化网络部署使用) |
测量类型说明
指定单次测量关注的所有测量类型指标,缺省值为
Category.MeasurementCategory.All
。默认提供“生理测量”和“情绪测量”两种预设类型。
生理测量包括 心率报告(心率、心率变异率)、房颤报告(房颤)、血压报告(舒张压、收缩压)、血氧报告(血氧饱和度)、风险报告(心脏病风险、中风风险、心血管疾病风险、心脏压力、血管功能)、基础报告(性别、皮肤年龄、体重指数)、综合心健康风险报告(综合心健康风险)。
情绪测量包含 心率报告(心率)、攻击性报告(攻击性)、焦虑度报告(焦虑度)、活力度报告(活力度)、抑郁度报告(抑郁度)、疲劳度报告(疲劳度)、压力度报告(压力度)、情绪综合分报告(情绪综合分)。
如果预设类型不满足,您也可以根据
MeasurementCategory
类型传入关注的指标分类。测量类型(SDK内部包含)定义如下:
javapublic enum MeasurementCategory { //心率房颤 HeartRate(1), //血压 BloodPressure(2), //血氧 BloodOxygen(4), //健康风险 Risk(8), //攻击性 Aggressivity(16), //焦虑度 Anxiety(32), //压抑度[已废弃] Depression(64), //活力度 Vitality(128), //积极性[已废弃] Positivity(256), //抑郁度 Suppression(512), //疲劳度 Fatigue(1024), //生理测量 Physiology(15), //情绪测量 Emotion(1713), //全部指标 All(1727) }
WARNING
测量类型参数将受限于您所购许可证绑定的测量类型,这意味着该参数应为许可证所授权测量类型的子集。
示例代码
如下示例中IMeasurementConfig
对象在Application.onCreate
中创建并缓存。
public class DemoApp extends Application {
public static IMeasurementConfig config;
@Override
public void onCreate() {
super.onCreate();
config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
}
}
public class DemoApp extends Application {
public static IMeasurementConfig config;
@Override
public void onCreate() {
super.onCreate();
config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
config.setMeasurementCategory(Category.MeasurementCategory.Physiology);
}
}
public class DemoApp extends Application {
public static IMeasurementConfig config;
@Override
public void onCreate() {
super.onCreate();
config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
config.setMeasurementCategory(Category.MeasurementCategory.Emotion);
}
}
public class DemoApp extends Application {
public static IMeasurementConfig config;
@Override
public void onCreate() {
super.onCreate();
config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
//仅关注 “血氧/焦虑” 指标
config.setMeasurementCategory(Category.MeasurementCategory.BloodPressure, Category.MeasurementCategory.Anxiety);
}
}
初始化测量
测量过程依赖特征提取器以进行人脸特征定位等,SDK 支持MediaPipe
或MNN
作为特征提取器。前者在数据准确性方面更优,后者对老旧设备的兼容支持更好,需要特别注意的是,选择MNN
作为特征提取器时仅能获得生理类型的测量指标。推荐选择MediaPipe
作为特征提取器,在老旧设备或性能较差的设备上选择MNN
。
IMPORTANT
根据不同设备性能,MediaPipe
初次初始化速度可能较慢,建议在APP初始化时预加载MediaPipe
对象并缓存,以减少测量初始化时间。
方法
//使用MediaPipe作为特征提取器
Measurement(IMeasurementConfig measurementConfig, FeatureExtractor featureExtractor);
//使用MNN作为特征提取器
Measurement(IMeasurementConfig measurementConfig);
参数说明
名称 | 类型 | 必需 | 示例值 | 描述 |
---|---|---|---|---|
measurementConfig | IMeasurementConfig | 是 | - | 必要测量配置 |
featureExtractor | FeatureExtractor | 是(MediaPipe作为特征提取时) | - | 特征提取器预加载对象 |
事件订阅
在测量过程中的关键步骤Measurement
对象会通过不同的事件暴露相应数据给用户,用户可以按需订阅相应事件。
名称 | 参数 | 触发时机 |
---|---|---|
onStarted | 测量ID | 开始测量成功 |
onStateUpdate | 测量异常状态集合 | 测量状态更新 |
onFrameProcessed | 视频帧(处理后),时间戳,宽度,高度 | 视频帧处理完成 |
onCollected | - | 数据采集完毕 |
onChunkReportGenerated | 测量ID,阶段性报告 | 阶段性报告生成 |
onWholeReportGenerated | 测量ID,完整报告 | 完整报告生成 |
onInterrupted | - | 测量已中断 |
onCrashed | 异常对象 | 测量出现异常 |
具体事件详解参见后续章节。
示例代码
public class DemoApp extends Application {
public static IMeasurementConfig config;
public static FeatureExtractor featureExtractor = null;
@Override
public void onCreate() {
super.onCreate();
config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
DemoApp.featureExtractor = new FeatureExtractor(this, new InitCallback() {
@Override
public void onInitialized() {}
@Override
public void onError(Exception e) {}
});
}
}
public class SwitchActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_switch);
initMeasurement();
}
private void initMeasurement() {
measurement = new Measurement(DemoApp.config, DemoApp.faceLandmarkerHelper);
measurement.setMeasurementListener(new IMeasurementListener() {
@Override
public void onStarted(String measurementId) {
}
@Override
public void onStateUpdated(Map<String, ExceptionBean> warnings) {
}
@Override
public void onFrameProcessed(byte[] nv21, long timestamp, int width, int height) {
}
@Override
public void onCollected() {
}
@Override
public void onChunkReportGenerated(String measurementId, MeasurementReport.HrReport hrReport){
}
@Override
public void onWholeReportGenerated(String measurementId, MeasurementReport.Report report) {
}
@Override
public void onInterrupted() {
}
@Override
public void onCrashed(ExceptionBean exception) {
}
});
}
}
测量检测
建议在测量开始前通过validate
方法对测量环境进行检测(非强制),用户可以根据返回的校验问题修正测量条件以提高测量成功率与准确性。 测量要求参阅 健康测量条件及要求。
方法
Map<String, ExceptionBean> validate(byte[] frame, long timestamp, int width, int height, int displayOrientation)
参数说明
名称 | 类型 | 必需 | 描述 |
---|---|---|---|
frame | byte[] | 是 | 视频帧 |
timestamp | long | 是 | 时间戳(务必在获得视频帧的第一时间记录时间戳,精确到毫秒的UNIX时间戳 |
width | int | 是 | 视频帧宽度 |
height | int | 是 | 视频帧高度 |
displayOrientation | int | 是 | 视频帧角度(使输入图像顺时针旋转的角度,旋转后人脸变为正向,正向为0) |
- 视频帧仅用于校验是否满足基本测量条件,测量服务不会保存用户数据。
- 请勿频繁进行测量检测,测量过程中会自动进行测量检测,无需手动检测。
校验结果
校验结果所暴露对象的Level
属性反映了错误级别,Msg/Msg_cn
为详细错误消息,建议用户根据错误原因做出相应调整再次检测,通过后再启动测量。
Map<String, ExceptionBean> warnings = measurement.validate(frame, timestamp, width, height, mCameraOrientation);
while (warnings.entrySet().iterator().hasNext()){
Map.Entry<String, ExceptionBean> bean = warnings.entrySet().iterator().next();
MyLog.d(TAG, String.format("Exception Key: %s, Message: %s, Message CN: %s, Level: %s",
entry.getKey(), bean.getMsg(), bean.getMsg_cn(), bean.getLevel()));
}
启动测量
启动测量,成功会触发onStarted
事件并返回measurement_id
(本次测量唯一标识),失败则会触发onCrashed
事件。
方法
void start()
示例代码
@Override
public void onStarted(String measurementId) {
MyLog.d(measurementId)
}
measurement.start();
采集数据
测量成功开始后,用户需要从摄像头、视频文件等采集视频帧并提供给 SDK 进行图像分析,视频帧需要保持连续,且帧率不能太低,建议15-30帧每秒。视频帧采集满足条件后会触发onCollected
事件,此时可以停止采集数据。
方法
void enqueue(byte[] frame, long timestamp, int width, int height, int displayOrientation);
参数说明
名称 | 类型 | 必需 | 描述 |
---|---|---|---|
frame | byte[] | 是 | 视频帧 |
timestamp | long | 是 | 时间戳(务必在获得视频帧的第一时间记录时间戳,精确到毫秒的UNIX时间戳 |
width | int | 是 | 视频帧宽度 |
height | int | 是 | 视频帧高度 |
displayOrientation | int | 是 | 视频帧角度(使输入图像顺时针旋转的角度,旋转后人脸变为正向,正向为0) |
视频帧中必须包含清晰人脸,人脸需要保持正脸,光线明亮均匀。视频帧务必保持连续,切勿跳帧。
示例代码
@Override
public void onCollected() {
}
measurement.enqueue(frame, timestamp, width, height, mCameraOrientation);
状态监测
测量过程中 SDK 会自动检测数据质量并通过onStateUpdated
事件发布,用户可以按需订阅此事件,以获得告警信息并处理。
IMPORTANT
未处理的告警累计可能会导致测量中断。
示例代码
@Override
public void onStateUpdated(Map<String, ExceptionBean> warnings) {
while (warnings.entrySet().iterator().hasNext()){
Map.Entry<String, ExceptionBean> bean = warnings.entrySet().iterator().next();
MyLog.d(TAG, String.format("Exception Key: %s, Message: %s, Message CN: %s, Level: %s",
entry.getKey(), bean.getMsg(), bean.getMsg_cn(), bean.getLevel()));
}
视频处理
测量过程中 SDK 会自动顺序处理队列中的视频帧,每成功处理一帧会通过onFrameProcessed
事件发布,用户可以按需订阅此事件,以进行必要的数据留存(如录制视频文件等)。
示例代码
@Override
public void onFrameProcessed(byte[] nv21, long timestamp, int width, int height) {
//录制视频等自定义操作
}
订阅报告
测量成功开始后,SDK 会在接收到足够数据的情况下请求测量服务生成健康报告。测量过程中会产生阶段性报告并通过onChunkReportGenerated
事件发布,测量结束后会产生完整健康报告并通过onWholeReportGenerated
事件发布。
用户可以按需订阅onChunkReportGenerated
和onWholeReportGenerated
事件以获得健康报告。
@Override
public void onChunkReportGenerated(String measurementId, MeasurementReport.HrReport hrReport) {
Log.d(TAG, "chunkReportHandler " + measurementId + " ,hrReport " + hrReport.toString());
}
@Override
public void onWholeReportGenerated(String measurementId, MeasurementReport.Report report) {
Log.d(TAG, "wholeReportHandler " + measurementId + " ,measurementReport " + measurementReport.toString());
}
报告解读
请参见 健康报告解读。
中断测量
在测量过程中可以随时终止测量。测量中断后会触发onInterrupted
事件。
方法
void interrupt()
示例代码
@Override
public void onInterrupted() {
}
measurement.interrupt();
异常处理
启动测量后,任意环节出现异常均会通过onCrashed
事件发布,用户可以按需订阅此事件,以获得错误信息并处理。
示例代码
@Override
public void onCrashed(ExceptionBean exception) {
Log.e(Constants.TAG, "onException: " + exception.getMsg_cn() + " level: " + exception.getLevel());
}
onCrashed
事件处理函数中所暴露对象的Level
属性反映了当前异常级别,如果异常级别为Error
则 SDK 会自动终止本次测量,用户需根据错误原因做出相应调整后重新开始新的测量。
示例程序
示例程序代码已分享至GitHub,仅供参考。