Files
ZXCLI 0bfb926892 完成官方管线全量逆向与 demo3 v5 复刻,清理仓库
- 逆向:Ghidra/IDA 全量反编译 CoreSDKLib.dll/ThermalSDK.dll/libthermalSDK.so/
  libcoresdk.so(ARM64)/libmagcore.so,导出 analysis/ida/export/
- 解码官方渲染管线:DDT 校准表加载->快门端点选择->Q12 插值->ref(4x type1 帧
  均值)->NUC 查表->盲元补偿->窗口->LUT1024 重建->2x 升采样->调色板
- 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
- demo3 v5:完整复刻官方管线(含 DDT 解析、FFC 状态机、快门温度驱动),
  修复 load_ddt 表错位导致的零像素问题
- 鬼影根因分析写入 analysis/reverse_20260813_full.md
- 心跳/恢复机制:analysis/session_state.md + tools/resume_rev.ps1
- 新增 tsdk_pair3 增强采集工具;历史工具归档 csdk/tools/legacy/;
  根目录抓帧残留删除,历史文档归档 analysis/history/
- csdk/README.md 完整使用文档;.gitignore/.gitattributes 补 LFS 规则
2026-08-13 23:16:12 +08:00

493 lines
20 KiB
C

/* MAG160C Windows Demo - live thermal imaging viewer.
*
* Features:
* - real-time pseudo-color thermal image (160x120 upscaled to 640x480)
* - estimated temperature readout (calibrated to a start-up background
* reference; ~147 counts/C measured on the reference unit)
* - mouse probe temperature, max-temp hotspot marker, center temp
* - manual FFC trigger, frame-difference display mode, BMP snapshot
*
* Build (MinGW):
* gcc -std=c11 -mwindows -I<libusb-include> mag160c_demo.c <libusb-1.0.x64.a> \
* -o mag160c_demo.exe -lgdi32 -luser32 -lpthread
* copy libusb-1.0.dll next to the exe.
*/
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <libusb.h>
static FILE *g_log;
/* ---- protocol (verified on hardware) ------------------------------------ */
#define MAG_CMD_PREPARE1 0x6bb6b66b
#define MAG_CMD_PREPARE2 0x6bb6b66c
#define MAG_CMD_GET_INFO 0x6bb6b66f
#define MAG_CMD_FFC 0x6bb6b672
#define MAG_CMD_START 0x6bb6b673
#define MAG_CMD_STOP 0x6bb6b674
#define IR_W 160
#define IR_H 120
#define FRAME_LEN (IR_W * IR_H * 2)
/* ---- globals ------------------------------------------------------------ */
static libusb_context *g_usb;
static libusb_device_handle *g_dev;
static unsigned char g_frame[FRAME_LEN];
static unsigned char g_bmp[54 + IR_W * IR_H * 3];
static char g_status[512];
static volatile int g_new_frame;
static volatile unsigned g_frame_count;
static volatile unsigned g_frame_type;
static int g_diff_mode = 1; /* diff mode by default (best hand contrast) */
static unsigned short g_reference[IR_W * IR_H];
static int g_has_reference;
static int g_running;
static double g_fps;
/* temperature estimate: counts -> deg C, linear about the startup reference */
static double g_counts_per_c = 147.0; /* measured: hand vs background 1322 cts / ~9 C */
static int g_probe_x = -1, g_probe_y = -1;
static int g_max_x = -1, g_max_y = -1;
static double g_max_temp = -100.0;
static double g_center_temp = -100.0;
/* ---- helpers ------------------------------------------------------------ */
static int sendcmd(unsigned magic, unsigned param, int len) {
if (g_log) { fprintf(g_log, "CMD %08x len=%d param=%u\n", magic, len, param); fflush(g_log); }
unsigned char cmd[8] = {0};
cmd[0] = (unsigned char)(magic);
cmd[1] = (unsigned char)(magic >> 8);
cmd[2] = (unsigned char)(magic >> 16);
cmd[3] = (unsigned char)(magic >> 24);
if (len >= 8) {
cmd[4] = (unsigned char)(param);
cmd[5] = (unsigned char)(param >> 8);
cmd[6] = (unsigned char)(param >> 16);
cmd[7] = (unsigned char)(param >> 24);
}
int xfer = 0;
if (libusb_bulk_transfer(g_dev, 0x03, cmd, len, &xfer, 2000)) return -1;
unsigned char resp[0x1000];
if (libusb_bulk_transfer(g_dev, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
return 0;
}
static double counts_to_c(unsigned v) {
return 25.0 + (v - (int)g_reference[g_probe_x >= 0 ? (g_probe_y * IR_W + g_probe_x) : 0]) / g_counts_per_c;
}
static void render(const unsigned char *frame) {
unsigned hist[65536] = {0};
unsigned nz = 0;
for (int i = 0; i < FRAME_LEN; i += 2) {
unsigned v = (unsigned)frame[i] | ((unsigned)frame[i + 1] << 8);
if (v == 0) continue;
hist[v]++;
nz++;
}
unsigned mn = 0, mx = 0, acc = 0;
unsigned lo = (unsigned)(nz * 2 / 100);
unsigned hi = (unsigned)(nz * 98 / 100);
for (unsigned k = 0; k < 65536; ++k) {
acc += hist[k];
if (acc >= lo && mn == 0) mn = k;
if (acc >= hi && mx == 0) { mx = k; break; }
}
if (mx <= mn + 50) { mn = 0; mx = 65535; }
int hot = 0;
unsigned hotv = 0;
unsigned center_sum = 0;
unsigned char *px = g_bmp + 54;
for (int y = 0; y < IR_H; ++y) {
for (int x = 0; x < IR_W; ++x) {
int o = (y * IR_W + x) * 2;
unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8);
int diffv = 0;
if (g_diff_mode && g_has_reference) {
diffv = (int)v - (int)g_reference[y * IR_W + x];
}
if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v;
if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; }
unsigned char r, g, b;
if (g_diff_mode && g_has_reference) {
/* neutral gray background, hand = bright red/orange, cold = blue
fixed span 2000: hand occlusion measures ~+1200 counts */
int span = 2000;
if (diffv > 0) {
unsigned idx = (unsigned)(diffv * 255 / span);
if (idx > 255) idx = 255;
r = (unsigned char)(128 + idx / 2);
g = (unsigned char)(128 - idx / 2);
b = (unsigned char)(128 - idx / 2);
} else {
unsigned idx = (unsigned)(-diffv * 255 / span);
if (idx > 255) idx = 255;
r = (unsigned char)(128 - idx / 2);
g = (unsigned char)(128 - idx / 2);
b = (unsigned char)(128 + idx / 2);
}
} else {
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
if (idx > 255) idx = 255;
if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; }
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); }
else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; }
else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; }
}
int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3;
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
}
}
if (hot >= 0) {
g_max_x = hot % IR_W;
g_max_y = hot / IR_W;
g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / g_counts_per_c;
/* draw a marker: white cross on the hottest pixel */
int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y;
for (int k = -2; k <= 2; ++k) {
if (mx2 + k >= 0 && mx2 + k < IR_W) {
int d2 = my2 * IR_W * 3 + (mx2 + k) * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
if (my2 + k >= 0 && my2 + k < IR_H) {
int d2 = (my2 + k) * IR_W * 3 + mx2 * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
}
}
g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / g_counts_per_c;
}
/* ---- window ------------------------------------------------------------- */
static HWND g_hwnd;
static HFONT g_font;
static void set_status(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(g_status, sizeof(g_status), fmt, ap);
va_end(ap);
InvalidateRect(g_hwnd, NULL, TRUE);
}
static void save_bmp(void) {
char path[MAX_PATH];
SYSTEMTIME st;
GetLocalTime(&st);
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
FILE *f = fopen(path, "wb");
if (f) {
fwrite(g_bmp, 1, sizeof(g_bmp), f);
fclose(f);
set_status("saved %s", path);
}
}
static void do_ffc(void) {
sendcmd(MAG_CMD_FFC, 1, 8);
Sleep(300);
sendcmd(MAG_CMD_FFC, 0, 8);
set_status("FFC triggered");
}
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC dc = BeginPaint(hw, &ps);
/* image */
HDC mem = CreateCompatibleDC(dc);
HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H);
HGDIOBJ old = SelectObject(mem, bm);
SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54,
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY);
SelectObject(mem, old);
DeleteObject(bm);
DeleteDC(mem);
/* text panel */
SelectObject(dc, g_font);
SetBkMode(dc, TRANSPARENT);
SetTextColor(dc, RGB(220, 220, 220));
int y = 20;
char line[256];
snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
if (g_probe_x >= 0) {
double t = counts_to_c((unsigned)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] |
(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)));
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
if (g_max_x >= 0) {
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF (reference)" : "absolute");
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "scale : %.0f counts/C", g_counts_per_c);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
SetTextColor(dc, RGB(120, 220, 120));
TextOutA(dc, 10, 500, g_status, (int)strlen(g_status));
EndPaint(hw, &ps);
break;
}
case WM_LBUTTONDOWN: {
int x = LOWORD(lp), y = HIWORD(lp);
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
g_probe_x = (x - 10) * IR_W / 640;
g_probe_y = (y - 10) * IR_H / 480;
g_probe_y = IR_H - 1 - g_probe_y;
InvalidateRect(hw, NULL, TRUE);
}
break;
}
case WM_COMMAND:
switch (LOWORD(wp)) {
case 1001: do_ffc(); break; /* FFC */
case 1002: save_bmp(); break; /* save */
case 1003: g_diff_mode = !g_diff_mode; break; /* diff mode */
case 1004: { /* re-reference */
for (int i = 0; i < FRAME_LEN; i += 2)
g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
g_has_reference = 1;
g_diff_mode = 1;
set_status("reference captured (press FFC then re-capture for best result)");
break;
}
case 1005: { /* reset probe */
g_probe_x = -1;
InvalidateRect(hw, NULL, TRUE);
break;
}
}
break;
case WM_ERASEBKGND:
return 1;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hw, msg, wp, lp);
}
static int open_camera(void) {
libusb_device **list = NULL;
ssize_t cnt = libusb_get_device_list(g_usb, &list);
for (ssize_t i = 0; i < cnt && !g_dev; ++i) {
struct libusb_device_descriptor d;
libusb_get_device_descriptor(list[i], &d);
if (d.idVendor == 0x833c) libusb_open(list[i], &g_dev);
}
libusb_free_device_list(list, 1);
if (!g_dev) return -1;
libusb_set_configuration(g_dev, 2);
libusb_set_configuration(g_dev, 1);
return libusb_claim_interface(g_dev, 0);
}
static int init_camera(void) {
for (int attempt = 0; attempt < 4; ++attempt) {
if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); }
{
/* hard reset before every attempt: the unit needs a fresh
session or the FFC/type switch stalls */
if (g_dev) libusb_reset_device(g_dev);
if (g_dev) { libusb_close(g_dev); g_dev = NULL; }
Sleep(2500);
}
if (open_camera() != 0) continue;
if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) continue;
if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) continue;
if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) continue;
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
Sleep(100);
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
Sleep(300);
if (sendcmd(MAG_CMD_START, 0, 4)) continue;
Sleep(700);
unsigned char probe[64];
int xfer = 0, got = 0;
for (int k = 0; k < 10; ++k) {
if (libusb_bulk_transfer(g_dev, 0x81, probe, sizeof(probe), &xfer, 300) == 0 && xfer >= 28) {
unsigned m = (unsigned)probe[0] | ((unsigned)probe[1] << 8) |
((unsigned)probe[2] << 16) | ((unsigned)probe[3] << 24);
if (m == 0x1bb1b11b) { got = 1; break; }
}
}
if (got) {
if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); }
return 0;
}
if (g_log) { fprintf(g_log, "init no frames, retry\n"); fflush(g_log); }
libusb_close(g_dev);
g_dev = NULL;
}
return -1;
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) {
(void)hprev; (void)cmd; (void)show;
g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_log.txt", "w");
if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); }
libusb_init(&g_usb);
if (init_camera() != 0) {
if (g_log) { fprintf(g_log, "init FAILED\n"); fflush(g_log); }
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
return 1;
}
WNDCLASSA wc = {0};
wc.lpfnWndProc = wndproc;
wc.hInstance = inst;
wc.lpszClassName = "Mag160cDemo";
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
RegisterClassA(&wc);
g_hwnd = CreateWindowA("Mag160cDemo", "MAG160C Thermal Demo",
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600,
NULL, NULL, inst, NULL);
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
/* bmp header */
unsigned sz = 54 + IR_W * IR_H * 3;
g_bmp[0] = 'B'; g_bmp[1] = 'M';
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
g_bmp[10] = 54; g_bmp[14] = 40;
g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0;
g_bmp[26] = 1; g_bmp[28] = 24;
g_running = 1;
ShowWindow(g_hwnd, SW_SHOW);
set_status("starting...");
/* capture startup background reference */
unsigned prevcnt = 0xffffffff;
int frames = 0, ffc_done = 0;
unsigned long long ref_sum[IR_W * IR_H] = {0};
int ref_n = 0;
DWORD t0 = GetTickCount();
DWORD last_fps_t = t0;
unsigned last_fps_cnt = 0;
MSG msg;
for (;;) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) goto done;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
unsigned char hdr[64];
int xfer = 0;
if (libusb_bulk_transfer(g_dev, 0x81, hdr, sizeof(hdr), &xfer, 500) || xfer < 28) continue;
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
if (m != 0x1bb1b11b) continue;
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
if (c == prevcnt) continue;
prevcnt = c;
if (libusb_bulk_transfer(g_dev, 0x81, g_frame, sizeof(g_frame), &xfer, 500) || xfer < FRAME_LEN) continue;
frames++;
if (g_log && (frames % 100) == 0) { fprintf(g_log, "frames=%d cnt=%u\n", frames, c); fflush(g_log); }
g_frame_count++;
g_frame_type = (unsigned)hdr[12];
/* accumulate reference over the first 30 frames */
/* wait for the stream to stabilize before capturing the reference */
if (!g_has_reference && frames >= 150 && ref_n < 30) {
for (int i = 0; i < FRAME_LEN; i += 2)
ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
ref_n++;
if (ref_n == 30) {
unsigned long long rs = 0;
for (int i = 0; i < IR_W * IR_H; ++i) {
g_reference[i] = (unsigned short)(ref_sum[i] / 30);
rs += g_reference[i];
}
g_has_reference = 1;
if (g_log) { fprintf(g_log, "reference captured: avg=%llu\n", rs / 19200); fflush(g_log); }
set_status("reference captured (frame 150+) - hand shows red");
}
} else if (g_has_reference) {
/* slow background tracking: reference follows slow drift so that a
hand (fast change) stays visible; 0.5%/frame */
for (int i = 0; i < FRAME_LEN; i += 2) {
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
unsigned r = g_reference[i / 2];
g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200);
}
}
if (!ffc_done && frames == 3) {
sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature);
same as verified thermal_viewer.c */
ffc_done = 1;
Sleep(400);
continue;
}
if (g_log && (g_frame_count % 50) == 0) {
unsigned long long sv = 0, sd = 0;
int dmax = 0, dmin = 0;
for (int i = 0; i < FRAME_LEN; i += 2) {
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
sv += v;
int d = (int)v - (int)g_reference[i / 2];
sd += (unsigned long long)(d < 0 ? -d : d);
if (d > dmax) dmax = d;
if (d < dmin) dmin = d;
}
fprintf(g_log, "frame %u: type=%u avg=%llu ref=%u dmean=%llu dmin=%d dmax=%d\n",
g_frame_count, g_frame_type, sv / 19200, (unsigned)g_reference[0],
sd / 19200, dmin, dmax);
fflush(g_log);
}
render(g_frame);
if ((g_frame_count % 200) == 0) {
FILE *tf = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/render_test.bmp", "wb");
if (tf) { fwrite(g_bmp, 1, sizeof(g_bmp), tf); fclose(tf); }
}
DWORD now = GetTickCount();
if (now - last_fps_t >= 1000) {
g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t);
last_fps_t = now;
last_fps_cnt = g_frame_count;
}
InvalidateRect(g_hwnd, NULL, FALSE);
UpdateWindow(g_hwnd);
}
done:
g_running = 0;
libusb_clear_halt(g_dev, 0x03);
libusb_clear_halt(g_dev, 0x82);
Sleep(100);
sendcmd(MAG_CMD_STOP, 0, 4);
libusb_close(g_dev);
libusb_exit(g_usb);
return 0;
}