# 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.