352 lines
11 KiB
Markdown
352 lines
11 KiB
Markdown
---
|
||
sidebar_position: 2
|
||
title: C++ API
|
||
---
|
||
|
||
# C++ API reference
|
||
|
||
`<cuframes/cuframes.hpp>` — header-only RAII wrapper над [C API](/docs/reference/api-c). Тонкий слой: handle-классы с automatic cleanup, exceptions вместо int return codes, `std::optional<FrameRef>` для `next`.
|
||
|
||
## Headers & linkage
|
||
|
||
```cpp
|
||
#include <cuframes/cuframes.hpp>
|
||
```
|
||
|
||
```bash
|
||
# 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`:
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
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`](/docs/reference/api-c#error-codes); `what()` — `"<context>: <strerror>"`.
|
||
|
||
```cpp
|
||
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.
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
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](/docs/reference/api-c#create--destroy).
|
||
|
||
`publish_packet` — единственный non-throwing метод (возвращает int). Это сделано чтобы в hot loop encoder'а не платить за exception unwind на каждом packet.
|
||
|
||
Минимальный publisher loop:
|
||
|
||
```cpp
|
||
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)
|
||
|
||
```cpp
|
||
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.
|
||
|
||
```cpp
|
||
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)
|
||
|
||
```cpp
|
||
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).
|
||
|
||
```cpp
|
||
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.
|
||
|
||
```cpp
|
||
// 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](/docs/reference/api-c#subscriber-side).
|
||
|
||
## Utilities
|
||
|
||
```cpp
|
||
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)
|
||
|
||
```cpp
|
||
#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
|
||
|
||
```cpp
|
||
#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
|
||
|
||
```cpp
|
||
#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](/docs/reference/api-c) — underlying C functions.
|
||
- [Protocol reference](/docs/reference/protocol) — wire format spec.
|
||
- [First publisher](/docs/getting-started/first-publisher) — minimal end-to-end пример.
|