From 44dab75e08f5d7751f83df1e92d0376b7cd88e4c Mon Sep 17 00:00:00 2001 From: Evgeny Demchenko Date: Thu, 14 May 2026 23:47:56 +0100 Subject: [PATCH] =?UTF-8?q?docs+docker:=20integration=20guide=20=D0=B8=20r?= =?UTF-8?q?untime=20image=20=D0=B4=D0=BB=D1=8F=20Frigate/cctv=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/integration.md — детальный guide для интеграции в существующий CCTV docker-compose: критичные требования (ipc=shareable/container, общий shared volume для socket), пример CuframesSource для cctv-processor, verification checklist, troubleshooting (timeout, ipc namespace mismatch, high latency). Зафиксировано: v0.1 frigate-decode не убирается без patch'а FFmpeg — это v0.2 scope. docker/Dockerfile.runtime — multi-stage build (devel → runtime), копирует libcuframes.so + cuframes-rtsp-source + sub_count в /usr/local. Образ ~700 MB (vs ~7 GB у dev'а). Smoke-test: бинарки запускаются, ldd видит все нужные libs. docker-compose.example.yml — reference docker-compose с правильным ipc mode и volume mounts для копирования в свои проекты. .dockerignore — исключает build/ и build-*/ из COPY context. README обновлён: статус v0.1 done, quickstart с реальным docker run, ссылка на integration guide. --- .dockerignore | 43 +++++++ README.md | 68 ++++++---- docker-compose.example.yml | 59 +++++++++ docker/Dockerfile.runtime | 58 +++++++++ docs/integration.md | 248 +++++++++++++++++++++++++++++++++++++ 5 files changed, 450 insertions(+), 26 deletions(-) create mode 100644 .dockerignore create mode 100644 docker-compose.example.yml create mode 100644 docker/Dockerfile.runtime create mode 100644 docs/integration.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4dad89f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,43 @@ +# Build artefacts — генерируются заново внутри образа +build/ +build-*/ +out/ +*.o +*.so +*.so.* + +# CMake +CMakeFiles/ +CMakeCache.txt +CTestTestfile.cmake + +# Git +.git/ +.gitignore + +# IDE / OS +.vscode/ +.idea/ +.DS_Store +*.swp +*~ + +# Local secrets +.env +.env.local + +# Tests +test-results/ + +# Python +__pycache__/ +*.pyc +.pytest_cache/ + +# FFmpeg (если когда-то распакован) +third_party/ffmpeg/ +ffmpeg-*/ + +# Sentinel files +*.pid +*.sock diff --git a/README.md b/README.md index bbd3f5a..aa8facd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Zero-copy sharing декодированных видеокадров между процессами через CUDA IPC. -**Статус:** ⚠️ Design phase. Дизайн-спецификация готова, реализация в процессе. +**Статус:** v0.1 — libcuframes готов, cuframes-rtsp-source готов, e2e-pipeline +протестирован (4×subscriber × 2000 frames, 0 torn). FFmpeg filter — v0.2. **Лицензия:** LGPL-2.1+ ## Минимальные требования @@ -46,37 +47,51 @@ Camera ─► ffmpeg + cuframes filter ─► VRAM ─┬─► consumer 1 (NVR) ## Состав -- **FFmpeg filter `cuda_ipc_export`** — добавляется в любой ffmpeg-pipeline -- **`libcuframes`** — C library + C++/Python bindings для consumers -- **Docker images** — drop-in replacement для существующих setups (включая Frigate) +- **`libcuframes`** — C library + C++ RAII wrapper (header-only) для producer/consumer +- **`cuframes-rtsp-source`** — standalone bridge RTSP → cuframes IPC (используется + как input для AI/mosaic consumer'ов; альтернатива FFmpeg-filter'а до v0.2) +- **`sub_count`** (examples/) — reference subscriber + smoke-test tool +- **Docker images** — runtime для drop-in deployment (см. docker/Dockerfile.runtime) +- **FFmpeg filter `cuda_ipc_export`** — *planned для v0.2* ## Quickstart -> 🚧 в разработке - ```bash -# Producer (после v0.1) -docker run --gpus all -v /run/cuframes:/run/cuframes ghcr.io//cuframes-ffmpeg:N \ - ffmpeg -hwaccel cuda -i rtsp://camera/stream \ - -vf "scale_cuda=1920:1080,cuda_ipc_export=key=cam1" \ - -c:v copy -f segment recording.mp4 +# Publisher: декодирует RTSP в CUDA, публикует через cuframes IPC +docker run -d --name cuframes-cam --runtime=nvidia --ipc=shareable \ + -v /run/cuframes:/run/cuframes \ + gx/cuframes:0.1 \ + /usr/local/bin/cuframes-rtsp-source \ + --rtsp 'rtsp://user:pass@cam/stream' --key cam1 --ring 6 -# Consumer (C++) -#include -cuframes::Subscriber sub("cam1"); -while (auto frame = sub.next()) { - // frame->cuda_ptr — device pointer, zero-copy - process_on_cuda(frame->cuda_ptr, frame->width, frame->height); +# Subscriber: получает декодированные frames zero-copy +docker run --rm --runtime=nvidia --ipc=container:cuframes-cam \ + -v /run/cuframes:/run/cuframes:ro \ + gx/cuframes:0.1 \ + /usr/local/bin/sub_count --key cam1 --max-frames 100 + +# Или C++ кодом: +#include +cuframes::SubscriberOptions opt; +opt.key = "cam1"; +cuframes::Subscriber sub(opt); +cudaStream_t s; cudaStreamCreate(&s); +while (auto frame = sub.next(s, 1000)) { // 1s timeout + cudaStreamSynchronize(s); + process_on_cuda(frame->cuda_ptr(), frame->width(), frame->height()); } ``` +**Полный integration guide** (docker-compose, cctv-processor, troubleshooting): +[docs/integration.md](docs/integration.md). + ## Документация - [docs/architecture.md](docs/architecture.md) — полный design document - [docs/protocol.md](docs/protocol.md) — bit-exact wire protocol spec - [docs/requirements.md](docs/requirements.md) — system requirements (hardware, software, build, Docker, k8s) +- [docs/integration.md](docs/integration.md) — **integration guide** для CCTV-стека (cuframes-rtsp-source + cctv-processor + Frigate) - [docs/benchmarks-phase0.md](docs/benchmarks-phase0.md) — Phase 0 latency/throughput measurements -- [docs/quickstart.md](docs/quickstart.md) — *(в разработке)* ## Why @@ -88,15 +103,16 @@ NVIDIA DeepStream закрытые / vendor-locked. Open source FFmpeg-plugin д ## Roadmap -| Phase | Что | Срок | +| Phase | Что | Статус | |---|---|---| -| 0 | PoC spike, CUDA IPC latency measurements | 3 дня | -| 1 | `libcuframes` (producer/consumer ring buffer + handshake protocol) | 1 неделя | -| 2 | FFmpeg filter `vf_cuda_ipc_export` + patched FFmpeg build | 1-2 недели | -| 3 | C++ / Python bindings | 1 неделя | -| 4 | Docker packaging + Frigate drop-in image | 3-5 дней | -| 5 | Reference consumer: 16-камерный mosaic compositor | 1 неделя | -| 6 | OSS launch (HN, reddit, FFmpeg upstream PR) | 3-5 дней | +| 0 | PoC spike, CUDA IPC latency measurements | ✅ done | +| 1 | `libcuframes` (producer/consumer ring buffer + handshake protocol) | ✅ done | +| 1.5 | C++ RAII wrapper, cuframes-rtsp-source, integration docs | ✅ done | +| 2 | FFmpeg filter `vf_cuda_ipc_input` + patched FFmpeg build | planned | +| 3 | Python bindings (pybind11) | planned | +| 4 | Docker runtime-images в реестре, Frigate plugin POC | planned | +| 5 | Reference: 16-камерный mosaic consumer | planned | +| 6 | OSS launch (HN, reddit, FFmpeg upstream PR) | planned | ## Contributing diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 0000000..898612e --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,59 @@ +# Пример docker-compose для cuframes intergration в CCTV-стек. +# +# Запуск: +# export CAM_PASS=your_camera_password +# docker compose -f docker-compose.example.yml up +# +# Этот файл — REFERENCE для adaptации в boost проектах (cctv, frigate stack). +# Не для production без review. + +services: + # Источник: декодирует RTSP в CUDA, публикует через cuframes IPC + cuframes-cam-test: + # Локальный build из этого репо + build: + context: . + dockerfile: docker/Dockerfile.runtime + image: gx/cuframes:0.1 + container_name: cuframes-cam-test + restart: unless-stopped + runtime: nvidia + environment: + NVIDIA_VISIBLE_DEVICES: all + NVIDIA_DRIVER_CAPABILITIES: compute,video,utility + # CRITICAL: ipc=shareable — позволяет другим контейнерам joinиться через + # `ipc: container:cuframes-cam-test` + ipc: shareable + shm_size: 1g + volumes: + - cuframes_sock:/run/cuframes + command: + - /usr/local/bin/cuframes-rtsp-source + - --rtsp=rtsp://admin:${CAM_PASS}@192.168.88.98:554/cam/realmonitor?channel=1&subtype=0 + - --key=cam-test + - --ring=6 + - --verbose + + # Потребитель: счётчик frames (smoke-test consumer) + cuframes-sub-test: + image: gx/cuframes:0.1 + container_name: cuframes-sub-test + depends_on: + - cuframes-cam-test + restart: "no" + runtime: nvidia + environment: + NVIDIA_VISIBLE_DEVICES: all + NVIDIA_DRIVER_CAPABILITIES: compute,video,utility + # CRITICAL: ipc namespace должен совпадать с publisher'ом + ipc: "container:cuframes-cam-test" + volumes: + - cuframes_sock:/run/cuframes:ro + command: + - /usr/local/bin/sub_count + - --key=cam-test + - --max-frames=500 + - --verbose + +volumes: + cuframes_sock: diff --git a/docker/Dockerfile.runtime b/docker/Dockerfile.runtime new file mode 100644 index 0000000..f68e4ff --- /dev/null +++ b/docker/Dockerfile.runtime @@ -0,0 +1,58 @@ +# Runtime-образ для cuframes — содержит: +# - libcuframes.so (built из dev-stage) +# - cuframes-rtsp-source bin +# - sub_count example bin +# - минимальный CUDA runtime + ffmpeg libs (без nvcc и headers) +# +# Не для разработки! Для dev — docker/Dockerfile.dev. +# +# Build: +# docker build -f docker/Dockerfile.runtime -t gx/cuframes:0.1 . +# +# Run (publisher): +# docker run --rm -it --runtime=nvidia --ipc=shareable \ +# -v cuframes_sock:/run/cuframes \ +# gx/cuframes:0.1 \ +# /usr/local/bin/cuframes-rtsp-source --rtsp ... --key ... + +# ─── Build stage ───────────────────────────────────────────────────────── +FROM nvidia/cuda:13.0.3-cudnn-devel-ubuntu24.04 AS build + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake ninja-build pkg-config \ + libavcodec-dev libavformat-dev libavutil-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +COPY . /src +RUN cmake -B build -S . -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_TOOLS=ON \ + -DBUILD_PYTHON_BINDINGS=OFF \ + -DBUILD_FFMPEG_FILTER=OFF \ + && cmake --build build --parallel + +# ─── Runtime stage ──────────────────────────────────────────────────────── +FROM nvidia/cuda:13.0.3-cudnn-runtime-ubuntu24.04 AS runtime + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + libavcodec60 libavformat60 libavutil58 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# libcuframes.so → /usr/local/lib (стандартный путь для ldconfig) +COPY --from=build /src/build/libcuframes/libcuframes.so* /usr/local/lib/ +COPY --from=build /src/include/cuframes /usr/local/include/cuframes +COPY --from=build /src/build/tools/cuframes-rtsp-source/cuframes-rtsp-source \ + /usr/local/bin/ +COPY --from=build /src/build/examples/sub_count/sub_count \ + /usr/local/bin/ + +RUN ldconfig + +# Default — print help, чтобы случайный `docker run` показал что это +CMD ["/usr/local/bin/cuframes-rtsp-source", "--help"] diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 0000000..0c830a5 --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,248 @@ +# Integration guide + +Этот guide описывает, как использовать cuframes для устранения дублирующего +GPU-декодирования между несколькими consumer'ами одного RTSP-потока. + +## Целевой сценарий (motivation) + +В типичной CCTV-системе один и тот же RTSP-stream декодируется несколько раз: + +``` + Камера ──► RTSP ──► Frigate (decode #1: detection + recording) + ─► mosaic-сервер (decode #2: компоновка сетки) + ─► AI-скрипт (decode #3: классификация / OCR) +``` + +На 16 камер × 25 fps × 3 consumer'а = 1200 NVDEC-операций/сек. RTX 5090 имеет +~3 NVDEC-движка, но шина PCIe и memory bandwidth становятся узким местом. + +С cuframes: + +``` + Камера ──► cuframes-rtsp-source ──► CUDA frame в /dev/shm + cudaIpcEvent + │ + ├──► Frigate (zero-copy) + ├──► mosaic-сервер (zero-copy) + └──► AI-скрипт (zero-copy) +``` + +Decode выполняется **один раз** на источник, потребители получают тот же CUDA +device pointer без копий. + +## Текущие limitations v0.1 + +- **Frigate** (по состоянию на 0.17) **не имеет** plugin-точки для приёма + готовых CUDA-frames. Чтобы убрать Frigate decode полностью, нужен: + - либо FFmpeg-filter `vf_cuda_ipc_input` (planned для cuframes v0.2 — требует + patch FFmpeg upstream и пересборку Frigate's bundled ffmpeg), + - либо Frigate-plugin (требует upstream работы с командой Frigate). +- В v0.1 практическое улучшение: **исключить decode для всех custom consumer'ов + кроме Frigate** (то есть cctv-processor, AI-скрипты — на cuframes; Frigate + остаётся как есть, со своим decode). + +Это уже даёт значительную экономию: было 1×Frigate + N×consumer decode'ов, +стало 1×Frigate + 1×cuframes-rtsp-source (один на все consumer'ы). + +## Сценарий 1: cuframes-rtsp-source + cctv-processor (FRIGATE остаётся) + +### docker-compose.yml + +```yaml +services: + # Один источник на камеру — публикует декодированный поток через cuframes IPC + cuframes-cam-parking: + image: gx/cuframes-rtsp-source:0.1 + restart: unless-stopped + runtime: nvidia + environment: + NVIDIA_VISIBLE_DEVICES: all + NVIDIA_DRIVER_CAPABILITIES: compute,video,utility + # CRITICAL: --ipc=shareable для cross-container CUDA IPC + ipc: shareable + shm_size: 1g + volumes: + - cuframes_sock:/run/cuframes + command: + - --rtsp=rtsp://admin:${CAM_PASS}@192.168.88.98:554/cam/realmonitor?channel=1&subtype=0 + - --key=cam-parking + - --ring=6 + - --realtime # не нужен для RTSP (real-time источник), оставлен для file:// + + # Frigate (как и был — со своим decode на main+sub streams) + frigate: + image: ghcr.io/blakeblackshear/frigate:stable-tensorrt + # ... как обычно + + # cctv-processor — подписывается на cuframes (без отдельного RTSP decode) + cctv-backend: + image: gx/cctv-processor:cuda + restart: unless-stopped + runtime: nvidia + # CRITICAL: --ipc=container:cuframes-cam-parking для shared CUDA context + ipc: container:cuframes-cam-parking + volumes: + - cuframes_sock:/run/cuframes:ro + environment: + # cuframes-keys для backend'а: + CCTV_SOURCES: cuframes:cam-parking,cuframes:cam-front-gate,... + +volumes: + cuframes_sock: +``` + +**Важно**: все consumer'ы должны использовать **тот же** `ipc:` namespace, что +и publisher (через `ipc: container:`). Это нужно для того, +чтобы cudaIpcOpenMemHandle / cudaIpcOpenEventHandle работали корректно. + +### Изменения в cctv-processor + +Нужно добавить новый Source-тип (рядом с RtspSource) — `CuframesSource`: + +```cpp +// cpp/apps/cctv-processor/src/sources/cuframes_source.hpp +#include + +class CuframesSource : public IVideoSource { +public: + CuframesSource(const std::string &key) : key_(key) { + cuframes::SubscriberOptions opt; + opt.key = key; + opt.consumer_name = "cctv-processor"; + opt.mode = CUFRAMES_MODE_NEWEST_ONLY; + sub_ = std::make_unique(opt); + cudaStreamCreate(&stream_); + } + + // Вызывается processing-loop'ом + std::optional nextFrame() override { + auto f = sub_->next(stream_, 100); // 100ms timeout + if (!f) return std::nullopt; + // cudaStreamWaitEvent уже сделан внутри next() — frame готов на stream_ + return GpuFrame{ + .cuda_ptr = f->cuda_ptr(), + .width = f->width(), + .height = f->height(), + .pitch_y = f->pitch_y(), + .pitch_uv = f->pitch_uv(), + .seq = f->seq(), + .pts_ns = f->pts_ns(), + .stream = stream_, + ._release = std::move(f), // RAII release при destroy + }; + } + +private: + std::string key_; + std::unique_ptr sub_; + cudaStream_t stream_; +}; +``` + +Конфиг `cameras.json` — добавить альтернативный source-тип: + +```jsonc +{ + "cameras": [ + { + "id": "parking", + "source_type": "cuframes", // вместо "rtsp" + "cuframes_key": "cam-parking", + // rtsp_url больше не нужен — он используется cuframes-rtsp-source'ом + } + ] +} +``` + +## Сценарий 2: AI-скрипт на Python (subscriber) + +Python-bindings — в Phase 3 cuframes. Сейчас простой workaround через +ctypes: + +```python +import ctypes +lib = ctypes.CDLL("libcuframes.so") +# ... wrap нужные функции — см. include/cuframes/cuframes.h +``` + +Или: writer simple C-обёртку, которая принимает callback и публикует +данные через ZMQ / shared memory в python-process. + +## Сценарий 3: Замена Frigate decode (v0.2+) + +Целевой сценарий — Frigate тоже подписан на cuframes. Реализуется через +один из двух путей: + +### Путь A: FFmpeg filter + +Добавить out-of-tree filter `vf_cuda_ipc_input` который читает кадр из +cuframes ring и эмитит AVFrame в pipeline. Frigate использует ffmpeg для +RTSP/decode — заменяем "RTSP→decode→detect" на +"cuframes_ipc_input→detect" (без decode'а вообще). + +Требования: +- Patch ffmpeg sources (libavfilter/vf_cuda_ipc_input.c + Makefile) +- Сборка кастомного Frigate-образа с patched ffmpeg +- Тестирование на совместимость с Frigate's pipeline assumptions + +### Путь B: Frigate plugin + +Engage с upstream Frigate чтобы добавить custom Source-type ("cuframes://"). +Это требует Python-API изменений в Frigate's source layer. + +## Verification checklist + +После настройки убедитесь: + +```bash +# 1. Publisher запущен и socket существует +ls -la /run/cuframes/cam-parking.sock +ls -la /dev/shm/cuframes-cam-parking + +# 2. Контейнеры в одном IPC namespace +docker inspect cuframes-cam-parking cctv-backend -f '{{.HostConfig.IpcMode}}' +# Должно быть "shareable" для publisher и "container:cuframes-cam-parking" для consumer + +# 3. Subscriber connect успешен +docker exec cctv-backend /usr/local/bin/sub_count --key cam-parking --max-frames 10 +# Ожидаем: +# [sub_count] connected to 'cuframes-cam-parking' +# [sub_count] received=10 gaps=0 elapsed=0.4s avg_fps=25 + +# 4. NVDEC utilization — должно быть N decodes, а не N*M +nvidia-smi dmon -s u +# Колонка %dec должна показать decode-нагрузку одного instance на камеру +``` + +## Troubleshooting + +### `Subscriber::create: timeout` +Subscriber не нашёл publisher. Причины: +- Publisher не запущен или crashed — проверь `docker logs cuframes-cam-parking` +- Socket-файл не volumes'нут в consumer-контейнер — добавь `volumes: + - cuframes_sock:/run/cuframes:ro` в consumer'е +- IPC namespace не совпадает — см. checklist пункт 2 + +### `cudaIpcOpenMemHandle returned 'invalid device pointer'` +- Контейнеры в РАЗНЫХ ipc namespace — должны быть в одном (через + `ipc: container:` или общий `ipc: shareable`) +- Subscriber работает на другом CUDA device — `--cuda-device` должен совпадать + у publisher и subscriber (одно и то же физическое GPU) + +### Высокая latency (>50ms tail) +- Subscriber slow — frames копятся в ring, по политике DROP_OLDEST они + пропускаются. Используй `CUFRAMES_MODE_NEWEST_ONLY` (default) — это нормально + для real-time системы. +- При STRICT_ORDER + STRICT_WAIT — slow consumer блокирует publisher. Не + рекомендуется для CCTV. + +### Frigate показывает чёрный экран после интеграции +- Frigate не подключён к cuframes (v0.1 — это not yet supported). В v0.1 + Frigate должен оставаться на своём RTSP decode (см. Сценарий 1). + +## Roadmap + +- **v0.1** (текущая): standalone publisher/subscriber, C/C++ API, examples. +- **v0.2**: FFmpeg filter `vf_cuda_ipc_input` (out-of-tree), Python bindings. +- **v0.3**: NVENC-bridge для re-encode подписчиков, Frigate plugin + proof-of-concept. +- **v1.0**: stable ABI, multi-GPU, documented Frigate integration.