bd7fd95fef
Реализация encoded packet ring per docs/protocol.md §10. Files: - internal.h: cuframes_pkt_slot_t (64b packed), cuframes_pkt_header_t (0x1040 fixed header), cuframes_pkt_ring_t handle, constants for default sizing, packet flags, helper inline functions for slot/data pointer arithmetic. - packet_ring.c (new, ~290 LOC): create/open/publish/read/destroy. Stale recovery симметрично frames SHM (pid liveness check). Seqlock pattern для subscriber защиты от overrun mid-read (post-check seq после copy). Wraparound memcpy helpers для variable-length data ring. - utils.c: cuframes_internal_pkt_shm_name helper + strerror entries. - cuframes.h: 4 новых error codes (PACKET_OVERSIZED, NO_PACKET_RING, NO_CODEC_PARAMS, PACKET_OVERRUN). - CMakeLists.txt: src/packet_ring.c в sources. API внутренний (cuframes_internal_pkt_ring_*) — publicly exposed функции будут в Step 3 (cuframes.h API extension). Связано: #2 (v0.2), PR #4 (draft).
163 lines
6.4 KiB
C
163 lines
6.4 KiB
C
/* Utility functions: error strings, frame size calc, timestamps. */
|
||
|
||
#include "internal.h"
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
#include <signal.h>
|
||
#include <stdio.h>
|
||
#include <sys/stat.h>
|
||
#include <time.h>
|
||
|
||
const char *cuframes_version_string(void) {
|
||
static char buf[32];
|
||
snprintf(buf, sizeof(buf), "%d.%d.%d",
|
||
CUFRAMES_VERSION_MAJOR, CUFRAMES_VERSION_MINOR, CUFRAMES_VERSION_PATCH);
|
||
return buf;
|
||
}
|
||
|
||
uint32_t cuframes_protocol_version(void) { return CUFRAMES_PROTOCOL_V1; }
|
||
|
||
const char *cuframes_strerror(int err) {
|
||
switch (err) {
|
||
case CUFRAMES_OK: return "ok";
|
||
case CUFRAMES_ERR_INVALID_ARG: return "invalid argument";
|
||
case CUFRAMES_ERR_OUT_OF_MEMORY: return "out of memory";
|
||
case CUFRAMES_ERR_CUDA: return "cuda error";
|
||
case CUFRAMES_ERR_IO: return "I/O error (socket/mmap/eventfd)";
|
||
case CUFRAMES_ERR_NOT_FOUND: return "publisher not found";
|
||
case CUFRAMES_ERR_ALREADY_EXISTS: return "publisher/subscriber-name already exists";
|
||
case CUFRAMES_ERR_TIMEOUT: return "timeout";
|
||
case CUFRAMES_ERR_PROTOCOL: return "wire protocol mismatch";
|
||
case CUFRAMES_ERR_DISCONNECTED: return "disconnected";
|
||
case CUFRAMES_ERR_FORMAT: return "unsupported format or size mismatch";
|
||
case CUFRAMES_ERR_WOULD_BLOCK: return "would block";
|
||
case CUFRAMES_ERR_TOO_MANY: return "too many subscribers (max 32)";
|
||
case CUFRAMES_ERR_PACKET_OVERSIZED: return "packet exceeds max_packet_size";
|
||
case CUFRAMES_ERR_NO_PACKET_RING: return "publisher has no packet ring";
|
||
case CUFRAMES_ERR_NO_CODEC_PARAMS: return "codec extradata not set by publisher";
|
||
case CUFRAMES_ERR_PACKET_OVERRUN: return "packet ring overrun — resync on keyframe";
|
||
case CUFRAMES_ERR_INTERNAL: return "internal error (please report)";
|
||
default: return "unknown error";
|
||
}
|
||
}
|
||
|
||
int64_t cuframes_now_ns(void) {
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||
}
|
||
|
||
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) {
|
||
return cuframes_internal_calc_size(format, width, height,
|
||
size_out, pitch_y_out, pitch_uv_out);
|
||
}
|
||
|
||
/* ─── Internal helpers ────────────────────────────────────────────────── */
|
||
|
||
int cuframes_internal_validate_key(const char *key) {
|
||
if (!key) return CUFRAMES_ERR_INVALID_ARG;
|
||
size_t len = strlen(key);
|
||
if (len < 1 || len > CUFRAMES_MAX_KEY_LEN) return CUFRAMES_ERR_INVALID_ARG;
|
||
for (size_t i = 0; i < len; ++i) {
|
||
char c = key[i];
|
||
if (!(isalnum((unsigned char)c) || c == '_' || c == '-')) {
|
||
return CUFRAMES_ERR_INVALID_ARG;
|
||
}
|
||
}
|
||
return CUFRAMES_OK;
|
||
}
|
||
|
||
int cuframes_internal_socket_path(const char *key, char *out, size_t out_size) {
|
||
int r = cuframes_internal_validate_key(key);
|
||
if (r != CUFRAMES_OK) return r;
|
||
int n = snprintf(out, out_size, "%s/%s.sock", CUFRAMES_RUNTIME_DIR, key);
|
||
if (n < 0 || (size_t)n >= out_size) return CUFRAMES_ERR_INVALID_ARG;
|
||
return CUFRAMES_OK;
|
||
}
|
||
|
||
int cuframes_internal_shm_name(const char *key, char *out, size_t out_size) {
|
||
int r = cuframes_internal_validate_key(key);
|
||
if (r != CUFRAMES_OK) return r;
|
||
int n = snprintf(out, out_size, "%s%s", CUFRAMES_SHM_PREFIX, key);
|
||
if (n < 0 || (size_t)n >= out_size) return CUFRAMES_ERR_INVALID_ARG;
|
||
return CUFRAMES_OK;
|
||
}
|
||
|
||
int cuframes_internal_pkt_shm_name(const char *key, char *out, size_t out_size) {
|
||
int r = cuframes_internal_validate_key(key);
|
||
if (r != CUFRAMES_OK) return r;
|
||
int n = snprintf(out, out_size, "%s%s%s",
|
||
CUFRAMES_SHM_PREFIX, key, CUFRAMES_PKT_SHM_SUFFIX);
|
||
if (n < 0 || (size_t)n >= out_size) return CUFRAMES_ERR_INVALID_ARG;
|
||
return CUFRAMES_OK;
|
||
}
|
||
|
||
int cuframes_internal_ensure_runtime_dir(void) {
|
||
if (mkdir(CUFRAMES_RUNTIME_DIR, 0755) == 0) return CUFRAMES_OK;
|
||
if (errno == EEXIST) return CUFRAMES_OK;
|
||
CUFRAMES_LOG_ERROR("cannot create %s: %s",
|
||
CUFRAMES_RUNTIME_DIR, strerror(errno));
|
||
return CUFRAMES_ERR_IO;
|
||
}
|
||
|
||
int cuframes_internal_pid_alive(pid_t pid) {
|
||
if (pid <= 0) return 0;
|
||
if (kill(pid, 0) == 0) return 1;
|
||
return (errno == EPERM) ? 1 : 0;
|
||
}
|
||
|
||
/* Возвращает размер frame'а + pitch'ы. Pitch aligned 256.
|
||
* NV12: Y w×h + UV w×h/2 (interleaved) — оба с тем же pitch_y.
|
||
* YUV420P: Y w×h + U w/2×h/2 + V w/2×h/2.
|
||
* RGB/BGR: 3 bytes per pixel, single plane.
|
||
* RGBA: 4 bytes per pixel.
|
||
* GRAYSCALE: 1 byte per pixel.
|
||
*/
|
||
int cuframes_internal_calc_size(cuframes_format_t format, int32_t w, int32_t h,
|
||
size_t *size_out, int32_t *pitch_y_out, int32_t *pitch_uv_out) {
|
||
if (w <= 0 || h <= 0) return CUFRAMES_ERR_INVALID_ARG;
|
||
|
||
int32_t py = 0, puv = 0;
|
||
size_t total = 0;
|
||
|
||
/* round up to 256-byte alignment for CUDA */
|
||
#define ALIGN256(x) (((x) + 255u) & ~255u)
|
||
|
||
switch (format) {
|
||
case CUFRAMES_FORMAT_NV12:
|
||
py = (int32_t)ALIGN256((uint32_t)w);
|
||
puv = py;
|
||
total = (size_t)py * (size_t)h + (size_t)puv * (size_t)(h / 2);
|
||
break;
|
||
case CUFRAMES_FORMAT_YUV420P:
|
||
py = (int32_t)ALIGN256((uint32_t)w);
|
||
puv = (int32_t)ALIGN256((uint32_t)(w / 2));
|
||
total = (size_t)py * (size_t)h + 2 * (size_t)puv * (size_t)(h / 2);
|
||
break;
|
||
case CUFRAMES_FORMAT_RGB:
|
||
case CUFRAMES_FORMAT_BGR:
|
||
py = (int32_t)ALIGN256((uint32_t)(w * 3));
|
||
total = (size_t)py * (size_t)h;
|
||
break;
|
||
case CUFRAMES_FORMAT_RGBA:
|
||
py = (int32_t)ALIGN256((uint32_t)(w * 4));
|
||
total = (size_t)py * (size_t)h;
|
||
break;
|
||
case CUFRAMES_FORMAT_GRAYSCALE:
|
||
py = (int32_t)ALIGN256((uint32_t)w);
|
||
total = (size_t)py * (size_t)h;
|
||
break;
|
||
default:
|
||
return CUFRAMES_ERR_FORMAT;
|
||
}
|
||
|
||
if (size_out) *size_out = total;
|
||
if (pitch_y_out) *pitch_y_out = py;
|
||
if (pitch_uv_out) *pitch_uv_out = puv;
|
||
return CUFRAMES_OK;
|
||
}
|