8c3c43709d
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>
146 lines
6.2 KiB
Markdown
146 lines
6.2 KiB
Markdown
---
|
|
title: First subscriber
|
|
sidebar_position: 3
|
|
---
|
|
|
|
# First subscriber
|
|
|
|
A minimal subscriber that connects to the publisher from [First publisher](./first-publisher.md), reads 10 frames, and checks that every byte of each frame matches the publisher-side pattern.
|
|
|
|
This is a stripped-down version of [`spike/smoke_v04/smoke_sub.c`](https://git.goldix.org/gx/cuframes/src/branch/main/spike/smoke_v04/smoke_sub.c).
|
|
|
|
## Source
|
|
|
|
```c
|
|
/* first_subscriber.c — connect, read 10 frames, verify pattern. */
|
|
#include <cuframes/cuframes.h>
|
|
#include <cuda_runtime.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *key = argc > 1 ? argv[1] : "mykey";
|
|
|
|
cuframes_subscriber_config_t cfg = {0};
|
|
cfg.key = key;
|
|
cfg.consumer_name = "first-sub";
|
|
cfg.mode = CUFRAMES_MODE_NEWEST_ONLY;
|
|
cfg.cuda_device = 0;
|
|
cfg.connect_timeout_ms = 5000;
|
|
|
|
cuframes_subscriber_t *sub = NULL;
|
|
int r = cuframes_subscriber_create(&cfg, &sub);
|
|
if (r != CUFRAMES_OK) {
|
|
fprintf(stderr, "create: %s\n", cuframes_strerror(r));
|
|
return 1;
|
|
}
|
|
|
|
cudaStream_t stream;
|
|
cudaStreamCreate(&stream);
|
|
|
|
const size_t sample = 1024; /* check first 1 KiB of each frame */
|
|
uint8_t *host = malloc(sample);
|
|
|
|
int frames = 0, good = 0;
|
|
while (frames < 10) {
|
|
cuframes_frame_t *f = NULL;
|
|
r = cuframes_subscriber_next(sub, stream, &f, 2000);
|
|
if (r != CUFRAMES_OK) {
|
|
fprintf(stderr, "next: %s\n", cuframes_strerror(r));
|
|
break;
|
|
}
|
|
|
|
cudaMemcpyAsync(host, cuframes_frame_cuda_ptr(f), sample,
|
|
cudaMemcpyDeviceToHost, stream);
|
|
cudaStreamSynchronize(stream);
|
|
|
|
int mismatch = 0;
|
|
for (size_t i = 1; i < sample; i++)
|
|
if (host[i] != host[0]) mismatch++;
|
|
if (mismatch == 0) good++;
|
|
|
|
printf("seq=%lu pts_ns=%lld pitch_y=%d byte0=0x%02x mismatch=%d\n",
|
|
(unsigned long)cuframes_frame_seq(f),
|
|
(long long)cuframes_frame_pts_ns(f),
|
|
cuframes_frame_pitch_y(f),
|
|
host[0], mismatch);
|
|
|
|
cuframes_subscriber_release(sub, f);
|
|
frames++;
|
|
}
|
|
|
|
free(host);
|
|
cudaStreamDestroy(stream);
|
|
cuframes_subscriber_destroy(sub);
|
|
return (good == frames && frames > 0) ? 0 : 1;
|
|
}
|
|
```
|
|
|
|
## Walk-through
|
|
|
|
**`cfg.key`** — must match the publisher's key exactly. The subscriber finds the publisher by `connect()`-ing to `/run/cuframes/<key>.sock`.
|
|
|
|
**`cfg.consumer_name = "first-sub"`** — identifies this subscriber inside the publisher's ACK bitmap. It must be unique among live subscribers of the same publisher; a collision returns `CUFRAMES_ERR_ALREADY_EXISTS`. If you pass `NULL` the library generates `subscriber-<pid>-<random>`. A publisher accepts up to 32 simultaneous subscribers.
|
|
|
|
**`cfg.mode = CUFRAMES_MODE_NEWEST_ONLY`** — the subscriber always jumps to the latest published frame and skips any frames the publisher produced while the previous `next()` call was being processed. Use `CUFRAMES_MODE_STRICT_ORDER` if you must see every frame in seq order; in that mode a ring overflow surfaces as `CUFRAMES_ERR_DISCONNECTED`.
|
|
|
|
**`cfg.connect_timeout_ms = 5000`** — how long `create()` waits for the publisher to exist. `0` fails immediately with `CUFRAMES_ERR_NOT_FOUND`, `-1` waits forever.
|
|
|
|
**`cfg.cuda_device`** — must equal the publisher's `cuda_device`. CUDA IPC handles are not portable across devices.
|
|
|
|
**`cuframes_subscriber_next(sub, stream, &f, 2000)`** — block up to 2 s for the next frame. The library internally issues `cudaStreamWaitEvent` on your `stream` against the publisher's record-event, so any kernel you launch on `stream` after `next()` returns is guaranteed to see the producer's writes. If you read with `cudaMemcpyDeviceToHost`, queue it on the same stream — that is what makes the cross-process sync work.
|
|
|
|
**Frame accessors** — `cuframes_frame_cuda_ptr()` (device pointer, read-only), `cuframes_frame_format()`, `cuframes_frame_pitch_y()` / `_pitch_uv()`, `cuframes_frame_seq()` (monotonic per publisher), `cuframes_frame_pts_ns()` (publisher-side CLOCK_MONOTONIC). After `release()` the handle is invalid — do not call any accessor on it.
|
|
|
|
**`cuframes_subscriber_release(sub, f)`** — acknowledges the slot back to the publisher. The publisher needs this only when running with `CUFRAMES_POLICY_STRICT_WAIT`; under the default `DROP_OLDEST` it is still required to free the consumer-side handle. NULL is a no-op.
|
|
|
|
## Compile
|
|
|
|
```bash
|
|
gcc -O2 -I/usr/local/include -I/usr/local/cuda/include \
|
|
-o first_subscriber first_subscriber.c \
|
|
-L/usr/local/lib -lcuframes \
|
|
-L/usr/local/cuda/lib64 -lcudart -lcuda
|
|
```
|
|
|
|
## Run
|
|
|
|
In one terminal, start the publisher from the previous page:
|
|
|
|
```bash
|
|
./first_publisher mykey
|
|
```
|
|
|
|
In another terminal:
|
|
|
|
```bash
|
|
./first_subscriber mykey
|
|
```
|
|
|
|
Expected output is 10 lines with rising `seq` and `mismatch=0`.
|
|
|
|
## Docker note
|
|
|
|
The subscriber must share the publisher's **IPC namespace** (so it can `shm_open` the same `/dev/shm/cuframes-mykey` header). It does **not** need to share the PID namespace — this is a v0.4 change. The old v0.1 / v0.2 requirement of `--pid=container:<publisher>` is gone because handles travel as POSIX file descriptors over the Unix socket (`SCM_RIGHTS`), not as CUDA IPC mem-handles.
|
|
|
|
```bash
|
|
docker run --rm --runtime=nvidia \
|
|
--ipc=container:cuframes-pub \
|
|
-v /run/cuframes:/run/cuframes:ro \
|
|
gx/cuframes:0.4 ./first_subscriber mykey
|
|
```
|
|
|
|
## Handling disconnects
|
|
|
|
If the publisher exits or crashes while you are looping, the next `cuframes_subscriber_next()` returns `CUFRAMES_ERR_DISCONNECTED`. The handle is then dead — destroy it and (optionally) reconnect:
|
|
|
|
```c
|
|
if (r == CUFRAMES_ERR_DISCONNECTED) {
|
|
cuframes_subscriber_destroy(sub);
|
|
sub = NULL;
|
|
/* sleep + retry cuframes_subscriber_create(&cfg, &sub) */
|
|
}
|
|
```
|
|
|
|
A reconnect pattern, including back-off and `consumer_name` reuse caveats, is : detect `CUFRAMES_ERR_DISCONNECTED`, call `cuframes_subscriber_destroy()`, back off (1-2 sec), and `cuframes_subscriber_create()` again with the same key. The FFmpeg `cuframes://` demuxer does this automatically (see [Integration → FFmpeg demuxer](/docs/integration/ffmpeg-demuxer)).
|