[Epic] vf-cuda-grid — implementation roadmap (Phases 1-6) #1

Open
opened 2026-05-19 20:38:46 +01:00 by gx · 9 comments
Owner

Epic: vf-cuda-grid implementation

Tracking issue для 6 phases implementation plan'а.

Дизайн зафиксирован в docs/design.md (1124 строки) — architect-reviewed.

Phases (последовательно — каждая зависит от предыдущей)

Phase 1 — MVP filter (PR-1)

  • Out-of-tree FFmpeg patch libavfilter/vf_cuda_grid.c
  • Fixed quad layout (2×2 hardcoded), 4 CUDA inputs → 1 output
  • CUDA memcpy regions (без scaling — input cell size == output cell size)
  • Configure-time --enable-vf-cuda-grid (chained на cuda-llvm? см. design §3)
  • CLI usable: ffmpeg -i ... -filter_complex "[0][1][2][3]cuda_grid[out]" ...
  • Smoke test — 4 cameras (cuframes://) → 1 mp4 quad output

Acceptance: локально на R9 работает ffmpeg -hide_banner -filter_complex "[0:v][1:v][2:v][3:v]cuda_grid[out]" -map "[out]" out.mp4 с фиксированной quad сеткой.

Phase 2 — Dynamic layouts + scaling (PR-2)

  • Layout DSL parser (см. design §4)
  • Per-cell scaling через CUDA NPP или custom kernel
  • Predefined templates: single, dual_horizontal, dual_vertical, quad, main_plus_preview, panoramic, six_grid, nine_grid, sixteen_grid
  • Filter option layout=<name> для выбора template
  • Aspect ratio handling (letterbox vs fill vs crop)

Acceptance: все 9 templates работают, переключение через rebuild filter graph (runtime будет в Phase 3).

Phase 3 — Sidecar controller (PR-3)

  • cuda-grid-controller Python package (FastAPI + asyncio + pyzmq + aiomqtt)
  • ZeroMQ command flow через FFmpeg's zmq filter (см. design §7)
  • MQTT subscribe + publish (commands IN + events OUT)
  • HTTP REST endpoints (POST /layout/{instance}/set, POST /layout/create, GET /state)
  • HA MQTT Discovery — select.layout, sensor.current_fps, binary_sensor.online
  • Bidirectional events: layout_switched, cell_camera_changed, fps_drop, audio_ducked
  • Filter process_command поддержка для set_layout, create_layout, delete_layout
  • Docker image + systemd unit
  • Integration test — HA toggle → MQTT command → controller → ZMQ → filter → layout switch verified

Acceptance: через HA UI можно переключать layouts на live filter без teardown.

Phase 4 — Basic overlays (PR-4)

  • AVFrame side data types для rect/text/icon
  • CUDA kernels: rect (alpha blending), icon (sprite blit от preloaded texture)
  • Text rendering через freetype + CPU-precomputed glyph atlas → CUDA texture (см. design §5)
  • Controller API: POST /overlay/add (returns overlay_id), DELETE /overlay/{id}, PATCH /overlay/{id}
  • Frigate bridge: subscribe frigate/+/motion + frigate/events → overlays auto

Acceptance: на live grid появляются bbox от Frigate detections + LPR text для проезжающих машин.

Phase 5 — Rich overlays (PR-5)

  • Image overlays (любые PNG/JPG с alpha → texture upload → CUDA blit)
  • Dim/darken areas (alpha rect с tunable opacity)
  • Graphs/charts — Cairo CPU rendering @ 1-5 Hz → texture upload → CUDA blit
  • Chats/scrolling text (история notifications)
  • Privacy filtering — per-instance overlay filter rules

Acceptance: public stream показывает logo + weather widget, private stream — те же overlays + LPR text + motion timeline graph.

Phase 6 — Audio orchestration (PR-6)

  • State machine в controller для use cases (transitions library)
  • Standard FFmpeg amix + sidechaincompress orchestration
  • Use case "domofon": звонок → duck music + swap layout на focus-door + door audio loud
  • Rules engine — YAML config (orchestration_rules.yaml)
  • Integration test — MQTT trigger door_ring → controller orchestrates audio+video transition

Acceptance: реальный setup в localhost-infra — physical doorbell или MQTT-кнопка вызывает full transition pipeline.

Связано

  • design.md — полный архитектурный документ
  • gx/cuframes — frame source
  • gx/ffmpeg-patched n7.1-cuframes — куда патч vf_cuda_grid.c пойдёт (или отдельный fork)
  • gx/cctv#22 Phase 4 — закроется после Phase 4 vf-cuda-grid + миграция cctv-processor (см. design §13)
  • gx/cctv#24 — superseded (см. design §14)

Open для обсуждения (см. design §12 Risks)

  • Pull-based vs push-based input — FFmpeg filter API ограничения
  • Lock-step inputs — что делать если один cuframes source отстаёт
  • Audio path latency vs video — sync через PTS
  • Multi-output sync — independent fps/timeline per screen
  • Migration cctv-processor — какие функции legacy GridComposer не покрываются филтером

Каждый Phase — отдельный PR. Подзадачи на дальнейших Phases можно decompose'ить в sub-issues по мере приближения.

# Epic: vf-cuda-grid implementation Tracking issue для **6 phases implementation** plan'а. **Дизайн зафиксирован** в [`docs/design.md`](../src/branch/main/docs/design.md) (1124 строки) — architect-reviewed. ## Phases (последовательно — каждая зависит от предыдущей) ### Phase 1 — MVP filter (PR-1) - [ ] Out-of-tree FFmpeg patch `libavfilter/vf_cuda_grid.c` - [ ] Fixed quad layout (2×2 hardcoded), 4 CUDA inputs → 1 output - [ ] CUDA memcpy regions (без scaling — input cell size == output cell size) - [ ] Configure-time `--enable-vf-cuda-grid` (chained на cuda-llvm? см. design §3) - [ ] CLI usable: `ffmpeg -i ... -filter_complex "[0][1][2][3]cuda_grid[out]" ...` - [ ] Smoke test — 4 cameras (cuframes://) → 1 mp4 quad output **Acceptance:** локально на R9 работает `ffmpeg -hide_banner -filter_complex "[0:v][1:v][2:v][3:v]cuda_grid[out]" -map "[out]" out.mp4` с фиксированной quad сеткой. ### Phase 2 — Dynamic layouts + scaling (PR-2) - [ ] Layout DSL parser (см. design §4) - [ ] Per-cell scaling через CUDA NPP или custom kernel - [ ] Predefined templates: `single`, `dual_horizontal`, `dual_vertical`, `quad`, `main_plus_preview`, `panoramic`, `six_grid`, `nine_grid`, `sixteen_grid` - [ ] Filter option `layout=<name>` для выбора template - [ ] Aspect ratio handling (letterbox vs fill vs crop) **Acceptance:** все 9 templates работают, переключение через rebuild filter graph (runtime будет в Phase 3). ### Phase 3 — Sidecar controller (PR-3) - [ ] `cuda-grid-controller` Python package (FastAPI + asyncio + pyzmq + aiomqtt) - [ ] ZeroMQ command flow через FFmpeg's `zmq` filter (см. design §7) - [ ] MQTT subscribe + publish (commands IN + events OUT) - [ ] HTTP REST endpoints (`POST /layout/{instance}/set`, `POST /layout/create`, `GET /state`) - [ ] HA MQTT Discovery — `select.layout`, `sensor.current_fps`, `binary_sensor.online` - [ ] Bidirectional events: `layout_switched`, `cell_camera_changed`, `fps_drop`, `audio_ducked` - [ ] Filter `process_command` поддержка для `set_layout`, `create_layout`, `delete_layout` - [ ] Docker image + systemd unit - [ ] Integration test — HA toggle → MQTT command → controller → ZMQ → filter → layout switch verified **Acceptance:** через HA UI можно переключать layouts на live filter без teardown. ### Phase 4 — Basic overlays (PR-4) - [ ] AVFrame side data types для rect/text/icon - [ ] CUDA kernels: rect (alpha blending), icon (sprite blit от preloaded texture) - [ ] Text rendering через freetype + CPU-precomputed glyph atlas → CUDA texture (см. design §5) - [ ] Controller API: `POST /overlay/add` (returns overlay_id), `DELETE /overlay/{id}`, `PATCH /overlay/{id}` - [ ] Frigate bridge: subscribe `frigate/+/motion` + `frigate/events` → overlays auto **Acceptance:** на live grid появляются bbox от Frigate detections + LPR text для проезжающих машин. ### Phase 5 — Rich overlays (PR-5) - [ ] Image overlays (любые PNG/JPG с alpha → texture upload → CUDA blit) - [ ] Dim/darken areas (alpha rect с tunable opacity) - [ ] Graphs/charts — Cairo CPU rendering @ 1-5 Hz → texture upload → CUDA blit - [ ] Chats/scrolling text (история notifications) - [ ] Privacy filtering — per-instance overlay filter rules **Acceptance:** public stream показывает logo + weather widget, private stream — те же overlays + LPR text + motion timeline graph. ### Phase 6 — Audio orchestration (PR-6) - [ ] State machine в controller для use cases (`transitions` library) - [ ] Standard FFmpeg `amix` + `sidechaincompress` orchestration - [ ] Use case "domofon": звонок → duck music + swap layout на focus-door + door audio loud - [ ] Rules engine — YAML config (`orchestration_rules.yaml`) - [ ] Integration test — MQTT trigger `door_ring` → controller orchestrates audio+video transition **Acceptance:** реальный setup в localhost-infra — physical doorbell или MQTT-кнопка вызывает full transition pipeline. ## Связано - **design.md** — полный архитектурный документ - `gx/cuframes` — frame source - `gx/ffmpeg-patched` n7.1-cuframes — куда патч `vf_cuda_grid.c` пойдёт (или отдельный fork) - `gx/cctv#22` Phase 4 — закроется после Phase 4 vf-cuda-grid + миграция cctv-processor (см. design §13) - `gx/cctv#24` — superseded (см. design §14) ## Open для обсуждения (см. design §12 Risks) - Pull-based vs push-based input — FFmpeg filter API ограничения - Lock-step inputs — что делать если один cuframes source отстаёт - Audio path latency vs video — sync через PTS - Multi-output sync — independent fps/timeline per screen - Migration cctv-processor — какие функции legacy GridComposer не покрываются филтером Каждый Phase — отдельный PR. Подзадачи на дальнейших Phases можно decompose'ить в sub-issues по мере приближения.
Author
Owner

Phase 1 — PR opened (draft)

gx/ffmpeg-patched PR #2vf_cuda_grid.c (~285 LOC).

Verified:

  • Build clean на CUDA 12.6 + Debian 12 + FFmpeg n7.1, без warnings (-Werror)
  • Filter регистрируется: ffmpeg -filters | grep cuda_gridVVVV->V
  • Filter help работает: ffmpeg -h filter=cuda_grid показывает 4 inputs + 1 output

Limitations Phase 1 (per design):

  • 4 inputs same size only (no scaling — Phase 2)
  • NV12 sw_format only
  • Hardcoded 2×2 layout
  • No runtime control
  • No overlays

Acceptance для не-draft (нужно):

  • Real cuframes input integration test (4 same-size publishers → quad mp4)
  • CUDA stream sync review
  • PTS rescale verify

После acceptance — merge → Phase 1 checklist в epic закрывается → переходим на Phase 2 (Layout DSL + scaling).

## Phase 1 — PR opened (draft) [gx/ffmpeg-patched PR #2](https://git.goldix.org/gx/ffmpeg-patched/pulls/2) — `vf_cuda_grid.c` (~285 LOC). **Verified:** - ✅ Build clean на CUDA 12.6 + Debian 12 + FFmpeg n7.1, без warnings (-Werror) - ✅ Filter регистрируется: `ffmpeg -filters | grep cuda_grid` → `VVVV->V` - ✅ Filter help работает: `ffmpeg -h filter=cuda_grid` показывает 4 inputs + 1 output **Limitations Phase 1** (per design): - 4 inputs same size only (no scaling — Phase 2) - NV12 sw_format only - Hardcoded 2×2 layout - No runtime control - No overlays **Acceptance для не-draft (нужно):** - Real cuframes input integration test (4 same-size publishers → quad mp4) - CUDA stream sync review - PTS rescale verify После acceptance — merge → Phase 1 checklist в epic закрывается → переходим на Phase 2 (Layout DSL + scaling).
Author
Owner

Phase 2a — layout templates + dynamic nb_inputs

PR-2 partial deliverable: commits 6ee2f47 + df47647 в gx/ffmpeg-patched n7.1-vf-cuda-grid.

Что сделано:

  • 9 предопределённых layouts (normalized coords, hardcoded table в C):
    single, dual_horizontal, dual_vertical, quad (default), main_plus_preview,
    six_grid, nine_grid, sixteen_grid, panoramic
  • Dynamic nb_inputs — определяется из layout (1, 2, 4, 6, 9, 16 inputs). AVFILTER_FLAG_DYNAMIC_INPUTS flag + ff_append_inpad_free_name в init().
  • Filter options: layout=<name>, out_w=<int>, out_h=<int> (defaults: quad, 1920×1080)
  • Per-cell pixel rects derived в config_output (normalized × out_size), align до chroma boundary

Verify:

$ ffmpeg -hide_banner -h filter=cuda_grid
Filter cuda_grid
  GPU-native video grid composer (CUDA).
    Inputs:
        dynamic (depending on the options)
    Outputs:
       #0: default (video)
cuda_grid AVOptions:
   layout            <string>     ..FV....... имя layout template (default "quad")
   out_w             <int>        ..FV....... ширина output frame (default 1920)
   out_h             <int>        ..FV....... высота output frame (default 1080)

Phase 2a limitation (всё ещё не закрыто):

  • Input frame size должен совпадать с cell pixel size. Mixed-size cameras (1080p + 2688×1520) пока не поддерживаются.

Дальше — Phase 2b: per-cell scaling через libnpp (nppiResize_8u_C1R для Y, nppiResize_8u_C2R для UV interleaved). Это разблокирует mixed-size cameras и main_plus_preview use case (1 large cell 1280×1080 + 3 small cells 640×360 из 1080p sources).

Phase 2a checked off в epic.

## Phase 2a — layout templates + dynamic nb_inputs ✅ PR-2 partial deliverable: commits `6ee2f47` + `df47647` в `gx/ffmpeg-patched n7.1-vf-cuda-grid`. **Что сделано:** - **9 предопределённых layouts** (normalized coords, hardcoded table в C): `single`, `dual_horizontal`, `dual_vertical`, `quad` (default), `main_plus_preview`, `six_grid`, `nine_grid`, `sixteen_grid`, `panoramic` - **Dynamic nb_inputs** — определяется из layout (1, 2, 4, 6, 9, 16 inputs). `AVFILTER_FLAG_DYNAMIC_INPUTS` flag + `ff_append_inpad_free_name` в `init()`. - **Filter options**: `layout=<name>`, `out_w=<int>`, `out_h=<int>` (defaults: quad, 1920×1080) - **Per-cell pixel rects** derived в `config_output` (normalized × out_size), align до chroma boundary **Verify:** ``` $ ffmpeg -hide_banner -h filter=cuda_grid Filter cuda_grid GPU-native video grid composer (CUDA). Inputs: dynamic (depending on the options) Outputs: #0: default (video) cuda_grid AVOptions: layout <string> ..FV....... имя layout template (default "quad") out_w <int> ..FV....... ширина output frame (default 1920) out_h <int> ..FV....... высота output frame (default 1080) ``` **Phase 2a limitation (всё ещё не закрыто):** - Input frame size **должен совпадать** с cell pixel size. Mixed-size cameras (1080p + 2688×1520) пока не поддерживаются. **Дальше — Phase 2b**: per-cell scaling через libnpp (`nppiResize_8u_C1R` для Y, `nppiResize_8u_C2R` для UV interleaved). Это разблокирует mixed-size cameras и `main_plus_preview` use case (1 large cell 1280×1080 + 3 small cells 640×360 из 1080p sources). Phase 2a checked off в epic.
Author
Owner

Phase 2b — scaling delegated to upstream scale_npp

После исследования NPP API обнаружено что nppiResize_* не имеет _C2R variant для 2-channel interleaved (только _C1R, _C3R, _C4R). NV12 UV plane = 2-channel interleaved → in-filter scaling требовал бы либо:

  • nppiResize_8u_C1R с split/merge через intermediate buffers
  • Custom CUDA kernel для NV12 UV bilinear
  • Treat UV pair as 16u_C1R (blending artifacts на boundaries)

Pragmatic decision (Unix philosophy): cuda_grid делает только composition. Scaling делегируется существующему production-tested scale_npp filter в filter chain:

ffmpeg ... -filter_complex \
  "[0]scale_npp=1280:1080[s0]; \
   [1]scale_npp=640:360[s1]; \
   [2]scale_npp=640:360[s2]; \
   [3]scale_npp=640:360[s3]; \
   [s0][s1][s2][s3]cuda_grid=layout=main_plus_preview[out]"

Controller (Phase 3) auto-generates filter graph с scale_npp per input на основе layout cell sizes. Это decoupling clean — каждый filter делает одну вещь, scale_npp полностью тестирован.

Trade-offs:

  • (+) No new CUDA kernels в нашем filter
  • (+) Leverages well-tested NPP code
  • (+) Single Responsibility Principle
  • (−) Filter chain становится длиннее (но controller это hide'нет)
  • (−) Один extra GPU memcpy (scale_npp out → cuda_grid in)

Filter теперь возвращает explicit error message с примером scale_npp chain если input size mismatch:

input 0 size 1920x1080 != cell size 1280x1080. cuda_grid не делает scaling — используй upstream scale_npp:
  [in0]scale_npp=1280:1080[scaled0]; [scaled0]...cuda_grid=layout=main_plus_preview

Phase 2 (2a + 2b) complete. Commits: 6ee2f47, df47647, 178fc5b в n7.1-vf-cuda-grid. Дальше Phase 3 controller sidecar.

## Phase 2b — scaling delegated to upstream `scale_npp` ✅ После исследования NPP API обнаружено что `nppiResize_*` **не имеет `_C2R`** variant для 2-channel interleaved (только `_C1R`, `_C3R`, `_C4R`). NV12 UV plane = 2-channel interleaved → in-filter scaling требовал бы либо: - **2× `nppiResize_8u_C1R`** с split/merge через intermediate buffers - **Custom CUDA kernel** для NV12 UV bilinear - **Treat UV pair as 16u_C1R** (blending artifacts на boundaries) **Pragmatic decision (Unix philosophy):** cuda_grid делает только composition. Scaling делегируется существующему **production-tested `scale_npp`** filter в filter chain: ```bash ffmpeg ... -filter_complex \ "[0]scale_npp=1280:1080[s0]; \ [1]scale_npp=640:360[s1]; \ [2]scale_npp=640:360[s2]; \ [3]scale_npp=640:360[s3]; \ [s0][s1][s2][s3]cuda_grid=layout=main_plus_preview[out]" ``` Controller (Phase 3) **auto-generates** filter graph с scale_npp per input на основе layout cell sizes. Это decoupling clean — каждый filter делает одну вещь, scale_npp полностью тестирован. **Trade-offs:** - (+) No new CUDA kernels в нашем filter - (+) Leverages well-tested NPP code - (+) Single Responsibility Principle - (−) Filter chain становится длиннее (но controller это hide'нет) - (−) Один extra GPU memcpy (scale_npp out → cuda_grid in) Filter теперь возвращает explicit error message с примером scale_npp chain если input size mismatch: ``` input 0 size 1920x1080 != cell size 1280x1080. cuda_grid не делает scaling — используй upstream scale_npp: [in0]scale_npp=1280:1080[scaled0]; [scaled0]...cuda_grid=layout=main_plus_preview ``` Phase 2 (2a + 2b) **complete**. Commits: `6ee2f47`, `df47647`, `178fc5b` в `n7.1-vf-cuda-grid`. Дальше Phase 3 controller sidecar.
Author
Owner

Phase 3 — cuda-grid-controller Python sidecar

Commit 37232ae в maincontroller/ directory (~700 LOC Python).

Stack: Python 3.11+ / FastAPI / asyncio / aiomqtt / pyzmq / pydantic / structlog / typer.

Modules:

File Что
config.py Pydantic schema (broker, instances[], ha_discovery, http, log) + YAML loader
layouts.py Registry известных layouts (sync с vf_cuda_grid.c Phase 2)
ha_discovery.py HA MQTT Discovery payloads — select.layout + sensor.current_layout + binary_sensor.online per instance
zmq_client.py Async ZMQ REQ socket к FFmpeg zmq filter (с timeout + reset on fail)
state.py In-memory ControllerState (active_layout per instance, asyncio.Lock)
mqtt_loop.py aiomqtt loop: subscribe cuda_grid/cmd/+/+/+, publish state + events, LWT, HA status reconnect detection
dispatch.py CommandDispatcher: layout.set action → ZMQ send + state update + events
http_api.py FastAPI: /health, /layouts, /state, POST /layout/{inst}/set
__main__.py typer CLI, asyncio.gather(mqtt_loop, uvicorn.server)

End-to-end flow готов:

HA dashboard (select.layout)
   │
   ▼ MQTT command
cuda_grid/cmd/livingroom_tv/layout/set = "quad"
   │
   ▼ controller.dispatch.handle
zmq.send: "Parsed_cuda_grid_0 layout quad" → tcp://127.0.0.1:5555
   │
   ▼ FFmpeg vf_cuda_grid process_command
filter switches layout без teardown filter graph
   │
   ▼ controller.state.set_layout
state.layout = "quad"
   │
   ▼ MQTT publish (retained)
cuda_grid/state/livingroom_tv/layout = "quad" → HA UI обновляется
   │
   ▼ MQTT publish (non-retained event)
cuda_grid/event/livingroom_tv/layout_switched = {"from":"single","to":"quad","reason":"mqtt"}

Examples + Docker:

  • examples/controller.yaml — 2 instances (livingroom_tv, public_stream) sample
  • Dockerfile — python:3.11-slim, entrypoint cuda-grid-controller
  • README.md — usage, FFmpeg side filter graph пример

HA dashboard entities (auto-discovery после startup):

  • select.cuda_grid_<instance>_layout — dropdown с 9 layouts
  • sensor.cuda_grid_<instance>_current_layout — текущий
  • binary_sensor.cuda_grid_controller_online

Phase 3 limitations (Phase 4+ роадмап):

  • Layout switching only — no overlays yet
  • No audio orchestration state machine
  • No /events SSE stream (только MQTT events)
  • process_command reply parsing simple (Phase 4 будет proper error propagation)
  • No filter discovery — filter_target hardcoded в config (Parsed_cuda_grid_0)

Next: Phase 4 — overlay primitives (rect/text/icon) с CUDA rendering в filter side + HTTP/MQTT API в controller'е (POST /overlay/add). См. design §5.

## Phase 3 — `cuda-grid-controller` Python sidecar ✅ Commit `37232ae` в `main` — `controller/` directory (~700 LOC Python). **Stack:** Python 3.11+ / FastAPI / asyncio / aiomqtt / pyzmq / pydantic / structlog / typer. **Modules:** | File | Что | |---|---| | `config.py` | Pydantic schema (broker, instances[], ha_discovery, http, log) + YAML loader | | `layouts.py` | Registry известных layouts (sync с `vf_cuda_grid.c` Phase 2) | | `ha_discovery.py` | HA MQTT Discovery payloads — `select.layout` + `sensor.current_layout` + `binary_sensor.online` per instance | | `zmq_client.py` | Async ZMQ REQ socket к FFmpeg `zmq` filter (с timeout + reset on fail) | | `state.py` | In-memory ControllerState (active_layout per instance, asyncio.Lock) | | `mqtt_loop.py` | aiomqtt loop: subscribe `cuda_grid/cmd/+/+/+`, publish state + events, LWT, HA status reconnect detection | | `dispatch.py` | CommandDispatcher: `layout.set` action → ZMQ send + state update + events | | `http_api.py` | FastAPI: `/health`, `/layouts`, `/state`, `POST /layout/{inst}/set` | | `__main__.py` | typer CLI, asyncio.gather(mqtt_loop, uvicorn.server) | **End-to-end flow готов:** ``` HA dashboard (select.layout) │ ▼ MQTT command cuda_grid/cmd/livingroom_tv/layout/set = "quad" │ ▼ controller.dispatch.handle zmq.send: "Parsed_cuda_grid_0 layout quad" → tcp://127.0.0.1:5555 │ ▼ FFmpeg vf_cuda_grid process_command filter switches layout без teardown filter graph │ ▼ controller.state.set_layout state.layout = "quad" │ ▼ MQTT publish (retained) cuda_grid/state/livingroom_tv/layout = "quad" → HA UI обновляется │ ▼ MQTT publish (non-retained event) cuda_grid/event/livingroom_tv/layout_switched = {"from":"single","to":"quad","reason":"mqtt"} ``` **Examples + Docker:** - `examples/controller.yaml` — 2 instances (livingroom_tv, public_stream) sample - `Dockerfile` — python:3.11-slim, entrypoint cuda-grid-controller - `README.md` — usage, FFmpeg side filter graph пример **HA dashboard entities (auto-discovery после startup):** - `select.cuda_grid_<instance>_layout` — dropdown с 9 layouts - `sensor.cuda_grid_<instance>_current_layout` — текущий - `binary_sensor.cuda_grid_controller_online` **Phase 3 limitations** (Phase 4+ роадмап): - Layout switching only — no overlays yet - No audio orchestration state machine - No /events SSE stream (только MQTT events) - `process_command` reply parsing simple (Phase 4 будет proper error propagation) - No filter discovery — `filter_target` hardcoded в config (`Parsed_cuda_grid_0`) **Next: Phase 4** — overlay primitives (rect/text/icon) с CUDA rendering в filter side + HTTP/MQTT API в controller'е (`POST /overlay/add`). См. design §5.
Author
Owner

Phase 4a — overlay infrastructure (controller side)

Commit a1090a5 в main — +520 LOC Python (overlays + Frigate bridge + REST/MQTT API).

Что сделано (controller-side, end-to-end except rendering):

Component Что
overlays.py 7 discriminated union types: rect, text, icon, image, dim, graph, chat через pydantic. Normalized coords (0.0-1.0), optional cell binding, z_order, opacity, visible
state.py Per-instance overlay storage, CRUD (add/remove/update/get/clear) с asyncio.Lock
dispatch.py overlay.add/remove/clear actions, JSON↔ZMQ serialization, events publish
http_api.py POST/GET/PATCH/DELETE /overlay/{inst}/{id} REST endpoints
mqtt_loop.py cuda_grid/cmd/<inst>/overlay/add|remove|clear subscribe
frigate_bridge.py Skeleton subscribing frigate/+/motion + frigate/events — мапит к target_instance + cell. Phase 4a: log only. Phase 4b: auto-generate overlays

API examples:

# Add rect overlay через REST
curl -X POST http://localhost:8080/overlay/livingroom_tv/add -H 'Content-Type: application/json' -d '{
  "type": "rect",
  "x": 0.1, "y": 0.1, "w": 0.3, "h": 0.3,
  "color": "#FF0000",
  "opacity": 0.5,
  "border_only": true,
  "border_width": 4
}'
# → {"ok":true,"id":"a3f5e1c2","type":"rect"}

# Через MQTT
mosquitto_pub -t cuda_grid/cmd/livingroom_tv/overlay/add -m '{"type":"text","x":0.5,"y":0.05,"text":"BACKYARD","font_size":36,"color":"#FFFF00"}'

# Удалить
curl -X DELETE http://localhost:8080/overlay/livingroom_tv/a3f5e1c2

Frigate bridge config (examples/controller.yaml):

frigate:
  enabled: true
  base_topic: frigate
  mappings:
    - frigate_camera: parking_overview
      target_instance: livingroom_tv
      cell: 0
    - frigate_camera: front_yard
      target_instance: livingroom_tv
      cell: 1
    ...

Сейчас bridge просто logs received motion/detection events. Phase 4b будет автоматически генерить overlay'ы (rect + label text для object detections, dim overlay для motion regions).

Phase 4a limitations:

  • Filter ничего не рендерит — Phase 4b добавит AVFrame side data + CUDA kernels (rect через NPP fill, text через freetype atlas, icon через sprite blit)
  • HA Discovery — overlays через REST, не через HA UI yet
  • Graph/chat — нужен Cairo CPU rendering pipeline (Phase 5)

Status v0.4 work:

  • Phase 1: MVP filter (fixed quad)
  • Phase 2: 9 layouts + dynamic inputs + delegated scaling
  • Phase 3: cuda-grid-controller sidecar
  • Phase 4a: overlay infrastructure (controller side, full pipeline minus rendering)
  • Phase 4b: filter-side CUDA rendering (rect → text → icon)
  • Phase 5: graph + chat overlays (Cairo→texture)
  • Phase 6: audio orchestration
## Phase 4a — overlay infrastructure (controller side) ✅ Commit `a1090a5` в `main` — +520 LOC Python (overlays + Frigate bridge + REST/MQTT API). **Что сделано** (controller-side, end-to-end except rendering): | Component | Что | |---|---| | `overlays.py` | **7 discriminated union types**: `rect`, `text`, `icon`, `image`, `dim`, `graph`, `chat` через pydantic. Normalized coords (0.0-1.0), optional cell binding, z_order, opacity, visible | | `state.py` | Per-instance overlay storage, CRUD (add/remove/update/get/clear) с asyncio.Lock | | `dispatch.py` | overlay.add/remove/clear actions, JSON↔ZMQ serialization, events publish | | `http_api.py` | `POST/GET/PATCH/DELETE /overlay/{inst}/{id}` REST endpoints | | `mqtt_loop.py` | `cuda_grid/cmd/<inst>/overlay/add\|remove\|clear` subscribe | | `frigate_bridge.py` | Skeleton subscribing `frigate/+/motion` + `frigate/events` — мапит к target_instance + cell. Phase 4a: log only. Phase 4b: auto-generate overlays | **API examples:** ```bash # Add rect overlay через REST curl -X POST http://localhost:8080/overlay/livingroom_tv/add -H 'Content-Type: application/json' -d '{ "type": "rect", "x": 0.1, "y": 0.1, "w": 0.3, "h": 0.3, "color": "#FF0000", "opacity": 0.5, "border_only": true, "border_width": 4 }' # → {"ok":true,"id":"a3f5e1c2","type":"rect"} # Через MQTT mosquitto_pub -t cuda_grid/cmd/livingroom_tv/overlay/add -m '{"type":"text","x":0.5,"y":0.05,"text":"BACKYARD","font_size":36,"color":"#FFFF00"}' # Удалить curl -X DELETE http://localhost:8080/overlay/livingroom_tv/a3f5e1c2 ``` **Frigate bridge config** (`examples/controller.yaml`): ```yaml frigate: enabled: true base_topic: frigate mappings: - frigate_camera: parking_overview target_instance: livingroom_tv cell: 0 - frigate_camera: front_yard target_instance: livingroom_tv cell: 1 ... ``` Сейчас bridge **просто logs** received motion/detection events. Phase 4b будет автоматически генерить overlay'ы (rect + label text для object detections, dim overlay для motion regions). **Phase 4a limitations:** - Filter ничего не рендерит — Phase 4b добавит AVFrame side data + CUDA kernels (rect через NPP fill, text через freetype atlas, icon через sprite blit) - HA Discovery — overlays через REST, не через HA UI yet - Graph/chat — нужен Cairo CPU rendering pipeline (Phase 5) **Status v0.4 work:** - ✅ Phase 1: MVP filter (fixed quad) - ✅ Phase 2: 9 layouts + dynamic inputs + delegated scaling - ✅ Phase 3: cuda-grid-controller sidecar - ✅ **Phase 4a**: overlay infrastructure (controller side, full pipeline minus rendering) - ⏳ Phase 4b: filter-side CUDA rendering (rect → text → icon) - ⏳ Phase 5: graph + chat overlays (Cairo→texture) - ⏳ Phase 6: audio orchestration
Author
Owner

Phase 4b завершена (filter-side)

Ночью 2026-05-18→19 implemented + compiled все 4 overlay primitive types через автономную работу.

Что сделано

Phase Тип Реализация
4b-1 rect filled + border, alpha-blended (kernel Alpha_Fill_Y/UV)
4b-2 dim затемнение region (alpha=128, Y=16) — kernel-based
4b-3 text freetype CPU rasterize → RGBA atlas → CUDA upload → blit
4b-4 icon libavcodec PNG/JPG decode → swscale RGBA → upload → blit

Изменения

ffmpeg-patched (branch n7.1-vf-cuda-grid):

  • libavfilter/vf_cuda_grid.c — +1100 LOC (overlay state, process_command, helpers)
  • libavfilter/vf_cuda_grid.cu — NEW 100 LOC (4 CUDA kernels: Alpha_Fill_Y/UV + Alpha_Blit_RGBA_Y/UV)
  • libavfilter/Makefile — wired vf_cuda_grid.ptx.o + cuda/load_helper.o
  • configurecuda_grid_filter_deps_any="cuda_nvcc cuda_llvm"

vf-cuda-grid (branch main):

  • controller/cuda_grid_controller/dispatch.py — switch JSON → key=val URL-encoded wire format
  • tools/smoke_test_overlays.sh — manual integration test script

Dockerfile.debian12 (frigate-cuframes-test, local-only):

  • --enable-libzmq --enable-cuda-nvcc --nvccflags="-gencode arch=compute_75,code=sm_75 -O2"
  • apt install libzmq3-dev

Wire format

<id> <type> <key>=<val> <key>=<val> ...

String values URL-encoded (text=hello%20world). Filter inline decode'ит %xx.

Examples:

  • rect_001 rect cell=0 x=0.1 y=0.1 w=0.3 h=0.3 r=255 g=0 b=0 thickness=4 opacity=255
  • text_001 text cell=1 x=0.3 y=0.4 text=Hello%20World font_size=48 r=255 g=255 b=255 opacity=200
  • icon_001 icon x=0.8 y=0.05 icon_name=domofon opacity=255

Build verified

Image ffmpeg-vf-cuda-grid:phase4b-icon (10 GB builder layer) собран. Smoke check:

  • ffmpeg -h filter=cuda_grid показывает все options (layout/out_w/out_h/font_file/font_size/icon_dir)
  • ffmpeg -filters | grep -E "cuda_grid|zmq" — оба filter'а present

Что НЕ сделано

  • Live integration test deferred — GPU занят прод (cctv + frigate + cuframes-rtsp-source съели всю VRAM, 299/32607 MB free). CUDA_ERROR_OUT_OF_MEMORY при cuCtxCreate. Test откладывается на утро когда часть процессов можно остановить.
  • FrigateBridge auto-rendering (Python TODO в frigate_bridge.py) — events приходят + логируются, но auto-create overlay команды не вызываются. Это Phase 4b separate task.
  • Phase 5 (audio orchestration) + Phase 6 (graphs/charts/chat overlays) — отдельные milestone.

Gotchas задокументированы в cuframes-stack-build skill Recipe B2:

  1. --enable-cuda-nvcc обязателен (default = clang autodetect → "not found")
  2. filter_deps_any: filter compiles но не enabled без правильного registration
  3. cuMemsetD2D* отсутствует в FFmpeg CudaFunctions wrapper → custom kernel required
  4. Werror=unused-function: удалили cuda_grid_config_input
  5. Makefile wiring .ptx.o обязателен
  6. libavformat/swscale fine to include из libavfilter
  7. Existing Dockerfile smoke test false-positive из-за неправильного LD_LIBRARY_PATH

Commits

  • ffmpeg-patched n7.1-vf-cuda-grid: 9deaca7c5130cb (5 commits: 4b-1, 4b-2, configure, 4b-3, URL-decode, 4b-4)
  • vf-cuda-grid main: c396a47 (wire format + smoke test script)
  • localhost-infra main: 77a3029 (skill update)
## Phase 4b завершена (filter-side) Ночью 2026-05-18→19 implemented + compiled все 4 overlay primitive types через автономную работу. ### Что сделано | Phase | Тип | Реализация | |---|---|---| | **4b-1** | rect | filled + border, alpha-blended (kernel `Alpha_Fill_Y/UV`) | | **4b-2** | dim | затемнение region (alpha=128, Y=16) — kernel-based | | **4b-3** | text | freetype CPU rasterize → RGBA atlas → CUDA upload → blit | | **4b-4** | icon | libavcodec PNG/JPG decode → swscale RGBA → upload → blit | ### Изменения **ffmpeg-patched** (branch `n7.1-vf-cuda-grid`): - `libavfilter/vf_cuda_grid.c` — +1100 LOC (overlay state, process_command, helpers) - `libavfilter/vf_cuda_grid.cu` — NEW 100 LOC (4 CUDA kernels: Alpha_Fill_Y/UV + Alpha_Blit_RGBA_Y/UV) - `libavfilter/Makefile` — wired `vf_cuda_grid.ptx.o` + `cuda/load_helper.o` - `configure` — `cuda_grid_filter_deps_any="cuda_nvcc cuda_llvm"` **vf-cuda-grid** (branch `main`): - `controller/cuda_grid_controller/dispatch.py` — switch JSON → key=val URL-encoded wire format - `tools/smoke_test_overlays.sh` — manual integration test script **Dockerfile.debian12** (frigate-cuframes-test, local-only): - `--enable-libzmq --enable-cuda-nvcc --nvccflags="-gencode arch=compute_75,code=sm_75 -O2"` - `apt install libzmq3-dev` ### Wire format ``` <id> <type> <key>=<val> <key>=<val> ... ``` String values URL-encoded (text=hello%20world). Filter inline decode'ит `%xx`. Examples: - `rect_001 rect cell=0 x=0.1 y=0.1 w=0.3 h=0.3 r=255 g=0 b=0 thickness=4 opacity=255` - `text_001 text cell=1 x=0.3 y=0.4 text=Hello%20World font_size=48 r=255 g=255 b=255 opacity=200` - `icon_001 icon x=0.8 y=0.05 icon_name=domofon opacity=255` ### Build verified Image `ffmpeg-vf-cuda-grid:phase4b-icon` (10 GB builder layer) собран. Smoke check: - `ffmpeg -h filter=cuda_grid` показывает все options (layout/out_w/out_h/font_file/font_size/icon_dir) - `ffmpeg -filters | grep -E "cuda_grid|zmq"` — оба filter'а present ### Что НЕ сделано - **Live integration test deferred** — GPU занят прод (cctv + frigate + cuframes-rtsp-source съели всю VRAM, 299/32607 MB free). CUDA_ERROR_OUT_OF_MEMORY при cuCtxCreate. Test откладывается на утро когда часть процессов можно остановить. - **FrigateBridge auto-rendering** (Python TODO в `frigate_bridge.py`) — events приходят + логируются, но auto-create overlay команды не вызываются. Это Phase 4b separate task. - **Phase 5** (audio orchestration) + **Phase 6** (graphs/charts/chat overlays) — отдельные milestone. ### Gotchas задокументированы в [cuframes-stack-build skill Recipe B2](https://git.goldix.org/goldix-smart-home/network/src/branch/main/skills/cuframes-stack-build.md): 1. `--enable-cuda-nvcc` обязателен (default = clang autodetect → "not found") 2. filter_deps_any: filter compiles но не enabled без правильного registration 3. cuMemsetD2D* отсутствует в FFmpeg CudaFunctions wrapper → custom kernel required 4. Werror=unused-function: удалили `cuda_grid_config_input` 5. Makefile wiring `.ptx.o` обязателен 6. libavformat/swscale fine to include из libavfilter 7. Existing Dockerfile smoke test false-positive из-за неправильного `LD_LIBRARY_PATH` ### Commits - ffmpeg-patched n7.1-vf-cuda-grid: `9deaca7` → `c5130cb` (5 commits: 4b-1, 4b-2, configure, 4b-3, URL-decode, 4b-4) - vf-cuda-grid main: `c396a47` (wire format + smoke test script) - localhost-infra main: `77a3029` (skill update)
Author
Owner

Live test PASSED 🎉

Утром 2026-05-20 GPU освободилась (7.5 GB free) — запустил smoke test, все 4 overlay типа реально рендерятся.

Setup:

  • 4× lavfi color inputs 640×360 @ 10fps
  • hwupload_cudacuda_grid=layout=quad:out_w=1280:out_h=720zmq=bind_address=tcp://0.0.0.0:5599hwdownload,format=nv12libx264 → mp4
  • pyzmq REQ-socket к 127.0.0.1:5599

Commands отправлены (4× add_overlay):

send("Parsed_cuda_grid_8", "add_overlay", "r1 rect cell=0 x=0.05 y=0.05 w=0.9 h=0.9 r=255 g=64 b=64 thickness=8 opacity=255")
send("Parsed_cuda_grid_8", "add_overlay", "t1 text cell=1 x=0.1 y=0.4 text=Hello%20vf_cuda_grid font_size=48 r=255 g=255 b=255 opacity=255")
send("Parsed_cuda_grid_8", "add_overlay", "d1 dim cell=2 x=0 y=0 w=1 h=1 amount=180")
send("Parsed_cuda_grid_8", "add_overlay", "r2 rect cell=3 x=0.1 y=0.1 w=0.8 h=0.8 r=64 g=255 b=64 thickness=0 opacity=128")

Replies: все 4 — 0 Success + ok id=X n=N. Filter автоматически загрузил /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf при первом text overlay.

Pipeline отработал 641 frames (64 sec), exit clean. Output mp4 = 65 KB. Frame extracted визуально подтверждает все 4 типа корректно рендерятся.

⚠️ Critical gotcha найден — ZMQ wire format

FFmpeg zmq filter parses через av_get_token (libavfilter/f_zmq.c:100-115) — берёт ОДИН token как arg. Если arg содержит spaces (наш случай), нужно обернуть в кавычки:

✅ target add_overlay 'r1 rect cell=0 x=0.1 ...'
❌ target add_overlay r1 rect cell=0 x=0.1 ...   (arg = только "r1"!)

TODO для controller: dispatch._serialize_overlay_to_zmq возвращает unquoted строку, zmq_client.send_command f-string не quote'ит args. Нужно добавить wrap в '...' в одном из этих мест.

Visual verification

Cell Тип Что отрендерилось
0 (тёмно-синий) rect border красная рамка 8px
1 (тёмно-красный) text "Hello vf_cuda_grid" белым 48px DejaVuSans-Bold
2 (тёмно-зелёный) dim затемнение amount=180 (≈70% opacity) → почти чёрный
3 (тёмно-жёлтый) rect filled зелёный @ opacity=128 → olive blend

Output mp4: /tmp/grid_with_overlays.mp4, frame: /tmp/grid_frame.png (на dev-машине localhost).

Phase 4b формально closed. Следующее: Phase 4b auto-rendering в FrigateBridge + Phase 5 (audio) + Phase 6 (graphs/charts).

## Live test PASSED 🎉 Утром 2026-05-20 GPU освободилась (7.5 GB free) — запустил smoke test, **все 4 overlay типа реально рендерятся**. **Setup:** - 4× lavfi color inputs 640×360 @ 10fps - `hwupload_cuda` → `cuda_grid=layout=quad:out_w=1280:out_h=720` → `zmq=bind_address=tcp://0.0.0.0:5599` → `hwdownload,format=nv12` → `libx264` → mp4 - pyzmq REQ-socket к 127.0.0.1:5599 **Commands отправлены (4× add_overlay):** ```python send("Parsed_cuda_grid_8", "add_overlay", "r1 rect cell=0 x=0.05 y=0.05 w=0.9 h=0.9 r=255 g=64 b=64 thickness=8 opacity=255") send("Parsed_cuda_grid_8", "add_overlay", "t1 text cell=1 x=0.1 y=0.4 text=Hello%20vf_cuda_grid font_size=48 r=255 g=255 b=255 opacity=255") send("Parsed_cuda_grid_8", "add_overlay", "d1 dim cell=2 x=0 y=0 w=1 h=1 amount=180") send("Parsed_cuda_grid_8", "add_overlay", "r2 rect cell=3 x=0.1 y=0.1 w=0.8 h=0.8 r=64 g=255 b=64 thickness=0 opacity=128") ``` **Replies:** все 4 — `0 Success` + `ok id=X n=N`. Filter автоматически загрузил `/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf` при первом text overlay. **Pipeline отработал 641 frames (64 sec), exit clean.** Output mp4 = 65 KB. Frame extracted визуально подтверждает все 4 типа корректно рендерятся. ### ⚠️ Critical gotcha найден — ZMQ wire format FFmpeg `zmq` filter parses через `av_get_token` (libavfilter/f_zmq.c:100-115) — **берёт ОДИН token как arg**. Если arg содержит spaces (наш случай), нужно обернуть в кавычки: ``` ✅ target add_overlay 'r1 rect cell=0 x=0.1 ...' ❌ target add_overlay r1 rect cell=0 x=0.1 ... (arg = только "r1"!) ``` **TODO для controller:** `dispatch._serialize_overlay_to_zmq` возвращает unquoted строку, `zmq_client.send_command` f-string не quote'ит args. Нужно добавить wrap в '...' в одном из этих мест. ### Visual verification | Cell | Тип | Что отрендерилось | |---|---|---| | 0 (тёмно-синий) | rect border | красная рамка 8px | | 1 (тёмно-красный) | text | "Hello vf_cuda_grid" белым 48px DejaVuSans-Bold | | 2 (тёмно-зелёный) | dim | затемнение amount=180 (≈70% opacity) → почти чёрный | | 3 (тёмно-жёлтый) | rect filled | зелёный @ opacity=128 → olive blend | Output mp4: `/tmp/grid_with_overlays.mp4`, frame: `/tmp/grid_frame.png` (на dev-машине localhost). Phase 4b формально **closed**. Следующее: Phase 4b auto-rendering в FrigateBridge + Phase 5 (audio) + Phase 6 (graphs/charts).
Author
Owner

Phase 4b — FrigateBridge auto-rendering LIVE 🎉

Полная цепочка production-ready:
Frigate MQTT eventFrigateBridge.handle_messageCommandDispatcher_serialize_overlay_to_zmq → ZMQ quoted → filter parse_overlay_args → CUDA kernel render.

Что добавлено

zmq_client.send_command — оборачивает args в single-quotes (escapes '\'). FFmpeg's av_get_token honours quoting → arg идёт как single token.

dispatch._serialize_overlay_to_zmq — translation layer pydantic → filter wire:

Pydantic field Filter field Transform
color="#FF8800" r=255 g=136 b=0 hex → 3 ints
opacity=0.95 opacity=242 × 255 round
border_only=True, border_width=8 thickness=8 True → width, False → 0
dim_factor=0.65 amount=166 × 255 round
text="Hello world" text=Hello%20world URL-encode

FrigateBridge Phase 4b auto-overlay:

  • frigate/<cam>/motion ON → add RectOverlay border orange (id=motion_<cam>) на весь cell
  • frigate/<cam>/motion OFF → remove тот overlay
  • frigate/events (new/update) → add RectOverlay (bbox normalized) + TextOverlay (label + score%)
  • frigate/events (end) → remove оба
  • Per-mapping config: camera_width/camera_height для normalize bbox (default 1920×1080), флаги motion_indicator/bbox_overlay

FrigateBridge принимает dispatcher в constructor → используется dispatcher.handle(instance, "overlay.add", json) для отправки.

Live test verification

Симулировал 4 Frigate events (через direct method calls — bypass MQTT broker для test):

  • front_yard motion ON → cell 0 orange border
  • gate_lpr event new car bbox @ middle → cell 1 green bbox + "car 87%"
  • back_yard event new person → cell 2 bbox + "person 93%"
  • parking_overview motion ON + car event → cell 3 orange + bbox + "car 71%"

Все 8 overlays (2 motion + 3 bbox + 3 text) корректно rendered, bbox normalize правильно подсчитан с camera resolution per mapping.

Что осталось до production deploy

  • Запустить controller container на R9 (88.23) с config указывающим на cctv-mosquitto broker
  • Frigate camera mappings: real имена камер + camera resolutions
  • Add cuda_grid+zmq filter в существующий cctv-processor / Frigate FFmpeg pipeline (или новый pipeline для TV output)
  • Test с реальными motion + detection events от prod Frigate

Commits:

  • vf-cuda-grid main: 96e6048 — controller fixes + FrigateBridge auto-overlay (+229/-67 LOC)
## Phase 4b — FrigateBridge auto-rendering LIVE 🎉 Полная цепочка production-ready: **Frigate MQTT event** → `FrigateBridge.handle_message` → `CommandDispatcher` → `_serialize_overlay_to_zmq` → ZMQ quoted → filter `parse_overlay_args` → CUDA kernel render. ### Что добавлено **`zmq_client.send_command`** — оборачивает `args` в single-quotes (escapes `'` → `\'`). FFmpeg's `av_get_token` honours quoting → arg идёт как single token. **`dispatch._serialize_overlay_to_zmq`** — translation layer pydantic → filter wire: | Pydantic field | Filter field | Transform | |---|---|---| | `color="#FF8800"` | `r=255 g=136 b=0` | hex → 3 ints | | `opacity=0.95` | `opacity=242` | × 255 round | | `border_only=True, border_width=8` | `thickness=8` | True → width, False → 0 | | `dim_factor=0.65` | `amount=166` | × 255 round | | `text="Hello world"` | `text=Hello%20world` | URL-encode | **`FrigateBridge` Phase 4b auto-overlay:** - `frigate/<cam>/motion ON` → add RectOverlay border orange (id=`motion_<cam>`) на весь cell - `frigate/<cam>/motion OFF` → remove тот overlay - `frigate/events` (new/update) → add RectOverlay (bbox normalized) + TextOverlay (label + score%) - `frigate/events` (end) → remove оба - Per-mapping config: `camera_width`/`camera_height` для normalize bbox (default 1920×1080), флаги `motion_indicator`/`bbox_overlay` `FrigateBridge` принимает `dispatcher` в constructor → используется `dispatcher.handle(instance, "overlay.add", json)` для отправки. ### Live test verification Симулировал 4 Frigate events (через direct method calls — bypass MQTT broker для test): - `front_yard motion ON` → cell 0 orange border ✅ - `gate_lpr` event new car bbox @ middle → cell 1 green bbox + "car 87%" ✅ - `back_yard` event new person → cell 2 bbox + "person 93%" ✅ - `parking_overview` motion ON + car event → cell 3 orange + bbox + "car 71%" ✅ Все 8 overlays (2 motion + 3 bbox + 3 text) корректно rendered, bbox normalize правильно подсчитан с camera resolution per mapping. ### Что осталось до production deploy - [ ] Запустить controller container на R9 (88.23) с config указывающим на cctv-mosquitto broker - [ ] Frigate camera mappings: real имена камер + camera resolutions - [ ] Add cuda_grid+zmq filter в существующий cctv-processor / Frigate FFmpeg pipeline (или новый pipeline для TV output) - [ ] Test с реальными motion + detection events от prod Frigate Commits: - vf-cuda-grid main: `96e6048` — controller fixes + FrigateBridge auto-overlay (+229/-67 LOC)
Author
Owner

⚠️ Phase 5b/5c regression — REVERT к Phase 5a

Root cause: multi-source audio chain (astreamselect + amix + lavfi sine) в одном ffmpeg pipeline с RTSP push блокирует video pacing. Encoder log показывает 25 fps speed=1.0x, но TV consumer (192.168.88.36) видит ~0.5 fps. Audio sync ffmpeg muxer waits для frames на всех inputs (включая lavfi infinite generator); если хоть один MP3 stream буферизует, output stalls → encoder backlog → TV starves.

Verified диагностически:

Pipeline encoder        : fps=25 speed=1.0x ✅
Host ffmpeg consume     : 201 frames за 8 sec = 25 fps ✅
TV display              : ~0.5 fps ❌
После disable audio     : TV 25 fps ✅
Single audio inline     : TV 25 fps + audio четко ✅

Applied fix: revert к Phase 5a single audio inline (Europa Plus direct -i + AAC re-encode, без astreamselect/amix). Phase 6 dynamic overlays тоже disabled временно (под подозрением до отдельной verify когда audio решим).

Memory зафиксировал:

Phase 5d — split-process architecture (новая phase)

Multi-source audio + ducking требуют отдельный ffmpeg process для audio path:

[video-pipeline] ─ NVDEC 4× cuframes → cuda_grid → NVENC → push к mediamtx /live
                                                           (V-only)
[audio-pipeline] ─ astreamselect + amix + volume     → push к mediamtx /live
                                                       (A-only, same path или separate)
[controller]     ─ ZMQ к обоим, MQTT bridge auto-control

Так video pipeline не блокируется backpressure из audio chain. mediamtx умеет mux два published stream'а в один path.

Phase 6 dynamic overlays — re-enable после Phase 5d confirm (для уверенности что reload_icon не мerge stalls).

Commits:

  • localhost-infra: 3866728 (revert + memory)
  • vf-cuda-grid: still at e877a25 (controller code не менялся — Phase 5b/5c controller endpoints оставлены, ждут split-process pipeline которая их consume'нет)
## ⚠️ Phase 5b/5c regression — REVERT к Phase 5a **Root cause:** multi-source audio chain (astreamselect + amix + lavfi sine) в одном ffmpeg pipeline с RTSP push **блокирует video pacing**. Encoder log показывает 25 fps speed=1.0x, но TV consumer (192.168.88.36) видит ~0.5 fps. Audio sync ffmpeg muxer waits для frames на всех inputs (включая lavfi infinite generator); если хоть один MP3 stream буферизует, output stalls → encoder backlog → TV starves. **Verified диагностически:** ``` Pipeline encoder : fps=25 speed=1.0x ✅ Host ffmpeg consume : 201 frames за 8 sec = 25 fps ✅ TV display : ~0.5 fps ❌ После disable audio : TV 25 fps ✅ Single audio inline : TV 25 fps + audio четко ✅ ``` **Applied fix:** revert к Phase 5a single audio inline (Europa Plus direct `-i` + AAC re-encode, без astreamselect/amix). Phase 6 dynamic overlays тоже disabled временно (под подозрением до отдельной verify когда audio решим). **Memory зафиксировал:** - [`feedback_audio-chain-blocks-video.md`](https://git.goldix.org/goldix-smart-home/network/src/branch/main/skills/) — правило + verification commands - [`localhost-infra commit 3866728`](https://git.goldix.org/goldix-smart-home/network/commit/3866728) — applied changes ### Phase 5d — split-process architecture (новая phase) Multi-source audio + ducking требуют **отдельный ffmpeg process** для audio path: ``` [video-pipeline] ─ NVDEC 4× cuframes → cuda_grid → NVENC → push к mediamtx /live (V-only) [audio-pipeline] ─ astreamselect + amix + volume → push к mediamtx /live (A-only, same path или separate) [controller] ─ ZMQ к обоим, MQTT bridge auto-control ``` Так video pipeline не блокируется backpressure из audio chain. mediamtx умеет mux два published stream'а в один path. **Phase 6 dynamic overlays** — re-enable после Phase 5d confirm (для уверенности что reload_icon не мerge stalls). Commits: - localhost-infra: `3866728` (revert + memory) - vf-cuda-grid: still at `e877a25` (controller code не менялся — Phase 5b/5c controller endpoints оставлены, ждут split-process pipeline которая их consume'нет)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gx/vf-cuda-grid#1