This commit is contained in:
ZhangJinLong 2025-09-03 13:12:28 +08:00
parent cb28adca69
commit f87f090542
4 changed files with 74 additions and 60 deletions

View File

@ -50,11 +50,11 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
// 流式数据处理相关 // 流式数据处理相关
private val channelBuffers = mutableMapOf<Int, MutableList<Float>>() // 通道号 -> 数据缓冲区 private val channelBuffers = mutableMapOf<Int, MutableList<Float>>() // 通道号 -> 数据缓冲区
private val processedChannelBuffers = mutableMapOf<Int, MutableList<Float>>() // 处理后的通道数据 private val processedChannelBuffers = mutableMapOf<Int, MutableList<Float>>() // 处理后的通道数据
private val processingWindowSize = 1000 // 优化:增大处理窗口大小,确保心率计算 private val processingWindowSize = 100 // 优化0.4秒窗口250Hz采样率进一步提高实时性
private val minSamplesForMetrics = 250 // 优化250个样本1秒@250Hz适应十二导联心电 private val minSamplesForMetrics = 25 // 优化25个样本0.1秒@250Hz快速响应
private var currentDataType: type.SensorData.DataType? = null // 当前数据类型 private var currentDataType: type.SensorData.DataType? = null // 当前数据类型
private var lastProcessTime = 0L // 上次处理时间 private var lastProcessTime = 0L // 上次处理时间
private val processingInterval = 200L // 优化200ms处理间隔提高实时性 private val processingInterval = 25L // 优化25ms处理间隔进一步提高实时性
private var totalProcessedSamples = 0L // 总处理样本数 private var totalProcessedSamples = 0L // 总处理样本数
// 陷波滤波器状态 // 陷波滤波器状态
@ -95,11 +95,11 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
Log.d("DataManager", "解析出 ${packets.size} 个数据包") Log.d("DataManager", "解析出 ${packets.size} 个数据包")
// 立即发送原始数据到图表显示 // 立即发送原始数据到图表显示(优先显示原始数据)
sendRawDataToCharts(packets) sendRawDataToCharts(packets)
// 应用流式数据处理 // 可选:后台进行流式处理(不影响显示)
processStreamingData(packets) // processStreamingData(packets)
} else { } else {
Log.w("DataManager", "没有解析出有效数据包,尝试生成测试数据") Log.w("DataManager", "没有解析出有效数据包,尝试生成测试数据")
@ -220,32 +220,23 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
*/ */
private fun sendRawDataToCharts(packets: List<SensorData>) { private fun sendRawDataToCharts(packets: List<SensorData>) {
if (packets.isEmpty()) { if (packets.isEmpty()) {
Log.w("DataManager", "sendRawDataToCharts: 没有数据包")
return return
} }
Log.d("DataManager", "立即发送单导联蓝牙数据到图表,处理 ${packets.size} 个数据包") // 快速处理:减少日志,提高速度
var totalDataPoints = 0
// 专门处理单导联数据包 // 专门处理单导联数据包
for ((packetIndex, packet) in packets.withIndex()) { for (packet in packets) {
Log.d("DataManager", "处理单导联数据包 $packetIndex: 数据类型=${packet.getDataType()}")
val channelData = packet.getChannelData() val channelData = packet.getChannelData()
if (channelData != null && channelData.isNotEmpty()) { if (channelData != null && channelData.isNotEmpty()) {
Log.d("DataManager", "单导联数据包 $packetIndex${channelData.size} 个通道")
// 对于单导联数据我们主要关注第一个通道通常是导联I或II // 对于单导联数据我们主要关注第一个通道通常是导联I或II
val primaryChannel = channelData[0] val primaryChannel = channelData[0]
if (primaryChannel.isNotEmpty()) { if (primaryChannel.isNotEmpty()) {
Log.d("DataManager", "单导联主通道数据长度: ${primaryChannel.size}")
Log.d("DataManager", "单导联主通道前3个值: ${primaryChannel.take(3).joinToString(", ")}")
// 立即发送单导联数据到图表 // 立即发送单导联数据到图表
if (realTimeCallback != null) { if (realTimeCallback != null) {
realTimeCallback!!.onRawDataAvailable(0, primaryChannel) // 使用通道0表示主导联 realTimeCallback!!.onRawDataAvailable(0, primaryChannel) // 使用通道0表示主导联
Log.d("DataManager", "已发送单导联数据到图表,数据长度: ${primaryChannel.size}") totalDataPoints += primaryChannel.size
} else {
Log.e("DataManager", "realTimeCallback 为空,无法发送单导联数据")
} }
// 如果有其他通道(如参考通道),也发送 // 如果有其他通道(如参考通道),也发送
@ -253,21 +244,18 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
for (i in 1 until channelData.size) { for (i in 1 until channelData.size) {
val additionalChannel = channelData[i] val additionalChannel = channelData[i]
if (additionalChannel.isNotEmpty()) { if (additionalChannel.isNotEmpty()) {
Log.d("DataManager", "发送附加通道 $i 数据,长度: ${additionalChannel.size}")
realTimeCallback?.onRawDataAvailable(i, additionalChannel) realTimeCallback?.onRawDataAvailable(i, additionalChannel)
} }
} }
} }
} else {
Log.w("DataManager", "单导联主通道数据为空")
} }
} else {
Log.w("DataManager", "单导联数据包 $packetIndex 没有通道数据")
} }
} }
// 添加调试信息 // 只在需要时记录日志
Log.d("DataManager", "单导联蓝牙数据发送完成,总共处理了 ${packets.size} 个数据包") if (totalDataPoints > 0) {
Log.d("DataManager", "快速发送原始数据: ${totalDataPoints} 个数据点")
}
} }
/** /**
@ -399,6 +387,20 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
Log.d("DataManager", "所有通道处理完成,总共处理了 $localProcessedSamples 个样本") Log.d("DataManager", "所有通道处理完成,总共处理了 $localProcessedSamples 个样本")
// 立即发送处理后的数据到图表
if (processedChannels.isNotEmpty() && processedChannels[0].isNotEmpty()) {
val processedData = processedChannels[0]
Log.d("DataManager", "发送处理后的数据到图表,长度: ${processedData.size}")
Log.d("DataManager", "处理后数据前3个值: ${processedData.take(3).joinToString(", ")}")
if (realTimeCallback != null) {
realTimeCallback!!.onRawDataAvailable(0, processedData)
Log.d("DataManager", "已发送处理后数据到图表")
} else {
Log.e("DataManager", "realTimeCallback 为空,无法发送处理后数据")
}
}
// 计算指标(使用第一个处理后的通道) // 计算指标(使用第一个处理后的通道)
Log.d("DataManager", "准备计算指标,处理后通道数量: ${processedChannels.size}") Log.d("DataManager", "准备计算指标,处理后通道数量: ${processedChannels.size}")
if (processedChannels.isNotEmpty()) { if (processedChannels.isNotEmpty()) {

View File

@ -98,28 +98,30 @@ class ECGChartView @JvmOverloads constructor(
private val lockButtonRect = RectF() private val lockButtonRect = RectF()
fun updateData(newData: List<Float>) { fun updateData(newData: List<Float>) {
// 累积数据而不是替换 // 快速累积数据
dataPoints.addAll(newData) dataPoints.addAll(newData)
// 限制数据点数量 // 限制数据点数量(避免内存溢出)
if (dataPoints.size > maxDataPoints) { if (dataPoints.size > maxDataPoints) {
dataPoints = dataPoints.takeLast(maxDataPoints).toMutableList() dataPoints = dataPoints.takeLast(maxDataPoints).toMutableList()
} }
// 计算数据范围 // 快速计算数据范围(减少计算量)
if (dataPoints.isNotEmpty()) { if (dataPoints.isNotEmpty()) {
minValue = dataPoints.minOrNull() ?: 0f // 只计算最近的数据范围,提高性能
maxValue = dataPoints.maxOrNull() ?: 0f val recentData = dataPoints.takeLast(minOf(1000, dataPoints.size))
minValue = recentData.minOrNull() ?: 0f
maxValue = recentData.maxOrNull() ?: 0f
// 确保有足够的显示范围,并添加上下边距 // 确保有足够的显示范围
val range = maxValue - minValue val range = maxValue - minValue
if (range < 0.1f) { if (range < 0.1f) {
val center = (maxValue + minValue) / 2 val center = (maxValue + minValue) / 2
minValue = center - 0.05f minValue = center - 0.05f
maxValue = center + 0.05f maxValue = center + 0.05f
} else { } else {
// 添加20%的上下边距,让曲线显示在中间 // 添加10%的上下边距(减少边距,提高响应速度)
val margin = range * 0.2f val margin = range * 0.1f
minValue -= margin minValue -= margin
maxValue += margin maxValue += margin
} }
@ -131,7 +133,8 @@ class ECGChartView @JvmOverloads constructor(
offsetY = 0f offsetY = 0f
} }
invalidate() // 重绘 // 立即重绘
invalidate()
} }
fun clearData() { fun clearData() {

View File

@ -13,6 +13,17 @@ import androidx.core.content.ContextCompat
import android.view.View import android.view.View
import com.example.cmake_project_test.databinding.ActivityMainBinding import com.example.cmake_project_test.databinding.ActivityMainBinding
import type.SensorData import type.SensorData
import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.Environment
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
import android.content.Intent
import androidx.core.content.FileProvider
import android.net.Uri
import android.widget.Toast
class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.RealTimeDataCallback, BluetoothManager.BluetoothCallback { class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.RealTimeDataCallback, BluetoothManager.BluetoothCallback {
@ -70,6 +81,11 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
private var lastChartUpdateTime = 0L private var lastChartUpdateTime = 0L
private var lastChartStatusTime = 0L private var lastChartStatusTime = 0L
private var lastProgressUpdateTime = 0L private var lastProgressUpdateTime = 0L
// 导出功能相关
private var exportedDataCount = 0
private val dataExportBuffer = mutableListOf<Float>()
private val exportTimeStamps = mutableListOf<Long>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -141,6 +157,12 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
showDebugStatusDialog() showDebugStatusDialog()
true true
} }
// 添加导出按钮(长按清空数据按钮)
binding.clearDataButton.setOnLongClickListener {
showExportDialog()
true
}
} }
private var notchFilterEnabled = false private var notchFilterEnabled = false
@ -402,21 +424,9 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
} }
override fun onRawDataAvailable(channelIndex: Int, rawData: List<Float>) { override fun onRawDataAvailable(channelIndex: Int, rawData: List<Float>) {
// 限制图表更新频率,避免过多更新导致卡顿 // 立即显示原始数据到图表,无延迟限制
val currentTime = System.currentTimeMillis()
if (currentTime - lastChartUpdateTime < 100) { // 100ms内不重复更新图表
return
}
lastChartUpdateTime = currentTime
// 立即显示原始数据到图表
runOnUiThread { runOnUiThread {
try { try {
Log.d("MainActivity", "收到原始数据回调,通道: $channelIndex,数据长度: ${rawData.size}")
if (rawData.isNotEmpty()) {
Log.d("MainActivity", "原始数据前3个值: ${rawData.take(3).joinToString(", ")}")
}
// 确保图表容器可见 // 确保图表容器可见
binding.ecgChartContainer.visibility = View.VISIBLE binding.ecgChartContainer.visibility = View.VISIBLE
@ -424,9 +434,8 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
binding.ecgRhythmView.updateData(rawData) binding.ecgRhythmView.updateData(rawData)
binding.ecgWaveformView.updateData(rawData) binding.ecgWaveformView.updateData(rawData)
Log.d("MainActivity", "已立即更新原始数据图表,通道: $channelIndex,数据长度: ${rawData.size}") // 减少状态更新频率,但不影响图表更新
val currentTime = System.currentTimeMillis()
// 减少状态更新频率
if (currentTime - lastChartStatusTime > 2000) { // 2秒内只更新一次状态 if (currentTime - lastChartStatusTime > 2000) { // 2秒内只更新一次状态
updateStatus("实时图表已更新,通道: $channelIndex,数据点: ${rawData.size}") updateStatus("实时图表已更新,通道: $channelIndex,数据点: ${rawData.size}")
lastChartStatusTime = currentTime lastChartStatusTime = currentTime

View File

@ -14,10 +14,10 @@ class StreamingSignalProcessor {
private var processorId: Long = -1L private var processorId: Long = -1L
private var signalProcessorInitialized = false private var signalProcessorInitialized = false
// 窗口参数 - 针对ECG信号优化确保心率计算 // 窗口参数 - 针对ECG信号优化进一步提高实时性
private var windowSize = 500 // 优化2秒窗口250Hz采样率确保心率计算 private var windowSize = 50 // 优化0.2秒窗口250Hz采样率进一步提高实时性
private var overlapSize = 100 // 优化20%重叠,减少边界效应 private var overlapSize = 10 // 优化20%重叠,减少边界效应
private var stepSize = windowSize - overlapSize // 步长400个样本 private var stepSize = windowSize - overlapSize // 步长40个样本
// 数据缓冲区 // 数据缓冲区
private val dataBuffer = mutableListOf<Float>() private val dataBuffer = mutableListOf<Float>()
@ -182,7 +182,7 @@ class StreamingSignalProcessor {
Log.d("StreamingSignalProcessor", "高通滤波成功(${highpassCutoff}Hz结果前3个值: ${filtered.take(3).joinToString(", ")}") Log.d("StreamingSignalProcessor", "高通滤波成功(${highpassCutoff}Hz结果前3个值: ${filtered.take(3).joinToString(", ")}")
} }
// 2. 低通滤波(去除肌电等高频噪声)- 使用100Hz平衡信号清晰度和噪声抑制 // 2. 低通滤波(去除肌电等高频噪声)- 使用80Hz减少计算量
if (lowpassCutoff > 0) { if (lowpassCutoff > 0) {
val lowpassResult = signalProcessor.lowpassFilter(filtered, sampleRate, lowpassCutoff) val lowpassResult = signalProcessor.lowpassFilter(filtered, sampleRate, lowpassCutoff)
if (lowpassResult == null) { if (lowpassResult == null) {
@ -243,11 +243,11 @@ class StreamingSignalProcessor {
Triple(40.0, 50.0, 30.0) Triple(40.0, 50.0, 30.0)
} }
type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD, type.SensorData.DataType.PW_ECG_SL -> { type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD, type.SensorData.DataType.PW_ECG_SL -> {
// ECG: 专业ECG滤波参数 // ECG: 优化滤波参数,提高实时性
// 低通100Hz (平衡信号清晰度和噪声抑制) // 低通80Hz (减少计算量,保持信号质量)
// 陷波50Hz (中国工频) // 陷波50Hz (中国工频)
// 品质因数10 (避免过度滤波) // 品质因数5 (减少计算复杂度)
Triple(100.0, 50.0, 10.0) Triple(80.0, 50.0, 5.0)
} }
type.SensorData.DataType.PPG -> { type.SensorData.DataType.PPG -> {
// PPG: 低通10Hz, 陷波50Hz // PPG: 低通10Hz, 陷波50Hz