Files
cuframes/docs/integration.md
T
gx f10413580d docs: cross-container CUDA IPC requires both --ipc и --pid namespace share
Реальный тест на 192.168.88.98 (1920x1080 HEVC, 25fps) показал: для отдельных
consumer-container'ов недостаточно ipc=container:X — нужен также
pid=container:X, иначе cudaIpcOpenEventHandle падает с invalid device
context. CUDA driver валидирует IPC peer через /proc/<pid>/...

E2E на реальной камере проверен:
  publisher (отдельный контейнер) -> consumer (docker exec): 250 frames, 0 gaps
  publisher (отдельный контейнер) -> consumer (отдельный с pid+ipc): 200, 0 gaps

Обновлено:
- docs/integration.md compose snippet, verification, troubleshooting section
- docker-compose.example.yml — добавлен pid: container:cuframes-cam-test
- README.md quickstart — добавлен --pid в docker run subscriber

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 06:37:09 +01:00

12 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: shared IPC + PID namespace с publisher'ом (см. ниже)
    ipc: container:cuframes-cam-parking
    pid: 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:

Важно — оба флага обязательны для cross-container CUDA IPC:

Флаг Зачем
ipc: container:<publisher> shared /dev/shm (нужен для shm_open под header/sockets)
pid: container:<publisher> CUDA driver валидирует IPC peer через /proc/<pid>/...; без этого cudaIpcOpenEventHandle падает с invalid device context

Альтернативы:

  • Запускать consumer внутри того же container'а через docker exec (наследует все namespaces) — удобно для отладки.
  • --ipc=host --pid=host — убирает namespacing вообще, но ослабляет изоляцию (не рекомендуется в production).

Изменения в 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 и PID namespace
docker inspect cuframes-cam-parking cctv-backend \
  -f '{{.Name}} ipc={{.HostConfig.IpcMode}} pid={{.HostConfig.PidMode}}'
# Publisher: ipc=shareable pid=(default)
# Consumer:  ipc=container:cuframes-cam-parking pid=container:cuframes-cam-parking

# 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

cudaIpcOpenEventHandle: invalid device context

Проявляется в отдельном consumer-container'е после успешного handshake (socket открыт, header валиден, но open event handle не проходит).

Причина: CUDA driver валидирует sender'а IPC peer'а через /proc. Если PID namespace не совпадает, sender невидим — context считается невалидным.

Fix: добавить pid: container:<publisher> в consumer's compose service (рядом с ipc: container:<publisher>). Проверено на CUDA 13.0 + driver 555+.

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.