android: 实时画面卡顿根因是软件画布(6.5→15.1fps),标注统一 + 7x7 细节增强
卡顿: 热像走 SurfaceView.lockCanvas 的软件画布, 每帧在 CPU 上做 320x240 -> ~810x1080 双线性放大 + 清整屏, 实测只有 6.5 帧/秒、单帧 134ms。dumpsys gfxinfo 看不到这条路径(只统计 Compose), 故给渲染线程加自测量日志才定位到。改用 lockHardwareCanvas(API29+, 旧系统回退) 后 15.1 帧/秒、单帧 6.4ms。 同时消除每帧垃圾: 复用翻转缓冲/标记列表, 色条改预渲染位图; 温度扫描从主线程 移到 Dispatchers.Default。 标注统一: 新增 AnnotSpec(唯一几何定义) + MarkerPainter(唯一绘制例程), 实时/ 分析/照片/视频共用, 极端值标签改用 max/min; 照片按传感器朝向 3x 分辨率保存。 修 placeLabel 不判重叠(min 与 max 印成一团), 现支持让位。 分析对齐: 极端值漏了 sensorToPhoto 映射(落在左上角几十像素内), annotateJpeg 忽略照片镜像(另存时标记镜像到另一侧), 两处已修。 新增 BLOCK_EXTREMES(0x5BB5B562) 存拍摄时的极值位置/温度: 分析页重扫 NUC 不可能 逐位复现 live 结果, 真机出现过两个相距几像素的 min。 分析面板被导航栏遮挡: UiInsets.navPx 改可观察状态并加 padding。 7x7 局部细节增强(官方 FilterDetailEnhancement_Simple 逐行移植), 强度换算 level shl 3 经 MAG_SetDetailEnhancement(钳 0..32)核对一致; 设置页新增 "图像增强" 0-4 级, 默认关闭(无官方参考输出), 不影响逐位基线。 真机验证(小米 22041211AC/Android12/MIUI): 15.1fps、拍照含 2 探针+2 极值、 照片与分析页标记位置/风格一致且各只有一个 min/max、分析数值与照片吻合、 增强 2 级细节明显且不掉帧。测试 96 项全绿。
This commit is contained in:
@@ -489,6 +489,105 @@
|
||||
"未连接"分支显示,正常出图时用户看不到任何反馈)。
|
||||
- [x] 单测 66 → **76 项全绿**;debug + release(R8) 双构建通过;APK 已更新。
|
||||
|
||||
## 用户反馈修复 第二十二轮(2026-09-12,7×7 细节增强 + 标注统一 + 卡顿根因)
|
||||
|
||||
用户第四轮实机反馈(四点):移植 7×7 局部细节增强;分析界面标点没对齐;
|
||||
实时界面与照片的标注风格必须**完全一致**("就像直接从实时界面截图"),
|
||||
最高最低用 `min`/`max` 标注;实时画面还是卡。
|
||||
|
||||
### 1) 卡顿根因(本轮最有价值的发现,已量化)
|
||||
|
||||
先前只靠 `dumpsys gfxinfo`(显示 7.26% janky)猜原因,但**它根本看不到热像画面**:
|
||||
热像走 `SurfaceView.lockCanvas()` 软件画布,gfxinfo 只统计 Compose 层
|
||||
(实测 5 秒内只记到 28 帧,而流本身在 15fps)。于是给渲染线程加了自测量日志
|
||||
(`MAG160C/render: paints=N avg=... worst=...`),一测即见真相:
|
||||
|
||||
| 阶段 | 绘制帧率 | 单帧绘制耗时 |
|
||||
|------|---------|------------|
|
||||
| 修复前 | **6.5 帧/秒** | **134 ms** |
|
||||
| 加硬件画布后 | **15.1 帧/秒** | **6.4 ms** |
|
||||
|
||||
根因:`SurfaceHolder.lockCanvas()` 返回**软件**画布,于是每帧都在 CPU 上做
|
||||
320×240 → 约 810×1080 的双线性放大 + 手工清整屏(约 10MB)。
|
||||
**改用 `lockHardwareCanvas()`(API 29+,旧系统自动回退软件路径)**,放大与清屏
|
||||
交给 GPU,CPU 只剩 76800 像素的拷贝。相机 15fps 因此第一次能完整到达屏幕。
|
||||
|
||||
配套修掉的每帧垃圾(卡顿的次要来源,也是 GC 停顿的来源):
|
||||
- `setFramePixels` 每帧 `IntArray(320*240)`(4.6MB/s 垃圾)→ 复用 `flipped`
|
||||
- 色条每帧 new 96 个 `Paint` + 96 次 `drawRect` → 预渲染 1×96 位图,仅换色板时重建
|
||||
- 标记列表每帧 new ArrayList → 复用
|
||||
- 温度点名 `refreshTemps()` 原本在**主线程**(LaunchedEffect 驱动):
|
||||
400ms 一次分配 19200 整型数组 + 全屏扫描 + `copyNuc`(持渲染线程的锁)
|
||||
→ 移到后台 `Dispatchers.Default` 循环,结果经 `Dispatchers.Main` 发布
|
||||
|
||||
### 2) 标注统一(照片 = 实时截图)
|
||||
|
||||
- 新增 `core/AnnotSpec.kt`(唯一几何定义)+ `media/MarkerPainter.kt`(唯一绘制例程),
|
||||
实时/分析/照片/视频四处共用。此前各画各的,所以"看起来不一样"。
|
||||
- 极端值标签由 高/低 改为 **`max` / `min`**(用户指定)。
|
||||
- 照片以**传感器朝向**保存(4:3 横),文字在该帧内水平,3 倍分辨率(文字清晰)。
|
||||
|
||||
### 3) 分析界面标点对齐(两个真实 bug)
|
||||
|
||||
1. **极端值没有走坐标映射**:probes 经 `sensorToPhoto` 转换,extremes 却被
|
||||
**原样加入**(`marks.addAll(extremes)`)。传感器坐标是 0..159/0..119,在
|
||||
960×720 的照片上就落在**左上角几十像素内**——真机照片实测两个 min/max
|
||||
全挤在左上角。已改为同一映射。
|
||||
2. **`annotateJpeg` 忽略照片镜像**:从分析界面另存时,标记按未镜像坐标烧录,
|
||||
于是**镜像到另一侧**。已加 `mirror` 参数并走 `sensorToPhoto`。
|
||||
|
||||
### 4) 标签互相压字(真机照片可见)
|
||||
|
||||
`min 21.9℃` 与 `max 32.6℃` 印在一起成一团。`placeLabel` 只判边界不判重叠,
|
||||
现已支持"已放置盒"列表:默认右置 → 被占则翻到左侧 → 仍冲突则下移让位,
|
||||
并保证始终在画面内。`MarkerPainter` 一次调用内跟踪,分析界面改为把 probes 与
|
||||
extremes **合并成一次 draw**(原来分两次调用,彼此看不见)。
|
||||
|
||||
### 5) 重复的 min 标记(分析页 vs 照片)
|
||||
|
||||
分析页重新扫描 NUC 推导极值,与拍摄时 live 扫描的结果**不可能逐位一致**
|
||||
(传感器在拍摄与重载之间会漂移,平坦区 argmin 极易移位)——真机出现
|
||||
两个相距几像素、22.0/22.1℃ 的 min。已在容器新增
|
||||
`BLOCK_EXTREMES (0x5BB5B562)`:拍摄时把 minPos/maxPos/minMc/maxMc 一并存盘,
|
||||
分析页优先采用,从而与照片上烧录的标记**完全相同**。另存新照片也继续携带。
|
||||
|
||||
### 6) 分析页测量面板被导航栏遮住
|
||||
|
||||
`UiInsets.navPx` 是普通 `var` 且初值 0,全屏覆盖层(分析查看器)读它时
|
||||
拿到的是首帧值 0,于是底部面板落在导航栏之下(截图可见数值不可见)。
|
||||
已改为 `mutableStateOf` 并让分析查看器 `padding(bottom = navPx)`。
|
||||
|
||||
### 7) 7×7 局部细节增强(官方同款)
|
||||
|
||||
按反编译的 `CFunctions::FilterDetailEnhancement_Simple` + `LocalMap7x7_Simple`
|
||||
移植(`core/DetailEnhance.kt`):7×7 窗口按 4×4 抽样(x/y 步长 2),
|
||||
`mean = sum >> 4`,`if (strength <= (max-min)*32)` 时
|
||||
`detail = (0x8000/divisor)*(center-mean)`,最后 `gray += (k*detail) >> 15`。
|
||||
管线位置与官方一致:`grayMap → 细节增强 → upscale2x → 调色板`。
|
||||
强度换算经官方 SDK 核对:`MAG_SetDetailEnhancement` 把等级钳到 0..32,
|
||||
调用方传 `level << 3`——**与本实现 `level shl 3` 完全一致**。
|
||||
设置页新增"图像增强"(关闭/1–4 级),**默认关闭**(无官方参考输出可逐位比对,
|
||||
故 opt-in)。默认关闭时 `RenderPipelineTest` 的逐位基线不受影响(96 项测试全绿)。
|
||||
|
||||
实机实测:级别 2 下仍 15.1fps,单帧绘制 11.3ms(滤波器约 +5ms,帧预算 66ms 内),
|
||||
细节增强清晰可见。
|
||||
|
||||
**本轮真机验证(小米 22041211AL / Android 12 / MIUI,无线 adb 192.168.88.137:44323)**:
|
||||
|
||||
- [x] 渲染帧率 6.5 → **15.1 帧/秒**,单帧 134ms → **6.4ms**(开增强 11.3ms)
|
||||
- [x] 拍照:`capture: nuc=yes probes=2 mirror(h=false,v=true) extremes=2 saved=true`
|
||||
- [x] 照片上标记风格与实时界面一致(同一 MarkerPainter),`max 32.6℃` 在热区、
|
||||
`min 22.0℃` 在冷区、Pt1/Pt2 带白底标签,互不压字
|
||||
- [x] 分析界面:标点与照片烧录位置对齐,**只有一个 min、一个 max**
|
||||
- [x] 分析面板数值可见且与照片一致(最高 32.9 / 最低 22.0 / 中心 24.4℃)
|
||||
- [x] 图像增强 2 级:画面细节明显增强,帧率不掉
|
||||
|
||||
**教训记录**:
|
||||
- `dumpsys gfxinfo` 不统计 SurfaceView 的 lockCanvas 绘制。判断自绘画面性能
|
||||
必须自己打点(本轮加的 `MAG160C/render` 日志就是为此,已保留)。
|
||||
- 断言"某处卡"之前先量出**每帧耗时**和**实际帧率**:本轮原以为是温度扫描
|
||||
(主线程 400ms 扫描)导致,量化后才发现是软件画布放大,量级差 20 倍。
|
||||
|
||||
## 用户反馈修复 第二十一轮(2026-09-12,无线 adb 真机调试:根因是 launchMode)
|
||||
|
||||
**本轮最大的发现**:前几轮反复出现的"连接风暴/重连循环/相机每 2 秒重枚举"
|
||||
|
||||
Reference in New Issue
Block a user