新增平台无关渲染模块 mag160c_render + Linux 参考程序 + 移植评估文档
- csdk/include/mag160c/mag160c_render.h + csdk/src/mag160c_render.c: 官方渲染管线(快门提取/FFC 状态机/ref 采集/表重建/NUC/盲元/窗口/ LUT(0x8c)/gray/2x/调色板)封装为无平台依赖 API,FFC 命令经回调发出 - csdk/tools/mag160c_linux_demo.c:Linux 风格参考程序(libusb + render API + 显示后端抽象 fbdev/DRM/LVGL),Windows 可编译验证(BMP 输出) - 修复帧组装:第二段 bulk 读 38428B(像素+尾),尾部含快门字 实测 shutter=31492 sel=2 stats 正常 - docs/linux_port_plan.md:芯片选型(推荐 T113-S3 QFP128 非 BGA ~20元)、 动画库(LVGL+rlottie 参考 LiThermal)、显示方案、接口抽象设计 - csdk/README 增加 Linux 移植章节
This commit is contained in:
+34
-1
@@ -207,7 +207,40 @@ Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDir
|
||||
- 输出日志:`demo3_diag.txt`(每帧统计)、`demo3_ffc.txt`(FFC 事件)、
|
||||
`demo3_auto_*.bmp`(每 30 帧快照,320×240)
|
||||
|
||||
### 5. 官方抓帧工具(tsdk_pair2 / tsdk_pair3)
|
||||
### 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 传输)。
|
||||
|
||||
```c
|
||||
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,
|
||||
.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 构建:
|
||||
|
||||
```bash
|
||||
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,再运行(同一设备独占):
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* mag160c_render.h - platform-independent official rendering pipeline.
|
||||
*
|
||||
* Mirrors the official CoreSDKLib.dll frame path (verified pixel-exact):
|
||||
* raw frame -> shutter extract -> FFC state machine -> endpoint select
|
||||
* + Q12 table interp -> ref (4x type-1 mean) -> NUC lookup -> blind
|
||||
* compensation -> stats/window -> LUT1024 rebuild -> gray -> 2x
|
||||
* upscale -> palette -> RGB24 output.
|
||||
*
|
||||
* No OS/display dependencies: feed it USB frames, get 320x240 RGB24.
|
||||
* FFC commands are emitted through a callback so the caller owns the
|
||||
* transport (libusb on Linux, bulk transfers on Windows, ...).
|
||||
*/
|
||||
#ifndef MAG160C_RENDER_H
|
||||
#define MAG160C_RENDER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "mag160c.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Default output geometry (official: 2x of the 160x120 sensor). */
|
||||
#define MAG160C_RENDER_OUT_W 320
|
||||
#define MAG160C_RENDER_OUT_H 240
|
||||
|
||||
/* FFC command callback: param 0 = FFC(0), 1 = FFC(1). */
|
||||
typedef void (*mag160c_render_ffc_cb_t)(void *user, int param);
|
||||
|
||||
typedef struct mag160c_render_t mag160c_render_t;
|
||||
|
||||
typedef struct mag160c_render_cfg_t {
|
||||
uint16_t width; /* sensor width, 160 */
|
||||
uint16_t height; /* sensor height, 120 */
|
||||
const char *ddt_path; /* official DDT calibration file */
|
||||
int ffc_period; /* frames between FFC pairs, official 1800 */
|
||||
int ffc_drift; /* shutter drift threshold, official 250 */
|
||||
int startup_force_ffc; /* official frame-75 forced FFC, 1 = on */
|
||||
int warm_frames; /* frames dropped after start, official 20 */
|
||||
int cdf_pivot_75; /* 0x8c: CDF pivot at 75% cumulative, 1 = on */
|
||||
mag160c_render_ffc_cb_t ffc_cb;
|
||||
void *ffc_user;
|
||||
} mag160c_render_cfg_t;
|
||||
|
||||
MAG160C_API mag160c_error_t
|
||||
mag160c_render_init(mag160c_render_t **out, const mag160c_render_cfg_t *cfg);
|
||||
|
||||
/* Process one raw frame.
|
||||
* frame : USB frame buffer. If has_hdr, the 28-byte header is at
|
||||
* frame[0] (marker 0x1bb1b11b, counter, len, type, ...) and
|
||||
* the shutter word is extracted from the tail; otherwise
|
||||
* frame is the bare 38400-byte pixel buffer and shutter
|
||||
* must be supplied via mag160c_render_set_shutter().
|
||||
* out_rgb : 320x240x3 RGB24 (palette-colored).
|
||||
* Returns MAG160C_OK when out_rgb was written, MAG160C_ERR_NOT_READY
|
||||
* while the startup/FFC window is running (image frozen by caller). */
|
||||
MAG160C_API mag160c_error_t
|
||||
mag160c_render_frame(mag160c_render_t *r, const uint8_t *frame,
|
||||
int has_hdr, uint8_t *out_rgb);
|
||||
|
||||
/* Bare-pixel mode: set the current shutter (device temperature proxy)
|
||||
* before calling mag160c_render_frame(). */
|
||||
MAG160C_API void mag160c_render_set_shutter(mag160c_render_t *r, int shutter);
|
||||
|
||||
/* Per-frame diagnostics for UI overlays. */
|
||||
MAG160C_API void mag160c_render_get_stats(mag160c_render_t *r,
|
||||
int *win_lo, int *win_hi,
|
||||
int *fmin, int *fmax,
|
||||
int *mean, int *stddev);
|
||||
MAG160C_API int mag160c_render_get_shutter(mag160c_render_t *r);
|
||||
MAG160C_API int mag160c_render_get_endpoint(mag160c_render_t *r);
|
||||
MAG160C_API int mag160c_render_get_frame_index(mag160c_render_t *r);
|
||||
MAG160C_API int mag160c_render_has_reference(mag160c_render_t *r);
|
||||
|
||||
/* Temperature probe on the current NUC output (millidegrees C). */
|
||||
MAG160C_API mag160c_error_t
|
||||
mag160c_render_probe_temp(mag160c_render_t *r, uint32_t x, uint32_t y,
|
||||
int32_t *out_temp_mc);
|
||||
|
||||
MAG160C_API void mag160c_render_destroy(mag160c_render_t *r);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MAG160C_RENDER_H */
|
||||
@@ -0,0 +1,665 @@
|
||||
/* mag160c_render.c - official pipeline, platform-independent.
|
||||
* Migrated from csdk/tools/mag160c_demo3.c (pixel-verified vs
|
||||
* CoreSDKLib.dll). Feed it USB frames; get 320x240 RGB24. */
|
||||
#include "mag160c/mag160c_render.h"
|
||||
#include "mag160c_official_palette256.h"
|
||||
#include "mag160c_official_t2e.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#define MAG_T2E_OFFSET 0x249f0
|
||||
#define TEMP_C 5797
|
||||
#define OFF_N0 4
|
||||
#define OFF_N1 5
|
||||
#define OFF_N2 4
|
||||
|
||||
struct mag160c_render_t {
|
||||
uint16_t w, h;
|
||||
int npix;
|
||||
|
||||
/* DDT state */
|
||||
int ep_count, nsegs, c5c, c60;
|
||||
int dev24, dev4c_shift, dev48, dev8c, dev47950, dev47948;
|
||||
int shift;
|
||||
int ep_temps[64];
|
||||
int blind_cnt[64];
|
||||
short *ep_thr[64];
|
||||
unsigned short *ep_gain[64];
|
||||
unsigned int *ep_blind[64];
|
||||
int sel;
|
||||
short *thr_work;
|
||||
unsigned short *gain_work;
|
||||
unsigned int *blind_work;
|
||||
int blind_count;
|
||||
|
||||
/* frame/FFC state */
|
||||
int ffc_idx, global_frames, shutter, shutter_at_ffc;
|
||||
int has_ref, ref_cnt, startup_ffc_done, ref_reset;
|
||||
unsigned short *ref;
|
||||
unsigned int *ref_acc;
|
||||
unsigned short *nuc;
|
||||
unsigned char *gray160, *gray320;
|
||||
|
||||
/* render state */
|
||||
unsigned int hist1024[1024];
|
||||
unsigned int cdf[1024];
|
||||
unsigned char lut[1024];
|
||||
int win_lo, win_hi;
|
||||
int stat_min, stat_max, stat_mean, stat_std;
|
||||
int center_prev, mean_prev;
|
||||
|
||||
/* config */
|
||||
mag160c_render_ffc_cb_t ffc_cb;
|
||||
void *ffc_user;
|
||||
int ffc_period, ffc_drift, warm_frames, cdf_pivot_75, force75;
|
||||
};
|
||||
|
||||
static int counts_to_temp_mc(int counts) {
|
||||
int64_t x = (int64_t)counts * 3 - TEMP_C;
|
||||
if (x < 0) x = 0;
|
||||
int lo = 0, hi = 645;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) >> 1;
|
||||
if (mag160c_official_t2e[mid] <= x) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int i = lo;
|
||||
if (i > 644) i = 644;
|
||||
int64_t diff = x - mag160c_official_t2e[i];
|
||||
int64_t t2 = mag160c_official_t2e[i + 1] - mag160c_official_t2e[i];
|
||||
int64_t slope = t2 ? (0x1000000 + t2 / 2) / t2 : 0;
|
||||
int64_t temp = ((slope * diff) >> 12) + ((int64_t)i << 12) - MAG_T2E_OFFSET;
|
||||
return (int)temp;
|
||||
}
|
||||
|
||||
/* ---------------- DDT loading (official 0x18000f130 v3) ---------------- */
|
||||
|
||||
static int load_ddt(mag160c_render_t *r, const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
unsigned char hdr[256];
|
||||
if (fread(hdr, 1, 0x100, f) < 0x98) { fclose(f); return 0; }
|
||||
unsigned magic = *(unsigned *)(hdr + 0);
|
||||
unsigned w = *(unsigned *)(hdr + 4), hh = *(unsigned *)(hdr + 8);
|
||||
if (magic != 0x5aa50003 && magic != 0x5aa50004) { fclose(f); return 0; }
|
||||
if (w != r->w || hh != r->h) { fclose(f); return 0; }
|
||||
r->ep_count = *(int *)(hdr + 12);
|
||||
r->nsegs = *(int *)(hdr + 16);
|
||||
if (r->ep_count < 2 || r->ep_count > 64 || r->nsegs < 1 || r->nsegs > 8) {
|
||||
fclose(f); return 0;
|
||||
}
|
||||
int v4 = (magic == 0x5aa50004);
|
||||
r->dev24 = *(int *)(hdr + 20);
|
||||
r->dev4c_shift = 3;
|
||||
if (!v4) {
|
||||
r->c5c = *(int *)(hdr + 28);
|
||||
r->c60 = *(int *)(hdr + 32);
|
||||
if (r->c5c > 0x10000) r->c5c = 0;
|
||||
} else {
|
||||
r->c5c = *(int *)(hdr + 0x2c + 0x120);
|
||||
r->c60 = *(int *)(hdr + 0x30 + 0x120);
|
||||
}
|
||||
if (r->c5c & 1) r->c5c &= ~1;
|
||||
int arro = v4 ? 0x120 + 36 : 36;
|
||||
if (fseek(f, arro, SEEK_SET)) { fclose(f); return 0; }
|
||||
if (fread(r->ep_temps, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; }
|
||||
unsigned tmp[64];
|
||||
if (fread(tmp, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; }
|
||||
if (fread(tmp, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; }
|
||||
if (fread(r->blind_cnt, 4, r->ep_count - 1, f) != (size_t)(r->ep_count - 1)) { fclose(f); return 0; }
|
||||
r->blind_cnt[r->ep_count - 1] = 0;
|
||||
for (int i = 0; i < r->ep_count; ++i)
|
||||
if (r->blind_cnt[i] > 4096) r->blind_cnt[i] = 4096;
|
||||
|
||||
size_t thr_sz = (size_t)(r->nsegs - 1) * r->npix + r->c5c / 2;
|
||||
size_t gain_sz = (size_t)r->nsegs * r->npix * 2;
|
||||
for (int e = 0; e < r->ep_count; ++e) {
|
||||
r->ep_thr[e] = malloc(thr_sz * 2);
|
||||
r->ep_gain[e] = malloc(gain_sz * 2);
|
||||
r->ep_blind[e] = malloc((size_t)r->blind_cnt[e] * 40);
|
||||
if (!r->ep_thr[e] || !r->ep_gain[e] ||
|
||||
(r->blind_cnt[e] && !r->ep_blind[e])) { fclose(f); return 0; }
|
||||
if (fread(r->ep_thr[e], 2, thr_sz, f) != thr_sz) { fclose(f); return 0; }
|
||||
if (fread(r->ep_gain[e], 2, gain_sz, f) != gain_sz) { fclose(f); return 0; }
|
||||
}
|
||||
for (int e = 0; e < r->ep_count - 1; ++e) {
|
||||
if (r->blind_cnt[e] &&
|
||||
fread(r->ep_blind[e], 40, r->blind_cnt[e], f) != (size_t)r->blind_cnt[e]) {
|
||||
fclose(f); return 0;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
r->shift = 12;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int select_endpoint(mag160c_render_t *r, int shutter) {
|
||||
int sel = 0;
|
||||
if (r->ep_count != 2) {
|
||||
while (sel < r->ep_count - 2 && shutter > r->ep_temps[sel + 1]) sel++;
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
|
||||
static void interp_i16(const short *a, const short *b, short *dst, int n, int t) {
|
||||
for (int i = 0; i < n; ++i)
|
||||
dst[i] = (short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
|
||||
}
|
||||
static void interp_u16(const unsigned short *a, const unsigned short *b,
|
||||
unsigned short *dst, int n, int t) {
|
||||
for (int i = 0; i < n; ++i)
|
||||
dst[i] = (unsigned short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
|
||||
}
|
||||
|
||||
static void rebuild_tables(mag160c_render_t *r, int shutter) {
|
||||
r->sel = select_endpoint(r, shutter);
|
||||
int sel = r->sel;
|
||||
int t = ((shutter - r->ep_temps[sel]) << 12) / (r->ep_temps[sel + 1] - r->ep_temps[sel]);
|
||||
if (t > 0x3fff) t = 0x3fff;
|
||||
if (t < -0x3fff) t = -0x3fff;
|
||||
interp_i16(r->ep_thr[sel], r->ep_thr[sel + 1], r->thr_work,
|
||||
(r->nsegs - 1) * r->npix + r->c5c / 2, t);
|
||||
interp_u16(r->ep_gain[sel], r->ep_gain[sel + 1], r->gain_work,
|
||||
r->nsegs * r->npix * 2, t);
|
||||
r->blind_work = r->ep_blind[sel];
|
||||
r->blind_count = r->blind_cnt[sel];
|
||||
}
|
||||
|
||||
/* ---------------- ref smoother (mode = 4-frame mean) ---------------- */
|
||||
|
||||
static void ref_push(mag160c_render_t *r, const unsigned short *frame) {
|
||||
if (r->ref_cnt == 0) {
|
||||
for (int i = 0; i < r->npix; ++i) r->ref_acc[i] = frame[i];
|
||||
} else {
|
||||
for (int i = 0; i < r->npix; ++i) r->ref_acc[i] += frame[i];
|
||||
}
|
||||
r->ref_cnt++;
|
||||
if (r->ref_cnt >= OFF_N0) {
|
||||
for (int i = 0; i < r->npix; ++i)
|
||||
r->ref[i] = (unsigned short)(r->ref_acc[i] >> 2);
|
||||
r->ref_cnt = 0;
|
||||
r->has_ref = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- NUC + blind (0x180017200 + 0x180017330) ---------------- */
|
||||
|
||||
static void nuc_and_blind(mag160c_render_t *r, const unsigned short *f20,
|
||||
unsigned short *out) {
|
||||
const short *thr = r->thr_work;
|
||||
const unsigned short *gg = r->gain_work;
|
||||
for (int i = 0; i < r->npix; ++i) {
|
||||
int d2 = ((int)f20[i] - (int)r->ref[i]) >> 1;
|
||||
int seg = 0;
|
||||
if (d2 > thr[i * 2]) {
|
||||
seg = 1;
|
||||
if (d2 > thr[i * 2 + 1]) seg = 2;
|
||||
}
|
||||
const unsigned short *p = gg + (seg * r->npix + i) * 2;
|
||||
int v = (int)p[1] + ((int)p[0] * d2 >> 12);
|
||||
if (v < 0) v = 0;
|
||||
if (v > 65535) v = 65535;
|
||||
out[i] = (unsigned short)v;
|
||||
}
|
||||
if (r->blind_work && r->blind_count > 0) {
|
||||
for (int k = 0; k < r->blind_count; ++k) {
|
||||
const unsigned int *rec = r->blind_work + k * 10;
|
||||
unsigned target = rec[0], typ = rec[1];
|
||||
const unsigned int *ng = rec + 2;
|
||||
if (target >= (unsigned)r->npix || typ < 3 || typ > 8) continue;
|
||||
long sum = 0;
|
||||
for (unsigned j = 0; j < typ; ++j) sum += out[ng[j]];
|
||||
if (typ == 8) out[target] = (unsigned short)(sum >> 3);
|
||||
else if (typ == 4) out[target] = (unsigned short)(sum >> 2);
|
||||
else out[target] = (unsigned short)(sum / typ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- stats + window (0x180010780 + 0x1800109c0) ---------------- */
|
||||
|
||||
static void stats_window(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
unsigned long long ssum = 0, s2sum = 0;
|
||||
unsigned mn = 0xffff, mx = 0;
|
||||
for (int i = 0; i < r->npix; ++i) {
|
||||
unsigned v = nuc[i];
|
||||
ssum += v;
|
||||
s2sum += (unsigned long long)v * v;
|
||||
if (v < mn) mn = v;
|
||||
if (v > mx) mx = v;
|
||||
}
|
||||
r->stat_min = (int)mn;
|
||||
r->stat_max = (int)mx;
|
||||
r->stat_mean = (int)(ssum / r->npix);
|
||||
double dmean = (double)ssum / r->npix;
|
||||
double var = (double)s2sum / r->npix - dmean * dmean;
|
||||
r->stat_std = (int)sqrt(var > 0 ? var : 0);
|
||||
if (mn == mx) {
|
||||
r->win_lo = r->win_hi = (int)mn;
|
||||
memset(r->lut, 0x80, sizeof(r->lut));
|
||||
memset(r->gray160, 0x80, r->npix);
|
||||
return;
|
||||
}
|
||||
int base = (r->dev24 * 1000) >> r->dev4c_shift;
|
||||
if (base < 0x80) base = 0x80;
|
||||
int half = base >> 1;
|
||||
int lo = r->stat_mean - half;
|
||||
if ((unsigned)lo > (unsigned)r->stat_min) lo = r->stat_min;
|
||||
if (lo < 0) lo = 0;
|
||||
int hi = r->stat_mean + half;
|
||||
if ((unsigned)hi < (unsigned)r->stat_max) hi = r->stat_max;
|
||||
if (hi > 65535) hi = 65535;
|
||||
r->win_lo = lo;
|
||||
r->win_hi = hi;
|
||||
if (r->win_hi <= r->win_lo) r->win_hi = r->win_lo + 1;
|
||||
}
|
||||
|
||||
/* ---------------- LUT1024 rebuild (0x180011950 + 0x180011ee0) ---------------- */
|
||||
|
||||
static void lut_rebuild(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
int span = r->win_hi - r->win_lo;
|
||||
unsigned S = 0xFFC00000u / (unsigned)span;
|
||||
memset(r->hist1024, 0, sizeof(r->hist1024));
|
||||
for (int i = 0; i < r->npix; ++i) {
|
||||
unsigned idx = ((unsigned)nuc[i] - (unsigned)r->win_lo) * S >> 0x16;
|
||||
if (idx < 1024) r->hist1024[idx]++;
|
||||
}
|
||||
for (int i = 0; i < 1023; i += 3) {
|
||||
r->hist1024[i] = (r->hist1024[i + 1] + r->hist1024[i]) >> 1;
|
||||
r->hist1024[i + 1] = (r->hist1024[i + 2] + r->hist1024[i + 1]) >> 1;
|
||||
r->hist1024[i + 2] = (r->hist1024[i + 3] + r->hist1024[i + 2]) >> 1;
|
||||
}
|
||||
unsigned total = 0;
|
||||
for (int i = 0; i < 1024; ++i) total += r->hist1024[i];
|
||||
unsigned u21 = total >> 12;
|
||||
if (u21 == 0) u21 = 1;
|
||||
int lres = ((r->stat_mean - r->win_lo) * 0x3ff) / span;
|
||||
if (lres < 0) lres = 0;
|
||||
if (lres > 1023) lres = 1023;
|
||||
if (r->cdf_pivot_75) {
|
||||
unsigned acc = 0;
|
||||
int k = 0;
|
||||
while (k < 1024) {
|
||||
acc += r->hist1024[k];
|
||||
if (acc >= (total * 3 >> 2)) break;
|
||||
k++;
|
||||
}
|
||||
if (k < 1024 && k > lres) lres = k;
|
||||
}
|
||||
unsigned u11 = 0, u13 = 0;
|
||||
for (int k = lres; k >= 0; --k) {
|
||||
u11 += r->hist1024[k];
|
||||
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
|
||||
r->cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
|
||||
}
|
||||
u11 = 0; u13 = 0;
|
||||
for (int k = lres + 1; k < 1024; ++k) {
|
||||
u11 += r->hist1024[k];
|
||||
if (u11 < u21 * 8) {
|
||||
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
|
||||
} else {
|
||||
u13 += 0x200;
|
||||
u11 = 0;
|
||||
}
|
||||
r->cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
|
||||
}
|
||||
static unsigned curve[1024];
|
||||
for (int k = 0; k < 1024; ++k) {
|
||||
unsigned v = (0x300000u + (unsigned)k * 0x800) >> 0xd;
|
||||
curve[k] = v ? v : 1;
|
||||
}
|
||||
int iv6 = (r->dev48 * 0x155) / 200;
|
||||
int iv7 = iv6 + 0x155;
|
||||
int iv26 = iv7;
|
||||
if (iv6 + 0x156 > 0x331) iv26 = 0x330;
|
||||
int iv10 = 0x332;
|
||||
int b3 = 1;
|
||||
unsigned iv22 = 0xFFC00000u / (unsigned)span;
|
||||
while (iv26 + 1 < iv10) {
|
||||
iv7 = (iv10 + iv26) / 2;
|
||||
if (b3) { b3 = 0; iv7 = iv26; }
|
||||
unsigned u5 = r->cdf[0];
|
||||
if (u5 != 0) {
|
||||
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
|
||||
for (int k = lres; k >= 0; --k) {
|
||||
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
|
||||
(r->cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
|
||||
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
|
||||
r->lut[k] = (unsigned char)(u12 >> 6);
|
||||
u21v = u12;
|
||||
}
|
||||
}
|
||||
u5 = r->cdf[1023];
|
||||
if (u5 != 0) {
|
||||
unsigned u21v = (unsigned)(iv7 * 0x10);
|
||||
for (int k = lres + 1; k < 1024; ++k) {
|
||||
unsigned u12 = (r->cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
|
||||
(unsigned)(iv7 * 0x10);
|
||||
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
|
||||
r->lut[k] = (unsigned char)(u12 >> 6);
|
||||
u21v = u12;
|
||||
}
|
||||
}
|
||||
int iv4 = iv7;
|
||||
unsigned bfmin = ((unsigned)r->stat_min - (unsigned)r->win_lo) * iv22 >> 0x16;
|
||||
unsigned bfmax = ((unsigned)r->stat_max - (unsigned)r->win_lo) * iv22 >> 0x16;
|
||||
if (bfmin < 1024 && bfmax < 1024 &&
|
||||
r->lut[bfmin] == 0 && r->lut[bfmax] != 0xff) {
|
||||
iv4 = iv10;
|
||||
iv26 = iv7;
|
||||
}
|
||||
iv10 = iv4;
|
||||
}
|
||||
int dm = r->stat_mean - r->mean_prev;
|
||||
if (dm < 0) dm = -dm;
|
||||
r->mean_prev = r->stat_mean;
|
||||
int iv18 = iv10 + ((dm & 0xffff) << r->dev4c_shift) * 2 + r->stat_mean;
|
||||
if (iv18 < 0x9c4) {
|
||||
if (iv18 < 100) iv18 = 100;
|
||||
iv7 = (iv18 * iv7 + r->center_prev * 500) / (iv18 + 500);
|
||||
r->center_prev = iv7;
|
||||
unsigned u5 = r->cdf[0];
|
||||
if (u5 != 0) {
|
||||
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
|
||||
for (int k = lres; k >= 0; --k) {
|
||||
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
|
||||
(r->cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
|
||||
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
|
||||
r->lut[k] = (unsigned char)(u12 >> 6);
|
||||
u21v = u12;
|
||||
}
|
||||
}
|
||||
u5 = r->cdf[1023];
|
||||
if (u5 != 0) {
|
||||
unsigned u21v = (unsigned)(iv7 * 0x10);
|
||||
for (int k = lres + 1; k < 1024; ++k) {
|
||||
unsigned u12 = (r->cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
|
||||
(unsigned)(iv7 * 0x10);
|
||||
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
|
||||
r->lut[k] = (unsigned char)(u12 >> 6);
|
||||
u21v = u12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- gray + 2x upscale (0x180019740) ---------------- */
|
||||
|
||||
static void gray_map(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
unsigned S = 0xFFC00000u / (unsigned)(r->win_hi - r->win_lo);
|
||||
for (int i = 0; i < r->npix; ++i) {
|
||||
unsigned v = nuc[i];
|
||||
unsigned idx;
|
||||
if (v <= (unsigned)r->win_lo) idx = 0;
|
||||
else if (v >= (unsigned)r->win_hi) idx = 1023;
|
||||
else idx = (v - (unsigned)r->win_lo) * S >> 0x16;
|
||||
if (idx > 1023) idx = 1023;
|
||||
r->gray160[i] = r->lut[idx];
|
||||
}
|
||||
}
|
||||
|
||||
static void upscale2x(mag160c_render_t *r) {
|
||||
const unsigned char *s = r->gray160;
|
||||
unsigned char *o = r->gray320;
|
||||
int W = r->w, H = r->h, W2 = W * 2;
|
||||
for (int y = 0; y < H - 1; ++y) {
|
||||
const unsigned char *r0 = s + y * W, *r1 = r0 + W;
|
||||
unsigned char *o0 = o + y * W2 * 2, *o1 = o0 + W2;
|
||||
int x;
|
||||
for (x = 0; x < W - 2; x += 2) {
|
||||
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
|
||||
unsigned b0 = r1[x], b1 = r1[x + 1], b2 = r1[x + 2];
|
||||
int c = x * 2;
|
||||
o0[c] = (unsigned char)a0;
|
||||
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
|
||||
o0[c + 2] = (unsigned char)a1;
|
||||
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
|
||||
o1[c] = (unsigned char)((b0 + a0) >> 1);
|
||||
o1[c + 1] = (unsigned char)((b0 + b1 + a0 + a1) >> 2);
|
||||
o1[c + 2] = (unsigned char)((b1 + a1) >> 1);
|
||||
o1[c + 3] = (unsigned char)((b2 + b1 + a2 + a1) >> 2);
|
||||
}
|
||||
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
|
||||
unsigned b0 = r1[W - 2], b1 = r1[W - 1];
|
||||
int c = (W - 2) * 2;
|
||||
o0[c] = (unsigned char)a0;
|
||||
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
|
||||
o1[c] = (unsigned char)((b0 + a0) >> 1);
|
||||
o1[c + 1] = (unsigned char)((a1 + b1 + b0 + a0) >> 2);
|
||||
c = (W - 1) * 2;
|
||||
o0[c] = (unsigned char)a1;
|
||||
o0[c + 1] = (unsigned char)((3 * a1 >> 2) + (a0 >> 2));
|
||||
o1[c] = (unsigned char)((b1 + a1) >> 1);
|
||||
o1[c + 1] = (unsigned char)((3 * (b1 + a1) + b0 + a0) >> 3);
|
||||
}
|
||||
{
|
||||
int y = H - 1;
|
||||
const unsigned char *r0 = s + y * W, *r1 = r0 - W;
|
||||
unsigned char *o0 = o + y * W2 * 2, *o1 = o0 + W2;
|
||||
int x;
|
||||
for (x = 0; x < W - 2; x += 2) {
|
||||
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
|
||||
unsigned u0 = r1[x], u1 = r1[x + 1], u2 = r1[x + 2];
|
||||
int c = x * 2;
|
||||
o0[c] = (unsigned char)a0;
|
||||
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
|
||||
o0[c + 2] = (unsigned char)a1;
|
||||
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
|
||||
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
|
||||
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
|
||||
o1[c + 2] = (unsigned char)((3 * a1 >> 2) + (u1 >> 2));
|
||||
o1[c + 3] = (unsigned char)((3 * (a2 + a1) + u2 + u1) >> 3);
|
||||
}
|
||||
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
|
||||
unsigned u0 = r1[W - 2], u1 = r1[W - 1];
|
||||
int c = (W - 2) * 2;
|
||||
o0[c] = (unsigned char)a0;
|
||||
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
|
||||
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
|
||||
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
|
||||
c = (W - 1) * 2;
|
||||
o0[c] = (unsigned char)a1;
|
||||
o0[c + 1] = (unsigned char)((3 * a1 + a0) >> 2);
|
||||
o1[c] = (unsigned char)((3 * a1 + u1) >> 2);
|
||||
o1[c + 1] = (unsigned char)((o0[c + 1] + o1[c] + a1) / 3);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- FFC state machine (0x180009ee0) ----------------
|
||||
* Returns: 0 = render this frame
|
||||
* 1 = hidden frame (startup/rebuild window), freeze display
|
||||
* 2 = ref collection frame (type-1 window): caller must push
|
||||
* the decoded pixels through ref_push before freezing. */
|
||||
|
||||
static int ffc_step(mag160c_render_t *r, int shutter) {
|
||||
if (r->ffc_idx < 0) {
|
||||
if (r->ffc_cb) r->ffc_cb(r->ffc_user, 0);
|
||||
r->ffc_idx = 0;
|
||||
r->shutter_at_ffc = shutter;
|
||||
}
|
||||
r->ffc_idx++;
|
||||
int idx = r->ffc_idx;
|
||||
if (idx <= OFF_N1) {
|
||||
if (idx == 1) rebuild_tables(r, r->shutter_at_ffc);
|
||||
return 1;
|
||||
}
|
||||
if (idx <= OFF_N0 + OFF_N1) {
|
||||
if (idx == OFF_N0 + OFF_N1) {
|
||||
if (r->ffc_cb) r->ffc_cb(r->ffc_user, 1);
|
||||
}
|
||||
if (idx == OFF_N1 + 1) {
|
||||
r->ref_cnt = 0;
|
||||
r->has_ref = 0;
|
||||
}
|
||||
return 2; /* ref collection window */
|
||||
}
|
||||
if (idx <= OFF_N0 + OFF_N2 + OFF_N1) return 1;
|
||||
/* normal frame: FFC condition */
|
||||
{
|
||||
int drift = shutter - r->shutter_at_ffc;
|
||||
if (drift < 0) drift = -drift;
|
||||
int cool = OFF_N0 + OFF_N2 + OFF_N1;
|
||||
int do_ffc = 0;
|
||||
if (r->force75 && !r->startup_ffc_done && idx == 75) {
|
||||
r->startup_ffc_done = 1;
|
||||
do_ffc = 1;
|
||||
}
|
||||
if (idx >= OFF_N0 + r->ffc_period)
|
||||
do_ffc = 1;
|
||||
else if (idx >= cool + 30 && drift > r->ffc_drift)
|
||||
do_ffc = 1;
|
||||
if (do_ffc) {
|
||||
if (r->ffc_cb) r->ffc_cb(r->ffc_user, 0);
|
||||
r->ffc_idx = 0;
|
||||
r->shutter_at_ffc = shutter;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------- public API ---------------- */
|
||||
|
||||
mag160c_error_t mag160c_render_init(mag160c_render_t **out,
|
||||
const mag160c_render_cfg_t *cfg) {
|
||||
if (!out || !cfg || !cfg->ddt_path) return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
mag160c_render_t *r = calloc(1, sizeof(*r));
|
||||
if (!r) return MAG160C_ERR_NO_MEMORY;
|
||||
r->w = cfg->width ? cfg->width : 160;
|
||||
r->h = cfg->height ? cfg->height : 120;
|
||||
r->npix = r->w * r->h;
|
||||
r->ffc_cb = cfg->ffc_cb;
|
||||
r->ffc_user = cfg->ffc_user;
|
||||
r->ffc_period = cfg->ffc_period ? cfg->ffc_period : 1800;
|
||||
r->ffc_drift = cfg->ffc_drift ? cfg->ffc_drift : 250;
|
||||
r->warm_frames = cfg->warm_frames ? cfg->warm_frames : 20;
|
||||
r->cdf_pivot_75 = cfg->cdf_pivot_75;
|
||||
r->force75 = cfg->startup_force_ffc;
|
||||
r->dev24 = 5;
|
||||
r->dev4c_shift = 3;
|
||||
r->dev48 = 0;
|
||||
r->dev47950 = 0;
|
||||
r->dev47948 = -50000;
|
||||
r->ffc_idx = -1;
|
||||
r->center_prev = 341;
|
||||
r->mean_prev = 0;
|
||||
|
||||
r->thr_work = malloc((r->npix * 2 + 4096) * sizeof(short));
|
||||
r->gain_work = malloc((size_t)r->npix * 6 * sizeof(unsigned short));
|
||||
r->ref = malloc((size_t)r->npix * 2);
|
||||
r->ref_acc = malloc((size_t)r->npix * 4);
|
||||
r->nuc = malloc((size_t)r->npix * 2);
|
||||
r->gray160 = malloc((size_t)r->npix);
|
||||
r->gray320 = malloc((size_t)r->npix * 4);
|
||||
if (!r->thr_work || !r->gain_work || !r->ref || !r->ref_acc ||
|
||||
!r->nuc || !r->gray160 || !r->gray320) {
|
||||
mag160c_render_destroy(r);
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
if (!load_ddt(r, cfg->ddt_path)) {
|
||||
mag160c_render_destroy(r);
|
||||
return MAG160C_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
*out = r;
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_render_frame(mag160c_render_t *r, const uint8_t *frame,
|
||||
int has_hdr, uint8_t *out_rgb) {
|
||||
if (!r || !frame || !out_rgb) return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
r->global_frames++;
|
||||
if (r->global_frames < r->warm_frames) return MAG160C_ERR_NOT_READY;
|
||||
|
||||
int shutter = r->shutter;
|
||||
if (has_hdr) {
|
||||
unsigned len = (unsigned)frame[8] | ((unsigned)frame[9] << 8) |
|
||||
((unsigned)frame[10] << 16) | ((unsigned)frame[11] << 24);
|
||||
if (len == (unsigned)(r->npix * 2)) {
|
||||
const uint8_t *tail = frame + 0x1c + len;
|
||||
shutter = (int)((unsigned)tail[8] | ((unsigned)tail[9] << 8) |
|
||||
((unsigned)tail[10] << 16) | ((unsigned)tail[11] << 24));
|
||||
}
|
||||
}
|
||||
r->shutter = shutter;
|
||||
|
||||
int st = ffc_step(r, shutter);
|
||||
if (st != 0) {
|
||||
if (st == 2) {
|
||||
/* ref window: push decoded pixels (type-1 calibration frames) */
|
||||
const uint8_t *px = has_hdr ? frame + 0x1c : frame;
|
||||
for (int i = 0; i < r->npix; ++i)
|
||||
r->nuc[i] = (unsigned short)(px[i * 2] | ((unsigned)px[i * 2 + 1] << 8));
|
||||
ref_push(r, r->nuc);
|
||||
}
|
||||
return MAG160C_ERR_NOT_READY;
|
||||
}
|
||||
|
||||
/* decode pixels (frame + 0x1c when header present) */
|
||||
const uint8_t *px = has_hdr ? frame + 0x1c : frame;
|
||||
unsigned short *live = r->nuc;
|
||||
for (int i = 0; i < r->npix; ++i)
|
||||
live[i] = (unsigned short)(px[i * 2] | ((unsigned)px[i * 2 + 1] << 8));
|
||||
|
||||
if (!r->has_ref) return MAG160C_ERR_NOT_READY;
|
||||
|
||||
nuc_and_blind(r, live, r->nuc);
|
||||
stats_window(r, r->nuc);
|
||||
lut_rebuild(r, r->nuc);
|
||||
gray_map(r, r->nuc);
|
||||
upscale2x(r);
|
||||
/* palette colorize 320x240 */
|
||||
for (int i = 0; i < r->npix * 4; ++i) {
|
||||
unsigned char gv = r->gray320[i];
|
||||
out_rgb[i * 3 + 0] = mag160c_official_palette256[gv][0];
|
||||
out_rgb[i * 3 + 1] = mag160c_official_palette256[gv][1];
|
||||
out_rgb[i * 3 + 2] = mag160c_official_palette256[gv][2];
|
||||
}
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void mag160c_render_set_shutter(mag160c_render_t *r, int shutter) {
|
||||
if (r) r->shutter = shutter;
|
||||
}
|
||||
|
||||
void mag160c_render_get_stats(mag160c_render_t *r, int *win_lo, int *win_hi,
|
||||
int *fmin, int *fmax, int *mean, int *stddev) {
|
||||
if (!r) return;
|
||||
if (win_lo) *win_lo = r->win_lo;
|
||||
if (win_hi) *win_hi = r->win_hi;
|
||||
if (fmin) *fmin = r->stat_min;
|
||||
if (fmax) *fmax = r->stat_max;
|
||||
if (mean) *mean = r->stat_mean;
|
||||
if (stddev) *stddev = r->stat_std;
|
||||
}
|
||||
|
||||
int mag160c_render_get_shutter(mag160c_render_t *r) { return r ? r->shutter : 0; }
|
||||
int mag160c_render_get_endpoint(mag160c_render_t *r) { return r ? r->sel : 0; }
|
||||
int mag160c_render_get_frame_index(mag160c_render_t *r) { return r ? r->ffc_idx : 0; }
|
||||
int mag160c_render_has_reference(mag160c_render_t *r) { return r ? r->has_ref : 0; }
|
||||
|
||||
mag160c_error_t mag160c_render_probe_temp(mag160c_render_t *r, uint32_t x, uint32_t y,
|
||||
int32_t *out_temp_mc) {
|
||||
if (!r || !out_temp_mc || x >= r->w || y >= r->h) return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
*out_temp_mc = counts_to_temp_mc(r->nuc[y * r->w + x]);
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void mag160c_render_destroy(mag160c_render_t *r) {
|
||||
if (!r) return;
|
||||
for (int e = 0; e < r->ep_count; ++e) {
|
||||
free(r->ep_thr[e]);
|
||||
free(r->ep_gain[e]);
|
||||
free(r->ep_blind[e]);
|
||||
}
|
||||
free(r->thr_work);
|
||||
free(r->gain_work);
|
||||
free(r->ref);
|
||||
free(r->ref_acc);
|
||||
free(r->nuc);
|
||||
free(r->gray160);
|
||||
free(r->gray320);
|
||||
free(r);
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/* 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)
|
||||
fwrite(rgb + (size_t)y * w * 3, 1, (size_t)w * 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;
|
||||
}
|
||||
Reference in New Issue
Block a user