Раньше detbox рисовал только рамку. Теперь на каждое active событие
отображает "label NN%" в полупрозрачной pill цвета рамки над верхним
краем bbox.
include/cuframes_composer/overlay.h:
- cfc_overlay_detbox_config_t: + font_path, font_size, label_bg_alpha
- cfc_overlay_detbox_upsert: + float score parameter
src/overlay.c:
- detbox_entry_t: + score, rendered_text (кеш), text_atlas/w/h/pitch
- detbox_data_t: + ft_library, ft_face, font_path_copy, font_size_px
- cfc_overlay_create_detection_boxes: opens FT face если font_path задан
- cfc_overlay_destroy: free cached atlas'ы + FT face/library
- detbox_rebuild_label_atlas: format "label NN%" → FT render → VRAM upload
(reuse text_measure / text_render из CFC_OVERLAY_TEXT)
- upsert: вызывает rebuild при изменении label/score (кеш по rendered_text)
- draw_detection_boxes: snap расширен под text_atlas; после border рисует
pill bg (fill_nv12 цветом рамки) + текст (blit_rgba_nv12, белый)
- mutex hold всю draw — atlas чтение должно быть atomic против upsert
src/frigate_mqtt.c:
- parse after.score → передаём в upsert (Frigate & yoloworld envelope
совместимы: оба содержат score)
examples/grid_record.c:
- 4 frigate detbox: font_path=/fonts/DejaVuSans-Bold.ttf, font_size=16
- 4 yw detbox: то же — magenta pill с белым текстом
Live: cfc-grid healthy 25 fps, image gx/cuframes-composer:0.11b-step4
deployed. Видно на TV при первом detection event.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: "при движении объект оборачивается в рамку, но функционал не
учитывает что сетки могут переключаться и координаты ячеек меняются".
Был баг: detbox-overlay хранил cell_x/y/w/h из --detection-cell CLI
(заданы при старте), при смене layout рамки рисовались по старым
координатам — мимо камеры.
Изменения:
- overlay.h/.c: новый API cfc_overlay_detbox_set_cell_geom(ov,x,y,w,h).
Mutex-защищённое обновление detbox config'а — composer вызывает
перед каждым draw.
- CameraCell: добавлено поле source_key (хранит cuframes-key камеры,
рендерящейся в этом cell). Layout::apply передаёт его из pool entry.
- Layout::find_camera_cell_rect(key) — возвращает Rect текущей cell
для камеры с заданным cuframes-key (или nullptr если её нет в layout).
- SourcePool::by_frigate_camera(name) — lookup pool-entry по
Frigate-camera-key (frigate event'ы приходят с этим именем).
- Composer::compose_frame: перед draw каждого DETECTION_BOXES overlay'я
— lookup frigate→cuframes_key→layout cell rect, обновляет detbox geom.
Если камера не в layout сейчас — cell_w/h=0, detbox draw skip'ает.
Теперь bbox от Frigate переезжает за камерой:
- tpl_1 → bbox в full screen 1920×1080
- tpl_3 → bbox в main 1440×810
- tpl_4 → bbox в quad ячейке 960×540
- камера не в layout → bbox скрыт
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: "у него есть полупрозрачная подложка? у него правильный z-order?"
Z-order был ок (overlays draw'аются после layout.render). Но без bg
текст плохо читался на пёстром кадре — и до прихода MQTT overlay был
visible=0 (не виден вообще).
Изменения:
- overlay.h: cfc_overlay_text_config_t расширена bg_alpha / bg_y/u/v / bg_pad.
bg_alpha=0 — фон отключён (default).
- overlay.c draw_text: если bg_alpha>0, перед blit'ом текста рисуем fill
rect (atlas_w+2*pad) × (atlas_h+2*pad) с заданным цветом и alpha.
- overlay.c update_text: пробрасывает bg-поля при апдейте.
- mqtt_overlay: MqttOverlayCfg + JSON loader научились читать bg_alpha,
bg_y/u/v, bg_pad, placeholder. Default bg = чёрный 160 alpha, pad 10.
- MqttOverlayItem::start: overlay сразу visible=1 с placeholder (default "—"),
reposition_overlay вызывается до получения MQTT — placeholder
позиционируется в anchor сразу.
User'у теперь видна тёмная подложка с текстом в правом-нижнем углу даже
если sensor молчит.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: "в предыдущих версиях у нас показывалась температура, можешь
сделать в нижнем правом углу оверлей не привязанный к сеткам?". Восстановили
прежнее поведение из vf-cuda-grid/controller (dynamic_overlays.py).
Новый класс cfc::TempMqttOverlay:
- libmosquitto subscriber в отдельном thread'е, auto-reconnect 1→30s
- json-c для parse JSON payload — extract поле .temperature (double)
- Формат: "%+.1f°C" (например "+18.5°C")
- Persistent FreeType text overlay (cfc_overlay_create_text), kept в
Composer::overlays_[] backward-compat листе — рендерится поверх Layout
- reposition_overlay() — пересчёт x/y после text_size() (right-bottom anchor)
C-shim (composer_c_api.cpp) для grid_record.c:
- cfc_temp_overlay_start(composer, host, port, user, pw, topic, W, H)
- singleton (один temp overlay на процесс, прода-композитору хватит)
CLI: --temp-topic="zigbee2mqtt/Температура на улице". MQTT credentials
переиспользуются из --mqtt-host/--mqtt-user/--mqtt-pass.
Compose override (localhost-infra):
--temp-topic=zigbee2mqtt/Температура на улице
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
В прод деплоен gx/cuframes-composer:0.11b-step1 — C++ ядро
работает через ABI shim, старые C-callers (grid_record.c, control.c,
frigate_mqtt.c) использует те же cfc_composer_* функции.
Что в этом коммите:
- src/cpp/composer_c_api.cpp: extern "C" обёртки над cfc::Composer
методами. Полный набор: _create/_destroy/_compose/_add_overlay/
_find_overlay/_set_layout/_current_layout/_add_pool_source/
_set_motion_mode/_get_motion_mode/_motion_pulse/_get_health.
- src/cpp/layouts_c_api.cpp: extern "C" обёртки над template_loader
для cfc_layout_find/_all/_load_file/_reload/_loaded_path/_to_pixels.
- cpp/template_loader: global registry (current_templates / set_*
/ load_into_current) — единый источник истины. Composer и C ABI
shim читают один и тот же mutex-защищённый vector<LayoutTemplate>.
Hot-reload через ZMQ cfc_layout_load_file подхватывается composer'ом
на следующем кадре без рестарта.
- cpp/composer: pick_best_fit, set_layout, maybe_relayout читают
current_templates() вместо локального snapshot.
- cpp/composer: backward-compat overlay list (add_overlay/find_overlay)
+ manual cells support (для C API без motion-mode).
- cpp/composer compose_frame: после Layout.render() рендерит overlays
(CLI text/icon/border + Frigate detbox) поверх.
- Удалены: src/composer.c (заменён composer_c_api.cpp + composer.cpp),
src/layouts.c (заменён layouts_c_api.cpp + template_loader.cpp).
- Оставлено как есть: src/overlay.c (PNG/text/border/detbox CLI overlays
— реализация не меняется, доступ через cfc_overlay_*).
- src/CMakeLists.txt: COMPOSER_SOURCES_C минус composer.c, layouts.c,
COMPOSER_SOURCES_CPP плюс composer_c_api.cpp, layouts_c_api.cpp.
Production smoke (R9-88.23):
[cfc/loader] /opt/templates.json: loaded 7 templates
[cfc/composer] templates loaded: 7 (path='/opt/templates.json')
[cfc/composer] pool+ cam-parking prio=100 / cam-gate_lpr prio=90 / ...
[cfc/composer] motion_mode=1 ttl=45000ms pool=4
[cfc/composer] grow → template='tpl_1' active=1
PASS.
Refs: #195 (Phase 11b C++ refactor).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Что в этом коммите:
Decoration реализации:
- cpp/label_decoration.hpp/.cpp — FreeType atlas + cugrid_blit_rgba_nv12.
UTF-8 декодер, atlas в VRAM (RAII через CudaBuffer), rebuild при set_text.
- cpp/border_decoration.hpp/.cpp — 4 cugrid_fill_nv12 (top/bottom/left/right).
Cell реализации:
- cpp/camera_cell.hpp/.cpp — cfc_source_get_latest + cugrid_resize_nv12.
Non-owning указатель на cfc_source_t (pool владеет).
- cpp/widget_cell.hpp/.cpp — тёмный fill placeholder.
- cpp/blank_cell.hpp/.cpp — BT.709 black fill.
Layout и Template:
- cpp/template.hpp — LayoutTemplate { name, cells[], priority }.
8×8 микро-сетка (kGridCols=kGridRows=8). to_pixels() переводит в Rect.
- cpp/layout.hpp/.cpp — vector<unique_ptr<Cell>>, apply() создаёт
CameraCell/WidgetCell/BlankCell + Decorations (Label с "{key} prio={N}").
- cpp/template_loader.hpp/.cpp — JSON → vector<LayoutTemplate> через json-c.
builtin_templates() = { tpl_1, tpl_4 } как fallback.
SourcePool:
- cpp/source_pool.hpp/.cpp — owner cfc_source_t*, motion state атомарный,
zone-filter в motion_pulse. Pool entries — non-copyable unique_ptr.
Composer:
- cpp/composer.hpp/.cpp — owner SourcePool + templates + Layout + output.
Алгоритмы: pick_best_fit (min nb_camera_cells >= need + priority tie-break),
collect_active (drawable AND motion_within_TTL), asymmetric hysteresis
(рост сразу через std::includes, сжатие — wait shrink_hysteresis_ms).
Public C++ API: set_motion_mode / set_layout / load_templates / compose_frame.
ООП-гипотеза smoke:
- examples/grid_record_cpp.cpp — минимальный smoke без NVENC. Init composer,
compose_frame N раз, dump NV12 в файл. Проверяет что C++ модель
компилируется, линкуется с C-кодом (source.c, nvenc.c остались на C через
extern "C"), и реально рисует кадр.
Производительность сохранена:
- Один output буфер VMM, передаётся как NV12Ref (read-write reference) во все
cells/decorations — НИКАКИХ memcpy на cells boundary.
- Virtual call overhead: 1 indirect call per cell per frame. Negligible.
- Heap allocations только при apply_template (раз в N секунд при relayout).
Build:
- CMakeLists.txt: CXX language, C++17.
- src/CMakeLists.txt: COMPOSER_SOURCES_CPP добавлен в lib.
- examples/CMakeLists.txt: grid_record_cpp.
Smoke test run jammy:
[cfc/loader] docker/templates.json: loaded 7 templates
[smoke] composer 1920x1080 templates=7 sources=0 motion=0
[smoke] wrote 3317760 bytes (Y=2211840 UV=1105920) to /out/blank.nv12
Build PASS, init PASS, compose PASS, dump PASS.
Что НЕ сделано:
- extern "C" ABI shim для control.c / grid_record.c (старый C-композитор
всё ещё единственный для prod stack).
- Удаление старых composer.c / overlay.c / layouts.c.
- Live deploy в прод (Step 1-3 функциональность).
- JSON ZMQ hot-reload (был в Step 3 C-version, восстановить в C++).
Refs: #195 (Phase 11b C++ refactor).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Объединённое состояние работ:
- 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>
10 predefined layouts (single, dual_horizontal, dual_vertical, quad,
main_plus_preview, six_grid, nine_grid, sixteen_grid, panoramic,
main_with_strip) — normalized [0..1] координаты, port из vf_cuda_grid.c
старого FFmpeg patch'а. Применяются к фактическому output разрешению
композитора через cfc_composer_set_layout(name).
Source pool НЕ пересоздаётся: sources привязаны к индексам cells, layout
меняет только геометрию (cell.x/y/w/h). Это даёт zero-disruption switch
без потери накопленного state и без re-subscribe к cuframes publishers.
ZMQ verbs: set_layout / list_layouts / get_layout. CLI: --layout=NAME
перетирает --cell координаты на старте.
Используется ONVIF wrapper'ом (gx/cctv-onvif:0.1) для PTZ presets:
GotoPreset(token) → ZMQ set_layout(token).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Frigate 0.17 schema отвергает objects.filters.<obj>.required_zones
("Extra inputs not permitted" → safe mode), поэтому фильтрацию по зонам
делаем на стороне composer'а. cfc_overlay_detbox_match_zones() сверяет
after.current_zones события с whitelist'ом overlay'я. end-события
вызываются безусловно (no-op если рамки нет).
CLI: --detection-cell key,camera,dx,dy,dw,dh,detect_w,detect_h[,zone1:zone2:...]
9-е поле опционально (colon-separated). Без него filter выключен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Robust pattern из docs/RESEARCH-frigate-overlay-integration.md:
композитор сам подписывается на frigate/events, парсит JSON, рисует
bounding rectangles в управляемый overlay-список с TTL. Никаких
sidecar'ов — root cause "перестало обновляться" в старом
cuda-grid-controller'е был именно two-hop ZMQ + state leak.
Содержимое:
- include/cuframes_composer/overlay.h — новый тип CFC_OVERLAY_DETECTION_BOXES
+ cfc_overlay_detbox_config_t (camera_key, detect_w/h, cell rect,
thickness, color, TTL stale_ms) + API _create / _upsert / _end /
_camera_key.
- src/overlay.c — реализация: массив 16 active детектов под mutex,
draw как 4 fill_nv12 на каждый valid box (TTL фильтрация по now-update).
Coordinate mapping detect→cell в момент draw — layout switch безопасен.
- include/cuframes_composer/frigate_mqtt.h + src/frigate_mqtt.c — MQTT
subscriber на libmosquitto. Parses Frigate event JSON через json-c
(after.camera, after.id, after.box, after.label). Lookup overlay по
camera_key. Mosquitto reconnect_delay_set 1s→30s exp backoff,
disconnect log rate-limited (раз в 30 сек).
- examples/grid_record.c — CLI флаги --frigate-mqtt host[:port],
--frigate-topic frigate/events, --detection-cell key,camera,dx,dy,
dw,dh,detect_w,detect_h. По одному --detection-cell создаётся overlay
+ регистрируется в MQTT subscriber.
- src/CMakeLists.txt — добавлен frigate_mqtt.c.
Параллельная подготовка на Frigate стороне:
- Frigate config /home/claude/cctv/frigate-config/config.yml — добавлен
parking_overview.objects.filters.{car,person,bicycle,motorcycle}.
required_zones=[canopy,parking_zone,private_area]. Это фильтрация
на tracker уровне (review.required_zones влиял только на Frigate
Review system). После: ~2 события/сутки реально с parking, вместо
1000+ от проезжей части.
Live-test:
- Synthetic mosquitto_pub frigate/events с box [200,200,400,360]
в detect 640×480 → cfc-grid render'ит зелёную рамку в parking cell
правильной геометрии (300,225, 300×180 в 1920×1080 output).
- TTL 8 сек — рамка пропадает если update не пришёл.
- restarts=0, 12650 кадров stable 25 fps — frigate_mqtt thread
не блокирует video.
Image gx/cuframes-composer:0.7 deployed.
Чеклист защиты от "перестало обновляться" (из research §6.4):
✅ MQTT auto-reconnect через mosquitto built-in backoff
✅ TTL независим от end event (страховка от потери MQTT)
✅ State полностью в composer-процессе (один источник истины)
✅ Mapping coords в момент draw (layout switch safe)
✅ Frigate-фильтр на источнике (objects.filters.required_zones)
⏳ Health-метрики (frigate_mqtt_connected, events_received) — TODO
Несколько сессий попыток реализовать audio mixing в композитор'е.
Не достигнуто sub-секундной latency со стабильным video+audio.
Откатано на parallel mode (cfc-grid video-only, live от pipeline с audio).
Полный набор выводов и pitfall'ов — docs/LESSONS-audio-mixing-attempts.md.
Главные lesson'ы для будущей попытки:
- mpegts mux libavformat авто-инсёртит h264_mp4toannexb BSF которому
не нравится Annex-B + inline SPS/PPS — NVENC OUTPUT_SPSPPS per-frame ломает
- SPSC ring drop newest при full, не oldest (consumer's domain)
- av_new_packet (не av_malloc) для av_interleaved_write_frame ownership
- Monotonic PTS на counter (frame_idx, total_samples) — не wallclock
- mediamtx env-var path names не должны иметь '-' (parser limitation)
- Default mediamtx ReadTimeout=10s короткий для burst write'ов
Изменения в repo сохранены для будущей доработки:
- src/writer.c — mpegts backend с audio stream support
- src/audio.c — RTSP AAC consumer + lock-free SPSC ring
- include/cuframes_composer/{writer,audio}.h — public API
- examples/grid_record.c — --format=mpegts + --audio-source flags
- include/cuframes_composer/composer.h — consumer_prefix field
- docker/Dockerfile — libavformat-dev добавлен в builder/runtime
cfc-grid composer стабильно работает на видео (substantially лучше
монолитного pipeline'а с audio bag'ом). TV рекомендуется использовать
rtsp://...:554/cfc-grid + опционально rtsp://...:554/live-audio
parallel.
Phase 5e revisited: visual artefacts в bottom rows 16-cell grid'а оказались
не race condition в cuframes ring buffer (как первоначальная гипотеза),
а burstiness IDR. Сложный grid (много границ между ячейками) генерит
огромные IDR (~400 КБ), которые переполняют mediamtx writeQueueSize=256
и discard'ятся → VLC видит покалеченный bitstream.
Правильное решение — canonical low-latency streaming pattern: вместо
периодических IDR использовать NVENC intra refresh. Вместо одного gigant'а
intra-кадра раз в секунду, кодируем N столбцов intra-блоков в каждом кадре.
За intra_refresh_period кадров — полный refresh. Bitstream становится
почти ровным — все кадры одинакового размера, никаких spike'ов.
Это индустриальный стандарт для low-latency: WebRTC, Twitch low-latency
mode, GeForce NOW, Zoom — все используют intra refresh без IDR.
Trade-off: новый клиент ждёт ~1 период (1 сек) для построения reference
frame. Для CCTV приемлемо. ffmpeg snapshot one-shot не работает на таких
потоках (нужен полный warmup), но VLC/TV/Frigate handles штатно.
Содержимое:
- cfc_encoder_config_t: добавлены intra_refresh + intra_refresh_period.
- nvenc.c: при enableIntraRefresh=1 устанавливается
intraRefreshPeriod/intraRefreshCnt и идуs IDR period отключается через
NVENC_INFINITE_GOPLENGTH.
- examples/grid_record: флаг --intra-refresh (период = fps).
Live-validated: rtsp://192.168.88.23:554/load16ir
- 16-cell 1080p 4×4 6Mbps intra refresh ON
- mediamtx tишина: ни одного 'reader is too slow' warning'а
- VLC connect → чистая картинка во всех 16 ячейках
- 100 кадров логи: '1 IDR' (только начальный) — после стартового никаких
больше IDR не было, ровный bitstream
Этот flag не default для случая single-source (Phase 1 simple_record) —
там IDR-based GOP всё ещё лучше (полный keyframe = быстрый connect).
Включать осознанно для multi-source grid'ов через --intra-refresh.
Phase 4a — control plane через ZMQ REP socket с JSON-командами.
Позволяет операторам менять text overlay'и runtime'ом без рестарта.
Live-validated: команды set_text label-parking → "⚠ ВНИМАНИЕ ⚠"
и set_text label-gate → "Машина у ворот" отрабатывают, текст
обновляется в потоке RTSP без перерывов.
Phase 4b — MQTT health publisher через libmosquitto. Каждые 10с в
composer/<instance>/health публикуется JSON {active,stale,dead,total,
uptime_s} с retain=true. Опционально публикуется HA MQTT discovery
config — четыре сенсора (active/stale/dead/total) появляются в HA
автоматически с expire_after=30s.
Содержимое:
- include/cuframes_composer/overlay.h — cfc_overlay_set_id/get_id/get_type
для lookup'а через control plane.
- include/cuframes_composer/composer.h — cfc_composer_find_overlay(id).
- include/cuframes_composer/control.h — cfc_control_config_t (endpoint +
composer + cuda_ctx для text rebuild в worker thread).
- src/control.c — ZMQ REP socket в фоновом потоке + zmq_poll с 200мс
timeout'ом для проверки stop_flag. JSON-диспатчер для команд ping /
health / set_text / set_visible / list_overlays. cuCtxSetCurrent
на старте worker'а — без этого update_text валится на cuMemAlloc.
- include/cuframes_composer/health.h — cfc_health_config_t (host/port/
user/pass + topic_prefix/instance + interval + publish_discovery).
- src/health.c — mosquitto_connect_async + loop_start + background
thread с publish health каждые N секунд + один разовый publish_discovery
для HA.
- examples/grid_record — флаги --control tcp://0.0.0.0:5599,
--mqtt host[:port], --mqtt-instance NAME, --mqtt-user/--mqtt-pass.
Для text overlay'ев — prefix "id=NAME:" в --text задаёт control-plane ID.
CMakeLists.txt — find_library(zmq), find_library(json-c),
find_library(mosquitto) + linkage всех трёх.
Production TODO: создать отдельного MQTT user'а для composer'а вместо
переиспользования frigate creds (используется только в smoke).
Phase 3c — динамический текст через libfreetype6: открывается font
(.ttf/.otf), измеряется bounding box строки, рендерится в RGBA-атлас
с anti-aliased alpha из FT bitmap, заливается в VRAM. На каждом
кадре блитится через cfc_cugrid_blit_rgba_nv12 (kernels уже есть).
Поддержка UTF-8 через простой inline-decoder (1/2/3/4-byte).
cfc_overlay_update_text() переподдерживает re-render atlas (text /
color change) — для Phase 4 ZMQ control plane (динамическое изменение
NO SIGNAL / RECORDING / timestamp'ов).
Адаптация под GTX 1050 (Pascal) в проде:
- CMAKE_CUDA_ARCHITECTURES = 61;75;86;89;120 (Pascal + Turing +
Ampere + Ada + Blackwell, покрывает все production-кейсы)
- grid_record default снижен с 4K@10Mbps на 1080p@4Mbps. 4K требует
явных --width 3840 --height 2160 (Pascal NVENC 4K H.264 на грани).
- Ссылка на research-документ + NVENC §Input Buffers рядом со staging
buffer'ом в nvenc.c с цитатой "the client is required to use buffers
allocated using the cuMemAlloc family of APIs".
Содержимое Phase 3c:
- overlay.h — cfc_overlay_create_text + update_text + text_size.
- overlay.c — utf8_next decoder, text_measure (ascender/descender),
text_render (alpha-over blend в RGBA), text_rebuild_atlas (VRAM
cycle), full FreeType lifecycle.
- CMakeLists.txt — find_package(Freetype REQUIRED) + link
Freetype::Freetype.
- examples/grid_record — флаг --text font,size,r,g,b,x,y,text.
Live-validated на rtsp://192.168.88.23:554/cfc-grid с иконками
temp_outside.png и offline.png из cuda_grid_icons volume.
Содержимое:
- cugrid.h/cugrid.cu — cfc_cugrid_blit_rgba_nv12 (Y+UV α-blend) +
два новых kernel'я blit_rgba_y/uv (BT.709 limited-range RGB→YUV
conversion, 4:2:0 chroma 2x2 averaging).
- overlay.h — cfc_overlay_create_png + cfc_overlay_png_size +
cfc_overlay_update_png.
- overlay.c — libpng decode (paletted/gray/16-bit → 8-bit RGBA),
cuMemAlloc atlas в VRAM, cuMemcpyHtoD один раз, draw_png через
cfc_cugrid_blit_rgba_nv12.
- CMakeLists.txt — find_package(PNG REQUIRED) + PNG::PNG в link.
- examples/grid_record — флаг --icon path,x,y[,alpha] (несколько раз
можно). Атлас грузится один раз при старте.
PNG из cuda_grid_icons volume переиспользуются (offline, temp_outside,
grafana_gpu, и пр.). PNG decode'ятся одинаково — paletted, RGBA, gray.
Phase 3c (text rendering через FreeType + font atlas) — отдельный
commit. Атлас text'а тоже окажется в VRAM как RGBA через тот же
cfc_cugrid_blit_rgba_nv12 — kernels уже готовы.
Live-validated на rtsp://192.168.88.23:554/cfc-grid: цветные рамки
вокруг каждой ячейки реализованы через 4 cfc_cugrid_fill_nv12
(top/bottom/left/right) — отдельного kernel'я не понадобилось,
переиспользуем existing fill_nv12 для region α-blend.
Содержимое:
- include/cuframes_composer/overlay.h — opaque cfc_overlay_t + типы
(BORDER реализован, PNG/TEXT skeleton'ы для Phase 3b/3c).
- src/overlay.c — реализация BORDER: clamp rect в границы кадра,
выравнивание координат на чётные (4:2:0 chroma требование),
thickness clamp если 2*thickness > w/h.
- composer.c — список overlays (max 64), z-order = порядок добавления,
draw поверх grid'а перед stream sync.
- examples/grid_record — флаг --border N (толщина пикселей) добавляет
серо-голубой border (Y=180,U=120,V=110 alpha=220) для каждой
ячейки автоматически.
Phase 3b (PNG icons через stb_image + cugrid alpha_blit_rgba) и Phase
3c (text через FreeType + font atlas) — отдельные commit'ы.
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 дизайн-документа).
Скелет проекта cuframes-composer (LGPL-2.1+) и MVP кодирования
одного источника в файл H.264.
Что включает Phase 1:
- LICENSE (LGPL-2.1+), README с поэтапным планом, корневой CMake
- Подмодули: cuframes v0.4 (pinned), nv-codec-headers (n12.2.72.0)
- include/cuframes_composer/source.h — публичный API источника
с явной машиной состояний (DISCONNECTED → CONNECTING → ACTIVE →
STALE → DEAD) и snapshot-паттерном для чтения без блокировки
- include/cuframes_composer/nvenc.h — публичный API кодировщика
на CUdeviceptr-вход (zero-copy через VMM-mapped указатели)
- src/nvenc_loader.{h,c} — dlopen libnvidia-encode.so.1 и инициализация
таблицы функций NVENC через NvEncodeAPICreateInstance. Идёт через
pthread_once. Сделано отдельно чтобы держать LGPL-совместимость:
проприетарный SDK не статически линкуется
- src/nvenc.c — обвязка над NVENC: open session, init encoder, кеш
registered resources, encode/lock/unlock, flush с EOS, поддержка
H.264 CBR low-latency, preset GUID p1/p4/p7
- src/source.c — обвязка над cuframes_subscriber c фоновым потоком,
exponential backoff reconnect (1с → 30с), и переходами по таймаутам
для stale/dead-детекта
- examples/simple_record — smoke-test программа: подписка на cuframes,
кодирование, запись в .h264 файл, корректное завершение по SIGINT