Phase 10/11 WIP — pool + motion-mode + 8×8 templates (rolled back)
Объединённое состояние работ:
- 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>
This commit is contained in:
@@ -117,6 +117,55 @@ int cfc_composer_set_layout(cfc_composer_t *comp, const char *layout_name);
|
||||
* Возвращает 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; /* всего источников */
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#ifndef CUFRAMES_COMPOSER_FRIGATE_MQTT_H
|
||||
#define CUFRAMES_COMPOSER_FRIGATE_MQTT_H
|
||||
|
||||
#include "composer.h"
|
||||
#include "overlay.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -28,6 +29,10 @@ typedef struct cfc_frigate_mqtt_config {
|
||||
const char *username; /* MQTT user — обычно тот же, что у health */
|
||||
const char *password;
|
||||
const char *topic; /* default "frigate/events" */
|
||||
/* Если задан — каждое new/update event для любой камеры вызывает
|
||||
* cfc_composer_motion_pulse(composer, after.camera). Это драйвер для
|
||||
* motion-driven auto-layout (Phase 10). NULL = motion-pulse не шлём. */
|
||||
cfc_composer_t *composer;
|
||||
} cfc_frigate_mqtt_config_t;
|
||||
|
||||
typedef struct cfc_frigate_mqtt cfc_frigate_mqtt_t;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
/* cuframes-composer — predefined layout templates.
|
||||
/* cuframes-composer — модель layout'ов на 8×8 микро-сетке (Phase 11).
|
||||
*
|
||||
* Phase 9 (task #194): runtime layout switching через ZMQ verb + ONVIF PTZ.
|
||||
* Output (1920×1080) разбит на 8 cols × 8 rows микроячеек 240×135 px.
|
||||
* Каждая микроячейка имеет aspect 16:9 — любой квадрат N×N микроячеек
|
||||
* тоже 16:9 (без растяжения камер).
|
||||
*
|
||||
* Layouts описаны normalized (0..1) — масштабируются к фактическому output
|
||||
* width/height композитора при apply_to_cells(). Список синхронизирован с
|
||||
* historic'ой `vf_cuda_grid.c` layouts[] (старый FFmpeg patch), что
|
||||
* обеспечивает совместимость с Python controller'ом и предыдущими ONVIF
|
||||
* presets.
|
||||
* Cell template — прямоугольник {col, row, cs, rs} в микроячейках,
|
||||
* с ролью CAMERA либо WIDGET. Camera-cells получают source из pool
|
||||
* композитора по polю `order`: order=0 → main (top-priority active),
|
||||
* order=1 → next и т.д.
|
||||
*
|
||||
* Layout template — набор cells одного экрана. Composer хранит таблицу
|
||||
* built-in templates + опционально загружает override из JSON (Step 7).
|
||||
*
|
||||
* Phase 11 Step 1: единственный built-in `tpl_1` = одна cell 8×8 (single).
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
@@ -18,25 +24,83 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CFC_LAYOUT_MAX_CELLS 16
|
||||
#define CFC_LAYOUT_MAX_NAME 32
|
||||
#define CFC_GRID_COLS 8
|
||||
#define CFC_GRID_ROWS 8
|
||||
#define CFC_LAYOUT_MAX_CELLS 32
|
||||
#define CFC_LAYOUT_MAX_NAME 32
|
||||
#define CFC_WIDGET_NAME_MAX 32
|
||||
|
||||
typedef struct cfc_layout_cell {
|
||||
float x, y, w, h; /* normalized fraction of output dims */
|
||||
} cfc_layout_cell_t;
|
||||
typedef enum cfc_cell_role {
|
||||
CFC_CELL_CAMERA = 0,
|
||||
CFC_CELL_WIDGET = 1,
|
||||
} cfc_cell_role_t;
|
||||
|
||||
/* Описание одной cell внутри layout-template'а.
|
||||
* col, row — top-left угол в микроячейках (0..7)
|
||||
* cs, rs — span в микроячейках (≥1, col+cs ≤ 8, row+rs ≤ 8)
|
||||
* role — CAMERA или WIDGET
|
||||
* order — для CAMERA: порядок placement'а активных камер
|
||||
* (order=0 получает top-priority); игнорируется для WIDGET
|
||||
* widget — для WIDGET: имя placeholder'а (текст подписи) */
|
||||
typedef struct cfc_cell {
|
||||
int col, row;
|
||||
int cs, rs;
|
||||
cfc_cell_role_t role;
|
||||
int order;
|
||||
char widget[CFC_WIDGET_NAME_MAX];
|
||||
} cfc_cell_t;
|
||||
|
||||
/* Описание layout-template'а: набор cells + metadata. */
|
||||
typedef struct cfc_layout {
|
||||
const char *name;
|
||||
int nb_cells;
|
||||
cfc_layout_cell_t cells[CFC_LAYOUT_MAX_CELLS];
|
||||
char name[CFC_LAYOUT_MAX_NAME];
|
||||
cfc_cell_t cells[CFC_LAYOUT_MAX_CELLS];
|
||||
int nb_cells; /* всего cells */
|
||||
int nb_camera_cells; /* кеш — для best-fit selection */
|
||||
int priority; /* tie-break при best-fit (выше = победитель) */
|
||||
} cfc_layout_t;
|
||||
|
||||
/* Найти layout по имени; NULL если нет. */
|
||||
const cfc_layout_t *cfc_layout_find(const char *name);
|
||||
|
||||
/* Возвращает указатель на array всех layout'ов + их количество. */
|
||||
/* Возвращает указатель на массив всех загруженных layout'ов и их количество. */
|
||||
const cfc_layout_t *cfc_layout_all(int *out_count);
|
||||
|
||||
/* Перевести {col,row,cs,rs} в pixel-координаты под output W×H.
|
||||
* x = col * (W/8), w = cs * (W/8) (выровнено на чётные).
|
||||
* NV12 требует чётных координат — встроено внутрь. */
|
||||
void cfc_layout_to_pixels(const cfc_cell_t *cell, int W, int H,
|
||||
int *out_x, int *out_y, int *out_w, int *out_h);
|
||||
|
||||
/* Загрузить templates из JSON-файла. При успехе сменяет реестр; при ошибке
|
||||
* реестр остаётся прежним (или built-in, если ничего не было).
|
||||
* Возвращает количество загруженных templates, либо отрицательное число
|
||||
* (-1 = parse error, -2 = invalid schema, -3 = file not found).
|
||||
*
|
||||
* Hot-reload: после успешного вызова cfc_layout_find/cfc_layout_all
|
||||
* возвращают новые template'ы. Композитор подхватывает на следующем кадре.
|
||||
*
|
||||
* JSON schema:
|
||||
* { "version": 1, "grid_cols": 8, "grid_rows": 8,
|
||||
* "templates": [
|
||||
* { "name": "tpl_1", "priority": 0,
|
||||
* "cells": [
|
||||
* { "col":0, "row":0, "cs":8, "rs":8,
|
||||
* "role":"camera", "order":0,
|
||||
* "widget":"ha_chat" // для role=widget
|
||||
* }, ...
|
||||
* ]
|
||||
* }, ...
|
||||
* ]
|
||||
* } */
|
||||
int cfc_layout_load_file(const char *path);
|
||||
|
||||
/* Перезагрузить из последнего успешно загруженного файла. Если файла не
|
||||
* было — возвращает -1. */
|
||||
int cfc_layout_reload(void);
|
||||
|
||||
/* Получить путь к текущему загруженному файлу (NULL если built-in). */
|
||||
const char *cfc_layout_loaded_path(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user