Files
cuframes/docs/integration.md
T
gx 44dab75e08 docs+docker: integration guide и runtime image для Frigate/cctv stack
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.
2026-05-14 23:47:56 +01:00

10 KiB
Raw Blame History

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

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.