initial commit: design specification + repo scaffolding

cuframes — open-source FFmpeg-плагин и runtime library для zero-copy
sharing декодированных видеокадров между процессами через CUDA IPC.

Содержимое initial commit:
- docs/architecture.md — полная design-spec (418 строк) с prior art,
  protocol design, API draft, phase plan, acceptance criteria
- README.md — landing с описанием идеи, состава, quickstart-tease,
  roadmap, ссылки на community-discussions подтверждающие спрос
- CONTRIBUTING.md — guidelines, code style, commit message convention
- CHANGELOG.md — Keep a Changelog format, Unreleased / 0.0.1
- LICENSE — LGPL-2.1+ (compatibility с FFmpeg)
- .gitignore — build/CMake/Docker/Python/CUDA-specific

Следующие шаги (отдельные коммиты):
- docker/Dockerfile.dev (CUDA 12.x dev environment)
- tools/spike/ (Phase 0 PoC код для measurement CUDA IPC latency)
This commit is contained in:
2026-05-14 21:17:34 +01:00
commit c8ab4522f2
6 changed files with 1183 additions and 0 deletions
+418
View File
@@ -0,0 +1,418 @@
# 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/<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++)
Пример:
```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.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)
```cpp
#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)
```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/<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.** `cudaStreamSynchronize` гарантирует correctness но
serialize'ит producer. CUDA IPC events дают overlap, но complicate API.
Default: stream sync. Advanced flag: events.
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+ по плану