--- sidebar_position: 1 title: C API --- # C API reference Полный listing public C API из `` (libcuframes 0.4.0). Source of truth — header в repo, эта страница его дублирует в Docusaurus формате с cross-links на концептуальные разделы. ## Headers & linkage ```c #include ``` ```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/.sock`, mmap'ит `/dev/shm/cuframes-`. См. [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--`. 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--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-пример.