Commit Graph

39 Commits

Author SHA1 Message Date
gx 265c5c9503 detbox: label + score pill над bbox (FreeType)
Раньше 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>
2026-06-18 03:08:14 +01:00
gx 7bd8184159 frigate_mqtt: unique client_id per instance (несколько subscriber'ов)
Bug: client_id format "composer-frigate-%d" использовал только now_ms().
Когда создаются 2 subscriber'а подряд (frigate + yoloworld) — оба
получают один client_id за тот же мс, mosquitto kick'ает старый при
connect нового → infinite reconnect loop, ни один не получает events
стабильно.

Fix: static _Atomic int instance_seq counter — tie-break суффикс.
Format: "composer-frigate-%d-%d".

Verify mosquitto logs:
  composer-frigate--2117451292-0  (frigate subscriber)
  composer-frigate--2117451292-1  (yoloworld subscriber)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:50:28 +01:00
gx ce7aa6cb8d grid_record: добавить --yw-mqtt + --yw-topic для magenta bbox от YOLO-World
Параллельный MQTT subscriber на отдельный topic (default yoloworld/events)
от yolo-world-detector сервиса. Использует те же --detection-cell что
Frigate, но рендерит bbox magenta цветом (Y=105 U=212 V=234) вместо
зелёного (Y=210 U=50 V=100).

На одной картинке композитор может одновременно показать:
- зелёный bbox от Frigate (person/car/...)
- magenta bbox от yolo-world (fox/dog/drone/...)

Backward compat 100% — без --yw-mqtt никаких изменений в поведении.

Архитектурно:
- Второй cfc_frigate_mqtt_t (тот же envelope schema через
  cfc_frigate_mqtt parser — yolo-world-detector публикует
  Frigate-compat events)
- Параллельный yw_detbox_overlays[] с magenta colors
- yolo-world subscriber не управляет motion-layout (composer=NULL),
  только Frigate шлёт motion-pulse'ы

Verify build:
  cmake --build build  # все 4 target'а (grid_record, simple_record,
                       # grid_record_cpp, cuframes_composer_static)

Live test пока через docker recreate в Phase 5 deploy, после того
как yolo-world publish'ит в реальный MQTT.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 18:32:17 +01:00
gx e54d55371c templates.json: tpl_3 теперь 4 камеры + temp_chart-узкий
Было: tpl_3 = main + 2 preview + temp_chart (rs=4) + ha_chat.
При 4 active композитор выбирал tpl_3 (waste=1 vs tpl_4 waste=0 — wait,
tpl_3 имел 3 cam cells, при 4 active брал tpl_4=quad без виджетов).

Стало: tpl_3 = main + 3 preview + temp_chart (rs=2, низ-право) + ha_chat.
4 cam cells, при 4 active best-fit выбирает tpl_3 (waste=0, раньше в registry
чем tpl_4 → tie-breaker за tpl_3). Виджеты остаются видимыми.

Geometry validated: 36+4+4+4+4+12=64 микроячеек, no overlaps.

Hot-reloaded via reload_templates ZMQ в проде.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 19:17:56 +01:00
gx e76360dbc4 docs: руководства пользователя / разработчика / operations (RU+EN)
Полный комплект документации к Phase 11b:

  docs/ru/user.md        — для админа инсталляции (motion-mode, PTZ,
                            templates.json, mqtt_overlays.json, ZMQ verbs)
  docs/ru/developer.md   — архитектура (Cell / Layout / Decoration),
                            как добавить новый Cell/Decoration, ABI shim,
                            algorithms (best-fit + asymmetric hysteresis)
  docs/ru/operations.md  — build (host + jammy + incremental bake),
                            deploy, logs/telemetry, troubleshooting
                            (broken pipe, MQTT-overlay, motion-mode)
  docs/en/*.md           — английская версия всех трёх
  README.md              — переписан с overview + ссылками на docs/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 22:02:47 +01:00
gx b68d00604f Merge phase11b-cpp: C++ refactor композитора (Phase 11b)
13 commits объединяются в main. C++17 ООП-модель Cell / Layout /
Decoration через extern "C" ABI shim — старые callers работают
без изменений.

Ключевое:
  - cpp/cuda_raii.hpp: RAII обёртки (CudaBuffer/CudaStream), zero-copy
  - cpp/cell.hpp + camera_cell/widget_cell/blank_cell: иерархия cells
  - cpp/decoration.hpp + label_decoration/border_decoration: композиция
  - cpp/layout.hpp + template.hpp: 8×8 микро-сетка, JSON-templates
  - cpp/source_pool: pool + motion-state, by_frigate_camera lookup
  - cpp/composer: best-fit + asymmetric hysteresis (рост 0s, shrink 3s),
    fill свободных cells остальными drawable, manual PTZ override 60s
  - cpp/mqtt_overlay: generic MQTT-driven text overlays из JSON,
    подложка, anchors
  - composer_c_api.cpp + layouts_c_api.cpp: extern "C" wrappers
  - Удалены: src/composer.c, src/layouts.c
  - Detection box bbox следует за камерой при смене layout

В проде: gx/cuframes-composer:0.11b-step1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 19:03:17 +01:00
gx 3730c65a1e mqtt_overlays: убраны assist_question / assist_response
Conversation events HA Assist не приходят (архитектурное ограничение).
Чтобы placeholders ❯ Вы: ... и ✎ HA: ... не висели на экране без
данных — overlay'и убраны до решения вопроса с источником диалога.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 19:01:31 +01:00
gx a8ce3f1ccb mqtt_overlays.json: добавлены HA Assist question / response overlays
Подписка на assist/conversation/text + /response (plain-text retained,
schema см. home-assistant/.../assist_mqtt.md).

Позиция: левый-нижний угол стопкой —
  🤖 ответ внизу (margin_y=32)
  🗣 вопрос выше (margin_y=78)

Полупрозрачная подложка (bg_alpha=180), placeholder с эмодзи пока
turn'а не было.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 15:22:04 +01:00
gx d8e69c6392 Phase 11b: detection-box bbox следует за камерой при смене layout
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>
2026-06-04 10:25:25 +01:00
gx 88fa73f922 Phase 11b: подложка для text overlay + сразу-видимый placeholder
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>
2026-06-04 10:19:29 +01:00
gx 75271436f7 Phase 11b: MQTT-overlays конфигурируются через JSON (вместо --temp-topic)
User: "оверлеев которые выводят какую-то инфу из MQTT может быть
бесконечно много. Надо делать настраиваемым через json/yaml конфиг".

Заменили захардкоженный TempMqttOverlay на generic MqttOverlay:

  - cpp/mqtt_overlay.hpp/.cpp: класс MqttOverlayItem (один topic + один
    text overlay) + MqttOverlayManager (контейнер). Загрузка из JSON.
  - cpp/mqtt_overlay_c_api.cpp: extern "C" обёртка для grid_record.c.
  - docker/mqtt_overlays.json: default config с temp_outside примером.
  - grid_record.c: --mqtt-overlays=PATH (заменил --temp-topic).
  - src/CMakeLists.txt: temp_overlay* удалены, mqtt_overlay* добавлены.

JSON schema:
  {
    "overlays": [
      {
        "id": "temp_outside",
        "topic": "zigbee2mqtt/Температура на улице",
        "json_field": "temperature",      // пусто = raw payload string
        "format": "%+.1f°C",                // printf
        "anchor": "right-bottom",           // right-top, left-bottom, ...
        "margin_x": 32, "margin_y": 24,
        "pixel_size": 32,
        "color": [255, 255, 255], "alpha": 230,
        "font_path": "/fonts/DejaVuSans-Bold.ttf"
      }
    ]
  }

MqttBrokerCfg делятся между всеми overlays (one connect_async per item
но shared credentials). Добавление новых overlays = редактирование JSON +
restart cfc-grid (hot-reload через ZMQ — Phase 12).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 10:07:05 +01:00
gx 362871a264 Phase 11b: глобальный temperature overlay (правый-нижний угол)
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>
2026-06-04 09:58:47 +01:00
gx 24d398526e Phase 11b: вернули asymmetric layouts с widget cells
User: "где сетки main+preview, сетки с виджетами?". Восстановили из
прежней истории:
  - tpl_3 (1+2+widget+widget): main 1440×810 + 2 преview 480×270 +
    widget temp_chart справа + widget ha_chat снизу
  - tpl_5 (1+4+widget): main + 4 preview right column + ha_chat снизу
  - tpl_6 (1+3+2+widget): main + 3 правых + 2 нижних + widget info
  - tpl_7 (1+3+3+widget): main + 3 правых + 3 нижних + widget ha_chat
  - tpl_8 (1+3+4): main + 3 правых + 4 в нижней строке (полное покрытие)
  - tpl_9 — 3×3 + widget полосы справа/снизу

Widget cells — placeholder (тёмно-серый Y=40 + LabelDecoration с именем
widget'а). Реальные виджеты (HA chat, температурный график) — Phase 12+.
Chёрные ячейки больше не будут — composer.maybe_relayout заполняет
свободные camera-cells остальными drawable из pool.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 09:44:00 +01:00
gx e8392dd5ff Phase 11b: серые рамки 2px вокруг каждой cell
User: "ячейки не имеют границ, сделай их серым цветом". В Layout::apply()
для каждого Cell (CameraCell/BlankCell/WidgetCell) добавлен BorderDecoration:
  - thickness 2px
  - Y=180, U=128, V=128 (нейтральный серый в BT.709 limited)
  - alpha 220 (слегка полупрозрачный — видно контент за рамкой)

Decoration рисуется поверх content (после draw_content) — поверх любого
кадра/widget'а.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 09:30:11 +01:00
gx 858fe61b56 Phase 11b: hybrid PTZ — set_layout с timed motion freeze
User: "PTZ снова не переключает сетки". Причина: при motion-mode set_layout
игнорировался. Теперь: применяется + замораживает motion-mode на
manual_override_duration_ms_ (60s default). По истечении — auto-возврат
в motion-mode.

В Composer добавлено:
  - manual_override_until_ms_ (моноклоk монотонное время)
  - manual_override_duration_ms_ (default 60s)
  - set_layout: применяет template, ставит override timestamp
  - maybe_relayout: пока now < override → пропускаем (sustain manual layout),
    после → лог "expired, возврат в motion-mode" + force relayout

ONVIF server.py одновременно обновлён под актуальные template имена:
  - PTZ_PRESETS: tpl_1 / tpl_4 / tpl_9 / tpl_16 (вместо single/quad/...)
  - ContinuousMove zoom-in → tpl_1, zoom-out → tpl_16,
    pan/tilt → cycle через эти 4

Production smoke:
  GotoPreset tpl_4 → composer log "manual override 'tpl_4' до +60000ms" PASS.

Refs: #195.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 09:24:40 +01:00
gx 9d2a0b2bd7 Phase 11b: fill свободных camera-cells остальными drawable камерами
User: "смущает чёрная ячейка в сетке". Причина: asymmetric templates
имели widget cells (placeholder тёмно-серый Y=40) + при недостатке active
camera cells оставались BlankCell (чёрный).

Два изменения:

1. templates.json — оставили только 16:9 layouts (tpl_1/tpl_4/tpl_9/tpl_16).
   Все camera-cells, никаких widget-областей. Cells full 16:9 (cs==rs
   микроячейки), полностью покрывают output 1920×1080 без чёрных полос.
   Asymmetric layouts (main + satellites) удалены — вернуть в Phase 12
   когда widget'ы будут реальными (HA-chat, temperature graph).

2. composer::maybe_relayout — заполнить свободные camera-cells остальными
   drawable камерами из pool (по priority), если template имеет больше
   cells чем motion-active. Условие: cap > active.size().

Производство при 4 источниках в pool:
  - 1 motion → tpl_1 (1 cell full screen)
  - 2 motion → tpl_4 (2 motion + 2 not-active drawable = 4 cells заняты)
  - 4 motion → tpl_4 (все 4 motion)
  - При добавлении новых камер (до 16) — tpl_9 при 5..9, tpl_16 при 10..16

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 09:15:20 +01:00
gx 6e0273f4b4 Phase 11b F: extern "C" ABI shim + production deploy
В прод деплоен 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>
2026-06-04 08:57:30 +01:00
gx beb8e1baa0 Phase 11b B-E: ООП-гипотеза проверена end-to-end
Что в этом коммите:

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>
2026-06-03 21:43:18 +01:00
gx f1c79eabde Phase 11b A: CMake C++17 + базовые headers
Branch phase11b-cpp — refactor композитора на ООП.

Что сделано в этом коммите:
  - CMakeLists.txt: CMAKE_CXX_STANDARD 17, language=CXX
  - include/cuframes_composer/cpp/cuda_raii.hpp: CudaBuffer + CudaStream
    как RAII обёртки (cuMemAlloc/cuMemFree, cuStreamCreate/Destroy).
    Non-copyable, movable. Zero-copy: handle CUdeviceptr передаётся
    идентично C-коду.
  - cpp/types.hpp: Rect (pixel coords) + NV12Ref (общий read-write
    референс на Y/UV plane'ы output буфера — composer + cells + decorations
    делят его без копий).
  - cpp/decoration.hpp: абстрактный Decoration с draw(stream, dst, parent_rect).
  - cpp/cell.hpp: абстрактный Cell с draw() = draw_content() +
    iterate decorations. Композиция через add_decoration().

Что НЕ сделано (следующие коммиты):
  - CameraCell, WidgetCell, BlankCell (cell-content реализации)
  - LabelDecoration, BorderDecoration (с FreeType/cugrid)
  - Layout (контейнер cells + apply_template)
  - Composer класс (owner SourcePool + Layout + OutputSurface)
  - extern "C" ABI shim для совместимости с control.c, grid_record.c
  - Удаление старых composer.c / overlay.c / layouts.c
  - Восстановление функционала JSON templates + auto-labels

Производительность: virtual call overhead 1 indirect call per cell per
frame (negligible), никаких heap allocations в hot path, CUDA pipeline
1:1 идентичен C-версии.

Refs: #195 (Phase 11b C++ refactor)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 21:27:44 +01:00
gx f8e27b9e85 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>
2026-06-03 21:24:23 +01:00
gx 2d7fc1e640 Phase 9 #193: runtime layout switching через ZMQ set_layout
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>
2026-06-03 18:14:58 +01:00
gx ac86534769 Phase 7 #190: zone-filter в Frigate MQTT subscriber
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>
2026-06-03 16:18:56 +01:00
gx 17261377cb Phase 7 task #190: Frigate→cfc-grid overlay через MQTT subscribe
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
2026-06-03 15:28:52 +01:00
gx fa6ab3069a Phase 7 audio mixing — attempt + rollback + lessons
Несколько сессий попыток реализовать 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.
2026-06-03 14:29:56 +01:00
gx 20b5234c41 docker: HEALTHCHECK через TCP probe ZMQ control port
netcat-openbsd добавлен в runtime layer (~50KB). HEALTHCHECK probe'ит
listening на 5599 (ZMQ control). Full functional ping через nc не сработает
(ZMTP требует binary greeting handshake), но liveness через TCP-проверку
достаточен для docker-compose restart-on-unhealthy логики.

Functional проверки делегированы external monitors:
  - MQTT health uptime_s растёт = composer не зависает
  - HA discovery sensor.composer_cfc-grid_active = camera-feed health
  - Phase 7 (Prometheus) добавит composition_time_us / fps metrics

Live-validated: cfc-grid container статус 'healthy' через 25с после
recreate, healthcheck Log shows exit 0.
2026-06-03 08:36:03 +01:00
gx cd4f3114d6 docker: CUDA 12.4 builder + runtime + PTX forward-compat для Pascal+Blackwell
CUDA 13 убрал sm_61 (Pascal), CUDA 12.4 убрал sm_120 (Blackwell).
Sweet spot: CUDA 12.4 с PTX-only для sm_89, JIT compile на Blackwell.

CMAKE_CUDA_ARCHITECTURES=61-real;75-real;86-real;89-real;89-virtual
покрывает Pascal/Turing/Ampere/Ada native + PTX для forward JIT.

Ubuntu 22.04 jammy glibc 2.35 forward-compatible с host 24.04 noble.

.dockerignore исключает build/ (от хостовой cmake) — иначе
docker build падает с 'CMakeCache directory mismatch'.
2026-06-03 07:51:57 +01:00
gx b8661f4017 nvenc: intra refresh для low-latency multi-source push
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.
2026-06-03 07:05:35 +01:00
gx af70829a1d Phase 5e: load test 16 источников 4K @ 25fps на RTX 5090
Результат:
  46+ секунд работы, 1150 кадров, 0 drops
  FPS = 25.0 СТАБИЛЬНО
  GPU util = 47% (большие запасы для масштабирования)
  16 active, 0 stale, 0 dead — все подписки удерживаются
  ffprobe видит валидный h264 3840x2160 25/1 на mediamtx

Visual artifact в rows 2-4 (вертикальные полосы) — гипотеза:
race condition при 4 cfc_source_t подписках на одного publisher'а
(16 consumers vs 16-slot cuframes ring buffer = tight fit).
FPS не пострадало, но визуально половина ячеек испорчена.

Phase 5e-fix planned: composer должен detect одинаковые source_key
и переиспользовать один cfc_source_t для N ячеек. Это устранит
multi-consumer race и снизит memory footprint.

Подробности + снимок в docs/LOADTEST-PHASE-5e.md +
docs/phase5e-load16.jpg.
2026-06-03 06:34:28 +01:00
gx c6882e46f7 Phase 5b: adversarial — ВСЕ источники мертвы
Composer настроен на 4 несуществующих cuframes-ключа. Live результат:
  health: {total:4, active:0, stale:0, dead:4}
  FPS: 25.2 СТАБИЛЬНО
  Encoder рендерит чёрный grid с красными NO_SIGNAL подписями
    + серо-голубыми border'ами
  ffprobe видит валидный h264 1920x1080 25/1 на mediamtx

Это самый жёсткий adversarial scenario — полная потеря сети камер.
Composer не падает, encoder работает на холостом ходу, RTSP-publish
непрерывен, MQTT health корректно отдаёт dead=4 (HA-алерты через
expire_after=30s).

Доказан главный production threat: обесточивание PoE-свитча /
uplink-отрезание не валит весь pipeline. TV видит NO_SIGNAL вместо
разрыва соединения, HA-оператор уведомляется.

Полный отчёт + снимок в docs/ADVERSARIAL-TEST-PHASE-5b.md +
phase5b-all-dead.jpg.
2026-06-03 06:29:59 +01:00
gx 804cadda08 Phase 6 prep: production Dockerfile + docker-compose пример
docker/Dockerfile — multi-stage build:
  builder: nvidia/cuda:12.6.0-devel-ubuntu24.04 + apt build-deps + cmake
  runtime: nvidia/cuda:12.6.0-runtime-ubuntu24.04 + apt runtime-deps
           + grid_record/simple_record + libcuframes.so.0 + libcuframes_composer.so.0

NVIDIA_DRIVER_CAPABILITIES=compute,utility,video в env image'а — обязательно
для NVENC dlopen libnvidia-encode.so.1.

docker/docker-compose.example.yml — образец для интеграции в
localhost-infra/hosts/R9-88.23/docker/cctv/. Два сервиса:
  cfc-grid          — композитор + control plane + MQTT health
  cfc-grid-ffmpeg   — pipe→RTSP push (использует существующий
                       ffmpeg-vf-cuda-grid image для совместимости glibc)

Сообщение в docs/docker/docker-compose.example.yml объясняет зависимости
(cuframes-ipc-anchor, cctv-mosquitto и т.д.) и нужный MQTT user.

Phase 6 deploy (intent перевести production):
  - Создать MQTT user composer + добавить в localhost-infra mosquitto passwd
  - Запустить cfc-grid параллельно с cuda-grid-pipeline (разные RTSP paths,
    не конфликтуют)
  - После проверки на проде — retire старый cuda-grid-pipeline
  - Обновить TV-плейлисты на новый rtsp://...:554/cfc-grid

Эти шаги требуют отдельных commit'ов в localhost-infra repo с явным
user approval, поэтому здесь только prep.
2026-06-03 06:26:14 +01:00
gx 4efaea8268 Phase 5a: adversarial test — отключение источника
Live-validated сценарий: stop cuframes-pub-back_yard, наблюдение
через ZMQ /health.

Timeline:
  t=0s:   4 active, 0 stale, 0 dead    baseline
  t+2s:   3 active, 1 stale            STALE через 500мс threshold
  t+10s:  3 active, 1 dead              DEAD через 5000мс threshold
                                        FPS = 25.0 СТАБИЛЬНО
                                        ячейка → blackout
  restart pub:
  t+~13s: 4 active, 0 stale, 0 dead    auto-reconnect отработал

Главный threat model подтверждён: отключение источника не валит
композитор, остальные 3 ячейки продолжают работу, RTSP не прерывается.

Полный отчёт в docs/ADVERSARIAL-TEST-PHASE-5a.md.

Phase 5b/5c (multi-source disconnect, mediamtx restart, 24h memory
test, 16-source load test) — отдельные сессии.
2026-06-03 06:24:43 +01:00
gx 636b70b64c Phase 4: ZMQ control plane + MQTT health publisher
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).
2026-06-03 06:20:38 +01:00
gx e02998cea7 Phase 3c: TEXT overlays через FreeType + GTX 1050 (Pascal) compat
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.
2026-06-03 05:44:57 +01:00
gx 8cc5a5acfb Phase 3b: PNG icon overlays через libpng + alpha_blit_rgba_nv12
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 уже готовы.
2026-06-03 05:34:07 +01:00
gx 87daff313e Phase 3a: BORDER overlays через 4 fill_nv12 операции
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'ы.
2026-06-03 05:20:13 +01:00
gx 962bea11ca Phase 2 RTSP: --out - / pipe:1 для прямого pipe в ffmpeg
Подтверждено live-тестом end-to-end в продакшен-условиях:
  grid_record --out -  →  ffmpeg -i pipe:0 -c copy -f rtsp -rtsp_transport tcp  →
  mediamtx rtsp://...:554/cfc-grid  →  VLC на MacBook

VLC принимает поток штатно, картинка не тормозит — лучше чем у
предыдущего цельного ffmpeg-pipeline (vf-cuda-grid). 1920x1080
H.264 High@4.0 4Mbps, 4 источника active.

Изменения тривиальные: stdout не закрывается через fclose, чтобы
пайп оставался открытым для дочернего ffmpeg-процесса.
2026-06-03 05:15:47 +01:00
gx 1e2b5d4e16 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 дизайн-документа).
2026-06-03 05:01:49 +01:00
gx eae902afb3 Phase 1 smoke test зелёный: фикс frame_busy + staging buffer
После live-теста на R9-88.23 с реальным publisher'ом cam-parking
(Dahua 1920x1080 @ 25fps) выявлены и поправлены два блокера:

1) source.c: release_current_frame ПЕРЕД cuframes_subscriber_next.
   cuframes валидирует frame_busy в начале next (consumer.c:334)
   и возвращает CUFRAMES_ERR_INVALID_ARG если предыдущий frame
   не release'нут. Прошлая реализация делала release ПОСЛЕ next —
   получалось много invalid_arg и каждые ~1с поток re-subscribe'ился,
   throughput падал до 0.87 fps. После фикса — стабильные 25.1 fps.

2) nvenc.c: добавлен staging buffer cuMemAlloc + cuMemcpy2D в encode_frame.
   NVENC nvEncRegisterResource возвращает RESOURCE_REGISTER_FAILED для
   VMM-mapped указателей cuframes v0.4 (CUdeviceptr из cuMemMap).
   Это известное ограничение NVENC SDK для VMM-памяти. Pre-copy в
   собственный cuMemAlloc-буфер решает проблему и сохраняет всё в VRAM
   (cudaMemcpyDeviceToDevice, без CPU bounce). Phase 1.1 будет research
   как получить настоящий zero-copy (cuMemExportToShareableHandle?).

Smoke test результаты (cuframes-dev image + NVIDIA_DRIVER_CAPABILITIES
video, /run/cuframes mount + cuframes-ipc-anchor IPC namespace):

  300 кадров, 12 IDR, 6.9 МБ за 11.9с (25.1 fps)
  достигнут лимит 15с
  flush encoder
  ffprobe: 377 кадров, 1920x1080 yuv420p H.264 High@4.0
  ffmpeg decode → PNG/JPG → реальное изображение с камеры
2026-06-03 04:42:41 +01:00
gx ba68550f4c Phase 1: NVENC через dlopen + источник через cuframes_subscriber
Скелет проекта 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
2026-06-03 04:28:33 +01:00