Files
MAG160C/docs/android_app/rendering_diff_phone_20261006.md
T

152 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 手机端渲染差异定位(Android SDK ↔ Windows/Linux SDK 交叉验证)2026-10-06
> 背景:相机改插 root 手机(22041211AC,Android 12 + Magisk)后,按用户要求
> "官方 App 拍照 vs 我们的 App 拍照" 做对比,出现差异就**同时**逆 Android SDK 与
> Linux/Win SDK 交叉验证。本轮结论:**亮度/对比度差异的根因是"窗口半宽 = 基础窗口 ×
> 对比度修正",官方手机 App 带对比度偏移,我们的移植只在 c=0 时一致。**
## 1. 对比做法与结果
| 项 | 官方 App(cn.com.magnity.magnitycx) | 我们的 App |
|---|---|---|
| 照片存放 | `/sdcard/magnity/Cx/media/MAG-160CORE<时间>.jpg`(+ `others/` 里是 DDT) | `/sdcard/DCIM/MAG160C/MAG160C_<时间>.jpg` |
| 照片内容 | **纯 JPEG(无任何数据块,EOI 即文件尾)** | 完整 MDT(INFO0/INFO1/FRAME/NUC/RENDER/EXTREMES) |
| 亮度(同场景) | luma p25=40 p50=49 mean=84 | p25=10 p50=19 mean=53 |
| 调色板索引分布(按 16 档) | 峰值在 2–4 档(10%/40%/12%) | 峰值在 0–1 档(15%/39%) |
→ 同一静止场景,**我们的图整体偏暗约 30 luma**:热点位置一致、调色板一致,属于
"灰度映射/窗口"差异(不是色板或数据差异)。
## 2. 交叉验证:窗口是怎么算的
**Android SDK(libcxsdk 反编译,权威)**:
```
CFunctions::GrayEnlarge() // 灰度/窗口总入口
uVar2 = CalContrastOffsetedAutoEnlargeRange() // 窗口"范围"(不是半宽)
winLo = min(mean - (uVar2>>1), statMin) // 注意:向外扩到数据范围
winHi = max(mean + (uVar2>>1), statMax)
HistDeRedundance() // 直方图去冗余(我们的 lutRebuild)
CFunctions::CalContrastOffsetedAutoEnlargeRange():
base = (dev24 * 1000) >> shift // dev24=8, shift=3 → 1000
c = clamp(contrast, -100, 100) // ★ 对比度设置
range = base - base*c / (150 + (c<0 ? -100 : 0)) // c>0:range 变小(窗口更窄→更亮)
if (brightness 项 dev+0x6c/0x38 有效) range += 亮度修正项
range = max(range, 0x80)
```
**Windows SDK**:`MAG_SetAutoEnlargePara` / `MAG_SetFixedEnlargePara` /
`MAG_SetSubsectionEnlargePara`(同一组参数的用户 API)。
**Linux SDK**:`MAG_SetAutoEnlargePara`(libmagcore @0x11be50)同名同参 —— 两条 SDK 线吻合。
**官方手机 App 侧**:`ExtPara`(设备上报)含 `intContrastOffset` / `intBrightOffset` /
`intAutoEnlargeRange` / `intDetailRatio` / `intCurrentEnvTemperature`,App 用
`DeviceController.getExtParameter()` 读取后回写(`setExtParameter`)。
## 3. 我们的移植差在哪
`RenderPipeline.statsWindow()` 的窗口结构与官方**完全一致**(`winLo=min(mean−half,min)`、
`winHi=max(mean+half,max)`),但:
- `half = base>>1` **只在对比度 c=0 时**等于官方(官方 `range=base` → `range>>1`);
- 官方手机 App 带 `intContrastOffset`(正对比度 → 窗口更窄 → 更亮)→ 这就解释了我们偏暗。
**证据链**:PC 端(对比度 0)我们的渲染与官方 PC SDK 逐像素差 1.3 级 ✔;
手机端(官方 App 带对比度)差值 ~30 级 ✔ —— 同一实现、不同对比度输入,两个场景都对得上。
## 4. 顺带确认的两件事
1. **环境温度输入**:官方 App 调 `setEmissivity(emissivity, ExtPara.intCurrentEnvTemperature)`
—— 即 PC SDK 的 `dev+0xA4`(我们温度链里的 `ATZeroErrorPoint`)就是**环境温度**
(设备默认 22216 = 22.2 ℃);App 的发射率设置也走这里(100 = 1.00)。
→ 我们 App 的发射率设置应当**接进 `OfficialTemp.temperature(emissivity=…)`**(当前固定 0.98)。
2. **BasePara2 真机报文**(60 B,magic 已由我们的读取层吃掉):
`fpaTempFix` @+14(short)=1699、`ATZeroErrorPoint` @+40(int)=22216、`ATErrorSlope` @+44(float)=−0.02789
—— 已写入单测作为地面真值。**注意**:PC 上实测的补偿量 `v50`(=dev+0x5C−dev+0x54,0…89 mC)
**不等于** `fpaTempFix`(1699),故 App 侧 `sensorDeltaMc` 暂取 0(误差 ≤0.09 ℃),
待定位 dev+0x5C 的来源(疑似滤波后的 FPA 温度)后再接入。
## 5. 下一步(按此顺序)
1. 移植 `CalContrastOffsetedAutoEnlargeRange`(含亮度项到 `statsWindow`),并在设置页加
"对比度/亮度"(默认值取官方 App 的默认),重拍对比 → 目标是 luma 直方图与官方吻合。
2. 补 `getExtParameter` 命令(Android SDK 侧 JNI:`DeviceController.getExtParameter`),
把设备的 `intContrastOffset/intBrightOffset/intCurrentEnvTemperature` 接进来。
3. 温度读数接 App 的发射率设置(`OfficialTemp` 已支持参数)。
4. 以上在 App 验证无误后,按用户要求同步进 `csdk`(C 参考实现)与 csdk 测试。
## 6. 追加实测(同日):实时画面亮度差已复现,差异区间收敛到"窗口/LUT"
同一静止场景、前后相隔约一分钟分别截取两个 App 的**实时画面**(官方横屏 3200×1440、
我们竖屏 1440×3200),按行/列亮度自动裁掉黑边后统计:
| | p25 | p50 | p75 | mean |
|---|---|---|---|---|
| 官方 App 实时 | 12–20 | 26–33 | 63–124 | 53–72 |
| 我们的实时 | 2–4 | 12–25 | 27 | 20–25 |
→ 差异是**真实且系统性的**(不是拍照时刻/场景差异),并已把差异区间收敛到显示阶段的
"窗口 + 灰度 LUT"两步。
**三方交叉验证(Android ↔ Windows ↔ Linux)**:
| 阶段 | Android(libcxsdk) | Windows(CoreSDKLib) | Linux(libmagcore) |
|---|---|---|---|
| 设置入口 | `setAutoEnlarge(enlargeRange)`(官方 App 传 BasePara2 的 `enlargeRange`=**8**) | `MAG_SetAutoEnlargePara(chan, range, contrast, brightness)` | 同名同参(+0x24/+0x30/+0x34) |
| 窗口范围 | `CalContrastOffsetedAutoEnlargeRange()`:`range = base − base*c/(150+(c<0?−100:0))`,`base=(dev24*1000)>>shift`;`winLo=min(mean−range/2, min)`、`winHi=max(mean+range/2, max)` | 同结构(我们的 `statsWindow` 即此移植) | 同结构 |
| 灰度 LUT | `HistDeRedundance()`:1024 桶直方图(`0xFFC00000/span`、`>>20 & 0xffc` = 我们的 `lutRebuild` 公式)+平滑+CDF | 我们的移植来源 | 同结构 |
**结论**:窗口公式在对比度 c=0 时与我们**逐字节一致**(且官方 App 只传了 range=8,
未设对比度),直方图/LUT 的骨架也一致 —— 所以剩下的差异只可能出在
① LUT 的**平滑/CDF/pivot 细节**(Android 版 `HistDeRedundance` 的后续段尚未逐行核对),
或 ② 官方 App 通过 `setEX`/`ExtPara` 设置的**亮度偏移**(`intBrightOffset`)等参数。
这两处是下一轮的直接目标(继续用同一套"三 SDK 交叉 + 真机截图定量"的方法)。
## 7. 渲染差异的真正落点:**调色板(铁虹表)不同**
### 7.1 先排除的两项(都有反编译证据)
- **窗口**:Android `GrayEnlarge` + `CalContrastOffsetedAutoEnlargeRange` 与我们
`statsWindow` 在对比度 c=0 时**逐字节一致**;官方 App 只调用
`setAutoEnlarge(BasePara2.enlargeRange=8)`,没有对比度/亮度参数。
- **灰度 LUT/CDF**:Android `HistDeRedundance` 的直方图与双向 CDF 段与我们 `lutRebuild`
**逐行一致**(`0xFFC00000/span`、`>>20 & 0xffc`、`total>>12`、`iVar3=0x10000/step`、
前后向各一张表、`+0x100/+0x200` 步进)——这段不是差异来源。
### 7.2 官方默认调色板 = 索引 2(和我们一样),但**表不同**
- 官方 App:`ImageViewer.java:383` / `DeviceController.java:91` →
`setColorPalette(SharedPreferencesManager.getInt("paletteIndex", 2))` → 默认 **2(铁虹)**。
- 判定方法:**"亮度 → 颜色"曲线在同一调色板下唯一**(与灰度分布/亮度水平无关),
用它做检验(把像素代入调色板表,取同亮度最近项的色差):
- 对照(我们自己的像素 → 我们的表):中位色差 **0.3**,83.6% 在 ±10 内;
- 官方像素 → 我们的表:中位色差 **20.3**,只有 36% 在 ±10 内;最接近的调色板也不是 2 号;
- 分亮度段看:亮度 32–96 时官方是**橙色**(R−B≈+90,R≈97–102/G≈44–77/B≈6),
我们同一亮度是**紫/蓝**(R−B≈−33…+20)。
- 原因(已定位到二进制):**我们索引 2 用的是 PC SDK 的表**
(`OfficialTables.PALETTE256_ARGB`,来自 CoreSDKLib dev+0xb18),而官方**手机** App 用的是
**Android SDK 运行时生成**的表 —— `CFunctions::SetColorPalette` case 2(libcxsdk @0x26c70)。
上一轮把 case 0/1/3…10 的生成式移植成了 `VendorPalettes`,**case 2(铁虹)当时是拿 PC 表
做"锚点校验"、没有移植生成式** → 两表在中间段不同,颜色/亮度观感因此不同。
- "对比度更高"和"条纹更少"都由此解释:不同色带的亮度斜率不同(观感对比度不同),
且官方色调更亮(暗端被抬起)→ 噪声与 FPN 在暗端不再显眼。
### 7.3 条纹本身的实测(数据层无差异)
| 指标 | 官方 | 我们 |
|---|---|---|
| NUC 帧 column-profile HF(PC 抓帧对照) | 7.81(0.63% of spread) | 7.20(0.63%) |
| 渲染图平坦块列抖动(同刻截图,16×16 块) | 0.81–1.44 | 0.34–0.94 |
| 渲染图 HF 能量 mean\|dx\|/mean\|dy\| | 2.80 / 3.67 | 2.71 / 3.47 |
→ 数据与降噪层面我们不差于官方;"条纹更明显"是**色调/亮度**的感知效应
(官方实时画面 HF 更低 1.27 vs 2.92,也可能有 SDK 时域平滑参与)。
### 7.4 下一步
1. 移植 `SetColorPalette` **case 2** 的生成式(4 段线性,常量 0x75/0xbc/0x25/0xff/0x27/0x3b/0x1c3… 已在反编译中),
替换 App 索引 2 的表;然后**同一分钟**内截两 App 实时图做"亮度→颜色"曲线回归(目标:中位色差降到控制组水平)。
2. 顺带核对其余 11 个索引是否也应改用 Android 生成式(本轮 `VendorPalettes` 已覆盖 10 个)。
3. 之后再按用户要求把验证过的改动同步进 `csdk`。