f8e27b9e85
Объединённое состояние работ:
- Phase 10: source pool, motion-driven layout, Frigate motion_pulse,
zone-filter в pool, --source / --motion-mode CLI
- Phase 11 Steps 1-3c: 8×8 микро-сетка, JSON templates с asymmetric
layouts (tpl_1/3/4/5/6/7/8), reload через ZMQ, auto-labels per camera
В прод отдеплоено и откачено: используем gx/cuframes-composer:0.10 как
baseline. Phase 11 продолжится в branch phase11b-cpp как C++ refactor
с ООП-моделью Cell/Layout/Decoration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
186 lines
10 KiB
C
186 lines
10 KiB
C
/* cuframes-composer — высокоуровневый композитор N источников в grid.
|
||
*
|
||
* Управляет:
|
||
* - N cfc_source (фоновые подписки на cuframes-publisher'ы)
|
||
* - Layout (cell positions + size)
|
||
* - Output NV12 buffer (cuMemAlloc, переиспользуется)
|
||
* - Композиция на каждом тике: clear background + resize/blit для каждого ACTIVE,
|
||
* fill чёрным для DEAD/STALE.
|
||
*
|
||
* Phase 2 архитектура:
|
||
* Композитор работает синхронно: caller вызывает cfc_composer_compose() →
|
||
* композитор для каждого источника берёт snapshot и пишет в output. После
|
||
* вызова output NV12 buffer готов к encode.
|
||
*
|
||
* Каноническая интеграция:
|
||
* for (;;) {
|
||
* cfc_composer_compose(comp); // grid готов в output buffer
|
||
* cfc_encoder_encode_frame(enc, ...); // → H.264
|
||
* }
|
||
*
|
||
* Лицензия: LGPL-2.1+
|
||
*/
|
||
|
||
#ifndef CUFRAMES_COMPOSER_COMPOSER_H
|
||
#define CUFRAMES_COMPOSER_COMPOSER_H
|
||
|
||
#include "source.h"
|
||
#include "overlay.h"
|
||
|
||
#include <cuda.h>
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* Описание одной ячейки grid'а — где на output буфере рисуется источник.
|
||
* Все координаты в full-res пикселях, должны быть чётными. */
|
||
typedef struct cfc_composer_cell {
|
||
const char *source_key; /* cuframes key, например "cam-parking" */
|
||
int x, y; /* top-left угол на output буфере */
|
||
int w, h; /* размер ячейки */
|
||
} cfc_composer_cell_t;
|
||
|
||
typedef struct cfc_composer_config {
|
||
/* Output буфер (одно фиксированное разрешение для всего grid'а). */
|
||
int width; /* output ширина (например 3840 для 2×2 1080p) */
|
||
int height; /* output высота (например 2160 для 2×2 1080p) */
|
||
|
||
/* Cells конфигурация. Массив указателей на cells (для удобства user'а). */
|
||
const cfc_composer_cell_t *cells; /* array, не копируется — caller держит */
|
||
int num_cells;
|
||
|
||
/* CUDA устройство. */
|
||
int cuda_device; /* индекс, обычно 0 */
|
||
|
||
/* Backgound цвет (BT.709 limited): Y=16/U=128/V=128 = чёрный. */
|
||
int bg_y, bg_u, bg_v;
|
||
|
||
/* Параметры stale/dead для источников. */
|
||
int reconnect_min_ms; /* default 1000 */
|
||
int reconnect_max_ms; /* default 30000 */
|
||
int stale_threshold_ms; /* default 500 */
|
||
int dead_threshold_ms; /* default 5000 */
|
||
|
||
/* Prefix для consumer_name внутри cuframes_subscriber_create.
|
||
* default = "composer" (для обратной совместимости с фазами 1-6).
|
||
* Уникальный prefix позволяет нескольким composer'ам одновременно
|
||
* subscribe к одним и тем же publisher'ам (cfc-grid + cuda-grid-pipeline
|
||
* параллельно, у каждого свой namespace). */
|
||
const char *consumer_prefix;
|
||
} cfc_composer_config_t;
|
||
|
||
typedef struct cfc_composer cfc_composer_t;
|
||
|
||
/* Создать композитор. Выделяет output NV12 буфер, запускает N source thread'ов. */
|
||
int cfc_composer_create(const cfc_composer_config_t *cfg, cfc_composer_t **out);
|
||
|
||
/* Скомпоновать один кадр. Вернёт указатели на output NV12 (Y + UV) и pitch.
|
||
* Указатели действительны до следующего compose. */
|
||
int cfc_composer_compose(
|
||
cfc_composer_t *comp,
|
||
CUdeviceptr *out_y_ptr,
|
||
int *out_pitch_y,
|
||
int *out_width,
|
||
int *out_height
|
||
);
|
||
|
||
/* Зарегистрировать overlay (border/png/text) для отрисовки поверх grid'а.
|
||
* composer takes ownership — на cfc_composer_destroy всё освобождается.
|
||
* Порядок добавления = z-order (последний рисуется поверх). Лимит — 64. */
|
||
int cfc_composer_add_overlay(cfc_composer_t *comp, cfc_overlay_t *ov);
|
||
|
||
/* Найти overlay по ID (если был задан через cfc_overlay_set_id). Возвращает
|
||
* NULL если не найден. Thread-safe — composer держит overlays в массиве,
|
||
* пока add/destroy не пересекаются с lookup'ом — но control plane вызывает
|
||
* это из своего потока, draw — из своего; они оба только читают список,
|
||
* а update полей overlay'я делается через cfc_overlay_update_text и пр.
|
||
* (содержимое overlay'я под mutex'ом не лежит, нужно лочиться вызывающему). */
|
||
cfc_overlay_t *cfc_composer_find_overlay(cfc_composer_t *comp, const char *id);
|
||
|
||
/* Переключить layout runtime: применяет normalized cells из named layout
|
||
* к фактическому output разрешению, пересчитывает comp->cells[].x/y/w/h.
|
||
*
|
||
* Source pool НЕ пересоздаётся: source threads привязаны к индексам ячеек
|
||
* (cell[0]=source[0], cell[1]=source[1], ...). Если у нового layout
|
||
* больше ячеек чем sources — лишние cells без источника останутся blackout'нутыми.
|
||
* Если у нового layout меньше ячеек чем sources — лишние sources продолжают
|
||
* работать, но не draw'аются (Phase 9 acceptable).
|
||
*
|
||
* Thread-safety: caller (ZMQ control thread) должен гарантировать что
|
||
* compose thread не выполняется параллельно. На single-threaded compose
|
||
* loop'е (Phase 2/3) это natural: control обработает запрос между кадрами. */
|
||
int cfc_composer_set_layout(cfc_composer_t *comp, const char *layout_name);
|
||
|
||
/* Получить имя текущего layout'а (если был задан через set_layout).
|
||
* Возвращает NULL если cells выставлены вручную через --cell. */
|
||
const char *cfc_composer_current_layout(cfc_composer_t *comp);
|
||
|
||
/* ── Motion-driven auto layout (Phase 10) ────────────────────────────────
|
||
* Composer держит pool из N (до 32) sources с priority. При включённом
|
||
* motion-mode на каждом compose'е выбирает active (last_motion_ms < TTL),
|
||
* сортирует по priority DESC, и применяет layout по count активных:
|
||
* 0 → top-1 by priority в single
|
||
* 1 → single
|
||
* 2 → dual_horizontal
|
||
* 3-4 → quad
|
||
* 5 → main_with_strip
|
||
* 6 → six_grid
|
||
* 7-9 → nine_grid
|
||
* 10-16 → sixteen_grid
|
||
* Сигналы motion поступают через cfc_composer_motion_pulse() — обычно из
|
||
* Frigate MQTT subscriber'а (frigate_mqtt.c). */
|
||
|
||
/* Добавить источник в pool (отдельно от --cell). Источник создаётся сразу,
|
||
* cuframes subscription стартует. Source доступен через motion-relayout.
|
||
*
|
||
* required_zones (опц.) — colon-separated whitelist для motion-фильтра:
|
||
* только события Frigate с current_zones intersect этот список считаются
|
||
* motion. Без него все события от frigate_camera считаются motion
|
||
* (полезно если камера без zones либо motion и так осмысленный). */
|
||
int cfc_composer_add_pool_source(
|
||
cfc_composer_t *comp,
|
||
const char *cuframes_key, /* "cam-parking" */
|
||
const char *frigate_camera, /* "parking_overview" для motion match */
|
||
int priority, /* выше = важнее, top попадает в первую ячейку */
|
||
const char *required_zones /* "parking_zone:canopy:private_area" или NULL */
|
||
);
|
||
|
||
/* Включить/выключить motion-mode. ttl_ms — сколько камера остаётся
|
||
* активной после последнего события (0 → default 45000). */
|
||
int cfc_composer_set_motion_mode(cfc_composer_t *comp, int on, int ttl_ms);
|
||
|
||
/* Узнать, включён ли motion-mode. */
|
||
int cfc_composer_get_motion_mode(cfc_composer_t *comp);
|
||
|
||
/* Зафиксировать факт motion от Frigate-камеры. Используется Frigate MQTT
|
||
* subscriber'ом. Если frigate_camera не найдена в pool — no-op.
|
||
*
|
||
* current_zones — список зон из after.current_zones события. Если у source
|
||
* задан required_zones — будет применён фильтр: motion засчитывается только
|
||
* если current_zones intersect required_zones. Если required_zones пуст или
|
||
* NULL — фильтр выключен. */
|
||
int cfc_composer_motion_pulse(cfc_composer_t *comp,
|
||
const char *frigate_camera,
|
||
const char *const *current_zones,
|
||
int n_zones);
|
||
|
||
/* Получить layout статистику по источникам — для debug / health-репортов. */
|
||
typedef struct cfc_composer_health {
|
||
int total; /* всего источников */
|
||
int active; /* в состоянии ACTIVE */
|
||
int stale; /* в STALE */
|
||
int dead; /* DEAD/DISCONNECTED/CONNECTING */
|
||
} cfc_composer_health_t;
|
||
int cfc_composer_get_health(cfc_composer_t *comp, cfc_composer_health_t *out);
|
||
|
||
/* Уничтожить композитор. */
|
||
int cfc_composer_destroy(cfc_composer_t *comp);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* CUFRAMES_COMPOSER_COMPOSER_H */
|