Skip to content

C Libraries

The C bindings expose Media over QUIC to C and C++ applications. Built on top of the Rust moq-net crate via FFI, with no Rust toolchain required at link time.

Libraries

libmoq

docs.rs

A C-callable shared and static library exposing the MoQ pub/sub API. Header files are generated by cbindgen and ship alongside the prebuilt binaries.

Features:

  • Static (libmoq.a) and dynamic (libmoq.so / libmoq.dylib / moq.dll) library targets
  • Auto-generated C header (moq.h)
  • No Rust runtime exposed to consumers
  • Works with any toolchain that can link a C library (CMake, Make, Meson, etc.)

Learn more

Installation

From prebuilt releases

Each libmoq-v* release ships a moq-<version>-<target>.tar.gz bundle with the static library, the dynamic library, and the generated header. Supported targets:

  • x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu
  • x86_64-apple-darwin, aarch64-apple-darwin

Download and extract a bundle (here 0.2.0 on Linux x86_64):

bash
ver=0.2.0
target=x86_64-unknown-linux-gnu
curl -fsSL "https://github.com/moq-dev/moq/releases/download/libmoq-v$ver/moq-$ver-$target.tar.gz" \
  | tar xz

That gives you a moq-$ver-$target/ directory containing include/moq.h and lib/ (libmoq.a plus the dynamic library).

Compile against it

Point your compiler at the extracted include/ and lib/ directories and link -lmoq, plus the platform system libraries libmoq needs:

bash
root=moq-$ver-$target

# Linux
cc subscribe.c -I"$root/include" -L"$root/lib" -lmoq -lpthread -ldl -lm -o subscribe

# macOS
cc subscribe.c -I"$root/include" -L"$root/lib" -lmoq \
  -framework CoreFoundation -framework Security -o subscribe

The static library (libmoq.a) links the whole Rust runtime in, so the result has no libmoq runtime dependency. To link the dynamic library instead, keep lib/ on your loader path (LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS) at runtime.

From source

If there's no prebuilt bundle for your target, build it yourself. You'll need a Rust toolchain:

bash
git clone https://github.com/moq-dev/moq
cd moq/rs/libmoq
cargo build --release

libmoq is part of the Cargo workspace, so the build emits to the workspace root target/ (two levels up from rs/libmoq/): ../../target/release/libmoq.a (static) and ../../target/release/libmoq.{so,dylib,dll} (dynamic), with the generated header at ../../target/release/moq.h.

Callback lifetime

Any function that registers a callback (moq_session_connect, moq_origin_announced, moq_consume_catalog, moq_consume_video_ordered, moq_consume_audio_ordered, moq_consume_track, moq_consume_audio_raw) takes a void *user_data pointer that libmoq passes back to every callback invocation. The status code carries the lifecycle:

  • > 0 — a live result you can use: a frame, catalog, or announce ID (or 1 to mean "session connected"). May fire any number of times.
  • 0 — closed cleanly. Terminal.
  • < 0 — closed with an error. Terminal.

Once a callback fires with any non-positive (<= 0) code, libmoq will never invoke it again and never touch user_data again. Release user_data in response to that final callback.

The matching *_close function only requests shutdown: it returns immediately, does not free user_data, and does not cancel the final callback. The terminal callback still fires (on libmoq's internal thread) once the background task stops, and that is the one safe point to free user_data. This means you never have to guess whether an in-flight callback is still running after close, and you don't need an external weak-reference or refcount around user_data.

Because the terminal callback runs on libmoq's thread, bindings that own thread-affine objects (e.g. a Qt QObject) should hop to the owning thread to perform the actual destruction; the user_data lifetime contract holds regardless of which thread tears the object down.

Error handling

Functions return a negative code on failure (0 or a positive handle on success). The code identifies the kind of failure, but the human-readable reason is available separately via moq_error():

c
int rc = moq_consume_catalog_close(catalog);
if (rc < 0) {
    fprintf(stderr, "close failed: %s\n", moq_error());
}

moq_error() returns the reason for the most recent failed call on the calling thread, including detail the numeric code can't carry (which URL failed to parse, why a decode failed, etc.). The returned pointer is valid until the next libmoq call on that thread, so copy it if you need to keep it. It is only meaningful after a call returned a negative code; check the code first. Errors delivered through status callbacks carry their code directly, so read moq_error() from inside the callback if you want the matching reason.

Failed calls are reported only through the return code and moq_error(), not logged. To surface libmoq's internal logs (moq-net / QUIC activity), call moq_log_level("debug") (or "trace", "info", etc.) to install a tracing subscriber.

Use cases

  • C/C++ applications integrating MoQ without a Rust toolchain
  • Bindings for other languages that aren't already covered by Python, Kotlin, Swift, or Go
  • Legacy systems and embedded targets where pulling in Rust at build time is impractical

Source and issues

Licensed under MIT or Apache-2.0