# CUDA IPC Frame Bus — open-source FFmpeg-плагин **Working name:** `cuframes` (рабочий, finalize при release) **License:** LGPL-2.1+ (compatibility с FFmpeg) **Статус:** дизайн-спецификация v0.1 **Дата:** 2026-05-14 ## TL;DR FFmpeg-плагин и C/C++ библиотека для zero-copy sharing декодированных видеокадров между процессами через CUDA IPC. Один процесс делает decode на GPU, кадр остаётся в VRAM, любое количество других процессов читает его без копирования. Distributed как open-source. Use case 0 (наш): Frigate decode'ит видео, `cctv-companion` consume'ит те же кадры для mosaic-композиции на TV. Без двойного decode. Use case N (общий): любая комбинация AI-pipeline + NVR + аналитика на одной машине с NVIDIA GPU. ## 1. Контекст и мотивация Сейчас типичный setup: ``` Camera → ffmpeg #1 (NVR, decode + record) → ffmpeg #2 (AI-detector, decode + inference) → ffmpeg #3 (custom analytics, decode + ...) Каждый decode = NVDEC operation + VRAM. Inter-process передача требует cudaMemcpy в host RAM и обратно. На 16 cameras × 25 fps × 3 consumers = 75% VRAM-bandwidth впустую. ``` Существующие решения: | | Что | Почему не подходит | |---|---|---| | NVIDIA DeepStream | Production-grade SDK, в т.ч. shared GPU buffers | Закрытое, vendor lock-in, требует переписи pipeline | | GStreamer NVMM | NVIDIA Multimedia memory plugin | Inter-process только через DMA-buf, сложная интеграция | | Custom CUDA IPC | Прямой `cudaIpcGetMemHandle` | Требует написать с нуля у каждого; нет интеграции с FFmpeg | | Frigate +1 ffmpeg | Каждый сервис свой decode | Дублирование, что и побудило проект | Пустая ниша: **простой FFmpeg-filter** который добавляется в любой ffmpeg-pipeline и публикует декодированные кадры через CUDA IPC. Без рерайта пайплайна, без vendor SDK, без специальной библиотеки на стороне producer'а — просто extra-flag к ffmpeg. ## 1.bis Prior art / competitive analysis Research проведён 2026-05-14, документирует что **ниша свободна**. ### Существующие технологии (building blocks, не plug-and-play) | Проект | License | Что | Почему не аналог | |---|---|---|---| | **NVIDIA CUDA IPC API** | Proprietary headers, free use | Core `cudaIpcGetMemHandle`/`cudaIpcOpenMemHandle` | Сырой API, не плагин для FFmpeg — наш фундамент | | **NVIDIA DeepStream** | Closed-source SDK | `NvDsBufSurface` shared-buffers + ML pipeline | Vendor lock-in, требует переписать pipeline в DeepStream API. Не FFmpeg-filter. | | **NVIDIA VPF** ([github](https://github.com/NVIDIA/VideoProcessingFramework)) | Apache 2.0 | Python bindings вокруг NvCodec | **Single-process только**, inter-process не делает | | **PyNvVideoCodec** | NVIDIA-proprietary, free | Python decoder с PyTorch zero-copy interop | Single-process | | **dora-rs CUDA IPC** ([dora-rs.ai](https://dora-rs.ai/docs/guides/Development/Cuda/)) | Apache 2.0 | `torch_to_ipc_buffer()` / `ipc_buffer_to_ipc_handle()` для dataflow framework | Robotics-focused, работает на уровне torch tensors. Не FFmpeg-плагин. **Можем переиспользовать low-level подходы**. | | **GStreamer NVMM** | LGPL | NVIDIA Multimedia Memory plugins | Inter-thread (intra-process); inter-process через DMA-buf только на Jetson | | **BabitMF** (Bytedance) | Apache 2.0 | GPU filtering framework | Single-process intra-pipeline, не FFmpeg-filter | | **Frigate go2rtc relay** | MIT | Re-stream compressed bytes между consumers | Не zero-copy — каждый consumer декодит снова | ### Подтверждение спроса в community Threads в `blakeblackshear/frigate`, ровно описывающие нашу проблему: - **#17033** — «Recommended GPU for Video Decoding with ~80 Cameras» — обсуждение saturation NVDEC engines при наивном double-decode - **#20191** — «As soon as I add a second camera, nothing works» — пользователи натыкаются на decode-related issues - **#21559** — «System with 25 cameras — which GPU» — все ответы про "купите больше железа", решения через shared decode нет - **#21865** — «Combining Nvidia GPU and integrated GPU» — workaround через split decoders, что лишний раз доказывает что shared decode был бы полезен ### Проверка FFmpeg upstream - FFmpeg-devel mailing list 2024-2026: **ноль** patches содержащих CUDA IPC inter-process filter - FFmpeg master tree (8.0.1): CUDA-фильтры (`scale_cuda`, `bilateral_cuda`, `bwdif_cuda`, `libvmaf_cuda`, `overlay_cuda`) — все intra-process - Search GitHub `vf_cuda_ipc | cudaipc | cuda_ipc_export`: zero forks/patches с этой функциональностью ### Вывод **Ниша свободна.** Подтверждённый спрос (Frigate community), стабильные building-blocks (NVIDIA CUDA IPC API + dora-rs reference). Нет risk'а дублирования усилий. Sources проверены 2026-05-14. ## 2. Архитектура ### 2.1 Компоненты репозитория ``` cuframes/ ← OSS repo ├── filter/ │ └── vf_cuda_ipc_export.c FFmpeg filter (C, ~300 строк) ├── libcuframes/ producer/consumer ядро │ ├── include/cuframes.h публичный C API │ ├── src/ │ │ ├── producer.c ring buffer + Unix socket server │ │ ├── consumer.c subscribe + cudaIpcOpenMemHandle │ │ └── protocol.c wire format, версионирование │ └── tests/ │ ├── pingpong.c smoke test: 1 producer ↔ 1 consumer │ ├── multi_consumer.c 1 → N │ ├── stress.c 24h, leak detection │ └── bench.c latency, throughput ├── bindings/ │ ├── cpp/ C++ RAII wrapper │ └── python/ pybind11 binding (numpy/torch interop) ├── examples/ │ ├── ffmpeg-basic/ минимальный FFmpeg → consumer │ ├── opencv-consumer/ cv::cuda::GpuMat из shared frame │ ├── pytorch-consumer/ torch.Tensor zero-copy │ ├── frigate-integration/ Frigate config + Docker compose │ └── mosaic-compositor/ наш cctv-companion (как demo) ├── docker/ │ ├── ffmpeg-cuframes/ FFmpeg с встроенным filter │ └── ffmpeg-cuframes-frigate/ Drop-in replacement для Frigate's ffmpeg ├── docs/ │ ├── architecture.md эта спека после редактирования для OSS │ ├── protocol.md wire format │ ├── quickstart.md 5-минутный пример │ ├── building-ffmpeg.md как пересобрать FFmpeg с filter │ ├── frigate-howto.md конкретно под Frigate │ ├── benchmarks.md numbers │ └── faq.md ├── scripts/ │ ├── build-ffmpeg-patched.sh автоматизация upstream build │ ├── package-deb.sh .deb для Ubuntu │ └── ci/ └── .github/ └── workflows/ CI: build matrix Ubuntu 22.04/24.04, CUDA 12.x ``` ### 2.2 Wire protocol (краткое) Producer: - Аллоцирует ring buffer (default 4 slots) в VRAM через `cudaMalloc` - На каждый decoded frame — пишет в next slot, делает `cudaStreamSynchronize` - Публикует `seq` атомарно - Сигналит consumers через eventfd Consumer: - Connect к `/run/cuframes/.sock`, handshake (negotiate format, ring size) - Mmap shared memory header `/dev/shm/cuframes.` - Wait eventfd → read latest slot → `cudaIpcOpenMemHandle` → CUDA pointer - Использует frame zero-copy → ACK через atomic bit в bitmap Backpressure: drop oldest (newest-wins). Multi-consumer независимы — медленный не блокирует быстрых. Полный формат — в `docs/protocol.md`. Здесь — концепт. ### 2.3 FFmpeg filter API ``` -vf "[any pipeline before],cuda_ipc_export=key=NAME[:ring=N][:sync_event=1],[pipeline after]" ``` Опции: - `key` *(required)* — имя shared resource. Должно совпадать у producer и consumer. - `ring` — глубина ring buffer (default 4) - `sync_event` — использовать `cudaIpcEventHandle_t` вместо `cudaStreamSynchronize` (zero-copy sync, но experimental) Filter: - Принимает frame в CUDA hwframe-context - Side-effect: публикует через `libcuframes` - Passthrough — frame идёт дальше в pipeline без модификации (refcount++) Пример: ```bash ffmpeg -hwaccel cuda -hwaccel_output_format cuda \ -i rtsp://camera/stream \ -vf "scale_cuda=1920:1080,cuda_ipc_export=key=cam1" \ -c:v copy -f segment recording.mp4 ``` В этой команде frame и публикуется через CUDA IPC, и пишется в архив. Один decode для всего. ### 2.4 Consumer API #### C ```c #include cuframes_subscriber_t* sub = cuframes_subscribe("cam1", "my-app"); cuframes_frame_t frame; while (cuframes_next(sub, &frame, /*timeout_ms=*/100) == 0) { // frame.cuda_ptr — device pointer, zero-copy // frame.width, height, pitch_y, pitch_uv, format // frame.pts_ns do_inference_on_cuda(frame.cuda_ptr, frame.width, frame.height); cuframes_release(&frame); // ACK обратно producer'у } cuframes_unsubscribe(sub); ``` #### C++ (RAII) ```cpp #include cuframes::Subscriber sub("cam1", "my-app"); while (auto frame = sub.next(std::chrono::milliseconds(100))) { // frame->cuda_ptr — zero-copy cv::cuda::GpuMat mat(frame->height, frame->width, CV_8UC2, // NV12 frame->cuda_ptr, frame->pitch_y); // ... use mat // frame destructor → ACK } ``` #### Python (numpy/torch interop) ```python import cuframes with cuframes.Subscriber("cam1", "my-app") as sub: for frame in sub: # frame.tensor: torch.Tensor on CUDA, zero-copy # frame.dlpack: __dlpack__ для numpy/torch/jax interop output = model(frame.tensor) ``` ### 2.5 Distribution и установка #### Уровень 1 — FFmpeg patch Минимальный патч к FFmpeg `libavfilter/`: - `vf_cuda_ipc_export.c` (новый файл) - `Makefile`: добавить filter - `allfilters.c`: регистрация Это **один commit** к FFmpeg-репо. Возможен upstream PR (надеемся примут; прецедент есть — Nvidia donated `scale_cuda` и др.). #### Уровень 2 — `libcuframes` runtime Stand-alone shared library. Зависимости: - `libcuda.so.1` (NVIDIA driver, и так у всех) - `cudart` (CUDA runtime) - Linux только (POSIX SHM, eventfd, AF_UNIX) #### Уровень 3 — упаковка - **Debian/Ubuntu**: `.deb` через `dpkg-buildpackage` - **Arch**: AUR package - **Docker**: pre-built image `ghcr.io//cuframes-ffmpeg:N` — drop-in замена `/usr/lib/ffmpeg/N/bin/ffmpeg` в Frigate image - **Frigate-specific image**: `ghcr.io//cuframes-frigate:0.17` — Frigate stable-tensorrt с pre-installed cuframes ffmpeg ## 3. Use cases для документации ### 3.1 Наш кейс — `cctv-companion` (mosaic) ``` Frigate (ffmpeg с cuda_ipc_export filter): Camera 1..16 → decode CUDA → publish key=cam_N ↘ свой Frigate pipeline (detect, record) cctv-companion: - подписан на cam_1..cam_16 через cuframes - подписан на frigate/events через MQTT - decision logic: какую сетку показать (full/2x2/4x4) - composite через CUDA-ядра (CUB / OpenCV CUDA / custom kernels) - encode → RTSP → TV ``` ### 3.2 Multi-AI на одном decode ``` ffmpeg decode → publish key=cam_main Subscriber A: YOLOv8 object detection Subscriber B: face recognition (ArcFace) Subscriber C: license plate OCR Subscriber D: motion-triggered cloud upload ``` Все читают **тот же** decoded frame. Один NVDEC operation вместо четырёх. ### 3.3 Замена RAM-based mmstreams Существующие custom-решения часто строят бypass через FIFO/pipe в RAM с encoding между процессами. cuframes делает это zero-copy на CUDA. ## 4. План работ ### Phase 0 — Spike (3 дня) - [ ] Прототип producer + consumer без FFmpeg: голый CUDA IPC + Unix socket - [ ] Замерить bandwidth, latency на RTX 5090 - [ ] Verify cross-container (docker → docker) и host → docker - [ ] Решить: sync через `cudaStreamSynchronize` или CUDA IPC events - **Deliverable:** `tools/spike/pingpong.cpp` + измерения ### Phase 1 — libcuframes (1 неделя) - [ ] Producer C API - [ ] Consumer C API - [ ] Wire-protocol implementation - [ ] Unit tests (pingpong, multi-consumer, crash-recovery) - [ ] Stress test rig (24h-runner) - **Deliverable:** Self-contained library + tests passing ### Phase 2 — FFmpeg filter (1-2 недели) - [ ] `vf_cuda_ipc_export.c` в стиле FFmpeg - [ ] Patch script для применения к FFmpeg N.x (4.4, 5.x, 6.x, 7.x) - [ ] Build на CUDA 12.0+ matrix - [ ] Integration test: `ffmpeg → cuframes filter → consumer` end-to-end - **Deliverable:** Patched FFmpeg builds (Ubuntu 22.04, 24.04, in Docker) ### Phase 3 — Bindings (1 неделя) - [ ] C++ RAII wrapper - [ ] Python binding через pybind11 - [ ] PyTorch tensor interop (DLPack) - **Deliverable:** `pip install cuframes` (через `pyproject.toml`) ### Phase 4 — Docker / packaging (3-5 дней) - [ ] `ghcr.io//cuframes-ffmpeg:N.x-cuda12.X` - [ ] `ghcr.io//cuframes-frigate:0.17` (Frigate с patched ffmpeg) - [ ] `.deb` packages для Ubuntu 22.04/24.04 - [ ] CI matrix: build всех вариантов на каждый push - **Deliverable:** `docker pull` ready-to-use ### Phase 5 — Naш use case: cctv-companion (1 неделя) - [ ] Минимальный C++ daemon - [ ] Subscribe на N камер - [ ] MQTT subscriber на Frigate events - [ ] Smart-grid logic (existing в проекте, переиспользовать) - [ ] Mosaic compositor на CUDA (cv::cuda::warpAffine для placement) - [ ] Output через `nvenc` → RTSP сервер для TV - **Deliverable:** Working setup на R9 со всеми 16 камерами ### Phase 6 — OSS launch (3-5 дней) - [ ] README с GIF (visual что это даёт) - [ ] Quickstart (5 минут от `docker pull` до working demo) - [ ] Benchmarks page с numbers - [ ] HackerNews / r/selfhosted / r/homeassistant анонс - [ ] Submit upstream PR в FFmpeg **Итого: ~6-8 недель** для одного разработчика, **3-4 недели** для двоих. ## 5. Acceptance criteria для v1.0 - [ ] FFmpeg-filter работает на 4.4, 5.x, 6.x, 7.x (Frigate diversity) - [ ] Latency producer→consumer ≤ 1 ms (p99) - [ ] 16 cameras × 4 consumers × 25 fps без drops 24h - [ ] Frigate (stable-tensorrt) drop-in replacement работает - [ ] PyTorch consumer: `frame.tensor` без копий на CUDA - [ ] Docker images < 4 GB сжатые - [ ] CI green на Ubuntu 22.04, 24.04 - [ ] `pip install cuframes` works на Python 3.10/3.11/3.12 - [ ] README с benchmark vs naive double-decode ## 6. Open questions для discussion 1. **Name.** `cuframes` рабочий, варианты: `vramshare`, `cudapipe`, `nvframe-bus`, `cuda-frame-bridge`, `framesink`. Финализируем перед public. 2. **License.** LGPL-2.1+ для совместимости с FFmpeg LGPL и spirit (linkable с MIT/Apache). Sourcing — на LGPL. 3. **Upstream FFmpeg PR vs custom build.** Best-effort вклад в FFmpeg, но не блокер — distrubution через own Docker images работает независимо. 4. **AMD/ROCm support.** Out of scope для v1. После v1 — если будет community-interest, добавить через HIP IPC. 5. **Multi-GPU.** Producer и consumer на разных CUDA devices — CUDA IPC ограничивает same device. В v1 — ограничение, документировать. 6. **Sync semantics.** ✅ **РЕШЕНО** (architectural review 2026-05-15 + spike-v2): Default — **CUDA IPC events** (`cudaIpcEventHandle_t`). Rationale: - NVIDIA Programming Guide §3.2.8 явно требует cross-process sync на стороне application; `cudaStreamSynchronize` producer'а гарантий не даёт contractually. - Spike-v2 measurements (`docs/measurements/spike-v2/`) показали: на нашем single-host single-GPU setup stream-only sync **работает** (0 torn frames на 1500 frames × 4 scenarios), но это **happens-to-work**, не contract. - Events добавляют mean +20µs overhead но дают **predictable tail latency** (max 5.2ms vs 14.7ms для FHD@60fps) и future-proof для multi-GPU. - API option `--sync=stream` оставлен как fallback для legacy / debugging. 7. **Где хостить.** GitHub `/cuframes`. Возможно перенос в `frigate-community/cuframes` после adoption. ## 7. Риски | Риск | Митигация | |---|---| | Upstream FFmpeg PR не примут | Distributable через custom builds, не блокирует release | | CUDA IPC subtle bugs | Phase 0 spike до commit'а; Stress test 24h | | Frigate не пропустит custom filter | `frigate-cuframes` docker image — pre-patched, готов к использованию | | Conflict с NVIDIA DeepStream APIs | None expected (разные scope) | | Community не подхватит | Это secondary — primary value для нас (cctv-companion работает) | ## 8. Зависимости (build-time) - CUDA Toolkit ≥ 12.0 - FFmpeg sources (target versions 4.4, 5.x, 6.x, 7.x) - pkg-config, autoconf, make - Optional: OpenCV CUDA (для C++ examples) - Optional: pybind11 (для Python binding) ## 9. Связь с этим репозиторием (cctv) Текущий `cpp/apps/cctv-processor` — full-featured C++ NVR с своим decode + mosaic + RTSP-server. После v1 cuframes: - `cuframes` выпускается **как отдельный** opensource-репо (`/home/claude/projects/cuframes/`) - В `cctv` создаётся новый `cpp/apps/cctv-companion/` — тонкий клиент к Frigate + cuframes (не Replaces existing processor, а соседствует) - Существующий cctv-processor — оставить как baseline / fallback для setups без Frigate ## 10. Готовность к next step После approval спеки: 1. `git init /home/claude/projects/cuframes/` — пустой OSS-репо 2. Phase 0 spike — 3 дня PoC 3. После Phase 0 — review результатов, корректировка дизайна (если CUDA IPC повёл себя не как ожидали) 4. Phase 1+ по плану --- # Appendix A — Production deployment notes (post-v0.1.0) Реальные наблюдения после первого production deployment (Frigate + cctv-processor на RTX 5090, 24h+ uptime). Обновляется по мере накопления опыта. ## Что подтвердилось из изначального дизайна - **CUDA IPC handshake через cudaIpcEventHandle_t работает стабильно** — нет ни одного torn frame за 24+ часов на 2 consumer'ах. - **EXTERNAL ownership** (publisher передаёт свои pre-allocated CUDA pointers) необходим для FFmpeg-based publisher — иначе нужен extra cudaMemcpy из FFmpeg's hwframe pool в library-managed pool. - **Unix socket handshake** ОК — простой, debug'абельный (`socat` для inspect). - **POSIX shm для header + atomic seq counters** — race-free на reader side. ## Что пришлось доработать в v0.1.0 vs initial design - **CMake install rules** изначально не предусмотрены. Downstream проекты делали `cmake --install` → пустой prefix. Fix: `install(TARGETS ...)` + `install(DIRECTORY include/cuframes ...)`. Лессон — install rules должны быть в day 1. - **Variable HINTS в find_library**: пользователи делают install в разные prefix'ы. HINTS для downstream `find_library(cuframes)` должны включать `$PREFIX/lib`, `$PREFIX/lib64`, и `build-dir/libcuframes/` для local-dev. ## Что не учли в дизайне (открытые grабли — см. troubleshooting.md) ### Cross-container CUDA IPC требует **shared pid + ipc namespace** `cudaIpcOpenEventHandle` validates IPC peer через `/proc//...`. Если consumer container не в same PID namespace что publisher — fail с `invalid device context`. Это **incompatible** с s6-overlay-based containers (linuxserver.io stack, Frigate), требующими PID 1 для self. Workaround: только `ipc:` shared, accept race window (works на Frigate в практике потому что подключается первым после publisher restart). **Real fix planned v0.2**: socket-based context validation вместо `/proc` reliance. ### Publisher-side resize нужен для consumers без cuda-llvm Большинство downstream FFmpeg builds — без `--enable-cuda-llvm` (на платформах с glibc < 2.38 эта опция не собирается, нужен `stdbit.h`). Без cuda-llvm нет `scale_cuda` filter. Consumer вынужден CPU-resize либо отключать hwaccel. **Fix planned v0.2**: publisher принимает `--scale=WxH` и делает GPU resize до publish. Consumer получает уже scaled frames, scale_cuda не нужен. ### Encoded packet sharing — отсутствует в v0.1 cuframes v0.1 раздаёт **только decoded** NV12. Для `record` use case (`-c:v copy` mux без decode) consumer всё ещё открывает свой RTSP — лимит камеры на concurrent streams (4-5 у Dahua) hit'ится. **v0.2 spec**: parallel encoded-packets ring + `cuframes_packets://` demuxer. См. [issue #2](https://git.goldix.org/gx/cuframes/issues/2). ## Production setup (gold path) ``` ┌─► Frigate (FFmpeg cuframes:// demuxer) → detect Camera RTSP ─► publisher ──┤ (1× NVDEC) └─► cctv-processor (CuframesSource C++ API) → motion+RTSP-encode→TV ``` | Метрика | Without cuframes (baseline) | С cuframes v0.1 | |---|---|---| | NVDEC operations на parking-камеру | 2 (Frigate detect + cctv detect) | **1** (publisher) | | VRAM extra cost | 0 (каждый своё) | ~3 MB (ring 6×460KB sub-stream) | | RTSP camera load | 2 streams | **1** stream | | Uptime (verified) | n/a | 24h+ без drops | ## См. также - [docs/troubleshooting.md](troubleshooting.md) — конкретные грабли + fixes - [BENCHMARKS.md](../BENCHMARKS.md) — измерения - [docs/integrations/frigate.md](integrations/frigate.md) — guide для Frigate - [ROADMAP.md](../ROADMAP.md) — v0.2/v0.3/v1.0