docs: full content + landing + RU translations

Initial documentation site for cuframes:

- Landing page (src/pages/index.mdx) — hero, quick example (publisher +
  subscriber), comparison table vs naive/DeepStream, honest "early but
  production-tested" status
- /docs/intro — full overview
- /docs/getting-started/{install,first-publisher,first-subscriber}
- /docs/concepts/{frame-vs-packet-ring,ownership-modes,sync-vmm-stream}
  with mermaid diagrams
- /docs/integration/{ffmpeg-demuxer,ffmpeg-filter,python}
- /docs/reference/{api-c,api-cpp,protocol} — full v4 wire protocol spec
  incl. VMM_FDS message, magic 0xCC7C1DCE bump diff
- /docs/faq — comparison vs DeepStream/GStreamer, license, multi-host
  limitations
- i18n/ru/ — parallel RU translation (tech terms latin, склонение апостроф)

Build:
- Docusaurus 3.10.1 + theme-mermaid + search-local
- Follows dagstack-* docs convention (canonical: dagstack-plugin-system-docs)
- Apache-2.0 license; cuframes lib itself remains LGPL-2.1+

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claude Opus
2026-05-26 23:31:03 +01:00
parent 7f45c36aa2
commit 8c3c43709d
10 changed files with 1579 additions and 8 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ int main(int argc, char **argv) {
**`cuframes_publisher_acquire(pub, &ptr)`** — returns a CUDA device pointer to the next writable slot. Valid only until the matching `publish()` call.
**`cudaMemsetAsync(ptr, ..., stream)`** — fill the frame on a CUDA stream of your choice. You do **not** have to synchronize this stream before publishing. The library will issue `cudaEventRecord` on the same stream inside `publish()`, and each subscriber will `cudaStreamWaitEvent` on its own stream before reading. This is the cross-process synchronization contract — see [Concepts → Cross-process sync](/docs/concepts/cross-process-sync).
**`cudaMemsetAsync(ptr, ..., stream)`** — fill the frame on a CUDA stream of your choice. You do **not** have to synchronize before calling `publish()`. The library issues `cuStreamSynchronize(stream)` inside `publish()` to flush pending GPU writes, then atomically publishes the sequence number. Subscribers see the data via hardware coherence on a same-GPU DtoD copy — no CUDA events needed. Full rationale: [Concepts → Sync: stream sync, not CUDA events](/docs/concepts/sync-vmm-stream).
**`cuframes_publisher_publish(pub, stream, pts_ns)`** — make the slot visible to subscribers. The `pts_ns` is opaque to the library; the recommended source is `cuframes_now_ns()` (CLOCK_MONOTONIC in nanoseconds).
@@ -142,4 +142,4 @@ if (r == CUFRAMES_ERR_DISCONNECTED) {
}
```
A reconnect pattern, including back-off and `consumer_name` reuse caveats, is covered in [Concepts → Reconnect](/docs/concepts/reconnect).
A reconnect pattern, including back-off and `consumer_name` reuse caveats, is : detect `CUFRAMES_ERR_DISCONNECTED`, call `cuframes_subscriber_destroy()`, back off (1-2 sec), and `cuframes_subscriber_create()` again with the same key. The FFmpeg `cuframes://` demuxer does this automatically (see [Integration → FFmpeg demuxer](/docs/integration/ffmpeg-demuxer)).
+2 -2
View File
@@ -7,7 +7,7 @@ sidebar_position: 1
cuframes is **Linux only**. The IPC mechanism relies on POSIX shared memory and `SCM_RIGHTS` file-descriptor passing over Unix sockets. Windows, macOS and WSL2 are not supported.
You also need an NVIDIA GPU with compute capability ≥ 7.5 (Turing or newer) and a CUDA 12+ driver. See [Concepts → Requirements](/docs/concepts/requirements) for the full matrix.
You also need an NVIDIA GPU with compute capability ≥ 7.5 (Turing or newer) and a CUDA 12+ driver. Specifically: 64-bit Linux, glibc 2.31+, kernel 5.4+ (for `cuMemMap` + `SCM_RIGHTS` support).
## Option 1 — Pre-built Docker image (recommended for trying it out)
@@ -41,7 +41,7 @@ docker run --rm --runtime=nvidia \
/usr/local/bin/sub_count --key cam1 --max-frames 100
```
See [Concepts → Docker IPC](/docs/concepts/docker-ipc) for the underlying namespace rules.
Namespace rules: subscriber must share IPC namespace with the publisher (POSIX `/dev/shm` lives in IPC ns). PID sharing is **not** required (this is a v0.4 property — pre-v0.4 needed both).
## Option 2 — Build from source
@@ -75,7 +75,7 @@ int main(int argc, char **argv) {
**`cuframes_publisher_acquire(pub, &ptr)`** — возвращает CUDA device pointer на следующий writable slot. Действителен только до соответствующего вызова `publish()`.
**`cudaMemsetAsync(ptr, ..., stream)`** — заполняем frame на CUDA stream'е по вашему выбору. **Не нужно** синхронизировать этот stream перед publish. Library внутри `publish()` сделает `cudaEventRecord` на тот же stream, а каждый subscriber вызовет `cudaStreamWaitEvent` на своём stream'е перед чтением. Это и есть cross-process контракт синхронизации — см. [Концепции → Cross-process sync](/docs/concepts/cross-process-sync).
**`cudaMemsetAsync(ptr, ..., stream)`** — заполняем frame на CUDA stream'е по вашему выбору. **Не нужно** синхронизировать stream до вызова `publish()`. Library внутри `publish()` делает `cuStreamSynchronize(stream)` чтобы дождаться flush pending GPU writes, потом атомарно публикует sequence number. Subscriber видит данные через hardware coherence при DtoD memcpy на том же GPU — никакие CUDA events не нужны. Полное обоснование: [Концепции → Sync: stream sync, не CUDA events](/docs/concepts/sync-vmm-stream).
**`cuframes_publisher_publish(pub, stream, pts_ns)`** — делает slot видимым subscriber'ам. `pts_ns` непрозрачен для library; рекомендуемый источник — `cuframes_now_ns()` (CLOCK_MONOTONIC в наносекундах).
@@ -142,4 +142,4 @@ if (r == CUFRAMES_ERR_DISCONNECTED) {
}
```
Паттерн reconnect, включая back-off и нюансы переиспользования `consumer_name`, разобран в [КонцепцииReconnect](/docs/concepts/reconnect).
Паттерн reconnect, включая back-off и нюансы переиспользования `consumer_name`, : поймать `CUFRAMES_ERR_DISCONNECTED`, вызвать `cuframes_subscriber_destroy()`, подождать (1-2 сек) и попробовать `cuframes_subscriber_create()` снова с тем же key. FFmpeg-демухер `cuframes://` делает это автоматически (см. [ИнтеграцияFFmpeg demuxer](/docs/integration/ffmpeg-demuxer)).
@@ -7,7 +7,7 @@ sidebar_position: 1
cuframes работает **только на Linux**. IPC-механизм опирается на POSIX shared memory и передачу file descriptors через `SCM_RIGHTS` поверх Unix sockets. Windows, macOS и WSL2 не поддерживаются.
Также нужен NVIDIA GPU с compute capability ≥ 7.5 (Turing или новее) и CUDA 12+ driver. Полная матрица — в [Концепции → Требования](/docs/concepts/requirements).
Также нужен NVIDIA GPU с compute capability ≥ 7.5 (Turing или новее) и CUDA 12+ driver. Точнее: 64-bit Linux, glibc 2.31+, ядро 5.4+ (для `cuMemMap` + `SCM_RIGHTS`).
## Вариант 1 — готовый Docker-образ (рекомендуется для первого знакомства)
@@ -41,7 +41,7 @@ docker run --rm --runtime=nvidia \
/usr/local/bin/sub_count --key cam1 --max-frames 100
```
Подробнее о правилах namespace — [Концепции → Docker IPC](/docs/concepts/docker-ipc).
Правила namespace: subscriber должен share IPC namespace с publisher (POSIX `/dev/shm` живёт в IPC ns). PID sharing **не** нужен (это свойство v0.4 — до v0.4 нужны были оба).
## Вариант 2 — сборка из исходников
@@ -0,0 +1,531 @@
---
sidebar_position: 1
title: C API
---
# C API reference
Полный листинг public C API из `<cuframes/cuframes.h>` (libcuframes 0.4.0). Source of truth — header в repo, эта страница его дублирует в Docusaurus-формате с cross-links на концептуальные разделы.
## Headers & linkage
```c
#include <cuframes/cuframes.h>
```
```bash
# pkg-config (если установлено через .deb)
cc app.c $(pkg-config --cflags --libs cuframes)
# вручную
cc app.c -lcuframes
```
`libcuframes.so.0` динамически линкуется к `libcuda.so.1` (CUDA driver API, не runtime). Для большинства user-кода также нужен `-lcudart`, чтобы манипулировать своими CUDA streams.
## Соглашения
- Все функции возвращают `int``0` (CUFRAMES_OK) при успехе, отрицательный код из [`cuframes_error_t`](#error-codes) при ошибке. Расшифровка кода — [`cuframes_strerror`](#error-decoding).
- Все handle types (`cuframes_publisher_t`, `cuframes_subscriber_t`, `cuframes_frame_t`, `cuframes_packet_t`) — **opaque**. Поля недоступны напрямую, только через accessor-функции. Это даёт ABI-stability в minor-релизах.
- Каждый handle принадлежит **одному потоку**. Cross-thread access — undefined behavior. Несколько handle'ов в разных потоках — OK.
- Endianness — little-endian (это и так фиксируется CUDA-платформами).
## Версии и error codes
### Версия библиотеки
```c
const char *cuframes_version_string(void);
uint32_t cuframes_protocol_version(void);
```
`cuframes_version_string` возвращает runtime-версию libcuframes в формате `"MAJOR.MINOR.PATCH"` (например `"0.4.0"`). Compile-time константы:
```c
#define CUFRAMES_VERSION_MAJOR 0
#define CUFRAMES_VERSION_MINOR 4
#define CUFRAMES_VERSION_PATCH 0
```
`cuframes_protocol_version` возвращает версию wire-protocol (для v0.4 — `4`). Subscribers с другой protocol-версией не подключатся — publisher вернёт `HELLO_RESP(result=CUFRAMES_ERR_PROTOCOL)`. См. [Protocol reference](/docs/reference/protocol).
### Error codes
```c
typedef enum cuframes_error {
CUFRAMES_OK = 0,
CUFRAMES_ERR_INVALID_ARG = -1,
CUFRAMES_ERR_OUT_OF_MEMORY = -2,
CUFRAMES_ERR_CUDA = -3,
CUFRAMES_ERR_IO = -4,
CUFRAMES_ERR_NOT_FOUND = -5,
CUFRAMES_ERR_ALREADY_EXISTS = -6,
CUFRAMES_ERR_TIMEOUT = -7,
CUFRAMES_ERR_PROTOCOL = -8,
CUFRAMES_ERR_DISCONNECTED = -9,
CUFRAMES_ERR_FORMAT = -10,
CUFRAMES_ERR_WOULD_BLOCK = -11,
CUFRAMES_ERR_TOO_MANY = -12,
CUFRAMES_ERR_PACKET_OVERSIZED = -20,
CUFRAMES_ERR_NO_PACKET_RING = -21,
CUFRAMES_ERR_NO_CODEC_PARAMS = -22,
CUFRAMES_ERR_PACKET_OVERRUN = -23,
CUFRAMES_ERR_INTERNAL = -100,
} cuframes_error_t;
```
| Код | Имя | Значение |
|---|---|---|
| `0` | `CUFRAMES_OK` | Success |
| `-1` | `CUFRAMES_ERR_INVALID_ARG` | NULL pointer или невалидное значение в config |
| `-2` | `CUFRAMES_ERR_OUT_OF_MEMORY` | malloc / cudaMalloc fail |
| `-3` | `CUFRAMES_ERR_CUDA` | Ошибка CUDA runtime / driver |
| `-4` | `CUFRAMES_ERR_IO` | socket / mmap / eventfd |
| `-5` | `CUFRAMES_ERR_NOT_FOUND` | Publisher с таким key не найден |
| `-6` | `CUFRAMES_ERR_ALREADY_EXISTS` | Publisher с этим key уже есть, либо `consumer_name` занят |
| `-7` | `CUFRAMES_ERR_TIMEOUT` | Операция не завершилась за timeout |
| `-8` | `CUFRAMES_ERR_PROTOCOL` | Несовместимая версия wire protocol |
| `-9` | `CUFRAMES_ERR_DISCONNECTED` | Publisher умер или сеть оборвалась |
| `-10` | `CUFRAMES_ERR_FORMAT` | Неподдерживаемый pixel format или несовпадение размеров |
| `-11` | `CUFRAMES_ERR_WOULD_BLOCK` | Non-blocking call — данных пока нет |
| `-12` | `CUFRAMES_ERR_TOO_MANY` | Превышен `MAX_SUBSCRIBERS` (32) |
| `-20` | `CUFRAMES_ERR_PACKET_OVERSIZED` | `publish_packet` size > `max_packet_size` |
| `-21` | `CUFRAMES_ERR_NO_PACKET_RING` | Subscriber запросил packets, у publisher'а нет ring'а |
| `-22` | `CUFRAMES_ERR_NO_CODEC_PARAMS` | Extradata ещё не set publisher'ом |
| `-23` | `CUFRAMES_ERR_PACKET_OVERRUN` | Slow subscriber, packet seq уехал — resync на keyframe |
| `-100` | `CUFRAMES_ERR_INTERNAL` | Bug в библиотеке — воспроизводи и репорти |
### Расшифровка ошибок
```c
const char *cuframes_strerror(int err);
```
Возвращает human-readable строку для error code. Pointer указывает на static storage, владеть им дальше не надо. Никогда не возвращает NULL — для unknown code вернёт `"unknown error"`.
## Pixel formats
```c
typedef enum cuframes_format {
CUFRAMES_FORMAT_NV12 = 0,
CUFRAMES_FORMAT_YUV420P = 1,
CUFRAMES_FORMAT_RGB = 2,
CUFRAMES_FORMAT_BGR = 3,
CUFRAMES_FORMAT_RGBA = 4,
CUFRAMES_FORMAT_GRAYSCALE = 5,
} cuframes_format_t;
```
| Format | Layout | Когда |
|---|---|---|
| `NV12` | Y plane + interleaved UV plane | NVDEC native, default для video pipeline'ов |
| `YUV420P` | Y + U + V separate planes | FFmpeg `yuv420p` |
| `RGB` | 24bpp packed RGB | ML inference, OpenGL |
| `BGR` | 24bpp packed BGR | OpenCV native |
| `RGBA` | 32bpp packed RGBA | overlays, compositing |
| `GRAYSCALE` | 8bpp single plane | depth maps, masks |
Format фиксирован для publisher'а в момент create — поменять нельзя без destroy + recreate с новым key.
## Policy & mode enums
### Publisher policy
```c
typedef enum cuframes_publisher_policy {
CUFRAMES_POLICY_DROP_OLDEST = 0,
CUFRAMES_POLICY_STRICT_WAIT = 1,
} cuframes_publisher_policy_t;
```
- `DROP_OLDEST` — publisher не ждёт, перезаписывает next slot. Slow consumer пропускает кадры. **Default для real-time.**
- `STRICT_WAIT` — publisher блокируется, пока все подписанные consumers не ACK'нут. Не теряет кадры, но slowest consumer тормозит всех. Для recording или критичной аналитики.
### Subscriber mode
```c
typedef enum cuframes_subscriber_mode {
CUFRAMES_MODE_NEWEST_ONLY = 0,
CUFRAMES_MODE_STRICT_ORDER = 1,
} cuframes_subscriber_mode_t;
```
- `NEWEST_ONLY` — брать самый свежий frame, пропускать промежуточные. **Default.**
- `STRICT_ORDER` — все frames по порядку. Если ring overflow — вернётся `CUFRAMES_ERR_DISCONNECTED`, нужно reconnect.
### Ownership mode
```c
typedef enum cuframes_ownership_mode {
CUFRAMES_OWNERSHIP_LIBRARY = 0,
CUFRAMES_OWNERSHIP_EXTERNAL = 1,
} cuframes_ownership_mode_t;
```
- `LIBRARY` — library владеет VMM-pool'ом (см. [Sync model](/docs/concepts/sync-vmm-stream)). Publisher делает `acquire()` → пишет → `publish()`. **Единственный поддерживаемый mode в v0.4.**
- `EXTERNAL`**в v0.4 deprecated.** `cuframes_publisher_create_external` возвращает `CUFRAMES_ERR_INVALID_ARG`. Для FFmpeg filter integration используй `LIBRARY` + одна D2D-копия в acquire'нутый slot (cuframes-rtsp-source именно так и работает с v0.4).
## Frame accessors
`cuframes_frame_t` — opaque handle на frame, полученный у subscriber'а. Валиден от `cuframes_subscriber_next` до `cuframes_subscriber_release`.
```c
typedef struct cuframes_frame cuframes_frame_t;
void *cuframes_frame_cuda_ptr(const cuframes_frame_t *frame);
cuframes_format_t cuframes_frame_format(const cuframes_frame_t *frame);
void cuframes_frame_size(const cuframes_frame_t *frame,
int32_t *width_out, int32_t *height_out);
int32_t cuframes_frame_pitch_y(const cuframes_frame_t *frame);
int32_t cuframes_frame_pitch_uv(const cuframes_frame_t *frame);
uint64_t cuframes_frame_seq(const cuframes_frame_t *frame);
int64_t cuframes_frame_pts_ns(const cuframes_frame_t *frame);
```
| Функция | Возвращает |
|---|---|
| `cuda_ptr` | CUDA device pointer на frame data (read-only для consumer'а) |
| `format` | `cuframes_format_t` |
| `size` | Ширина и высота в пикселях через out-параметры |
| `pitch_y` | Pitch (байт на строку) для Y plane или единственного plane |
| `pitch_uv` | Pitch для UV plane (NV12 / YUV420P); `0` для форматов без UV |
| `seq` | Sequence number — монотонная нумерация у publisher'а |
| `pts_ns` | Timestamp publisher'а (наносекунды, `CLOCK_MONOTONIC`) |
PTS epoch caveat: publisher и consumer могут иметь разные эпохи `CLOCK_MONOTONIC` (после publisher restart counter сбрасывается). Consumer должен sanity-checkить — например, детектить epoch change, когда `pts_ns_curr < pts_ns_prev`.
## Publisher API
### Config struct
```c
typedef struct cuframes_publisher_config {
const char *key; /* unique имя ("cam1"). Не NULL. */
int32_t width;
int32_t height;
cuframes_format_t format;
cuframes_ownership_mode_t ownership;
int32_t ring_size; /* 2..16, рекомендуется 4 */
cuframes_publisher_policy_t policy;
int32_t consumer_ack_timeout_ms; /* STRICT_WAIT; 0 = ждать вечно */
int32_t cuda_device;
uint64_t _reserved[4]; /* должно быть 0 */
} cuframes_publisher_config_t;
```
| Поле | Ограничения |
|---|---|
| `key` | ASCII `[a-zA-Z0-9_-]{1,63}`. Не NULL. |
| `width`, `height` | Pixels. Фиксированы после create. |
| `format` | См. [Pixel formats](#pixel-formats). Фиксирован. |
| `ownership` | В v0.4 — только `LIBRARY`. |
| `ring_size` | 2..16 для `LIBRARY`. Меньше — больше шанс overrun, больше — больше VRAM. |
| `policy` | См. [Policy](#publisher-policy). |
| `consumer_ack_timeout_ms` | Только для `STRICT_WAIT`. `0` = ждать бесконечно. |
| `cuda_device` | Обычно `0`. Должен совпадать с consumer'ским. |
| `_reserved` | Reserved для ABI-stability, должно быть нулями. |
### Create / destroy
```c
int cuframes_publisher_create(const cuframes_publisher_config_t *cfg,
cuframes_publisher_t **out);
int cuframes_publisher_create_external(const cuframes_publisher_config_t *cfg,
void *const *cuda_ptrs,
int32_t ptr_count,
size_t frame_size,
cuframes_publisher_t **out);
int cuframes_publisher_destroy(cuframes_publisher_t *pub);
```
`cuframes_publisher_create` аллоцирует `ring_size` × `frame_size` через `cuMemCreate(POSIX_FILE_DESCRIPTOR)`, открывает Unix socket `/run/cuframes/<key>.sock`, mmap'ит `/dev/shm/cuframes-<key>`. См. [Synchronization & VMM stream](/docs/concepts/sync-vmm-stream).
Ошибки:
| Код | Когда |
|---|---|
| `INVALID_ARG` | `cfg` NULL, ring_size out of range, key не проходит regex |
| `ALREADY_EXISTS` | Publisher с этим key уже есть и его процесс живой |
| `CUDA` | `cuMemCreate` fail (out of VRAM, unsupported driver) |
| `IO` | Не получилось `bind()` socket или `shm_open()` |
`cuframes_publisher_create_external`**в v0.4 возвращает `CUFRAMES_ERR_INVALID_ARG`**. EXTERNAL ownership убран потому, что VMM требует `cuMemCreate`-allocated memory. Для упомянутого FFmpeg filter use case — переходи на `LIBRARY` + одна `cudaMemcpyAsync(D2D)` в acquire'нутый slot. Cuframes-rtsp-source работает именно так начиная с v0.4.
`cuframes_publisher_destroy` шлёт `SHUTDOWN` всем connected subscribers, unlink'ает socket и shm. NULL-safe.
### Publish (LIBRARY mode)
```c
int cuframes_publisher_acquire(cuframes_publisher_t *pub,
void **cuda_ptr_out);
int cuframes_publisher_publish(cuframes_publisher_t *pub,
void *stream, /* cudaStream_t */
int64_t pts_ns);
```
`acquire` возвращает CUDA device pointer на следующий slot в ring'е для записи. Pointer стабилен, пока ты держишь ring slot — обычно до следующего `publish`.
Ошибки:
| Код | Когда |
|---|---|
| `TIMEOUT` | Все slots заняты в `STRICT_WAIT` mode |
| `INVALID_ARG` | `pub` NULL, или publisher был создан в EXTERNAL mode |
`publish` финализирует acquire'нутый slot. Внутри: `cuStreamSynchronize(stream)` гарантирует, что producer's writes hardware-coherent, затем atomic update `slot.seq` + `global_seq`. См. [Synchronization](/docs/concepts/sync-vmm-stream) — почему именно stream sync, а не CUDA events.
| Параметр | Значение |
|---|---|
| `stream` | CUDA stream, на котором писались данные. `0` для default stream. |
| `pts_ns` | Timestamp, рекомендуется [`cuframes_now_ns()`](#utils). |
### Publish (EXTERNAL mode)
```c
int cuframes_publisher_publish_external(cuframes_publisher_t *pub,
void *cuda_ptr,
void *stream,
int64_t pts_ns);
```
**В v0.4 deprecated** — см. note про `create_external` выше. Всегда возвращает `CUFRAMES_ERR_INVALID_ARG`.
## Subscriber API (sync)
### Config struct
```c
typedef struct cuframes_subscriber_config {
const char *key;
const char *consumer_name; /* NULL = auto */
cuframes_subscriber_mode_t mode;
int32_t cuda_device;
int32_t connect_timeout_ms; /* 0=fail, -1=ждать вечно */
uint64_t _reserved[4];
} cuframes_subscriber_config_t;
```
| Поле | Ограничения |
|---|---|
| `key` | Должен совпадать с publisher'ским |
| `consumer_name` | Если NULL — library сгенерирует `subscriber-<pid>-<random>`. Unique в пределах publisher'а — иначе `ALREADY_EXISTS`. MAX 32 subscribers. |
| `mode` | См. [Subscriber mode](#subscriber-mode) |
| `cuda_device` | Должен совпадать с publisher'ским — VMM FD импортируется на тот же device |
| `connect_timeout_ms` | `0` = fail сразу с `NOT_FOUND`; `-1` = ждать вечно |
### Create / destroy
```c
int cuframes_subscriber_create(const cuframes_subscriber_config_t *cfg,
cuframes_subscriber_t **out);
int cuframes_subscriber_destroy(cuframes_subscriber_t *sub);
```
`create` выполняет handshake (`HELLO``SUBSCRIBE``VMM_FDS`), импортирует N file descriptors через `cuMemImportFromShareableHandle`. См. [Protocol reference §3](/docs/reference/protocol).
Ошибки:
| Код | Когда |
|---|---|
| `NOT_FOUND` | Publisher с этим key не найден до `connect_timeout_ms` |
| `PROTOCOL` | Publisher имеет другую protocol version |
| `TOO_MANY` | Publisher уже имеет 32 subscriber'а |
| `ALREADY_EXISTS` | `consumer_name` занят |
| `CUDA` | `cuMemImportFromShareableHandle` fail |
`destroy` — graceful close: `UNSUBSCRIBE` msg → cleanup VMM mappings → close socket. NULL-safe.
### Next frame
```c
int cuframes_subscriber_next(cuframes_subscriber_t *sub,
void *consumer_stream,
cuframes_frame_t **frame_out,
int32_t timeout_ms);
int cuframes_subscriber_release(cuframes_subscriber_t *sub,
cuframes_frame_t *frame);
```
`next` блокируется до `timeout_ms`, ожидая новый frame. Семантика по mode:
- `NEWEST_ONLY` — возвращает самый свежий frame, пропускает промежуточные;
- `STRICT_ORDER` — следующий по seq; `DISCONNECTED` при overflow.
`consumer_stream` — твой CUDA stream, на котором будешь читать frame. В v0.4 синхронизация делается на стороне publisher'а через `cuStreamSynchronize`, так что параметр зарезервирован для будущего event-based fast path и сейчас не обязателен (`0` допустимо).
| Параметр | Значение |
|---|---|
| `consumer_stream` | CUDA stream consumer'а. `0` допустимо. |
| `frame_out` | Output handle. Освободить через `release`. |
| `timeout_ms` | `<0` = блокироваться, `0` = non-blocking (вернёт `WOULD_BLOCK`), `>0` = с timeout'ом |
Ошибки:
| Код | Когда |
|---|---|
| `WOULD_BLOCK` | `timeout_ms=0` и нет данных |
| `TIMEOUT` | За `timeout_ms` ничего не пришло |
| `DISCONNECTED` | Publisher shutdown, либо ring overrun в `STRICT_ORDER` |
`release` ACK'ает frame publisher'у (важно для `STRICT_WAIT` policy). NULL-safe. После release frame handle invalid.
## Subscriber API (async)
```c
typedef void (*cuframes_frame_callback_t)(const cuframes_frame_t *frame,
void *user_data);
typedef void (*cuframes_error_callback_t)(int err, const char *msg,
void *user_data);
int cuframes_async_subscriber_create(const cuframes_subscriber_config_t *cfg,
cuframes_frame_callback_t on_frame,
cuframes_error_callback_t on_error,
void *user_data,
cuframes_async_subscriber_t **out);
int cuframes_async_subscriber_destroy(cuframes_async_subscriber_t *sub);
```
Callback-based wrapper над sync API. Library поднимает internal thread, который сидит на `next`, вызывает `on_frame` / `on_error`, сам делает `release` после возврата из callback.
Ограничения:
- Frame **валиден только в течение callback'а** — никаких saved pointer'ов;
- Library использует internal CUDA stream, pre-wait уже выполнен — для своего stream'а используй sync API;
- `destroy` join'ит internal thread и гарантирует, что callback больше не вызовется после возврата (может занять до длительности текущего callback'а).
## Packet ring API
См. [Frame vs Packet ring](/docs/concepts/frame-vs-packet-ring) — когда нужно использовать packet ring.
### Flags
```c
#define CUFRAMES_PKT_FLAG_KEY 0x01u
#define CUFRAMES_PKT_FLAG_CORRUPT 0x02u
#define CUFRAMES_PKT_FLAG_DISCONTINUITY 0x04u
#define CUFRAMES_PKT_FLAG_LAST_IN_AU 0x08u
```
Биты соответствуют `AV_PKT_FLAG_*` у FFmpeg.
### Publisher-side
```c
typedef struct cuframes_packet_ring_options {
uint32_t ring_slots; /* default 64 */
uint32_t data_size; /* default 8 MiB */
uint32_t max_packet_size; /* default 2 MiB */
uint32_t codec_id; /* AV_CODEC_ID_* */
uint64_t _reserved[4];
} cuframes_packet_ring_options_t;
int cuframes_publisher_enable_packets(cuframes_publisher_t *pub,
const cuframes_packet_ring_options_t *opts);
int cuframes_publisher_set_codec_extradata(cuframes_publisher_t *pub,
const void *extradata, size_t size);
int cuframes_publisher_publish_packet(cuframes_publisher_t *pub,
const void *data, size_t size,
int64_t pts_ns, int64_t dts_ns,
uint32_t flags);
```
`enable_packets` создаёт отдельный SHM `/dev/shm/cuframes-<key>-packets`. **Должно быть вызвано до первого `publish_packet` и желательно до того, как subscribers начнут подключаться** — иначе subscriber увидит publisher без ring'а и не получит packets. `opts=NULL` → default sizing.
`set_codec_extradata` пишет SPS/PPS/VPS bytes в shared header. Subscribers (FFmpeg demuxer) подставят это в `AVCodecContext.extradata`. Size ≤ 4096 байт.
`publish_packet` записывает один NAL unit (Annex B). На IDR обязательно ставить `CUFRAMES_PKT_FLAG_KEY` — иначе late subscriber не сможет resync'нуться.
Ошибки:
| Код | Когда |
|---|---|
| `NO_PACKET_RING` | Не вызвали `enable_packets` |
| `PACKET_OVERSIZED` | `size > max_packet_size` |
| `ALREADY_EXISTS` | (`enable_packets`) ring уже активирован |
### Subscriber-side
```c
typedef struct cuframes_packet cuframes_packet_t;
const void *cuframes_packet_data(const cuframes_packet_t *p);
size_t cuframes_packet_size(const cuframes_packet_t *p);
int64_t cuframes_packet_pts(const cuframes_packet_t *p);
int64_t cuframes_packet_dts(const cuframes_packet_t *p);
uint32_t cuframes_packet_flags(const cuframes_packet_t *p);
uint64_t cuframes_packet_seq(const cuframes_packet_t *p);
int cuframes_subscriber_enable_packets(cuframes_subscriber_t *sub);
int cuframes_subscriber_next_packet(cuframes_subscriber_t *sub,
cuframes_packet_t **pkt_out,
int32_t timeout_ms);
int cuframes_subscriber_release_packet(cuframes_subscriber_t *sub,
cuframes_packet_t *pkt);
int cuframes_subscriber_get_codec_params(cuframes_subscriber_t *sub,
uint32_t *codec_id_out,
const void **extradata_out,
size_t *extradata_size_out);
```
`enable_packets` открывает второй SHM (если publisher его создал). Subscriber может одновременно иметь frames ring и packets ring, или только один из них.
`next_packet` — late subscriber на первом вызове начнёт с `last_keyframe_seq` publisher'а (decoder получит valid stream без glitches). См. [Protocol §10.14](/docs/reference/protocol).
Ошибки:
| Код | Когда |
|---|---|
| `WOULD_BLOCK` | `timeout_ms=0`, нет данных |
| `TIMEOUT` | За `timeout_ms` ничего не пришло |
| `PACKET_OVERRUN` | Subscriber отстал; library автоматически resync'нется на keyframe на next call |
| `DISCONNECTED` | Publisher shutdown |
| `NOT_FOUND` | (`enable_packets`) publisher не имеет packet ring |
`get_codec_params` возвращает pointer в library-local buffer, валидный пока subscriber жив. Если хочешь hold data past subscriber lifetime — копируй сам. Возвращает `NO_CODEC_PARAMS`, если publisher ещё не звал `set_codec_extradata`.
`release_packet` — NULL-safe. После release pointer'ы от `cuframes_packet_*` invalid.
## Utils
### Расчёт frame size
```c
int cuframes_calc_frame_size(cuframes_format_t format,
int32_t width, int32_t height,
size_t *size_out,
int32_t *pitch_y_out,
int32_t *pitch_uv_out);
```
Учитывает pitch alignment 256 байт (CUDA recommendation). `pitch_y_out` / `pitch_uv_out` опциональны (можно NULL). Возвращает `INVALID_ARG` для unknown format.
### Monotonic time
```c
int64_t cuframes_now_ns(void);
```
`CLOCK_MONOTONIC` в наносекундах. Используй как `pts_ns` для real-time pipeline'ов:
```c
cuframes_publisher_publish(pub, stream, cuframes_now_ns());
```
## См. также
- [C++ API](/docs/reference/api-cpp) — RAII wrapper.
- [Protocol reference](/docs/reference/protocol) — wire format, handshake, ABI layouts.
- [Frame vs Packet ring](/docs/concepts/frame-vs-packet-ring) — когда какой использовать.
- [Synchronization](/docs/concepts/sync-vmm-stream) — почему `cuStreamSynchronize`, а не CUDA events.
- [First publisher](/docs/getting-started/first-publisher) — рабочий C-пример.
@@ -0,0 +1,351 @@
---
sidebar_position: 2
title: C++ API
---
# C++ API reference
`<cuframes/cuframes.hpp>` — header-only RAII-wrapper над [C API](/docs/reference/api-c). Тонкий слой: handle-классы с automatic cleanup, exceptions вместо int return codes, `std::optional<FrameRef>` для `next`.
## Headers & linkage
```cpp
#include <cuframes/cuframes.hpp>
```
```bash
# C++17 минимум (нужен std::optional)
c++ -std=c++17 app.cpp -lcuframes
# при использовании своих CUDA streams
c++ -std=c++17 app.cpp -lcuframes -lcudart
```
Header-only — самой C++ библиотеки не существует, есть только wrapper над `libcuframes.so`. ABI-совместимость наследуется от C API.
Всё в namespace `cuframes`:
```cpp
namespace cuframes {
class Error;
class Frame;
class FrameRef;
class Publisher;
class Subscriber;
class AsyncSubscriber;
struct PublisherOptions;
struct SubscriberOptions;
inline int64_t now_ns();
inline size_t calc_frame_size(...);
}
```
## Исключения
```cpp
class cuframes::Error : public std::runtime_error {
public:
Error(int code, const std::string &context);
int code() const noexcept;
};
```
Бросается из всех методов, кроме explicitly `noexcept`. `code()` — оригинальный [`cuframes_error_t`](/docs/reference/api-c#error-codes); `what()``"<context>: <strerror>"`.
```cpp
try {
cuframes::Publisher pub({.key = "cam1", .width = 1920, .height = 1080});
} catch (const cuframes::Error &e) {
if (e.code() == CUFRAMES_ERR_ALREADY_EXISTS) {
// stale publisher с этим key
}
}
```
## Frame & FrameRef
`Frame` — read-only non-owning view над `cuframes_frame_t`. Используется в callback'ах async subscriber'а.
`FrameRef` — RAII owning handle: `release` вызывается автоматически в destructor'е. Moveable, не copyable.
```cpp
class Frame {
public:
void *cuda_ptr() const noexcept;
cuframes_format_t format() const noexcept;
int32_t width() const noexcept;
int32_t height() const noexcept;
int32_t pitch_y() const noexcept;
int32_t pitch_uv() const noexcept;
uint64_t seq() const noexcept;
int64_t pts_ns() const noexcept;
const cuframes_frame_t *raw() const noexcept;
};
class FrameRef {
public:
explicit operator bool() const noexcept;
Frame view() const noexcept;
// Shortcut accessors: cuda_ptr, width, height, pitch_y, pitch_uv, seq, pts_ns
};
```
Все accessor-методы `noexcept` — они идут в C accessor'ы, которые ничего не аллоцируют.
## Publisher
```cpp
struct PublisherOptions {
std::string key;
int32_t width = 0;
int32_t height = 0;
cuframes_format_t format = CUFRAMES_FORMAT_NV12;
int32_t ring_size = 4;
cuframes_publisher_policy_t policy = CUFRAMES_POLICY_DROP_OLDEST;
int32_t consumer_ack_timeout_ms = 0;
int32_t cuda_device = 0;
};
class Publisher {
public:
explicit Publisher(const PublisherOptions &opt);
// EXTERNAL ownership — DEPRECATED в v0.4, бросает Error(INVALID_ARG)
Publisher(const PublisherOptions &opt,
void *const *cuda_ptrs, int32_t ptr_count, size_t frame_size);
~Publisher();
Publisher(Publisher &&) noexcept;
Publisher &operator=(Publisher &&) noexcept;
void *acquire();
void publish(void *stream, int64_t pts_ns);
// EXTERNAL mode — DEPRECATED в v0.4
void publish_external(void *cuda_ptr, void *stream, int64_t pts_ns);
// Packet ring
void enable_packets(const cuframes_packet_ring_options_t *opts = nullptr);
void set_codec_extradata(const void *data, size_t size);
int publish_packet(const void *data, size_t size,
int64_t pts_ns, int64_t dts_ns, uint32_t flags) noexcept;
cuframes_publisher_t *raw() noexcept;
};
```
**Note про deprecated EXTERNAL ownership-конструктор.** В v0.4 второй конструктор `Publisher(opt, cuda_ptrs, ...)` под капотом вызывает `cuframes_publisher_create_external` и сразу получает `INVALID_ARG` → бросает `cuframes::Error`. Для FFmpeg filter / custom decoder integration переходи на LIBRARY mode + одна `cudaMemcpyAsync(D2D)` в acquire'нутый pointer. См. [C API note](/docs/reference/api-c#create--destroy).
`publish_packet` — единственный non-throwing метод (возвращает int). Это сделано чтобы в hot loop encoder'а не платить за exception unwind на каждом packet.
Минимальный publisher-loop:
```cpp
cuframes::Publisher pub({
.key = "cam1",
.width = 1920, .height = 1080,
.format = CUFRAMES_FORMAT_NV12,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (;;) {
void *slot = pub.acquire();
// ... NVDEC / kernel пишут в slot на `stream` ...
pub.publish(stream, cuframes::now_ns());
}
```
## Subscriber (sync)
```cpp
struct SubscriberOptions {
std::string key;
std::string consumer_name; // empty = auto-generate
cuframes_subscriber_mode_t mode = CUFRAMES_MODE_NEWEST_ONLY;
int32_t cuda_device = 0;
int32_t connect_timeout_ms = 5000;
};
class Subscriber {
public:
explicit Subscriber(const SubscriberOptions &opt);
~Subscriber();
Subscriber(Subscriber &&) noexcept;
Subscriber &operator=(Subscriber &&) noexcept;
std::optional<FrameRef> next(void *stream, int32_t timeout_ms = -1);
cuframes_subscriber_t *raw() noexcept;
};
```
`next` возвращает `std::nullopt` для recoverable conditions (`TIMEOUT`, `WOULD_BLOCK`, `DISCONNECTED`) и бросает `Error` для всего остального. Эта асимметрия сделана сознательно — три перечисленных случая ожидаемы в обычном loop'е и не должны платить за exception unwind.
```cpp
cuframes::Subscriber sub({
.key = "cam1",
.mode = CUFRAMES_MODE_NEWEST_ONLY,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
while (auto frame = sub.next(stream, 1000)) {
// frame->cuda_ptr(), frame->width(), frame->pts_ns()
// release будет автоматически при выходе из scope
}
```
## Subscriber (async)
```cpp
class AsyncSubscriber {
public:
using OnFrame = std::function<void(const Frame &)>;
using OnError = std::function<void(int err, const std::string &msg)>;
AsyncSubscriber(const SubscriberOptions &opt,
OnFrame on_frame,
OnError on_error = {});
~AsyncSubscriber();
};
```
Callback-based wrapper. Library поднимает internal thread; `Frame` валиден **только** в течение callback'а (автоматический release после return).
```cpp
cuframes::AsyncSubscriber sub(
{.key = "cam1"},
[](const cuframes::Frame &f) {
std::printf("seq=%lu pts=%ld\n", f.seq(), f.pts_ns());
},
[](int err, const std::string &msg) {
std::fprintf(stderr, "cuframes error %d: %s\n", err, msg.c_str());
});
// держим subscriber alive...
std::this_thread::sleep_for(std::chrono::seconds(60));
// destructor join'ит worker thread
```
Не copyable. Move в текущей версии тоже запрещён (поля `std::function` хранят `this`-pointer для trampoline'а — move сломает).
## Packet ring
В C++ wrapper'е packet ring доступен через прямые C-функции и `Publisher::publish_packet`. Отдельных `cuframes::Packet` / `cuframes::PacketRef` классов нет — packet API проще, и FFmpeg interop часто пишется напрямую через C.
```cpp
// Publisher-side
cuframes_packet_ring_options_t pkt_opts{};
pkt_opts.ring_slots = 64;
pkt_opts.data_size = 8 * 1024 * 1024;
pkt_opts.max_packet_size = 2 * 1024 * 1024;
pkt_opts.codec_id = AV_CODEC_ID_H264;
pub.enable_packets(&pkt_opts);
pub.set_codec_extradata(sps_pps.data(), sps_pps.size());
int rc = pub.publish_packet(nal, nal_size, pts, dts, CUFRAMES_PKT_FLAG_KEY);
if (rc < 0 && rc != CUFRAMES_ERR_PACKET_OVERSIZED) {
// log + skip; OVERSIZED безопасно игнорировать
}
```
Subscriber-side — pure C-функции из `<cuframes/cuframes.h>`, см. [Packet subscriber API](/docs/reference/api-c#subscriber-side).
## Утилиты
```cpp
inline int64_t cuframes::now_ns();
inline size_t cuframes::calc_frame_size(cuframes_format_t format,
int32_t w, int32_t h,
int32_t *pitch_y = nullptr,
int32_t *pitch_uv = nullptr);
```
`calc_frame_size` бросает `Error` на unknown format (в отличие от C-варианта, который возвращает код).
## Примеры
### Complete publisher (LIBRARY mode)
```cpp
#include <cuframes/cuframes.hpp>
#include <cuda_runtime.h>
int main() {
cuframes::Publisher pub({
.key = "cam1",
.width = 1920, .height = 1080,
.format = CUFRAMES_FORMAT_NV12,
.ring_size = 4,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (int i = 0; i < 1000; i++) {
void *slot = pub.acquire();
// ... NVDEC decode или kernel write в slot ...
pub.publish(stream, cuframes::now_ns());
}
}
```
### Complete subscriber
```cpp
#include <cuframes/cuframes.hpp>
#include <cuda_runtime.h>
int main() {
cuframes::Subscriber sub({
.key = "cam1",
.consumer_name = "my-detector",
.mode = CUFRAMES_MODE_NEWEST_ONLY,
.connect_timeout_ms = 5000,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (;;) {
auto frame = sub.next(stream, 1000);
if (!frame) continue; // timeout/disconnect
// frame->cuda_ptr() — VRAM pointer
// frame->width(), frame->height() — pixels
// frame->pitch_y(), frame->pitch_uv() — байт на строку
// ... ML inference / CUDA filter на stream ...
}
}
```
### Async with lambdas
```cpp
#include <cuframes/cuframes.hpp>
#include <atomic>
#include <thread>
int main() {
std::atomic<uint64_t> frames{0};
cuframes::AsyncSubscriber sub(
{.key = "cam1"},
[&](const cuframes::Frame &f) {
frames.fetch_add(1);
// pre-sync уже выполнен library-side; f.cuda_ptr() ready
});
std::this_thread::sleep_for(std::chrono::seconds(10));
std::printf("processed %lu frames\n", frames.load());
}
```
## См. также
- [C API](/docs/reference/api-c) — underlying C-функции.
- [Protocol reference](/docs/reference/protocol) — wire format spec.
- [First publisher](/docs/getting-started/first-publisher) — minimal end-to-end пример.
@@ -0,0 +1,608 @@
---
sidebar_position: 3
title: Спецификация wire protocol v4
---
# cuframes wire protocol — version 4
**Status:** v4 — production (deployed 2026-05-25). Semver-stable в пределах major.
**Endianness:** little-endian (CUDA-platforms only).
**libcuframes version:** 0.4.0.
Эта страница описывает **byte-exact** формат всех структур и сообщений cuframes. Любая реализация (C, Python ctypes, Rust bindings, FFmpeg plugin) должна соответствовать этому документу. Reference implementation — `libcuframes/src/` в репозитории.
## Что изменилось в v4
В v0.4 заменили механизм sharing'а GPU-памяти с CUDA IPC mem-handles на CUDA VMM + POSIX file descriptors. Это **breaking change** на wire level.
| Аспект | v1v3 (legacy) | v4 (current) |
|---|---|---|
| Magic | `0xCC7C1DCC` | `0xCC7C1DCE` |
| Protocol version | 13 | 4 |
| GPU memory share | `cudaIpcMemHandle_t` в slot descriptor | `cuMemCreate(POSIX_FILE_DESCRIPTOR)` + `SCM_RIGHTS` |
| Cross-process sync | `cudaIpcEventHandle_t` + `cudaStreamWaitEvent` | producer's `cuStreamSynchronize` + atomic seq release |
| PID namespace sharing | required (ограничение CUDA IPC) | **not required** — POSIX FD работает поверх SCM_RIGHTS |
| Handshake messages | `HELLO``SUBSCRIBE` | `HELLO``SUBSCRIBE``VMM_FDS` (new) |
| EXTERNAL ownership | поддерживался | удалён (VMM требует cuMemCreate-memory) |
Старый magic `0xCC7C1DCC` приводит к clean fail в v4 (`HELLO_RESP(result=CUFRAMES_ERR_PROTOCOL)`). Mixed-version deployment **не работает** — обновлять надо publisher и всех subscribers одновременно.
См. [Synchronization & VMM stream](/docs/concepts/sync-vmm-stream) — почему именно stream sync, а не CUDA events.
## 1. Ресурсы / Lifecycle
Один publisher создаёт следующие kernel-level ресурсы:
| Ресурс | Path | Назначение | Cleanup |
|---|---|---|---|
| Unix socket | `/run/cuframes/<key>.sock` | Handshake + control plane | unlink при `destroy()`; orphaned после crash — cleanup'ится при следующем `create` через `O_EXCL` retry |
| Frame SHM | `/dev/shm/cuframes-<key>` | Frame ring header + slot descriptors | `shm_unlink` при `destroy()`; orphaned автоматически, если nobody mmap'ит |
| VMM-allocated VRAM | (no path; FD получают subscribers через `SCM_RIGHTS`) | Frame data в HBM | освобождается, когда last subscriber `cuMemUnmap` + publisher `cuMemRelease` |
| Packet SHM | `/dev/shm/cuframes-<key>-packets` | Packet ring header + slots + data section | `shm_unlink` при `destroy()`; opt-in (только если вызван `enable_packets`) |
`<key>` — ASCII, `[a-zA-Z0-9_-]`, 163 байт. Library валидирует regex `^[a-zA-Z0-9_-]{1,63}$`.
### Normal shutdown
1. `cuframes_publisher_destroy()` вызван.
2. Publisher шлёт всем connected subscribers `SHUTDOWN` через socket.
3. Subscribers закрывают VMM mappings (`cuMemUnmap`, `cuMemAddressFree`, `close(fd)`), socket, munmap, возвращают `DISCONNECTED`.
4. Publisher: `cuMemUnmap` own pool + `cuMemRelease`, close socket, unlink socket + shm.
### Abnormal shutdown (publisher crash)
Producer не успевает unlink. Stale socket и shm остаются. Recovery при следующем `cuframes_publisher_create()`:
1. Попытка `connect(sock_path)``ECONNREFUSED` → stale, unlink.
2. Открытие `/dev/shm/cuframes-<key>`: если magic совпадает, проверка liveness через `pidfd_open(producer_pid)` или `kill(pid, 0)`.
3. Если producer dead → `shm_unlink` + продолжаем create.
4. Если producer жив → return `ALREADY_EXISTS`.
Subscribers детектят publisher crash через socket EOF (recv returns 0) или `SIGPIPE` на send. После этого они должны `cuMemUnmap` все slot mappings и вернуть `DISCONNECTED` в `cuframes_subscriber_next`.
**Note про VMM leak risk.** В отличие от CUDA IPC handles, POSIX FDs автоматически очищаются ядром при `close()` процесса. Даже если subscriber падает unclean'но — kernel сам close'нет FD, и `cuMemUnmap` отработает в driver'е при последующем `cuMemRelease` publisher'а. В v0.4 это менее опасно, чем было в legacy v1v3.
## 2. Frame SHM layout
`/dev/shm/cuframes-<key>` имеет фиксированный размер: `sizeof(cuframes_shared_header_t)`. Без variable-length секций.
### 2.0 Header byte layout
```
Offset Size Field Comments
─────── ────── ──────────────────────── ────────────────────────────────────────
0x0000 4 magic (LE u32) 0xCC7C1DCE
0x0004 4 proto_version (LE u32) 4
0x0008 4 lib_version_major
0x000C 4 lib_version_minor
0x0010 4 lib_version_patch
0x0014 4 reserved_a 0
0x0018 8 producer_pid (LE u64) для liveness check
0x0020 8 ring_size (LE u64) 1..16
0x0028 8 ownership_mode 0 (LIBRARY only в v4)
0x0030 8 policy 0=DROP_OLDEST, 1=STRICT_WAIT
0x0038 8 max_subscribers 32 (захардкожено)
0x0040 64 meta frame meta packed (см. §2.1)
0x0080 64 reserved_events 0 (был ipc_event_handle в v1v3)
0x00C0 8 global_seq (LE u64) atomic, монотонная
0x00C8 8 subscriber_bitmap atomic, bit per subscriber slot
0x00D0 8 shutdown_flag atomic, 0=normal, 1=shutting down
0x00D8 40 reserved_b 0
0x0100 N×D slots[ring_size] slot descriptor, см. §2.2
0x0100+ND M×S subscribers[32] subscriber slot, см. §2.3
```
Constants:
- N = `ring_size`, до 16
- D = `sizeof(slot_descriptor)` = **192 байт** (§2.2)
- M = 32 (max subscribers)
- S = `sizeof(subscriber_slot)` = 128 байт (§2.3)
- Max SHM size: `0x0100 + 16×192 + 32×128` ≈ 7.3 KB
Все atomic-помеченные поля доступны через C11 `_Atomic` (или `__atomic_*`).
### 2.1 Frame meta (64 байта)
```
Offset Size Field Comments
0x00 4 format (LE u32) cuframes_format_t enum
0x04 4 width
0x08 4 height
0x0C 4 pitch_y байт на строку Y / single plane
0x10 4 pitch_uv байт на UV (0 если no chroma plane)
0x14 4 bits_per_pixel информативно
0x18 8 frame_size_bytes полный размер (Y+UV или packed)
0x20 32 reserved 0
```
### 2.2 Slot descriptor (192 байта)
```
Offset Size Field Comments
0x00 8 seq (LE u64, atomic) published seq; UINT64_MAX = invalid
0x08 8 pts_ns (LE i64, atomic)
0x10 8 ack_bitmap (LE u64, atomic) bit i = subscriber #i ACK'нул
0x18 8 written_bytes для diagnostics (может быть 0)
0x20 64 reserved_mem_handle 0 в v4 (был cudaIpcMemHandle_t в v1v3)
0x60 32 reserved_external 0 в v4 (был cuda_ptr_external)
0x80 16 reserved_a
0x90 48 reserved_b
0xC0 END
```
В v4 поле `mem_handle` (offset 0x20, 64 байта) **больше не используется** — вместо IPC handle subscribers получают POSIX FD через `SCM_RIGHTS` во время handshake (см. §3). Поле зарезервировано для возможного re-use в будущих версиях. Reader v4 должен игнорировать его содержимое.
Slot — статичный в плане memory layout (создаётся в publisher_create), но `seq` / `pts_ns` / `ack_bitmap` / `written_bytes` обновляются атомарно на каждом publish.
### 2.3 Subscriber slot (128 байт)
```
Offset Size Field Comments
0x00 8 state (LE u64, atomic) 0=free, 1=connecting, 2=active, 3=draining
0x08 8 consumer_pid (LE u64) liveness check
0x10 8 last_seen_seq (LE u64) monotonic — последний ACK'нутый seq
0x18 8 last_ack_ns (LE i64) wall-clock последнего ACK
0x20 32 consumer_name ASCII zero-terminated, max 31 char
0x40 64 reserved 0
```
Bit-position **0** зарезервирован — sentinel. Используются bits 1..31 → max 31 subscriber'ов (errcode `TOO_MANY` при попытке 32-го).
## 3. Unix socket protocol
Publisher `listen()`'ит SOCK_STREAM по `/run/cuframes/<key>.sock`. Subscriber `connect()`'ится.
Socket используется для:
- Handshake (`HELLO`, `SUBSCRIBE`)
- **VMM file descriptor delivery** (`VMM_FDS`, новое в v4) через `sendmsg(SCM_RIGHTS)`
- Lifetime signals (`SHUTDOWN`, force-disconnect, `UNSUBSCRIBE`)
Socket **не** используется для frame transfer — это VMM-mapped shared memory + atomic `global_seq`.
### 3.1 Framing
Каждое сообщение — TLV (type-length-value):
```
[4 bytes] message_type (LE u32, см. §3.2)
[4 bytes] payload_length (LE u32, bytes excl. these 8 header bytes)
[N bytes] payload (длина = payload_length)
```
Для `VMM_FDS` сопровождается ancillary data через `sendmsg` (см. §3.7).
### 3.2 Message types
| ID | Name | Direction | Когда |
|---|---|---|---|
| `0x01` | `HELLO_REQ` | C→P | Первое сообщение от consumer |
| `0x02` | `HELLO_RESP` | P→C | Ответ publisher'а |
| `0x03` | `SUBSCRIBE_REQ` | C→P | Завершение handshake, выделить subscriber slot |
| `0x04` | `SUBSCRIBE_RESP` | P→C | Подтверждение + assigned bit |
| `0x05` | `VMM_FDS` | P→C | **(new в v4)** Передача N file descriptors через SCM_RIGHTS |
| `0x10` | `UNSUBSCRIBE` | C→P | Graceful disconnect |
| `0x30` | `SHUTDOWN` | P→C | Publisher shutting down |
| `0xF0` | `PING` | both | Liveness check |
| `0xF1` | `PONG` | both | Reply to PING |
| `0xFE` | `ERROR` | both | Error notification (см. §3.9) |
`0x20` (`EVENT_FD` в legacy v1v3) — **deprecated в v4**. Wakeup сейчас делается через atomic polling `global_seq`; reintroduce через FD wakeup channel — возможная фича v0.5.
### 3.3 HELLO_REQ payload (consumer → publisher)
```
[4 bytes] proto_version (LE u32) consumer's wire version (must be 4)
[4 bytes] consumer_name_len (LE u32)
[N bytes] consumer_name (UTF-8, без null-terminator)
[4 bytes] cuda_device (LE i32)
[4 bytes] mode (LE u32, cuframes_subscriber_mode_t)
[12 bytes] reserved (must be 0)
```
### 3.4 HELLO_RESP payload (publisher → consumer)
```
[4 bytes] result (LE i32) 0 = success, negative = cuframes_error_t
[4 bytes] proto_version_actual (LE u32) publisher's wire version
[4 bytes] ring_size (LE u32)
[4 bytes] ownership_mode (LE u32) 0 (LIBRARY)
[64 bytes] frame_meta см. §2.1
[4 bytes] shm_path_len (LE u32)
[N bytes] shm_path (UTF-8, ASCII subset)
[12 bytes] reserved
```
При `result < 0` payload может быть короче (только `result` + `reserved`).
### 3.5 SUBSCRIBE_REQ payload
```
[4 bytes] proto_version (повтор, для double-check)
[12 bytes] reserved
```
### 3.6 SUBSCRIBE_RESP payload
```
[4 bytes] result (LE i32)
[4 bytes] assigned_bit (LE u32) 1..31
[8 bytes] initial_seq (LE u64) текущий global_seq на момент handshake
[12 bytes] reserved
```
После SUBSCRIBE_RESP publisher сразу отправляет `VMM_FDS` (§3.7) — это часть handshake'а, subscriber должен ожидать его перед переходом в READY.
### 3.7 VMM_FDS — file descriptor delivery (v4)
Publisher отправляет `ring_size` file descriptors через `sendmsg()` с ancillary data (`SCM_RIGHTS`).
**TLV payload (без ancillary):**
```
[4 bytes] fd_count (LE u32) должно = ring_size
[8 bytes] frame_size_bytes (LE u64) size одного VMM mapping
[8 bytes] vmm_granularity (LE u64) округление от CUDA driver (обычно 2 MiB)
[12 bytes] reserved (must be 0)
```
**Ancillary data:** `cmsg` уровня `SOL_SOCKET`, type `SCM_RIGHTS`, contains `fd_count × sizeof(int)` file descriptors. Kernel дублирует FDs в consumer process автоматически.
Consumer обязан:
1. `recvmsg` с buffer'ом для cmsg достаточного размера (рекомендуется `CMSG_SPACE(sizeof(int) * 16)`);
2. Для каждого FD: `cuMemImportFromShareableHandle(&handle, fd, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 0)`;
3. `cuMemAddressReserve``cuMemMap``cuMemSetAccess` (READ_WRITE на consumer device);
4. `close(fd)` (CUDA driver держит свою reference после import).
После успешного импорта всех FDs subscriber переходит в READY state.
### 3.8 SHUTDOWN payload
```
[4 bytes] reason (LE u32) 0 = normal, 1 = error, 2 = upgrade
[12 bytes] reserved
```
Subscriber на SHUTDOWN → `cuMemUnmap`/`cuMemAddressFree` на всех slots → возвращает `DISCONNECTED` user'у на next call.
### 3.9 ERROR payload
```
[4 bytes] error_code (LE i32) cuframes_error_t
[4 bytes] message_len (LE u32)
[N bytes] message (UTF-8)
[12 bytes] reserved
```
## 4. Handshake sequence
```mermaid
sequenceDiagram
autonumber
participant C as Subscriber (consumer)
participant P as Publisher
C->>P: socket connect /run/cuframes/cam1.sock
C->>P: HELLO_REQ (proto_version=4, consumer_name, cuda_device, mode)
alt proto mismatch / format unsupported
P-->>C: HELLO_RESP (result=ERR_PROTOCOL or ERR_FORMAT)
Note over C,P: socket close, subscriber returns error
else handshake ok
P-->>C: HELLO_RESP (result=0, ring_size, frame_meta, shm_path)
C->>P: SUBSCRIBE_REQ (proto_version=4)
alt subscriber slot pool full
P-->>C: SUBSCRIBE_RESP (result=ERR_TOO_MANY)
else slot allocated
P-->>C: SUBSCRIBE_RESP (result=0, assigned_bit, initial_seq)
P-->>C: VMM_FDS (fd_count, frame_size, granularity) + SCM_RIGHTS [fd0..fdN]
Note over C: cuMemImportFromShareableHandle for each fd<br/>cuMemAddressReserve + Map + SetAccess
Note over C,P: READY — subscriber may poll global_seq
end
end
```
Sequence для legacy v1v3 не имеет шага `VMM_FDS` — там slot descriptor в SHM сам нёс `cudaIpcMemHandle_t`, и subscriber делал `cudaIpcOpenMemHandle` без socket interaction. В v4 этот шаг **обязателен** — без `VMM_FDS` subscriber не получит pointer'ы и не сможет читать frames.
## 5. Sync model (v4)
В v0.4 cross-process synchronization упрощена до stream sync на стороне producer'а:
```c
// Publisher side (libcuframes/src/producer.c)
cuStreamSynchronize(stream); // ensure all writes coherent
atomic_store_release(&slot.seq, next_seq);
atomic_store_release(&hdr.global_seq, next_seq);
// Consumer side (libcuframes/src/consumer.c)
uint64_t cur = atomic_load_acquire(&hdr.global_seq);
if (cur > my_last) {
// read slot metadata, do cudaMemcpyAsync(DtoD, consumer_stream)
// HW coherence на одном GPU — no event wait нужен
}
```
Почему так:
- VMM-shared memory на одном GPU — single physical HBM region. Producer's `cuStreamSynchronize` гарантирует, что writes завершились в HBM. Consumer reads после atomic-release-acquire pair видят валидные данные без CUDA event roundtrip.
- Это **не работает cross-GPU** — для multi-GPU нужен `cuMemSetAccess` на оба device'а + явные events. v4 не поддерживает cross-device VMM share (ограничение CUDA driver).
- Producer overhead `cuStreamSynchronize` ≈ 1 мс на frame при 25 fps — измеримо, но приемлемо.
Подробнее: [Synchronization & VMM stream](/docs/concepts/sync-vmm-stream).
## 6. ACK protocol
При публикации slot N publisher:
1. Записывает frame data в acquire'нутый slot;
2. `cuStreamSynchronize(stream)` — coherence barrier;
3. Atomic: `slot[N].seq = next_seq`, `slot[N].pts_ns = now`, `slot[N].ack_bitmap = 0`;
4. Atomic RELEASE: `global_seq = next_seq`.
Subscriber:
1. ACQUIRE load `global_seq`. Если новое — process;
2. Process frame (CUDA kernel, DtoD copy, etc.) на consumer_stream;
3. ACK: `atomic_fetch_or(&slot.ack_bitmap, 1ULL << my_bit, RELEASE)`;
4. Atomic store `subscriber_slot.last_seen_seq = seq`, `last_ack_ns = now`.
Publisher (STRICT_WAIT mode) перед next publish ждёт:
- `ack_bitmap == subscriber_bitmap` (все active subscribers ACK'нули), или
- timeout `consumer_ack_timeout_ms` истёк → mark dead subscriber, clear его bit.
Publisher (DROP_OLDEST mode) — не ждёт ACK, просто перезаписывает slot.
## 7. Versioning rules
### 7.1 Wire protocol version
`proto_version` — single integer. Текущий — `4`. Breaking changes → bump.
Handshake:
- Если `subscriber.proto_version != publisher.proto_version``HELLO_RESP(result=ERR_PROTOCOL)`. **В v4 нет backward-compat layer'а** с v1v3 — magic другой, mem-handle scheme другая.
### 7.2 Library version (semver)
`lib_version_major.minor.patch` — informational, не используется для compat-decisions. Передаётся в SHM header для diagnostics.
### 7.3 Reserved fields
Все `reserved_*` — должны быть `0` при write, reader игнорирует. Это позволяет в minor-релизах добавлять fields в reserved space без breaking ABI.
## 8. Conformance fixture
Test skeleton (Phase 1, в `tests/conformance/`):
```c
TEST(ProtocolLayout, ShmHeaderMagic) {
EXPECT_EQ(CUFRAMES_PROTOCOL_MAGIC_V4, 0xCC7C1DCE);
EXPECT_EQ(cuframes_protocol_version(), 4);
}
TEST(ProtocolLayout, ShmHeaderOffsets) {
EXPECT_EQ(offsetof(cuframes_shared_header_t, magic), 0x0000);
EXPECT_EQ(offsetof(cuframes_shared_header_t, proto_version), 0x0004);
EXPECT_EQ(offsetof(cuframes_shared_header_t, meta), 0x0040);
EXPECT_EQ(offsetof(cuframes_shared_header_t, global_seq), 0x00C0);
}
TEST(ProtocolLayout, SlotDescriptorSize) {
EXPECT_EQ(sizeof(cuframes_slot_descriptor_t), 192);
}
TEST(ProtocolLayout, SubscriberSlotSize) {
EXPECT_EQ(sizeof(cuframes_subscriber_slot_t), 128);
}
TEST(Handshake, VmmFdsDelivered) {
// Setup publisher with ring_size=4
// Connect subscriber, complete HELLO+SUBSCRIBE
// Expect: VMM_FDS message with fd_count=4 + 4 valid FDs in SCM_RIGHTS
}
```
## 9. Reference implementation
`libcuframes/src/` в repo:
- `producer.c` — VMM allocation, FD send, atomic publish
- `consumer.c` — FD receive, VMM import, atomic read
- `protocol.c` — TLV framing, `send_msg_with_fds` / `recv_msg_with_fds` helpers
Любая другая реализация (Python ctypes, Rust bindings, FFmpeg plugin) должна быть conformance-tested против этого документа.
## 10. Packet ring (proto_version остаётся 4)
Packet ring — отдельный SHM `/dev/shm/cuframes-<key>-packets` с собственным magic `0xCC7C1DCD`. **Не использует CUDA** (encoded data на CPU, без GPU sync). VMM-изменения v0.4 packet ring **не затронули** — wire format и layout совпадают с v0.2 спецификацией.
### 10.1 Совместимость с frames ring
- Subscriber может запросить только frames, только packets, или оба (см. §10.9 — subscribe flags).
- Packet ring опционален — если publisher не вызвал `enable_packets`, subscriber'ам `enable_packets` вернёт `NOT_FOUND`.
### 10.2 Packet SHM layout
Размер: `sizeof(packet_ring_header_t) + N×PSE + DATA_SIZE`, где:
- N = `ring_slots`, default 64 (1..1024)
- PSE = `sizeof(packet_slot_entry_t)` = 64 байт (§10.5)
- DATA_SIZE = `data_size`, default 8 MB
#### Header byte layout
```
Offset Size Field Comments
─────────────────────── ────── ────────────────────────── ────────────────────────────
0x0000 4 magic (LE u32) 0xCC7C1DCD (frames magic + (1))
0x0004 4 proto_version (LE u32) 4 (совпадает с frames в v0.4)
0x0008 4 ring_slots (LE u32) N (1..1024)
0x000C 4 data_size (LE u32) bytes for packet data ring
0x0010 4 codec_id (LE u32) AV_CODEC_ID_* enum
0x0014 4 codec_extradata_size ≤ 4096
0x0018 8 producer_pid (LE u64)
0x0020 8 global_seq (LE u64, atomic) монотонная по packets
0x0028 8 last_keyframe_seq (atomic) для late subscribers
0x0030 8 write_offset (LE u64, atom) текущий cursor в data ring
0x0038 8 shutdown_flag (atomic)
0x0040 4096 codec_extradata SPS/PPS/VPS bytes
0x1040 N×64 slots[N] packet_slot_entry_t
0x1040+N×64 DATA_SIZE data[] wraparound byte buffer
```
### 10.3 Codec extradata
H.264 — SPS + PPS, конкатенированные в **Annex B** формате (start codes `00 00 00 01`). H.265 — VPS + SPS + PPS.
`codec_id` соответствует FFmpeg `AV_CODEC_ID_H264` (27), `AV_CODEC_ID_HEVC` (173), `AV_CODEC_ID_AV1`. Subscriber пишет extradata в `AVCodecContext.extradata` своего decoder'а или в `AVStream.codecpar->extradata` для muxer'ов.
Extradata устанавливается publisher'ом **один раз** при первом keyframe (или из RTSP SDP). После — fixed на lifetime publisher'а (codec change mid-stream → destroy + recreate с новым `<key>`).
### 10.4 Packet slot entry (64 байта)
```
Offset Size Field Comments
0x00 8 seq (LE u64, atomic) published seq; UINT64_MAX = invalid
0x08 8 pts_ns (LE i64)
0x10 8 dts_ns (LE i64) для B-frames
0x18 8 data_offset (LE u64) absolute byte cursor в data[]
0x20 4 data_size (LE u32) payload size
0x24 4 flags (LE u32) см. §10.5
0x28 24 reserved 0
```
`data_offset` — absolute byte cursor (может расти неограниченно), фактический byte index = `data_offset % data_size`. Subscriber'у возможно понадобится split read при wraparound.
### 10.5 Packet flags
| Бит | Name | Комментарий |
|---|---|---|
| 0 | `KEY` | keyframe (IDR for H.264, CRA/IDR for HEVC). **Critical** для late subscribers. |
| 1 | `CORRUPT` | publisher детектнул damaged packet (RTP loss) — subscriber может skip |
| 2 | `DISCONTINUITY` | был gap перед этим packet (publisher reconnect к камере) |
| 3 | `LAST_IN_AU` | last NAL в access unit — для muxer'ов, которые ждут полный frame |
| 431 | reserved | 0 |
Маппинг в `AVPacket.flags`:
- bit 0 → `AV_PKT_FLAG_KEY`
- bit 1 → `AV_PKT_FLAG_CORRUPT`
- bit 2 → `AV_PKT_FLAG_DISCONTINUITY` (FFmpeg 5+)
### 10.6 Atomic publish (pseudo-C)
```c
uint64_t seq = atomic_load(&hdr->global_seq, RELAXED) + 1;
uint64_t off = atomic_load(&hdr->write_offset, RELAXED);
size_t slot_idx = seq % hdr->ring_slots;
packet_slot_entry_t *slot = &slots[slot_idx];
// Wraparound write
size_t off_in_ring = off % hdr->data_size;
size_t first = min(size, hdr->data_size - off_in_ring);
memcpy(data + off_in_ring, payload, first);
if (first < size)
memcpy(data, payload + first, size - first);
slot->pts_ns = pts;
slot->dts_ns = dts;
slot->data_offset = off;
slot->data_size = size;
slot->flags = flags;
atomic_store(&slot->seq, seq, RELEASE);
atomic_store(&hdr->write_offset, off + size, RELEASE);
atomic_store(&hdr->global_seq, seq, RELEASE);
if (flags & PKT_FLAG_KEY)
atomic_store(&hdr->last_keyframe_seq, seq, RELEASE);
```
### 10.7 Atomic read (pseudo-C)
```c
uint64_t cur = atomic_load(&hdr->global_seq, ACQUIRE);
if (cur <= my_last_seq) return TIMEOUT;
uint64_t want_seq = my_last_seq + 1;
size_t slot_idx = want_seq % hdr->ring_slots;
packet_slot_entry_t *slot = &slots[slot_idx];
uint64_t slot_seq = atomic_load(&slot->seq, ACQUIRE);
if (slot_seq != want_seq) {
// Overrun — re-anchor on last keyframe
want_seq = atomic_load(&hdr->last_keyframe_seq, ACQUIRE);
return PACKET_OVERRUN;
}
// Copy payload (wraparound aware)
uint64_t off = slot->data_offset % hdr->data_size;
uint32_t size = slot->data_size;
uint32_t first = min(size, hdr->data_size - off);
memcpy(out, data + off, first);
if (first < size)
memcpy(out + first, data, size - first);
// Re-check (seqlock variant) — защита от overrun mid-read
if (atomic_load(&slot->seq, ACQUIRE) != want_seq)
return PACKET_OVERRUN;
my_last_seq = want_seq;
return OK;
```
### 10.8 Late subscriber → keyframe-aligned start
При `cuframes_subscriber_enable_packets()` subscriber устанавливает `my_last_seq = last_keyframe_seq - 1` (читая `last_keyframe_seq` из header'а). Первый `next_packet` вернёт keyframe (decoder может start без glitches).
**Risk:** если в момент enable_packets `last_keyframe_seq` уже выехал из ring (slow start subscriber, GOP > ring_slots packets) — subscriber детектит overrun в первом read, и library переходит на следующий keyframe.
Sizing guide: `packet_ring_slots × avg_packet_size > GOP_size_in_bytes` для нормальной работы.
### 10.9 Socket protocol extensions для packets
#### HELLO_REQ — subscribe flags в reserved field
v4 интерпретирует первые 4 байта `reserved` (offset HELLO_REQ + 28) как `subscribe_flags`:
| Бит | Name | Комментарий |
|---|---|---|
| 0 | `WANTS_FRAMES` | подписаться на frames ring (default — implicit) |
| 1 | `WANTS_PACKETS` | подписаться на packet ring |
| 231 | reserved | 0 |
Если subscriber оставляет flags=0 — publisher интерпретирует как `WANTS_FRAMES=true, WANTS_PACKETS=false`.
#### HELLO_RESP — packet-ring fields в reserved секции
```
[4 bytes] packet_shm_path_len (LE u32) 0 = packets disabled at publisher
[N bytes] packet_shm_path (UTF-8) e.g. "cuframes-cam1-packets" (relative to /dev/shm/)
[4 bytes] codec_id (LE u32) AV_CODEC_ID_*
[8 bytes] initial_packet_seq (LE u64) last_keyframe_seq на момент handshake
```
Если subscriber запросил `WANTS_PACKETS=1`, но publisher не имеет packet ring → `result = ERR_NOT_FOUND`.
## 11. Open для v0.5+
Эти решения **не** должны нарушить v4-совместимость:
- **FD wakeup channel** — возврат `EVENT_FD` message + eventfd для replace polling (latency win);
- **Multi-codec в одном publisher** — отдельный slot для thumbnail meta;
- **AMD/ROCm HIP IPC** — заменит cuMemCreate на rocSharedMemoryCreate;
- **Cross-host** через RDMA — отдельный transport, новый proto_version.
Любое из этих → bump `proto_version` в v5, отдельный документ.
## См. также
- [C API reference](/docs/reference/api-c) — обёртка над protocol.
- [Synchronization & VMM stream](/docs/concepts/sync-vmm-stream) — почему `cuStreamSynchronize`.
- [Frame ring vs Packet ring](/docs/concepts/frame-vs-packet-ring) — когда нужно использовать какой канал.
- [Install](/docs/getting-started/install) — runtime-требования.
+81
View File
@@ -0,0 +1,81 @@
---
title: cuframes — zero-copy GPU video frames across processes
hide_table_of_contents: true
---
import Link from '@docusaurus/Link';
# cuframes
**Zero-copy decoded video frames over CUDA, shared across processes — without pid namespace sharing.**
A small (~140 KB shared object) C library for Linux. Producer decodes once on the GPU; any number of consumer processes get the same frames via POSIX file descriptors and CUDA VMM. No re-encode, no host-side memcpy, no Unix-pipe serialization.
Built for video pipelines where the same decoded stream feeds several specialized processes (NVR record + AI inference + GPU compositor) and you don't want to run N decoders.
<div style={{display: 'flex', gap: '12px', marginTop: '24px', flexWrap: 'wrap'}}>
<Link className="button button--primary button--lg" to="/docs/intro">What is cuframes →</Link>
<Link className="button button--secondary button--lg" to="/docs/getting-started/install">Install</Link>
<Link className="button button--secondary button--lg" to="https://git.goldix.org/gx/cuframes">Source</Link>
</div>
## Quick example
```c
// Publisher: 30 lines to share decoded NV12 frames
cuframes_publisher_config_t cfg = {
.key = "cam1",
.width = 1920, .height = 1080, .format = CUFRAMES_FORMAT_NV12,
.ownership = CUFRAMES_OWNERSHIP_LIBRARY,
.ring_size = 4,
};
cuframes_publisher_t *pub;
cuframes_publisher_create(&cfg, &pub);
while (alive) {
void *cuda_ptr;
cuframes_publisher_acquire(pub, &cuda_ptr);
decode_into(cuda_ptr, stream); // your decoder
cuframes_publisher_publish(pub, stream, cuframes_now_ns());
}
```
```c
// Subscriber: just connect by key, frames arrive
cuframes_subscriber_config_t cfg = {.key = "cam1", .consumer_name = "ai"};
cuframes_subscriber_t *sub;
cuframes_subscriber_create(&cfg, &sub);
cuframes_frame_t *f;
cuframes_subscriber_next(sub, my_stream, &f, /*timeout_ms*/ 1000);
void *cuda_ptr = cuframes_frame_cuda_ptr(f); // same GPU memory as publisher
process_with_cuda(cuda_ptr, my_stream);
cuframes_subscriber_release(sub, f);
```
## Why
| | Without cuframes | With cuframes |
| --------------------------------- | ------------------------------------- | -------------------------- |
| 4 cameras × 3 consumers | 12 RTSP streams, 12 NVDEC decodes | 4 RTSP, 4 NVDEC |
| Cross-process shared GPU memory | CUDA IPC handles (needs PID share) | POSIX FD via `SCM_RIGHTS` |
| Frigate / s6-overlay containers | Broken — can't share PID | Works |
| Footprint | DeepStream multi-GB runtime | 140 KB `.so` + headers |
## Status
**v0.4 — early but production-tested.** Single deployment (4 IP cameras + NVR + grid compositor + AI detector) has been running on the maintainer's home server. Not enterprise-ready: one maintainer, no SLA, ABI broke four times during initial development.
If you're building your own pipeline and can read C — try it. If you need vendor support and an enterprise contract — use DeepStream.
[FAQ → Is cuframes production-ready?](/docs/faq#is-cuframes-production-ready)
## Documentation
- **[Getting started](/docs/getting-started/install)** — install, first publisher, first subscriber (10 minutes)
- **[Concepts](/docs/concepts/frame-vs-packet-ring)** — frame vs packet ring, ownership modes, the v0.4 stream-sync model
- **[Integration](/docs/integration/ffmpeg-demuxer)** — FFmpeg `cuframes://` demuxer, `vf_cuda_grid` filter
- **[Reference](/docs/reference/api-c)** — full C / C++ API, wire protocol spec
- **[FAQ](/docs/faq)** — comparisons, license, common questions
LGPL-2.1+. [Source on Gitea](https://git.goldix.org/gx/cuframes).