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

25 KiB
Raw Blame History

sidebar_position, title
sidebar_position title
1 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

#include <cuframes/cuframes.h>
# 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

  • Все функции возвращают int0 (CUFRAMES_OK) при успехе, отрицательный код из cuframes_error_t при ошибке. Расшифровка кода — cuframes_strerror.
  • Все 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

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 константы:

#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.

Error codes

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

const char *cuframes_strerror(int err);

Возвращает human-readable строку для error code. Pointer указывает на static storage, дальше владеть им не надо. Никогда не возвращает NULL — для unknown code вернёт "unknown error".

Pixel formats

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

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

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

typedef enum cuframes_ownership_mode {
    CUFRAMES_OWNERSHIP_LIBRARY  = 0,
    CUFRAMES_OWNERSHIP_EXTERNAL = 1,
} cuframes_ownership_mode_t;
  • LIBRARY — library владеет VMM pool'ом (см. Sync model). 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.

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

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. Фиксирован.
ownership В v0.4 — только LIBRARY.
ring_size 2..16 для LIBRARY. Меньше — больше chance overrun, больше — больше VRAM.
policy См. Policy.
consumer_ack_timeout_ms Только для STRICT_WAIT. 0 = ждать бесконечно.
cuda_device Обычно 0. Должен совпадать с consumer'ским.
_reserved Reserved для ABI-stability, должно быть нулями.

Create / destroy

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.

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)

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 — почему именно stream sync, а не CUDA events.

Param Meaning
stream CUDA stream на котором писались данные. 0 для default stream.
pts_ns Timestamp, рекомендуется cuframes_now_ns().

Publish (EXTERNAL mode)

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

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
cuda_device Должен совпадать с publisher'ским — VMM FD импортируется на тот же device
connect_timeout_ms 0 = fail сразу с NOT_FOUND; -1 = ждать вечно

Create / destroy

int cuframes_subscriber_create(const cuframes_subscriber_config_t *cfg,
                               cuframes_subscriber_t **out);

int cuframes_subscriber_destroy(cuframes_subscriber_t *sub);

create выполняет handshake (HELLOSUBSCRIBEVMM_FDS), импортирует N file descriptors через cuMemImportFromShareableHandle. См. Protocol reference §3.

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

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)

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 — когда нужно использовать packet ring.

Flags

#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

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

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.

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

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

int64_t cuframes_now_ns(void);

CLOCK_MONOTONIC в наносекундах. Используйте как pts_ns для real-time pipeline'ов:

cuframes_publisher_publish(pub, stream, cuframes_now_ns());

See also