Files
cuframes-docs/site/docs/reference/api-c.md
T
Claude Opus 7f45c36aa2 init
2026-05-26 23:23:25 +01:00

532 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
sidebar_position: 1
title: C API
---
# C API reference
Полный listing 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.
## Conventions
- Все функции возвращают `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-платформами).
## Version & error codes
### Library version
```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 version не подключатся — 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;
```
| Code | Name | Meaning |
|---|---|---|
| `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 died или сеть оборвалась |
| `-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 в библиотеке — repro и report'ить |
### Error decoding
```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);
```
| Function | Returns |
|---|---|
| `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ить, например detect 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;
```
| Field | Constraints |
|---|---|
| `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`. Меньше — больше chance 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).
Errors:
| Code | Когда |
|---|---|
| `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`.
Errors:
| Code | Когда |
|---|---|
| `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.
| Param | Meaning |
|---|---|
| `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** — see 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;
```
| Field | Constraints |
|---|---|
| `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).
Errors:
| Code | Когда |
|---|---|
| `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` допустимо).
| Param | Meaning |
|---|---|
| `consumer_stream` | CUDA stream consumer'а. `0` допустимо. |
| `frame_out` | Output handle. Освободить через `release`. |
| `timeout_ms` | `<0` = блокироваться, `0` = non-blocking (вернёт `WOULD_BLOCK`), `>0` = с timeout'ом |
Errors:
| Code | Когда |
|---|---|
| `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, который sit'ит на `next`, вызывает `on_frame` / `on_error`, сам делает `release` после возврата из callback.
Constraints:
- Frame **валиден только в течение callback'а** — никаких saved pointer'ов;
- Library использует internal CUDA stream, pre-wait уже выполнен — для своего stream'а используйте sync API;
- `destroy` joins 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'нуться.
Errors:
| Code | Когда |
|---|---|
| `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).
Errors:
| Code | Когда |
|---|---|
| `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 past subscriber lifetime — копируйте сами. Возвращает `NO_CODEC_PARAMS` если publisher ещё не звал `set_codec_extradata`.
`release_packet` — NULL-safe. После release pointer'ы от `cuframes_packet_*` invalid.
## Utils
### Frame size calculation
```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());
```
## See also
- [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-пример.