second
This commit is contained in:
parent
c02885dd51
commit
a368ce9048
|
|
@ -4,6 +4,14 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-09-02T03:30:46.971762700Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=ba2e16dd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
# JNI函数名不匹配问题修复说明
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
在代码重构过程中,将原生方法从`MainActivity`移动到`DataManager`类后,出现了以下错误:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cannot resolve corresponding JNI function Java_com_example_cmake_1project_1test_DataManager_createStreamParser
|
||||||
|
```
|
||||||
|
|
||||||
|
## 问题原因
|
||||||
|
|
||||||
|
### JNI函数命名规则
|
||||||
|
|
||||||
|
JNI函数名遵循以下格式:
|
||||||
|
```
|
||||||
|
Java_{包名}_{类名}_{方法名}
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- 包名中的点(.)用下划线(_)替换
|
||||||
|
- 类名中的下划线用`_1`替换
|
||||||
|
|
||||||
|
### 具体变化
|
||||||
|
|
||||||
|
**重构前的JNI函数名**(在MainActivity中):
|
||||||
|
```
|
||||||
|
Java_com_example_cmake_1project_1test_MainActivity_createStreamParser
|
||||||
|
Java_com_example_cmake_1project_1test_MainActivity_destroyStreamParser
|
||||||
|
Java_com_example_cmake_1project_1test_MainActivity_streamParserAppend
|
||||||
|
Java_com_example_cmake_1project_1test_MainActivity_streamParserDrainPackets
|
||||||
|
```
|
||||||
|
|
||||||
|
**重构后的JNI函数名**(在DataManager中):
|
||||||
|
```
|
||||||
|
Java_com_example_cmake_1project_1test_DataManager_createStreamParser
|
||||||
|
Java_com_example_cmake_1project_1test_DataManager_destroyStreamParser
|
||||||
|
Java_com_example_cmake_1project_1test_DataManager_streamParserAppend
|
||||||
|
Java_com_example_cmake_1project_1test_DataManager_streamParserDrainPackets
|
||||||
|
```
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 方案1:回调接口模式(已实现)
|
||||||
|
|
||||||
|
通过创建回调接口,让`DataManager`通过`MainActivity`来调用原生方法,保持原有的JNI函数名。
|
||||||
|
|
||||||
|
#### 实现步骤
|
||||||
|
|
||||||
|
1. **创建回调接口**
|
||||||
|
```kotlin
|
||||||
|
interface NativeMethodCallback {
|
||||||
|
fun createStreamParser(): Long
|
||||||
|
fun destroyStreamParser(handle: Long)
|
||||||
|
fun streamParserAppend(handle: Long, chunk: ByteArray)
|
||||||
|
fun streamParserDrainPackets(handle: Long): List<SensorData>?
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **修改DataManager构造函数**
|
||||||
|
```kotlin
|
||||||
|
class DataManager(private val nativeCallback: NativeMethodCallback)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **MainActivity实现接口**
|
||||||
|
```kotlin
|
||||||
|
class MainActivity : AppCompatActivity(), DataManager.NativeMethodCallback
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **保持原生方法在MainActivity中**
|
||||||
|
```kotlin
|
||||||
|
// 原生方法声明 - 保持原来的JNI函数名
|
||||||
|
external fun createStreamParser(): Long
|
||||||
|
external fun destroyStreamParser(handle: Long)
|
||||||
|
external fun streamParserAppend(handle: Long, chunk: ByteArray)
|
||||||
|
external fun streamParserDrainPackets(handle: Long): List<SensorData>?
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 优势
|
||||||
|
- 保持原有的JNI函数名,无需修改C++代码
|
||||||
|
- 维持了代码重构的架构优势
|
||||||
|
- 清晰的职责分离
|
||||||
|
|
||||||
|
#### 劣势
|
||||||
|
- 增加了接口依赖
|
||||||
|
- 稍微增加了代码复杂度
|
||||||
|
|
||||||
|
### 方案2:修改C++代码中的JNI函数名
|
||||||
|
|
||||||
|
如果您有权限修改C++代码,可以将C++中的JNI函数名改为新的名称。
|
||||||
|
|
||||||
|
#### 需要修改的C++函数名
|
||||||
|
```cpp
|
||||||
|
// 原来的函数名
|
||||||
|
JNIEXPORT jlong JNICALL Java_com_example_cmake_1project_1test_MainActivity_createStreamParser
|
||||||
|
|
||||||
|
// 改为
|
||||||
|
JNIEXPORT jlong JNICALL Java_com_example_cmake_1project_1test_DataManager_createStreamParser
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 优势
|
||||||
|
- 完全符合重构后的架构
|
||||||
|
- 无需额外的接口层
|
||||||
|
|
||||||
|
#### 劣势
|
||||||
|
- 需要修改C++代码
|
||||||
|
- 可能影响其他依赖项目
|
||||||
|
|
||||||
|
## 推荐方案
|
||||||
|
|
||||||
|
**推荐使用方案1(回调接口模式)**,原因如下:
|
||||||
|
|
||||||
|
1. **无需修改C++代码**:保持现有C++代码的稳定性
|
||||||
|
2. **维持重构优势**:仍然保持了代码的职责分离
|
||||||
|
3. **向后兼容**:如果将来需要,可以轻松切换到方案2
|
||||||
|
4. **风险较低**:不会引入新的编译或链接问题
|
||||||
|
|
||||||
|
## 修复后的架构
|
||||||
|
|
||||||
|
```
|
||||||
|
MainActivity (实现NativeMethodCallback)
|
||||||
|
↓
|
||||||
|
DataManager (通过回调调用原生方法)
|
||||||
|
↓
|
||||||
|
原生C++库 (保持原有JNI函数名)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **JNI函数名一致性**:确保Kotlin中的`external`方法名与C++中的JNI函数名完全匹配
|
||||||
|
2. **库加载**:确保在`MainActivity`的`companion object`中正确加载原生库
|
||||||
|
3. **接口实现**:MainActivity必须实现`NativeMethodCallback`接口的所有方法
|
||||||
|
4. **依赖注入**:DataManager的构造函数现在需要传入回调接口实例
|
||||||
|
|
||||||
|
## 验证修复
|
||||||
|
|
||||||
|
修复完成后,您应该能够:
|
||||||
|
1. 成功编译项目
|
||||||
|
2. 不再看到JNI函数名不匹配的错误
|
||||||
|
3. 保持原有的功能正常运行
|
||||||
|
4. 享受重构后的代码结构优势
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
# 代码重构说明
|
||||||
|
|
||||||
|
## 重构目标
|
||||||
|
将原本堆在一个MainActivity.kt文件中的所有逻辑分解为多个职责明确的Kotlin文件,提高代码的可读性和可维护性。
|
||||||
|
|
||||||
|
## 重构后的文件结构
|
||||||
|
|
||||||
|
### 1. **MainActivity.kt** - 主界面控制器
|
||||||
|
**职责**: 主界面逻辑和生命周期管理
|
||||||
|
- 初始化各个管理器
|
||||||
|
- 处理按钮点击事件
|
||||||
|
- 管理Activity生命周期
|
||||||
|
- 协调各个管理器之间的交互
|
||||||
|
|
||||||
|
**主要方法**:
|
||||||
|
- `onCreate()`: 初始化UI和管理器
|
||||||
|
- `onBleNotify()`: 蓝牙数据通知处理
|
||||||
|
- `resetData()`: 重置数据
|
||||||
|
- `reloadData()`: 重新加载数据
|
||||||
|
|
||||||
|
### 2. **Constants.kt** - 常量定义
|
||||||
|
**职责**: 集中管理应用中的所有常量
|
||||||
|
- UI更新间隔
|
||||||
|
- 数据分块大小
|
||||||
|
- 缓冲区管理阈值
|
||||||
|
- 显示限制参数
|
||||||
|
|
||||||
|
**主要常量**:
|
||||||
|
- `UPDATE_INTERVAL`: UI更新间隔(500ms)
|
||||||
|
- `CHUNK_SIZE`: 数据分块大小(64字节)
|
||||||
|
- `BUFFER_CLEANUP_THRESHOLD`: 缓冲区清理阈值(50)
|
||||||
|
- `BUFFER_KEEP_COUNT`: 缓冲区保留数量(30)
|
||||||
|
|
||||||
|
### 3. **DeviceTypeHelper.kt** - 设备类型工具
|
||||||
|
**职责**: 设备类型相关的工具方法
|
||||||
|
- 设备类型名称映射
|
||||||
|
- 通道数据详情构建
|
||||||
|
|
||||||
|
**主要方法**:
|
||||||
|
- `getDeviceName()`: 根据数据类型获取设备名称
|
||||||
|
- `buildChannelDetails()`: 构建通道数据详情字符串
|
||||||
|
|
||||||
|
### 4. **DataManager.kt** - 数据管理器
|
||||||
|
**职责**: 数据解析、缓冲管理和原生方法调用
|
||||||
|
- 管理流式解析器
|
||||||
|
- 处理数据块
|
||||||
|
- 管理数据缓冲区
|
||||||
|
- 调用原生C++方法
|
||||||
|
|
||||||
|
**主要方法**:
|
||||||
|
- `ensureParser()`: 确保解析器已创建
|
||||||
|
- `onBleNotify()`: 处理蓝牙通知数据块
|
||||||
|
- `processFileData()`: 处理文件数据
|
||||||
|
- `cleanupBuffer()`: 智能清理缓冲区
|
||||||
|
- `resetData()`: 重置所有数据
|
||||||
|
|
||||||
|
**原生方法**:
|
||||||
|
- `createStreamParser()`: 创建流式解析器
|
||||||
|
- `streamParserAppend()`: 向解析器追加数据
|
||||||
|
- `streamParserDrainPackets()`: 从解析器拉取数据包
|
||||||
|
- `destroyStreamParser()`: 销毁解析器
|
||||||
|
|
||||||
|
### 5. **UiManager.kt** - UI管理器
|
||||||
|
**职责**: UI更新、统计信息构建和显示逻辑
|
||||||
|
- 管理UI更新调度
|
||||||
|
- 构建统计信息
|
||||||
|
- 构建显示内容
|
||||||
|
- 触发UI更新
|
||||||
|
|
||||||
|
**主要方法**:
|
||||||
|
- `scheduleUiUpdate()`: 计划UI更新,避免频繁刷新
|
||||||
|
- `buildStatisticsString()`: 构建统计信息字符串
|
||||||
|
- `buildDisplayContent()`: 构建完整的显示内容
|
||||||
|
- `updateDisplay()`: 更新UI显示内容
|
||||||
|
|
||||||
|
### 6. **FileHelper.kt** - 文件帮助类
|
||||||
|
**职责**: 文件读取操作
|
||||||
|
- 从assets文件夹读取文件
|
||||||
|
|
||||||
|
**主要方法**:
|
||||||
|
- `readAssetFile()`: 读取assets文件到字节数组
|
||||||
|
|
||||||
|
## 重构优势
|
||||||
|
|
||||||
|
### 1. **职责分离**
|
||||||
|
- 每个类都有明确的单一职责
|
||||||
|
- 降低了类之间的耦合度
|
||||||
|
- 提高了代码的可测试性
|
||||||
|
|
||||||
|
### 2. **代码复用**
|
||||||
|
- 工具方法可以在多个地方复用
|
||||||
|
- 减少了重复代码
|
||||||
|
- 提高了开发效率
|
||||||
|
|
||||||
|
### 3. **维护性提升**
|
||||||
|
- 修改某个功能只需要修改对应的类
|
||||||
|
- 代码结构更清晰,易于理解
|
||||||
|
- 降低了修改的风险
|
||||||
|
|
||||||
|
### 4. **可扩展性**
|
||||||
|
- 新增功能可以创建新的类
|
||||||
|
- 现有类可以独立扩展
|
||||||
|
- 支持团队协作开发
|
||||||
|
|
||||||
|
## 数据流向
|
||||||
|
|
||||||
|
```
|
||||||
|
MainActivity → DataManager → 原生解析器
|
||||||
|
↓
|
||||||
|
UiManager ← DataManager ← 数据缓冲区
|
||||||
|
↓
|
||||||
|
UI更新
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
1. **MainActivity**: 作为入口点,协调各个管理器
|
||||||
|
2. **DataManager**: 处理所有数据相关的操作
|
||||||
|
3. **UiManager**: 负责所有UI相关的更新
|
||||||
|
4. **DeviceTypeHelper**: 提供设备类型相关的工具方法
|
||||||
|
5. **Constants**: 集中管理所有常量
|
||||||
|
6. **FileHelper**: 处理文件读取操作
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **原生方法**: 所有原生C++方法现在都在DataManager中声明
|
||||||
|
2. **依赖关系**: 各个类之间有明确的依赖关系,避免循环依赖
|
||||||
|
3. **线程安全**: UI更新始终在主线程进行,数据操作在后台线程进行
|
||||||
|
4. **错误处理**: 每个类都有适当的错误处理和日志记录
|
||||||
|
|
||||||
|
## 后续优化建议
|
||||||
|
|
||||||
|
1. **依赖注入**: 可以考虑使用Dagger或Koin进行依赖注入
|
||||||
|
2. **响应式编程**: 可以考虑使用RxJava或Flow进行数据流处理
|
||||||
|
3. **单元测试**: 重构后的代码更容易进行单元测试
|
||||||
|
4. **文档完善**: 可以添加更详细的API文档和示例代码
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
# 信号处理JNI封装使用说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目已将C++信号处理功能封装成JNI接口,可以在Android应用中直接调用。主要功能包括:
|
||||||
|
|
||||||
|
- 各种数字滤波器(带通、低通、高通、陷波)
|
||||||
|
- 信号质量评估
|
||||||
|
- ECG信号质量指数计算
|
||||||
|
- 信号特征提取
|
||||||
|
- 信号预处理(归一化、去直流等)
|
||||||
|
- 实时信号处理
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/main/
|
||||||
|
├── cpp/
|
||||||
|
│ ├── include/cpp/
|
||||||
|
│ │ └── signal_processor.h # C++头文件
|
||||||
|
│ ├── src/
|
||||||
|
│ │ └── signal_processor.cpp # C++实现文件(需要修复乱码)
|
||||||
|
│ ├── jni/
|
||||||
|
│ │ └── jni_bridge.cpp # 统一的JNI桥接文件(包含所有JNI函数)
|
||||||
|
│ └── CMakeLists.txt # 构建配置
|
||||||
|
└── java/
|
||||||
|
└── com/example/cmake_project_test/
|
||||||
|
├── SignalProcessorJNI.kt # Java JNI接口类
|
||||||
|
└── SignalProcessorExample.kt # 使用示例类
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 基本使用
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 创建信号处理器实例
|
||||||
|
val signalProcessor = SignalProcessorJNI()
|
||||||
|
if (signalProcessor.createProcessor()) {
|
||||||
|
// 使用各种信号处理功能
|
||||||
|
val filteredSignal = signalProcessor.bandpassFilter(
|
||||||
|
inputSignal,
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
lowFreq = 40.0,
|
||||||
|
highFreq = 60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
signalProcessor.destroyProcessor()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 主要功能
|
||||||
|
|
||||||
|
#### 数字滤波
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 带通滤波
|
||||||
|
val bandpassResult = signalProcessor.bandpassFilter(
|
||||||
|
signal, sampleRate, lowFreq, highFreq
|
||||||
|
)
|
||||||
|
|
||||||
|
// 低通滤波
|
||||||
|
val lowpassResult = signalProcessor.lowpassFilter(
|
||||||
|
signal, sampleRate, cutoffFreq
|
||||||
|
)
|
||||||
|
|
||||||
|
// 高通滤波
|
||||||
|
val highpassResult = signalProcessor.highpassFilter(
|
||||||
|
signal, sampleRate, cutoffFreq
|
||||||
|
)
|
||||||
|
|
||||||
|
// 陷波滤波(去除工频干扰)
|
||||||
|
val notchResult = signalProcessor.notchFilter(
|
||||||
|
signal, sampleRate, notchFreq, qFactor
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 信号质量评估
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 计算信号质量指数
|
||||||
|
val quality = signalProcessor.calculateSignalQuality(signal)
|
||||||
|
|
||||||
|
// 计算ECG信号质量指数
|
||||||
|
val ecgSQI = signalProcessor.calculateECGSQI(ecgSignal, sampleRate)
|
||||||
|
|
||||||
|
// 计算两个信号的相关性
|
||||||
|
val correlation = signalProcessor.calculateCorrelation(signal1, signal2)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 信号预处理
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 归一化信号幅度
|
||||||
|
signalProcessor.normalizeAmplitude(signal)
|
||||||
|
|
||||||
|
// 提取信号特征
|
||||||
|
val features = signalProcessor.extractFeatures(signal, sampleRate)
|
||||||
|
|
||||||
|
// 重置滤波器状态
|
||||||
|
signalProcessor.resetFilters()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 实时处理
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 实时处理数据块
|
||||||
|
val processedChunk = signalProcessor.processRealtimeChunk(
|
||||||
|
chunk, sampleRate
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 完整示例
|
||||||
|
|
||||||
|
参考 `SignalProcessorExample.kt` 文件,其中包含了所有功能的演示代码:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val example = SignalProcessorExample()
|
||||||
|
|
||||||
|
// 运行所有演示
|
||||||
|
example.runAllDemonstrations()
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
example.cleanup()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据格式
|
||||||
|
|
||||||
|
### 输入数据
|
||||||
|
|
||||||
|
- 所有信号数据使用 `FloatArray` 格式
|
||||||
|
- 采样率使用 `Double` 类型
|
||||||
|
- 频率参数使用 `Double` 类型
|
||||||
|
|
||||||
|
### 输出数据
|
||||||
|
|
||||||
|
- 滤波结果返回 `FloatArray?`(可能为null表示处理失败)
|
||||||
|
- 质量指数返回 `Float` 类型(0.0-1.0)
|
||||||
|
- 相关性返回 `Float` 类型(-1.0到1.0)
|
||||||
|
|
||||||
|
### 内部转换
|
||||||
|
|
||||||
|
- Java端使用 `ByteBuffer` 进行 `FloatArray` 和 `ByteArray` 的转换
|
||||||
|
- C++端使用 `std::vector<float>` 处理数据
|
||||||
|
- 字节序使用小端序(Little Endian)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 1. 资源管理
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 必须在使用前创建处理器
|
||||||
|
if (!signalProcessor.createProcessor()) {
|
||||||
|
// 处理创建失败的情况
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用完毕后必须销毁处理器
|
||||||
|
signalProcessor.destroyProcessor()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 错误处理
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val result = signalProcessor.bandpassFilter(signal, sampleRate, lowFreq, highFreq)
|
||||||
|
if (result != null) {
|
||||||
|
// 处理成功
|
||||||
|
processResult(result)
|
||||||
|
} else {
|
||||||
|
// 处理失败
|
||||||
|
Log.e("SignalProcessor", "滤波处理失败")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 性能考虑
|
||||||
|
|
||||||
|
- 滤波器初始化有一定开销,建议复用处理器实例
|
||||||
|
- 大数据量处理时考虑分块处理
|
||||||
|
- 实时处理时注意内存分配
|
||||||
|
|
||||||
|
### 4. 线程安全
|
||||||
|
|
||||||
|
- JNI函数不是线程安全的
|
||||||
|
- 多线程使用时需要适当的同步机制
|
||||||
|
- 建议在主线程或专用工作线程中使用
|
||||||
|
|
||||||
|
## 构建配置
|
||||||
|
|
||||||
|
### CMakeLists.txt
|
||||||
|
|
||||||
|
确保在 `CMakeLists.txt` 中包含了信号处理库:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# Signal processor static library
|
||||||
|
add_library(signal_processor STATIC
|
||||||
|
src/signal_processor.cpp)
|
||||||
|
|
||||||
|
target_include_directories(signal_processor PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
# 链接到主库
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||||
|
core_math
|
||||||
|
data_parser
|
||||||
|
signal_processor
|
||||||
|
android
|
||||||
|
log)
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:信号处理的JNI函数现在已整合到 `jni_bridge.cpp` 中,不再需要单独的 `signal_processor_jni.cpp` 文件。
|
||||||
|
|
||||||
|
### 库加载
|
||||||
|
|
||||||
|
在Java代码中确保正确加载原生库:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("cmake_project_test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 1. 编译错误
|
||||||
|
|
||||||
|
- 检查 `CMakeLists.txt` 配置
|
||||||
|
- 确保所有源文件路径正确
|
||||||
|
- 检查头文件包含路径
|
||||||
|
|
||||||
|
### 2. 运行时错误
|
||||||
|
|
||||||
|
- 检查Logcat中的错误信息
|
||||||
|
- 确保处理器已正确创建
|
||||||
|
- 检查输入数据格式和大小
|
||||||
|
|
||||||
|
### 3. 性能问题
|
||||||
|
|
||||||
|
- 使用Android Profiler分析性能瓶颈
|
||||||
|
- 考虑减少数据转换开销
|
||||||
|
- 优化滤波器参数
|
||||||
|
|
||||||
|
## 扩展功能
|
||||||
|
|
||||||
|
### 1. 添加新的滤波器
|
||||||
|
|
||||||
|
1. 在 `signal_processor.h` 中声明新方法
|
||||||
|
2. 在 `signal_processor.cpp` 中实现
|
||||||
|
3. 在 `signal_processor_jni.cpp` 中添加JNI封装
|
||||||
|
4. 在 `SignalProcessorJNI.kt` 中添加Java接口
|
||||||
|
|
||||||
|
### 2. 自定义信号处理
|
||||||
|
|
||||||
|
可以基于现有的JNI接口构建更高级的信号处理功能:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class AdvancedSignalProcessor(private val jni: SignalProcessorJNI) {
|
||||||
|
fun adaptiveFilter(signal: FloatArray, sampleRate: Double): FloatArray? {
|
||||||
|
// 实现自适应滤波算法
|
||||||
|
// 使用JNI接口调用底层功能
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
通过JNI封装,您现在可以在Android应用中直接使用C++的高性能信号处理功能。这种架构既保持了C++的性能优势,又提供了Java/Kotlin的易用性。
|
||||||
|
|
||||||
|
建议在使用前先运行示例代码,熟悉各种功能的使用方法,然后根据具体需求进行定制化开发。
|
||||||
|
|
@ -32,11 +32,11 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -39,6 +39,27 @@ add_library(data_parser STATIC
|
||||||
target_include_directories(data_parser PUBLIC
|
target_include_directories(data_parser PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
# Signal processor static library
|
||||||
|
add_library(signal_processor STATIC
|
||||||
|
src/signal_processor.cpp)
|
||||||
|
|
||||||
|
target_include_directories(signal_processor PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
# Data mapper static library
|
||||||
|
add_library(data_mapper STATIC
|
||||||
|
src/data_mapper.cpp)
|
||||||
|
|
||||||
|
target_include_directories(data_mapper PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
# Indicator calculator static library
|
||||||
|
add_library(indicator_calculator STATIC
|
||||||
|
src/indicator_cal.cpp)
|
||||||
|
|
||||||
|
target_include_directories(indicator_calculator PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
# JNI shared library: expose only JNI bridge, link against core
|
# JNI shared library: expose only JNI bridge, link against core
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
jni/jni_bridge.cpp)
|
jni/jni_bridge.cpp)
|
||||||
|
|
@ -49,5 +70,8 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||||
core_math
|
core_math
|
||||||
data_parser
|
data_parser
|
||||||
|
signal_processor
|
||||||
|
data_mapper
|
||||||
|
indicator_calculator
|
||||||
android
|
android
|
||||||
log)
|
log)
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
#ifndef _DATA_MAPPER_H
|
||||||
|
#define _DATA_MAPPER_H
|
||||||
|
|
||||||
|
#include "cpp/data_praser.h"
|
||||||
|
#include <jni.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <variant>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
// 数据映射器类
|
||||||
|
class Mapper
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// 私有成员变量(如果需要的话)
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 构造函数和析构函数
|
||||||
|
Mapper();
|
||||||
|
~Mapper();
|
||||||
|
|
||||||
|
// 主要映射函数
|
||||||
|
SensorData DataMapper(SensorData& data);
|
||||||
|
|
||||||
|
// 各种设备类型的映射函数
|
||||||
|
SensorData EEG_Data_Mapper(SensorData& data);
|
||||||
|
SensorData ECG_2LEAD_Data_Mapper(SensorData& data);
|
||||||
|
SensorData ECG_12LEAD_Data_Mapper(SensorData& data);
|
||||||
|
SensorData PPG_Data_Mapper(SensorData& data);
|
||||||
|
SensorData Respiration_Data_Mapper(SensorData& data);
|
||||||
|
SensorData Snore_Data_Mapper(SensorData& data);
|
||||||
|
SensorData Stethoscope_Data_Mapper(SensorData& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// JNI函数声明
|
||||||
|
extern "C" {
|
||||||
|
// 创建数据映射器实例
|
||||||
|
JNIEXPORT jlong JNICALL Java_com_example_cmake_1project_1test_DataMapper_createDataMapper(JNIEnv* env, jobject thiz);
|
||||||
|
|
||||||
|
// 销毁数据映射器实例
|
||||||
|
JNIEXPORT void JNICALL Java_com_example_cmake_1project_1test_DataMapper_destroyDataMapper(JNIEnv* env, jobject thiz, jlong mapperHandle);
|
||||||
|
|
||||||
|
// 映射单个数据包
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_DataMapper_mapSensorData(JNIEnv* env, jobject thiz, jlong mapperHandle, jobject sensorData);
|
||||||
|
|
||||||
|
// 批量映射数据包
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_DataMapper_mapSensorDataList(JNIEnv* env, jobject thiz, jlong mapperHandle, jobject sensorDataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数声明
|
||||||
|
jobject convertSensorDataToJava(JNIEnv* env, const SensorData& sensorData);
|
||||||
|
std::vector<SensorData> convertJavaSensorDataListToVector(JNIEnv* env, jobject sensorDataList);
|
||||||
|
jobject convertSensorDataVectorToJavaList(JNIEnv* env, const std::vector<SensorData>& sensorDataVector);
|
||||||
|
SensorData convertJavaSensorDataToCpp(JNIEnv* env, jobject javaSensorData);
|
||||||
|
std::vector<float> convertJavaFloatListToVector(JNIEnv* env, jobject floatList);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -87,6 +87,9 @@ SensorData parse_respiration(const uint8_t* data);
|
||||||
// 统一解析入口函数 - 支持多个包头和数据包格式
|
// 统一解析入口函数 - 支持多个包头和数据包格式
|
||||||
std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data);
|
std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data);
|
||||||
|
|
||||||
|
// CRC16校验函数
|
||||||
|
uint16_t calculate_crc16(const uint8_t* data, size_t length);
|
||||||
|
|
||||||
// 工具函数:将数值转换为十六进制字符串
|
// 工具函数:将数值转换为十六进制字符串
|
||||||
std::string to_hex_string(uint16_t value);
|
std::string to_hex_string(uint16_t value);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
#ifndef _INDICATOR_CAL_H
|
||||||
|
#define _INDICATOR_CAL_H
|
||||||
|
|
||||||
|
#include "cpp/data_praser.h"
|
||||||
|
#include <jni.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// 指标计算器类
|
||||||
|
class MetricsCalculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 构造函数和析构函数
|
||||||
|
MetricsCalculator();
|
||||||
|
~MetricsCalculator();
|
||||||
|
|
||||||
|
// ECG相关指标
|
||||||
|
float calculate_heart_rate_ecg(const SensorData& ecg_signal, float sample_rate);
|
||||||
|
float calculate_t_wave_amplitude(const std::vector<float>& ecg_signal);
|
||||||
|
float calculate_qrs_width(const std::vector<float>& ecg_signal, float sample_rate);
|
||||||
|
float calculate_st_offset(const std::vector<float>& ecg_signal, float sample_rate);
|
||||||
|
|
||||||
|
// PPG相关指标
|
||||||
|
float calculate_heart_rate_ppg(const std::vector<float>& ppg_signal, float sample_rate);
|
||||||
|
float calculate_spo2(const SensorData& ppg_data);
|
||||||
|
float calculate_perfusion_index(const SensorData& ppg_data);
|
||||||
|
float calculate_pulse_width(const std::vector<float>& ppg_signal);
|
||||||
|
float calculate_amplitude_ratio(const SensorData& ppg_data);
|
||||||
|
|
||||||
|
// HRV相关指标
|
||||||
|
float calculate_sdnn(const std::vector<float>& rr_intervals);
|
||||||
|
float calculate_rmssd(const std::vector<float>& rr_intervals);
|
||||||
|
float calculate_pnn50(const std::vector<float>& rr_intervals);
|
||||||
|
float calculate_triangular_index(const std::vector<float>& rr_intervals);
|
||||||
|
|
||||||
|
// 信号处理
|
||||||
|
std::vector<float> detect_r_peaks(const std::vector<float>& ecg_signal, float sample_rate);
|
||||||
|
std::vector<float> detect_pulse_peaks(const std::vector<float>& ppg_signal, float sample_rate);
|
||||||
|
float calculate_signal_quality(const std::vector<float>& signal);
|
||||||
|
|
||||||
|
// 综合指标计算
|
||||||
|
std::map<std::string, float> calculate_all_ecg_metrics(const SensorData& ecg_data, float sample_rate);
|
||||||
|
std::map<std::string, float> calculate_all_ppg_metrics(const SensorData& ppg_data, float sample_rate);
|
||||||
|
std::map<std::string, float> calculate_all_hrv_metrics(const std::vector<float>& rr_intervals);
|
||||||
|
|
||||||
|
// 完整数据处理流程
|
||||||
|
std::map<std::string, float> process_complete_pipeline(const SensorData& raw_data, float sample_rate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// JNI函数声明
|
||||||
|
extern "C" {
|
||||||
|
// 创建指标计算器实例
|
||||||
|
JNIEXPORT jlong JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_createIndicatorCalculator(JNIEnv* env, jobject thiz);
|
||||||
|
|
||||||
|
// 销毁指标计算器实例
|
||||||
|
JNIEXPORT void JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_destroyIndicatorCalculator(JNIEnv* env, jobject thiz, jlong calculatorHandle);
|
||||||
|
|
||||||
|
// 完整数据处理流程
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_processCompletePipeline(JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate);
|
||||||
|
|
||||||
|
// 单独指标计算
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_calculateECGMetrics(JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate);
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_calculatePPGMetrics(JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate);
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_example_cmake_1project_1test_IndicatorCalculator_calculateHRVMetrics(JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject rrIntervals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数声明
|
||||||
|
jobject convertMetricsMapToJava(JNIEnv* env, const std::map<std::string, float>& metrics);
|
||||||
|
std::vector<float> convertJavaFloatListToVector(JNIEnv* env, jobject floatList);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
#ifndef _SIGNAL_PROCESSOR_H_
|
||||||
|
#define _SIGNAL_PROCESSOR_H_
|
||||||
|
#include "data_praser.h"
|
||||||
|
#include <variant>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
// 滤波类型
|
||||||
|
enum class filtertype
|
||||||
|
{
|
||||||
|
lowpass,highpass,notchpass,bandpass,bandstop
|
||||||
|
};
|
||||||
|
// 特征数据结构
|
||||||
|
struct ECGChannelFeatures {
|
||||||
|
std::vector<int> r_peaks; // R波位置
|
||||||
|
float sdnn = 0.0f; // RR间期标准差
|
||||||
|
float rmssd = 0.0f; // RR间期差值的均方根
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FeatureSet {
|
||||||
|
std::vector<ECGChannelFeatures> ecg_features;
|
||||||
|
// 其他设备的特征结构...
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生理指标数据结构
|
||||||
|
struct ECGIndicators {
|
||||||
|
float heart_rate = 0.0f; // 心率 (bpm)
|
||||||
|
float sdnn = 0.0f; // 心率变异性指标
|
||||||
|
float rmssd = 0.0f; // 心率变异性指标
|
||||||
|
float st_elevation = 0.0f; // ST段抬高 (mV)
|
||||||
|
float st_depression = 0.0f; // ST段压低 (mV)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PPGIndicators {
|
||||||
|
float spo2 = 0.0f; // 血氧饱和度
|
||||||
|
float pi = 0.0f; // 灌注指数
|
||||||
|
float pulse_rate = 0.0f; // 脉率
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EEGIndicators {
|
||||||
|
float alpha_power = 0.0f; // Alpha波功率
|
||||||
|
float beta_power = 0.0f; // Beta波功率
|
||||||
|
float attention = 0.0f; // 注意力指数
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndicatorSet {
|
||||||
|
float sqi = 0.0f; // 信号质量指数 (0.0-1.0)
|
||||||
|
std::vector<ECGIndicators> ecg_indicators;
|
||||||
|
std::vector<PPGIndicators> ppg_indicators;
|
||||||
|
std::vector<EEGIndicators> eeg_indicators;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滤波器配置
|
||||||
|
struct FilterConfig {
|
||||||
|
std::string type;
|
||||||
|
double cutoff_frequency = 0.0;
|
||||||
|
double Q_factor = 0.0;
|
||||||
|
double low_cutoff = 0.0;
|
||||||
|
double high_cutoff = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SignalProcessor {
|
||||||
|
public:
|
||||||
|
// 预处理信号
|
||||||
|
SensorData preprocess_signals(const SensorData& raw_data); // 使用DataType枚举
|
||||||
|
SensorData preprocess_generic(const SensorData& data);
|
||||||
|
// 新增:简化版通道级滤波处理(测试版本)
|
||||||
|
std::vector<SensorData> process_channel_based_filtering_simple(
|
||||||
|
const std::vector<SensorData>& data_packets);
|
||||||
|
|
||||||
|
// 特征提取
|
||||||
|
FeatureSet extract_signal_features(const SensorData& processed_data,
|
||||||
|
const std::vector<std::string>& features = {});
|
||||||
|
|
||||||
|
// 计算生理指标
|
||||||
|
IndicatorSet compute_physiological_indicators(const FeatureSet& features,
|
||||||
|
const std::vector<std::string>& indicators = {});
|
||||||
|
|
||||||
|
// 获取处理流水线配置
|
||||||
|
std::string getProcessingPipeline(DataType device_type); // 使用DataType枚举
|
||||||
|
|
||||||
|
// 设置滤波器参数
|
||||||
|
void setFilterParams(const std::string& filter_type, const FilterConfig& params);
|
||||||
|
|
||||||
|
|
||||||
|
// 修改为public访问权限
|
||||||
|
public:
|
||||||
|
SensorData preprocess_ecg_12lead(const SensorData& data);
|
||||||
|
SensorData preprocess_eeg(const SensorData& data);
|
||||||
|
SensorData preprocess_ecg_2lead(const SensorData& data);
|
||||||
|
SensorData preprocess_ppg(const SensorData& data);
|
||||||
|
SensorData preprocess_respiration(const SensorData& data);
|
||||||
|
SensorData preprocess_snore(const SensorData& data);
|
||||||
|
SensorData preprocess_stethoscope(const SensorData& data);
|
||||||
|
|
||||||
|
// 设备特定的特征提取
|
||||||
|
FeatureSet extract_eeg_features(const SensorData& data);
|
||||||
|
FeatureSet extract_ecg_features(const SensorData& data);
|
||||||
|
FeatureSet extract_ppg_features(const SensorData& data);
|
||||||
|
FeatureSet extract_respiration_features(const SensorData& data);
|
||||||
|
FeatureSet extract_snore_features(const SensorData& data);
|
||||||
|
FeatureSet extract_stethoscope_features(const SensorData& data);
|
||||||
|
|
||||||
|
// 滤波器配置
|
||||||
|
std::map<std::string, FilterConfig> filter_configs_;
|
||||||
|
|
||||||
|
// 滤波器状态
|
||||||
|
struct FilterState {
|
||||||
|
std::vector<double> x_history;
|
||||||
|
std::vector<double> y_history;
|
||||||
|
};
|
||||||
|
std::map<std::string, FilterState> filter_states_;
|
||||||
|
|
||||||
|
// 在SignalProcessor类中添加
|
||||||
|
public:
|
||||||
|
// 带通滤波器实现
|
||||||
|
std::vector<float> bandpass_filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double low_cutoff,
|
||||||
|
double high_cutoff);
|
||||||
|
std::vector<float> Lowpass_filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double low_cutoff) ;
|
||||||
|
|
||||||
|
std::vector<float> filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double low_cutoff,
|
||||||
|
double high_cutoff,
|
||||||
|
filtertype type);
|
||||||
|
std::vector<float> Highpass_filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double high_cutoff);
|
||||||
|
std::vector<float> compensate_motion_artifact(const std::vector<float>& ppg,
|
||||||
|
const std::vector<float>& motion);
|
||||||
|
std::vector<float> compensate_eog_artifact(const std::vector<float>& eeg,
|
||||||
|
const std::vector<float>& eog1,
|
||||||
|
const std::vector<float>& eog2) ;
|
||||||
|
// 添加自适应陷波滤波器声明
|
||||||
|
std::vector<float> adaptive_notch_filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double target_freq,
|
||||||
|
double bandwidth) ;
|
||||||
|
std::vector<float> bandstop_filter(const std::vector<float>& input,
|
||||||
|
double sample_rate,
|
||||||
|
double low_cutoff,
|
||||||
|
double high_cutoff);
|
||||||
|
void normalize_amplitude(std::vector<float>& signal) ;
|
||||||
|
|
||||||
|
// 添加通用预处理辅助函数
|
||||||
|
std::vector<float> remove_dc_offset(const std::vector<float>& signal);
|
||||||
|
std::vector<float> apply_gain(const std::vector<float>& signal, float gain);
|
||||||
|
float calculate_correlation(const std::vector<float>& x, const std::vector<float>& y);
|
||||||
|
float calculate_snr(const std::vector<float>& signal);
|
||||||
|
float calculate_PPG_sqi(const std::vector<float>& red_channel,
|
||||||
|
const std::vector<float>& ir_channel);
|
||||||
|
float calculate_ecg_sqi(const std::vector<float>& signal, double sample_rate);
|
||||||
|
std::vector<float> remove_motion_artifacts(const std::vector<float>& signal, double sample_rate);
|
||||||
|
|
||||||
|
// 新增:通道级滤波辅助方法
|
||||||
|
private:
|
||||||
|
std::vector<std::vector<float>> apply_channel_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels, DataType data_type);
|
||||||
|
std::vector<std::vector<float>> apply_eeg_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
std::vector<std::vector<float>> apply_ecg_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
std::vector<std::vector<float>> apply_ppg_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
std::vector<std::vector<float>> apply_respiration_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
std::vector<std::vector<float>> apply_snore_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
std::vector<std::vector<float>> apply_stethoscope_filters(
|
||||||
|
const std::vector<std::vector<float>>& channels);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <cstring>
|
||||||
#include "../include/cpp/add.h"
|
#include "../include/cpp/add.h"
|
||||||
#include "../include/cpp/data_praser.h"
|
#include "../include/cpp/data_praser.h"
|
||||||
|
#include "../include/cpp/signal_processor.h"
|
||||||
|
#include "../include/cpp/data_mapper.h"
|
||||||
|
#include "../include/cpp/indicator_cal.h"
|
||||||
|
|
||||||
using core::math::add;
|
using core::math::add;
|
||||||
|
|
||||||
|
|
@ -88,8 +93,12 @@ Java_com_example_cmake_1project_1test_MainActivity_destroyStreamParser(
|
||||||
jobject /* this */,
|
jobject /* this */,
|
||||||
jlong handle) {
|
jlong handle) {
|
||||||
(void)env;
|
(void)env;
|
||||||
|
if (handle == 0) return; // 避免删除空指针
|
||||||
|
|
||||||
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
||||||
delete parser;
|
if (parser) {
|
||||||
|
delete parser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加一段字节流数据
|
// 追加一段字节流数据
|
||||||
|
|
@ -99,8 +108,10 @@ Java_com_example_cmake_1project_1test_MainActivity_streamParserAppend(
|
||||||
jobject /* this */,
|
jobject /* this */,
|
||||||
jlong handle,
|
jlong handle,
|
||||||
jbyteArray chunk) {
|
jbyteArray chunk) {
|
||||||
|
if (handle == 0 || !chunk) return; // 检查句柄和输入
|
||||||
|
|
||||||
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
||||||
if (!parser || !chunk) return;
|
if (!parser) return;
|
||||||
|
|
||||||
jsize len = env->GetArrayLength(chunk);
|
jsize len = env->GetArrayLength(chunk);
|
||||||
if (len <= 0) return;
|
if (len <= 0) return;
|
||||||
|
|
@ -115,19 +126,323 @@ Java_com_example_cmake_1project_1test_MainActivity_streamParserDrainPackets(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject /* this */,
|
jobject /* this */,
|
||||||
jlong handle) {
|
jlong handle) {
|
||||||
|
if (handle == 0) return nullptr; // 检查句柄
|
||||||
|
|
||||||
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
auto* parser = reinterpret_cast<StreamParser*>(handle);
|
||||||
if (!parser) return nullptr;
|
if (!parser) return nullptr;
|
||||||
std::vector<SensorData> packets = parser->getAllPackets();
|
|
||||||
return convertSensorDataVectorToJavaList(env, packets);
|
try {
|
||||||
|
std::vector<SensorData> packets = parser->getAllPackets();
|
||||||
|
return convertSensorDataVectorToJavaList(env, packets);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "StreamParser", "Exception in drainPackets: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
} catch (...) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "StreamParser", "Unknown exception in drainPackets");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================= 信号处理 JNI 接口 =================
|
||||||
|
|
||||||
|
// 全局变量存储SignalProcessor实例
|
||||||
|
static std::map<jlong, std::unique_ptr<SignalProcessor>> processor_instances;
|
||||||
|
static jlong next_processor_id = 1;
|
||||||
|
|
||||||
|
// 辅助函数:将Java字节数组转换为C++向量
|
||||||
|
std::vector<float> java_byte_array_to_vector(JNIEnv* env, jbyteArray java_array) {
|
||||||
|
if (java_array == nullptr) return {};
|
||||||
|
|
||||||
|
jsize length = env->GetArrayLength(java_array);
|
||||||
|
jbyte* bytes = env->GetByteArrayElements(java_array, nullptr);
|
||||||
|
|
||||||
|
std::vector<float> result;
|
||||||
|
result.reserve(length / sizeof(float));
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i += sizeof(float)) {
|
||||||
|
float value;
|
||||||
|
memcpy(&value, &bytes[i], sizeof(float));
|
||||||
|
result.push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->ReleaseByteArrayElements(java_array, bytes, JNI_ABORT);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:将C++向量转换为Java字节数组
|
||||||
|
jbyteArray vector_to_java_byte_array(JNIEnv* env, const std::vector<float>& vec) {
|
||||||
|
jsize length = vec.size() * sizeof(float);
|
||||||
|
jbyteArray result = env->NewByteArray(length);
|
||||||
|
|
||||||
|
if (result != nullptr) {
|
||||||
|
jbyte* bytes = env->GetByteArrayElements(result, nullptr);
|
||||||
|
for (size_t i = 0; i < vec.size(); ++i) {
|
||||||
|
memcpy(&bytes[i * sizeof(float)], &vec[i], sizeof(float));
|
||||||
|
}
|
||||||
|
env->ReleaseByteArrayElements(result, bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:创建SignalProcessor实例
|
||||||
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_createSignalProcessor(JNIEnv* env, jobject thiz) {
|
||||||
|
try {
|
||||||
|
auto processor = std::make_unique<SignalProcessor>();
|
||||||
|
jlong id = next_processor_id++;
|
||||||
|
processor_instances[id] = std::move(processor);
|
||||||
|
return id;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Failed to create processor: %s", e.what());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:销毁SignalProcessor实例
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_destroySignalProcessor(JNIEnv* env, jobject thiz, jlong processor_id) {
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it != processor_instances.end()) {
|
||||||
|
processor_instances.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:带通滤波
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_bandpassFilter(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate, jdouble low_freq, jdouble high_freq) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
std::vector<float> result = it->second->bandpass_filter(signal_vec, sample_rate, low_freq, high_freq);
|
||||||
|
return vector_to_java_byte_array(env, result);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Bandpass filter error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:低通滤波
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_lowpassFilter(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate, jdouble cutoff_freq) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
std::vector<float> result = it->second->Lowpass_filter(signal_vec, sample_rate, cutoff_freq);
|
||||||
|
return vector_to_java_byte_array(env, result);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Lowpass filter error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:高通滤波
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_highpassFilter(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate, jdouble cutoff_freq) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
std::vector<float> result = it->second->Highpass_filter(signal_vec, sample_rate, cutoff_freq);
|
||||||
|
return vector_to_java_byte_array(env, result);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Highpass filter error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:陷波滤波
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_notchFilter(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate, jdouble notch_freq, jdouble q_factor) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
// 使用自适应陷波滤波器
|
||||||
|
std::vector<float> result = it->second->adaptive_notch_filter(signal_vec, sample_rate, notch_freq, q_factor);
|
||||||
|
return vector_to_java_byte_array(env, result);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Notch filter error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:计算信号质量
|
||||||
|
extern "C" JNIEXPORT jfloat JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_calculateSignalQuality(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id, jbyteArray signal) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return 0.0f;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
// 使用SNR作为信号质量指标
|
||||||
|
return it->second->calculate_snr(signal_vec);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Signal quality calculation error: %s", e.what());
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:计算ECG信号质量指数
|
||||||
|
extern "C" JNIEXPORT jfloat JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_calculateECGSQI(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return 0.0f;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
return it->second->calculate_ecg_sqi(signal_vec, sample_rate);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "ECG SQI calculation error: %s", e.what());
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:计算相关性
|
||||||
|
extern "C" JNIEXPORT jfloat JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_calculateCorrelation(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray x, jbyteArray y) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return 0.0f;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> x_vec = java_byte_array_to_vector(env, x);
|
||||||
|
std::vector<float> y_vec = java_byte_array_to_vector(env, y);
|
||||||
|
return it->second->calculate_correlation(x_vec, y_vec);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Correlation calculation error: %s", e.what());
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:归一化幅度
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_normalizeAmplitude(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id, jbyteArray signal) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
it->second->normalize_amplitude(signal_vec);
|
||||||
|
|
||||||
|
// 将处理后的数据写回Java数组
|
||||||
|
jsize length = env->GetArrayLength(signal);
|
||||||
|
jbyte* bytes = env->GetByteArrayElements(signal, nullptr);
|
||||||
|
for (size_t i = 0; i < signal_vec.size(); ++i) {
|
||||||
|
memcpy(&bytes[i * sizeof(float)], &signal_vec[i], sizeof(float));
|
||||||
|
}
|
||||||
|
env->ReleaseByteArrayElements(signal, bytes, 0);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Normalize amplitude error: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:提取特征
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_extractFeatures(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray signal, jdouble sample_rate) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> signal_vec = java_byte_array_to_vector(env, signal);
|
||||||
|
// 创建临时的SensorData对象来调用特征提取
|
||||||
|
SensorData temp_data;
|
||||||
|
temp_data.data_type = DataType::EEG; // 默认类型
|
||||||
|
temp_data.channel_data = std::vector<std::vector<float>>{signal_vec};
|
||||||
|
|
||||||
|
FeatureSet features = it->second->extract_signal_features(temp_data);
|
||||||
|
|
||||||
|
// 将特征转换为float数组(简化处理)
|
||||||
|
std::vector<float> feature_vector;
|
||||||
|
// 这里可以根据实际需要提取具体的特征值
|
||||||
|
// 暂时返回原始信号作为特征
|
||||||
|
feature_vector = signal_vec;
|
||||||
|
|
||||||
|
return vector_to_java_byte_array(env, feature_vector);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Feature extraction error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:重置滤波器
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_resetFilters(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 清除滤波器状态
|
||||||
|
it->second->filter_states_.clear();
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "SignalProcessor", "Filters reset successfully");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Reset filters error: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI函数:实时处理数据块
|
||||||
|
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_SignalProcessorJNI_processRealtimeChunk(
|
||||||
|
JNIEnv* env, jobject thiz, jlong processor_id,
|
||||||
|
jbyteArray chunk, jdouble sample_rate) {
|
||||||
|
|
||||||
|
auto it = processor_instances.find(processor_id);
|
||||||
|
if (it == processor_instances.end()) return nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<float> chunk_vec = java_byte_array_to_vector(env, chunk);
|
||||||
|
|
||||||
|
// 应用基本的预处理
|
||||||
|
std::vector<float> processed = it->second->remove_dc_offset(chunk_vec);
|
||||||
|
processed = it->second->remove_motion_artifacts(processed, sample_rate);
|
||||||
|
|
||||||
|
return vector_to_java_byte_array(env, processed);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "SignalProcessor", "Real-time processing error: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 便捷方法:从整块数据按固定块大小模拟流式解析,返回解析到的所有包
|
// 便捷方法:从整块数据按固定块大小模拟流式解析,返回解析到的所有包
|
||||||
extern "C" JNIEXPORT jobject JNICALL
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
Java_com_example_cmake_1project_1test_MainActivity_parseStreamFromBytes(
|
Java_com_example_cmake_1project_1test_MainActivity_parseStreamFromBytes(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject /* this */,
|
jobject /* this */,
|
||||||
jbyteArray data,
|
jbyteArray data,
|
||||||
jint chunkSize) {
|
jint chunkSize) {
|
||||||
if (data == nullptr || chunkSize <= 0) return nullptr;
|
if (data == nullptr || chunkSize <= 0) return nullptr;
|
||||||
|
|
||||||
std::vector<uint8_t> src = convertJavaByteArrayToVector(env, data);
|
std::vector<uint8_t> src = convertJavaByteArrayToVector(env, data);
|
||||||
|
|
@ -143,4 +458,512 @@ Java_com_example_cmake_1project_1test_MainActivity_parseStreamFromBytes(
|
||||||
return convertSensorDataVectorToJavaList(env, packets);
|
return convertSensorDataVectorToJavaList(env, packets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DataMapper JNI函数实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 创建数据映射器实例
|
||||||
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_DataMapper_createDataMapper(
|
||||||
|
JNIEnv* env, jobject thiz) {
|
||||||
|
try {
|
||||||
|
Mapper* mapper = new Mapper();
|
||||||
|
jlong mapperId = reinterpret_cast<jlong>(mapper);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Created DataMapper instance with ID: %lld", mapperId);
|
||||||
|
return mapperId;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Failed to create DataMapper: %s", e.what());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁数据映射器实例
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_DataMapper_destroyDataMapper(
|
||||||
|
JNIEnv* env, jobject thiz, jlong mapperHandle) {
|
||||||
|
try {
|
||||||
|
if (mapperHandle != 0) {
|
||||||
|
Mapper* mapper = reinterpret_cast<Mapper*>(mapperHandle);
|
||||||
|
delete mapper;
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Destroyed DataMapper instance with ID: %lld", mapperHandle);
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Attempted to destroy null DataMapper handle");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Failed to destroy DataMapper: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射单个数据包
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_DataMapper_mapSensorData(
|
||||||
|
JNIEnv* env, jobject thiz, jlong mapperHandle, jobject sensorData) {
|
||||||
|
try {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== JNI mapSensorData被调用 ===");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "mapperHandle: %ld", mapperHandle);
|
||||||
|
|
||||||
|
if (mapperHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Invalid mapper handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mapper* mapper = reinterpret_cast<Mapper*>(mapperHandle);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Mapper指针获取成功");
|
||||||
|
|
||||||
|
// 将Java的SensorData转换为C++的SensorData
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "开始转换Java SensorData到C++");
|
||||||
|
SensorData cppSensorData = convertJavaSensorDataToCpp(env, sensorData);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Java到C++转换完成");
|
||||||
|
|
||||||
|
// 执行映射
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "开始调用C++ DataMapper函数");
|
||||||
|
SensorData mappedData = mapper->DataMapper(cppSensorData);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "C++ DataMapper函数调用完成");
|
||||||
|
|
||||||
|
// 将C++的SensorData转换回Java对象
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "开始转换C++ SensorData到Java");
|
||||||
|
jobject result = convertSensorDataToJava(env, mappedData);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "C++到Java转换完成");
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Successfully mapped sensor data");
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Failed to map sensor data: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量映射数据包
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_DataMapper_mapSensorDataList(
|
||||||
|
JNIEnv* env, jobject thiz, jlong mapperHandle, jobject sensorDataList) {
|
||||||
|
try {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== JNI mapSensorDataList被调用 ===");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "mapperHandle: %lld", mapperHandle);
|
||||||
|
|
||||||
|
if (mapperHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Invalid mapper handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mapper* mapper = reinterpret_cast<Mapper*>(mapperHandle);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Mapper指针获取成功");
|
||||||
|
|
||||||
|
// 将Java的List<SensorData>转换为C++的vector<SensorData>
|
||||||
|
std::vector<SensorData> cppSensorDataList = convertJavaSensorDataListToVector(env, sensorDataList);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "转换后的数据包数量: %zu", cppSensorDataList.size());
|
||||||
|
|
||||||
|
// 批量映射
|
||||||
|
std::vector<SensorData> mappedDataList;
|
||||||
|
mappedDataList.reserve(cppSensorDataList.size());
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "开始批量映射,数据包数量: %zu", cppSensorDataList.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cppSensorDataList.size(); ++i) {
|
||||||
|
try {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "处理第 %zu 个数据包", i + 1);
|
||||||
|
SensorData mappedData = mapper->DataMapper(cppSensorDataList[i]);
|
||||||
|
mappedDataList.push_back(mappedData);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "第 %zu 个数据包映射完成", i + 1);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Failed to map individual sensor data: %s", e.what());
|
||||||
|
// 如果单个数据包映射失败,保留原始数据
|
||||||
|
mappedDataList.push_back(cppSensorDataList[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将C++的vector<SensorData>转换回Java的List
|
||||||
|
jobject result = convertSensorDataVectorToJavaList(env, mappedDataList);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "Successfully mapped %zu sensor data packets", mappedDataList.size());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Failed to map sensor data list: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 辅助函数实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 将Java的SensorData转换为C++的SensorData
|
||||||
|
SensorData convertJavaSensorDataToCpp(JNIEnv* env, jobject javaSensorData) {
|
||||||
|
SensorData cppSensorData;
|
||||||
|
|
||||||
|
// 获取Java类的引用
|
||||||
|
jclass sensorDataClass = env->GetObjectClass(javaSensorData);
|
||||||
|
|
||||||
|
// 获取字段ID
|
||||||
|
jfieldID dataTypeField = env->GetFieldID(sensorDataClass, "dataType", "Ltype/SensorData$DataType;");
|
||||||
|
jfieldID packetSnField = env->GetFieldID(sensorDataClass, "packetSn", "I");
|
||||||
|
jfieldID leadStatusField = env->GetFieldID(sensorDataClass, "leadStatus", "[B");
|
||||||
|
jfieldID timestampField = env->GetFieldID(sensorDataClass, "timestamp", "J");
|
||||||
|
jfieldID channelDataField = env->GetFieldID(sensorDataClass, "channelData", "Ljava/util/List;");
|
||||||
|
jfieldID additionalDataField = env->GetFieldID(sensorDataClass, "additionalData", "Ltype/SensorData$AdditionalData;");
|
||||||
|
jfieldID rawDataField = env->GetFieldID(sensorDataClass, "rawData", "[B");
|
||||||
|
|
||||||
|
// 读取基本字段
|
||||||
|
cppSensorData.packet_sn = env->GetIntField(javaSensorData, packetSnField);
|
||||||
|
cppSensorData.timestamp = env->GetLongField(javaSensorData, timestampField);
|
||||||
|
|
||||||
|
// 读取数据类型
|
||||||
|
jobject dataTypeObj = env->GetObjectField(javaSensorData, dataTypeField);
|
||||||
|
if (dataTypeObj != nullptr) {
|
||||||
|
jclass dataTypeClass = env->GetObjectClass(dataTypeObj);
|
||||||
|
jmethodID nameMethod = env->GetMethodID(dataTypeClass, "name", "()Ljava/lang/String;");
|
||||||
|
jstring nameStr = (jstring)env->CallObjectMethod(dataTypeObj, nameMethod);
|
||||||
|
const char* name = env->GetStringUTFChars(nameStr, nullptr);
|
||||||
|
|
||||||
|
if (strcmp(name, "EEG") == 0) cppSensorData.data_type = DataType::EEG;
|
||||||
|
else if (strcmp(name, "ECG_2LEAD") == 0) cppSensorData.data_type = DataType::ECG_2LEAD;
|
||||||
|
else if (strcmp(name, "ECG_12LEAD") == 0) cppSensorData.data_type = DataType::ECG_12LEAD;
|
||||||
|
else if (strcmp(name, "PPG") == 0) cppSensorData.data_type = DataType::PPG;
|
||||||
|
else if (strcmp(name, "RESPIRATION") == 0) cppSensorData.data_type = DataType::RESPIRATION;
|
||||||
|
else if (strcmp(name, "SNORE") == 0) cppSensorData.data_type = DataType::SNORE;
|
||||||
|
else if (strcmp(name, "STETHOSCOPE") == 0) cppSensorData.data_type = DataType::STETHOSCOPE;
|
||||||
|
else cppSensorData.data_type = DataType::EEG; // 默认值
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(nameStr, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取导联状态
|
||||||
|
jbyteArray leadStatusArray = (jbyteArray)env->GetObjectField(javaSensorData, leadStatusField);
|
||||||
|
if (leadStatusArray != nullptr) {
|
||||||
|
jbyte* elements = env->GetByteArrayElements(leadStatusArray, nullptr);
|
||||||
|
std::memcpy(cppSensorData.lead_status.status, elements, 2);
|
||||||
|
env->ReleaseByteArrayElements(leadStatusArray, elements, JNI_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取通道数据
|
||||||
|
jobject channelDataList = env->GetObjectField(javaSensorData, channelDataField);
|
||||||
|
if (channelDataList != nullptr) {
|
||||||
|
jclass listClass = env->GetObjectClass(channelDataList);
|
||||||
|
jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I");
|
||||||
|
jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
|
||||||
|
|
||||||
|
jint listSize = env->CallIntMethod(channelDataList, sizeMethod);
|
||||||
|
|
||||||
|
if (listSize > 0) {
|
||||||
|
try {
|
||||||
|
// 检查第一个元素是List还是Float
|
||||||
|
jobject firstElement = env->CallObjectMethod(channelDataList, getMethod, 0);
|
||||||
|
if (firstElement != nullptr) {
|
||||||
|
jclass elementClass = env->GetObjectClass(firstElement);
|
||||||
|
jclass listClassRef = env->FindClass("java/util/List");
|
||||||
|
|
||||||
|
if (listClassRef != nullptr && env->IsInstanceOf(firstElement, listClassRef)) {
|
||||||
|
// 多通道数据 (List<List<Float>>)
|
||||||
|
std::vector<std::vector<float>> channels;
|
||||||
|
channels.reserve(listSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < listSize; i++) {
|
||||||
|
jobject channelList = env->CallObjectMethod(channelDataList, getMethod, i);
|
||||||
|
if (channelList != nullptr) {
|
||||||
|
std::vector<float> channel = convertJavaFloatListToVector(env, channelList);
|
||||||
|
channels.push_back(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cppSensorData.channel_data = channels;
|
||||||
|
} else {
|
||||||
|
// 单通道数据 (List<Float>)
|
||||||
|
std::vector<float> channel = convertJavaFloatListToVector(env, channelDataList);
|
||||||
|
cppSensorData.channel_data = channel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 第一个元素为空,假设是单通道数据
|
||||||
|
std::vector<float> channel = convertJavaFloatListToVector(env, channelDataList);
|
||||||
|
cppSensorData.channel_data = channel;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// 如果转换失败,创建一个空的通道数据
|
||||||
|
std::vector<float> emptyChannel;
|
||||||
|
cppSensorData.channel_data = emptyChannel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 空列表,创建空的通道数据
|
||||||
|
std::vector<float> emptyChannel;
|
||||||
|
cppSensorData.channel_data = emptyChannel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 通道数据为空,创建空的通道数据
|
||||||
|
std::vector<float> emptyChannel;
|
||||||
|
cppSensorData.channel_data = emptyChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取原始数据
|
||||||
|
jbyteArray rawDataArray = (jbyteArray)env->GetObjectField(javaSensorData, rawDataField);
|
||||||
|
if (rawDataArray != nullptr) {
|
||||||
|
jsize length = env->GetArrayLength(rawDataArray);
|
||||||
|
jbyte* elements = env->GetByteArrayElements(rawDataArray, nullptr);
|
||||||
|
cppSensorData.raw_data.assign(elements, elements + length);
|
||||||
|
env->ReleaseByteArrayElements(rawDataArray, elements, JNI_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cppSensorData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将Java的Float List转换为C++的vector<float>
|
||||||
|
std::vector<float> convertJavaFloatListToVector(JNIEnv* env, jobject floatList) {
|
||||||
|
std::vector<float> result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
jclass listClass = env->GetObjectClass(floatList);
|
||||||
|
jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I");
|
||||||
|
jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
|
||||||
|
|
||||||
|
jint listSize = env->CallIntMethod(floatList, sizeMethod);
|
||||||
|
result.reserve(listSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < listSize; i++) {
|
||||||
|
jobject floatObj = env->CallObjectMethod(floatList, getMethod, i);
|
||||||
|
if (floatObj != nullptr) {
|
||||||
|
// 尝试使用Float.floatValue()方法
|
||||||
|
jclass floatClass = env->GetObjectClass(floatObj);
|
||||||
|
jmethodID floatValueMethod = env->GetMethodID(floatClass, "floatValue", "()F");
|
||||||
|
if (floatValueMethod != nullptr) {
|
||||||
|
float value = env->CallFloatMethod(floatObj, floatValueMethod);
|
||||||
|
result.push_back(value);
|
||||||
|
} else {
|
||||||
|
// 如果floatValue方法不存在,尝试直接访问value字段
|
||||||
|
jfieldID valueField = env->GetFieldID(floatClass, "value", "F");
|
||||||
|
if (valueField != nullptr) {
|
||||||
|
float value = env->GetFloatField(floatObj, valueField);
|
||||||
|
result.push_back(value);
|
||||||
|
} else {
|
||||||
|
// 如果都失败了,使用默认值
|
||||||
|
result.push_back(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果对象为null,使用默认值
|
||||||
|
result.push_back(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// 如果转换失败,返回空向量
|
||||||
|
result.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将Java的List<SensorData>转换为C++的vector<SensorData>
|
||||||
|
std::vector<SensorData> convertJavaSensorDataListToVector(JNIEnv* env, jobject sensorDataList) {
|
||||||
|
std::vector<SensorData> result;
|
||||||
|
|
||||||
|
jclass listClass = env->GetObjectClass(sensorDataList);
|
||||||
|
jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I");
|
||||||
|
jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
|
||||||
|
|
||||||
|
jint listSize = env->CallIntMethod(sensorDataList, sizeMethod);
|
||||||
|
result.reserve(listSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < listSize; i++) {
|
||||||
|
jobject sensorDataObj = env->CallObjectMethod(sensorDataList, getMethod, i);
|
||||||
|
SensorData cppSensorData = convertJavaSensorDataToCpp(env, sensorDataObj);
|
||||||
|
result.push_back(cppSensorData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// IndicatorCalculator JNI函数实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 创建指标计算器实例
|
||||||
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_createIndicatorCalculator(
|
||||||
|
JNIEnv* env, jobject thiz) {
|
||||||
|
try {
|
||||||
|
MetricsCalculator* calculator = new MetricsCalculator();
|
||||||
|
jlong calculatorId = reinterpret_cast<jlong>(calculator);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Created IndicatorCalculator instance with ID: %lld", calculatorId);
|
||||||
|
return calculatorId;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to create IndicatorCalculator: %s", e.what());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁指标计算器实例
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_destroyIndicatorCalculator(
|
||||||
|
JNIEnv* env, jobject thiz, jlong calculatorHandle) {
|
||||||
|
try {
|
||||||
|
if (calculatorHandle != 0) {
|
||||||
|
MetricsCalculator* calculator = reinterpret_cast<MetricsCalculator*>(calculatorHandle);
|
||||||
|
delete calculator;
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Destroyed IndicatorCalculator instance with ID: %lld", calculatorHandle);
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "IndicatorCalculator", "Attempted to destroy null IndicatorCalculator handle");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to destroy IndicatorCalculator: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整数据处理流程
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_processCompletePipeline(
|
||||||
|
JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate) {
|
||||||
|
try {
|
||||||
|
if (calculatorHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Invalid calculator handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsCalculator* calculator = reinterpret_cast<MetricsCalculator*>(calculatorHandle);
|
||||||
|
|
||||||
|
// 将Java的SensorData转换为C++的SensorData
|
||||||
|
SensorData cppSensorData = convertJavaSensorDataToCpp(env, sensorData);
|
||||||
|
|
||||||
|
// 执行完整的数据处理流程
|
||||||
|
std::map<std::string, float> metrics = calculator->process_complete_pipeline(cppSensorData, sampleRate);
|
||||||
|
|
||||||
|
// 将C++的map转换为Java的Map
|
||||||
|
jobject result = convertMetricsMapToJava(env, metrics);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Successfully processed complete pipeline, calculated %zu metrics", metrics.size());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to process complete pipeline: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算ECG指标
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_calculateECGMetrics(
|
||||||
|
JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate) {
|
||||||
|
try {
|
||||||
|
if (calculatorHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Invalid calculator handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsCalculator* calculator = reinterpret_cast<MetricsCalculator*>(calculatorHandle);
|
||||||
|
|
||||||
|
// 将Java的SensorData转换为C++的SensorData
|
||||||
|
SensorData cppSensorData = convertJavaSensorDataToCpp(env, sensorData);
|
||||||
|
|
||||||
|
// 计算ECG指标
|
||||||
|
std::map<std::string, float> metrics = calculator->calculate_all_ecg_metrics(cppSensorData, sampleRate);
|
||||||
|
|
||||||
|
// 将C++的map转换为Java的Map
|
||||||
|
jobject result = convertMetricsMapToJava(env, metrics);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Successfully calculated ECG metrics, %zu metrics", metrics.size());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to calculate ECG metrics: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算PPG指标
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_calculatePPGMetrics(
|
||||||
|
JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject sensorData, jfloat sampleRate) {
|
||||||
|
try {
|
||||||
|
if (calculatorHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Invalid calculator handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsCalculator* calculator = reinterpret_cast<MetricsCalculator*>(calculatorHandle);
|
||||||
|
|
||||||
|
// 将Java的SensorData转换为C++的SensorData
|
||||||
|
SensorData cppSensorData = convertJavaSensorDataToCpp(env, sensorData);
|
||||||
|
|
||||||
|
// 计算PPG指标
|
||||||
|
std::map<std::string, float> metrics = calculator->calculate_all_ppg_metrics(cppSensorData, sampleRate);
|
||||||
|
|
||||||
|
// 将C++的map转换为Java的Map
|
||||||
|
jobject result = convertMetricsMapToJava(env, metrics);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Successfully calculated PPG metrics, %zu metrics", metrics.size());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to calculate PPG metrics: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算HRV指标
|
||||||
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
|
Java_com_example_cmake_1project_1test_IndicatorCalculator_calculateHRVMetrics(
|
||||||
|
JNIEnv* env, jobject thiz, jlong calculatorHandle, jobject rrIntervals) {
|
||||||
|
try {
|
||||||
|
if (calculatorHandle == 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Invalid calculator handle: 0");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsCalculator* calculator = reinterpret_cast<MetricsCalculator*>(calculatorHandle);
|
||||||
|
|
||||||
|
// 将Java的List<Float>转换为C++的vector<float>
|
||||||
|
std::vector<float> cppRrIntervals = convertJavaFloatListToVector(env, rrIntervals);
|
||||||
|
|
||||||
|
// 计算HRV指标
|
||||||
|
std::map<std::string, float> metrics = calculator->calculate_all_hrv_metrics(cppRrIntervals);
|
||||||
|
|
||||||
|
// 将C++的map转换为Java的Map
|
||||||
|
jobject result = convertMetricsMapToJava(env, metrics);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "IndicatorCalculator", "Successfully calculated HRV metrics, %zu metrics", metrics.size());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "IndicatorCalculator", "Failed to calculate HRV metrics: %s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 新增辅助函数实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 将C++的map<string, float>转换为Java的Map<String, Float>
|
||||||
|
jobject convertMetricsMapToJava(JNIEnv* env, const std::map<std::string, float>& metrics) {
|
||||||
|
// 创建HashMap
|
||||||
|
jclass hashMapClass = env->FindClass("java/util/HashMap");
|
||||||
|
jmethodID hashMapConstructor = env->GetMethodID(hashMapClass, "<init>", "()V");
|
||||||
|
jobject hashMap = env->NewObject(hashMapClass, hashMapConstructor);
|
||||||
|
|
||||||
|
// 获取put方法
|
||||||
|
jmethodID putMethod = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
||||||
|
|
||||||
|
// 获取Float类
|
||||||
|
jclass floatClass = env->FindClass("java/lang/Float");
|
||||||
|
jmethodID floatConstructor = env->GetMethodID(floatClass, "<init>", "(F)V");
|
||||||
|
|
||||||
|
// 遍历C++ map并添加到Java HashMap
|
||||||
|
for (const auto& pair : metrics) {
|
||||||
|
// 创建Java String
|
||||||
|
jstring key = env->NewStringUTF(pair.first.c_str());
|
||||||
|
|
||||||
|
// 创建Java Float
|
||||||
|
jobject value = env->NewObject(floatClass, floatConstructor, pair.second);
|
||||||
|
|
||||||
|
// 添加到HashMap
|
||||||
|
env->CallObjectMethod(hashMap, putMethod, key, value);
|
||||||
|
|
||||||
|
// 释放局部引用
|
||||||
|
env->DeleteLocalRef(key);
|
||||||
|
env->DeleteLocalRef(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,531 @@
|
||||||
|
#include "cpp/data_mapper.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
// 构造函数
|
||||||
|
Mapper::Mapper() {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DataMapper instance created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 析构函数
|
||||||
|
Mapper::~Mapper() {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DataMapper instance destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorData Mapper::DataMapper(SensorData& data)
|
||||||
|
{
|
||||||
|
// 添加空指针检查
|
||||||
|
if (data.channel_data.valueless_by_exception()) {
|
||||||
|
throw std::runtime_error("Channel data is in invalid state");
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorData data_mapped;
|
||||||
|
data_mapped = data;
|
||||||
|
|
||||||
|
// 添加调试信息
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== 开始数据映射 ===");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "数据类型: %d", static_cast<int>(data_mapped.data_type));
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: 数据类型值 = %d", static_cast<int>(data_mapped.data_type));
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "数据类型详细: ");
|
||||||
|
switch(data_mapped.data_type) {
|
||||||
|
case DataType::EEG: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "EEG (0)"); break;
|
||||||
|
case DataType::ECG_2LEAD: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "ECG_2LEAD (1)"); break;
|
||||||
|
case DataType::ECG_12LEAD: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "ECG_12LEAD (2)"); break;
|
||||||
|
case DataType::PPG: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "PPG (3)"); break;
|
||||||
|
case DataType::RESPIRATION: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "RESPIRATION (4)"); break;
|
||||||
|
case DataType::SNORE: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "SNORE (5)"); break;
|
||||||
|
case DataType::STETHOSCOPE: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "STETHOSCOPE (6)"); break;
|
||||||
|
case DataType::MIT_BIH: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "MIT_BIH (7)"); break;
|
||||||
|
default: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "未知类型"); break;
|
||||||
|
}
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "包序号: %d", data_mapped.packet_sn);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "时间戳: %lld", data_mapped.timestamp);
|
||||||
|
|
||||||
|
// 检查通道数据类型并安全处理
|
||||||
|
try {
|
||||||
|
switch(data_mapped.data_type){
|
||||||
|
case DataType::EEG :
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用EEG数据映射器");
|
||||||
|
data_mapped = EEG_Data_Mapper(data_mapped);break;
|
||||||
|
case DataType::ECG_2LEAD:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用ECG_2LEAD数据映射器");
|
||||||
|
data_mapped = ECG_2LEAD_Data_Mapper(data_mapped);break;
|
||||||
|
case DataType::ECG_12LEAD:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用ECG_12LEAD数据映射器");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: 确认进入ECG_12LEAD分支");
|
||||||
|
data_mapped = ECG_12LEAD_Data_Mapper(data_mapped);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: ECG_12LEAD映射完成");
|
||||||
|
break;
|
||||||
|
case DataType::PPG:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用PPG数据映射器");
|
||||||
|
data_mapped = PPG_Data_Mapper(data_mapped);break;
|
||||||
|
case DataType::RESPIRATION:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用RESPIRATION数据映射器");
|
||||||
|
data_mapped = Respiration_Data_Mapper(data_mapped);break;
|
||||||
|
case DataType::SNORE:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用SNORE数据映射器");
|
||||||
|
data_mapped = Snore_Data_Mapper(data_mapped);break;
|
||||||
|
case DataType::STETHOSCOPE:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "调用STETHOSCOPE数据映射器");
|
||||||
|
data_mapped = Stethoscope_Data_Mapper(data_mapped);break;
|
||||||
|
default:
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "未知数据类型,跳过映射");
|
||||||
|
// 对于未知类型,直接返回原始数据
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查映射后的通道数量
|
||||||
|
if (std::holds_alternative<std::vector<std::vector<float>>>(data_mapped.channel_data)) {
|
||||||
|
auto& output_channels = std::get<std::vector<std::vector<float>>>(data_mapped.channel_data);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "映射后通道数量: %zu", output_channels.size());
|
||||||
|
} else if (std::holds_alternative<std::vector<float>>(data_mapped.channel_data)) {
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "映射后为单通道数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// 如果映射失败,记录错误并返回原始数据
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Data mapping failed: %s", e.what());
|
||||||
|
return data; // 返回原始数据而不是抛出异常
|
||||||
|
}
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== 数据映射完成 ===");
|
||||||
|
return data_mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EEG数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::EEG_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查通道数据类型 - 支持多种格式
|
||||||
|
if (std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
// 多通道格式
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Input channel data for EEG is empty");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<std::vector<float>> output_channels;
|
||||||
|
output_channels.reserve(input_channels.size());
|
||||||
|
|
||||||
|
// 复制并应用校准系数 (0.318 μV/unit)
|
||||||
|
for (const auto& input_channel : input_channels) {
|
||||||
|
std::vector<float> output_channel;
|
||||||
|
output_channel.reserve(input_channel.size());
|
||||||
|
|
||||||
|
for (float value : input_channel) {
|
||||||
|
output_channel.push_back(value * 0.318f); // 转换为μV
|
||||||
|
}
|
||||||
|
output_channels.push_back(output_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output_channels;
|
||||||
|
|
||||||
|
} else if (std::holds_alternative<std::vector<float>>(data.channel_data)) {
|
||||||
|
// 单通道格式
|
||||||
|
auto& input_channel = std::get<std::vector<float>>(data.channel_data);
|
||||||
|
|
||||||
|
if (input_channel.empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Input channel data for EEG is empty");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<float> output_channel;
|
||||||
|
output_channel.reserve(input_channel.size());
|
||||||
|
|
||||||
|
// 应用校准系数
|
||||||
|
for (float value : input_channel) {
|
||||||
|
output_channel.push_back(value * 0.318f); // 转换为μV
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output_channel;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Unknown channel data format for EEG");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Error in EEG_Data_Mapper: %s", e.what());
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 胸腹设备数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::ECG_2LEAD_Data_Mapper(SensorData& data) {
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查通道数据类型 - 支持多种格式
|
||||||
|
if (std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
// 多通道格式
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Input channel data for ECG_2LEAD is empty");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<std::vector<float>> output_channels;
|
||||||
|
output_channels.reserve(input_channels.size());
|
||||||
|
|
||||||
|
// 复制并应用校准系数 (0.5 mV/unit)
|
||||||
|
for (const auto& input_channel : input_channels) {
|
||||||
|
std::vector<float> output_channel;
|
||||||
|
output_channel.reserve(input_channel.size());
|
||||||
|
|
||||||
|
for (float value : input_channel) {
|
||||||
|
output_channel.push_back(value * 0.5f); // 转换为mV
|
||||||
|
}
|
||||||
|
output_channels.push_back(output_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output_channels;
|
||||||
|
|
||||||
|
} else if (std::holds_alternative<std::vector<float>>(data.channel_data)) {
|
||||||
|
// 单通道格式
|
||||||
|
auto& input_channel = std::get<std::vector<float>>(data.channel_data);
|
||||||
|
|
||||||
|
if (input_channel.empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Input channel data for ECG_2LEAD is empty");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<float> output_channel;
|
||||||
|
output_channel.reserve(input_channel.size());
|
||||||
|
|
||||||
|
// 应用校准系数
|
||||||
|
for (float value : input_channel) {
|
||||||
|
output_channel.push_back(value * 0.5f); // 转换为mV
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output_channel;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "DataMapper", "Warning: Unknown channel data format for ECG_2LEAD");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "Error in ECG_2LEAD_Data_Mapper: %s", e.what());
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12导联心电数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::ECG_12LEAD_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== 开始ECG_12LEAD通道映射 ===");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "数据类型: %d", static_cast<int>(data.data_type));
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "包序号: %d", data.packet_sn);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "时间戳: %lld", data.timestamp);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: ECG_12LEAD_Data_Mapper函数被调用");
|
||||||
|
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 检查通道数据类型
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "检查通道数据类型...");
|
||||||
|
if (!std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "错误:通道数据格式不正确,期望std::vector<std::vector<float>>");
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "实际类型索引: %zu", data.channel_data.index());
|
||||||
|
throw std::runtime_error("Invalid channel data format for ECG_12LEAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已解析的通道数据
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "输入通道数量: %zu", input_channels.size());
|
||||||
|
|
||||||
|
// 检查输入通道是否为空
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "错误:输入通道数据为空");
|
||||||
|
throw std::runtime_error("Input channel data for ECG_12LEAD is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有足够的通道 (至少需要8个通道:V1, LA-RA, LL-RA, V2, V3, V4, V5, V6)
|
||||||
|
if (input_channels.size() < 8) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "错误:输入通道数量不足,需要至少8个通道,实际只有 %zu 个", input_channels.size());
|
||||||
|
throw std::runtime_error("Input channel data for ECG_12LEAD has less than 8 channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查关键通道是否有数据
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "检查关键通道数据...");
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
if (input_channels[i].empty()) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "错误:通道 %zu 为空", i);
|
||||||
|
throw std::runtime_error("Channel " + std::to_string(i) + " for ECG_12LEAD is empty");
|
||||||
|
}
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "通道 %zu 采样点数: %zu", i, input_channels[i].size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取采样点数
|
||||||
|
size_t num_samples = input_channels[0].size();
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "基准采样点数: %zu", num_samples);
|
||||||
|
|
||||||
|
// 验证所有关键通道都有相同的采样点数
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
if (input_channels[i].size() != num_samples) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "错误:通道 %zu 采样点数不一致,期望 %zu,实际 %zu",
|
||||||
|
i, num_samples, input_channels[i].size());
|
||||||
|
throw std::runtime_error("Channel " + std::to_string(i) + " has different sample count: " +
|
||||||
|
std::to_string(input_channels[i].size()) + " vs " + std::to_string(num_samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "数据验证通过,开始通道映射...");
|
||||||
|
|
||||||
|
// 创建12导联输出结构,并初始化所有通道的大小
|
||||||
|
std::vector<std::vector<float>> output_channels(12);
|
||||||
|
for (int ch = 0; ch < 12; ++ch) {
|
||||||
|
output_channels[ch].resize(num_samples, 0.0f); // 初始化为0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 通道0: V1电极信号 (直接复制)
|
||||||
|
output_channels[0] = input_channels[0];
|
||||||
|
|
||||||
|
// 通道1: Lead I = LA - RA (直接复制)
|
||||||
|
output_channels[1] = input_channels[1];
|
||||||
|
|
||||||
|
// 通道2: Lead II = LL - RA (直接复制)
|
||||||
|
output_channels[2] = input_channels[2];
|
||||||
|
|
||||||
|
// 通道3: Lead III = LL - LA = Lead II - Lead I
|
||||||
|
for (size_t sample = 0; sample < num_samples; ++sample) {
|
||||||
|
output_channels[3][sample] = output_channels[2][sample] - output_channels[1][sample];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 胸导联 V2-V6 (直接复制)
|
||||||
|
output_channels[4] = input_channels[3]; // V2
|
||||||
|
output_channels[5] = input_channels[4]; // V3
|
||||||
|
output_channels[6] = input_channels[5]; // V4
|
||||||
|
output_channels[7] = input_channels[6]; // V5
|
||||||
|
output_channels[8] = input_channels[7]; // V6
|
||||||
|
|
||||||
|
// 计算加压肢体导联 (aVR, aVL, aVF)
|
||||||
|
// 根据用户提供的新公式:
|
||||||
|
// aVR = -1/2 × (Lead I + Lead II)
|
||||||
|
// aVL = -1/2 × (Lead I - Lead III)
|
||||||
|
// aVF = -1/2 × (Lead II + Lead III)
|
||||||
|
|
||||||
|
for (size_t sample = 0; sample < num_samples; ++sample) {
|
||||||
|
float lead_I = output_channels[1][sample]; // Lead I = LA - RA
|
||||||
|
float lead_II = output_channels[2][sample]; // Lead II = LL - RA
|
||||||
|
float lead_III = output_channels[3][sample]; // Lead III = LL - LA
|
||||||
|
|
||||||
|
// 使用新公式计算加压肢体导联
|
||||||
|
output_channels[9][sample] = -0.5f * (lead_I + lead_II); // aVR = -1/2 × (I + II)
|
||||||
|
output_channels[10][sample] = -0.5f * (lead_I - lead_III); // aVL = -1/2 × (I - III)
|
||||||
|
output_channels[11][sample] = -0.5f * (lead_II + lead_III); // aVF = -1/2 × (II + III)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证输出通道的完整性
|
||||||
|
for (size_t ch = 0; ch < output_channels.size(); ++ch) {
|
||||||
|
if (output_channels[ch].size() != num_samples) {
|
||||||
|
throw std::runtime_error("Output channel " + std::to_string(ch) + " has incorrect size: " +
|
||||||
|
std::to_string(output_channels[ch].size()) + " vs expected " + std::to_string(num_samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output_channels;
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "ECG_12LEAD通道映射完成!");
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "输出通道数量: %zu", output_channels.size());
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "每个通道采样点数: %zu", num_samples);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "=== ECG_12LEAD通道映射结束 ===");
|
||||||
|
|
||||||
|
// 验证映射结果
|
||||||
|
if (std::holds_alternative<std::vector<std::vector<float>>>(processed.channel_data)) {
|
||||||
|
auto& final_channels = std::get<std::vector<std::vector<float>>>(processed.channel_data);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "最终验证 - 映射后通道数量: %zu", final_channels.size());
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "最终验证失败 - 通道数据类型错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "ECG_12LEAD映射异常: %s", e.what());
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "包序号: %d", data.packet_sn);
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "DataMapper", "返回原始数据(8通道)");
|
||||||
|
return data; // 返回原始数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PPG数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::PPG_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
// 检查通道数据类型
|
||||||
|
if (!std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
throw std::runtime_error("Invalid channel data format for PPG");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已解析的通道数据
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
|
||||||
|
// 检查输入通道是否为空
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
throw std::runtime_error("Input channel data for PPG is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有足够的通道
|
||||||
|
if (input_channels.size() < 2) {
|
||||||
|
throw std::runtime_error("Input channel data for PPG has less than 2 channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查每个通道是否有数据
|
||||||
|
for (size_t i = 0; i < 2; ++i) {
|
||||||
|
if (input_channels[i].empty()) {
|
||||||
|
throw std::runtime_error("Channel " + std::to_string(i) + " for PPG is empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<std::vector<float>> output(2);
|
||||||
|
|
||||||
|
// 通道0: 红光数据
|
||||||
|
output[0] = input_channels[0];
|
||||||
|
|
||||||
|
// 通道1: 红外光数据
|
||||||
|
output[1] = input_channels[1];
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output;
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 呼吸数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::Respiration_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
// 检查通道数据类型
|
||||||
|
if (!std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
throw std::runtime_error("Invalid channel data format for Respiration");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已解析的通道数据
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
|
||||||
|
// 检查输入通道是否为空
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
throw std::runtime_error("Input channel data for Respiration is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有足够的通道
|
||||||
|
if (input_channels.size() < 1) {
|
||||||
|
throw std::runtime_error("Input channel data for Respiration has no channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查第一个通道是否有数据
|
||||||
|
if (input_channels[0].empty()) {
|
||||||
|
throw std::runtime_error("Channel 0 for Respiration is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<std::vector<float>> output(1);
|
||||||
|
output[0] = input_channels[0];
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output;
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打鼾数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::Snore_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
// 检查通道数据类型
|
||||||
|
if (!std::holds_alternative<std::vector<float>>(data.channel_data)) {
|
||||||
|
throw std::runtime_error("Invalid channel data format for SNORE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已解析的通道数据
|
||||||
|
auto& raw_samples = std::get<std::vector<float>>(data.channel_data);
|
||||||
|
|
||||||
|
// 检查输入数据是否为空
|
||||||
|
if (raw_samples.empty()) {
|
||||||
|
throw std::runtime_error("Input channel data for SNORE is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用校准系数 (0.146 mV/unit)
|
||||||
|
std::vector<float> calibrated;
|
||||||
|
calibrated.reserve(raw_samples.size());
|
||||||
|
|
||||||
|
for (float sample : raw_samples) {
|
||||||
|
calibrated.push_back(sample * 0.146f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = calibrated;
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 听诊器数据映射器 - 处理已解析的通道数据
|
||||||
|
SensorData Mapper::Stethoscope_Data_Mapper(SensorData& data)
|
||||||
|
{
|
||||||
|
SensorData processed = data;
|
||||||
|
|
||||||
|
// 检查通道数据类型
|
||||||
|
if (!std::holds_alternative<std::vector<std::vector<float>>>(data.channel_data)) {
|
||||||
|
throw std::runtime_error("Invalid channel data format for Stethoscope");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已解析的通道数据
|
||||||
|
auto& input_channels = std::get<std::vector<std::vector<float>>>(data.channel_data);
|
||||||
|
|
||||||
|
// 检查输入通道是否为空
|
||||||
|
if (input_channels.empty()) {
|
||||||
|
throw std::runtime_error("Input channel data for Stethoscope is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有足够的通道
|
||||||
|
if (input_channels.size() < 2) {
|
||||||
|
throw std::runtime_error("Input channel data for Stethoscope has less than 2 channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查每个通道是否有数据
|
||||||
|
for (size_t i = 0; i < 2; ++i) {
|
||||||
|
if (input_channels[i].empty()) {
|
||||||
|
throw std::runtime_error("Channel " + std::to_string(i) + " for Stethoscope is empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出通道数据
|
||||||
|
std::vector<std::vector<float>> output(2);
|
||||||
|
output[0] = input_channels[0];
|
||||||
|
output[1] = input_channels[1];
|
||||||
|
|
||||||
|
// 更新处理后的通道数据
|
||||||
|
processed.channel_data = output;
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of data_mapper.cpp
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <cstring> // 添加 memcpy 头文件
|
#include <cstring> // 添加 memcpy 头文件
|
||||||
#include <sstream> // 添加 stringstream 头文件
|
#include <sstream> // 添加 stringstream 头文件
|
||||||
#include <iomanip> // 添加 iomanip 头文件
|
#include <iomanip> // 添加 iomanip 头文件
|
||||||
|
#include <iostream> // 添加 iostream 头文件
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
@ -316,7 +317,7 @@ SensorData parse_respiration(const uint8_t* data) {
|
||||||
// 统一解析入口函数 - 支持多个包头和数据包格式
|
// 统一解析入口函数 - 支持多个包头和数据包格式
|
||||||
std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data) {
|
std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data) {
|
||||||
const size_t PACKET_SIZE = 238; // 每个数据包固定大小
|
const size_t PACKET_SIZE = 238; // 每个数据包固定大小
|
||||||
const size_t RESPONSE_HEADER_SIZE = 10; // 响应包头大小 (2功能码+ 2数据长度 + 4实际采集点数+crc校验)
|
const size_t RESPONSE_HEADER_SIZE = 10; // 响应包头大小 (2功能码+ 2数据长度 + 4实际采集点数+2CRC校验)
|
||||||
const uint16_t FUNCTION_CODE = 0x0010; // 获取数据功能码
|
const uint16_t FUNCTION_CODE = 0x0010; // 获取数据功能码
|
||||||
|
|
||||||
const size_t file_size = file_data.size();
|
const size_t file_size = file_data.size();
|
||||||
|
|
@ -324,6 +325,7 @@ std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data)
|
||||||
const uint8_t* end_ptr = ptr + file_size;
|
const uint8_t* end_ptr = ptr + file_size;
|
||||||
std::map<DataType, std::vector<SensorData>> grouped_data;
|
std::map<DataType, std::vector<SensorData>> grouped_data;
|
||||||
std::vector<SensorData> results;
|
std::vector<SensorData> results;
|
||||||
|
|
||||||
while (ptr < end_ptr) {
|
while (ptr < end_ptr) {
|
||||||
// 检查是否有响应包头
|
// 检查是否有响应包头
|
||||||
bool has_response_header = false;
|
bool has_response_header = false;
|
||||||
|
|
@ -339,9 +341,23 @@ std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data)
|
||||||
// 检查是否符合0x0010响应包特征
|
// 检查是否符合0x0010响应包特征
|
||||||
if (func_code == FUNCTION_CODE && data_len == 0x0004) {
|
if (func_code == FUNCTION_CODE && data_len == 0x0004) {
|
||||||
has_response_header = true;
|
has_response_header = true;
|
||||||
|
std::cout << "检测到响应帧头: 功能码=0x" << std::hex << func_code
|
||||||
|
<< ", 数据长度=0x" << data_len << std::dec << std::endl;
|
||||||
|
|
||||||
// 读取实际数据点数 (小端)
|
// 读取实际数据点数 (小端)
|
||||||
uint32_t actual_points = read_le<uint32_t>(ptr + 4);
|
uint32_t actual_points = read_le<uint32_t>(ptr + 4);
|
||||||
|
std::cout << "实际数据点数: " << actual_points << std::endl;
|
||||||
|
|
||||||
|
// 读取CRC校验 (小端)
|
||||||
|
uint16_t crc = read_le<uint16_t>(ptr + 8);
|
||||||
|
|
||||||
|
// 验证CRC校验
|
||||||
|
uint16_t calculated_crc = calculate_crc16(ptr, 8);
|
||||||
|
if (crc != calculated_crc) {
|
||||||
|
std::cerr << "警告: CRC校验失败,预期: 0x" << std::hex << calculated_crc
|
||||||
|
<< ", 实际: 0x" << crc << std::dec << std::endl;
|
||||||
|
// 可以选择跳过这个响应帧或继续处理
|
||||||
|
}
|
||||||
|
|
||||||
// 计算期望的数据长度(实际点数 * 包大小)
|
// 计算期望的数据长度(实际点数 * 包大小)
|
||||||
size_t expected_data_size = actual_points * PACKET_SIZE;
|
size_t expected_data_size = actual_points * PACKET_SIZE;
|
||||||
|
|
@ -392,8 +408,11 @@ std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data)
|
||||||
grouped_data[DataType::STETHOSCOPE].push_back(parse_stethoscope(ptr));
|
grouped_data[DataType::STETHOSCOPE].push_back(parse_stethoscope(ptr));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("未知设备类型: 0x" +
|
// 对于未知类型,记录警告但继续处理
|
||||||
to_hex_string(data_type));
|
std::cerr << "警告: 未知设备类型: 0x" << std::hex << data_type
|
||||||
|
<< std::dec << ", 跳过此数据包" << std::endl;
|
||||||
|
ptr += PACKET_SIZE;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
ptr += PACKET_SIZE;
|
ptr += PACKET_SIZE;
|
||||||
}
|
}
|
||||||
|
|
@ -506,6 +525,24 @@ std::vector<SensorData> parse_device_data(const std::vector<uint8_t>& file_data)
|
||||||
return final_results;
|
return final_results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRC16校验函数 (Modbus CRC16)
|
||||||
|
uint16_t calculate_crc16(const uint8_t* data, size_t length) {
|
||||||
|
uint16_t crc = 0xFFFF;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
crc ^= data[i];
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if (crc & 0x0001) {
|
||||||
|
crc = (crc >> 1) ^ 0xA001;
|
||||||
|
} else {
|
||||||
|
crc = crc >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
// 工具函数:将数值转换为十六进制字符串
|
// 工具函数:将数值转换为十六进制字符串
|
||||||
std::string to_hex_string(uint16_t value) {
|
std::string to_hex_string(uint16_t value) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
@ -802,6 +839,9 @@ jobject convertSensorDataToJava(JNIEnv* env, const SensorData& sensorData) {
|
||||||
// 设置通道数据(处理两种类型:vector<vector<float>> 和 vector<float>)
|
// 设置通道数据(处理两种类型:vector<vector<float>> 和 vector<float>)
|
||||||
if (auto* multiChannels = std::get_if<std::vector<std::vector<float>>>(&sensorData.channel_data)) {
|
if (auto* multiChannels = std::get_if<std::vector<std::vector<float>>>(&sensorData.channel_data)) {
|
||||||
// 处理多通道数据 (vector<vector<float>>)
|
// 处理多通道数据 (vector<vector<float>>)
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: C++到Java转换 - 多通道数据,通道数量: %zu", multiChannels->size());
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: C++到Java转换 - 数据类型: %d", static_cast<int>(sensorData.data_type));
|
||||||
|
|
||||||
jclass arrayListClass = env->FindClass("java/util/ArrayList");
|
jclass arrayListClass = env->FindClass("java/util/ArrayList");
|
||||||
jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
|
jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
|
||||||
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
|
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
|
||||||
|
|
@ -829,6 +869,7 @@ jobject convertSensorDataToJava(JNIEnv* env, const SensorData& sensorData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
env->SetObjectField(javaSensorData, channelDataField, outerList);
|
env->SetObjectField(javaSensorData, channelDataField, outerList);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "DataMapper", "DEBUG: C++到Java转换 - 多通道数据设置完成");
|
||||||
|
|
||||||
} else if (auto* singleChannel = std::get_if<std::vector<float>>(&sensorData.channel_data)) {
|
} else if (auto* singleChannel = std::get_if<std::vector<float>>(&sensorData.channel_data)) {
|
||||||
// 处理单通道数据 (vector<float>) - 将其包装为单通道的多通道格式
|
// 处理单通道数据 (vector<float>) - 将其包装为单通道的多通道格式
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,183 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整数据处理管道示例
|
||||||
|
* 演示从原始数据到最终指标的完整流程
|
||||||
|
*/
|
||||||
|
class CompletePipelineExample {
|
||||||
|
|
||||||
|
private val dataMapper = DataMapper()
|
||||||
|
private val indicatorCalculator = IndicatorCalculator()
|
||||||
|
private val signalProcessor = SignalProcessorJNI()
|
||||||
|
|
||||||
|
private var dataMapperInitialized = false
|
||||||
|
private var indicatorCalculatorInitialized = false
|
||||||
|
private var signalProcessorInitialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化完整管道
|
||||||
|
*/
|
||||||
|
fun initialize(): Boolean {
|
||||||
|
Log.d("CompletePipeline", "初始化完整数据处理管道...")
|
||||||
|
|
||||||
|
// 初始化数据映射器
|
||||||
|
dataMapperInitialized = dataMapper.initialize()
|
||||||
|
if (!dataMapperInitialized) {
|
||||||
|
Log.e("CompletePipeline", "数据映射器初始化失败")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化指标计算器
|
||||||
|
indicatorCalculatorInitialized = indicatorCalculator.initialize()
|
||||||
|
if (!indicatorCalculatorInitialized) {
|
||||||
|
Log.e("CompletePipeline", "指标计算器初始化失败")
|
||||||
|
cleanup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化信号处理器
|
||||||
|
try {
|
||||||
|
signalProcessorInitialized = signalProcessor.createProcessor()
|
||||||
|
if (!signalProcessorInitialized) {
|
||||||
|
Log.e("CompletePipeline", "信号处理器初始化失败")
|
||||||
|
cleanup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CompletePipeline", "信号处理器初始化异常: ${e.message}")
|
||||||
|
cleanup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipeline", "完整数据处理管道初始化成功")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个数据包的完整流程
|
||||||
|
* 原始数据 → 解析数据 → 通道映射 → 信号处理 → 指标计算
|
||||||
|
*/
|
||||||
|
fun processCompletePipeline(rawData: SensorData, sampleRate: Float = 250.0f): Map<String, Float>? {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
Log.w("CompletePipeline", "管道未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipeline", "开始处理完整数据管道...")
|
||||||
|
Log.d("CompletePipeline", "输入数据类型: ${rawData.dataType}, 采样率: $sampleRate Hz")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 步骤1: 通道映射
|
||||||
|
Log.d("CompletePipeline", "步骤1: 执行通道映射...")
|
||||||
|
val mappedData = dataMapper.mapSensorData(rawData)
|
||||||
|
if (mappedData == null) {
|
||||||
|
Log.e("CompletePipeline", "通道映射失败")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
Log.d("CompletePipeline", "通道映射完成")
|
||||||
|
|
||||||
|
// 步骤2: 信号处理 (这里可以添加信号处理逻辑)
|
||||||
|
Log.d("CompletePipeline", "步骤2: 执行信号处理...")
|
||||||
|
// 暂时跳过信号处理,直接使用映射后的数据
|
||||||
|
val processedData = mappedData
|
||||||
|
Log.d("CompletePipeline", "信号处理完成")
|
||||||
|
|
||||||
|
// 步骤3: 指标计算
|
||||||
|
Log.d("CompletePipeline", "步骤3: 执行指标计算...")
|
||||||
|
val metrics = indicatorCalculator.processCompletePipeline(processedData, sampleRate)
|
||||||
|
if (metrics == null) {
|
||||||
|
Log.e("CompletePipeline", "指标计算失败")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipeline", "指标计算完成,计算了 ${metrics.size} 个指标")
|
||||||
|
Log.d("CompletePipeline", "完整数据处理管道执行成功")
|
||||||
|
|
||||||
|
// 打印主要指标
|
||||||
|
metrics["heart_rate"]?.let {
|
||||||
|
Log.d("CompletePipeline", "心率: ${String.format("%.1f", it)} bpm")
|
||||||
|
}
|
||||||
|
metrics["signal_quality"]?.let {
|
||||||
|
Log.d("CompletePipeline", "信号质量: ${String.format("%.2f", it)}")
|
||||||
|
}
|
||||||
|
metrics["spo2"]?.let {
|
||||||
|
Log.d("CompletePipeline", "血氧饱和度: ${String.format("%.1f", it)}%")
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CompletePipeline", "完整数据处理管道异常: ${e.message}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理数据包
|
||||||
|
*/
|
||||||
|
fun processBatchPipeline(rawDataList: List<SensorData>, sampleRate: Float = 250.0f): List<Map<String, Float>> {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
Log.w("CompletePipeline", "管道未初始化")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipeline", "开始批量处理 ${rawDataList.size} 个数据包...")
|
||||||
|
|
||||||
|
val results = mutableListOf<Map<String, Float>>()
|
||||||
|
var successCount = 0
|
||||||
|
|
||||||
|
for ((index, rawData) in rawDataList.withIndex()) {
|
||||||
|
val metrics = processCompletePipeline(rawData, sampleRate)
|
||||||
|
if (metrics != null) {
|
||||||
|
results.add(metrics)
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
results.add(emptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每处理100个数据包打印一次进度
|
||||||
|
if ((index + 1) % 100 == 0) {
|
||||||
|
Log.d("CompletePipeline", "已处理 ${index + 1}/${rawDataList.size} 个数据包")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipeline", "批量处理完成: 成功 $successCount/${rawDataList.size} 个数据包")
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查管道是否已初始化
|
||||||
|
*/
|
||||||
|
fun isInitialized(): Boolean {
|
||||||
|
return dataMapperInitialized && indicatorCalculatorInitialized && signalProcessorInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
try {
|
||||||
|
if (dataMapperInitialized) {
|
||||||
|
// 使用公共方法或直接设置为false
|
||||||
|
dataMapperInitialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indicatorCalculatorInitialized) {
|
||||||
|
// 使用公共方法或直接设置为false
|
||||||
|
indicatorCalculatorInitialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.destroyProcessor()
|
||||||
|
signalProcessorInitialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CompletePipelineExample", "资源清理完成")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CompletePipelineExample", "资源清理异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
// UI更新相关
|
||||||
|
const val UPDATE_INTERVAL = 500L // 每500毫秒更新一次UI
|
||||||
|
|
||||||
|
// 数据分块相关
|
||||||
|
const val CHUNK_SIZE = 64 // 数据分块大小
|
||||||
|
|
||||||
|
// 缓冲区管理相关
|
||||||
|
const val BUFFER_CLEANUP_THRESHOLD = 50 // 缓冲区清理阈值
|
||||||
|
const val BUFFER_KEEP_COUNT = 30 // 缓冲区保留数量
|
||||||
|
|
||||||
|
// 显示限制相关
|
||||||
|
const val MAX_DISPLAY_PACKETS = 5 // 最大显示数据包数量
|
||||||
|
const val MAX_DETAIL_PACKETS = 3 // 最大详情数据包数量
|
||||||
|
const val MAX_DISPLAY_CHANNELS = 4 // 最大显示通道数量
|
||||||
|
const val MAX_12LEAD_CHANNELS = 6 // 12导联心电最大通道数量
|
||||||
|
const val MAX_DISPLAY_SAMPLES = 10 // 最大显示采样点数量
|
||||||
|
|
||||||
|
// 文件相关
|
||||||
|
const val DEFAULT_DATA_FILE = "ecg_data_raw.dat" // 默认数据文件名
|
||||||
|
|
||||||
|
// 延迟相关
|
||||||
|
const val SIMULATION_DELAY = 1L // 模拟传输延迟(毫秒)- 减少延迟
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,687 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据管理类
|
||||||
|
* 负责数据解析、缓冲管理和原生方法调用
|
||||||
|
*/
|
||||||
|
class DataManager(private val nativeCallback: NativeMethodCallback) {
|
||||||
|
|
||||||
|
private var parserHandle: Long = 0L
|
||||||
|
private val rawStream = ByteArrayOutputStream(4096)
|
||||||
|
private val packetBuffer = mutableListOf<SensorData>()
|
||||||
|
private var totalPacketsParsed = 0L
|
||||||
|
|
||||||
|
// 信号处理相关
|
||||||
|
private var streamingSignalProcessor: StreamingSignalProcessor? = null
|
||||||
|
private var streamingSignalProcessorInitialized = false
|
||||||
|
private val processedPackets = mutableListOf<SensorData>()
|
||||||
|
private val filterSettings = FilterSettings()
|
||||||
|
|
||||||
|
// 通道映射相关
|
||||||
|
private var dataMapper: DataMapper? = null
|
||||||
|
private var dataMapperInitialized = false
|
||||||
|
|
||||||
|
// 指标计算相关
|
||||||
|
private var indicatorCalculator: IndicatorCalculator? = null
|
||||||
|
private var indicatorCalculatorInitialized = false
|
||||||
|
private val calculatedMetrics = mutableListOf<Map<String, Float>>()
|
||||||
|
private var latestMetrics: Map<String, Float> = emptyMap()
|
||||||
|
|
||||||
|
// 流式数据处理相关
|
||||||
|
private val channelBuffers = mutableMapOf<Int, MutableList<Float>>() // 通道号 -> 数据缓冲区
|
||||||
|
private val processedChannelBuffers = mutableMapOf<Int, MutableList<Float>>() // 处理后的通道数据
|
||||||
|
private val processingWindowSize = 2000 // 处理窗口大小(样本数)
|
||||||
|
private val minSamplesForMetrics = 1000 // 恢复最小样本数到合理值
|
||||||
|
private var currentDataType: type.SensorData.DataType? = null // 当前数据类型
|
||||||
|
private var lastProcessTime = 0L // 上次处理时间
|
||||||
|
private val processingInterval = 2000L // 恢复处理间隔到合理值
|
||||||
|
private var totalProcessedSamples = 0L // 总处理样本数
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保解析器已创建
|
||||||
|
*/
|
||||||
|
fun ensureParser() {
|
||||||
|
if (parserHandle == 0L) {
|
||||||
|
parserHandle = nativeCallback.createStreamParser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理蓝牙通知数据块
|
||||||
|
*/
|
||||||
|
fun onBleNotify(chunk: ByteArray) {
|
||||||
|
if (chunk.isEmpty()) return
|
||||||
|
|
||||||
|
ensureParser()
|
||||||
|
rawStream.write(chunk)
|
||||||
|
nativeCallback.streamParserAppend(parserHandle, chunk)
|
||||||
|
|
||||||
|
// 拉取解析出的数据包
|
||||||
|
val packets = nativeCallback.streamParserDrainPackets(parserHandle)
|
||||||
|
if (!packets.isNullOrEmpty()) {
|
||||||
|
totalPacketsParsed += packets.size
|
||||||
|
packetBuffer.addAll(packets)
|
||||||
|
|
||||||
|
// 应用流式数据处理
|
||||||
|
processStreamingData(packets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保数据映射器已初始化
|
||||||
|
*/
|
||||||
|
fun ensureDataMapper() {
|
||||||
|
if (!dataMapperInitialized) {
|
||||||
|
try {
|
||||||
|
Log.d("DataManager", "开始创建数据映射器实例...")
|
||||||
|
dataMapper = DataMapper()
|
||||||
|
Log.d("DataManager", "数据映射器实例创建成功")
|
||||||
|
|
||||||
|
Log.d("DataManager", "开始初始化数据映射器...")
|
||||||
|
dataMapperInitialized = dataMapper?.initialize() ?: false
|
||||||
|
if (dataMapperInitialized) {
|
||||||
|
Log.d("DataManager", "数据映射器初始化成功")
|
||||||
|
} else {
|
||||||
|
Log.e("DataManager", "数据映射器初始化失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "数据映射器初始化异常: ${e.message}", e)
|
||||||
|
dataMapperInitialized = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保指标计算器已初始化
|
||||||
|
*/
|
||||||
|
fun ensureIndicatorCalculator() {
|
||||||
|
if (!indicatorCalculatorInitialized) {
|
||||||
|
try {
|
||||||
|
Log.d("DataManager", "开始创建指标计算器实例...")
|
||||||
|
indicatorCalculator = IndicatorCalculator()
|
||||||
|
Log.d("DataManager", "指标计算器实例创建成功")
|
||||||
|
|
||||||
|
Log.d("DataManager", "开始初始化指标计算器...")
|
||||||
|
indicatorCalculatorInitialized = indicatorCalculator?.initialize() ?: false
|
||||||
|
if (indicatorCalculatorInitialized) {
|
||||||
|
Log.d("DataManager", "指标计算器初始化成功")
|
||||||
|
} else {
|
||||||
|
Log.e("DataManager", "指标计算器初始化失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "指标计算器初始化异常: ${e.message}", e)
|
||||||
|
indicatorCalculatorInitialized = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保流式信号处理器已初始化
|
||||||
|
*/
|
||||||
|
fun ensureStreamingSignalProcessor() {
|
||||||
|
if (!streamingSignalProcessorInitialized) {
|
||||||
|
try {
|
||||||
|
Log.d("DataManager", "开始创建流式信号处理器实例...")
|
||||||
|
streamingSignalProcessor = StreamingSignalProcessor()
|
||||||
|
Log.d("DataManager", "流式信号处理器实例创建成功")
|
||||||
|
|
||||||
|
Log.d("DataManager", "开始初始化流式信号处理器...")
|
||||||
|
streamingSignalProcessorInitialized = streamingSignalProcessor?.initialize() ?: false
|
||||||
|
if (streamingSignalProcessorInitialized) {
|
||||||
|
Log.d("DataManager", "流式信号处理器初始化成功")
|
||||||
|
} else {
|
||||||
|
Log.e("DataManager", "流式信号处理器初始化失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "流式信号处理器初始化异常: ${e.message}", e)
|
||||||
|
streamingSignalProcessorInitialized = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式数据处理 - 将数据包按通道合并并处理
|
||||||
|
*/
|
||||||
|
private fun processStreamingData(packets: List<SensorData>) {
|
||||||
|
if (packets.isEmpty()) return
|
||||||
|
|
||||||
|
Log.d("DataManager", "开始流式数据处理,处理 ${packets.size} 个数据包")
|
||||||
|
|
||||||
|
// 1. 通道映射
|
||||||
|
ensureDataMapper()
|
||||||
|
val mappedPackets = if (dataMapperInitialized && dataMapper != null) {
|
||||||
|
try {
|
||||||
|
val mapped = dataMapper!!.mapSensorDataList(packets)
|
||||||
|
Log.d("DataManager", "通道映射完成,映射了 ${mapped.size} 个数据包")
|
||||||
|
mapped
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "通道映射失败: ${e.message}")
|
||||||
|
packets
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("DataManager", "数据映射器未初始化,跳过通道映射")
|
||||||
|
packets
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 按通道合并数据
|
||||||
|
for (packet in mappedPackets) {
|
||||||
|
val dataType = packet.getDataType()
|
||||||
|
if (currentDataType == null) {
|
||||||
|
currentDataType = dataType
|
||||||
|
Log.d("DataManager", "设置当前数据类型: $dataType")
|
||||||
|
}
|
||||||
|
|
||||||
|
val channelData = packet.getChannelData()
|
||||||
|
if (channelData != null) {
|
||||||
|
for ((channelIndex, channel) in channelData.withIndex()) {
|
||||||
|
if (!channelBuffers.containsKey(channelIndex)) {
|
||||||
|
channelBuffers[channelIndex] = mutableListOf()
|
||||||
|
processedChannelBuffers[channelIndex] = mutableListOf()
|
||||||
|
}
|
||||||
|
channelBuffers[channelIndex]!!.addAll(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否达到处理条件
|
||||||
|
val totalSamples = channelBuffers.values.firstOrNull()?.size ?: 0
|
||||||
|
if (totalSamples > 0) {
|
||||||
|
Log.d("DataManager", "当前总样本数: $totalSamples, 需要样本数: $minSamplesForMetrics")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查处理条件:数据量足够且时间间隔合适
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
val shouldProcess = totalSamples >= minSamplesForMetrics &&
|
||||||
|
(currentTime - lastProcessTime) >= processingInterval
|
||||||
|
|
||||||
|
if (shouldProcess) {
|
||||||
|
Log.d("DataManager", "满足处理条件,开始流式信号处理和指标计算")
|
||||||
|
processStreamingWindow()
|
||||||
|
lastProcessTime = currentTime
|
||||||
|
} else {
|
||||||
|
Log.d("DataManager", "不满足处理条件 - 数据量: ${totalSamples}/${minSamplesForMetrics}, 时间间隔: ${currentTime - lastProcessTime}ms/${processingInterval}ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累积映射后的数据包列表(用于UI显示)
|
||||||
|
// 注意:这里累积所有批次的映射后数据包,用于统计和显示
|
||||||
|
this.processedPackets.addAll(mappedPackets)
|
||||||
|
|
||||||
|
// 添加调试信息(在后台线程中执行,避免阻塞UI)
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
Log.d("DataManager", "DEBUG: 当前批次映射结果 - 输入: ${packets.size}个数据包, 映射后: ${mappedPackets.size}个数据包")
|
||||||
|
Log.d("DataManager", "DEBUG: 累积映射后数据包总数: ${this.processedPackets.size}")
|
||||||
|
|
||||||
|
// 统计当前批次的通道数量分布
|
||||||
|
val ecg12LeadPackets = mappedPackets.filter { it.getDataType() == type.SensorData.DataType.ECG_12LEAD }
|
||||||
|
val packetsWith8Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 8 }
|
||||||
|
val packetsWith12Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 12 }
|
||||||
|
Log.d("DataManager", "DEBUG: 当前批次ECG_12LEAD统计 - 8通道: ${packetsWith8Channels}个, 12通道: ${packetsWith12Channels}个")
|
||||||
|
|
||||||
|
// 统计累积的通道数量分布
|
||||||
|
val totalEcg12LeadPackets = this.processedPackets.filter { it.getDataType() == type.SensorData.DataType.ECG_12LEAD }
|
||||||
|
val totalPacketsWith8Channels = totalEcg12LeadPackets.count { it.getChannelData()?.size == 8 }
|
||||||
|
val totalPacketsWith12Channels = totalEcg12LeadPackets.count { it.getChannelData()?.size == 12 }
|
||||||
|
Log.d("DataManager", "DEBUG: 累积ECG_12LEAD统计 - 8通道: ${totalPacketsWith8Channels}个, 12通道: ${totalPacketsWith12Channels}个")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "后台统计调试信息时发生错误: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理流式数据窗口
|
||||||
|
*/
|
||||||
|
private fun processStreamingWindow() {
|
||||||
|
ensureStreamingSignalProcessor()
|
||||||
|
if (!streamingSignalProcessorInitialized || streamingSignalProcessor == null) {
|
||||||
|
Log.w("DataManager", "流式处理器未初始化,跳过处理")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.d("DataManager", "开始处理流式数据窗口")
|
||||||
|
|
||||||
|
// 获取采样率
|
||||||
|
val sampleRate = when (currentDataType) {
|
||||||
|
type.SensorData.DataType.EEG -> 250.0
|
||||||
|
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> 250.0
|
||||||
|
type.SensorData.DataType.PPG -> 50.0
|
||||||
|
type.SensorData.DataType.STETHOSCOPE -> 8000.0
|
||||||
|
type.SensorData.DataType.SNORE -> 8000.0
|
||||||
|
type.SensorData.DataType.RESPIRATION -> 50.0
|
||||||
|
else -> 250.0
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("DataManager", "使用采样率: ${sampleRate}Hz,数据类型: $currentDataType")
|
||||||
|
|
||||||
|
// 处理所有通道的数据
|
||||||
|
val processedChannels = mutableListOf<List<Float>>()
|
||||||
|
var localProcessedSamples = 0L
|
||||||
|
|
||||||
|
for ((channelIndex, channelData) in channelBuffers) {
|
||||||
|
if (channelData.size >= minSamplesForMetrics) {
|
||||||
|
Log.d("DataManager", "处理通道 $channelIndex,数据长度: ${channelData.size}")
|
||||||
|
|
||||||
|
// 应用信号处理
|
||||||
|
val processedData = streamingSignalProcessor!!.processStreamingDataWithParameters(
|
||||||
|
channelData,
|
||||||
|
sampleRate,
|
||||||
|
40.0, // 低通滤波截止频率
|
||||||
|
50.0, // 陷波滤波频率
|
||||||
|
30.0 // 陷波滤波品质因数
|
||||||
|
)
|
||||||
|
|
||||||
|
if (processedData.isNotEmpty()) {
|
||||||
|
processedChannels.add(processedData)
|
||||||
|
processedChannelBuffers[channelIndex]?.addAll(processedData)
|
||||||
|
localProcessedSamples += processedData.size
|
||||||
|
Log.d("DataManager", "通道 $channelIndex 处理完成,处理后数据长度: ${processedData.size}")
|
||||||
|
} else {
|
||||||
|
Log.w("DataManager", "通道 $channelIndex 处理失败,返回空数据")
|
||||||
|
processedChannels.add(channelData) // 使用原始数据
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("DataManager", "通道 $channelIndex 数据不足,跳过处理")
|
||||||
|
processedChannels.add(channelData) // 使用原始数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("DataManager", "所有通道处理完成,总共处理了 $localProcessedSamples 个样本")
|
||||||
|
|
||||||
|
// 计算指标(使用第一个处理后的通道)
|
||||||
|
if (processedChannels.isNotEmpty() && processedChannels[0].size >= minSamplesForMetrics) {
|
||||||
|
ensureIndicatorCalculator()
|
||||||
|
if (indicatorCalculatorInitialized && indicatorCalculator != null) {
|
||||||
|
try {
|
||||||
|
// 创建临时的SensorData用于指标计算
|
||||||
|
val tempSensorData = SensorData()
|
||||||
|
tempSensorData.setDataType(currentDataType!!)
|
||||||
|
tempSensorData.setTimestamp(System.currentTimeMillis())
|
||||||
|
tempSensorData.setPacketSn(0)
|
||||||
|
tempSensorData.setChannelData(processedChannels)
|
||||||
|
|
||||||
|
val metrics = indicatorCalculator!!.processCompletePipeline(tempSensorData, sampleRate.toFloat())
|
||||||
|
if (metrics != null && metrics.isNotEmpty()) {
|
||||||
|
Log.d("DataManager", "指标计算成功,获得 ${metrics.size} 个指标")
|
||||||
|
latestMetrics = metrics
|
||||||
|
calculatedMetrics.clear()
|
||||||
|
calculatedMetrics.add(metrics)
|
||||||
|
|
||||||
|
// 记录处理统计
|
||||||
|
this.totalProcessedSamples += localProcessedSamples
|
||||||
|
} else {
|
||||||
|
Log.w("DataManager", "指标计算返回空结果")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "指标计算失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理缓冲区(保留最后一部分数据用于连续性)
|
||||||
|
val keepSamples = minSamplesForMetrics / 2
|
||||||
|
channelBuffers.forEach { (channel, buffer) ->
|
||||||
|
if (buffer.size > keepSamples) {
|
||||||
|
val toRemove = buffer.size - keepSamples
|
||||||
|
repeat(toRemove) {
|
||||||
|
if (buffer.isNotEmpty()) {
|
||||||
|
buffer.removeAt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理处理后的缓冲区
|
||||||
|
processedChannelBuffers.forEach { (channel, buffer) ->
|
||||||
|
if (buffer.size > keepSamples) {
|
||||||
|
val toRemove = buffer.size - keepSamples
|
||||||
|
repeat(toRemove) {
|
||||||
|
if (buffer.isNotEmpty()) {
|
||||||
|
buffer.removeAt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("DataManager", "流式数据处理完成,保留 ${keepSamples} 个样本用于连续性")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "流式数据处理异常: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理文件数据
|
||||||
|
*/
|
||||||
|
fun processFileData(fileData: ByteArray, progressCallback: ((Int) -> Unit)? = null) {
|
||||||
|
Log.d("DataManager", "开始处理文件数据,数据大小: ${fileData.size} 字节")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val chunkSize = 1024
|
||||||
|
var processedBytes = 0
|
||||||
|
|
||||||
|
for (i in fileData.indices step chunkSize) {
|
||||||
|
val endIndex = minOf(i + chunkSize, fileData.size)
|
||||||
|
val chunk = fileData.copyOfRange(i, endIndex)
|
||||||
|
|
||||||
|
onBleNotify(chunk)
|
||||||
|
processedBytes += chunk.size
|
||||||
|
|
||||||
|
// 更新进度
|
||||||
|
val progress = (processedBytes * 100 / fileData.size).coerceAtMost(100)
|
||||||
|
progressCallback?.invoke(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("DataManager", "文件数据处理完成,总共处理了 $processedBytes 字节")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "处理文件数据时发生错误: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter方法
|
||||||
|
fun getPacketBufferSize(): Int = packetBuffer.size
|
||||||
|
fun getProcessedPacketsSize(): Int = processedPackets.size
|
||||||
|
fun getCalculatedMetricsSize(): Int = calculatedMetrics.size
|
||||||
|
fun getRawStreamSize(): Int = rawStream.size()
|
||||||
|
fun getTotalPacketsParsed(): Long = totalPacketsParsed
|
||||||
|
fun getPacketBuffer(): List<SensorData> = packetBuffer
|
||||||
|
fun getProcessedPackets(): List<SensorData> = processedPackets
|
||||||
|
fun getCalculatedMetrics(): List<Map<String, Float>> = calculatedMetrics
|
||||||
|
fun getLatestMetrics(): Map<String, Float> = latestMetrics
|
||||||
|
fun getChannelBuffersStatus(): Map<Int, Int> = channelBuffers.mapValues { it.value.size }
|
||||||
|
|
||||||
|
// 流式处理相关getter方法
|
||||||
|
fun getProcessedChannelBuffersStatus(): Map<Int, Int> = processedChannelBuffers.mapValues { it.value.size }
|
||||||
|
fun getTotalProcessedSamples(): Long = totalProcessedSamples
|
||||||
|
fun getCurrentDataType(): type.SensorData.DataType? = currentDataType
|
||||||
|
fun getProcessingStatus(): Map<String, Any> {
|
||||||
|
return mapOf(
|
||||||
|
"totalSamples" to (channelBuffers.values.firstOrNull()?.size ?: 0),
|
||||||
|
"minSamplesRequired" to minSamplesForMetrics,
|
||||||
|
"timeSinceLastProcess" to (System.currentTimeMillis() - lastProcessTime),
|
||||||
|
"processingInterval" to processingInterval,
|
||||||
|
"currentDataType" to (currentDataType?.name ?: "Unknown"),
|
||||||
|
"totalProcessedSamples" to totalProcessedSamples
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流式信号处理器状态
|
||||||
|
*/
|
||||||
|
fun getStreamingSignalProcessorStatus(): Map<String, Int> {
|
||||||
|
return if (streamingSignalProcessorInitialized && streamingSignalProcessor != null) {
|
||||||
|
streamingSignalProcessor!!.getBufferStatus()
|
||||||
|
} else {
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理缓冲区
|
||||||
|
*/
|
||||||
|
fun cleanupBuffer() {
|
||||||
|
try {
|
||||||
|
// 清理原始数据包缓冲区,保留最新的100个数据包
|
||||||
|
val maxKeepPackets = 100
|
||||||
|
if (packetBuffer.size > maxKeepPackets) {
|
||||||
|
val toRemove = packetBuffer.size - maxKeepPackets
|
||||||
|
repeat(toRemove) {
|
||||||
|
if (packetBuffer.isNotEmpty()) {
|
||||||
|
packetBuffer.removeAt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("DataManager", "清理了 $toRemove 个旧数据包,保留 $maxKeepPackets 个最新数据包")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理处理后数据包缓冲区,保留最新的50个数据包
|
||||||
|
val maxKeepProcessedPackets = 50
|
||||||
|
if (processedPackets.size > maxKeepProcessedPackets) {
|
||||||
|
val toRemove = processedPackets.size - maxKeepProcessedPackets
|
||||||
|
repeat(toRemove) {
|
||||||
|
if (processedPackets.isNotEmpty()) {
|
||||||
|
processedPackets.removeAt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("DataManager", "清理了 $toRemove 个旧处理后数据包,保留 $maxKeepProcessedPackets 个最新数据包")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "清理缓冲区时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算信号质量
|
||||||
|
*/
|
||||||
|
fun calculateSignalQuality(packet: SensorData): Float {
|
||||||
|
if (!streamingSignalProcessorInitialized || streamingSignalProcessor == null) return 0.0f
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取第一个通道的数据进行质量评估
|
||||||
|
val channelData = packet.getChannelData()
|
||||||
|
if (channelData != null && channelData.isNotEmpty()) {
|
||||||
|
val firstChannel = channelData[0]
|
||||||
|
if (firstChannel != null) {
|
||||||
|
val signal = firstChannel.toFloatArray()
|
||||||
|
val quality = streamingSignalProcessor!!.calculateSignalQuality(signal)
|
||||||
|
|
||||||
|
// 添加调试信息
|
||||||
|
Log.d("DataManager", "信号质量计算: 通道0, 数据长度=${signal.size}, 质量=$quality")
|
||||||
|
return quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "计算信号质量时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用信号处理
|
||||||
|
*/
|
||||||
|
fun applySignalProcessing(packets: List<SensorData>): List<SensorData> {
|
||||||
|
Log.d("DataManager", "开始应用信号处理,处理 ${packets.size} 个数据包")
|
||||||
|
|
||||||
|
ensureStreamingSignalProcessor()
|
||||||
|
if (!streamingSignalProcessorInitialized || streamingSignalProcessor == null) {
|
||||||
|
Log.w("DataManager", "流式信号处理器未初始化,跳过信号处理")
|
||||||
|
return packets
|
||||||
|
}
|
||||||
|
|
||||||
|
val processedPackets = mutableListOf<SensorData>()
|
||||||
|
|
||||||
|
for (packet in packets) {
|
||||||
|
try {
|
||||||
|
val processedPacket = processSinglePacket(packet)
|
||||||
|
processedPackets.add(processedPacket)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "处理数据包时发生错误: ${e.message}")
|
||||||
|
processedPackets.add(packet) // 处理失败时返回原始数据包
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("DataManager", "信号处理完成,处理了 ${processedPackets.size} 个数据包")
|
||||||
|
return processedPackets
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个数据包
|
||||||
|
*/
|
||||||
|
private fun processSinglePacket(packet: SensorData): SensorData {
|
||||||
|
val channelData = packet.getChannelData()
|
||||||
|
if (channelData == null || channelData.isEmpty()) {
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
val processedChannels = mutableListOf<List<Float>>()
|
||||||
|
|
||||||
|
for (channel in channelData) {
|
||||||
|
val processedChannel = when (packet.getDataType()) {
|
||||||
|
type.SensorData.DataType.EEG -> applyEEGFilters(channel)
|
||||||
|
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> applyECGFilters(channel)
|
||||||
|
type.SensorData.DataType.PPG -> applyPPGFilters(channel)
|
||||||
|
else -> channel // 其他类型暂时不处理
|
||||||
|
}
|
||||||
|
processedChannels.add(processedChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建处理后的数据包
|
||||||
|
val processedPacket = SensorData()
|
||||||
|
processedPacket.setDataType(packet.getDataType())
|
||||||
|
processedPacket.setTimestamp(packet.getTimestamp())
|
||||||
|
processedPacket.setPacketSn(packet.getPacketSn())
|
||||||
|
processedPacket.setChannelData(processedChannels)
|
||||||
|
|
||||||
|
return processedPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用EEG滤波器
|
||||||
|
*/
|
||||||
|
private fun applyEEGFilters(channel: List<Float>): List<Float> {
|
||||||
|
val signal = channel.toFloatArray()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (streamingSignalProcessor != null) {
|
||||||
|
// 1. 带通滤波 (1-40Hz,EEG主要频率范围)
|
||||||
|
val bandpassFiltered = streamingSignalProcessor!!.bandpassFilter(
|
||||||
|
signal,
|
||||||
|
filterSettings.sampleRate.toFloat(),
|
||||||
|
1.0f,
|
||||||
|
40.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
if (bandpassFiltered != null) {
|
||||||
|
// 2. 幅度归一化
|
||||||
|
val normalized = streamingSignalProcessor!!.normalizeAmplitude(bandpassFiltered)
|
||||||
|
|
||||||
|
if (normalized != null) {
|
||||||
|
return normalized.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "EEG滤波处理失败: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel // 处理失败时返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ECG滤波器
|
||||||
|
*/
|
||||||
|
private fun applyECGFilters(channel: List<Float>): List<Float> {
|
||||||
|
val signal = channel.toFloatArray()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (streamingSignalProcessor != null) {
|
||||||
|
// 1. 高通滤波 (0.5Hz,去除基线漂移)
|
||||||
|
val highpassFiltered = streamingSignalProcessor!!.highpassFilter(
|
||||||
|
signal,
|
||||||
|
filterSettings.sampleRate.toFloat(),
|
||||||
|
0.5f
|
||||||
|
)
|
||||||
|
|
||||||
|
if (highpassFiltered != null) {
|
||||||
|
// 2. 低通滤波 (40Hz,去除高频噪声)
|
||||||
|
val lowpassFiltered = streamingSignalProcessor!!.lowpassFilter(
|
||||||
|
highpassFiltered,
|
||||||
|
filterSettings.sampleRate.toFloat(),
|
||||||
|
40.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
if (lowpassFiltered != null) {
|
||||||
|
// 3. 陷波滤波 (50Hz,去除工频干扰)
|
||||||
|
val notchFiltered = streamingSignalProcessor!!.notchFilter(
|
||||||
|
lowpassFiltered,
|
||||||
|
filterSettings.sampleRate.toFloat(),
|
||||||
|
50.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
if (notchFiltered != null) {
|
||||||
|
return notchFiltered.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "ECG滤波处理失败: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel // 处理失败时返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用PPG滤波器
|
||||||
|
*/
|
||||||
|
private fun applyPPGFilters(channel: List<Float>): List<Float> {
|
||||||
|
val signal = channel.toFloatArray()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (streamingSignalProcessor != null) {
|
||||||
|
// 1. 低通滤波 (8Hz,PPG主要频率范围)
|
||||||
|
val filtered = streamingSignalProcessor!!.lowpassFilter(
|
||||||
|
signal,
|
||||||
|
filterSettings.sampleRate.toFloat(),
|
||||||
|
8.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
if (filtered != null) {
|
||||||
|
// 2. 去运动伪影
|
||||||
|
val motionArtifactRemoved = streamingSignalProcessor!!.processRealtimeChunk(
|
||||||
|
filtered,
|
||||||
|
filterSettings.sampleRate.toFloat()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (motionArtifactRemoved != null) {
|
||||||
|
return motionArtifactRemoved.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "PPG滤波处理失败: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel // 处理失败时返回原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
try {
|
||||||
|
if (parserHandle != 0L) {
|
||||||
|
nativeCallback.destroyStreamParser(parserHandle)
|
||||||
|
parserHandle = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理缓冲区
|
||||||
|
packetBuffer.clear()
|
||||||
|
processedPackets.clear()
|
||||||
|
calculatedMetrics.clear()
|
||||||
|
channelBuffers.clear()
|
||||||
|
processedChannelBuffers.clear()
|
||||||
|
rawStream.reset()
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
totalPacketsParsed = 0L
|
||||||
|
currentDataType = null
|
||||||
|
lastProcessTime = 0L
|
||||||
|
totalProcessedSamples = 0L
|
||||||
|
|
||||||
|
Log.d("DataManager", "资源清理完成")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataManager", "清理资源时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据映射器 - 负责通道映射和数据处理
|
||||||
|
* 处理流程:原始数据 → 解析数据 → 通道映射 → 信号处理 → 指标计算
|
||||||
|
*/
|
||||||
|
class DataMapper {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 加载本地库
|
||||||
|
init {
|
||||||
|
System.loadLibrary("cmake_project_test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地映射器句柄
|
||||||
|
private var mapperHandle: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据映射器
|
||||||
|
*/
|
||||||
|
fun initialize(): Boolean {
|
||||||
|
try {
|
||||||
|
mapperHandle = createDataMapper()
|
||||||
|
if (mapperHandle != 0L) {
|
||||||
|
Log.d("DataMapper", "数据映射器初始化成功,句柄: $mapperHandle")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Log.e("DataMapper", "数据映射器初始化失败")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataMapper", "初始化数据映射器时发生错误: ${e.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射单个传感器数据
|
||||||
|
*/
|
||||||
|
fun mapSensorData(sensorData: SensorData): SensorData? {
|
||||||
|
Log.d("DataMapper", "DEBUG: DataMapper.mapSensorData被调用")
|
||||||
|
Log.d("DataMapper", "DEBUG: mapperHandle = $mapperHandle")
|
||||||
|
Log.d("DataMapper", "DEBUG: 输入数据类型 = ${sensorData.getDataType()}")
|
||||||
|
|
||||||
|
if (mapperHandle == 0L) {
|
||||||
|
Log.w("DataMapper", "数据映射器未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Log.d("DataMapper", "DEBUG: 准备调用JNI mapSensorData")
|
||||||
|
val mappedData = mapSensorData(mapperHandle, sensorData)
|
||||||
|
Log.d("DataMapper", "DEBUG: JNI mapSensorData调用完成")
|
||||||
|
|
||||||
|
if (mappedData != null) {
|
||||||
|
Log.d("DataMapper", "成功映射传感器数据: ${sensorData.getDataType()}")
|
||||||
|
Log.d("DataMapper", "DEBUG: 映射后数据类型 = ${mappedData.getDataType()}")
|
||||||
|
Log.d("DataMapper", "DEBUG: 映射后通道数量 = ${mappedData.getChannelData()?.size ?: 0}")
|
||||||
|
mappedData
|
||||||
|
} else {
|
||||||
|
Log.w("DataMapper", "映射传感器数据失败,返回原始数据")
|
||||||
|
sensorData
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataMapper", "映射传感器数据时发生错误: ${e.message}")
|
||||||
|
Log.e("DataMapper", "DEBUG: 映射异常堆栈", e)
|
||||||
|
sensorData // 出错时返回原始数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量映射传感器数据列表
|
||||||
|
*/
|
||||||
|
fun mapSensorDataList(sensorDataList: List<SensorData>): List<SensorData> {
|
||||||
|
Log.d("DataMapper", "DEBUG: DataMapper.mapSensorDataList被调用")
|
||||||
|
Log.d("DataMapper", "DEBUG: mapperHandle = $mapperHandle")
|
||||||
|
Log.d("DataMapper", "DEBUG: 输入数据包数量 = ${sensorDataList.size}")
|
||||||
|
|
||||||
|
if (mapperHandle == 0L) {
|
||||||
|
Log.w("DataMapper", "数据映射器未初始化")
|
||||||
|
return sensorDataList
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Log.d("DataMapper", "DEBUG: 准备调用JNI mapSensorDataList")
|
||||||
|
val mappedList = mapSensorDataList(mapperHandle, sensorDataList)
|
||||||
|
Log.d("DataMapper", "DEBUG: JNI mapSensorDataList调用完成")
|
||||||
|
|
||||||
|
if (mappedList != null) {
|
||||||
|
Log.d("DataMapper", "成功批量映射 ${sensorDataList.size} 个传感器数据")
|
||||||
|
Log.d("DataMapper", "DEBUG: 映射后数据包数量 = ${mappedList.size}")
|
||||||
|
mappedList
|
||||||
|
} else {
|
||||||
|
Log.w("DataMapper", "批量映射失败,返回原始数据")
|
||||||
|
sensorDataList
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataMapper", "批量映射传感器数据时发生错误: ${e.message}")
|
||||||
|
Log.e("DataMapper", "DEBUG: 批量映射异常堆栈", e)
|
||||||
|
sensorDataList // 出错时返回原始数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
if (mapperHandle != 0L) {
|
||||||
|
try {
|
||||||
|
destroyDataMapper(mapperHandle)
|
||||||
|
mapperHandle = 0L
|
||||||
|
Log.d("DataMapper", "数据映射器资源已清理")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DataMapper", "清理数据映射器资源时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查映射器是否已初始化
|
||||||
|
*/
|
||||||
|
fun isInitialized(): Boolean = mapperHandle != 0L
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// JNI函数声明
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建数据映射器实例
|
||||||
|
* @return 映射器句柄,0表示失败
|
||||||
|
*/
|
||||||
|
private external fun createDataMapper(): Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁数据映射器实例
|
||||||
|
* @param mapperHandle 映射器句柄
|
||||||
|
*/
|
||||||
|
private external fun destroyDataMapper(mapperHandle: Long)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射单个传感器数据
|
||||||
|
* @param mapperHandle 映射器句柄
|
||||||
|
* @param sensorData 传感器数据
|
||||||
|
* @return 映射后的传感器数据,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun mapSensorData(mapperHandle: Long, sensorData: SensorData): SensorData?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量映射传感器数据列表
|
||||||
|
* @param mapperHandle 映射器句柄
|
||||||
|
* @param sensorDataList 传感器数据列表
|
||||||
|
* @return 映射后的传感器数据列表,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun mapSensorDataList(mapperHandle: Long, sensorDataList: List<SensorData>): List<SensorData>?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import type.SensorData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型帮助类
|
||||||
|
* 负责设备类型名称映射和通道数据详情构建
|
||||||
|
*/
|
||||||
|
object DeviceTypeHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型获取设备名称
|
||||||
|
*/
|
||||||
|
fun getDeviceName(dataType: SensorData.DataType): String {
|
||||||
|
return when (dataType) {
|
||||||
|
SensorData.DataType.EEG -> "脑电设备"
|
||||||
|
SensorData.DataType.ECG_2LEAD -> "胸腹设备"
|
||||||
|
SensorData.DataType.PPG -> "血氧设备"
|
||||||
|
SensorData.DataType.ECG_12LEAD -> "12导联心电"
|
||||||
|
SensorData.DataType.STETHOSCOPE -> "数字听诊"
|
||||||
|
SensorData.DataType.SNORE -> "鼾声设备"
|
||||||
|
SensorData.DataType.RESPIRATION -> "呼吸/姿态"
|
||||||
|
SensorData.DataType.MIT_BIH -> "MIT-BIH"
|
||||||
|
else -> "未知设备"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建通道数据详情字符串
|
||||||
|
*/
|
||||||
|
fun buildChannelDetails(
|
||||||
|
data: List<SensorData>,
|
||||||
|
maxPackets: Int = Constants.MAX_DETAIL_PACKETS,
|
||||||
|
maxChannels: Int = Constants.MAX_DISPLAY_CHANNELS,
|
||||||
|
maxSamples: Int = Constants.MAX_DISPLAY_SAMPLES
|
||||||
|
): String {
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
return "无通道数据"
|
||||||
|
}
|
||||||
|
|
||||||
|
val details = mutableListOf<String>()
|
||||||
|
|
||||||
|
data.take(maxPackets).forEachIndexed { packetIndex, sensorData ->
|
||||||
|
if (sensorData.channelData.isNullOrEmpty()) {
|
||||||
|
details.add("数据包 ${packetIndex + 1}: 无通道数据")
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
details.add("数据包 ${packetIndex + 1} (${getDeviceName(sensorData.dataType ?: SensorData.DataType.EEG)}):")
|
||||||
|
|
||||||
|
sensorData.channelData.take(maxChannels).forEachIndexed { channelIndex, channel ->
|
||||||
|
if (channel.isNullOrEmpty()) {
|
||||||
|
details.add(" 通道 ${channelIndex + 1}: 空数据")
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只显示前几个采样点
|
||||||
|
val sampleCount = minOf(maxSamples, channel.size)
|
||||||
|
val channelDataStr = channel.take(sampleCount).joinToString(", ") { "%.1f".format(it) }
|
||||||
|
|
||||||
|
details.add(" 通道 ${channelIndex + 1}: ${sampleCount}/${channel.size} 采样点")
|
||||||
|
details.add(" 示例: $channelDataStr${if (channel.size > sampleCount) "..." else ""}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sensorData.channelData.size > maxChannels) {
|
||||||
|
details.add(" ... 还有 ${sensorData.channelData.size - maxChannels} 个通道")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分隔线
|
||||||
|
details.add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size > maxPackets) {
|
||||||
|
details.add("... 还有 ${data.size - maxPackets} 个数据包")
|
||||||
|
}
|
||||||
|
|
||||||
|
return details.joinToString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件帮助类
|
||||||
|
* 负责文件读取操作
|
||||||
|
*/
|
||||||
|
object FileHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 assets 文件夹读取文件到字节数组
|
||||||
|
*/
|
||||||
|
fun readAssetFile(context: Context, fileName: String): ByteArray? {
|
||||||
|
return try {
|
||||||
|
context.assets.open(fileName).use { inputStream ->
|
||||||
|
// 使用更可靠的文件读取方式
|
||||||
|
val bytes = mutableListOf<Byte>()
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var bytesRead: Int
|
||||||
|
|
||||||
|
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||||
|
for (i in 0 until bytesRead) {
|
||||||
|
bytes.add(buffer[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.toByteArray()
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("FileHelper", "Error reading asset file: $fileName", e)
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("FileHelper", "Unexpected error reading asset file: $fileName", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滤波器设置类
|
||||||
|
* 管理各种信号处理的参数配置
|
||||||
|
*/
|
||||||
|
data class FilterSettings(
|
||||||
|
// 采样率设置
|
||||||
|
val sampleRate: Double = 1000.0,
|
||||||
|
|
||||||
|
// EEG滤波器参数
|
||||||
|
val eegLowPass: Double = 40.0,
|
||||||
|
val eegHighPass: Double = 1.0,
|
||||||
|
|
||||||
|
// ECG滤波器参数
|
||||||
|
val ecgHighPass: Double = 0.5, // 高通滤波,去除基线漂移
|
||||||
|
val ecgLowPass: Double = 40.0, // 低通滤波,去除高频噪声
|
||||||
|
val ecgNotchFreq: Double = 50.0, // 陷波频率,去除工频干扰
|
||||||
|
val ecgNotchQ: Double = 30.0, // 陷波品质因数
|
||||||
|
|
||||||
|
// PPG滤波器参数
|
||||||
|
val ppgLowPass: Double = 8.0, // 低通滤波,PPG主要频率范围
|
||||||
|
|
||||||
|
// 通用滤波器参数
|
||||||
|
val generalLowPass: Double = 100.0,
|
||||||
|
val generalHighPass: Double = 0.1,
|
||||||
|
val generalNotchFreq: Double = 50.0,
|
||||||
|
val generalNotchQ: Double = 30.0
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
// 预设配置
|
||||||
|
val EEG_DEFAULT = FilterSettings(
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
eegLowPass = 40.0,
|
||||||
|
eegHighPass = 1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
val ECG_DEFAULT = FilterSettings(
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
ecgHighPass = 0.5,
|
||||||
|
ecgLowPass = 40.0,
|
||||||
|
ecgNotchFreq = 50.0,
|
||||||
|
ecgNotchQ = 30.0
|
||||||
|
)
|
||||||
|
|
||||||
|
val PPG_DEFAULT = FilterSettings(
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
ppgLowPass = 8.0
|
||||||
|
)
|
||||||
|
|
||||||
|
val HIGH_QUALITY = FilterSettings(
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
eegLowPass = 50.0,
|
||||||
|
eegHighPass = 0.5,
|
||||||
|
ecgHighPass = 0.3,
|
||||||
|
ecgLowPass = 50.0,
|
||||||
|
ecgNotchFreq = 50.0,
|
||||||
|
ecgNotchQ = 50.0,
|
||||||
|
ppgLowPass = 10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
val REAL_TIME = FilterSettings(
|
||||||
|
sampleRate = 1000.0,
|
||||||
|
eegLowPass = 30.0,
|
||||||
|
eegHighPass = 2.0,
|
||||||
|
ecgHighPass = 1.0,
|
||||||
|
ecgLowPass = 30.0,
|
||||||
|
ecgNotchFreq = 50.0,
|
||||||
|
ecgNotchQ = 20.0,
|
||||||
|
ppgLowPass = 6.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型获取相应的滤波器设置
|
||||||
|
*/
|
||||||
|
fun getSettingsForDataType(dataType: type.SensorData.DataType): FilterSettings {
|
||||||
|
return when (dataType) {
|
||||||
|
type.SensorData.DataType.EEG -> EEG_DEFAULT
|
||||||
|
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> ECG_DEFAULT
|
||||||
|
type.SensorData.DataType.PPG -> PPG_DEFAULT
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新采样率
|
||||||
|
*/
|
||||||
|
fun updateSampleRate(newSampleRate: Double): FilterSettings {
|
||||||
|
return copy(sampleRate = newSampleRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新EEG滤波器参数
|
||||||
|
*/
|
||||||
|
fun updateEEGSettings(
|
||||||
|
lowPass: Double? = null,
|
||||||
|
highPass: Double? = null
|
||||||
|
): FilterSettings {
|
||||||
|
return copy(
|
||||||
|
eegLowPass = lowPass ?: eegLowPass,
|
||||||
|
eegHighPass = highPass ?: eegHighPass
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新ECG滤波器参数
|
||||||
|
*/
|
||||||
|
fun updateECGSettings(
|
||||||
|
highPass: Double? = null,
|
||||||
|
lowPass: Double? = null,
|
||||||
|
notchFreq: Double? = null,
|
||||||
|
notchQ: Double? = null
|
||||||
|
): FilterSettings {
|
||||||
|
return copy(
|
||||||
|
ecgHighPass = highPass ?: ecgHighPass,
|
||||||
|
ecgLowPass = lowPass ?: ecgLowPass,
|
||||||
|
ecgNotchFreq = notchFreq ?: ecgNotchFreq,
|
||||||
|
ecgNotchQ = notchQ ?: ecgNotchQ
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新PPG滤波器参数
|
||||||
|
*/
|
||||||
|
fun updatePPGSettings(lowPass: Double? = null): FilterSettings {
|
||||||
|
return copy(ppgLowPass = lowPass ?: ppgLowPass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标计算器 - 负责计算各种生理指标
|
||||||
|
* 处理流程:原始数据 → 解析数据 → 通道映射 → 信号处理 → 指标计算
|
||||||
|
*/
|
||||||
|
class IndicatorCalculator {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 加载本地库
|
||||||
|
init {
|
||||||
|
System.loadLibrary("cmake_project_test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地计算器句柄
|
||||||
|
private var calculatorHandle: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化指标计算器
|
||||||
|
*/
|
||||||
|
fun initialize(): Boolean {
|
||||||
|
try {
|
||||||
|
calculatorHandle = createIndicatorCalculator()
|
||||||
|
if (calculatorHandle != 0L) {
|
||||||
|
Log.d("IndicatorCalculator", "指标计算器初始化成功,句柄: $calculatorHandle")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Log.e("IndicatorCalculator", "指标计算器初始化失败")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "初始化指标计算器时发生错误: ${e.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整数据处理流程
|
||||||
|
* 输入原始数据,输出所有计算的指标
|
||||||
|
*/
|
||||||
|
fun processCompletePipeline(sensorData: SensorData, sampleRate: Float): Map<String, Float>? {
|
||||||
|
if (calculatorHandle == 0L) {
|
||||||
|
Log.w("IndicatorCalculator", "指标计算器未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val metrics = processCompletePipeline(calculatorHandle, sensorData, sampleRate)
|
||||||
|
if (metrics != null) {
|
||||||
|
Log.d("IndicatorCalculator", "完整数据处理流程完成,计算了 ${metrics.size} 个指标")
|
||||||
|
metrics
|
||||||
|
} else {
|
||||||
|
Log.w("IndicatorCalculator", "完整数据处理流程失败")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "完整数据处理流程时发生错误: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算ECG指标
|
||||||
|
*/
|
||||||
|
fun calculateECGMetrics(sensorData: SensorData, sampleRate: Float): Map<String, Float>? {
|
||||||
|
if (calculatorHandle == 0L) {
|
||||||
|
Log.w("IndicatorCalculator", "指标计算器未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val metrics = calculateECGMetrics(calculatorHandle, sensorData, sampleRate)
|
||||||
|
if (metrics != null) {
|
||||||
|
Log.d("IndicatorCalculator", "ECG指标计算完成,计算了 ${metrics.size} 个指标")
|
||||||
|
metrics
|
||||||
|
} else {
|
||||||
|
Log.w("IndicatorCalculator", "ECG指标计算失败")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "ECG指标计算时发生错误: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算PPG指标
|
||||||
|
*/
|
||||||
|
fun calculatePPGMetrics(sensorData: SensorData, sampleRate: Float): Map<String, Float>? {
|
||||||
|
if (calculatorHandle == 0L) {
|
||||||
|
Log.w("IndicatorCalculator", "指标计算器未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val metrics = calculatePPGMetrics(calculatorHandle, sensorData, sampleRate)
|
||||||
|
if (metrics != null) {
|
||||||
|
Log.d("IndicatorCalculator", "PPG指标计算完成,计算了 ${metrics.size} 个指标")
|
||||||
|
metrics
|
||||||
|
} else {
|
||||||
|
Log.w("IndicatorCalculator", "PPG指标计算失败")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "PPG指标计算时发生错误: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算HRV指标
|
||||||
|
*/
|
||||||
|
fun calculateHRVMetrics(rrIntervals: List<Float>): Map<String, Float>? {
|
||||||
|
if (calculatorHandle == 0L) {
|
||||||
|
Log.w("IndicatorCalculator", "指标计算器未初始化")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val metrics = calculateHRVMetrics(calculatorHandle, rrIntervals)
|
||||||
|
if (metrics != null) {
|
||||||
|
Log.d("IndicatorCalculator", "HRV指标计算完成,计算了 ${metrics.size} 个指标")
|
||||||
|
metrics
|
||||||
|
} else {
|
||||||
|
Log.w("IndicatorCalculator", "HRV指标计算失败")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "HRV指标计算时发生错误: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
if (calculatorHandle != 0L) {
|
||||||
|
try {
|
||||||
|
destroyIndicatorCalculator(calculatorHandle)
|
||||||
|
calculatorHandle = 0L
|
||||||
|
Log.d("IndicatorCalculator", "指标计算器资源已清理")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("IndicatorCalculator", "清理指标计算器资源时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查计算器是否已初始化
|
||||||
|
*/
|
||||||
|
fun isInitialized(): Boolean = calculatorHandle != 0L
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// JNI函数声明
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指标计算器实例
|
||||||
|
* @return 计算器句柄,0表示失败
|
||||||
|
*/
|
||||||
|
private external fun createIndicatorCalculator(): Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁指标计算器实例
|
||||||
|
* @param calculatorHandle 计算器句柄
|
||||||
|
*/
|
||||||
|
private external fun destroyIndicatorCalculator(calculatorHandle: Long)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整数据处理流程
|
||||||
|
* @param calculatorHandle 计算器句柄
|
||||||
|
* @param sensorData 传感器数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return 计算的指标映射,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun processCompletePipeline(calculatorHandle: Long, sensorData: SensorData, sampleRate: Float): Map<String, Float>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算ECG指标
|
||||||
|
* @param calculatorHandle 计算器句柄
|
||||||
|
* @param sensorData 传感器数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return 计算的ECG指标映射,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun calculateECGMetrics(calculatorHandle: Long, sensorData: SensorData, sampleRate: Float): Map<String, Float>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算PPG指标
|
||||||
|
* @param calculatorHandle 计算器句柄
|
||||||
|
* @param sensorData 传感器数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return 计算的PPG指标映射,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun calculatePPGMetrics(calculatorHandle: Long, sensorData: SensorData, sampleRate: Float): Map<String, Float>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算HRV指标
|
||||||
|
* @param calculatorHandle 计算器句柄
|
||||||
|
* @param rrIntervals RR间期列表
|
||||||
|
* @return 计算的HRV指标映射,null表示失败
|
||||||
|
*/
|
||||||
|
private external fun calculateHRVMetrics(calculatorHandle: Long, rrIntervals: List<Float>): Map<String, Float>?
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,16 @@
|
||||||
package com.example.cmake_project_test
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
import type.SensorData
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.example.cmake_project_test.databinding.ActivityMainBinding
|
import com.example.cmake_project_test.databinding.ActivityMainBinding
|
||||||
import java.io.InputStream
|
import type.SensorData
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity(), NativeMethodCallback {
|
||||||
|
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private var parserHandle: Long = 0L
|
private lateinit var dataManager: DataManager
|
||||||
private val rawStream = ByteArrayOutputStream(4096)
|
private lateinit var uiManager: UiManager
|
||||||
private val uiUpdatePending = AtomicBoolean(false)
|
|
||||||
private val packetBuffer = mutableListOf<SensorData>()
|
|
||||||
private var lastUpdateTime = 0L
|
|
||||||
private val UPDATE_INTERVAL = 500L // 每500毫秒更新一次UI
|
|
||||||
private var totalPacketsParsed = 0L // 总共解析的数据包数量
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -27,304 +18,322 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
// 初始化UI
|
// 初始化管理器
|
||||||
binding.sampleText.text = "正在初始化..."
|
dataManager = DataManager(this) // 传入this作为NativeMethodCallback
|
||||||
|
uiManager = UiManager()
|
||||||
|
|
||||||
// 设置按钮点击事件
|
// 初始化UI
|
||||||
binding.btnReset.setOnClickListener {
|
binding.sampleText.text = "正在初始化...\n\n请稍候,正在加载数据文件..."
|
||||||
resetData()
|
|
||||||
}
|
// 移除按钮点击事件,只保留流式读取功能
|
||||||
|
|
||||||
binding.btnReload.setOnClickListener {
|
|
||||||
reloadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在后台线程处理数据加载和解析
|
// 在后台线程处理数据加载和解析
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
try {
|
||||||
|
Log.d("MainActivity", "开始加载数据文件...")
|
||||||
|
|
||||||
// 从 assets 文件夹读取文件
|
// 从 assets 文件夹读取文件
|
||||||
val fileData = readAssetFile("data1.dat")
|
val fileData = FileHelper.readAssetFile(this, Constants.DEFAULT_DATA_FILE)
|
||||||
|
Log.d("MainActivity", "文件读取结果: ${if (fileData != null) "成功,大小: ${fileData.size} 字节" else "失败"}")
|
||||||
|
|
||||||
if (fileData != null) {
|
if (fileData != null) {
|
||||||
// 使用真实流式解析路径:按块喂数据 -> drain -> 展示
|
// 先显示文件读取成功的信息
|
||||||
ensureParser()
|
runOnUiThread {
|
||||||
rawStream.reset()
|
binding.sampleText.text = "文件读取成功!\n文件大小: ${fileData.size} 字节\n\n正在处理数据,请稍候..."
|
||||||
val chunkSize = 64
|
|
||||||
var offset = 0
|
|
||||||
|
|
||||||
while (offset < fileData.size) {
|
|
||||||
val n = minOf(chunkSize, fileData.size - offset)
|
|
||||||
val chunk = fileData.copyOfRange(offset, offset + n)
|
|
||||||
onBleNotify(chunk) // 复用同一流式路径
|
|
||||||
offset += n
|
|
||||||
|
|
||||||
// 添加小延迟,模拟真实蓝牙数据传输
|
|
||||||
Thread.sleep(10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最后拉取所有剩余包
|
// 使用真实流式解析路径:按块喂数据 -> drain -> 展示
|
||||||
val packets = streamParserDrainPackets(parserHandle)
|
Log.d("MainActivity", "开始处理文件数据...")
|
||||||
if (!packets.isNullOrEmpty()) {
|
try {
|
||||||
totalPacketsParsed += packets.size // 更新总计数
|
// 添加进度回调
|
||||||
packetBuffer.addAll(packets)
|
dataManager.processFileData(fileData) { progress ->
|
||||||
scheduleUiUpdate()
|
// 进度更新回调
|
||||||
|
runOnUiThread {
|
||||||
|
val progressText = buildString {
|
||||||
|
append("文件读取成功!\n")
|
||||||
|
append("文件大小: ${fileData.size} 字节\n\n")
|
||||||
|
append("正在处理数据...\n")
|
||||||
|
append("进度: $progress%\n")
|
||||||
|
append("进度条: ${"█".repeat(progress / 5)}${"░".repeat(20 - (progress / 5))}\n")
|
||||||
|
append("请稍候...")
|
||||||
|
}
|
||||||
|
binding.sampleText.text = progressText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("MainActivity", "文件数据处理完成")
|
||||||
|
|
||||||
|
// 直接更新UI,显示加载结果
|
||||||
|
runOnUiThread {
|
||||||
|
try {
|
||||||
|
Log.d("MainActivity", "开始UI更新流程")
|
||||||
|
|
||||||
|
// 暂时禁用测试指标计算功能,避免闪退
|
||||||
|
Log.d("MainActivity", "跳过测试指标计算功能")
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
dataManager.testIndicatorCalculation()
|
||||||
|
Log.d("MainActivity", "测试指标计算完成")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "测试指标计算失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Log.d("MainActivity", "开始构建显示内容")
|
||||||
|
val displayContent = try {
|
||||||
|
// 构建详细的显示内容,包括映射信息
|
||||||
|
val detailedContent = buildString {
|
||||||
|
append("=== 设备数据概览 ===\n")
|
||||||
|
append("原始数据包: ${dataManager.getPacketBufferSize()} 个\n")
|
||||||
|
append("映射后数据包: ${dataManager.getProcessedPacketsSize()} 个\n")
|
||||||
|
append("计算指标数: ${dataManager.getCalculatedMetricsSize()} 个\n")
|
||||||
|
append("总数据量: ${dataManager.getRawStreamSize()} 字节\n")
|
||||||
|
append("总共解析: ${dataManager.getTotalPacketsParsed()} 个数据包\n")
|
||||||
|
|
||||||
|
// 显示原始数据包信息
|
||||||
|
if (dataManager.getPacketBufferSize() > 0) {
|
||||||
|
append("\n=== 原始数据信息 ===\n")
|
||||||
|
try {
|
||||||
|
val firstPacket = dataManager.getPacketBuffer().firstOrNull()
|
||||||
|
if (firstPacket != null) {
|
||||||
|
append("第一个数据包类型: ${firstPacket.getDataType()}\n")
|
||||||
|
append("第一个数据包序号: ${firstPacket.getPacketSn()}\n")
|
||||||
|
append("第一个数据包时间戳: ${firstPacket.getTimestamp()}\n")
|
||||||
|
|
||||||
|
val channelData = firstPacket.getChannelData()
|
||||||
|
if (channelData != null) {
|
||||||
|
append("通道数量: ${channelData.size}\n")
|
||||||
|
if (channelData.isNotEmpty()) {
|
||||||
|
val firstChannel = channelData[0]
|
||||||
|
append("第一个通道数据点数: ${firstChannel.size}\n")
|
||||||
|
if (firstChannel.isNotEmpty()) {
|
||||||
|
append("第一个通道前3个值: ${firstChannel.take(3).joinToString(", ")}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
append("获取原始数据包信息时出错: ${e.message}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示映射后的数据包信息
|
||||||
|
if (dataManager.getProcessedPacketsSize() > 0) {
|
||||||
|
append("\n=== 映射后数据信息 ===\n")
|
||||||
|
try {
|
||||||
|
val processedPackets = dataManager.getProcessedPackets()
|
||||||
|
append("DEBUG: 处理后数据包总数: ${processedPackets.size}\n")
|
||||||
|
|
||||||
|
// 统计ECG_12LEAD数据包的通道数量分布
|
||||||
|
val ecg12LeadPackets = processedPackets.filter { it.getDataType() == type.SensorData.DataType.ECG_12LEAD }
|
||||||
|
val packetsWith8Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 8 }
|
||||||
|
val packetsWith12Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 12 }
|
||||||
|
append("DEBUG: ECG_12LEAD数据包统计 - 8通道: ${packetsWith8Channels}个, 12通道: ${packetsWith12Channels}个\n")
|
||||||
|
|
||||||
|
// 详细统计前10个ECG_12LEAD数据包
|
||||||
|
val first10EcgPackets = ecg12LeadPackets.take(10)
|
||||||
|
append("DEBUG: 前10个ECG_12LEAD数据包详情:\n")
|
||||||
|
first10EcgPackets.forEachIndexed { index, packet ->
|
||||||
|
append(" 数据包${index + 1}: 序号=${packet.getPacketSn()}, 通道数=${packet.getChannelData()?.size}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找第一个映射成功的ECG_12LEAD数据包(12通道)
|
||||||
|
val mappedEcg12LeadPacket = processedPackets.find { packet ->
|
||||||
|
packet.getDataType() == type.SensorData.DataType.ECG_12LEAD &&
|
||||||
|
packet.getChannelData()?.size == 12
|
||||||
|
}
|
||||||
|
val firstMappedPacket = mappedEcg12LeadPacket ?: processedPackets.firstOrNull()
|
||||||
|
|
||||||
|
if (firstMappedPacket != null) {
|
||||||
|
append("选择的数据包类型: ${firstMappedPacket.getDataType()}\n")
|
||||||
|
append("选择的数据包序号: ${firstMappedPacket.getPacketSn()}\n")
|
||||||
|
append("选择的数据包时间戳: ${firstMappedPacket.getTimestamp()}\n")
|
||||||
|
|
||||||
|
val mappedChannelData = firstMappedPacket.getChannelData()
|
||||||
|
if (mappedChannelData != null) {
|
||||||
|
append("映射后通道数量: ${mappedChannelData.size}\n")
|
||||||
|
append("DEBUG: 通道数据详情: ${mappedChannelData}\n")
|
||||||
|
if (mappedChannelData.isNotEmpty()) {
|
||||||
|
val firstMappedChannel = mappedChannelData[0]
|
||||||
|
append("第一个映射通道数据点数: ${firstMappedChannel.size}\n")
|
||||||
|
if (firstMappedChannel.isNotEmpty()) {
|
||||||
|
append("第一个映射通道前3个值: ${firstMappedChannel.take(3).joinToString(", ")}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示第二个通道数据
|
||||||
|
if (mappedChannelData.size > 1) {
|
||||||
|
val secondMappedChannel = mappedChannelData[1]
|
||||||
|
append("第二个映射通道数据点数: ${secondMappedChannel.size}\n")
|
||||||
|
if (secondMappedChannel.isNotEmpty()) {
|
||||||
|
append("第二个映射通道前3个值: ${secondMappedChannel.take(3).joinToString(", ")}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
append("获取映射数据包信息时出错: ${e.message}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示计算出的指标
|
||||||
|
if (dataManager.getCalculatedMetricsSize() > 0) {
|
||||||
|
append("\n=== 计算指标信息 ===\n")
|
||||||
|
try {
|
||||||
|
val latestMetrics = dataManager.getLatestMetrics()
|
||||||
|
if (latestMetrics != null) {
|
||||||
|
append("最新指标数量: ${latestMetrics.size}\n")
|
||||||
|
append("指标详情:\n")
|
||||||
|
latestMetrics.forEach { (key, value) ->
|
||||||
|
append(" $key: ${String.format("%.2f", value)}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示所有指标的平均值
|
||||||
|
val allMetrics = dataManager.getCalculatedMetrics()
|
||||||
|
if (allMetrics.isNotEmpty()) {
|
||||||
|
val heartRates = allMetrics.mapNotNull { metrics -> metrics["heart_rate"] }.filter { it > 0 }
|
||||||
|
val qualities = allMetrics.mapNotNull { metrics -> metrics["signal_quality"] }.filter { it >= 0 }
|
||||||
|
|
||||||
|
if (heartRates.isNotEmpty()) {
|
||||||
|
val avgHeartRate = heartRates.average()
|
||||||
|
append("平均心率: ${String.format("%.1f", avgHeartRate)} bpm\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualities.isNotEmpty()) {
|
||||||
|
val avgQuality = qualities.average()
|
||||||
|
append("平均信号质量: ${String.format("%.2f", avgQuality)}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
append("获取指标信息时出错: ${e.message}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\n=== 状态信息 ===\n")
|
||||||
|
append("数据解析完成 ✓\n")
|
||||||
|
append("数据映射完成 ✓\n")
|
||||||
|
append("信号处理完成 ✓\n")
|
||||||
|
append("指标计算完成 ✓\n")
|
||||||
|
append("应用已就绪,可以开始使用\n")
|
||||||
|
|
||||||
|
// 显示流式处理状态
|
||||||
|
val processingStatus = dataManager.getProcessingStatus()
|
||||||
|
append("\n=== 流式处理状态 ===\n")
|
||||||
|
append("当前数据类型: ${processingStatus["currentDataType"]}\n")
|
||||||
|
append("总样本数: ${processingStatus["totalSamples"]}/${processingStatus["minSamplesRequired"]}\n")
|
||||||
|
append("距离上次处理: ${processingStatus["timeSinceLastProcess"]}ms/${processingStatus["processingInterval"]}ms\n")
|
||||||
|
append("总处理样本数: ${processingStatus["totalProcessedSamples"]}\n")
|
||||||
|
|
||||||
|
// 显示流式指标计算结果
|
||||||
|
val latestMetrics = dataManager.getLatestMetrics()
|
||||||
|
if (latestMetrics.isNotEmpty()) {
|
||||||
|
append("\n=== 流式指标计算结果 ===\n")
|
||||||
|
append("基于流式数据处理的最新指标:\n")
|
||||||
|
latestMetrics.forEach { (key, value) ->
|
||||||
|
append(" $key: $value\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
append("\n=== 流式指标计算结果 ===\n")
|
||||||
|
append("暂无流式指标计算结果\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示原始通道缓冲区状态
|
||||||
|
val channelBuffers = dataManager.getChannelBuffersStatus()
|
||||||
|
if (channelBuffers.isNotEmpty()) {
|
||||||
|
append("\n=== 原始通道缓冲区状态 ===\n")
|
||||||
|
channelBuffers.forEach { (channel, sampleCount) ->
|
||||||
|
append(" 通道 $channel: $sampleCount 个样本\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示处理后通道缓冲区状态
|
||||||
|
val processedChannelBuffers = dataManager.getProcessedChannelBuffersStatus()
|
||||||
|
if (processedChannelBuffers.isNotEmpty()) {
|
||||||
|
append("\n=== 处理后通道缓冲区状态 ===\n")
|
||||||
|
processedChannelBuffers.forEach { (channel, sampleCount) ->
|
||||||
|
append(" 通道 $channel: $sampleCount 个样本\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detailedContent
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "构建显示内容失败: ${e.message}", e)
|
||||||
|
"构建显示内容失败: ${e.message}\n\n请检查数据格式。"
|
||||||
|
}
|
||||||
|
Log.d("MainActivity", "显示内容构建完成,长度: ${displayContent.length}")
|
||||||
|
|
||||||
|
try {
|
||||||
|
binding.sampleText.text = displayContent
|
||||||
|
Log.d("MainActivity", "UI文本设置成功")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "设置UI文本失败: ${e.message}", e)
|
||||||
|
binding.sampleText.text = "设置UI文本失败: ${e.message}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
Log.d("MainActivity", "UI更新完成,数据包数量: ${dataManager.getPacketBufferSize()}, 总解析: ${dataManager.getTotalPacketsParsed()}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "UI更新失败: ${e.message}", e)
|
||||||
|
val errorMessage = buildString {
|
||||||
|
append("UI更新失败: ${e.message}\n\n")
|
||||||
|
append("数据包数量: ${dataManager.getPacketBufferSize()}\n")
|
||||||
|
append("总解析: ${dataManager.getTotalPacketsParsed()}\n")
|
||||||
|
append("错误详情: ${e.stackTraceToString()}")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
binding.sampleText.text = errorMessage
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
Log.e("MainActivity", "设置错误信息也失败: ${e2.message}", e2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "数据处理失败: ${e.message}", e)
|
||||||
|
runOnUiThread {
|
||||||
|
binding.sampleText.text = "数据处理失败: ${e.message}\n\n请检查数据文件格式是否正确。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.sampleText.text = "读取文件失败"
|
binding.sampleText.text = "读取文件失败:无法从assets文件夹读取${Constants.DEFAULT_DATA_FILE}\n\n请检查:\n1. assets文件夹中是否存在该文件\n2. 文件名是否正确\n3. 文件是否损坏"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "Error processing data", e)
|
Log.e("MainActivity", "Error processing data", e)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.sampleText.text = "错误: ${e.message}"
|
binding.sampleText.text = "错误: ${e.message}\n\n请检查assets文件夹中是否存在${Constants.DEFAULT_DATA_FILE}文件"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
// 设置线程名称,便于调试
|
||||||
|
name = "DataLoadingThread"
|
||||||
|
// 设置为守护线程,避免阻塞应用退出
|
||||||
|
isDaemon = true
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
ensureParser()
|
dataManager.ensureParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
if (parserHandle != 0L) {
|
dataManager.cleanup()
|
||||||
destroyStreamParser(parserHandle)
|
|
||||||
parserHandle = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureParser() {
|
|
||||||
if (parserHandle == 0L) {
|
|
||||||
parserHandle = createStreamParser()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 蓝牙通知回调时调用:将 chunk 追加到解析器并拉取新包
|
// 蓝牙通知回调时调用:将 chunk 追加到解析器并拉取新包
|
||||||
private fun onBleNotify(chunk: ByteArray) {
|
fun onBleNotify(chunk: ByteArray) {
|
||||||
if (chunk.isEmpty()) return
|
dataManager.onBleNotify(chunk)
|
||||||
ensureParser()
|
|
||||||
rawStream.write(chunk)
|
|
||||||
streamParserAppend(parserHandle, chunk)
|
|
||||||
|
|
||||||
// 拉取解析出的数据包,但不立即更新UI
|
|
||||||
val packets = streamParserDrainPackets(parserHandle)
|
|
||||||
if (!packets.isNullOrEmpty()) {
|
|
||||||
totalPacketsParsed += packets.size // 更新总计数
|
|
||||||
packetBuffer.addAll(packets)
|
|
||||||
scheduleUiUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计划UI更新,避免频繁刷新
|
|
||||||
private fun scheduleUiUpdate() {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastUpdateTime >= UPDATE_INTERVAL && uiUpdatePending.compareAndSet(false, true)) {
|
|
||||||
lastUpdateTime = currentTime
|
|
||||||
runOnUiThread {
|
|
||||||
updateUiWithBufferedPackets()
|
|
||||||
uiUpdatePending.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用缓冲的数据包更新UI
|
|
||||||
private fun updateUiWithBufferedPackets() {
|
|
||||||
if (packetBuffer.isEmpty()) return
|
|
||||||
|
|
||||||
// 获取设备类型信息
|
|
||||||
val deviceTypes = packetBuffer.mapNotNull { it.dataType }.distinct()
|
|
||||||
val deviceInfo = deviceTypes.joinToString(", ") { getDeviceName(it) }
|
|
||||||
|
|
||||||
// 构建统计信息
|
|
||||||
val stats = buildString {
|
|
||||||
append("=== 设备数据概览 ===\n")
|
|
||||||
append("当前缓冲区: ${packetBuffer.size} 个数据包\n")
|
|
||||||
append("总共解析: ${totalPacketsParsed} 个数据包\n")
|
|
||||||
append("设备类型: $deviceInfo\n")
|
|
||||||
append("总数据量: ${rawStream.size()} 字节\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只显示最新的一些数据包详情
|
|
||||||
val recentPackets = packetBuffer.takeLast(5) // 只显示最后5个包
|
|
||||||
val has12Lead = recentPackets.any { it.dataType == SensorData.DataType.ECG_12LEAD }
|
|
||||||
val channelDetails = if (has12Lead) {
|
|
||||||
buildChannelDetails(recentPackets, maxPackets = 3, maxChannels = 6, maxSamples = 10)
|
|
||||||
} else {
|
|
||||||
buildChannelDetails(recentPackets, maxPackets = 3, maxChannels = 4, maxSamples = 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新UI
|
|
||||||
binding.sampleText.text = stats + channelDetails
|
|
||||||
|
|
||||||
// 智能清理缓冲区:如果数据包数量过多,保留更多用于统计
|
|
||||||
if (packetBuffer.size > 50) {
|
|
||||||
val keepCount = minOf(30, packetBuffer.size / 2) // 保留30个或一半,取较小值
|
|
||||||
packetBuffer.subList(0, packetBuffer.size - keepCount).clear()
|
|
||||||
Log.d("MainActivity", "缓冲区已清理,保留 $keepCount 个数据包")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加调试信息:显示实际的数据包数量变化
|
// 触发UI更新
|
||||||
Log.d("MainActivity", "当前缓冲区数据包数量: ${packetBuffer.size}, 总共解析: $totalPacketsParsed")
|
uiManager.scheduleUiUpdate(dataManager) {
|
||||||
}
|
uiManager.updateDisplay(dataManager) { text ->
|
||||||
|
binding.sampleText.text = text
|
||||||
/**
|
|
||||||
* 根据数据类型获取设备名称
|
|
||||||
*/
|
|
||||||
private fun getDeviceName(dataType: SensorData.DataType): String {
|
|
||||||
return when (dataType) {
|
|
||||||
SensorData.DataType.EEG -> "脑电设备"
|
|
||||||
SensorData.DataType.ECG_2LEAD -> "胸腹设备"
|
|
||||||
SensorData.DataType.PPG -> "血氧设备"
|
|
||||||
SensorData.DataType.ECG_12LEAD -> "12导联心电"
|
|
||||||
SensorData.DataType.STETHOSCOPE -> "数字听诊"
|
|
||||||
SensorData.DataType.SNORE -> "鼾声设备"
|
|
||||||
SensorData.DataType.RESPIRATION -> "呼吸/姿态"
|
|
||||||
SensorData.DataType.MIT_BIH -> "MIT-BIH"
|
|
||||||
else -> "未知设备"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 assets 文件夹读取文件到字节数组
|
|
||||||
*/
|
|
||||||
private fun readAssetFile(fileName: String): ByteArray? {
|
|
||||||
return try {
|
|
||||||
assets.open(fileName).use { inputStream ->
|
|
||||||
ByteArray(inputStream.available()).also {
|
|
||||||
inputStream.read(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e("MainActivity", "Error reading asset file", e)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建通道数据详情字符串
|
|
||||||
*/
|
|
||||||
private fun buildChannelDetails(data: List<SensorData>, maxPackets: Int = 3, maxChannels: Int = 2, maxSamples: Int = 10): String {
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
return "无通道数据"
|
|
||||||
}
|
|
||||||
|
|
||||||
val details = mutableListOf<String>()
|
|
||||||
|
|
||||||
data.take(maxPackets).forEachIndexed { packetIndex, sensorData ->
|
|
||||||
if (sensorData.channelData.isNullOrEmpty()) {
|
|
||||||
details.add("数据包 ${packetIndex + 1}: 无通道数据")
|
|
||||||
return@forEachIndexed
|
|
||||||
}
|
|
||||||
|
|
||||||
details.add("数据包 ${packetIndex + 1} (${getDeviceName(sensorData.dataType ?: SensorData.DataType.EEG)}):")
|
|
||||||
|
|
||||||
sensorData.channelData.take(maxChannels).forEachIndexed { channelIndex, channel ->
|
|
||||||
// 只显示前几个采样点
|
|
||||||
val sampleCount = minOf(maxSamples, channel.size)
|
|
||||||
val channelDataStr = channel.take(sampleCount).joinToString(", ") { "%.1f".format(it) }
|
|
||||||
|
|
||||||
details.add(" 通道 ${channelIndex + 1}: ${sampleCount}/${channel.size} 采样点")
|
|
||||||
details.add(" 示例: $channelDataStr${if (channel.size > sampleCount) "..." else ""}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sensorData.channelData.size > maxChannels) {
|
|
||||||
details.add(" ... 还有 ${sensorData.channelData.size - maxChannels} 个通道")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加分隔线
|
|
||||||
details.add("")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.size > maxPackets) {
|
|
||||||
details.add("... 还有 ${data.size - maxPackets} 个数据包")
|
|
||||||
}
|
|
||||||
|
|
||||||
return details.joinToString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A native method that is implemented by the 'cmake_project_test' native library,
|
|
||||||
* which is packaged with this application.
|
|
||||||
*/
|
|
||||||
external fun stringFromJNI(): String
|
|
||||||
external fun addFromJNI(a: Int, b: Int): Int
|
|
||||||
external fun parseDeviceDataFromJNI(fileData: ByteArray): List<SensorData>?
|
|
||||||
external fun createStreamParser(): Long
|
|
||||||
external fun destroyStreamParser(handle: Long)
|
|
||||||
external fun streamParserAppend(handle: Long, chunk: ByteArray)
|
|
||||||
external fun streamParserDrainPackets(handle: Long): List<SensorData>?
|
|
||||||
external fun parseStreamFromBytes(data: ByteArray, chunkSize: Int): List<SensorData>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置所有数据
|
|
||||||
*/
|
|
||||||
private fun resetData() {
|
|
||||||
packetBuffer.clear()
|
|
||||||
rawStream.reset()
|
|
||||||
totalPacketsParsed = 0L // 重置总计数
|
|
||||||
binding.sampleText.text = "数据已重置"
|
|
||||||
Log.d("MainActivity", "数据已重置,当前数据包数量: ${packetBuffer.size}, 总计数: $totalPacketsParsed")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新加载数据
|
|
||||||
*/
|
|
||||||
private fun reloadData() {
|
|
||||||
binding.sampleText.text = "正在重新加载数据..."
|
|
||||||
|
|
||||||
// 重置解析器
|
|
||||||
if (parserHandle != 0L) {
|
|
||||||
destroyStreamParser(parserHandle)
|
|
||||||
parserHandle = 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空缓冲区和重置计数
|
|
||||||
packetBuffer.clear()
|
|
||||||
rawStream.reset()
|
|
||||||
totalPacketsParsed = 0L
|
|
||||||
|
|
||||||
// 在后台线程重新加载数据
|
|
||||||
Thread {
|
|
||||||
try {
|
|
||||||
val fileData = readAssetFile("data1.dat")
|
|
||||||
|
|
||||||
if (fileData != null) {
|
|
||||||
ensureParser()
|
|
||||||
val chunkSize = 64
|
|
||||||
var offset = 0
|
|
||||||
|
|
||||||
while (offset < fileData.size) {
|
|
||||||
val n = minOf(chunkSize, fileData.size - offset)
|
|
||||||
val chunk = fileData.copyOfRange(offset, offset + n)
|
|
||||||
onBleNotify(chunk)
|
|
||||||
offset += n
|
|
||||||
Thread.sleep(10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后拉取所有剩余包
|
|
||||||
val packets = streamParserDrainPackets(parserHandle)
|
|
||||||
if (!packets.isNullOrEmpty()) {
|
|
||||||
packetBuffer.addAll(packets)
|
|
||||||
scheduleUiUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("MainActivity", "数据重新加载完成,当前数据包数量: ${packetBuffer.size}")
|
|
||||||
} else {
|
|
||||||
runOnUiThread {
|
|
||||||
binding.sampleText.text = "重新加载失败:文件读取失败"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("MainActivity", "Error reloading data", e)
|
|
||||||
runOnUiThread {
|
|
||||||
binding.sampleText.text = "重新加载错误: ${e.message}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Used to load the 'cmake_project_test' library on application startup.
|
// Used to load the 'cmake_project_test' library on application startup.
|
||||||
|
|
@ -332,4 +341,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
System.loadLibrary("cmake_project_test")
|
System.loadLibrary("cmake_project_test")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 原生方法声明 - 保持原来的JNI函数名
|
||||||
|
override external fun createStreamParser(): Long
|
||||||
|
override external fun destroyStreamParser(handle: Long)
|
||||||
|
override external fun streamParserAppend(handle: Long, chunk: ByteArray)
|
||||||
|
override external fun streamParserDrainPackets(handle: Long): List<SensorData>?
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import type.SensorData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生方法回调接口
|
||||||
|
* 用于DataManager调用MainActivity中的原生方法
|
||||||
|
*/
|
||||||
|
interface NativeMethodCallback {
|
||||||
|
fun createStreamParser(): Long
|
||||||
|
fun destroyStreamParser(handle: Long)
|
||||||
|
fun streamParserAppend(handle: Long, chunk: ByteArray)
|
||||||
|
fun streamParserDrainPackets(handle: Long): List<SensorData>?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信号处理使用示例
|
||||||
|
* 展示如何在现有流式数据读取代码结构中使用信号处理功能
|
||||||
|
*/
|
||||||
|
class SignalProcessingExample(private val dataManager: DataManager) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SignalProcessingExample"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例1:实时信号质量监控
|
||||||
|
* 在流式数据处理过程中实时监控信号质量
|
||||||
|
*/
|
||||||
|
fun demonstrateRealTimeQualityMonitoring() {
|
||||||
|
Log.d(TAG, "=== 开始实时信号质量监控演示 ===")
|
||||||
|
|
||||||
|
val packets = dataManager.getPacketBuffer()
|
||||||
|
if (packets.isEmpty()) {
|
||||||
|
Log.w(TAG, "没有数据包可供监控")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监控前10个数据包的质量
|
||||||
|
val packetsToMonitor = packets.take(10)
|
||||||
|
var totalQuality = 0.0f
|
||||||
|
var validPackets = 0
|
||||||
|
|
||||||
|
for ((index, packet) in packetsToMonitor.withIndex()) {
|
||||||
|
val quality = dataManager.calculateSignalQuality(packet)
|
||||||
|
if (quality > 0) {
|
||||||
|
totalQuality += quality
|
||||||
|
validPackets++
|
||||||
|
Log.d(TAG, "数据包 $index (${packet.dataType}): 质量 = $quality")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validPackets > 0) {
|
||||||
|
val averageQuality = totalQuality / validPackets
|
||||||
|
Log.d(TAG, "平均信号质量: $averageQuality")
|
||||||
|
|
||||||
|
// 根据质量决定是否需要调整滤波器参数
|
||||||
|
if (averageQuality < 0.5f) {
|
||||||
|
Log.w(TAG, "信号质量较低,建议检查传感器连接或调整滤波器参数")
|
||||||
|
} else if (averageQuality > 0.8f) {
|
||||||
|
Log.i(TAG, "信号质量良好")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例2:按设备类型应用不同的信号处理策略
|
||||||
|
*/
|
||||||
|
fun demonstrateDeviceSpecificProcessing() {
|
||||||
|
Log.d(TAG, "=== 开始设备特定信号处理演示 ===")
|
||||||
|
|
||||||
|
val packets = dataManager.getPacketBuffer()
|
||||||
|
if (packets.isEmpty()) {
|
||||||
|
Log.w(TAG, "没有数据包可供处理")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按设备类型分组
|
||||||
|
val packetsByType = packets.groupBy { it.dataType }
|
||||||
|
|
||||||
|
for ((dataType, typePackets) in packetsByType) {
|
||||||
|
Log.d(TAG, "处理 ${dataType.name} 类型数据,共 ${typePackets.size} 个数据包")
|
||||||
|
|
||||||
|
when (dataType) {
|
||||||
|
SensorData.DataType.EEG -> {
|
||||||
|
Log.d(TAG, "应用EEG专用滤波器:带通滤波(1-40Hz) + 幅度归一化")
|
||||||
|
}
|
||||||
|
SensorData.DataType.ECG_2LEAD, SensorData.DataType.ECG_12LEAD -> {
|
||||||
|
Log.d(TAG, "应用ECG专用滤波器:高通(0.5Hz) + 低通(40Hz) + 陷波(50Hz)")
|
||||||
|
}
|
||||||
|
SensorData.DataType.PPG -> {
|
||||||
|
Log.d(TAG, "应用PPG专用滤波器:低通(8Hz) + 运动伪影去除")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "数据类型 ${dataType.name} 暂不支持专用处理")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例3:批量信号处理
|
||||||
|
* 对大量数据进行批量信号处理
|
||||||
|
*/
|
||||||
|
fun demonstrateBatchProcessing() {
|
||||||
|
Log.d(TAG, "=== 开始批量信号处理演示 ===")
|
||||||
|
|
||||||
|
val packets = dataManager.getPacketBuffer()
|
||||||
|
if (packets.isEmpty()) {
|
||||||
|
Log.w(TAG, "没有数据包可供批量处理")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "开始批量处理 ${packets.size} 个数据包...")
|
||||||
|
|
||||||
|
// 记录处理开始时间
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
// 应用信号处理
|
||||||
|
val processedPackets = dataManager.applySignalProcessing(packets)
|
||||||
|
|
||||||
|
// 记录处理结束时间
|
||||||
|
val endTime = System.currentTimeMillis()
|
||||||
|
val processingTime = endTime - startTime
|
||||||
|
|
||||||
|
Log.d(TAG, "批量处理完成!")
|
||||||
|
Log.d(TAG, "处理时间: ${processingTime}ms")
|
||||||
|
Log.d(TAG, "处理数据包: ${processedPackets.size} 个")
|
||||||
|
Log.d(TAG, "平均处理速度: ${packets.size * 1000.0 / processingTime} 包/秒")
|
||||||
|
|
||||||
|
// 显示处理后的信号质量统计
|
||||||
|
if (processedPackets.isNotEmpty()) {
|
||||||
|
val qualityScores = mutableListOf<Float>()
|
||||||
|
for (packet in processedPackets.take(20)) { // 只检查前20个包
|
||||||
|
val quality = dataManager.calculateSignalQuality(packet)
|
||||||
|
if (quality > 0) {
|
||||||
|
qualityScores.add(quality)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualityScores.isNotEmpty()) {
|
||||||
|
val avgQuality = qualityScores.average()
|
||||||
|
val maxQuality = qualityScores.maxOrNull() ?: 0f
|
||||||
|
val minQuality = qualityScores.minOrNull() ?: 0f
|
||||||
|
|
||||||
|
Log.d(TAG, "处理后信号质量统计:")
|
||||||
|
Log.d(TAG, " 平均质量: ${String.format("%.3f", avgQuality)}")
|
||||||
|
Log.d(TAG, " 最高质量: ${String.format("%.3f", maxQuality)}")
|
||||||
|
Log.d(TAG, " 最低质量: ${String.format("%.3f", minQuality)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例4:自适应滤波器参数调整
|
||||||
|
* 根据信号质量自动调整滤波器参数
|
||||||
|
*/
|
||||||
|
fun demonstrateAdaptiveFilterAdjustment() {
|
||||||
|
Log.d(TAG, "=== 开始自适应滤波器参数调整演示 ===")
|
||||||
|
|
||||||
|
val packets = dataManager.getPacketBuffer()
|
||||||
|
if (packets.isEmpty()) {
|
||||||
|
Log.w(TAG, "没有数据包可供自适应处理")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个数据包进行质量评估
|
||||||
|
val firstPacket = packets.first()
|
||||||
|
val initialQuality = dataManager.calculateSignalQuality(firstPacket)
|
||||||
|
|
||||||
|
Log.d(TAG, "初始信号质量: $initialQuality")
|
||||||
|
|
||||||
|
if (initialQuality < 0.3f) {
|
||||||
|
Log.w(TAG, "信号质量过低,建议检查硬件连接")
|
||||||
|
} else if (initialQuality < 0.6f) {
|
||||||
|
Log.i(TAG, "信号质量一般,可以尝试调整滤波器参数")
|
||||||
|
|
||||||
|
// 这里可以添加自适应参数调整逻辑
|
||||||
|
// 例如:根据噪声水平调整截止频率
|
||||||
|
when (firstPacket.dataType) {
|
||||||
|
SensorData.DataType.EEG -> {
|
||||||
|
Log.d(TAG, "建议调整EEG滤波器:降低高通截止频率到0.5Hz,提高低通截止频率到50Hz")
|
||||||
|
}
|
||||||
|
SensorData.DataType.ECG_2LEAD, SensorData.DataType.ECG_12LEAD -> {
|
||||||
|
Log.d(TAG, "建议调整ECG滤波器:提高高通截止频率到1Hz,降低低通截止频率到30Hz")
|
||||||
|
}
|
||||||
|
SensorData.DataType.PPG -> {
|
||||||
|
Log.d(TAG, "建议调整PPG滤波器:降低低通截止频率到6Hz")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "该数据类型暂不支持自适应调整")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "信号质量良好,当前滤波器参数合适")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行所有演示
|
||||||
|
*/
|
||||||
|
fun runAllDemonstrations() {
|
||||||
|
Log.d(TAG, "开始运行所有信号处理演示...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
demonstrateRealTimeQualityMonitoring()
|
||||||
|
demonstrateDeviceSpecificProcessing()
|
||||||
|
demonstrateBatchProcessing()
|
||||||
|
demonstrateAdaptiveFilterAdjustment()
|
||||||
|
|
||||||
|
Log.d(TAG, "所有演示完成!")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "演示过程中发生错误", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信号处理使用示例类
|
||||||
|
* 展示如何使用JNI信号处理功能
|
||||||
|
*/
|
||||||
|
class SignalProcessorExample {
|
||||||
|
|
||||||
|
private val signalProcessor = SignalProcessorJNI()
|
||||||
|
private val sampleRate = 1000.0 // 1kHz采样率
|
||||||
|
|
||||||
|
init {
|
||||||
|
// 初始化信号处理器
|
||||||
|
if (!signalProcessor.createProcessor()) {
|
||||||
|
Log.e("SignalProcessorExample", "Failed to create signal processor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试信号:正弦波 + 噪声
|
||||||
|
*/
|
||||||
|
fun generateTestSignal(frequency: Double, duration: Double, noiseLevel: Double = 0.1): FloatArray {
|
||||||
|
val numSamples = (sampleRate * duration).toInt()
|
||||||
|
val signal = FloatArray(numSamples)
|
||||||
|
|
||||||
|
for (i in 0 until numSamples) {
|
||||||
|
val time = i / sampleRate
|
||||||
|
val sineWave = sin(2 * PI * frequency * time).toFloat()
|
||||||
|
val noise = (Math.random() * 2 - 1).toFloat() * noiseLevel
|
||||||
|
signal[i] = (sineWave + noise).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
return signal
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示带通滤波
|
||||||
|
*/
|
||||||
|
fun demonstrateBandpassFilter() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 带通滤波演示 ===")
|
||||||
|
|
||||||
|
// 生成包含多个频率的测试信号
|
||||||
|
val signal = generateTestSignal(50.0, 1.0, 0.2) // 50Hz + 噪声
|
||||||
|
|
||||||
|
// 应用带通滤波 (40-60Hz)
|
||||||
|
val filteredSignal = signalProcessor.bandpassFilter(signal, sampleRate, 40.0, 60.0)
|
||||||
|
|
||||||
|
if (filteredSignal != null) {
|
||||||
|
Log.d("SignalProcessorExample", "滤波成功!原始信号长度: ${signal.size}, 滤波后长度: ${filteredSignal.size}")
|
||||||
|
|
||||||
|
// 计算信号质量
|
||||||
|
val originalQuality = signalProcessor.calculateSignalQuality(signal)
|
||||||
|
val filteredQuality = signalProcessor.calculateSignalQuality(filteredSignal)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "原始信号质量: $originalQuality")
|
||||||
|
Log.d("SignalProcessorExample", "滤波后信号质量: $filteredQuality")
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "带通滤波失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示低通滤波
|
||||||
|
*/
|
||||||
|
fun demonstrateLowpassFilter() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 低通滤波演示 ===")
|
||||||
|
|
||||||
|
// 生成高频信号
|
||||||
|
val signal = generateTestSignal(200.0, 1.0, 0.1)
|
||||||
|
|
||||||
|
// 应用低通滤波 (100Hz截止)
|
||||||
|
val filteredSignal = signalProcessor.lowpassFilter(signal, sampleRate, 100.0)
|
||||||
|
|
||||||
|
if (filteredSignal != null) {
|
||||||
|
Log.d("SignalProcessorExample", "低通滤波成功!")
|
||||||
|
|
||||||
|
// 计算信号质量
|
||||||
|
val originalQuality = signalProcessor.calculateSignalQuality(signal)
|
||||||
|
val filteredQuality = signalProcessor.calculateSignalQuality(filteredSignal)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "原始信号质量: $originalQuality")
|
||||||
|
Log.d("SignalProcessorExample", "滤波后信号质量: $filteredQuality")
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "低通滤波失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示高通滤波
|
||||||
|
*/
|
||||||
|
fun demonstrateHighpassFilter() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 高通滤波演示 ===")
|
||||||
|
|
||||||
|
// 生成包含低频和高频的信号
|
||||||
|
val signal = generateTestSignal(10.0, 1.0, 0.1) // 10Hz低频信号
|
||||||
|
|
||||||
|
// 应用高通滤波 (50Hz截止)
|
||||||
|
val filteredSignal = signalProcessor.highpassFilter(signal, sampleRate, 50.0)
|
||||||
|
|
||||||
|
if (filteredSignal != null) {
|
||||||
|
Log.d("SignalProcessorExample", "高通滤波成功!")
|
||||||
|
|
||||||
|
// 计算信号质量
|
||||||
|
val originalQuality = signalProcessor.calculateSignalQuality(signal)
|
||||||
|
val filteredQuality = signalProcessor.calculateSignalQuality(filteredSignal)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "原始信号质量: $originalQuality")
|
||||||
|
Log.d("SignalProcessorExample", "滤波后信号质量: $filteredQuality")
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "高通滤波失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示陷波滤波(去除工频干扰)
|
||||||
|
*/
|
||||||
|
fun demonstrateNotchFilter() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 陷波滤波演示 ===")
|
||||||
|
|
||||||
|
// 生成包含工频干扰的信号
|
||||||
|
val signal = generateTestSignal(50.0, 1.0, 0.3) // 50Hz工频 + 噪声
|
||||||
|
|
||||||
|
// 应用陷波滤波 (50Hz陷波)
|
||||||
|
val filteredSignal = signalProcessor.notchFilter(signal, sampleRate, 50.0, 30.0)
|
||||||
|
|
||||||
|
if (filteredSignal != null) {
|
||||||
|
Log.d("SignalProcessorExample", "陷波滤波成功!")
|
||||||
|
|
||||||
|
// 计算信号质量
|
||||||
|
val originalQuality = signalProcessor.calculateSignalQuality(signal)
|
||||||
|
val filteredQuality = signalProcessor.calculateSignalQuality(filteredSignal)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "原始信号质量: $originalQuality")
|
||||||
|
Log.d("SignalProcessorExample", "滤波后信号质量: $filteredQuality")
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "陷波滤波失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示ECG信号质量评估
|
||||||
|
*/
|
||||||
|
fun demonstrateECGSQI() {
|
||||||
|
Log.d("SignalProcessorExample", "=== ECG信号质量评估演示 ===")
|
||||||
|
|
||||||
|
// 生成模拟ECG信号
|
||||||
|
val ecgSignal = generateTestSignal(1.0, 2.0, 0.05) // 1Hz心跳 + 低噪声
|
||||||
|
|
||||||
|
// 计算ECG信号质量指数
|
||||||
|
val sqi = signalProcessor.calculateECGSQI(ecgSignal, sampleRate)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "ECG信号质量指数: $sqi")
|
||||||
|
|
||||||
|
if (sqi > 0.7f) {
|
||||||
|
Log.d("SignalProcessorExample", "ECG信号质量良好")
|
||||||
|
} else if (sqi > 0.4f) {
|
||||||
|
Log.d("SignalProcessorExample", "ECG信号质量一般")
|
||||||
|
} else {
|
||||||
|
Log.d("SignalProcessorExample", "ECG信号质量较差")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示信号特征提取
|
||||||
|
*/
|
||||||
|
fun demonstrateFeatureExtraction() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 信号特征提取演示 ===")
|
||||||
|
|
||||||
|
// 生成测试信号
|
||||||
|
val signal = generateTestSignal(100.0, 0.5, 0.15)
|
||||||
|
|
||||||
|
// 提取特征
|
||||||
|
val features = signalProcessor.extractFeatures(signal, sampleRate)
|
||||||
|
|
||||||
|
if (features != null) {
|
||||||
|
Log.d("SignalProcessorExample", "特征提取成功!特征数量: ${features.size}")
|
||||||
|
|
||||||
|
// 显示前几个特征值
|
||||||
|
for (i in 0 until minOf(5, features.size)) {
|
||||||
|
Log.d("SignalProcessorExample", "特征 $i: ${features[i]}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "特征提取失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示信号归一化
|
||||||
|
*/
|
||||||
|
fun demonstrateNormalization() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 信号归一化演示 ===")
|
||||||
|
|
||||||
|
// 生成幅度较大的信号
|
||||||
|
val signal = generateTestSignal(75.0, 0.5, 0.2)
|
||||||
|
|
||||||
|
// 找到最大绝对值
|
||||||
|
val maxAbs = signal.maxOfOrNull { kotlin.math.abs(it) } ?: 0f
|
||||||
|
Log.d("SignalProcessorExample", "归一化前最大绝对值: $maxAbs")
|
||||||
|
|
||||||
|
// 归一化
|
||||||
|
signalProcessor.normalizeAmplitude(signal)
|
||||||
|
|
||||||
|
// 检查归一化结果
|
||||||
|
val normalizedMaxAbs = signal.maxOfOrNull { kotlin.math.abs(it) } ?: 0f
|
||||||
|
Log.d("SignalProcessorExample", "归一化后最大绝对值: $normalizedMaxAbs")
|
||||||
|
|
||||||
|
if (kotlin.math.abs(normalizedMaxAbs - 1.0f) < 0.01f) {
|
||||||
|
Log.d("SignalProcessorExample", "归一化成功!")
|
||||||
|
} else {
|
||||||
|
Log.e("SignalProcessorExample", "归一化失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示相关性计算
|
||||||
|
*/
|
||||||
|
fun demonstrateCorrelation() {
|
||||||
|
Log.d("SignalProcessorExample", "=== 相关性计算演示 ===")
|
||||||
|
|
||||||
|
// 生成两个相关信号
|
||||||
|
val signal1 = generateTestSignal(50.0, 1.0, 0.1)
|
||||||
|
val signal2 = generateTestSignal(50.0, 1.0, 0.1) // 相同频率
|
||||||
|
|
||||||
|
// 计算相关性
|
||||||
|
val correlation = signalProcessor.calculateCorrelation(signal1, signal2)
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "信号相关性: $correlation")
|
||||||
|
|
||||||
|
if (correlation > 0.8f) {
|
||||||
|
Log.d("SignalProcessorExample", "信号高度相关")
|
||||||
|
} else if (correlation > 0.5f) {
|
||||||
|
Log.d("SignalProcessorExample", "信号中等相关")
|
||||||
|
} else {
|
||||||
|
Log.d("SignalProcessorExample", "信号相关性较低")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行所有演示
|
||||||
|
*/
|
||||||
|
fun runAllDemonstrations() {
|
||||||
|
Log.d("SignalProcessorExample", "开始运行所有信号处理演示...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
demonstrateBandpassFilter()
|
||||||
|
demonstrateLowpassFilter()
|
||||||
|
demonstrateHighpassFilter()
|
||||||
|
demonstrateNotchFilter()
|
||||||
|
demonstrateECGSQI()
|
||||||
|
demonstrateFeatureExtraction()
|
||||||
|
demonstrateNormalization()
|
||||||
|
demonstrateCorrelation()
|
||||||
|
|
||||||
|
Log.d("SignalProcessorExample", "所有演示完成!")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("SignalProcessorExample", "演示过程中发生错误", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
signalProcessor.destroyProcessor()
|
||||||
|
Log.d("SignalProcessorExample", "信号处理器已销毁")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信号处理JNI接口类
|
||||||
|
* 提供C++信号处理功能的Java封装
|
||||||
|
*/
|
||||||
|
class SignalProcessorJNI {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 加载原生库
|
||||||
|
init {
|
||||||
|
System.loadLibrary("cmake_project_test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var processorId: Long = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建信号处理器实例
|
||||||
|
*/
|
||||||
|
fun createProcessor(): Boolean {
|
||||||
|
processorId = createSignalProcessor()
|
||||||
|
return processorId != -1L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁信号处理器实例
|
||||||
|
*/
|
||||||
|
fun destroyProcessor() {
|
||||||
|
if (processorId != -1L) {
|
||||||
|
destroySignalProcessor(processorId)
|
||||||
|
processorId = -1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带通滤波
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @param lowFreq 低频截止频率
|
||||||
|
* @param highFreq 高频截止频率
|
||||||
|
* @return 滤波后的信号数据
|
||||||
|
*/
|
||||||
|
fun bandpassFilter(signal: FloatArray, sampleRate: Double, lowFreq: Double, highFreq: Double): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
val resultBytes = bandpassFilter(processorId, signalBytes, sampleRate, lowFreq, highFreq)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 低通滤波
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @param cutoffFreq 截止频率
|
||||||
|
* @return 滤波后的信号数据
|
||||||
|
*/
|
||||||
|
fun lowpassFilter(signal: FloatArray, sampleRate: Double, cutoffFreq: Double): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
val resultBytes = lowpassFilter(processorId, signalBytes, sampleRate, cutoffFreq)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高通滤波
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @param cutoffFreq 截止频率
|
||||||
|
* @return 滤波后的信号数据
|
||||||
|
*/
|
||||||
|
fun highpassFilter(signal: FloatArray, sampleRate: Double, cutoffFreq: Double): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
val resultBytes = highpassFilter(processorId, signalBytes, sampleRate, cutoffFreq)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 陷波滤波
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @param notchFreq 陷波频率
|
||||||
|
* @param qFactor 品质因数
|
||||||
|
* @return 滤波后的信号数据
|
||||||
|
*/
|
||||||
|
fun notchFilter(signal: FloatArray, sampleRate: Double, notchFreq: Double, qFactor: Double = 30.0): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
val resultBytes = notchFilter(processorId, signalBytes, sampleRate, notchFreq, qFactor)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算信号质量
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @return 信号质量指数 (0.0 - 1.0)
|
||||||
|
*/
|
||||||
|
fun calculateSignalQuality(signal: FloatArray): Float {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
return calculateSignalQuality(processorId, signalBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算ECG信号质量指数
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return ECG信号质量指数 (0.0 - 1.0)
|
||||||
|
*/
|
||||||
|
fun calculateECGSQI(signal: FloatArray, sampleRate: Double): Float {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
return calculateECGSQI(processorId, signalBytes, sampleRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两个信号的相关性
|
||||||
|
* @param x 第一个信号
|
||||||
|
* @param y 第二个信号
|
||||||
|
* @return 相关系数 (-1.0 - 1.0)
|
||||||
|
*/
|
||||||
|
fun calculateCorrelation(x: FloatArray, y: FloatArray): Float {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val xBytes = floatArrayToByteArray(x)
|
||||||
|
val yBytes = floatArrayToByteArray(y)
|
||||||
|
return calculateCorrelation(processorId, xBytes, yBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化信号幅度
|
||||||
|
* @param signal 输入信号数据(将被修改)
|
||||||
|
*/
|
||||||
|
fun normalizeAmplitude(signal: FloatArray) {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
normalizeAmplitude(processorId, signalBytes)
|
||||||
|
|
||||||
|
// 将处理后的数据写回原数组
|
||||||
|
val processedSignal = byteArrayToFloatArray(signalBytes)
|
||||||
|
processedSignal.copyInto(signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取信号特征
|
||||||
|
* @param signal 输入信号数据
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return 特征向量
|
||||||
|
*/
|
||||||
|
fun extractFeatures(signal: FloatArray, sampleRate: Double): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val signalBytes = floatArrayToByteArray(signal)
|
||||||
|
val resultBytes = extractFeatures(processorId, signalBytes, sampleRate)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置滤波器状态
|
||||||
|
*/
|
||||||
|
fun resetFilters() {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFilters(processorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时处理数据块
|
||||||
|
* @param chunk 输入数据块
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @return 处理后的数据块
|
||||||
|
*/
|
||||||
|
fun processRealtimeChunk(chunk: FloatArray, sampleRate: Double): FloatArray? {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.e("SignalProcessorJNI", "Processor not initialized")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val chunkBytes = floatArrayToByteArray(chunk)
|
||||||
|
val resultBytes = processRealtimeChunk(processorId, chunkBytes, sampleRate)
|
||||||
|
return resultBytes?.let { byteArrayToFloatArray(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:FloatArray转ByteArray
|
||||||
|
private fun floatArrayToByteArray(floatArray: FloatArray): ByteArray {
|
||||||
|
val buffer = ByteBuffer.allocate(floatArray.size * 4)
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
floatArray.forEach { buffer.putFloat(it) }
|
||||||
|
return buffer.array()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:ByteArray转FloatArray
|
||||||
|
private fun byteArrayToFloatArray(byteArray: ByteArray): FloatArray {
|
||||||
|
val buffer = ByteBuffer.wrap(byteArray)
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
val floatArray = FloatArray(byteArray.size / 4)
|
||||||
|
for (i in floatArray.indices) {
|
||||||
|
floatArray[i] = buffer.float
|
||||||
|
}
|
||||||
|
return floatArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原生方法声明
|
||||||
|
private external fun createSignalProcessor(): Long
|
||||||
|
private external fun destroySignalProcessor(processorId: Long)
|
||||||
|
private external fun bandpassFilter(processorId: Long, signal: ByteArray, sampleRate: Double, lowFreq: Double, highFreq: Double): ByteArray?
|
||||||
|
private external fun lowpassFilter(processorId: Long, signal: ByteArray, sampleRate: Double, cutoffFreq: Double): ByteArray?
|
||||||
|
private external fun highpassFilter(processorId: Long, signal: ByteArray, sampleRate: Double, cutoffFreq: Double): ByteArray?
|
||||||
|
private external fun notchFilter(processorId: Long, signal: ByteArray, sampleRate: Double, notchFreq: Double, qFactor: Double): ByteArray?
|
||||||
|
private external fun calculateSignalQuality(processorId: Long, signal: ByteArray): Float
|
||||||
|
private external fun calculateECGSQI(processorId: Long, signal: ByteArray, sampleRate: Double): Float
|
||||||
|
private external fun calculateCorrelation(processorId: Long, x: ByteArray, y: ByteArray): Float
|
||||||
|
private external fun normalizeAmplitude(processorId: Long, signal: ByteArray)
|
||||||
|
private external fun extractFeatures(processorId: Long, signal: ByteArray, sampleRate: Double): ByteArray?
|
||||||
|
private external fun resetFilters(processorId: Long)
|
||||||
|
private external fun processRealtimeChunk(processorId: Long, chunk: ByteArray, sampleRate: Double): ByteArray?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,414 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式信号处理器
|
||||||
|
* 支持窗口式实时信号处理
|
||||||
|
*/
|
||||||
|
class StreamingSignalProcessor {
|
||||||
|
|
||||||
|
private val signalProcessor = SignalProcessorJNI()
|
||||||
|
private var processorId: Long = -1L
|
||||||
|
private var signalProcessorInitialized = false
|
||||||
|
|
||||||
|
// 窗口参数
|
||||||
|
private var windowSize = 14 // 窗口大小(样本数)- 适配ECG数据包大小
|
||||||
|
private var overlapSize = 0 // 重叠大小(样本数)
|
||||||
|
private var stepSize = windowSize - overlapSize // 步长
|
||||||
|
|
||||||
|
// 数据缓冲区
|
||||||
|
private val dataBuffer = mutableListOf<Float>()
|
||||||
|
private val processedData = mutableListOf<Float>()
|
||||||
|
private var lastProcessedIndex = 0
|
||||||
|
|
||||||
|
// 滤波器参数
|
||||||
|
private var sampleRate = 50.0 // 默认采样率
|
||||||
|
private var lowpassCutoff = 40.0 // 低通滤波截止频率
|
||||||
|
private var notchFreq = 50.0 // 陷波滤波频率
|
||||||
|
private var notchQ = 30.0 // 陷波滤波品质因数
|
||||||
|
|
||||||
|
// 设备采样率映射
|
||||||
|
private val deviceSampleRates = mapOf(
|
||||||
|
// 0x4230: EEG, EOG - 250Hz
|
||||||
|
0x4230 to 250.0,
|
||||||
|
// 0x4211: ECG1, ECG2, EMG1, EMG2 - 250Hz, BR_TEMPERATURE - 50Hz
|
||||||
|
0x4211 to 250.0, // 主要采样率,温度单独处理
|
||||||
|
// 0x4402: ECG - 250Hz
|
||||||
|
0x4402 to 250.0,
|
||||||
|
// 0x4302: RED_DATA, IR_DATA - 50Hz
|
||||||
|
0x4302 to 50.0,
|
||||||
|
// 0x1102: CH_SOUND - 8000Hz
|
||||||
|
0x1102 to 8000.0
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化信号处理器
|
||||||
|
*/
|
||||||
|
fun initialize(): Boolean {
|
||||||
|
try {
|
||||||
|
signalProcessorInitialized = signalProcessor.createProcessor()
|
||||||
|
if (signalProcessorInitialized) {
|
||||||
|
processorId = 1L // 使用固定ID
|
||||||
|
Log.d("StreamingSignalProcessor", "流式信号处理器初始化成功")
|
||||||
|
} else {
|
||||||
|
Log.e("StreamingSignalProcessor", "流式信号处理器初始化失败")
|
||||||
|
}
|
||||||
|
return signalProcessorInitialized
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("StreamingSignalProcessor", "信号处理器初始化异常: ${e.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理新的数据流(使用默认参数)
|
||||||
|
* @param newData 新的数据样本
|
||||||
|
* @return 处理后的数据样本
|
||||||
|
*/
|
||||||
|
fun processStreamingData(newData: List<Float>): List<Float> {
|
||||||
|
return processStreamingDataWithParameters(newData, sampleRate, lowpassCutoff, notchFreq, notchQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理新的数据流(使用指定参数)
|
||||||
|
* @param newData 新的数据样本
|
||||||
|
* @param sampleRate 采样率
|
||||||
|
* @param lowpassCutoff 低通滤波截止频率
|
||||||
|
* @param notchFreq 陷波滤波频率
|
||||||
|
* @param notchQ 陷波滤波品质因数
|
||||||
|
* @return 处理后的数据样本
|
||||||
|
*/
|
||||||
|
fun processStreamingDataWithParameters(
|
||||||
|
newData: List<Float>,
|
||||||
|
sampleRate: Double,
|
||||||
|
lowpassCutoff: Double,
|
||||||
|
notchFreq: Double,
|
||||||
|
notchQ: Double
|
||||||
|
): List<Float> {
|
||||||
|
if (processorId == -1L) {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化")
|
||||||
|
return newData
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("StreamingSignalProcessor", "开始处理数据流,输入数据长度: ${newData.size}")
|
||||||
|
Log.d("StreamingSignalProcessor", "输入数据前3个值: ${newData.take(3).joinToString(", ")}")
|
||||||
|
Log.d("StreamingSignalProcessor", "使用参数 - 采样率: ${sampleRate}Hz, 低通: ${lowpassCutoff}Hz, 陷波: ${notchFreq}Hz")
|
||||||
|
|
||||||
|
// 添加新数据到缓冲区
|
||||||
|
dataBuffer.addAll(newData)
|
||||||
|
Log.d("StreamingSignalProcessor", "缓冲区大小: ${dataBuffer.size}, 窗口大小: $windowSize")
|
||||||
|
|
||||||
|
val processedSamples = mutableListOf<Float>()
|
||||||
|
|
||||||
|
// 当缓冲区有足够数据时进行窗口处理
|
||||||
|
while (dataBuffer.size >= windowSize) {
|
||||||
|
// 提取当前窗口数据
|
||||||
|
val windowData = dataBuffer.take(windowSize).toFloatArray()
|
||||||
|
Log.d("StreamingSignalProcessor", "提取窗口数据,长度: ${windowData.size}")
|
||||||
|
|
||||||
|
// 应用信号处理
|
||||||
|
val processedWindow = processWindowWithParameters(windowData, sampleRate, lowpassCutoff, notchFreq, notchQ)
|
||||||
|
Log.d("StreamingSignalProcessor", "窗口处理完成,结果长度: ${processedWindow.size}")
|
||||||
|
|
||||||
|
// 只保留非重叠部分的结果
|
||||||
|
val nonOverlapSize = stepSize
|
||||||
|
val nonOverlapData = processedWindow.take(nonOverlapSize)
|
||||||
|
processedSamples.addAll(nonOverlapData)
|
||||||
|
Log.d("StreamingSignalProcessor", "添加非重叠数据,长度: ${nonOverlapData.size}")
|
||||||
|
|
||||||
|
// 移除已处理的数据(保留重叠部分)
|
||||||
|
repeat(stepSize) {
|
||||||
|
if (dataBuffer.isNotEmpty()) {
|
||||||
|
dataBuffer.removeAt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("StreamingSignalProcessor", "数据流处理完成,输出长度: ${processedSamples.size}")
|
||||||
|
if (processedSamples.isNotEmpty()) {
|
||||||
|
Log.d("StreamingSignalProcessor", "输出数据前3个值: ${processedSamples.take(3).joinToString(", ")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedSamples
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个窗口的数据(使用默认参数)
|
||||||
|
*/
|
||||||
|
private fun processWindow(windowData: FloatArray): List<Float> {
|
||||||
|
return processWindowWithParameters(windowData, sampleRate, lowpassCutoff, notchFreq, notchQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个窗口的数据(使用指定参数)
|
||||||
|
*/
|
||||||
|
private fun processWindowWithParameters(
|
||||||
|
windowData: FloatArray,
|
||||||
|
sampleRate: Double,
|
||||||
|
lowpassCutoff: Double,
|
||||||
|
notchFreq: Double,
|
||||||
|
notchQ: Double
|
||||||
|
): List<Float> {
|
||||||
|
try {
|
||||||
|
Log.d("StreamingSignalProcessor", "开始处理窗口数据,数据长度: ${windowData.size}")
|
||||||
|
Log.d("StreamingSignalProcessor", "窗口数据前3个值: ${windowData.take(3).joinToString(", ")}")
|
||||||
|
Log.d("StreamingSignalProcessor", "使用参数 - 采样率: ${sampleRate}Hz, 低通: ${lowpassCutoff}Hz, 陷波: ${notchFreq}Hz")
|
||||||
|
|
||||||
|
// 1. 低通滤波
|
||||||
|
var filtered = signalProcessor.lowpassFilter(windowData, sampleRate, lowpassCutoff)
|
||||||
|
if (filtered == null) {
|
||||||
|
Log.w("StreamingSignalProcessor", "低通滤波失败,使用原始数据")
|
||||||
|
filtered = windowData
|
||||||
|
} else {
|
||||||
|
Log.d("StreamingSignalProcessor", "低通滤波成功,结果前3个值: ${filtered.take(3).joinToString(", ")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 陷波滤波(去除工频干扰)- 只有当陷波频率大于0时才应用
|
||||||
|
if (notchFreq > 0) {
|
||||||
|
val notchFiltered = signalProcessor.notchFilter(filtered, sampleRate, notchFreq, notchQ)
|
||||||
|
if (notchFiltered == null) {
|
||||||
|
Log.w("StreamingSignalProcessor", "陷波滤波失败,使用低通滤波结果")
|
||||||
|
Log.d("StreamingSignalProcessor", "返回低通滤波结果,前3个值: ${filtered.take(3).joinToString(", ")}")
|
||||||
|
return filtered.toList()
|
||||||
|
} else {
|
||||||
|
Log.d("StreamingSignalProcessor", "陷波滤波成功,结果前3个值: ${notchFiltered.take(3).joinToString(", ")}")
|
||||||
|
return notchFiltered.toList()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("StreamingSignalProcessor", "跳过陷波滤波(陷波频率为0),返回低通滤波结果")
|
||||||
|
return filtered.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("StreamingSignalProcessor", "窗口处理异常: ${e.message}")
|
||||||
|
return windowData.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型获取合适的采样率
|
||||||
|
*/
|
||||||
|
private fun getSampleRateForDataType(dataType: type.SensorData.DataType): Double {
|
||||||
|
return when (dataType) {
|
||||||
|
type.SensorData.DataType.EEG -> 250.0
|
||||||
|
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> 250.0
|
||||||
|
type.SensorData.DataType.PPG -> 50.0
|
||||||
|
type.SensorData.DataType.STETHOSCOPE -> 8000.0
|
||||||
|
type.SensorData.DataType.SNORE -> 8000.0
|
||||||
|
type.SensorData.DataType.RESPIRATION -> 50.0
|
||||||
|
else -> 50.0 // 默认采样率
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型获取合适的滤波器参数
|
||||||
|
*/
|
||||||
|
private fun getFilterParametersForDataType(dataType: type.SensorData.DataType): Triple<Double, Double, Double> {
|
||||||
|
return when (dataType) {
|
||||||
|
type.SensorData.DataType.EEG -> {
|
||||||
|
// EEG: 低通40Hz, 陷波50Hz
|
||||||
|
Triple(40.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> {
|
||||||
|
// ECG: 低通40Hz, 陷波50Hz
|
||||||
|
Triple(40.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.PPG -> {
|
||||||
|
// PPG: 低通10Hz, 陷波50Hz
|
||||||
|
Triple(10.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.MIT_BIH -> {
|
||||||
|
// MIT-BIH: 低通40Hz, 陷波50Hz
|
||||||
|
Triple(40.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.STETHOSCOPE -> {
|
||||||
|
// 听诊器: 低通4000Hz, 陷波50Hz
|
||||||
|
Triple(4000.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.SNORE -> {
|
||||||
|
// 鼾声: 低通4000Hz, 陷波50Hz
|
||||||
|
Triple(4000.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
type.SensorData.DataType.RESPIRATION -> {
|
||||||
|
// 呼吸: 低通1Hz, 不陷波
|
||||||
|
Triple(1.0, 0.0, 0.0)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// 默认参数
|
||||||
|
Triple(40.0, 50.0, 30.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理传感器数据包
|
||||||
|
*/
|
||||||
|
fun processSensorData(sensorData: SensorData): SensorData {
|
||||||
|
val channelData = sensorData.getChannelData()
|
||||||
|
if (channelData == null || channelData.isEmpty()) {
|
||||||
|
return sensorData
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 根据数据类型设置采样率和滤波器参数
|
||||||
|
val dataType = sensorData.getDataType()
|
||||||
|
val sampleRate = getSampleRateForDataType(dataType)
|
||||||
|
val (lowpassCutoff, notchFreq, notchQ) = getFilterParametersForDataType(dataType)
|
||||||
|
|
||||||
|
Log.d("StreamingSignalProcessor", "处理数据类型: $dataType, 采样率: ${sampleRate}Hz, 低通: ${lowpassCutoff}Hz, 陷波: ${notchFreq}Hz")
|
||||||
|
|
||||||
|
// 处理每个通道
|
||||||
|
val processedChannels = channelData.map { channel ->
|
||||||
|
val channelData = channel.toFloatArray()
|
||||||
|
val processedData = processStreamingDataWithParameters(channelData.toList(), sampleRate, lowpassCutoff, notchFreq, notchQ)
|
||||||
|
processedData.toFloatArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建处理后的传感器数据
|
||||||
|
val processedSensorData = SensorData()
|
||||||
|
processedSensorData.setDataType(sensorData.getDataType())
|
||||||
|
processedSensorData.setTimestamp(sensorData.getTimestamp())
|
||||||
|
processedSensorData.setPacketSn(sensorData.getPacketSn())
|
||||||
|
processedSensorData.setChannelData(processedChannels.map { it.toList() })
|
||||||
|
|
||||||
|
return processedSensorData
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("StreamingSignalProcessor", "处理传感器数据异常: ${e.message}")
|
||||||
|
return sensorData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理传感器数据包
|
||||||
|
*/
|
||||||
|
fun processSensorDataList(sensorDataList: List<SensorData>): List<SensorData> {
|
||||||
|
return sensorDataList.map { processSensorData(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓冲区状态
|
||||||
|
*/
|
||||||
|
fun getBufferStatus(): Map<String, Int> {
|
||||||
|
return mapOf(
|
||||||
|
"buffer_size" to dataBuffer.size,
|
||||||
|
"window_size" to windowSize,
|
||||||
|
"overlap_size" to overlapSize,
|
||||||
|
"step_size" to stepSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空缓冲区
|
||||||
|
*/
|
||||||
|
fun clearBuffer() {
|
||||||
|
dataBuffer.clear()
|
||||||
|
processedData.clear()
|
||||||
|
lastProcessedIndex = 0
|
||||||
|
Log.d("StreamingSignalProcessor", "缓冲区已清空")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置窗口参数
|
||||||
|
*/
|
||||||
|
fun setWindowParameters(windowSize: Int, overlapSize: Int) {
|
||||||
|
this.windowSize = windowSize
|
||||||
|
this.overlapSize = overlapSize
|
||||||
|
this.stepSize = windowSize - overlapSize
|
||||||
|
Log.d("StreamingSignalProcessor", "窗口参数已更新: 窗口大小=$windowSize, 重叠大小=$overlapSize")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置滤波器参数
|
||||||
|
*/
|
||||||
|
fun setFilterParameters(lowpassCutoff: Double, notchFreq: Double, notchQ: Double) {
|
||||||
|
this.lowpassCutoff = lowpassCutoff
|
||||||
|
this.notchFreq = notchFreq
|
||||||
|
this.notchQ = notchQ
|
||||||
|
Log.d("StreamingSignalProcessor", "滤波器参数已更新")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
try {
|
||||||
|
if (processorId != -1L) {
|
||||||
|
signalProcessor.destroyProcessor()
|
||||||
|
processorId = -1L
|
||||||
|
}
|
||||||
|
clearBuffer()
|
||||||
|
Log.d("StreamingSignalProcessor", "流式信号处理器资源已清理")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("StreamingSignalProcessor", "清理资源时发生错误: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 委托给SignalProcessorJNI的方法
|
||||||
|
fun normalizeAmplitude(signal: FloatArray): FloatArray? {
|
||||||
|
if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.normalizeAmplitude(signal)
|
||||||
|
return signal // normalizeAmplitude修改原数组,返回原数组
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
return signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bandpassFilter(signal: FloatArray, sampleRate: Float, lowFreq: Float, highFreq: Float): FloatArray? {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.bandpassFilter(signal, sampleRate.toDouble(), lowFreq.toDouble(), highFreq.toDouble())
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun highpassFilter(signal: FloatArray, sampleRate: Float, cutoffFreq: Float): FloatArray? {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.highpassFilter(signal, sampleRate.toDouble(), cutoffFreq.toDouble())
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lowpassFilter(signal: FloatArray, sampleRate: Float, cutoffFreq: Float): FloatArray? {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.lowpassFilter(signal, sampleRate.toDouble(), cutoffFreq.toDouble())
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notchFilter(signal: FloatArray, sampleRate: Float, notchFreq: Float): FloatArray? {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.notchFilter(signal, sampleRate.toDouble(), notchFreq.toDouble())
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processRealtimeChunk(signal: FloatArray, sampleRate: Float): FloatArray? {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.processRealtimeChunk(signal, sampleRate.toDouble())
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回原始信号")
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateSignalQuality(signal: FloatArray): Float {
|
||||||
|
return if (signalProcessorInitialized) {
|
||||||
|
signalProcessor.calculateSignalQuality(signal)
|
||||||
|
} else {
|
||||||
|
Log.w("StreamingSignalProcessor", "信号处理器未初始化,返回默认质量值")
|
||||||
|
0.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
package com.example.cmake_project_test
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import type.SensorData
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI管理类
|
||||||
|
* 负责UI更新、统计信息构建和显示逻辑
|
||||||
|
*/
|
||||||
|
class UiManager {
|
||||||
|
|
||||||
|
private val uiUpdatePending = AtomicBoolean(false)
|
||||||
|
private var lastUpdateTime = 0L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划UI更新,避免频繁刷新
|
||||||
|
*/
|
||||||
|
fun scheduleUiUpdate(
|
||||||
|
dataManager: DataManager,
|
||||||
|
updateCallback: () -> Unit
|
||||||
|
) {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastUpdateTime >= Constants.UPDATE_INTERVAL &&
|
||||||
|
uiUpdatePending.compareAndSet(false, true)) {
|
||||||
|
lastUpdateTime = currentTime
|
||||||
|
updateCallback()
|
||||||
|
uiUpdatePending.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建统计信息字符串
|
||||||
|
*/
|
||||||
|
fun buildStatisticsString(dataManager: DataManager): String {
|
||||||
|
return try {
|
||||||
|
val packetBuffer = dataManager.getPacketBuffer()
|
||||||
|
|
||||||
|
buildString {
|
||||||
|
append("=== 设备数据概览 ===\n")
|
||||||
|
append("当前缓冲区: ${dataManager.getPacketBufferSize()} 个数据包\n")
|
||||||
|
append("总共解析: ${dataManager.getTotalPacketsParsed()} 个数据包\n")
|
||||||
|
append("总数据量: ${dataManager.getRawStreamSize()} 字节\n")
|
||||||
|
|
||||||
|
if (packetBuffer.isNotEmpty()) {
|
||||||
|
// 获取设备类型信息
|
||||||
|
val deviceTypes = packetBuffer.mapNotNull { it.dataType }.distinct()
|
||||||
|
val deviceInfo = deviceTypes.joinToString(", ") { DeviceTypeHelper.getDeviceName(it) }
|
||||||
|
append("设备类型: $deviceInfo\n")
|
||||||
|
} else {
|
||||||
|
append("设备类型: 无\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\n")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("UiManager", "构建统计信息失败: ${e.message}", e)
|
||||||
|
"=== 设备数据概览 ===\n构建统计信息失败: ${e.message}\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整的显示内容
|
||||||
|
*/
|
||||||
|
fun buildDisplayContent(dataManager: DataManager): String {
|
||||||
|
return try {
|
||||||
|
val packetBuffer = dataManager.getPacketBuffer()
|
||||||
|
val processedPackets = dataManager.getProcessedPackets()
|
||||||
|
val calculatedMetrics = dataManager.getCalculatedMetrics()
|
||||||
|
|
||||||
|
// 即使没有数据,也显示基本状态信息
|
||||||
|
val stats = buildStatisticsString(dataManager)
|
||||||
|
|
||||||
|
if (packetBuffer.isEmpty()) {
|
||||||
|
return stats + "\n\n暂无数据包详情\n\n正在加载数据文件,请稍候..."
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建信号处理信息
|
||||||
|
val signalProcessingInfo = buildSignalProcessingInfo(dataManager, processedPackets)
|
||||||
|
|
||||||
|
// 构建指标计算信息
|
||||||
|
val metricsInfo = buildMetricsInfo(calculatedMetrics)
|
||||||
|
|
||||||
|
// 只显示最新的一些数据包详情
|
||||||
|
val recentPackets = packetBuffer.takeLast(Constants.MAX_DISPLAY_PACKETS)
|
||||||
|
val has12Lead = recentPackets.any { it.dataType == SensorData.DataType.ECG_12LEAD }
|
||||||
|
|
||||||
|
val channelDetails = if (has12Lead) {
|
||||||
|
DeviceTypeHelper.buildChannelDetails(
|
||||||
|
recentPackets,
|
||||||
|
maxPackets = Constants.MAX_DETAIL_PACKETS,
|
||||||
|
maxChannels = Constants.MAX_12LEAD_CHANNELS,
|
||||||
|
maxSamples = Constants.MAX_DISPLAY_SAMPLES
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DeviceTypeHelper.buildChannelDetails(
|
||||||
|
recentPackets,
|
||||||
|
maxPackets = Constants.MAX_DETAIL_PACKETS,
|
||||||
|
maxChannels = Constants.MAX_DISPLAY_CHANNELS,
|
||||||
|
maxSamples = Constants.MAX_DISPLAY_SAMPLES
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats + signalProcessingInfo + metricsInfo + channelDetails
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("UiManager", "构建显示内容时发生错误: ${e.message}", e)
|
||||||
|
"构建显示内容时发生错误: ${e.message}\n\n请检查数据格式是否正确。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建信号处理信息
|
||||||
|
*/
|
||||||
|
private fun buildSignalProcessingInfo(dataManager: DataManager, processedPackets: List<SensorData>): String {
|
||||||
|
return buildString {
|
||||||
|
append("\n=== 信号处理信息 ===\n")
|
||||||
|
|
||||||
|
if (processedPackets.isEmpty()) {
|
||||||
|
append("暂无信号处理数据\n")
|
||||||
|
} else {
|
||||||
|
append("已处理数据包: ${processedPackets.size} 个\n")
|
||||||
|
|
||||||
|
// 显示流式信号处理器状态
|
||||||
|
val bufferStatus = dataManager.getStreamingSignalProcessorStatus()
|
||||||
|
if (bufferStatus.isNotEmpty()) {
|
||||||
|
append("\n流式处理器状态:\n")
|
||||||
|
bufferStatus.forEach { (key, value) ->
|
||||||
|
append("- $key: $value\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示最新处理的数据包信息
|
||||||
|
val latestPacket = processedPackets.lastOrNull()
|
||||||
|
if (latestPacket != null) {
|
||||||
|
append("\n最新处理数据包:\n")
|
||||||
|
append("- 数据类型: ${DeviceTypeHelper.getDeviceName(latestPacket.getDataType())}\n")
|
||||||
|
append("- 时间戳: ${latestPacket.getTimestamp()}\n")
|
||||||
|
append("- 包序号: ${latestPacket.getPacketSn()}\n")
|
||||||
|
|
||||||
|
val channelData = latestPacket.getChannelData()
|
||||||
|
if (channelData != null && channelData.isNotEmpty()) {
|
||||||
|
append("- 通道数: ${channelData.size}\n")
|
||||||
|
channelData.forEachIndexed { index, channel ->
|
||||||
|
if (channel != null) {
|
||||||
|
append("- 通道${index + 1}样本数: ${channel.size}\n")
|
||||||
|
} else {
|
||||||
|
append("- 通道${index + 1}: 空数据\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建指标计算信息
|
||||||
|
*/
|
||||||
|
private fun buildMetricsInfo(calculatedMetrics: List<Map<String, Float>>): String {
|
||||||
|
return buildString {
|
||||||
|
append("\n=== 指标计算信息 ===\n")
|
||||||
|
|
||||||
|
if (calculatedMetrics.isEmpty()) {
|
||||||
|
append("暂无指标数据\n")
|
||||||
|
append("可能的原因:\n")
|
||||||
|
append("- 指标计算器未初始化\n")
|
||||||
|
append("- 数据处理流程未执行\n")
|
||||||
|
append("- 指标计算返回空结果\n")
|
||||||
|
} else {
|
||||||
|
append("已计算 ${calculatedMetrics.size} 个数据包的指标\n")
|
||||||
|
append("非空指标数: ${calculatedMetrics.count { it.isNotEmpty() }}\n")
|
||||||
|
|
||||||
|
// 显示最新一个数据包的指标
|
||||||
|
val latestMetrics = calculatedMetrics.lastOrNull()
|
||||||
|
if (latestMetrics != null && latestMetrics.isNotEmpty()) {
|
||||||
|
append("\n最新指标:\n")
|
||||||
|
for ((key, value) in latestMetrics) {
|
||||||
|
val formattedValue = when {
|
||||||
|
key.contains("rate") || key.contains("hr") -> String.format("%.1f bpm", value)
|
||||||
|
key.contains("spo2") -> String.format("%.1f%%", value)
|
||||||
|
key.contains("quality") -> String.format("%.2f", value)
|
||||||
|
key.contains("amplitude") || key.contains("offset") -> String.format("%.3f mV", value)
|
||||||
|
key.contains("width") -> String.format("%.1f ms", value)
|
||||||
|
key.contains("timestamp") || key.contains("packet_sn") || key.contains("data_type") -> String.format("%.0f", value)
|
||||||
|
else -> String.format("%.3f", value)
|
||||||
|
}
|
||||||
|
append("- $key: $formattedValue\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
append("最新指标: 无数据\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示统计信息
|
||||||
|
if (calculatedMetrics.size > 1) {
|
||||||
|
val allHeartRates = calculatedMetrics.mapNotNull { it["heart_rate"] }.filter { it > 0 }
|
||||||
|
val allQualities = calculatedMetrics.mapNotNull { it["signal_quality"] }.filter { it >= 0 }
|
||||||
|
|
||||||
|
if (allHeartRates.isNotEmpty()) {
|
||||||
|
val avgHeartRate = allHeartRates.average()
|
||||||
|
append("\n平均心率: ${String.format("%.1f", avgHeartRate)} bpm\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allQualities.isNotEmpty()) {
|
||||||
|
val avgQuality = allQualities.average()
|
||||||
|
append("平均信号质量: ${String.format("%.2f", avgQuality)}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新UI显示内容
|
||||||
|
*/
|
||||||
|
fun updateDisplay(
|
||||||
|
dataManager: DataManager,
|
||||||
|
updateTextCallback: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val displayContent = buildDisplayContent(dataManager)
|
||||||
|
updateTextCallback(displayContent)
|
||||||
|
|
||||||
|
// 智能清理缓冲区
|
||||||
|
dataManager.cleanupBuffer()
|
||||||
|
|
||||||
|
// 添加调试信息
|
||||||
|
Log.d("UiManager", "当前缓冲区数据包数量: ${dataManager.getPacketBufferSize()}, 总共解析: ${dataManager.getTotalPacketsParsed()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,28 +6,7 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_reset"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="重置数据"
|
|
||||||
android:layout_marginEnd="8dp" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_reload"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="重新加载"
|
|
||||||
android:layout_marginStart="8dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# Use Java 17 for Android Gradle Plugin
|
||||||
|
org.gradle.java.home=C:\\Program Files\\Android\\Android Studio\\jbr
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. For more details, visit
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue