Phase 2: composer + libcugrid (N источников → 2x2 grid в NV12 буфер)

Multi-source композитор работает на 4K @ 25fps стабильно. Live-тест с
4 камерами (parking, back_yard, front_yard, gate_lpr): все 4 active,
350 кадров за 14с, 27.6 МБ H.264 файл, кадр декодируется ffmpeg'ом
с корректным 2x2 layout'ом.

Содержимое:

- include/cuframes_composer/cugrid.h — публичный API libcugrid:
  cfc_cugrid_fill_nv12 (region fill с alpha blend),
  cfc_cugrid_resize_nv12 (bilinear scale в rect).
- src/cugrid/cugrid.cu — извлечённые из vf_cuda_grid kernel'ы
  (Y+UV fill + bilinear resize), объединены с C launcher'ом в одном
  .cu файле, под LGPL-2.1+.
- include/cuframes_composer/composer.h — публичный API композитора:
  cfc_composer_cell_t для layout, get_health для observability.
- src/composer.c — manager N cfc_source_t + единый NV12 output buffer
  (cuMemAlloc, переиспользуется на каждом compose'е). compose_clear
  fillит фон BT.709-чёрным, compose_cell делает resize ACTIVE
  источника или оставляет blackout для DEAD/STALE/CONNECTING.
- examples/grid_record — Phase 2 smoke test: N --cell ключ,x,y,w,h
  → grid composer → NVENC → file.

Сборка: добавлен LANGUAGES CUDA и CMAKE_CUDA_ARCHITECTURES 89;120
(Ada + Blackwell). Compile options раздельные для C и CUDA
(-Wpedantic не подходит для .cu).

Phase 2 RTSP push отложен на отдельный commit — будет через pipe-out
к локальному ffmpeg'у, который публикует в mediamtx (вариант
утверждён в Q2 дизайн-документа).
This commit is contained in:
2026-06-03 05:01:49 +01:00
parent eae902afb3
commit 1e2b5d4e16
8 changed files with 949 additions and 12 deletions
+97
View File
@@ -0,0 +1,97 @@
/* 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 <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 */
} 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
);
/* Получить 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 */
+81
View File
@@ -0,0 +1,81 @@
/* cuframes-composer — CUDA kernels для grid-композиции NV12 кадров.
*
* Извлечено из vf_cuda_grid (FFmpeg out-of-tree filter), под LGPL-2.1+.
* Все операции работают на NV12-кадрах:
* - Y plane: full resolution, 1 byte per pixel
* - UV plane: half resolution, 2 bytes per pixel (interleaved U,V)
*
* Все операции:
* - Принимают CUstream (NULL = default stream)
* - Не аллоцируют память (caller выделяет dst буфер заранее)
* - Idempotent для re-use (composer вызывает на каждом кадре)
*
* Lifecycle:
* cfc_cugrid_init() один раз при старте композитора (no-op для CUDA, но
* резервирован под cuModuleLoad если перейдём на PTX)
*
* Лицензия: LGPL-2.1+
*/
#ifndef CUFRAMES_COMPOSER_CUGRID_H
#define CUFRAMES_COMPOSER_CUGRID_H
#include <cuda.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Lazy init (no-op в текущей реализации; зарезервировано под future PTX module). */
int cfc_cugrid_init(void);
/* Заполнить прямоугольник на NV12 кадре solid color'ом с alpha-блендингом.
*
* dst_y, dst_uv — указатели на Y и UV plane соответственно.
* pitch_y, pitch_uv — pitch в bytes (для NV12 обычно равны и кратны 256).
* x, y, w, h — прямоугольник в full-res пикселях (chroma usually пишется
* автоматически на половинном rastr'е).
* w и h должны быть чётными (требование 4:2:0 subsampling'а).
* color_y/u/v — компоненты цвета в BT.709 limited range (Y: 16-235, UV: 16-240).
* Для чёрного: Y=16, U=128, V=128.
* alpha — 0..255 (0 = transparent, 255 = opaque).
*
* Возвращает 0 при успехе.
*/
int cfc_cugrid_fill_nv12(
CUstream stream,
CUdeviceptr dst_y, int pitch_y,
CUdeviceptr dst_uv, int pitch_uv,
int x, int y, int w, int h,
int color_y, int color_u, int color_v, int alpha
);
/* Resize NV12 src → rect (dst_x, dst_y, dst_w, dst_h) на NV12 dst.
*
* Bilinear interpolation на Y и UV. Y и UV plane src'а должны быть одним
* allocation'ом (NV12 layout) или хотя бы иметь корректные указатели каждый.
*
* src_y, src_uv — src указатели.
* src_w, src_h — размер src в full-res пикселях.
* src_pitch_y/uv — pitch'ы (для NV12 cuframes pitch_y == pitch_uv).
* dst_y, dst_uv — dst указатели.
* dst_pitch_y/uv — pitch'ы dst NV12 кадра.
* dst_x, dst_y_off, — координата top-left прямоугольника на dst (full-res пиксели).
* dst_w, dst_h dst_x и dst_y_off, dst_w, dst_h ДОЛЖНЫ быть чётными
* (для UV/chroma subsampling'а).
*/
int cfc_cugrid_resize_nv12(
CUstream stream,
CUdeviceptr src_y, int src_w, int src_h, int src_pitch_y,
CUdeviceptr src_uv, int src_pitch_uv,
CUdeviceptr dst_y, int dst_pitch_y,
CUdeviceptr dst_uv, int dst_pitch_uv,
int dst_x, int dst_y_off, int dst_w, int dst_h
);
#ifdef __cplusplus
}
#endif
#endif /* CUFRAMES_COMPOSER_CUGRID_H */