C ABI reference
core/include/xcaicx/xcaicx.h is the entire supported integration surface. Anything not in that header is internal and will move.
Current version: XCAICX_VERSION_MAJOR 1, MINOR 0, PATCH 0.
ABI contract#
- Structs are versioned by a leading
size_t struct_size. Callers set it tosizeofthe struct they compiled against; the library uses it to detect which fields are present. New fields are only ever appended. - Enum values are never renumbered. New values are appended.
- Functions are never removed within a major version.
Always call the matching *_init function before setting fields on a config struct — that is what stamps struct_size.
Threading#
xcaicx_engineis thread-safe for submit/poll from different threads.- A single
xcaicx_streammust be driven from one thread at a time.
Memory#
Any xcaicx_* pointer returned by the library is owned by the library and valid until the next call on the same handle, unless documented otherwise. Copy anything you need to retain.
Status codes#
typedef enum xcaicx_status {
XCAICX_OK = 0,
XCAICX_ERR_INVALID_ARG = -1,
XCAICX_ERR_OOM = -2,
XCAICX_ERR_NOT_INITIALIZED = -3,
XCAICX_ERR_INTERNAL = -4,
XCAICX_ERR_UNSUPPORTED = -5,
XCAICX_ERR_ABI_MISMATCH = -6,
/* Licensing — handle these explicitly in firmware. */
XCAICX_ERR_LICENSE_MISSING = -100,
XCAICX_ERR_LICENSE_MALFORMED = -101,
XCAICX_ERR_LICENSE_BAD_SIGNATURE = -102,
XCAICX_ERR_LICENSE_EXPIRED = -103,
XCAICX_ERR_LICENSE_NOT_YET_VALID = -104,
XCAICX_ERR_LICENSE_WRONG_DEVICE = -105,
XCAICX_ERR_LICENSE_REVOKED = -106,
XCAICX_ERR_LICENSE_CLOCK_ROLLBACK = -107,
XCAICX_ERR_ENTITLEMENT_DENIED = -108,
XCAICX_ERR_LIMIT_EXCEEDED = -109,
XCAICX_ERR_ACTIVATION_REQUIRED = -110,
/* Runtime */
XCAICX_ERR_MODEL_LOAD = -200,
XCAICX_ERR_BACKEND = -201,
XCAICX_ERR_FRAME_FORMAT = -202,
XCAICX_ERR_QUEUE_FULL = -203,
XCAICX_ERR_NO_RESULT = -204
} xcaicx_status;Causes and fixes for each: Status codes.
const char *xcaicx_status_str(xcaicx_status s);Human-readable name. Never NULL, statically allocated.
const char *xcaicx_last_error(void);Thread-local detail string for the last failing call on this thread. Empty string if there is no detail. Valid until the next XCAICX call on this thread. Written to be read by a field technician — surface it verbatim in diagnostics.
Modules#
typedef enum xcaicx_module {
XCAICX_MODULE_DETECT = 1 << 0, /* object + person detection, tracking */
XCAICX_MODULE_DEFECT = 1 << 1, /* anomaly / defect inspection */
XCAICX_MODULE_PPE = 1 << 2, /* helmet / vest / glove compliance */
XCAICX_MODULE_ANPR = 1 << 3 /* plate detect + OCR */
} xcaicx_module;
typedef uint32_t xcaicx_module_mask;
const char *xcaicx_module_name(xcaicx_module m);These map one-to-one onto licence entitlements. A module whose entitlement bit is false cannot be enabled: xcaicx_engine_create fails with XCAICX_ERR_ENTITLEMENT_DENIED.
Frames#
typedef enum xcaicx_pixel_format {
XCAICX_PIX_UNKNOWN = 0,
XCAICX_PIX_RGB8 = 1, /* 3 bytes/px, packed */
XCAICX_PIX_BGR8 = 2, /* 3 bytes/px, packed (OpenCV native) */
XCAICX_PIX_GRAY8 = 3, /* 1 byte/px */
XCAICX_PIX_NV12 = 4, /* Y plane + interleaved UV, ISP native */
XCAICX_PIX_I420 = 5 /* Y + U + V planes */
} xcaicx_pixel_format;
typedef struct xcaicx_frame {
size_t struct_size;
const uint8_t *data;
size_t data_len;
int32_t width;
int32_t height;
int32_t stride; /* bytes per row of plane 0; 0 = packed */
xcaicx_pixel_format format;
int64_t pts_us; /* presentation timestamp, microseconds */
uint64_t frame_id; /* caller-assigned, echoed in results */
} xcaicx_frame;data is a borrowed view. XCAICX never takes ownership and never frees it; it must stay valid for the duration of the submit call only.
Dimensions above 16384 are rejected. data_len is validated against the declared geometry, and a mismatch returns XCAICX_ERR_FRAME_FORMAT with both numbers in xcaicx_last_error().
See Feeding frames for strides and zero-copy patterns.
Results#
typedef struct xcaicx_box {
float x, y, w, h; /* normalized [0,1] relative to frame */
} xcaicx_box;
typedef struct xcaicx_detection {
size_t struct_size;
xcaicx_box box;
float score; /* [0,1] */
int32_t class_id;
const char *class_name; /* library-owned, stable for engine lifetime */
int64_t track_id; /* -1 if untracked */
xcaicx_module source; /* which module produced this */
const char *text; /* ANPR only: UTF-8 plate text, else NULL */
float text_score;
} xcaicx_detection;
typedef enum xcaicx_event_kind {
XCAICX_EVENT_NONE = 0,
XCAICX_EVENT_ZONE_ENTER = 1,
XCAICX_EVENT_ZONE_EXIT = 2,
XCAICX_EVENT_LINE_CROSS = 3,
XCAICX_EVENT_DWELL_EXCEEDED = 4,
XCAICX_EVENT_PPE_VIOLATION = 5,
XCAICX_EVENT_DEFECT_FOUND = 6,
XCAICX_EVENT_PLATE_READ = 7
} xcaicx_event_kind;
typedef struct xcaicx_event {
size_t struct_size;
xcaicx_event_kind kind;
int64_t track_id;
int32_t zone_id; /* -1 if not zone-scoped */
const char *zone_name; /* NULL if not zone-scoped */
const char *detail; /* "no_helmet", "scratch", "a_to_b", plate text */
float score;
int64_t pts_us;
} xcaicx_event;
typedef struct xcaicx_result {
size_t struct_size;
uint64_t frame_id;
int64_t pts_us;
const xcaicx_detection *detections;
size_t detection_count;
const xcaicx_event *events;
size_t event_count;
float inference_ms;
float total_ms;
} xcaicx_result;Careful
xcaicx_result and every pointer inside it belong to the stream and are valid only until the next call on that stream. That includes class_name, zone_name and detail. Copy before queueing.
Class names from the reference backend: person, vehicle, object, defect, ppe_violation, plate.
Licence state#
typedef enum xcaicx_license_state {
XCAICX_LIC_INVALID = 0,
XCAICX_LIC_VALID = 1,
XCAICX_LIC_GRACE = 2, /* expired but inside grace window */
XCAICX_LIC_EXPIRED = 3,
XCAICX_LIC_REVOKED = 4
} xcaicx_license_state;
typedef struct xcaicx_license_info {
size_t struct_size;
xcaicx_license_state state;
const char *license_id;
const char *sku;
const char *customer_id;
const char *device_id; /* bound unit, or "*" for batch */
const char *batch_id; /* NULL if not an OEM batch key */
xcaicx_module_mask entitlements;
int32_t max_streams;
int32_t max_fps_per_stream;
int64_t issued_at; /* unix seconds */
int64_t expires_at; /* unix seconds; 0 = perpetual */
int64_t seconds_remaining;
int32_t grace_days;
uint64_t metered_inferences;
uint64_t metered_stream_seconds;
} xcaicx_license_info;XCAICX_LIC_GRACE means expired but everything still runs. Surface it as a warning, not an error.
Configuration#
typedef enum xcaicx_log_level {
XCAICX_LOG_OFF = 0, XCAICX_LOG_ERROR = 1, XCAICX_LOG_WARN = 2,
XCAICX_LOG_INFO = 3, XCAICX_LOG_DEBUG = 4
} xcaicx_log_level;
typedef void (*xcaicx_log_fn)(xcaicx_log_level level, const char *msg, void *user);
typedef enum xcaicx_backend {
XCAICX_BACKEND_AUTO = 0,
XCAICX_BACKEND_REFERENCE = 1,
XCAICX_BACKEND_ORT_CPU = 2,
XCAICX_BACKEND_ORT_CUDA = 3,
XCAICX_BACKEND_TENSORRT = 4,
XCAICX_BACKEND_OPENVINO = 5,
XCAICX_BACKEND_RKNN = 6,
XCAICX_BACKEND_HAILO = 7
} xcaicx_backend;
typedef struct xcaicx_config {
size_t struct_size;
const char *license_token; /* "XCAICX1.<payload>.<sig>" */
const char *license_path; /* file containing the token */
const char *license_pubkey; /* base64 Ed25519; NULL = built-in */
const char *state_dir;
const char *model_dir;
xcaicx_module_mask modules;
xcaicx_backend backend;
int32_t num_threads; /* 0 = auto */
int32_t max_queue_depth; /* per stream; 0 = default 4 */
xcaicx_log_level log_level;
xcaicx_log_fn log_fn; /* NULL = stderr */
void *log_user;
const char *cloud_endpoint; /* NULL = offline only */
int32_t heartbeat_seconds;/* 0 = default 3600 */
int32_t offline; /* nonzero = never touch network */
} xcaicx_config;
xcaicx_status xcaicx_config_init(xcaicx_config *cfg);Provide exactly one of license_token / license_path.
state_dir must be writable and survive reboot — see state_dir requirements.
license_pubkey overrides the compiled-in verification key, which is how a key rotation is staged through configuration before a firmware release makes it permanent.
Custom logging#
static void my_log(xcaicx_log_level lvl, const char *msg, void *user) {
(void)user;
syslog(lvl == XCAICX_LOG_ERROR ? LOG_ERR : LOG_INFO, "xcaicx: %s", msg);
}
cfg.log_fn = my_log;
cfg.log_user = NULL;
cfg.log_level = XCAICX_LOG_INFO;Keep it at INFO in the field. The engine logs which backend it selected at INFO on every start, and that line is how you discover a camera silently running the reference backend because its model bundle is missing.
Engine lifecycle#
typedef struct xcaicx_engine xcaicx_engine;
typedef struct xcaicx_stream xcaicx_stream;
xcaicx_status xcaicx_engine_create(const xcaicx_config *cfg, xcaicx_engine **out);
void xcaicx_engine_destroy(xcaicx_engine *e);
xcaicx_status xcaicx_engine_license_info(xcaicx_engine *e, xcaicx_license_info *out);
xcaicx_status xcaicx_engine_heartbeat(xcaicx_engine *e);
const char *xcaicx_version_string(void);
xcaicx_module_mask xcaicx_engine_active_modules(xcaicx_engine *e);xcaicx_engine_destroy flushes metering to disk. Skipping it loses the session's usage.
xcaicx_engine_heartbeat forces a licensing/metering round-trip now rather than at the next scheduled one. Returns XCAICX_OK and does nothing when offline.
Set out->struct_size = sizeof *out before calling xcaicx_engine_license_info.
Zones and rules#
typedef struct xcaicx_point { float x, y; } xcaicx_point; /* normalized */
xcaicx_status xcaicx_stream_add_zone(xcaicx_stream *s,
const char *name,
const xcaicx_point *pts,
size_t pt_count,
int32_t dwell_seconds,
int32_t *out_zone_id);
xcaicx_status xcaicx_stream_add_line(xcaicx_stream *s,
const char *name,
xcaicx_point a, xcaicx_point b,
int32_t *out_line_id);Polygons need three or more points and may be concave. Tripwires are directed: a→b crossings report detail = "a_to_b".
Tracks are tested at the midpoint of their bottom edge. See Zones, tripwires & events.
Streams#
typedef struct xcaicx_stream_config {
size_t struct_size;
const char *name; /* logs + metering attribution */
xcaicx_module_mask modules; /* subset of engine modules; 0 = all */
float detect_threshold;
float nms_threshold;
int32_t detect_every_n; /* 1 = every frame */
} xcaicx_stream_config;
xcaicx_status xcaicx_stream_config_init(xcaicx_stream_config *cfg);
xcaicx_status xcaicx_stream_open(xcaicx_engine *e,
const xcaicx_stream_config *cfg,
xcaicx_stream **out);
void xcaicx_stream_close(xcaicx_stream *s);
xcaicx_status xcaicx_stream_process(xcaicx_stream *s,
const xcaicx_frame *frame,
const xcaicx_result **out);
xcaicx_status xcaicx_stream_submit(xcaicx_stream *s, const xcaicx_frame *frame);
xcaicx_status xcaicx_stream_poll(xcaicx_stream *s,
int32_t timeout_ms,
const xcaicx_result **out);Defaults from xcaicx_stream_config_init: detect_threshold = 0.35, nms_threshold = 0.45, detect_every_n = 1.
Opening more streams than max_streams allows fails with XCAICX_ERR_LIMIT_EXCEEDED.
submit() returns XCAICX_ERR_QUEUE_FULL under backpressure — flow control, not an error. poll() returns XCAICX_ERR_NO_RESULT when nothing is ready.
Windows export macros#
#define XCAICX_USING_SHARED /* before including, when linking the DLL */
#include "xcaicx/xcaicx.h"On non-Windows platforms XCAICX_API is __attribute__((visibility("default"))) and internal symbols are hidden.
Next#
- Status codes — cause and fix for every value
- Python API — the same surface from Python
- Quickstart: C — the whole thing in order