Files
Claude Opus 8c3c43709d docs: full content + landing + RU translations
Initial documentation site for cuframes:

- Landing page (src/pages/index.mdx) — hero, quick example (publisher +
  subscriber), comparison table vs naive/DeepStream, honest "early but
  production-tested" status
- /docs/intro — full overview
- /docs/getting-started/{install,first-publisher,first-subscriber}
- /docs/concepts/{frame-vs-packet-ring,ownership-modes,sync-vmm-stream}
  with mermaid diagrams
- /docs/integration/{ffmpeg-demuxer,ffmpeg-filter,python}
- /docs/reference/{api-c,api-cpp,protocol} — full v4 wire protocol spec
  incl. VMM_FDS message, magic 0xCC7C1DCE bump diff
- /docs/faq — comparison vs DeepStream/GStreamer, license, multi-host
  limitations
- i18n/ru/ — parallel RU translation (tech terms latin, склонение апостроф)

Build:
- Docusaurus 3.10.1 + theme-mermaid + search-local
- Follows dagstack-* docs convention (canonical: dagstack-plugin-system-docs)
- Apache-2.0 license; cuframes lib itself remains LGPL-2.1+

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 23:31:03 +01:00

11 KiB
Raw Permalink Blame History

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(...);
}

Исключения

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-конструктор. В 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.

Утилиты

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-варианта, который возвращает код).

Примеры

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());
}

См. также