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.
10 KiB
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).
- либо FFmpeg-filter
- В 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
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:<publisher_container>). Это нужно для того,
чтобы cudaIpcOpenMemHandle / cudaIpcOpenEventHandle работали корректно.
Изменения в cctv-processor
Нужно добавить новый Source-тип (рядом с RtspSource) — CuframesSource:
// cpp/apps/cctv-processor/src/sources/cuframes_source.hpp
#include <cuframes/cuframes.hpp>
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<cuframes::Subscriber>(opt);
cudaStreamCreate(&stream_);
}
// Вызывается processing-loop'ом
std::optional<GpuFrame> 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<cuframes::Subscriber> sub_;
cudaStream_t stream_;
};
Конфиг cameras.json — добавить альтернативный source-тип:
{
"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:
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
После настройки убедитесь:
# 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:<publisher>или общий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.