- 逆向: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 规则
249 lines
8.9 KiB
C
249 lines
8.9 KiB
C
/* Real-time thermal viewer: Win32 window showing live 160x120 frames
|
|
with pseudo-color. Esc or close window to stop. */
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <libusb.h>
|
|
|
|
static libusb_device_handle *g_h;
|
|
static unsigned char g_frame[40000];
|
|
static CRITICAL_SECTION g_lock;
|
|
static volatile int g_new_frame;
|
|
static volatile int g_running;
|
|
static unsigned char g_bmp[54 + 160 * 120 * 3];
|
|
static char g_title[128];
|
|
|
|
static int sendcmd(libusb_device_handle *h, unsigned magic, unsigned param, int len) {
|
|
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;
|
|
int rc = libusb_bulk_transfer(h, 0x03, cmd, len, &xfer, 2000);
|
|
if (rc) return rc;
|
|
unsigned char resp[0x1000];
|
|
rc = libusb_bulk_transfer(h, 0x82, resp, sizeof(resp), &xfer, 2000);
|
|
return rc;
|
|
}
|
|
|
|
static void render(const unsigned char *data, unsigned mn, unsigned mx) {
|
|
unsigned char *px = g_bmp + 54;
|
|
for (int y = 0; y < 120; ++y) {
|
|
for (int x = 0; x < 160; ++x) {
|
|
int o = (y * 160 + x) * 2 + 28;
|
|
unsigned v = (unsigned)data[o] | ((unsigned)data[o+1] << 8);
|
|
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
|
|
if (idx > 255) idx = 255;
|
|
unsigned char r, g, b;
|
|
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 = (119 - y) * 160 * 3 + x * 3;
|
|
px[dst+0] = b; px[dst+1] = g; px[dst+2] = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD WINAPI reader_thread(LPVOID p) {
|
|
(void)p;
|
|
unsigned char hdr[64];
|
|
unsigned prev = 0xffffffff;
|
|
int frames = 0;
|
|
int ffc_done = 0;
|
|
while (g_running) {
|
|
int xfer = 0;
|
|
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 2000) || 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 == prev) continue;
|
|
prev = c;
|
|
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 2000) || xfer < 38400) continue;
|
|
frames++;
|
|
/* switch to type=0 after the 3rd complete frame (device is streaming) */
|
|
if (!ffc_done && frames == 3) {
|
|
sendcmd(g_h, 0x6bb6b672, 1, 8);
|
|
ffc_done = 1;
|
|
Sleep(400);
|
|
continue;
|
|
}
|
|
unsigned mn = 0xffff, mx = 0;
|
|
for (int k = 0; k < 38400; k += 2) {
|
|
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
|
|
if (v < mn) mn = v;
|
|
if (v > mx) mx = v;
|
|
}
|
|
EnterCriticalSection(&g_lock);
|
|
render(g_frame, mn, mx);
|
|
snprintf(g_title, sizeof(g_title), "MAG160C thermal live - frame %u type=%u range [%u..%u]",
|
|
c, (unsigned)hdr[12], mn, mx);
|
|
g_new_frame = 1;
|
|
LeaveCriticalSection(&g_lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC dc = BeginPaint(hw, &ps);
|
|
HDC mem = CreateCompatibleDC(dc);
|
|
HBITMAP bm = CreateCompatibleBitmap(dc, 160, 120);
|
|
HGDIOBJ old = SelectObject(mem, bm);
|
|
SetDIBitsToDevice(mem, 0, 0, 160, 120, 0, 0, 0, 120, g_bmp + 54,
|
|
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
|
StretchBlt(dc, 0, 0, 640, 480, mem, 0, 0, 160, 120, SRCCOPY);
|
|
SelectObject(mem, old);
|
|
DeleteObject(bm);
|
|
DeleteDC(mem);
|
|
EndPaint(hw, &ps);
|
|
break;
|
|
}
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_KEYDOWN:
|
|
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
|
|
break;
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
return DefWindowProc(hw, msg, wp, lp);
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
|
|
(void)prev; (void)cmd; (void)show;
|
|
libusb_context *ctx = NULL;
|
|
libusb_init(&ctx);
|
|
libusb_device **list = NULL;
|
|
ssize_t cnt = libusb_get_device_list(ctx, &list);
|
|
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
|
struct libusb_device_descriptor d;
|
|
libusb_get_device_descriptor(list[i], &d);
|
|
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
|
}
|
|
if (!g_h) {
|
|
MessageBoxA(NULL, "no MAG160C device found", "MAG160C", MB_ICONERROR);
|
|
return 1;
|
|
}
|
|
libusb_set_configuration(g_h, 2);
|
|
libusb_set_configuration(g_h, 1);
|
|
libusb_claim_interface(g_h, 0);
|
|
|
|
sendcmd(g_h, 0x6bb6b66b, 0, 4);
|
|
sendcmd(g_h, 0x6bb6b66c, 0, 4);
|
|
sendcmd(g_h, 0x6bb6b66f, 0, 4);
|
|
sendcmd(g_h, 0x6bb6b672, 0, 8);
|
|
sendcmd(g_h, 0x6bb6b672, 0, 8);
|
|
Sleep(300);
|
|
sendcmd(g_h, 0x6bb6b673, 0, 4);
|
|
Sleep(500);
|
|
/* FFC(1) is issued from the reader thread after the 3rd frame */
|
|
|
|
WNDCLASSA wc = {0};
|
|
wc.lpfnWndProc = wndproc;
|
|
wc.hInstance = inst;
|
|
wc.lpszClassName = "ThermalView";
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
RegisterClassA(&wc);
|
|
HWND hw = CreateWindowA("ThermalView", "MAG160C thermal live",
|
|
WS_OVERLAPPEDWINDOW, 100, 100, 656, 520,
|
|
NULL, NULL, inst, NULL);
|
|
|
|
/* BMP header */
|
|
unsigned sz = 54 + 160*120*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]=160; g_bmp[19]=0; g_bmp[22]=120; g_bmp[23]=0;
|
|
g_bmp[26]=1; g_bmp[28]=24;
|
|
|
|
InitializeCriticalSection(&g_lock);
|
|
g_running = 1;
|
|
ShowWindow(hw, SW_SHOW);
|
|
|
|
/* single-threaded: read one frame, render, pump messages */
|
|
int nread = 0;
|
|
unsigned prev2 = 0xffffffff;
|
|
int ffc2 = 0;
|
|
for (;;) {
|
|
MSG msg;
|
|
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_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) continue;
|
|
if (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 == prev2) continue;
|
|
prev2 = c;
|
|
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue;
|
|
nread++;
|
|
if (!ffc2 && nread == 3) {
|
|
sendcmd(g_h, 0x6bb6b672, 1, 8);
|
|
ffc2 = 1;
|
|
Sleep(400);
|
|
continue;
|
|
}
|
|
unsigned hist[65536] = {0};
|
|
unsigned nz = 0;
|
|
for (int k = 0; k < 38400; k += 2) {
|
|
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
|
|
if (v == 0) continue;
|
|
hist[v]++;
|
|
nz++;
|
|
}
|
|
unsigned mn = 0, mx = 0;
|
|
unsigned 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; }
|
|
EnterCriticalSection(&g_lock);
|
|
render(g_frame, mn, mx);
|
|
snprintf(g_title, sizeof(g_title),
|
|
"MAG160C thermal live - frame %u type=%u range [%u..%u]",
|
|
c, (unsigned)hdr[12], mn, mx);
|
|
LeaveCriticalSection(&g_lock);
|
|
SetWindowTextA(hw, g_title);
|
|
InvalidateRect(hw, NULL, FALSE);
|
|
UpdateWindow(hw);
|
|
}
|
|
}
|
|
done:;
|
|
g_running = 0;
|
|
|
|
g_running = 0;
|
|
Sleep(300);
|
|
libusb_clear_halt(g_h, 0x03);
|
|
libusb_clear_halt(g_h, 0x82);
|
|
sendcmd(g_h, 0x6bb6b674, 0, 4);
|
|
libusb_close(g_h);
|
|
libusb_exit(ctx);
|
|
return 0;
|
|
}
|