Files
MAG160C/csdk/tools/mag160c_linux_demo.c
ZXCLI 0dcb2830c6 修复调色板 BGR/RGB 顺序:render 输出标准 RGB24(官方 palette 为 BGR 存储)
- mag160c_render_frame 之前把 palette[gv][0]=B 直接写入 out_rgb[0],
  导致 R/B 互换,高温区域显示为蓝色
- 改为 out_rgb = {palette[2], palette[1], palette[0]} 标准 RGB24
- linux_demo 的 BMP 输出相应改为写回 BGR(BMP 原生顺序)
- 实测:最热像素 (255,255,150) 亮黄白,低温区域蓝黑,色温方向正确
2026-08-19 15:46:21 +08:00

223 lines
8.2 KiB
C

/* mag160c_linux_demo.c - Linux-oriented reference app.
*
* Uses the platform-independent mag160c_render API. On Windows this
* builds/runs too (no GDI): output is written to thermal.bmp every 30
* frames. On Linux the same code path is used; only the display
* backend differs (fbdev/DRM/LVGL - see DISPLAY_BACKEND below).
*
* Build (Windows, same toolchain as demo3):
* gcc -O2 -w -DMAG160C_STATIC -I"csdk\third_party\libusb\win64"
* -I"csdk\include" -I"csdk\src" -o build-artifacts\mag160c_linux_demo.exe
* 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
* csdk\third_party\libusb\win64\libusb-1.0.x64.a
* Build (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
*/
#define _WIN32_WINNT 0x0601
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifdef _WIN32
#include <windows.h>
#define SLEEP_MS(ms) Sleep(ms)
#else
#include <unistd.h>
#include <pthread.h>
#define SLEEP_MS(ms) usleep((ms) * 1000)
#endif
#include <libusb.h>
#include "mag160c/mag160c.h"
#include "mag160c/mag160c_render.h"
#include "mag160c/mag160c_display.h"
#define W 160
#define H 120
#define OUT_W 320
#define OUT_H 240
static libusb_context *g_ctx;
static libusb_device_handle *g_h;
static int sendcmd(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;
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
unsigned char resp[0x1000];
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
return 0;
}
static void ffc_cb(void *user, int param) {
(void)user;
if (g_h) sendcmd(0x6bb6b672, (unsigned)param, 8);
}
/* ---- display backend abstraction ----
* Windows: no-op (terminal stats). Linux: implement one of:
* A. fbdev: open("/dev/fb0"), mmap, memcpy RGB (convert to RGB565)
* B. DRM/KMS: drmModeSetCrtc + dumb buffer
* C. LVGL: lv_img_set_src + lv_obj_invalidate (best for UI/animation)
*/
static void display_rgb(const uint8_t *rgb, int w, int h) {
(void)rgb; (void)w; (void)h;
/* Linux example (fbdev RGB565):
* static int fd = -1; static unsigned char *fb; static int fbw, fbh;
* ... open/mmap once, then:
* for (y) for (x) { u16 v = ((rgb[y*w*3+x*3]>>3)<<11) |
* ((rgb[y*w*3+x*3+1]>>2)<<5) |
* (rgb[y*w*3+x*3+2]>>3);
* *(u16*)(fb + y*fbw*2 + x*2) = v; }
*/
}
static void save_bmp(const char *path, const uint8_t *rgb, int w, int h) {
uint32_t sz = 54 + (uint32_t)w * h * 3;
uint8_t bmp[54];
memset(bmp, 0, 54);
bmp[0] = 'B'; bmp[1] = 'M';
bmp[2] = (uint8_t)sz; bmp[3] = (uint8_t)(sz >> 8);
bmp[4] = (uint8_t)(sz >> 16); bmp[5] = (uint8_t)(sz >> 24);
bmp[10] = 54; bmp[14] = 40;
bmp[18] = (uint8_t)w; bmp[19] = (uint8_t)(w >> 8);
bmp[20] = (uint8_t)(w >> 16); bmp[21] = (uint8_t)(w >> 24);
bmp[22] = (uint8_t)h; bmp[23] = (uint8_t)(h >> 8);
bmp[24] = (uint8_t)(h >> 16); bmp[25] = (uint8_t)(h >> 24);
bmp[26] = 1; bmp[28] = 24;
FILE *f = fopen(path, "wb");
if (!f) return;
fwrite(bmp, 1, 54, f);
for (int y = h - 1; y >= 0; --y) {
const uint8_t *row = rgb + (size_t)y * w * 3;
for (int x = 0; x < w; ++x) {
uint8_t bgr[3] = { row[x * 3 + 2], row[x * 3 + 1], row[x * 3] };
fwrite(bgr, 1, 3, f);
}
}
fclose(f);
}
int main(int argc, char **argv) {
const char *ddt = argc > 1 ? argv[1] : "mag160c_official.ddt";
libusb_init(&g_ctx);
mag160c_render_cfg_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.width = W;
cfg.height = H;
cfg.ddt_path = ddt;
cfg.ffc_period = 1800;
cfg.ffc_drift = 250;
cfg.startup_force_ffc = 1;
cfg.warm_frames = 20;
cfg.cdf_pivot_75 = 1;
cfg.ffc_cb = ffc_cb;
mag160c_render_t *render = NULL;
if (mag160c_render_init(&render, &cfg) != MAG160C_OK) {
fprintf(stderr, "render init failed (need %s)\n", ddt);
return 1;
}
int ok = 0;
for (int attempt = 0; attempt < 4 && !ok; ++attempt) {
if (attempt > 0) {
if (g_h) libusb_reset_device(g_h);
if (g_h) { libusb_close(g_h); g_h = NULL; }
SLEEP_MS(2500);
}
libusb_device **list = NULL;
ssize_t cnt = libusb_get_device_list(g_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);
}
libusb_free_device_list(list, 1);
if (!g_h) continue;
libusb_set_configuration(g_h, 2);
libusb_set_configuration(g_h, 1);
if (libusb_claim_interface(g_h, 0) != 0) { libusb_close(g_h); g_h = NULL; continue; }
if (sendcmd(0x6bb6b66b, 0, 4)) continue;
if (sendcmd(0x6bb6b66c, 0, 4)) continue;
if (sendcmd(0x6bb6b66f, 0, 4)) continue;
if (sendcmd(0x6bb6b672, 0, 8)) continue;
SLEEP_MS(100);
if (sendcmd(0x6bb6b672, 0, 8)) continue;
SLEEP_MS(300);
if (sendcmd(0x6bb6b673, 0, 4)) continue;
SLEEP_MS(700);
ok = 1;
}
if (!ok) {
fprintf(stderr, "camera init failed\n");
mag160c_render_destroy(render);
libusb_exit(g_ctx);
return 1;
}
static uint8_t rgb[OUT_W * OUT_H * 3];
static uint8_t frame[40000];
unsigned prev = 0xffffffff;
unsigned long nframe = 0;
for (;;) {
uint8_t hdr[64];
int xfer = 0;
if (libusb_bulk_transfer(g_h, 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 == prev) continue;
prev = c;
if (libusb_bulk_transfer(g_h, 0x81, frame, sizeof(frame), &xfer, 500) || xfer < 38400)
continue;
nframe++;
/* assemble the full frame (28B header + pixels + tail) for the
* renderer: the second bulk read carries 38400 px + 28B tail */
static uint8_t full[28 + 40000];
memcpy(full, hdr, 28);
memcpy(full + 28, frame, 38400);
if (xfer > 38400)
memcpy(full + 28 + 38400, frame + 38400, (size_t)(xfer - 38400));
/* feed the platform-independent pipeline */
mag160c_error_t rc = mag160c_render_frame(render, full, 1, rgb);
if (rc != MAG160C_OK) continue; /* FFC window / startup: frozen */
display_rgb(rgb, OUT_W, OUT_H);
if (nframe % 30 == 0) {
char path[64];
snprintf(path, sizeof(path), "thermal_%05lu.bmp", nframe);
save_bmp(path, rgb, OUT_W, OUT_H);
int lo, hi, fmin, fmax, mean, std;
mag160c_render_get_stats(render, &lo, &hi, &fmin, &fmax, &mean, &std);
printf("f=%lu shutter=%d sel=%d win=[%d,%d] stats=(%d..%d m%d s%d) ref=%d\n",
nframe, mag160c_render_get_shutter(render),
mag160c_render_get_endpoint(render), lo, hi,
fmin, fmax, mean, std, mag160c_render_has_reference(render));
fflush(stdout);
}
}
mag160c_render_destroy(render);
libusb_close(g_h);
libusb_exit(g_ctx);
return 0;
}