Files
..

MAG160C C SDK (csdk/)

MAG160C 热像仪(160×120,USB VID 0x833C)的纯 C 重新实现,基于对原厂 Windows/Linux/Android SDK 的完整逆向(见 ../analysis/reverse_20260813_full.md)。 包含:USB 会话层、帧解析/流组装、温度换算、TCM 板通信、以及官方渲染管线的 逐像素复刻(demo3 v5)。

目录结构

csdk/
  include/mag160c/
    mag160c.h             公共 C ABI(错误域、协议常量、帧格式、温度、TCM)
    mag160c_display.h     FFC 调度器 + 显示辅助(ref rebase 等)
  src/
    mag160c_internal.h    内部共享(last-error)
    mag160c_error.c       TLS last-error + 错误名
    mag160c_frame.c       0x1bb1b11b 帧解析 + 流组装器
    mag160c_temp.c        Calibration / ConvertResponse2Temperature / T2E
    mag160c_tcm.c         FTDICommand 0x7e 组帧 + 旋转/指示灯构建
    mag160c_ir.c          libusb 会话(link/start/stop/ffc/info/回调/FFC调度)
    mag160c_display.c     显示辅助(FFC 调度器、ref rebase、坏点)
    mag160c_tables.h      T2E/E2TAccQ10 温度表(从二进制提取)
    mag160c_official_*.h  官方调色板 / LUT1024 / T2E(从官方活体抓取导出)
  tools/
    mag160c_demo3.c       主程序:官方管线复刻(DDT+NUC+盲元+窗口+LUT+2x)
    tsdk_pair2.c          官方 ThermalSDK 同帧抓帧工具(含表/盲元/快门)
    tsdk_pair3.c          增强采集(DDT 复制、完整端点表、盲元、h1024/cdf)
    mag160c_cli.c         CLI:ir-info/ffc/start/stop/tcm-rotate/tcm-light 等
    mag160c_csdk_stream_test.c  csdk 流测试
    legacy/               历史探索工具(归档,勿用)
  tests/                  test_frame / test_temp / test_tcm / test_api / test_display
  CMakeLists.txt          CMake 构建(可选;当前环境直接用 gcc)
  WINDOWS_TESTING.md      Windows 硬件测试指南(USBPcap/Wireshark/MinGW)

构建

方式 A:直接 GCC(Windows + MinGW-w64)

编译器:WinLibs MinGW-w64(gcc 16.2.0,UCRT),winget install BrechtSanders.WinLibs.POSIX.UCRT。 2026-10-08 已用该编译器完成全部程序与测试的编译验证(见各节"验证状态")。

# CLI 工具
gcc -O2 -w -DMAG160C_STATIC -I"csdk\third_party\libusb\win64" -I"csdk\include" -I"csdk\src" `
    -o build-artifacts\mag160c_cli.exe csdk\tools\mag160c_cli.c `
    csdk\src\mag160c_ir.c csdk\src\mag160c_frame.c csdk\src\mag160c_temp.c `
    csdk\src\mag160c_tcm.c csdk\src\mag160c_error.c csdk\src\mag160c_display.c `
    csdk\third_party\libusb\win64\libusb-1.0.x64.a

# 主程序 demo3(官方管线复刻;demo3 调用 render API,必须链接 mag160c_render.c)
gcc -O2 -w -DMAG160C_STATIC -I"csdk\third_party\libusb\win64" -I"csdk\include" -I"csdk\src" `
    -o build-artifacts\mag160c_demo3.exe csdk\tools\mag160c_demo3.c `
    csdk\src\mag160c_render.c csdk\src\mag160c_display.c csdk\src\mag160c_error.c `
    csdk\third_party\libusb\win64\libusb-1.0.x64.a -lgdi32 -luser32

# 官方抓帧工具
gcc -O2 -w -o build-artifacts\tsdk_pair3.exe csdk\tools\tsdk_pair3.c

libusb 依赖:csdk/third_party/libusb/win64/(头文件 + DLL + MinGW 导入库)。

方式 B:CMake(有 CMake 时)

cmake -B build -S csdk
cmake --build build
ctest --test-dir build

无 libusb-1.0 时,IR 会话 API 返回 MAG160C_ERR_NOT_SUPPORTED;纯计算 (帧解析、温度、TCM)和对应测试无依赖可跑。

快速开始

1. 连接并启动(核心 API 流程)

#include <mag160c/mag160c.h>
#include <mag160c/mag160c_display.h>

static void on_frame(uint32_t idx, const uint8_t *data, size_t len, void *user) {
    /* data: 完整帧(0x1c 头 + 像素)。用 mag160c_frame_parse 解析。 */
    mag160c_frame_header_t hdr;
    const uint16_t *pixels;
    if (mag160c_frame_parse(data, len, &hdr, &pixels) == MAG160C_OK && hdr.frame_type == 0)
        ; /* pixels[19200] 小端 uint16,160×120 */
}

int main(void) {
    mag160c_ctx_t *ctx = NULL;
    mag160c_ir_t *ir = NULL;
    mag160c_init(&ctx);
    mag160c_ir_open(ctx, &ir);              /* 自动找 VID 0x833c,config 2,interface 0 */
    mag160c_ir_prepare(ir);                 /* 66b/66c/66f + FFC(0)x2 + 300ms */
    mag160c_ir_set_frame_callback(ir, on_frame, NULL);

    /* 官方 FFC 节律(周期 1800 帧 / 间隔 9),保持 type=0 流稳定 */
    static mag160c_ffc_scheduler_t sched;
    mag160c_ffc_scheduler_init(&sched, 1800, 9);
    mag160c_ir_set_ffc_scheduler(ir, &sched, 0);

    mag160c_ir_start(ir);                   /* 线程收帧 + 0x6bb6b673 */
    /* ... 收帧 / 读温度 ... */
    int32_t temp_mc = 0;
    mag160c_ir_read_temperature(ir, 80, 60, &temp_mc);

    mag160c_ir_stop(ir);
    mag160c_ir_close(ir);
    mag160c_shutdown(ctx);
    return 0;
}

2. 关键 API

API 说明
mag160c_ir_open/close USB 设备会话
mag160c_ir_prepare 官方初始化命令序列
mag160c_ir_start/stop 起停流(0x6bb6b673 / 0x6bb6b674)
mag160c_ir_trigger_ffc(param) 手动 FFC(0 或 1)
mag160c_ir_set_ffc_scheduler 挂官方节律 FFC 调度器
mag160c_ir_set_frame_callback 每帧回调(解析后数据)
mag160c_ir_read_temperature(x,y,&t) 探针温度(毫摄氏度×100)
mag160c_frame_parse 纯解析:marker/计数/类型/像素指针
mag160c_frame_stream_* 流组装器(乱序/分包恢复)
mag160c_temp_calibrate 官方逐像素分段线性校准(NUC 温度域)
mag160c_temp_convert_response 自动增益线性换算
mag160c_temp_t2e_interp T2E 曲线 Q13 插值
mag160c_tcm_* TCM 板 FTDICommand 0x7e 帧(旋转/指示灯)

错误码域 mag160c_error_t 与官方 errno 域 0xE4AE0001.. 对齐; mag160c_last_error() 取线程局部最近错误字符串。

3. CLI 用法(mag160c_cli.exe,无硬件可跑纯计算)

build-artifacts\mag160c_cli.exe ir-info                 # 枚举/信息
build-artifacts\mag160c_cli.exe frame-test              # 帧解析自测
build-artifacts\mag160c_cli.exe temp-test               # 温度换算自测
build-artifacts\mag160c_cli.exe tcm-rotate 5            # TCM 旋转指令编码
build-artifacts\mag160c_cli.exe tcm-light green blink   # TCM 指示灯指令编码

4. 主程序 demo3(官方渲染管线复刻)

tools/mag160c_demo3.c 是当前维护的 Windows 直连演示/研究程序,逐像素复刻 CoreSDKLib.dll 的完整链路:

USB帧(type=0) → 快门提取(帧尾字) → FFC状态机(0x9ee0)
→ 端点选择+Q12插值(0x16ae0/0x16dd0/0x16f10, 快门驱动)
→ ref = FFC窗口4个type=1帧均值(0x41ee0 mode=4)
→ NUC查表(0x17200) → 盲元补偿(0x17330, 32条固定记录)
→ [可选后处理: temporal 0x1e920 / 帧差 0x175a0 / 隔行平滑 0x184f0/0x18660]
→ 统计+窗口(0x10780/0x109c0, half=312) → LUT1024重建(0x11ee0, 含0x8c)
→ 灰度+2x升采样(0x19740) → 官方调色板

官方附加环节编译开关(默认 0 = 官方当前会话行为)

官方 SDK 中这些路径编译在内但当前会话控制字段为 0(未执行)。demo3 全部移植,用编译开关启用,运行时用按键切换测试:

开关 对应官方函数 官方字段 按键
OFF_IMPL_230 帧差调整 0x175a0 0x230=0 8
OFF_IMPL_41FE8 隔行平滑 0x184f0(2x)/0x18660(4x) 0x41fe8=0 0
OFF_IMPL_30 gray 覆盖 0x17770(auto)/0x17c40(manual) 0x30=0, 0x84 9
OFF_IMPL_UPSCALE Nx 升采样 4x/8x 当前 2x —
# 默认版本(与官方行为一致)
gcc ... (见上文 demo3 编译命令)

# 全部附加环节开启版(默认展示各路径,按键切回官方默认)
gcc -O2 -w -DOFF_IMPL_41FE8=1 -DOFF_IMPL_30=1 -DOFF_IMPL_230=1 -DMAG160C_STATIC ...

说明:

  • 0x17770(auto gray 双边滤波)的空间权重表在官方二进制 .data 节被裁剪 无法提取,demo3 用标准高斯核近似(σ=2.0),值域核按官方生成式(sqrt 核)。
  • 0x17c40(manual)已逐行翻译:8x8 窗口 4x4 采样对比度增强累积 → gray。
  • 0x18660 为逐行翻译(4 相位加权插值);0x184f0 为 2-tap 半分辨率插值。
  • 验证:默认版无回归;41fe8=1 开启后 NUC 输出 std 从 ~250 降到 ~95(平滑生效)。

运行(需要 build-artifacts/ 下的 DDT 表文件):

Stop-Process -Name mag160c_demo3 -Force -ErrorAction SilentlyContinue
Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDirectory "C:\Project\MAG160C\build-artifacts"

依赖文件(放 exe 同目录):

文件 来源
mag160c_official.ddt 官方 DDT 校准文件(%TEMP%\Core<序列号> 副本)
mag160c_official_nuc_gain.bin 官方 NUC gain/off 表(历史抓取,备用)
mag160c_official_nuc_thr.bin 官方 NUC 阈值表(历史抓取,备用)
libusb-1.0.dll libusb 运行库

验证状态(2026-08-14):

  • NUC+盲元 与官方同帧抓帧逐像素 0/19200 误差
  • 阈值/gain 插值 0 误差;2x 升采样 0/76800 误差;窗口公式与官方一致
  • 同静态场景 320×240 RGB 直方图与官方相关性 0.98(跨会话)
  • 显示层 = 官方同款 320×240(2x 平滑灰度 + 调色板 + HALFTONE 放大)
  • 输出日志:demo3_diag.txt(每帧统计)、demo3_ffc.txt(FFC 事件)、 demo3_auto_*.bmp(每 30 帧快照,320×240)

5. Linux 移植(mag160c_render API)

include/mag160c/mag160c_render.h + src/mag160c_render.c 是平台无关的 官方渲染管线模块:喂原始 USB 帧 → 输出 320x240 RGB24。内部完整实现 官方链路(快门提取→FFC 状态机→ref 采集→表重建→NUC→盲元→窗口→LUT→ gray→2x→调色板),FFC 命令通过回调发给调用方(调用方持有 USB 传输)。

mag160c_render_cfg_t cfg = { .width = 160, .height = 120,
    .ddt_path = "mag160c_official.ddt", .ffc_period = 1800,
    .ffc_drift = 250, .startup_force_ffc = 1, .cdf_pivot_75 = 1,  /* PC 参考实现自身取值 */
    .ffc_cb = my_ffc_sender, .ffc_user = NULL };
mag160c_render_init(&r, &cfg);
/* 每帧: */
mag160c_render_frame(r, full_frame_with_header, 1, rgb320x240);

tools/mag160c_linux_demo.c 是 Linux 风格参考程序(libusb + render API + display 后端抽象,Windows 上也可编译运行,输出 thermal_*.bmp 验证)。 Linux 构建:

gcc -O2 -Wall csdk/tools/mag160c_linux_demo.c csdk/src/mag160c_render.c \
    csdk/src/mag160c_ir.c csdk/src/mag160c_frame.c csdk/src/mag160c_temp.c \
    csdk/src/mag160c_tcm.c csdk/src/mag160c_error.c csdk/src/mag160c_display.c \
    -lusb-1.0 -lpthread -lm -o mag160c_linux_demo

显示后端(display_rgb 中)可按目标平台实现:fbdev / DRM-KMS / LVGL image。 UI 与动画建议:LVGL(界面/交互动画)+ rlottie(Lottie 特效),参考 LiThermal 项目;主控建议全志 T113-S3(QFP128,非 BGA,~20 元,G2D 2D 加速,Tina Linux)。完整评估见 ../docs/linux_port_plan.md。

6. 官方抓帧工具(tsdk_pair2 / tsdk_pair3)

用途:对照验证 demo3 与官方管线。先停 demo3,再运行(同一设备独占):

# tsdk_pair2:官方 ThermalSDK 同帧抓帧(gray/rgb/raw/ref/f20/tables/win)
& build-artifacts\tsdk_pair2_fixed.exe C:\Project\MAG160C\analysis\pairs_xxx 12 6000

# tsdk_pair3:增强采集(+ 完整端点表/盲元记录/shutter/h1024/cdf/DDT 复制)
& build-artifacts\tsdk_pair3.exe C:\Project\MAG160C\analysis\pairs_xxx 8 6000
# 注意:config.txt 和 pair_*.meta/win 写在进程 CWD,跑完移回输出目录

参数:<输出目录> <抓帧对数> <官方SDK初始化等待ms>。 若报 "no channel":停止所有程序后重新插拔 USB。

测试

# 无硬件验证(纯计算)
build-artifacts\csdk_test_frame.exe
build-artifacts\csdk_test_temp.exe
build-artifacts\csdk_test_tcm.exe
build-artifacts\csdk_test_api.exe
build-artifacts\csdk_test_display.exe

协议要点(详见 analysis/protocol_spec.md)

  • 端点:EP0x03 命令 OUT / 0x82 响应 IN / 0x81 帧流 IN / 0x84 大块读 IN
  • 命令 4 字节 {magic},仅 FFC(0x6bb6b672) 用 8 字节 {magic, param}
  • 帧:0x1bb1b11b | counter | len | type(0/1) | ... | 像素 u16 LE | 0x1bb1b11c
  • 快门(设备温度代理)= 帧尾字 frame[len+0x24](width=160 时)
  • 温度:temp_mC = T2E[3*nuc - C],C≈5797(会话相关)

文档索引

文档 内容
../analysis/protocol_spec.md USB 协议/帧/命令/温度完整规格
../analysis/reverse_20260813_full.md 官方管线 Ghidra 全量逆向 + 鬼影根因
../analysis/session_state.md 当前会话状态/恢复点(心跳)
../analysis/history/worklog_完整逆向工程说明_20260811.md 项目总览(目录/环境/历史)
WINDOWS_TESTING.md Windows 抓包与硬件测试指南
legacy/ 历史工具源码归档(早期探索,功能已被现工具取代)

与官方手机 App 的对齐(2026-10-06,从安卓侧验证后同步)

  1. 灰度 LUT:手机对齐移植在 Android 管线,不在本 C 参考里(2026-10-06 复核修正)。 第 29 轮曾把 cdf_pivot_75 改成 0 去凑官方手机 App,那个判断已作废:真机 libcxsdk 反汇编显示官方开着 pivot,而且它的 HistDeRedundance 与本 C 参考 用的(PC 版)结构不同——固定基值 16*(448+447*b/200)、步进上限 max(1,(0x1800+4j)·k>>13)(k=min(0x200,16·span/range))、反向 CDF 从 hist[pivot] 起、pivot=max(center_idx,(meanBelow+meanAbove)/2)。 C 侧已实现:mag160c_render.h 的配置项 android_lut(1 = 用 Android 版 HistDeRedundance,即 mag160c_render.c: android_lut_rebuild(),是 Kotlin 侧 RenderPipeline.androidLutOf 的逐行转写,两边同一套 golden 向量;0 = 保持 PC 语义, 与 analysis/render_offline.c 的黄金夹具一致),两个 demo 默认打开(真机验证: 同场景照片逐像素平均差 4.5/255、逐计数中位差 2 级,见 §10/§11)。 ✅ 编译验证(2026-10-08):mag160c_render.c(含 android_lut_rebuild)与 mag160c_linux_demo.c 全链接通过(gcc 16.2.0 / WinLibs UCRT)。
    • 同一节还有个 upscale_nearest:官方安卓路径没有 160→320 的插值级 (SDK 给 App 的位图就是 160×120,App 用 new Paint(1)(无 FILTER_BITMAP)最近邻放大), 而 PC 版的双线性会展出一层"全幅/半幅"交错细纹(暗/低温区最明显)。 真机实测:冷区细尺度列条纹比 1.16/1.29(双线性)→ 1.00/0.93(最近邻)。
    • 38 轮补充(显示链两级结构,39 轮已修正结论):官方 ImageViewer 是两级 ——160×120 SDK 位图先用 PaintFlagsDrawFilter + FILTER_BITMAP 画进 640×480 离屏 (4× 双线性),SurfaceView 再放大到屏。38 轮曾据此把"背景条纹更不明显"归因于 显示链;39 轮真机 A/B 推翻:硬件/软件画布、单级/Skia 6× 预放大、App 内位图 dump vs 截屏逐环对照全部无差——显示链(含 SurfaceFlinger 合成与截屏)不添加 行交替;真因是我们的 App 开着 7×7 细节增强(enhanceLevel=4),官方 App 从不 调用 SetDetailEnhancement(jadx 全量无此符号)。关闭后暗区行交替 0.31-0.46 → 0.14-0.21(×1e-2 弧长)≈ 官方 0.15。Kotlin 侧维持 38 轮的 nativeOut 单级输出(160×120 → LiveRenderer 单次滤波放大),640×480 中间级 已于 38 轮内回退(竖屏 fitRect 二级 2.25× 非整数伪影)、39 轮的 720×960 预放大 试验也因无显示收益回退;C 参考路径(upscale2x/upscale_nearest)保持不变, 仅供黄金夹具与 PC 路径。
    • 顺带(同一条链):曲线插值温度要加 BasePara2.fpaTempFix。官方 App 传给 SDK 的是 帧尾 FPA + fpaTempFix(真机 1699),SelectCaliCurve/GenerateVolatileLens 用它选/插值每像素标定表;温度换算链路仍用帧尾原值。C 侧已实现 (配置项 fpa_temp_fix,在 rebuild_tables() 的调用处加到帧尾 FPA 上; 两个 demo 设 1699 = 本机 BasePara2 实测值)。
  2. 温度换算待移植:官方对这台设备走的是"放射学分支"(每波段线性映射 + 环境温度误差修正
    • 发射率/透过率 revise),不是本 SDK 现在用的 T2E 表分支。完整反编译结论、伪代码与 参数来源见 docs/android_app/temperature_chain_re_20261006.md(§4d 可直接落地, 已用 15 帧官方同帧数据验证到平均 13.9 mC)。C 侧还需要:
    • DDT 尾部辐射标定记录的解析(阈值/gain/offset/shift/±500/eps,偏移表见该文档 §4d);
    • BasePara2 的 ATZeroErrorPoint/ATErrorSlope/fpaTempFix(线上偏移同样见该文档);
    • 曲线 A 与本 SDK 的 T2E 表相同(646 项,已验证逐项一致)。 注:C 侧改动已随 2026-10-08 的编译验证一并覆盖(gcc 16.2.0 全部源文件 编译 + csdk 测试套件通过)。