11 KiB
sidebar_position, title
| sidebar_position | title |
|---|---|
| 2 | C++ API |
C++ API reference
<cuframes/cuframes.hpp> — header-only RAII wrapper над C API. Тонкий слой: handle-классы с automatic cleanup, exceptions вместо int return codes, std::optional<FrameRef> для next.
Headers & linkage
#include <cuframes/cuframes.hpp>
# C++17 минимум (нужен std::optional)
c++ -std=c++17 app.cpp -lcuframes
# при использовании своих CUDA streams
c++ -std=c++17 app.cpp -lcuframes -lcudart
Header-only — самой C++ библиотеки не существует, есть только wrapper над libcuframes.so. ABI-совместимость наследуется от C API.
Всё в namespace cuframes:
namespace cuframes {
class Error;
class Frame;
class FrameRef;
class Publisher;
class Subscriber;
class AsyncSubscriber;
struct PublisherOptions;
struct SubscriberOptions;
inline int64_t now_ns();
inline size_t calc_frame_size(...);
}
Exceptions
class cuframes::Error : public std::runtime_error {
public:
Error(int code, const std::string &context);
int code() const noexcept;
};
Бросается из всех методов кроме explicitly noexcept. code() — оригинальный cuframes_error_t; what() — "<context>: <strerror>".
try {
cuframes::Publisher pub({.key = "cam1", .width = 1920, .height = 1080});
} catch (const cuframes::Error &e) {
if (e.code() == CUFRAMES_ERR_ALREADY_EXISTS) {
// stale publisher с этим key
}
}
Frame & FrameRef
Frame — read-only non-owning view над cuframes_frame_t. Используется в callback'ах async subscriber'а.
FrameRef — RAII owning handle: release вызывается автоматически в destructor'е. Moveable, не copyable.
class Frame {
public:
void *cuda_ptr() const noexcept;
cuframes_format_t format() const noexcept;
int32_t width() const noexcept;
int32_t height() const noexcept;
int32_t pitch_y() const noexcept;
int32_t pitch_uv() const noexcept;
uint64_t seq() const noexcept;
int64_t pts_ns() const noexcept;
const cuframes_frame_t *raw() const noexcept;
};
class FrameRef {
public:
explicit operator bool() const noexcept;
Frame view() const noexcept;
// Shortcut accessors: cuda_ptr, width, height, pitch_y, pitch_uv, seq, pts_ns
};
Все accessor-методы noexcept — они идут в C accessor'ы которые ничего не аллоцируют.
Publisher
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);
// EXTERNAL ownership — DEPRECATED в v0.4, бросает Error(INVALID_ARG)
Publisher(const PublisherOptions &opt,
void *const *cuda_ptrs, int32_t ptr_count, size_t frame_size);
~Publisher();
Publisher(Publisher &&) noexcept;
Publisher &operator=(Publisher &&) noexcept;
void *acquire();
void publish(void *stream, int64_t pts_ns);
// EXTERNAL mode — DEPRECATED в v0.4
void publish_external(void *cuda_ptr, void *stream, int64_t pts_ns);
// Packet ring
void enable_packets(const cuframes_packet_ring_options_t *opts = nullptr);
void set_codec_extradata(const void *data, size_t size);
int publish_packet(const void *data, size_t size,
int64_t pts_ns, int64_t dts_ns, uint32_t flags) noexcept;
cuframes_publisher_t *raw() noexcept;
};
Note про deprecated EXTERNAL ownership constructor. В v0.4 второй конструктор Publisher(opt, cuda_ptrs, ...) под капотом вызывает cuframes_publisher_create_external и сразу получает INVALID_ARG → бросает cuframes::Error. Для FFmpeg filter / custom decoder integration переходите на LIBRARY mode + одна cudaMemcpyAsync(D2D) в acquire()'нутый pointer. См. C API note.
publish_packet — единственный non-throwing метод (возвращает int). Это сделано чтобы в hot loop encoder'а не платить за exception unwind на каждом packet.
Минимальный publisher loop:
cuframes::Publisher pub({
.key = "cam1",
.width = 1920, .height = 1080,
.format = CUFRAMES_FORMAT_NV12,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (;;) {
void *slot = pub.acquire();
// ... NVDEC / kernel пишут в slot на `stream` ...
pub.publish(stream, cuframes::now_ns());
}
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;
};
class Subscriber {
public:
explicit Subscriber(const SubscriberOptions &opt);
~Subscriber();
Subscriber(Subscriber &&) noexcept;
Subscriber &operator=(Subscriber &&) noexcept;
std::optional<FrameRef> next(void *stream, int32_t timeout_ms = -1);
cuframes_subscriber_t *raw() noexcept;
};
next возвращает std::nullopt для recoverable conditions (TIMEOUT, WOULD_BLOCK, DISCONNECTED) и бросает Error для всего остального. Эта асимметрия сделана сознательно — три перечисленных случая ожидаемы в обычном loop'е и не должны платить за exception unwind.
cuframes::Subscriber sub({
.key = "cam1",
.mode = CUFRAMES_MODE_NEWEST_ONLY,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
while (auto frame = sub.next(stream, 1000)) {
// frame->cuda_ptr(), frame->width(), frame->pts_ns()
// release будет автоматически при выходе из scope
}
Subscriber (async)
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 = {});
~AsyncSubscriber();
};
Callback-based wrapper. Library поднимает internal thread; Frame валиден только в течение callback'а (автоматический release после return).
cuframes::AsyncSubscriber sub(
{.key = "cam1"},
[](const cuframes::Frame &f) {
std::printf("seq=%lu pts=%ld\n", f.seq(), f.pts_ns());
},
[](int err, const std::string &msg) {
std::fprintf(stderr, "cuframes error %d: %s\n", err, msg.c_str());
});
// держим subscriber alive...
std::this_thread::sleep_for(std::chrono::seconds(60));
// destructor join'ит worker thread
Не copyable. Move в текущей версии тоже запрещён (поля std::function хранят this-pointer для trampoline'а — move сломает).
Packet ring
В C++ wrapper'е packet ring доступен через прямые C-функции и Publisher::publish_packet. Отдельных cuframes::Packet / cuframes::PacketRef классов нет — packet API проще и FFmpeg interop часто пишется напрямую через C.
// Publisher-side
cuframes_packet_ring_options_t pkt_opts{};
pkt_opts.ring_slots = 64;
pkt_opts.data_size = 8 * 1024 * 1024;
pkt_opts.max_packet_size = 2 * 1024 * 1024;
pkt_opts.codec_id = AV_CODEC_ID_H264;
pub.enable_packets(&pkt_opts);
pub.set_codec_extradata(sps_pps.data(), sps_pps.size());
int rc = pub.publish_packet(nal, nal_size, pts, dts, CUFRAMES_PKT_FLAG_KEY);
if (rc < 0 && rc != CUFRAMES_ERR_PACKET_OVERSIZED) {
// log + skip; OVERSIZED безопасно игнорировать
}
Subscriber-side — pure C функции из <cuframes/cuframes.h>, см. Packet subscriber API.
Utilities
inline int64_t cuframes::now_ns();
inline size_t cuframes::calc_frame_size(cuframes_format_t format,
int32_t w, int32_t h,
int32_t *pitch_y = nullptr,
int32_t *pitch_uv = nullptr);
calc_frame_size бросает Error на unknown format (в отличие от C-варианта который возвращает код).
Examples
Complete publisher (LIBRARY mode)
#include <cuframes/cuframes.hpp>
#include <cuda_runtime.h>
int main() {
cuframes::Publisher pub({
.key = "cam1",
.width = 1920, .height = 1080,
.format = CUFRAMES_FORMAT_NV12,
.ring_size = 4,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (int i = 0; i < 1000; i++) {
void *slot = pub.acquire();
// ... NVDEC decode или kernel write в slot ...
pub.publish(stream, cuframes::now_ns());
}
}
Complete subscriber
#include <cuframes/cuframes.hpp>
#include <cuda_runtime.h>
int main() {
cuframes::Subscriber sub({
.key = "cam1",
.consumer_name = "my-detector",
.mode = CUFRAMES_MODE_NEWEST_ONLY,
.connect_timeout_ms = 5000,
});
cudaStream_t stream;
cudaStreamCreate(&stream);
for (;;) {
auto frame = sub.next(stream, 1000);
if (!frame) continue; // timeout/disconnect
// frame->cuda_ptr() — VRAM pointer
// frame->width(), frame->height() — pixels
// frame->pitch_y(), frame->pitch_uv() — байт на строку
// ... ML inference / CUDA filter на stream ...
}
}
Async with lambdas
#include <cuframes/cuframes.hpp>
#include <atomic>
#include <thread>
int main() {
std::atomic<uint64_t> frames{0};
cuframes::AsyncSubscriber sub(
{.key = "cam1"},
[&](const cuframes::Frame &f) {
frames.fetch_add(1);
// pre-sync уже выполнен library-side; f.cuda_ptr() ready
});
std::this_thread::sleep_for(std::chrono::seconds(10));
std::printf("processed %lu frames\n", frames.load());
}
See also
- C API — underlying C functions.
- Protocol reference — wire format spec.
- First publisher — minimal end-to-end пример.