hpp: C++ RAII wrapper (header-only, Step 7)
Тонкий слой поверх C API: - cuframes::Error — exception при ошибках, code() для подробностей - cuframes::Publisher — RAII обёртка publisher'а (LIBRARY + EXTERNAL constructors) - cuframes::Subscriber + cuframes::FrameRef — RAII frame с автo-release - cuframes::AsyncSubscriber — с std::function callbacks - cuframes::Frame — read-only view (для callback'а) - cuframes::calc_frame_size(), now_ns() — utilities Smoke test (in dev container): $ g++ -std=c++17 ... -lcuframes -lcudart smoke.cpp $ ./smoke version: 0.1.0 FullHD NV12 frame: 3317760 bytes (pitch_y=2048, pitch_uv=2048)
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
/* cuframes C++ wrapper — header-only RAII.
|
||||
*
|
||||
* Тонкий слой поверх C API: классы с automatic resource cleanup,
|
||||
* exceptions вместо int return codes, std::optional<Frame> для next().
|
||||
*
|
||||
* Linkage: header-only; нужен `-lcuframes` (shared) или
|
||||
* `libcuframes_static.a` (static) при линковке user-кода.
|
||||
*
|
||||
* License: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_HPP
|
||||
#define CUFRAMES_HPP
|
||||
|
||||
#include <cuframes/cuframes.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace cuframes {
|
||||
|
||||
/* ─── Exceptions ─────────────────────────────────────────────────────── */
|
||||
|
||||
class Error : public std::runtime_error {
|
||||
public:
|
||||
Error(int code, const std::string &context)
|
||||
: std::runtime_error(context + ": " + cuframes_strerror(code)),
|
||||
code_(code) {}
|
||||
int code() const noexcept { return code_; }
|
||||
|
||||
private:
|
||||
int code_;
|
||||
};
|
||||
|
||||
inline void check(int rc, const char *context) {
|
||||
if (rc != CUFRAMES_OK) throw Error(rc, context);
|
||||
}
|
||||
|
||||
/* ─── Frame (read-only view, не owning) ──────────────────────────────── */
|
||||
|
||||
class Frame {
|
||||
public:
|
||||
explicit Frame(const cuframes_frame_t *f) : f_(f) {}
|
||||
|
||||
void *cuda_ptr() const noexcept { return cuframes_frame_cuda_ptr(f_); }
|
||||
cuframes_format_t format() const noexcept { return cuframes_frame_format(f_); }
|
||||
int32_t width() const noexcept {
|
||||
int32_t w = 0, h = 0; cuframes_frame_size(f_, &w, &h); return w;
|
||||
}
|
||||
int32_t height() const noexcept {
|
||||
int32_t w = 0, h = 0; cuframes_frame_size(f_, &w, &h); return h;
|
||||
}
|
||||
int32_t pitch_y() const noexcept { return cuframes_frame_pitch_y(f_); }
|
||||
int32_t pitch_uv() const noexcept { return cuframes_frame_pitch_uv(f_); }
|
||||
uint64_t seq() const noexcept { return cuframes_frame_seq(f_); }
|
||||
int64_t pts_ns() const noexcept { return cuframes_frame_pts_ns(f_); }
|
||||
|
||||
/* Raw C handle (для interop с C APIs) */
|
||||
const cuframes_frame_t *raw() const noexcept { return f_; }
|
||||
|
||||
private:
|
||||
const cuframes_frame_t *f_;
|
||||
};
|
||||
|
||||
/* ─── Publisher (LIBRARY ownership) ──────────────────────────────────── */
|
||||
|
||||
struct PublisherOptions {
|
||||
std::string key;
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
cuframes_format_t format = CUFRAMES_FORMAT_NV12;
|
||||
int32_t ring_size = 4;
|
||||
cuframes_publisher_policy_t policy = CUFRAMES_POLICY_DROP_OLDEST;
|
||||
int32_t consumer_ack_timeout_ms = 0;
|
||||
int32_t cuda_device = 0;
|
||||
};
|
||||
|
||||
class Publisher {
|
||||
public:
|
||||
explicit Publisher(const PublisherOptions &opt) {
|
||||
cuframes_publisher_config_t cfg{};
|
||||
cfg.key = opt.key.c_str();
|
||||
cfg.width = opt.width;
|
||||
cfg.height = opt.height;
|
||||
cfg.format = opt.format;
|
||||
cfg.ownership = CUFRAMES_OWNERSHIP_LIBRARY;
|
||||
cfg.ring_size = opt.ring_size;
|
||||
cfg.policy = opt.policy;
|
||||
cfg.consumer_ack_timeout_ms = opt.consumer_ack_timeout_ms;
|
||||
cfg.cuda_device = opt.cuda_device;
|
||||
check(cuframes_publisher_create(&cfg, &pub_), "Publisher::create");
|
||||
}
|
||||
|
||||
/* EXTERNAL ownership constructor */
|
||||
Publisher(const PublisherOptions &opt,
|
||||
void *const *cuda_ptrs, int32_t ptr_count, size_t frame_size) {
|
||||
cuframes_publisher_config_t cfg{};
|
||||
cfg.key = opt.key.c_str();
|
||||
cfg.width = opt.width;
|
||||
cfg.height = opt.height;
|
||||
cfg.format = opt.format;
|
||||
cfg.ownership = CUFRAMES_OWNERSHIP_EXTERNAL;
|
||||
cfg.policy = opt.policy;
|
||||
cfg.consumer_ack_timeout_ms = opt.consumer_ack_timeout_ms;
|
||||
cfg.cuda_device = opt.cuda_device;
|
||||
check(cuframes_publisher_create_external(&cfg, cuda_ptrs, ptr_count,
|
||||
frame_size, &pub_),
|
||||
"Publisher::create_external");
|
||||
}
|
||||
|
||||
~Publisher() {
|
||||
if (pub_) cuframes_publisher_destroy(pub_);
|
||||
}
|
||||
|
||||
Publisher(const Publisher &) = delete;
|
||||
Publisher &operator=(const Publisher &) = delete;
|
||||
Publisher(Publisher &&o) noexcept : pub_(o.pub_) { o.pub_ = nullptr; }
|
||||
Publisher &operator=(Publisher &&o) noexcept {
|
||||
if (this != &o) {
|
||||
if (pub_) cuframes_publisher_destroy(pub_);
|
||||
pub_ = o.pub_;
|
||||
o.pub_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* LIBRARY mode: acquire pointer + publish */
|
||||
void *acquire() {
|
||||
void *p = nullptr;
|
||||
check(cuframes_publisher_acquire(pub_, &p), "Publisher::acquire");
|
||||
return p;
|
||||
}
|
||||
|
||||
/* `stream` — CUDA stream на котором писались данные */
|
||||
void publish(void *stream, int64_t pts_ns) {
|
||||
check(cuframes_publisher_publish(pub_, stream, pts_ns), "Publisher::publish");
|
||||
}
|
||||
|
||||
/* EXTERNAL mode */
|
||||
void publish_external(void *cuda_ptr, void *stream, int64_t pts_ns) {
|
||||
check(cuframes_publisher_publish_external(pub_, cuda_ptr, stream, pts_ns),
|
||||
"Publisher::publish_external");
|
||||
}
|
||||
|
||||
cuframes_publisher_t *raw() noexcept { return pub_; }
|
||||
|
||||
private:
|
||||
cuframes_publisher_t *pub_ = nullptr;
|
||||
};
|
||||
|
||||
/* ─── Subscriber (sync) ──────────────────────────────────────────────── */
|
||||
|
||||
struct SubscriberOptions {
|
||||
std::string key;
|
||||
std::string consumer_name; // empty = auto-generate
|
||||
cuframes_subscriber_mode_t mode = CUFRAMES_MODE_NEWEST_ONLY;
|
||||
int32_t cuda_device = 0;
|
||||
int32_t connect_timeout_ms = 5000;
|
||||
};
|
||||
|
||||
/* RAII-обёртка для одного frame'а; release при destruct */
|
||||
class FrameRef {
|
||||
public:
|
||||
FrameRef() noexcept = default;
|
||||
FrameRef(cuframes_subscriber_t *sub, cuframes_frame_t *f) noexcept
|
||||
: sub_(sub), f_(f) {}
|
||||
|
||||
~FrameRef() {
|
||||
if (f_ && sub_) cuframes_subscriber_release(sub_, f_);
|
||||
}
|
||||
|
||||
FrameRef(const FrameRef &) = delete;
|
||||
FrameRef &operator=(const FrameRef &) = delete;
|
||||
FrameRef(FrameRef &&o) noexcept : sub_(o.sub_), f_(o.f_) {
|
||||
o.sub_ = nullptr;
|
||||
o.f_ = nullptr;
|
||||
}
|
||||
FrameRef &operator=(FrameRef &&o) noexcept {
|
||||
if (this != &o) {
|
||||
if (f_ && sub_) cuframes_subscriber_release(sub_, f_);
|
||||
sub_ = o.sub_;
|
||||
f_ = o.f_;
|
||||
o.sub_ = nullptr;
|
||||
o.f_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return f_ != nullptr; }
|
||||
|
||||
Frame view() const noexcept { return Frame(f_); }
|
||||
|
||||
/* Accessor shortcuts */
|
||||
void *cuda_ptr() const noexcept { return cuframes_frame_cuda_ptr(f_); }
|
||||
int32_t width() const noexcept {
|
||||
int32_t w = 0, h = 0; cuframes_frame_size(f_, &w, &h); return w;
|
||||
}
|
||||
int32_t height() const noexcept {
|
||||
int32_t w = 0, h = 0; cuframes_frame_size(f_, &w, &h); return h;
|
||||
}
|
||||
int32_t pitch_y() const noexcept { return cuframes_frame_pitch_y(f_); }
|
||||
int32_t pitch_uv() const noexcept { return cuframes_frame_pitch_uv(f_); }
|
||||
uint64_t seq() const noexcept { return cuframes_frame_seq(f_); }
|
||||
int64_t pts_ns() const noexcept { return cuframes_frame_pts_ns(f_); }
|
||||
|
||||
private:
|
||||
cuframes_subscriber_t *sub_ = nullptr;
|
||||
cuframes_frame_t *f_ = nullptr;
|
||||
};
|
||||
|
||||
class Subscriber {
|
||||
public:
|
||||
explicit Subscriber(const SubscriberOptions &opt) {
|
||||
cuframes_subscriber_config_t cfg{};
|
||||
cfg.key = opt.key.c_str();
|
||||
cfg.consumer_name = opt.consumer_name.empty() ? nullptr : opt.consumer_name.c_str();
|
||||
cfg.mode = opt.mode;
|
||||
cfg.cuda_device = opt.cuda_device;
|
||||
cfg.connect_timeout_ms = opt.connect_timeout_ms;
|
||||
check(cuframes_subscriber_create(&cfg, &sub_), "Subscriber::create");
|
||||
}
|
||||
|
||||
~Subscriber() {
|
||||
if (sub_) cuframes_subscriber_destroy(sub_);
|
||||
}
|
||||
|
||||
Subscriber(const Subscriber &) = delete;
|
||||
Subscriber &operator=(const Subscriber &) = delete;
|
||||
Subscriber(Subscriber &&o) noexcept : sub_(o.sub_) { o.sub_ = nullptr; }
|
||||
Subscriber &operator=(Subscriber &&o) noexcept {
|
||||
if (this != &o) {
|
||||
if (sub_) cuframes_subscriber_destroy(sub_);
|
||||
sub_ = o.sub_;
|
||||
o.sub_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Returns empty FrameRef если TIMEOUT/WOULD_BLOCK/DISCONNECTED.
|
||||
* Иначе бросает Error.
|
||||
*
|
||||
* `stream` — ваш CUDA stream. Если nullptr — будет cudaEventSynchronize. */
|
||||
std::optional<FrameRef> next(void *stream, int32_t timeout_ms = -1) {
|
||||
cuframes_frame_t *f = nullptr;
|
||||
int r = cuframes_subscriber_next(sub_, stream, &f, timeout_ms);
|
||||
if (r == CUFRAMES_OK) return FrameRef(sub_, f);
|
||||
if (r == CUFRAMES_ERR_TIMEOUT || r == CUFRAMES_ERR_WOULD_BLOCK ||
|
||||
r == CUFRAMES_ERR_DISCONNECTED)
|
||||
return std::nullopt;
|
||||
throw Error(r, "Subscriber::next");
|
||||
}
|
||||
|
||||
cuframes_subscriber_t *raw() noexcept { return sub_; }
|
||||
|
||||
private:
|
||||
cuframes_subscriber_t *sub_ = nullptr;
|
||||
};
|
||||
|
||||
/* ─── AsyncSubscriber ────────────────────────────────────────────────── */
|
||||
|
||||
class AsyncSubscriber {
|
||||
public:
|
||||
using OnFrame = std::function<void(const Frame &)>;
|
||||
using OnError = std::function<void(int err, const std::string &msg)>;
|
||||
|
||||
AsyncSubscriber(const SubscriberOptions &opt,
|
||||
OnFrame on_frame,
|
||||
OnError on_error = {})
|
||||
: on_frame_(std::move(on_frame)), on_error_(std::move(on_error)) {
|
||||
cuframes_subscriber_config_t cfg{};
|
||||
cfg.key = opt.key.c_str();
|
||||
cfg.consumer_name = opt.consumer_name.empty() ? nullptr : opt.consumer_name.c_str();
|
||||
cfg.mode = opt.mode;
|
||||
cfg.cuda_device = opt.cuda_device;
|
||||
cfg.connect_timeout_ms = opt.connect_timeout_ms;
|
||||
check(cuframes_async_subscriber_create(&cfg,
|
||||
&AsyncSubscriber::frame_trampoline,
|
||||
&AsyncSubscriber::error_trampoline,
|
||||
this, &sub_),
|
||||
"AsyncSubscriber::create");
|
||||
}
|
||||
|
||||
~AsyncSubscriber() {
|
||||
if (sub_) cuframes_async_subscriber_destroy(sub_);
|
||||
}
|
||||
|
||||
AsyncSubscriber(const AsyncSubscriber &) = delete;
|
||||
AsyncSubscriber &operator=(const AsyncSubscriber &) = delete;
|
||||
|
||||
private:
|
||||
static void frame_trampoline(const cuframes_frame_t *f, void *ud) {
|
||||
auto *self = static_cast<AsyncSubscriber *>(ud);
|
||||
if (self->on_frame_) self->on_frame_(Frame(f));
|
||||
}
|
||||
static void error_trampoline(int err, const char *msg, void *ud) {
|
||||
auto *self = static_cast<AsyncSubscriber *>(ud);
|
||||
if (self->on_error_) self->on_error_(err, msg ? msg : "");
|
||||
}
|
||||
|
||||
cuframes_async_subscriber_t *sub_ = nullptr;
|
||||
OnFrame on_frame_;
|
||||
OnError on_error_;
|
||||
};
|
||||
|
||||
/* ─── Утилиты ─────────────────────────────────────────────────────────── */
|
||||
|
||||
inline int64_t now_ns() { return cuframes_now_ns(); }
|
||||
|
||||
inline size_t calc_frame_size(cuframes_format_t format, int32_t w, int32_t h,
|
||||
int32_t *pitch_y = nullptr,
|
||||
int32_t *pitch_uv = nullptr) {
|
||||
size_t s = 0;
|
||||
check(cuframes_calc_frame_size(format, w, h, &s, pitch_y, pitch_uv),
|
||||
"calc_frame_size");
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace cuframes
|
||||
|
||||
#endif /* CUFRAMES_HPP */
|
||||
Reference in New Issue
Block a user