Files
cuframes/docs/architecture.md
T
gx fbe1d18c39 docs: troubleshooting guide + production notes
- docs/troubleshooting.md — 13 секций с реальными grабельками которые мы
  прошли: cudaIpcOpenEventHandle invalid device context (pid namespace),
  s6-overlay vs pid share, scale_cuda missing (cuda-llvm + stdbit.h glibc 2.36),
  libcuframes not found install paths, ffbuild/ missing source, GMP no working
  compiler (long-long reliability), zlib.net deprecated URL, RTSP/RTP UDP
  docker NAT, gitea actions Node version
- docs/architecture.md — Appendix A "Production deployment notes" с реальными
  observations после 24h+ run: что подтвердилось, что доработали, что не учли
- docs/requirements.md — production deployment matrix + Docker namespace
  requirements таблица (cross-container CUDA IPC требует 5 условий)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:37:13 +01:00

24 KiB
Raw Permalink Blame History

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) 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) 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/<key>.sock, handshake (negotiate format, ring size)
  • Mmap shared memory header /dev/shm/cuframes.<key>
  • 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++)

Пример:

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

#include <cuframes.h>

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)

#include <cuframes.hpp>

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)

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/<org>/cuframes-ffmpeg:N — drop-in замена /usr/lib/ffmpeg/N/bin/ffmpeg в Frigate image
  • Frigate-specific image: ghcr.io/<org>/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/<org>/cuframes-ffmpeg:N.x-cuda12.X
  • ghcr.io/<org>/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 <personal-org>/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/<pid>/.... Если 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.

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

См. также