main.c 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903
  1. #define _GNU_SOURCE
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <strings.h>
  7. #include <signal.h>
  8. #include <pthread.h>
  9. #include <sys/time.h>
  10. #include <time.h>
  11. #include <sys/wait.h>
  12. #include <errno.h>
  13. #include <fcntl.h>
  14. #include <unistd.h>
  15. #include <sys/ioctl.h>
  16. #include <linux/dma-heap.h>
  17. #include <linux/dma-buf.h>
  18. #include <sys/mman.h>
  19. #include <ifaddrs.h>
  20. #include <arpa/inet.h>
  21. #include <netinet/in.h>
  22. #include "dma_util.h"
  23. #include "decoder.h"
  24. #include "composer.h"
  25. #include "display.h"
  26. #include "plc.h"
  27. #include "title.h"
  28. #include "flog.h"
  29. #include "hikvision.h"
  30. #include "web_server.h"
  31. #define MAX_STREAMS 25
  32. #define MAX_TILES 25
  33. #define MAX_ISAPI_WRITE 64
  34. #define MAX_LAYOUTS 16
  35. #define MAX_RULES 32
  36. #define MAX_RULE_BITS 16
  37. /* Split layout mode: fixed top row + PLC-driven adaptive bottom row. */
  38. #define SPLIT_MAX_BOTTOM 8
  39. static int g_split_enabled = 0; /* 1 = use split mode */
  40. static int g_split_top_ids[MAX_TILES]; /* top row camera ids */
  41. static int g_split_top_n = 0; /* top row count */
  42. static double g_split_top_ratio = 0.3; /* top row height fraction */
  43. static int g_split_bottom_ids[SPLIT_MAX_BOTTOM][MAX_TILES]; /* per variant */
  44. static int g_split_bottom_cnt[SPLIT_MAX_BOTTOM] = {0}; /* cams per variant */
  45. static int g_split_bottom_valid[SPLIT_MAX_BOTTOM] = {0}; /* 1 if variant usable */
  46. static int g_split_bottom_count = 0; /* temp: current bottom_N being parsed */
  47. static int g_split_bottom_def_ids[MAX_TILES] = {0}; /* [layout_bottom] default */
  48. static int g_split_bottom_def_cnt = 0;
  49. static int g_split_bottom_def_valid = 0;
  50. /* Default bottom layout used when no PLC rule matches: the [layout_bottom]
  51. * group when present, else the highest valid [layout_bottom_N]. Sentinel 0
  52. * means "use the [layout_bottom] default group". */
  53. static int split_idle_bottom(void) {
  54. if (g_split_bottom_def_valid) return 0;
  55. for (int bv = SPLIT_MAX_BOTTOM; bv >= 1; bv--)
  56. if (g_split_bottom_valid[bv - 1]) return bv;
  57. return 1;
  58. }
  59. static int split_bottom_cfg(int n, int **ids) {
  60. if (n <= 0) { *ids = g_split_bottom_def_ids; return g_split_bottom_def_cnt; }
  61. *ids = g_split_bottom_ids[n - 1]; return g_split_bottom_cnt[n - 1];
  62. }
  63. /* 超过该时长没有任何新帧到达,视为流静默死亡,worker 断开并重连。 */
  64. #define STREAM_STALL_TIMEOUT_MS 5000
  65. /* 最后一次新帧后超过该时长仍无数据,主循环停止定格旧帧并显示 NO SIGNAL。 */
  66. #define HOLD_LAST_FRAME_MS 8000
  67. typedef struct {
  68. char url[512];
  69. char url_main[512]; /* main-stream URL; used when a single-channel
  70. * layout is active (use_main=1). Empty = none. */
  71. Decoder *dec;
  72. int stream_id;
  73. int running;
  74. pthread_t thread;
  75. int connected;
  76. int codec;
  77. int width;
  78. int height;
  79. /* Ring buffer: worker writes, main reads */
  80. DecodedFrame ring[2];
  81. int ring_count;
  82. int ring_head;
  83. pthread_mutex_t mutex;
  84. pthread_cond_t cond;
  85. /* Worker-assigned monotonic frame seq (stored in ring[].pts) */
  86. int64_t frame_seq;
  87. /* Main currently holds this decoder buffer index (set by main while it
  88. * composes/displays the frame; worker must not reuse it). -1 = none. */
  89. int held_pin;
  90. /* Main/sub stream selection (guarded by mutex). Main thread flips
  91. * use_main when the active layout is single-channel; the worker
  92. * reconnects to url_main/url accordingly and records which one it
  93. * is currently connected to in dec_using_main. */
  94. int use_main;
  95. int dec_using_main;
  96. /* Dup'd fd of the last frame main composed from this stream. Kept across
  97. * reconnects/layout switches so a brief gap shows the frozen last frame
  98. * instead of NO SIGNAL. -1 = never had a frame. (main thread only) */
  99. int last_fd;
  100. int last_w, last_h, last_stride, last_vstride;
  101. /* (main thread only) Monotonic time of the last *new* frame and the seq
  102. * it was stamped with, so a silent stream is detected even though the
  103. * same ring buffer keeps being re-acquired. */
  104. int64_t last_seq_seen;
  105. double last_frame_mono;
  106. } StreamWorker;
  107. static StreamWorker workers[MAX_STREAMS];
  108. static int num_streams = 0;
  109. static int tile_map[MAX_TILES];
  110. static int num_tiles = 0;
  111. static volatile sig_atomic_t g_running = 1;
  112. static volatile sig_atomic_t g_rebuild_req = 0;
  113. static Composer *g_composer = NULL;
  114. static Display *g_display = NULL;
  115. static int display_fail_streak = 0; /* consecutive display_commit failures */
  116. static int output_fd[3] = {-1, -1, -1};
  117. static void *output_map[3] = {NULL, NULL, NULL};
  118. static int output_front = 0;
  119. static int output_last = 0; /* previous front, for triple-buffer reuse */
  120. static int output_w = 0;
  121. static int output_h = 0;
  122. static int output_stride = 0;
  123. static size_t output_size = 0;
  124. /* Triple buffering: pick a back buffer that is neither the current scanout
  125. * (front) nor the immediately previous one (last), so VOP can never be mid-scan
  126. * on a buffer we are about to overwrite -> eliminates tearing. */
  127. #define BACK_BUF() (3 - output_front - output_last)
  128. /* ---- HikVision ISAPI camera list / optional writes ---- */
  129. typedef struct {
  130. int id;
  131. int main; /* 1 = main stream channel, 0 = sub */
  132. int width, height, fps, bitrate;
  133. } IsapiWrite;
  134. static HvCam g_cams[MAX_STREAMS];
  135. static HvUrls g_urls[MAX_STREAMS];
  136. static int g_num_cams = 0;
  137. static IsapiWrite g_isapi_write[MAX_ISAPI_WRITE];
  138. static int g_isapi_write_count = 0;
  139. /* ---- reconnect / display behaviour options ---- */
  140. static int g_reconnect_ms = 3000;static int g_reconnect_max_ms = 15000;
  141. static int g_connect_timeout_sec = 10;
  142. static int g_letterbox = 1;
  143. static int g_no_signal_text = 1;
  144. static int g_blank_on_missing = 1;
  145. static int g_grid_lines = 1; /* draw grid separators between tiles (0 to disable) */
  146. static int g_hold_layout_sec = 5;
  147. static double g_last_layout_ts = 0;
  148. /* Layout switch debounce: a new layout must stay stable for this many ms
  149. * before it is applied, so a momentary PLC bit pulse (a point that triggers
  150. * and clears within one scan) does not visibly flicker the screen. */
  151. static int g_layout_debounce_ms = 300;
  152. static int g_pending_layout = -1;
  153. static double g_pending_layout_ts = 0;
  154. /* ---- PLC-driven layout state ---- */
  155. typedef struct {
  156. int id;
  157. int map[MAX_TILES]; /* cam ids in grid order; -1 = empty cell */
  158. int n; /* total cells across all rows */
  159. int row_cols[MAX_TILES]; /* column count per row (row-major) */
  160. int nrows; /* number of rows */
  161. } LayoutDef;
  162. typedef struct {
  163. int byte_idx; /* offset relative to plc read_start */
  164. int bit;
  165. } RuleBit;
  166. typedef struct {
  167. int layout; /* index into g_layouts */
  168. RuleBit bits[MAX_RULE_BITS];
  169. int group[MAX_RULE_BITS]; /* group index per bit (0-based) */
  170. int nbits;
  171. int ngroups; /* number of ';' groups */
  172. int all; /* 1 = all bits in a group required, 0 = any */
  173. } PlcRule;
  174. static LayoutDef g_layouts[MAX_LAYOUTS];
  175. static int g_num_layouts = 0;
  176. static PlcRule g_rules[MAX_RULES];
  177. static int g_num_rules = 0;
  178. static int g_plc_enabled = 0;
  179. static PlcConfig g_plc_cfg;
  180. /* [auto_reboot] scheduled device restart */
  181. static int g_reboot_enable = 0; /* 0=off 1=on */
  182. static int g_reboot_period = 0; /* 0=daily 1=weekly */
  183. static int g_reboot_hour = 3; /* HH */
  184. static int g_reboot_min = 0; /* MM */
  185. static int g_reboot_wday = -1; /* tm_wday (0=Sun..6=Sat); -1 = any day */
  186. static int g_cur_layout = -1;
  187. static int g_grid_x[MAX_TILES], g_grid_y[MAX_TILES];
  188. static int g_grid_w[MAX_TILES], g_grid_h[MAX_TILES];
  189. static int g_grid_nrows = 0; /* base [layout] grid: #rows */
  190. static int g_grid_row_cols[MAX_TILES]; /* base grid: cols per row */
  191. /* Active layout compiled to regions (merged cells). Each region maps 1:1 to a
  192. * camera and a screen rectangle; these drive composition every frame. */
  193. static int g_disp_sid[MAX_TILES];
  194. static int g_disp_tiles = 0;
  195. /* ---- title bar ---- */
  196. static TitleCfg g_title;
  197. static int g_title_enabled = 0;
  198. static int g_title_h = 0;
  199. static int g_title_overlay = 0;
  200. /* ---- fault log ---- */
  201. static char g_flog_path[512];
  202. static long g_flog_rotate_ms = 3600000;
  203. static long g_flog_max_bytes = 1 << 20;
  204. static void sig_handler(int sig) {
  205. if (sig == SIGUSR1) { g_rebuild_req = 1; return; }
  206. g_running = 0;
  207. }
  208. static void install_signal_handlers(void) {
  209. struct sigaction sa;
  210. memset(&sa, 0, sizeof(sa));
  211. sa.sa_handler = sig_handler;
  212. sigemptyset(&sa.sa_mask);
  213. sa.sa_flags = 0; /* no SA_RESTART: block syscalls must return so loops see g_running */
  214. sigaction(SIGINT, &sa, NULL);
  215. sigaction(SIGTERM, &sa, NULL);
  216. sigaction(SIGUSR1, &sa, NULL);
  217. }
  218. /* PLC 离线/恢复状态跳变时写一次故障日志(不在画面上叠加提示文字,
  219. * 避免画面右下角每帧闪烁)。 */
  220. static void log_plc_state_change(void) {
  221. if (!g_plc_enabled) return;
  222. int offline = !plc_is_connected();
  223. static int last_offline = -1;
  224. if (offline == last_offline) return;
  225. last_offline = offline;
  226. if (offline) flog("PLC 离线报警");
  227. else flog("PLC 恢复在线");
  228. }
  229. /* Current IPv4 address(es) shown on screen (eth0 first, then any other
  230. * non-loopback address), refreshed about once a second. Lets the operator
  231. * read the board's IP straight off the video if they forgot it. */
  232. static char g_ip_text[128] = "";
  233. static time_t g_ip_last = 0;
  234. static void update_ip_text(void) {
  235. time_t now = time(NULL);
  236. if (now == g_ip_last && g_ip_text[0]) return;
  237. g_ip_last = now;
  238. char eth0[48] = "", other[48] = "", extra[48] = "";
  239. struct ifaddrs *ifa = NULL;
  240. if (getifaddrs(&ifa) == 0) {
  241. for (struct ifaddrs *p = ifa; p; p = p->ifa_next) {
  242. if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET) continue;
  243. if (strcmp(p->ifa_name, "lo") == 0) continue;
  244. char buf[48];
  245. struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr;
  246. if (!inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf))) continue;
  247. if (strcmp(p->ifa_name, "eth0") == 0) {
  248. if (!eth0[0]) snprintf(eth0, sizeof(eth0), "%s", buf);
  249. else if (!extra[0]) snprintf(extra, sizeof(extra), "%s", buf);
  250. } else if (!other[0]) {
  251. snprintf(other, sizeof(other), "%s", buf);
  252. }
  253. }
  254. freeifaddrs(ifa);
  255. }
  256. const char *a = eth0[0] ? eth0 : other;
  257. const char *b = eth0[0] ? extra : NULL;
  258. char line[128];
  259. if (!a[0]) snprintf(line, sizeof(line), "IP 无地址");
  260. else if (b && b[0]) snprintf(line, sizeof(line), "IP %s %s", a, b);
  261. else snprintf(line, sizeof(line), "IP %s", a);
  262. if (strcmp(line, g_ip_text) != 0) {
  263. snprintf(g_ip_text, sizeof(g_ip_text), "%s", line);
  264. fprintf(stderr, "IP 显示: %s\n", g_ip_text);
  265. }
  266. }
  267. /* Draw a 2px grid line around every on-screen tile in the NV12 output buffer.
  268. * Lines use a bright gray (Y high, neutral UV) so they read as white grid
  269. * separators over any video content. Call after composition, on the back
  270. * buffer (which the caller keeps CPU-visible). */
  271. static void draw_grid_lines(uint8_t *map, int out_w, int out_h,
  272. Composer *comp, int n) {
  273. if (!map || !comp || n <= 0) return;
  274. static const int TH = 2; /* line thickness, px */
  275. static const uint8_t YW = 220; /* near-white */
  276. uint8_t *Y = map;
  277. for (int t = 0; t < n; t++) {
  278. int x, y, w, h;
  279. composer_get_tile_layout(comp, t, &x, &y, &w, &h);
  280. if (x < 0) x = 0;
  281. if (y < 0) y = 0;
  282. if (x + w > out_w) w = out_w - x;
  283. if (y + h > out_h) h = out_h - y;
  284. if (w <= 0 || h <= 0) continue;
  285. /* top */
  286. for (int r = 0; r < TH && y + r < out_h; r++)
  287. memset(Y + (size_t)(y + r) * out_w + x, YW, (size_t)w);
  288. /* bottom */
  289. int by = y + h - TH;
  290. if (by < 0) by = 0;
  291. for (int r = 0; r < TH && by + r < out_h; r++)
  292. memset(Y + (size_t)(by + r) * out_w + x, YW, (size_t)w);
  293. /* left */
  294. for (int yy = y; yy < y + h && yy < out_h; yy++) {
  295. int x0 = x, x1 = x + TH;
  296. if (x1 > out_w) x1 = out_w;
  297. if (x0 < 0) x0 = 0;
  298. for (int xx = x0; xx < x1; xx++)
  299. Y[(size_t)yy * out_w + xx] = YW;
  300. }
  301. /* right */
  302. int xr0 = x + w - TH;
  303. if (xr0 < 0) xr0 = 0;
  304. for (int yy = y; yy < y + h && yy < out_h; yy++) {
  305. int xx0 = xr0, xx1 = x + w;
  306. if (xx0 < 0) xx0 = 0;
  307. if (xx1 > out_w) xx1 = out_w;
  308. for (int xx = xx0; xx < xx1; xx++)
  309. Y[(size_t)yy * out_w + xx] = YW;
  310. }
  311. }
  312. }
  313. static void *worker_thread(void *arg) {
  314. StreamWorker *w = (StreamWorker *)arg;
  315. int backoff = g_reconnect_ms;
  316. struct timespec last_data_ts;
  317. while (g_running && w->running) {
  318. /* Pick the stream kind this worker should use right now. */
  319. pthread_mutex_lock(&w->mutex);
  320. int want_main = w->use_main && w->url_main[0];
  321. pthread_mutex_unlock(&w->mutex);
  322. if (!w->dec) {
  323. const char *url = want_main ? w->url_main : w->url;
  324. fprintf(stderr, "DBG: [stream %d] Connecting to %s\n", w->stream_id, url);
  325. w->dec = decoder_create(url, g_connect_timeout_sec);
  326. if (!w->dec) {
  327. fprintf(stderr, "DBG: [stream %d] Failed to connect\n", w->stream_id);
  328. flog("视频流 %d 连接失败: %s", w->stream_id, url);
  329. usleep((useconds_t)backoff * 1000);
  330. if (backoff < g_reconnect_max_ms) {
  331. backoff *= 2;
  332. if (backoff > g_reconnect_max_ms) backoff = g_reconnect_max_ms;
  333. }
  334. continue;
  335. }
  336. pthread_mutex_lock(&w->mutex);
  337. w->dec_using_main = want_main;
  338. w->connected = 1;
  339. pthread_mutex_unlock(&w->mutex);
  340. backoff = g_reconnect_ms;
  341. clock_gettime(CLOCK_MONOTONIC, &last_data_ts);
  342. w->codec = decoder_get_codec(w->dec);
  343. w->width = decoder_get_width(w->dec);
  344. w->height = decoder_get_height(w->dec);
  345. fprintf(stderr, "DBG: [stream %d] Connected: %dx%d codec=%d\n",
  346. w->stream_id, w->width, w->height, w->codec);
  347. flog("视频流 %d 已连接 %dx%d", w->stream_id, w->width, w->height);
  348. }
  349. while (g_running && w->running) {
  350. /* A single-channel layout switched which stream kind to present:
  351. * drop out and reconnect on the other URL. */
  352. pthread_mutex_lock(&w->mutex);
  353. int want_now = w->use_main && w->url_main[0];
  354. int switch_now = (want_now != w->dec_using_main);
  355. pthread_mutex_unlock(&w->mutex);
  356. if (switch_now) {
  357. fprintf(stderr, "DBG: [stream %d] switching to %s stream\n",
  358. w->stream_id, want_now ? "main" : "sub");
  359. backoff = 50;
  360. break;
  361. }
  362. DecodedFrame frame;
  363. int ret = decoder_get_frame(w->dec, &frame);
  364. if (ret == -3) {
  365. /* No frame ready. A live stream delivers continuously, so a
  366. * long silent stretch means the connection died quietly
  367. * (RTSP server gone / TCP dropped without a FIN). */
  368. struct timespec now_ts;
  369. clock_gettime(CLOCK_MONOTONIC, &now_ts);
  370. double idle = (now_ts.tv_sec - last_data_ts.tv_sec) +
  371. (now_ts.tv_nsec - last_data_ts.tv_nsec) / 1000000000.0;
  372. if (idle >= STREAM_STALL_TIMEOUT_MS / 1000.0) {
  373. flog_throttled("stream_stall", 10000,
  374. "视频流 %d 无数据 %d 秒,重连",
  375. w->stream_id, STREAM_STALL_TIMEOUT_MS / 1000);
  376. break;
  377. }
  378. usleep(1000);
  379. continue;
  380. }
  381. if (ret < 0) {
  382. flog_throttled("stream_break", 10000,
  383. "视频流 %d 解码中断 (ret=%d)", w->stream_id, ret);
  384. break;
  385. }
  386. clock_gettime(CLOCK_MONOTONIC, &last_data_ts);
  387. pthread_mutex_lock(&w->mutex);
  388. /* Never recycle the buffer main is currently displaying. */
  389. while (w->ring_count >= 2 && g_running && w->running &&
  390. w->ring[w->ring_head].idx == w->held_pin) {
  391. pthread_cond_wait(&w->cond, &w->mutex);
  392. }
  393. if (w->ring_count >= 2) {
  394. int ri = w->ring_head;
  395. decoder_release_frame(w->dec, w->ring[ri].idx);
  396. if (w->ring[ri].fd >= 0) close(w->ring[ri].fd);
  397. w->ring[ri].fd = -1;
  398. w->ring_head = (w->ring_head + 1) % 2;
  399. w->ring_count--;
  400. }
  401. int wi = (w->ring_head + w->ring_count) % 2;
  402. w->ring[wi] = frame;
  403. w->ring[wi].pts = ++w->frame_seq;
  404. w->ring_count++;
  405. pthread_cond_signal(&w->cond);
  406. pthread_mutex_unlock(&w->mutex);
  407. }
  408. /* Tear down decoder. Wait (bounded) for main to drain the ring so no
  409. * decoder buffer is outstanding when the pool is destroyed. */
  410. pthread_mutex_lock(&w->mutex);
  411. w->connected = 0;
  412. if (w->dec) {
  413. struct timespec ts;
  414. clock_gettime(CLOCK_REALTIME, &ts);
  415. ts.tv_sec += 1;
  416. while (g_running && w->ring_count > 0) {
  417. int rc = pthread_cond_timedwait(&w->cond, &w->mutex, &ts);
  418. if (rc == ETIMEDOUT) break;
  419. }
  420. }
  421. Decoder *old = w->dec;
  422. w->dec = NULL;
  423. pthread_mutex_unlock(&w->mutex);
  424. decoder_destroy(old);
  425. fprintf(stderr, "DBG: [stream %d] decoder released\n", w->stream_id);
  426. if (!(g_running && w->running)) break;
  427. usleep((useconds_t)backoff * 1000);
  428. if (backoff < g_reconnect_max_ms) {
  429. backoff *= 2;
  430. if (backoff > g_reconnect_max_ms) backoff = g_reconnect_max_ms;
  431. }
  432. }
  433. /* final cleanup on shutdown */
  434. pthread_mutex_lock(&w->mutex);
  435. Decoder *last = w->dec;
  436. w->dec = NULL;
  437. w->connected = 0;
  438. pthread_mutex_unlock(&w->mutex);
  439. decoder_destroy(last);
  440. printf("[stream %d] Exiting\n", w->stream_id);
  441. return NULL;
  442. }
  443. /* Parse one layout row into per-cell region labels.
  444. * - decimal number (0..25) -> that region/camera id
  445. * - single letter A-Z -> alias for region id (A=0, B=1, ... Z=25)
  446. * - x / X -> empty cell (-1)
  447. * Cells sharing the same label are merged into one on-screen region. */
  448. static int parse_grid_row(char *s, int *out, int max) {
  449. int nv = 0;
  450. while (*s) {
  451. while (*s == ' ' || *s == '\t') s++;
  452. if (*s == 'x' || *s == 'X') {
  453. out[nv++] = -1; /* empty cell */
  454. s++;
  455. } else if (*s >= '0' && *s <= '9') {
  456. out[nv++] = atoi(s);
  457. while (*s >= '0' && *s <= '9') s++;
  458. } else if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z')) {
  459. int c = *s;
  460. out[nv++] = (c >= 'a' && c <= 'z') ? (c - 'a') : (c - 'A');
  461. s++;
  462. } else {
  463. break;
  464. }
  465. if (nv >= max) break;
  466. }
  467. return nv;
  468. }
  469. static int parse_config(const char *path, int *p_w, int *p_h,
  470. int *p_nstreams, int *p_ntiles, int *tilemap) {
  471. FILE *fp = fopen(path, "r");
  472. if (!fp) return -1;
  473. char line[1024];
  474. int section = 0;
  475. int nstreams = 0, ntiles = 0, ncols = 0;
  476. memset(&g_title, 0, sizeof(g_title));
  477. g_plc_cfg.tsap_local = -1;
  478. g_plc_cfg.tsap_remote = -1;
  479. g_title.enable = 0;
  480. g_title.height = 80;
  481. g_title.font_size = 48;
  482. g_title.color = 0xFFFFFF;
  483. g_title.logo_pad = 8;
  484. g_title.opacity = 255;
  485. strncpy(g_title.font, title_default_font(), sizeof(g_title.font) - 1);
  486. g_flog_path[0] = '\0';
  487. g_flog_rotate_ms = 3600000;
  488. g_flog_max_bytes = 1 << 20;
  489. while (fgets(line, sizeof(line), fp)) {
  490. char *s = line;
  491. while (*s == ' ' || *s == '\t') s++;
  492. if (*s == '#' || *s == '\n' || *s == '\r') continue;
  493. if (s[0] == '[') {
  494. if (strncmp(s, "[output]", 8) == 0) section = 1;
  495. else if (strncmp(s, "[streams]", 9) == 0) section = 2;
  496. else if (strncmp(s, "[main_streams]", 14) == 0) section = 11;
  497. else if (strncmp(s, "[camera_list]", 13) == 0) section = 12;
  498. else if (strncmp(s, "[isapi_write]", 13) == 0) section = 13;
  499. else if (strncmp(s, "[plc]", 5) == 0) section = 5;
  500. else if (strncmp(s, "[title]", 7) == 0) section = 9;
  501. else if (strncmp(s, "[log]", 5) == 0) section = 10;
  502. else if (strncmp(s, "[plc_rules]", 11) == 0) section = 7;
  503. else if (strncmp(s, "[auto_reboot]", 13) == 0) section = 17;
  504. else if (strncmp(s, "[layout", 7) == 0) {
  505. if (s[7] == ']') {
  506. section = 3; /* [layout] base grid */
  507. } else if (s[7] >= '0' && s[7] <= '9') {
  508. section = 8; /* [layoutN] named grid */
  509. if (g_num_layouts < MAX_LAYOUTS) {
  510. LayoutDef *L = &g_layouts[g_num_layouts];
  511. memset(L, 0, sizeof(*L));
  512. L->id = atoi(s + 7);
  513. g_num_layouts++;
  514. }
  515. } else if (strncmp(s + 7, "_top]", 5) == 0) {
  516. section = 14; /* [layout_top] */
  517. } else if (strncmp(s + 7, "_bottom", 7) == 0) {
  518. if (s[14] == '_' && s[15] >= '0' && s[15] <= '9') {
  519. section = 15; /* [layout_bottom_N] */
  520. g_split_bottom_count = atoi(s + 15);
  521. } else {
  522. section = 16; /* [layout_bottom] default row */
  523. }
  524. } else {
  525. section = 0;
  526. }
  527. } else {
  528. section = 0;
  529. }
  530. continue;
  531. }
  532. if (section == 1) {
  533. if (strchr(s, '=')) {
  534. char key[64], val[64];
  535. if (sscanf(s, "%63s = %63s", key, val) >= 2) {
  536. if (strcmp(key, "letterbox") == 0) g_letterbox = atoi(val) != 0;
  537. else if (strcmp(key, "no_signal_text") == 0) g_no_signal_text = atoi(val) != 0;
  538. else if (strcmp(key, "blank_on_missing") == 0) g_blank_on_missing = atoi(val) != 0;
  539. else if (strcmp(key, "grid_lines") == 0) g_grid_lines = atoi(val) != 0;
  540. }
  541. } else {
  542. sscanf(s, "%d %d", p_w, p_h);
  543. }
  544. } else if (section == 2) {
  545. if (strncmp(s, "reconnect_ms", 12) == 0 && strchr(s, '=')) {
  546. g_reconnect_ms = atoi(strchr(s, '=') + 1);
  547. } else if (strncmp(s, "reconnect_max_ms", 16) == 0 && strchr(s, '=')) {
  548. g_reconnect_max_ms = atoi(strchr(s, '=') + 1);
  549. } else if (strncmp(s, "connect_timeout_sec", 19) == 0 && strchr(s, '=')) {
  550. g_connect_timeout_sec = atoi(strchr(s, '=') + 1);
  551. } else {
  552. if (nstreams >= MAX_STREAMS) continue;
  553. char *url = strchr(s, ' ');
  554. if (!url) url = strchr(s, '\t');
  555. if (!url) { url = s; } else { url++; }
  556. while (*url == ' ' || *url == '\t') url++;
  557. size_t len = strlen(url);
  558. while (len > 0 && (url[len-1] == '\n' || url[len-1] == '\r')) url[--len] = 0;
  559. if (len > 0) {
  560. size_t cp = len < sizeof(workers[nstreams].url) - 1 ?
  561. len : sizeof(workers[nstreams].url) - 1;
  562. memcpy(workers[nstreams].url, url, cp);
  563. workers[nstreams].url[cp] = '\0';
  564. nstreams++;
  565. }
  566. }
  567. } else if (section == 3) {
  568. /* Check for key=value (split mode) */
  569. if (strchr(s, '=')) {
  570. char key[64], val[64];
  571. if (sscanf(s, "%63s = %63s", key, val) >= 2) {
  572. if (strcmp(key, "split") == 0) g_split_enabled = atoi(val) != 0;
  573. else if (strcmp(key, "top_ratio") == 0) g_split_top_ratio = atof(val);
  574. }
  575. } else {
  576. int vals[32];
  577. int nv = parse_grid_row(s, vals, 32);
  578. if (nv > 0) {
  579. if (ncols == 0) ncols = nv;
  580. if (g_grid_nrows < MAX_TILES) {
  581. g_grid_row_cols[g_grid_nrows++] = nv;
  582. }
  583. for (int j = 0; j < nv && ntiles < MAX_TILES; j++)
  584. tilemap[ntiles++] = vals[j];
  585. }
  586. }
  587. } else if (section == 14) {
  588. /* [layout_top]: space-separated camera IDs for top row */
  589. int id;
  590. const char *p = s;
  591. while (*p && g_split_top_n < MAX_TILES) {
  592. while (*p == ' ' || *p == '\t') p++;
  593. if (!*p || *p == '\n' || *p == '\r' || *p == '#') break;
  594. if (sscanf(p, "%d", &id) == 1) {
  595. g_split_top_ids[g_split_top_n++] = id;
  596. while (*p && *p != ' ' && *p != '\t') p++;
  597. } else break;
  598. }
  599. } else if (section == 15) {
  600. /* [layout_bottom_N]: space-separated camera IDs for bottom row */
  601. int id;
  602. const char *p = s;
  603. int cnt = 0;
  604. while (*p && cnt < MAX_TILES) {
  605. while (*p == ' ' || *p == '\t') p++;
  606. if (!*p || *p == '\n' || *p == '\r' || *p == '#') break;
  607. if (sscanf(p, "%d", &id) == 1) {
  608. if (g_split_bottom_count >= 1 && g_split_bottom_count <= SPLIT_MAX_BOTTOM)
  609. g_split_bottom_ids[g_split_bottom_count - 1][cnt] = id;
  610. cnt++;
  611. while (*p && *p != ' ' && *p != '\t') p++;
  612. } else break;
  613. }
  614. if (g_split_bottom_count >= 1 && g_split_bottom_count <= SPLIT_MAX_BOTTOM) {
  615. g_split_bottom_valid[g_split_bottom_count - 1] = 1;
  616. g_split_bottom_cnt[g_split_bottom_count - 1] = cnt;
  617. fprintf(stderr, "DBG: [layout_bottom_%d] cnt=%d ids=%d %d %d\n",
  618. g_split_bottom_count, cnt,
  619. g_split_bottom_ids[g_split_bottom_count-1][0],
  620. g_split_bottom_ids[g_split_bottom_count-1][1],
  621. g_split_bottom_ids[g_split_bottom_count-1][2]);
  622. }
  623. } else if (section == 16) {
  624. /* [layout_bottom]: default bottom row used when no PLC rule matches */
  625. int id;
  626. const char *p = s;
  627. int cnt = 0;
  628. while (*p && cnt < MAX_TILES) {
  629. while (*p == ' ' || *p == '\t') p++;
  630. if (!*p || *p == '\n' || *p == '\r' || *p == '#') break;
  631. if (sscanf(p, "%d", &id) == 1) {
  632. g_split_bottom_def_ids[cnt++] = id;
  633. while (*p && *p != ' ' && *p != '\t') p++;
  634. } else break;
  635. }
  636. if (cnt > 0) {
  637. g_split_bottom_def_cnt = cnt;
  638. g_split_bottom_def_valid = 1;
  639. fprintf(stderr, "DBG: [layout_bottom] default cnt=%d ids=%d %d %d\n",
  640. cnt, g_split_bottom_def_ids[0],
  641. g_split_bottom_def_ids[1], g_split_bottom_def_ids[2]);
  642. }
  643. } else if (section == 8) {
  644. int vals[32];
  645. int nv = parse_grid_row(s, vals, 32);
  646. if (nv > 0 && g_num_layouts > 0) {
  647. LayoutDef *L = &g_layouts[g_num_layouts - 1];
  648. if (L->nrows < MAX_TILES) L->row_cols[L->nrows++] = nv;
  649. for (int j = 0; j < nv && L->n < MAX_TILES; j++)
  650. L->map[L->n++] = vals[j]; /* -1 = empty slot */
  651. }
  652. } else if (section == 5) {
  653. char key[64], val[256];
  654. if (sscanf(s, "%63s = %255[^\n\r]", key, val) >= 2) {
  655. char *c = strchr(val, ';');
  656. if (!c) c = strchr(val, '#');
  657. if (c) *c = '\0';
  658. int vlen = (int)strlen(val);
  659. while (vlen > 0 && (val[vlen-1] == ' ' || val[vlen-1] == '\t'))
  660. val[--vlen] = '\0';
  661. if (strcmp(key, "enable") == 0) {
  662. g_plc_enabled = atoi(val) != 0;
  663. } else if (strcmp(key, "ip") == 0) {
  664. strncpy(g_plc_cfg.ip, val, sizeof(g_plc_cfg.ip) - 1);
  665. } else if (strcmp(key, "port") == 0) {
  666. g_plc_cfg.port = atoi(val);
  667. } else if (strcmp(key, "area") == 0) {
  668. if (strcasecmp(val, "m") == 0) g_plc_cfg.area = PLC_AREA_M;
  669. else if (strcasecmp(val, "i") == 0) g_plc_cfg.area = PLC_AREA_I;
  670. else if (strcasecmp(val, "q") == 0) g_plc_cfg.area = PLC_AREA_Q;
  671. else if (strcasecmp(val, "db") == 0) g_plc_cfg.area = PLC_AREA_DB;
  672. } else if (strcmp(key, "db") == 0) {
  673. g_plc_cfg.db = atoi(val);
  674. } else if (strcmp(key, "read_start") == 0) {
  675. g_plc_cfg.read_start = atoi(val);
  676. } else if (strcmp(key, "read_len") == 0) {
  677. g_plc_cfg.read_len = atoi(val);
  678. } else if (strcmp(key, "poll_ms") == 0) {
  679. g_plc_cfg.poll_ms = atoi(val);
  680. } else if (strcmp(key, "reconnect_ms") == 0) {
  681. g_plc_cfg.reconnect_ms = atoi(val);
  682. } else if (strcmp(key, "hold_layout_sec") == 0) {
  683. g_hold_layout_sec = atoi(val);
  684. } else if (strcmp(key, "debounce_ms") == 0) {
  685. g_layout_debounce_ms = atoi(val);
  686. } else if (strcmp(key, "tsap_local") == 0) {
  687. g_plc_cfg.tsap_local = (int)strtol(val, NULL, 0);
  688. } else if (strcmp(key, "tsap_remote") == 0) {
  689. g_plc_cfg.tsap_remote = (int)strtol(val, NULL, 0);
  690. }
  691. }
  692. } else if (section == 7) {
  693. /* <layout_id> : <byte>.<bit>[,<byte>.<bit>...] [';' ...] [: all]
  694. * ';' separates groups; every group must be satisfied (AND across
  695. * groups), any single bit inside a group is enough (OR within a
  696. * group); ':' suffix "all" also requires every bit of a group. */
  697. if (g_num_rules >= MAX_RULES) continue;
  698. int lid;
  699. char *colon = strchr(s, ':');
  700. if (!colon) continue;
  701. if (sscanf(s, "%d", &lid) != 1) continue;
  702. PlcRule *R = &g_rules[g_num_rules];
  703. memset(R, 0, sizeof(*R));
  704. char *tok = colon + 1;
  705. int cur_group = 0;
  706. while (tok && *tok && R->nbits < MAX_RULE_BITS) {
  707. while (*tok == ' ' || *tok == '\t' || *tok == ',' || *tok == ';') {
  708. if (*tok == ';') cur_group++;
  709. tok++;
  710. }
  711. if (*tok == ':' || *tok == '\0' ||
  712. *tok == '\n' || *tok == '\r') break;
  713. int by, bi = 0;
  714. if (sscanf(tok, "%d.%d", &by, &bi) == 2) {
  715. R->bits[R->nbits].byte_idx = by;
  716. R->bits[R->nbits].bit = bi;
  717. } else if (sscanf(tok, "%d", &by) == 1) {
  718. R->bits[R->nbits].byte_idx = by;
  719. R->bits[R->nbits].bit = 0;
  720. } else {
  721. break;
  722. }
  723. R->group[R->nbits] = cur_group;
  724. R->nbits++;
  725. while (*tok && *tok != ',' && *tok != ';' && *tok != ':')
  726. tok++;
  727. }
  728. R->ngroups = cur_group + 1;
  729. char *mode = strrchr(s, ':');
  730. if (mode && strstr(mode, "all")) R->all = 1;
  731. int li = -1;
  732. for (int i = 0; i < g_num_layouts; i++)
  733. if (g_layouts[i].id == lid) { li = i; break; }
  734. if (li < 0) continue;
  735. R->layout = li;
  736. if (R->nbits > 0) g_num_rules++;
  737. } else if (section == 9) {
  738. char key[64], val[512];
  739. if (sscanf(s, "%63s = %511[^\n\r]", key, val) >= 2) {
  740. int vlen = (int)strlen(val);
  741. while (vlen > 0 && (val[vlen-1] == ' ' || val[vlen-1] == '\t'
  742. || val[vlen-1] == '\r' || val[vlen-1] == '\n'))
  743. val[--vlen] = '\0';
  744. char *lead = val;
  745. while (*lead == ' ' || *lead == '\t') lead++;
  746. if (strcmp(key, "enable") == 0) {
  747. g_title.enable = atoi(lead) != 0;
  748. } else if (strcmp(key, "type") == 0) {
  749. if (strncmp(lead, "overlay", 7) == 0 ||
  750. atoi(lead) != 0) g_title.type = 1;
  751. else g_title.type = 0;
  752. } else if (strcmp(key, "height") == 0) {
  753. g_title.height = atoi(lead);
  754. } else if (strcmp(key, "text") == 0) {
  755. strncpy(g_title.text, lead, sizeof(g_title.text) - 1);
  756. } else if (strcmp(key, "logo") == 0) {
  757. strncpy(g_title.logo, lead, sizeof(g_title.logo) - 1);
  758. } else if (strcmp(key, "logo_pad") == 0) {
  759. g_title.logo_pad = atoi(lead);
  760. } else if (strcmp(key, "opacity") == 0) {
  761. g_title.opacity = atoi(lead);
  762. } else if (strcmp(key, "font") == 0) {
  763. strncpy(g_title.font, lead, sizeof(g_title.font) - 1);
  764. } else if (strcmp(key, "font_size") == 0) {
  765. g_title.font_size = atoi(lead);
  766. } else if (strcmp(key, "color") == 0) {
  767. g_title.color = (unsigned)strtoul(lead, NULL, 0);
  768. }
  769. }
  770. } else if (section == 10) {
  771. char key[64], val[512];
  772. if (sscanf(s, "%63s = %511[^\n\r]", key, val) >= 2) {
  773. int vlen = (int)strlen(val);
  774. while (vlen > 0 && (val[vlen-1] == ' ' || val[vlen-1] == '\t'
  775. || val[vlen-1] == '\r' || val[vlen-1] == '\n'))
  776. val[--vlen] = '\0';
  777. char *lead = val;
  778. while (*lead == ' ' || *lead == '\t') lead++;
  779. if (strcmp(key, "file") == 0) {
  780. strncpy(g_flog_path, lead, sizeof(g_flog_path) - 1);
  781. } else if (strcmp(key, "rotate_ms") == 0) {
  782. g_flog_rotate_ms = atol(lead);
  783. } else if (strcmp(key, "max_bytes") == 0) {
  784. g_flog_max_bytes = atol(lead);
  785. }
  786. }
  787. } else if (section == 17) {
  788. char key[64], val[64];
  789. if (sscanf(s, "%63s = %63s", key, val) >= 2) {
  790. if (strcmp(key, "enable") == 0) {
  791. g_reboot_enable = atoi(val) != 0;
  792. } else if (strcmp(key, "period") == 0) {
  793. g_reboot_period = (strcasecmp(val, "weekly") == 0) ? 1 : 0;
  794. } else if (strcmp(key, "time") == 0) {
  795. int hh = 3, mm = 0;
  796. if (sscanf(val, "%d:%d", &hh, &mm) >= 1) {
  797. g_reboot_hour = (hh >= 0 && hh <= 23) ? hh : 3;
  798. g_reboot_min = (mm >= 0 && mm <= 59) ? mm : 0;
  799. }
  800. } else if (strcmp(key, "weekday") == 0) {
  801. const char *dn[] = {"sunday", "monday", "tuesday",
  802. "wednesday", "thursday", "friday", "saturday"};
  803. g_reboot_wday = -1;
  804. for (int i = 0; i < 7; i++)
  805. if (strcasecmp(val, dn[i]) == 0) g_reboot_wday = i;
  806. }
  807. }
  808. } else if (section == 11) {
  809. /* <index> <main-stream url> ; used only by single-channel layouts */
  810. int idx;
  811. char *url = strchr(s, ' ');
  812. if (!url) url = strchr(s, '\t');
  813. if (!url) continue;
  814. *url++ = '\0';
  815. idx = atoi(s);
  816. while (*url == ' ' || *url == '\t') url++;
  817. size_t len = strlen(url);
  818. while (len > 0 && (url[len-1] == '\n' || url[len-1] == '\r')) url[--len] = 0;
  819. if (idx >= 0 && idx < MAX_STREAMS && len > 0) {
  820. size_t cp = len < sizeof(workers[idx].url_main) - 1 ?
  821. len : sizeof(workers[idx].url_main) - 1;
  822. memcpy(workers[idx].url_main, url, cp);
  823. workers[idx].url_main[cp] = '\0';
  824. }
  825. } else if (section == 12) {
  826. /* [camera_list] <id> <ip> <user> <pass> [login_chan] [rtsp_port] [http_port] */
  827. int id, chan = 1, port = 554, http_port = 80;
  828. char ip[64], user[64], pass[64];
  829. int nf = sscanf(s, "%d %63s %63s %63s %d %d %d",
  830. &id, ip, user, pass, &chan, &port, &http_port);
  831. if (nf >= 4 && id >= 0 && id < MAX_STREAMS) {
  832. HvCam *c = &g_cams[id];
  833. memset(c, 0, sizeof(*c));
  834. c->id = id;
  835. strncpy(c->ip, ip, sizeof(c->ip) - 1);
  836. strncpy(c->user, user, sizeof(c->user) - 1);
  837. strncpy(c->pass, pass, sizeof(c->pass) - 1);
  838. c->login_chan = (nf >= 5 && chan > 0) ? chan : 1;
  839. c->port = (nf >= 6 && port > 0) ? port : 554;
  840. c->http_port = (nf >= 7 && http_port > 0) ? http_port : 80;
  841. if (id + 1 > g_num_cams) g_num_cams = id + 1;
  842. hv_build_urls(c, &g_urls[id]);
  843. }
  844. } else if (section == 13) {
  845. /* [isapi_write] <id> <main|sub> <width> <height> <fps> <bitrate_kbps>
  846. * 可选:启动时通过 ISAPI 把指定通道改成给定分辨率/帧率/码率。 */
  847. char stream[16];
  848. int id, w = 0, h = 0, fps = 0, br = 0;
  849. int nf = sscanf(s, "%d %15s %d %d %d %d",
  850. &id, stream, &w, &h, &fps, &br);
  851. if (nf == 6 && id >= 0 && id < MAX_STREAMS &&
  852. g_isapi_write_count < MAX_ISAPI_WRITE) {
  853. IsapiWrite *r = &g_isapi_write[g_isapi_write_count];
  854. r->id = id;
  855. r->main = (strcmp(stream, "main") == 0);
  856. r->width = w; r->height = h; r->fps = fps; r->bitrate = br;
  857. g_isapi_write_count++;
  858. }
  859. }
  860. }
  861. fclose(fp);
  862. if (g_num_cams > 0) {
  863. /* A camera list entirely replaces the manual streams: one worker per
  864. * camera, using ISAPI-derived sub/main RTSP URLs. */
  865. nstreams = g_num_cams;
  866. for (int i = 0; i < nstreams && i < MAX_STREAMS; i++) {
  867. strncpy(workers[i].url, g_urls[i].sub, sizeof(workers[i].url) - 1);
  868. strncpy(workers[i].url_main, g_urls[i].main,
  869. sizeof(workers[i].url_main) - 1);
  870. }
  871. }
  872. if (nstreams == 0) return -1;
  873. if (ntiles == 0) {
  874. ntiles = nstreams;
  875. for (int i = 0; i < ntiles; i++) tilemap[i] = i;
  876. }
  877. if (g_reconnect_ms < 100) g_reconnect_ms = 100;
  878. if (g_reconnect_max_ms < g_reconnect_ms) g_reconnect_max_ms = g_reconnect_ms;
  879. if (g_connect_timeout_sec < 1) g_connect_timeout_sec = 1;
  880. if (g_hold_layout_sec < 0) g_hold_layout_sec = 0;
  881. if (g_layout_debounce_ms < 0) g_layout_debounce_ms = 0;
  882. *p_nstreams = nstreams;
  883. *p_ntiles = ntiles;
  884. return 0;
  885. }
  886. /* HikVision ISAPI startup: optional parameter writes first, then read-and-log
  887. * the encoding params of every configured camera. Each request can block up
  888. * to 8 s, so this runs on a detached background thread and must not delay
  889. * video startup. Uses g_cams/g_urls which are only written during
  890. * parse_config (before threads start). */
  891. static void *isapi_startup_thread(void *arg) {
  892. (void)arg;
  893. for (int w = 0; w < g_isapi_write_count; w++) {
  894. IsapiWrite *r = &g_isapi_write[w];
  895. if (r->id < 0 || r->id >= g_num_cams) continue;
  896. int enc = (g_cams[r->id].login_chan * 100) + (r->main ? 1 : 2);
  897. /* Hikvision stores frame rate x100 (e.g. 25fps -> 2500). */
  898. hv_write_params(&g_cams[r->id], enc, r->width, r->height,
  899. r->fps * 100, r->bitrate);
  900. }
  901. for (int i = 0; i < g_num_cams; i++) {
  902. if (!g_running) break;
  903. hv_read_and_log(&g_cams[i], g_cams[i].login_chan * 100 + 1);
  904. hv_read_and_log(&g_cams[i], g_cams[i].login_chan * 100 + 2);
  905. }
  906. return NULL;
  907. }
  908. /* Scheduled reboot ([auto_reboot]): wake up exactly at HH:MM (local time),
  909. * daily or on the configured weekday, then reboot the device once. The reboot
  910. * is executed by a forked child so a hang in the reboot path can't wedge the
  911. * running application. */
  912. static void reboot_now(void) {
  913. fprintf(stderr, "DBG: auto reboot triggered at %ld\n", (long)time(NULL));
  914. sync();
  915. pid_t pid = fork();
  916. if (pid == 0) {
  917. setsid();
  918. sleep(2);
  919. execl("/sbin/reboot", "reboot", (char *)NULL);
  920. _exit(127);
  921. }
  922. }
  923. static void *reboot_thread(void *arg) {
  924. (void)arg;
  925. while (1) {
  926. time_t now = time(NULL);
  927. struct tm t;
  928. localtime_r(&now, &t);
  929. struct tm cand = t;
  930. time_t next = 0;
  931. for (int guard = 0; guard < 10; guard++) {
  932. struct tm c = cand;
  933. c.tm_sec = 0;
  934. c.tm_min = g_reboot_min;
  935. c.tm_hour = g_reboot_hour;
  936. if (g_reboot_period == 1 && g_reboot_wday >= 0) {
  937. time_t w = mktime(&c);
  938. struct tm m;
  939. localtime_r(&w, &m);
  940. if (m.tm_wday != g_reboot_wday) { cand.tm_mday++; continue; }
  941. }
  942. next = mktime(&c);
  943. if (next > now) break;
  944. cand.tm_mday++;
  945. }
  946. long dt = (long)(next - now);
  947. if (dt < 15 || dt > 2 * 24 * 3600) dt = 24 * 3600;
  948. struct timespec ts;
  949. ts.tv_sec = dt;
  950. ts.tv_nsec = 0;
  951. nanosleep(&ts, NULL);
  952. if (g_reboot_enable) reboot_now();
  953. }
  954. return NULL;
  955. }
  956. static void display_rebuild_now(void) {
  957. fprintf(stderr, "display: rebuilding output\n");
  958. if (g_display) display_destroy(g_display);
  959. for (int tries = 0; tries < 10 && g_running; tries++) {
  960. g_display = display_create(NULL);
  961. if (!g_display) {
  962. fprintf(stderr, "display: recreate failed, retrying in 1s\n");
  963. sleep(1);
  964. continue;
  965. }
  966. usleep(500000);
  967. /* Ask for the size our existing output buffers were allocated at, so
  968. * display_init prefers a matching connector mode. Without this a
  969. * monitor hot-plug can re-enumerate a different preferred mode and the
  970. * scanout buffer no longer matches the CRTC (drmModeAddFB2 EINVAL
  971. * rebuild loop). */
  972. if (display_init(g_display, output_w, output_h) == 0) {
  973. fprintf(stderr, "display: rebuilt OK\n");
  974. return;
  975. }
  976. fprintf(stderr, "display: init deferred (no monitor), retrying in 1s\n");
  977. display_destroy(g_display);
  978. g_display = NULL;
  979. sleep(1);
  980. }
  981. fprintf(stderr, "display: rebuild deferred, will retry later\n");
  982. }
  983. int main(int argc, char **argv) {
  984. fprintf(stderr, "DBG: starting\n");
  985. install_signal_handlers();
  986. const char *config_path = NULL;
  987. if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
  988. fprintf(stderr, "DBG: parsing config\n");
  989. config_path = argv[2];
  990. if (parse_config(config_path, &output_w, &output_h,
  991. &num_streams, &num_tiles, tile_map) < 0) {
  992. fprintf(stderr, "Failed to parse config: %s\n", config_path);
  993. return 1;
  994. }
  995. fprintf(stderr, "DBG: parsed: %dx%d %d streams %d tiles\n",
  996. output_w, output_h, num_streams, num_tiles);
  997. if (flog_init(g_flog_path, g_flog_rotate_ms, g_flog_max_bytes) < 0)
  998. fprintf(stderr, "WARN: fault log disabled (%s)\n", g_flog_path);
  999. /* ISAPI read/write on a background thread: video must not wait on
  1000. * camera HTTP round-trips. */
  1001. if (g_num_cams > 0) {
  1002. pthread_t isapi_thr;
  1003. if (pthread_create(&isapi_thr, NULL, isapi_startup_thread, NULL) == 0)
  1004. pthread_detach(isapi_thr);
  1005. }
  1006. } else if (argc >= 3) {
  1007. output_w = atoi(argv[1]);
  1008. output_h = atoi(argv[2]);
  1009. num_streams = argc - 3;
  1010. if (num_streams > MAX_STREAMS) num_streams = MAX_STREAMS;
  1011. if (num_streams < 1) { printf("At least 1 RTSP URL required\n"); return 1; }
  1012. num_tiles = num_streams;
  1013. for (int i = 0; i < num_tiles; i++) {
  1014. tile_map[i] = i;
  1015. strncpy(workers[i].url, argv[3 + i], sizeof(workers[i].url) - 1);
  1016. }
  1017. } else {
  1018. printf("Usage 1: %s <width> <height> <url1> [url2...]\n", argv[0]);
  1019. printf("Usage 2: %s -c <config.conf>\n", argv[0]);
  1020. printf("Config sections: [output], [streams], [main_streams], [camera_list],\n");
  1021. printf(" [isapi_write], [layout], [layoutN], [plc], [plc_rules], [title], [log]\n");
  1022. return 1;
  1023. }
  1024. if (g_plc_enabled && g_plc_cfg.ip[0]) {
  1025. if (g_plc_cfg.port <= 0) g_plc_cfg.port = 102;
  1026. if (g_plc_cfg.area == 0) g_plc_cfg.area = PLC_AREA_M;
  1027. if (g_plc_cfg.read_len <= 0) g_plc_cfg.read_len = 1;
  1028. if (g_plc_cfg.tsap_local == -1) g_plc_cfg.tsap_local = 0x0100;
  1029. if (g_plc_cfg.tsap_remote == -1) g_plc_cfg.tsap_remote = 0x0102;
  1030. if (plc_init(&g_plc_cfg) < 0) {
  1031. fprintf(stderr, "WARN: plc_init failed (%s)\n", plc_last_error());
  1032. g_plc_enabled = 0;
  1033. } else {
  1034. fprintf(stderr, "DBG: plc enabled %s:%d area=0x%02x db=%d start=%d len=%d\n",
  1035. g_plc_cfg.ip, g_plc_cfg.port, g_plc_cfg.area, g_plc_cfg.db,
  1036. g_plc_cfg.read_start, g_plc_cfg.read_len);
  1037. }
  1038. }
  1039. /* Start embedded web config server */
  1040. if (config_path) {
  1041. web_server_start(config_path, WEB_SERVER_PORT_DEFAULT,
  1042. WEB_SERVER_PASS_DEFAULT);
  1043. }
  1044. if (g_reboot_enable) {
  1045. pthread_t rbt;
  1046. if (pthread_create(&rbt, NULL, reboot_thread, NULL) == 0) {
  1047. pthread_detach(rbt);
  1048. fprintf(stderr, "DBG: auto reboot on, %02d:%02d %s\n",
  1049. g_reboot_hour, g_reboot_min,
  1050. g_reboot_period == 1 ? "weekly" : "daily");
  1051. }
  1052. }
  1053. fprintf(stderr, "DBG: output %dx%d %d streams %d tiles\n",
  1054. output_w, output_h, num_streams, num_tiles);
  1055. /* Force the HDMI output to RGB: after a hotplug the VOP's YUV passthrough
  1056. * path renders with wrong colours (purple wall); RGB keeps the window on
  1057. * a full Y2R CSC path and the monitor locked to a deterministic format. */
  1058. display_set_color_format(1);
  1059. g_display = display_create(NULL);
  1060. if (!g_display) { fprintf(stderr, "Failed to create display\n"); return 1; }
  1061. /* If the board powers up without a monitor, the DRM connector has no mode
  1062. * and display_init fails ("display: no monitor ... retry"). Retry once per
  1063. * second so the video appears automatically as soon as a monitor is
  1064. * plugged in; without this the desktop stays up forever. */
  1065. while (g_running && display_init(g_display, 0, 0) < 0) {
  1066. fprintf(stderr, "display: init deferred (no monitor connected), retrying in 1s\n");
  1067. display_destroy(g_display);
  1068. g_display = display_create(NULL);
  1069. if (!g_display) { fprintf(stderr, "Failed to create display\n"); return 1; }
  1070. sleep(1);
  1071. }
  1072. if (!g_running) { /* stop requested while waiting for a monitor */
  1073. display_destroy(g_display);
  1074. return 0;
  1075. }
  1076. output_w = display_get_width(g_display);
  1077. output_h = display_get_height(g_display);
  1078. fprintf(stderr, "DBG: display resolution %dx%d\n", output_w, output_h);
  1079. /* Title bar: enabled only if configured and height fits the output.
  1080. * bar = solid bar, reserves g_title_h rows (video area shifted down);
  1081. * overlay = translucent strip drawn on top of the video, no space taken. */
  1082. g_title_h = 0;
  1083. g_title_overlay = (g_title.enable && g_title.type != 0);
  1084. if (g_title.enable && g_title.height > 0 && g_title.height < output_h) {
  1085. if (!g_title_overlay) {
  1086. g_title_h = g_title.height;
  1087. if (g_title_h & 1) g_title_h--; /* keep UV rows aligned */
  1088. if (g_title_h < 2) g_title_h = 0;
  1089. }
  1090. }
  1091. output_stride = output_w;
  1092. output_size = output_w * output_h * 3 / 2;
  1093. for (int i = 0; i < 3; i++) {
  1094. /* Output buffers feed RGA as dmabuf fds. The RGA driver refuses any
  1095. * buffer whose physical pages sit above 4G ("RGA_MMU unsupported
  1096. * memory larger than 4G"). The board maps part of DDR above 4G and
  1097. * CMA is only 16MB, so prefer the dma32 heap (guaranteed <4G);
  1098. * cma is the next fallback (also <4G), plain memory last. */
  1099. if (dma_heap_alloc("system-uncached-dma32", output_size, &output_fd[i]) < 0) {
  1100. if (dma_heap_alloc("cma", output_size, &output_fd[i]) < 0) {
  1101. if (dma_heap_alloc("system-uncached", output_size, &output_fd[i]) < 0) {
  1102. fprintf(stderr, "Failed to allocate DMA buffer %d\n", i);
  1103. return 1;
  1104. }
  1105. }
  1106. }
  1107. output_map[i] = mmap(NULL, output_size, PROT_READ | PROT_WRITE,
  1108. MAP_SHARED, output_fd[i], 0);
  1109. if (output_map[i] == MAP_FAILED) {
  1110. fprintf(stderr, "mmap failed for output buffer %d\n", i);
  1111. return 1;
  1112. }
  1113. memset(output_map[i], 0x80, output_size);
  1114. memset(output_map[i], 0x10, output_w * output_h);
  1115. }
  1116. /* Render the title bar once into all buffers (runtime cost = 0).
  1117. * In overlay mode this covers only the initial frame; the main loop
  1118. * re-draws it on top of every composed frame. */
  1119. if (g_title_h > 0 || g_title_overlay) {
  1120. int ok = 1;
  1121. for (int i = 0; i < 3; i++) {
  1122. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE };
  1123. ioctl(output_fd[i], DMA_BUF_IOCTL_SYNC, &sync);
  1124. if (g_title_overlay) {
  1125. if (title_render_overlay(output_map[i], output_w, output_h, &g_title) != 0)
  1126. ok = 0;
  1127. } else {
  1128. if (title_render(output_map[i], output_w, output_h, &g_title) != 0)
  1129. ok = 0;
  1130. }
  1131. struct dma_buf_sync sync2 = { .flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE };
  1132. ioctl(output_fd[i], DMA_BUF_IOCTL_SYNC, &sync2);
  1133. }
  1134. if (ok) {
  1135. g_title_enabled = 1;
  1136. fprintf(stderr, "DBG: title %s %dx%d enabled\n",
  1137. g_title_overlay ? "overlay" : "bar", output_w,
  1138. g_title_overlay ? g_title.height : g_title_h);
  1139. } else {
  1140. /* fall back to no title bar: clear bar area and drop offset */
  1141. fprintf(stderr, "WARN: title render failed, disabling title bar\n");
  1142. g_title_h = 0;
  1143. g_title_overlay = 0;
  1144. for (int i = 0; i < 3; i++) {
  1145. memset(output_map[i], 0x80, output_size);
  1146. memset(output_map[i], 0x10, output_w * output_h);
  1147. }
  1148. }
  1149. } else {
  1150. fprintf(stderr, "DBG: title bar disabled\n");
  1151. }
  1152. g_composer = composer_create(output_w, output_h, num_tiles, g_title_h);
  1153. if (!g_composer) {
  1154. fprintf(stderr, "Failed to create composer\n");
  1155. display_destroy(g_display);
  1156. return 1;
  1157. }
  1158. composer_set_letterbox(g_composer, g_letterbox);
  1159. /* Layout the base grid. If the config gave row counts (via [layout]),
  1160. * compile it into merged regions; otherwise use a uniform grid. */
  1161. int flow_ok = 0;
  1162. if (g_split_enabled && g_split_top_n > 0) {
  1163. /* Split mode: start with the [layout_bottom] default group, fallback
  1164. * to the highest valid [layout_bottom_N]. */
  1165. int bottom_n = split_idle_bottom();
  1166. int *bid = NULL;
  1167. int bottom_cnt = split_bottom_cfg(bottom_n, &bid);
  1168. if (bottom_cnt < 1) bottom_cnt = 1;
  1169. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES], sid_arr[MAX_TILES];
  1170. int n = composer_split_rects(
  1171. output_w, output_h, g_title_h,
  1172. g_split_top_ids, g_split_top_n, g_split_top_ratio,
  1173. bid, bottom_cnt, bottom_cnt,
  1174. xs, ys, ws, hs, sid_arr);
  1175. if (n > 0) {
  1176. composer_set_tiles(g_composer, xs, ys, ws, hs, n);
  1177. g_disp_tiles = n;
  1178. for (int t = 0; t < n; t++)
  1179. g_disp_sid[t] = (sid_arr[t] >= 0 && sid_arr[t] < MAX_STREAMS) ? sid_arr[t] : -1;
  1180. flow_ok = 1;
  1181. fprintf(stderr, "DBG: split mode init top=%d bottom_idle=%d bottom_cnt=%d ratio=%.1f\n",
  1182. g_split_top_n, bottom_n, bottom_cnt, g_split_top_ratio);
  1183. }
  1184. }
  1185. if (!flow_ok && g_grid_nrows > 0) {
  1186. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES], idx[MAX_TILES];
  1187. int n = composer_region_rects(output_w, output_h, g_grid_row_cols,
  1188. g_grid_nrows, tile_map,
  1189. xs, ys, ws, hs, idx, g_title_h);
  1190. if (n > 0) {
  1191. composer_set_tiles(g_composer, xs, ys, ws, hs, n);
  1192. g_disp_tiles = n;
  1193. for (int t = 0; t < n; t++)
  1194. g_disp_sid[t] = (idx[t] >= 0 && idx[t] < MAX_STREAMS) ? idx[t] : -1;
  1195. flow_ok = 1;
  1196. }
  1197. }
  1198. if (!flow_ok) {
  1199. for (int i = 0; i < num_tiles; i++) {
  1200. composer_get_tile_layout(g_composer, i, &g_grid_x[i], &g_grid_y[i],
  1201. &g_grid_w[i], &g_grid_h[i]);
  1202. }
  1203. g_disp_tiles = num_tiles;
  1204. for (int t = 0; t < g_disp_tiles; t++)
  1205. g_disp_sid[t] = (tile_map[t] >= 0 && tile_map[t] < MAX_STREAMS) ? tile_map[t] : -1;
  1206. } else {
  1207. for (int i = 0; i < g_disp_tiles && i < MAX_TILES; i++) {
  1208. int x, y, w, h;
  1209. composer_get_tile_layout(g_composer, i, &x, &y, &w, &h);
  1210. g_grid_x[i] = x; g_grid_y[i] = y; g_grid_w[i] = w; g_grid_h[i] = h;
  1211. }
  1212. }
  1213. fprintf(stderr, "DBG: layouts=%d rules=%d grid%d split=%d\n",
  1214. g_num_layouts, g_num_rules, flow_ok ? g_grid_nrows : -1,
  1215. g_split_enabled);
  1216. output_front = 0;
  1217. output_last = 1; /* so first BACK_BUF() = 2 (not front, not last) */
  1218. if (display_commit(g_display, output_fd[output_front], output_stride) < 0)
  1219. fprintf(stderr, "Warning: initial display_commit failed\n");
  1220. for (int i = 0; i < num_streams; i++) {
  1221. StreamWorker *w = &workers[i];
  1222. w->stream_id = i;
  1223. w->running = 1;
  1224. w->connected = 0;
  1225. w->dec = NULL;
  1226. w->ring_count = 0;
  1227. w->ring_head = 0;
  1228. w->frame_seq = 0;
  1229. w->held_pin = -1;
  1230. w->use_main = 0;
  1231. w->dec_using_main = 0;
  1232. w->last_fd = -1;
  1233. w->last_w = 0; w->last_h = 0; w->last_stride = 0; w->last_vstride = 0;
  1234. w->last_seq_seen = -1;
  1235. w->last_frame_mono = -1e9;
  1236. pthread_mutex_init(&w->mutex, NULL);
  1237. pthread_cond_init(&w->cond, NULL);
  1238. pthread_create(&w->thread, NULL, worker_thread, w);
  1239. }
  1240. int active_streams = 0, connect_wait = 0;
  1241. while (g_running && active_streams == 0 && connect_wait < 200) {
  1242. active_streams = 0;
  1243. for (int i = 0; i < num_streams; i++)
  1244. if (workers[i].connected) active_streams++;
  1245. if (active_streams == 0) { usleep(50000); connect_wait++; }
  1246. }
  1247. if (active_streams != num_streams)
  1248. printf("Proceeding with %d/%d connected streams\n", active_streams, num_streams);
  1249. fprintf(stderr, "DBG: entering main loop\n");
  1250. printf("Entering display loop (%d tiles from %d streams)...\n", num_tiles, num_streams);
  1251. int frame_count = 0;
  1252. struct timeval last_tv;
  1253. gettimeofday(&last_tv, NULL);
  1254. struct timeval comp_stats_last = last_tv;
  1255. int held_idx[MAX_STREAMS];
  1256. int held_valid[MAX_STREAMS];
  1257. memset(held_valid, 0, sizeof(held_valid));
  1258. static int disp_was_lost = -1; /* hotplug edge state */
  1259. static double last_disp_rebuild = -1e9;
  1260. while (g_running) {
  1261. /* Hotplug recovery: when an HDMI switch points this display away our
  1262. * connector reports disconnected; once it points back we must rebuild
  1263. * the DRM output (Xorg/desktop may have taken the CRTC meanwhile), or
  1264. * the screen stays on the desktop. Detect the lost->connected edge
  1265. * and re-create the display. */
  1266. if (g_rebuild_req) {
  1267. g_rebuild_req = 0;
  1268. fprintf(stderr, "display: SIGUSR1 rebuild requested\n");
  1269. display_rebuild_now();
  1270. }
  1271. if (g_display) {
  1272. int lost = display_signal_lost(g_display);
  1273. double now_m = 0;
  1274. struct timespec mt2;
  1275. clock_gettime(CLOCK_MONOTONIC, &mt2);
  1276. now_m = mt2.tv_sec + mt2.tv_nsec / 1000000000.0;
  1277. if (disp_was_lost == 1 && lost == 0 && now_m - last_disp_rebuild > 0.3) {
  1278. last_disp_rebuild = now_m;
  1279. fprintf(stderr, "display: monitor reconnected, rebuilding output\n");
  1280. display_rebuild_now();
  1281. }
  1282. disp_was_lost = lost ? 1 : 0;
  1283. } else {
  1284. /* No live display (rebuild gave up while unplugged): keep retrying
  1285. * so a later plug-in brings the picture back by itself. */
  1286. double now_m = 0;
  1287. struct timespec mt2;
  1288. clock_gettime(CLOCK_MONOTONIC, &mt2);
  1289. now_m = mt2.tv_sec + mt2.tv_nsec / 1000000000.0;
  1290. if (now_m - last_disp_rebuild > 0.2) {
  1291. last_disp_rebuild = now_m;
  1292. display_rebuild_now();
  1293. }
  1294. }
  1295. int src_fds[MAX_STREAMS];
  1296. int src_widths[MAX_STREAMS];
  1297. int src_heights[MAX_STREAMS];
  1298. int src_strides[MAX_STREAMS];
  1299. int src_vstrides[MAX_STREAMS];
  1300. int valid_srcs = 0;
  1301. struct timespec mts;
  1302. clock_gettime(CLOCK_MONOTONIC, &mts);
  1303. double mono_now = mts.tv_sec + mts.tv_nsec / 1000000000.0;
  1304. for (int i = 0; i < num_streams; i++) {
  1305. StreamWorker *w = &workers[i];
  1306. src_fds[i] = -1;
  1307. held_valid[i] = 0;
  1308. pthread_mutex_lock(&w->mutex);
  1309. if (!w->connected) {
  1310. /* Worker is tearing down / reconnecting: drain stale frames. */
  1311. for (int j = 0; j < w->ring_count; j++) {
  1312. int ri = (w->ring_head + j) % 2;
  1313. if (w->ring[ri].idx >= 0 && w->dec)
  1314. decoder_release_frame(w->dec, w->ring[ri].idx);
  1315. if (w->ring[ri].fd >= 0) close(w->ring[ri].fd);
  1316. w->ring[ri].fd = -1;
  1317. w->ring[ri].idx = -1;
  1318. }
  1319. w->ring_count = 0;
  1320. w->held_pin = -1;
  1321. pthread_cond_broadcast(&w->cond);
  1322. pthread_mutex_unlock(&w->mutex);
  1323. continue;
  1324. }
  1325. if (w->ring_count > 0) {
  1326. int latest = (w->ring_head + w->ring_count - 1) % 2;
  1327. src_fds[i] = fcntl(w->ring[latest].fd, F_DUPFD_CLOEXEC, 3);
  1328. src_widths[i] = w->ring[latest].width;
  1329. src_heights[i] = w->ring[latest].height;
  1330. src_strides[i] = w->ring[latest].hor_stride;
  1331. src_vstrides[i] = w->ring[latest].ver_stride;
  1332. held_idx[i] = w->ring[latest].idx;
  1333. /* A new frame arrived (seq changed): timestamp it. If the
  1334. * worker is stuck on a silent stream the same seq keeps being
  1335. * re-acquired, so this stays stale and the tile eventually
  1336. * falls back to NO SIGNAL. */
  1337. if (w->ring[latest].pts != w->last_seq_seen) {
  1338. w->last_seq_seen = w->ring[latest].pts;
  1339. w->last_frame_mono = mono_now;
  1340. /* Refresh the frozen last-frame handle (only on new frames;
  1341. * the dup stays valid even after the decoder is destroyed
  1342. * on a reconnect, so the tile can keep showing this frame
  1343. * during the gap). */
  1344. if (w->last_fd >= 0) close(w->last_fd);
  1345. w->last_fd = fcntl(w->ring[latest].fd, F_DUPFD_CLOEXEC, 3);
  1346. w->last_w = w->ring[latest].width;
  1347. w->last_h = w->ring[latest].height;
  1348. w->last_stride = w->ring[latest].hor_stride;
  1349. w->last_vstride = w->ring[latest].ver_stride;
  1350. }
  1351. /* Release dropped frames (all except latest). */
  1352. for (int j = 0; j < w->ring_count - 1; j++) {
  1353. int ri = (w->ring_head + j) % 2;
  1354. if (w->dec) decoder_release_frame(w->dec, w->ring[ri].idx);
  1355. if (w->ring[ri].fd >= 0) close(w->ring[ri].fd);
  1356. w->ring[ri].fd = -1;
  1357. }
  1358. w->ring_count = 1;
  1359. w->ring_head = latest;
  1360. w->held_pin = held_idx[i];
  1361. held_valid[i] = 1;
  1362. valid_srcs++;
  1363. } else {
  1364. w->held_pin = -1;
  1365. }
  1366. pthread_cond_broadcast(&w->cond);
  1367. pthread_mutex_unlock(&w->mutex);
  1368. }
  1369. /* --- PLC-driven layout selection --- */
  1370. int active_layout = -1; /* -1 = grid ([layout]) */
  1371. int plc_ok = g_plc_enabled && plc_is_connected();
  1372. if (plc_ok) {
  1373. /* Among matching rules, the most specific one wins:
  1374. * more groups first, then more bits, then earlier in file. */
  1375. int best_g = -1, best_b = -1;
  1376. for (int r = 0; r < g_num_rules; r++) {
  1377. PlcRule *R = &g_rules[r];
  1378. /* Default: any bit inside a ';'-group is enough (OR within a
  1379. * group) and every group must be satisfied (AND across
  1380. * groups), e.g. `3 : 6.6,6.7;8.4,8.5` -> (6.6|6.7)&(8.4|8.5).
  1381. * Trailing `: all` also requires every bit inside a group. */
  1382. int match = 1;
  1383. for (int g = 0; g < R->ngroups; g++) {
  1384. int gok = R->all ? 1 : 0;
  1385. for (int b = 0; b < R->nbits; b++) {
  1386. if (R->group[b] != g) continue;
  1387. int v = plc_get_bit(R->bits[b].byte_idx,
  1388. R->bits[b].bit);
  1389. if (R->all) {
  1390. if (v <= 0) { gok = 0; break; }
  1391. } else {
  1392. if (v < 0) continue;
  1393. if (v) { gok = 1; break; }
  1394. }
  1395. }
  1396. if (!gok) { match = 0; break; }
  1397. }
  1398. if (!match) continue;
  1399. if (R->ngroups > best_g ||
  1400. (R->ngroups == best_g && R->nbits > best_b)) {
  1401. best_g = R->ngroups;
  1402. best_b = R->nbits;
  1403. active_layout = R->layout;
  1404. }
  1405. }
  1406. }
  1407. struct timeval layout_tv;
  1408. gettimeofday(&layout_tv, NULL);
  1409. double layout_now = layout_tv.tv_sec + layout_tv.tv_usec / 1000000.0;
  1410. if (plc_ok) {
  1411. g_last_layout_ts = layout_now;
  1412. } else if (g_hold_layout_sec > 0 && g_cur_layout >= 0 &&
  1413. layout_now - g_last_layout_ts < g_hold_layout_sec) {
  1414. /* PLC briefly lost: keep the last good layout. */
  1415. active_layout = g_cur_layout;
  1416. }
  1417. if (active_layout != g_cur_layout) {
  1418. /* Debounce: only commit a layout change once the new layout has
  1419. * been stable for g_layout_debounce_ms. A momentary PLC bit pulse
  1420. * (point triggers then clears within one scan) is thus ignored and
  1421. * the screen does not flicker. The very first layout applies at
  1422. * once so there is no startup delay. */
  1423. int do_switch;
  1424. if (g_cur_layout < 0 || g_layout_debounce_ms <= 0) {
  1425. do_switch = 1;
  1426. g_pending_layout = -1;
  1427. } else {
  1428. if (active_layout != g_pending_layout) {
  1429. g_pending_layout = active_layout;
  1430. g_pending_layout_ts = layout_now;
  1431. }
  1432. do_switch = (layout_now - g_pending_layout_ts) * 1000.0
  1433. >= g_layout_debounce_ms;
  1434. }
  1435. if (do_switch) {
  1436. g_pending_layout = -1;
  1437. /* ---- Split mode: fixed top + adaptive bottom ---- */
  1438. if (g_split_enabled && g_split_top_n > 0) {
  1439. int bottom_n;
  1440. if (active_layout < 0) {
  1441. /* No PLC rule matched: default bottom ([layout_bottom]). */
  1442. bottom_n = split_idle_bottom();
  1443. } else {
  1444. bottom_n = active_layout + 1; /* layout 0→1cam, 1→2cam, 2→3cam */
  1445. if (bottom_n < 1) bottom_n = 1;
  1446. if (bottom_n > SPLIT_MAX_BOTTOM) bottom_n = SPLIT_MAX_BOTTOM;
  1447. }
  1448. if (bottom_n > 0 && !g_split_bottom_valid[bottom_n - 1]) {
  1449. /* Fallback: find first valid bottom variant */
  1450. for (int bv = SPLIT_MAX_BOTTOM; bv >= 1; bv--)
  1451. if (g_split_bottom_valid[bv - 1]) { bottom_n = bv; break; }
  1452. }
  1453. int *bid = NULL;
  1454. int bottom_cnt = split_bottom_cfg(bottom_n, &bid);
  1455. if (bottom_cnt < 1) bottom_cnt = 1;
  1456. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES], sid[MAX_TILES];
  1457. int placement = composer_split_rects(
  1458. output_w, output_h, g_title_h,
  1459. g_split_top_ids, g_split_top_n, g_split_top_ratio,
  1460. bid, bottom_cnt, bottom_cnt,
  1461. xs, ys, ws, hs, sid);
  1462. if (placement > 0) {
  1463. composer_set_tiles(g_composer, xs, ys, ws, hs, placement);
  1464. g_disp_tiles = placement;
  1465. for (int t = 0; t < placement; t++)
  1466. g_disp_sid[t] = (sid[t] >= 0 && sid[t] < MAX_STREAMS) ? sid[t] : -1;
  1467. }
  1468. g_cur_layout = active_layout;
  1469. } else if (active_layout >= 0 && active_layout < g_num_layouts) {
  1470. LayoutDef *L = &g_layouts[active_layout];
  1471. int ncell = L->n; if (ncell > MAX_TILES) ncell = MAX_TILES;
  1472. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES];
  1473. int idx[MAX_TILES]; /* region -> region sid mapping */
  1474. int placement = 0;
  1475. if (L->nrows > 0) {
  1476. /* named layout: merged regions for same-label cells
  1477. * (cells are already flattened in L->map, row-major) */
  1478. placement = composer_region_rects(
  1479. output_w, output_h, L->row_cols, L->nrows, L->map,
  1480. xs, ys, ws, hs, idx, g_title_h);
  1481. }
  1482. if (placement <= 0) { /* fallback: uniform grid */
  1483. composer_grid_rects(output_w, output_h, ncell,
  1484. xs, ys, ws, hs, g_title_h);
  1485. placement = ncell;
  1486. for (int t = 0; t < placement; t++) idx[t] = L->map[t];
  1487. }
  1488. composer_set_tiles(g_composer, xs, ys, ws, hs, placement);
  1489. g_disp_tiles = placement;
  1490. for (int t = 0; t < placement; t++)
  1491. g_disp_sid[t] = (idx[t] >= 0 && idx[t] < MAX_STREAMS) ? idx[t] : -1;
  1492. } else if (g_split_enabled && g_split_top_n > 0) {
  1493. /* Split mode, no PLC rule: default bottom ([layout_bottom]) */
  1494. int bottom_n = split_idle_bottom();
  1495. int *bid = NULL;
  1496. int bottom_cnt = split_bottom_cfg(bottom_n, &bid);
  1497. if (bottom_cnt < 1) bottom_cnt = 1;
  1498. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES], sid_arr[MAX_TILES];
  1499. int placement = composer_split_rects(
  1500. output_w, output_h, g_title_h,
  1501. g_split_top_ids, g_split_top_n, g_split_top_ratio,
  1502. bid, bottom_cnt, bottom_cnt,
  1503. xs, ys, ws, hs, sid_arr);
  1504. if (placement > 0) {
  1505. composer_set_tiles(g_composer, xs, ys, ws, hs, placement);
  1506. g_disp_tiles = placement;
  1507. for (int t = 0; t < placement; t++)
  1508. g_disp_sid[t] = (sid_arr[t] >= 0 && sid_arr[t] < MAX_STREAMS) ? sid_arr[t] : -1;
  1509. }
  1510. } else {
  1511. /* base grid: compile to regions as well (merges repeated ids) */
  1512. if (g_grid_nrows > 0) {
  1513. int xs[MAX_TILES], ys[MAX_TILES], ws[MAX_TILES], hs[MAX_TILES], idx[MAX_TILES];
  1514. int placement = composer_region_rects(
  1515. output_w, output_h, g_grid_row_cols, g_grid_nrows, tile_map,
  1516. xs, ys, ws, hs, idx, g_title_h);
  1517. if (placement > 0) {
  1518. composer_set_tiles(g_composer, xs, ys, ws, hs, placement);
  1519. g_disp_tiles = placement;
  1520. for (int t = 0; t < placement; t++)
  1521. g_disp_sid[t] = (idx[t] >= 0 && idx[t] < MAX_STREAMS) ? idx[t] : -1;
  1522. } else {
  1523. composer_set_tiles(g_composer, g_grid_x, g_grid_y,
  1524. g_grid_w, g_grid_h, num_tiles);
  1525. g_disp_tiles = num_tiles;
  1526. for (int t = 0; t < g_disp_tiles; t++)
  1527. g_disp_sid[t] = tile_map[t];
  1528. }
  1529. } else {
  1530. composer_set_tiles(g_composer, g_grid_x, g_grid_y,
  1531. g_grid_w, g_grid_h, num_tiles);
  1532. g_disp_tiles = num_tiles;
  1533. for (int t = 0; t < g_disp_tiles; t++)
  1534. g_disp_sid[t] = tile_map[t];
  1535. }
  1536. }
  1537. fprintf(stderr, "DBG: layout switched to %d\n", active_layout);
  1538. g_cur_layout = active_layout;
  1539. } /* do_switch */
  1540. } else {
  1541. /* active_layout returned to the current one before the debounce
  1542. * elapsed: cancel any pending change so a later pulse starts a
  1543. * fresh, full debounce window. */
  1544. g_pending_layout = -1;
  1545. }
  1546. /* --- Single-channel active layout: the shown stream's worker
  1547. switches to its main-channel URL. A layout is single-channel when
  1548. it compiles to exactly one region. --- */
  1549. {
  1550. int sid = -1;
  1551. if (g_disp_tiles == 1) {
  1552. sid = (g_disp_sid[0] >= 0) ? g_disp_sid[0] : -1;
  1553. }
  1554. for (int i = 0; i < num_streams; i++) {
  1555. StreamWorker *w = &workers[i];
  1556. int want = (i == sid && w->url_main[0]);
  1557. pthread_mutex_lock(&w->mutex);
  1558. w->use_main = want;
  1559. pthread_mutex_unlock(&w->mutex);
  1560. }
  1561. }
  1562. if (valid_srcs > 0) {
  1563. int back = BACK_BUF();
  1564. /* Clear the video area only; the title bar rows are drawn once
  1565. * at startup and must not be wiped each frame. */
  1566. {
  1567. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE };
  1568. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1569. }
  1570. memset((uint8_t *)output_map[back] + (size_t)output_w * g_title_h, 0x10,
  1571. (size_t)output_w * (output_h - g_title_h));
  1572. memset((uint8_t *)output_map[back] + (size_t)output_w * output_h +
  1573. (size_t)output_w * (g_title_h / 2), 0x80,
  1574. (size_t)output_w * (output_h - g_title_h) / 2);
  1575. {
  1576. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE };
  1577. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1578. }
  1579. /* --- Composition --- */
  1580. int tile_fds[MAX_TILES];
  1581. int tile_sids[MAX_TILES];
  1582. int tile_widths[MAX_TILES];
  1583. int tile_heights[MAX_TILES];
  1584. int tile_strides[MAX_TILES];
  1585. int tile_vstrides[MAX_TILES];
  1586. /* Resolve each stream to its display source: a fresh frame when
  1587. * available, else the held last frame if the stream is only
  1588. * briefly quiet (switch/reconnect), else nothing (NO SIGNAL). */
  1589. int disp_fd[MAX_STREAMS];
  1590. int disp_w[MAX_STREAMS];
  1591. int disp_h[MAX_STREAMS];
  1592. int disp_stride[MAX_STREAMS];
  1593. int disp_vstride[MAX_STREAMS];
  1594. for (int i = 0; i < num_streams; i++) {
  1595. StreamWorker *sw = &workers[i];
  1596. disp_fd[i] = -1;
  1597. if (mono_now - sw->last_frame_mono <= HOLD_LAST_FRAME_MS / 1000.0) {
  1598. if (src_fds[i] >= 0) {
  1599. disp_fd[i] = src_fds[i];
  1600. disp_w[i] = src_widths[i];
  1601. disp_h[i] = src_heights[i];
  1602. disp_stride[i] = src_strides[i];
  1603. disp_vstride[i] = src_vstrides[i];
  1604. } else if (sw->last_fd >= 0) {
  1605. disp_fd[i] = sw->last_fd;
  1606. disp_w[i] = sw->last_w;
  1607. disp_h[i] = sw->last_h;
  1608. disp_stride[i] = sw->last_stride;
  1609. disp_vstride[i] = sw->last_vstride;
  1610. }
  1611. }
  1612. }
  1613. /* Composition uses the precompiled regions of the active layout
  1614. * (g_disp_sid[] / g_disp_tiles), each region mapping to one camera
  1615. * and one screen rect already set on the composer. */
  1616. int cur_tiles = g_disp_tiles;
  1617. if (cur_tiles > MAX_TILES) cur_tiles = MAX_TILES;
  1618. for (int t = 0; t < cur_tiles; t++) {
  1619. int sid = g_disp_sid[t];
  1620. if (sid >= 0 && sid < num_streams && disp_fd[sid] >= 0) {
  1621. tile_sids[t] = sid;
  1622. tile_fds[t] = disp_fd[sid];
  1623. tile_widths[t] = disp_w[sid];
  1624. tile_heights[t] = disp_h[sid];
  1625. tile_strides[t] = disp_stride[sid];
  1626. tile_vstrides[t] = disp_vstride[sid];
  1627. } else {
  1628. tile_sids[t] = -1;
  1629. tile_fds[t] = -1;
  1630. }
  1631. }
  1632. int ret = composer_compose(g_composer, tile_fds, tile_widths,
  1633. tile_heights, tile_strides, tile_vstrides,
  1634. cur_tiles, output_fd[back], output_stride);
  1635. if (ret != 0) {
  1636. flog_throttled("compose", 10000, "合成失败 (compose ret=%d)", ret);
  1637. }
  1638. /* Commit even when compose fails: the buffer holds the fresh
  1639. * dark clear, and committing keeps the pipeline alive so the
  1640. * screen auto-recovers once RGA is healthy again. */
  1641. {
  1642. /* Invalidate the CPU cache so the CPU-side overlays (NO
  1643. * SIGNAL / detection / translucent title) blend against the
  1644. * freshly written pixels, not stale ones. Without this, the
  1645. * translucent title strip tears when the content under it
  1646. * changes (e.g. layout switch). */
  1647. {
  1648. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE };
  1649. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1650. }
  1651. /* --- Draw detection boxes + NO SIGNAL overlays --- */
  1652. if (g_grid_lines)
  1653. draw_grid_lines((uint8_t *)output_map[back], output_w, output_h,
  1654. g_composer, cur_tiles);
  1655. if (g_no_signal_text) {
  1656. for (int t = 0; t < cur_tiles; t++) {
  1657. if (tile_sids[t] >= 0) continue;
  1658. int tile_x, tile_y, tile_w, tile_h;
  1659. composer_get_tile_layout(g_composer, t,
  1660. &tile_x, &tile_y, &tile_w, &tile_h);
  1661. int fsz = tile_h / 4;
  1662. if (fsz < 16) fsz = 16;
  1663. if (fsz > 72) fsz = 72;
  1664. title_render_text(output_map[back], output_w, output_h,
  1665. g_title.font, "NO SIGNAL", fsz, 0xC0C0C0,
  1666. tile_x + tile_w / 2, tile_y + tile_h / 2);
  1667. }
  1668. }
  1669. if (g_title_overlay && g_title_enabled) {
  1670. title_render_overlay(output_map[back], output_w, output_h,
  1671. &g_title);
  1672. }
  1673. if (g_plc_enabled) {
  1674. title_render_plc_status(output_map[back], output_w, output_h,
  1675. g_title.font, plc_is_connected());
  1676. }
  1677. update_ip_text();
  1678. title_render_ip(output_map[back], output_w, output_h,
  1679. g_title.font, g_ip_text);
  1680. log_plc_state_change();
  1681. __sync_synchronize();
  1682. /* Flush CPU-written detection boxes so RGA DMA reads them correctly */
  1683. {
  1684. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE };
  1685. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1686. }
  1687. if (g_display && display_commit(g_display, output_fd[back], output_stride) == 0) {
  1688. output_last = output_front;
  1689. output_front = back;
  1690. frame_count++;
  1691. display_fail_streak = 0;
  1692. } else {
  1693. flog_throttled("commit", 10000, "显示提交失败 (display_commit)");
  1694. if (g_display && ++display_fail_streak >= 5) {
  1695. display_fail_streak = 0;
  1696. display_rebuild_now();
  1697. }
  1698. }
  1699. }
  1700. } else {
  1701. /* All streams dead / never connected: show a dark NO SIGNAL frame
  1702. * instead of freezing the last frame. Nothing changes between
  1703. * iterations here, so throttle to ~5 fps instead of burning the
  1704. * CPU + display pipeline at vblank rate. */
  1705. static double last_nosig_commit = -1e9;
  1706. if (mono_now - last_nosig_commit < 0.2) {
  1707. log_plc_state_change();
  1708. goto release_held;
  1709. }
  1710. last_nosig_commit = mono_now;
  1711. int back = BACK_BUF();
  1712. {
  1713. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE };
  1714. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1715. }
  1716. memset((uint8_t *)output_map[back] + (size_t)output_w * g_title_h, 0x08,
  1717. (size_t)output_w * (output_h - g_title_h));
  1718. memset((uint8_t *)output_map[back] + (size_t)output_w * output_h +
  1719. (size_t)output_w * (g_title_h / 2), 0x80,
  1720. (size_t)output_w * (output_h - g_title_h) / 2);
  1721. if (g_grid_lines)
  1722. draw_grid_lines((uint8_t *)output_map[back], output_w, output_h,
  1723. g_composer, g_disp_tiles);
  1724. if (g_no_signal_text) {
  1725. title_render_text(output_map[back], output_w, output_h,
  1726. g_title.font, "NO SIGNAL", 96, 0xC0C0C0,
  1727. 0, output_h / 2);
  1728. }
  1729. if (g_title_overlay && g_title_enabled) {
  1730. title_render_overlay(output_map[back], output_w, output_h,
  1731. &g_title);
  1732. }
  1733. update_ip_text();
  1734. title_render_ip(output_map[back], output_w, output_h,
  1735. g_title.font, g_ip_text);
  1736. log_plc_state_change();
  1737. {
  1738. struct dma_buf_sync sync = { .flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE };
  1739. ioctl(output_fd[back], DMA_BUF_IOCTL_SYNC, &sync);
  1740. }
  1741. if (g_display && display_commit(g_display, output_fd[back], output_stride) == 0) {
  1742. output_last = output_front;
  1743. output_front = back;
  1744. frame_count++;
  1745. display_fail_streak = 0;
  1746. } else {
  1747. flog_throttled("commit", 10000, "显示提交失败 (display_commit)");
  1748. if (g_display && ++display_fail_streak >= 5) {
  1749. display_fail_streak = 0;
  1750. display_rebuild_now();
  1751. }
  1752. }
  1753. }
  1754. /* --- Release held decoder frames --- */
  1755. release_held:
  1756. for (int i = 0; i < num_streams; i++) {
  1757. StreamWorker *w = &workers[i];
  1758. if (src_fds[i] >= 0) close(src_fds[i]);
  1759. pthread_mutex_lock(&w->mutex);
  1760. if (held_valid[i]) {
  1761. if (w->dec) decoder_release_frame(w->dec, held_idx[i]);
  1762. held_valid[i] = 0;
  1763. w->held_pin = -1;
  1764. }
  1765. pthread_cond_broadcast(&w->cond);
  1766. pthread_mutex_unlock(&w->mutex);
  1767. }
  1768. struct timeval now;
  1769. gettimeofday(&now, NULL);
  1770. double elapsed = (now.tv_sec - last_tv.tv_sec) +
  1771. (now.tv_usec - last_tv.tv_usec) / 1000000.0;
  1772. if (elapsed >= 5.0) {
  1773. printf("Display FPS: %.1f\n", frame_count / elapsed);
  1774. frame_count = 0;
  1775. last_tv = now;
  1776. }
  1777. /* Every 15s report per-tile compose health (dirty tiles only) so a
  1778. * recurring "black flashing tile" is attributable from the log. */
  1779. if (now.tv_sec - comp_stats_last.tv_sec >= 15) {
  1780. composer_dump_stats(g_composer);
  1781. comp_stats_last = now;
  1782. }
  1783. flog_rotate_check();
  1784. usleep(1000);
  1785. }
  1786. printf("Shutting down...\n");
  1787. flog_shutdown();
  1788. if (g_plc_enabled) plc_shutdown();
  1789. for (int i = 0; i < num_streams; i++) {
  1790. StreamWorker *w = &workers[i];
  1791. w->running = 0;
  1792. if (w->thread) pthread_join(w->thread, NULL);
  1793. for (int j = 0; j < 2; j++) {
  1794. if (w->ring[j].fd >= 0) { close(w->ring[j].fd); w->ring[j].fd = -1; }
  1795. }
  1796. if (w->last_fd >= 0) { close(w->last_fd); w->last_fd = -1; }
  1797. if (w->dec) decoder_destroy(w->dec);
  1798. pthread_mutex_destroy(&w->mutex);
  1799. pthread_cond_destroy(&w->cond);
  1800. }
  1801. for (int i = 0; i < 3; i++) {
  1802. if (output_map[i]) munmap(output_map[i], output_size);
  1803. if (output_fd[i] >= 0) close(output_fd[i]);
  1804. }
  1805. composer_destroy(g_composer);
  1806. display_destroy(g_display);
  1807. printf("Done.\n");
  1808. return 0;
  1809. }