nvnmos Rust Workspace

August 17, 2026 · View on GitHub

Rust components for the NMOS daemon and GStreamer plugin family.

DocContents
This file — Quick startBuild, run nvnmosd, try two example GStreamer pipelines
Daemon guide and gRPC API referenceMinimal client sequence, daemon operation, and generated API reference
GStreamer plugin documentationUsage guide, element reference, and property groups
GStreamer pipeline examplesFull pipeline catalog (MXL, flipper, demo script, …)
Daemon design recordArchitecture, element integration, and historical rationale
How to contributeContribution guidelines (sign-off, DCO)
How to report a vulnerabilitySecurity vulnerability reporting

Quick Start

Three terminals on Linux: build once, start the daemon, then run a sender and receiver. The example scripts use RTP/UDP (transport=udp, ST 2110-style multicast) with auto-activate=true, so video starts without an NMOS Controller or IS-05 PATCH step.

Tested on Ubuntu 24.04 (same as CI).

Prerequisites

System packages — C++ compiler, CMake, Python, libclang (bindgen for nvnmos-sys), and GStreamer headers plus runtime plugins for the example pipelines:

sudo apt-get update
sudo apt-get install --no-install-recommends -y \
  build-essential cmake python3 python3-venv \
  clang \
  libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
  gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
  iproute2 curl jq

iproute2 (for ss), curl, and jq are only required by the example pipeline scripts and the interactive demo.

CMake must be 3.17+. Ubuntu 24.04's cmake package is sufficient; on older distros use pip install cmake~=3.17 inside the venv below (see building.md).

Conan — resolves and builds C++ dependencies for libnvnmos. Use a Python venv at the repository root so Conan stays off the system Python (building.md):

# repository root
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install conan~=2.2
conan profile detect    # creates ~/.conan2/profiles/default

Keep the venv activated while running conan install in the next section.

Rust — install rustup if cargo is not on your PATH. Building inside rust/ picks up rust-toolchain.toml.

Build libnvnmos.so

From the repository root (full detail in building.md):

conan install src \
  --settings:all build_type=Release \
  --build=missing \
  --output-folder=src/conan \
  --lockfile=src/conan.lock

cmake -B build \
  -DCMAKE_TOOLCHAIN_FILE=conan/conan_toolchain.cmake \
  -DCMAKE_BUILD_TYPE=Release \
  src

cmake --build build --parallel

Point the Rust build at the shared library:

export NVNMOS_LIB_DIR=$PWD/build
export LD_LIBRARY_PATH=$NVNMOS_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}

Build nvnmosd and the GStreamer plugin

cd rust
cargo build -p nvnmosd -p gst-nmos-rs
export GST_PLUGIN_PATH=$PWD/target/debug

Start the daemon (terminal 1)

export NVNMOS_LIB_DIR=/path/to/nvnmos/build
export LD_LIBRARY_PATH=$NVNMOS_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}

cd rust
cargo run --bin nvnmosd

Run the sender pipeline (terminal 2)

After the daemon is running, set the same library and plugin paths as above, then start the sender:

export NVNMOS_LIB_DIR=/path/to/nvnmos/build
export LD_LIBRARY_PATH=$NVNMOS_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
export GST_PLUGIN_PATH=/path/to/nvnmos/rust/target/debug

cd rust/gst-nmos-rs
# Optional: pick the NIC that carries multicast (defaults to first non-loopback).
# export DEMO_NIC_IP=203.0.113.1
./scripts/example-pipelines/1080p25-sender-udp.sh

This runs videotestsrcnmossink (with transport=udp), registers an NMOS Sender, and publishes 1080p25 test video to the default multicast group (see scripts/env.sh).

Run the receiver pipeline (terminal 3)

Start the sender first, then (same env vars as terminal 2):

export NVNMOS_LIB_DIR=/path/to/nvnmos/build
export LD_LIBRARY_PATH=$NVNMOS_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
export GST_PLUGIN_PATH=/path/to/nvnmos/rust/target/debug

cd rust/gst-nmos-rs
./scripts/example-pipelines/1080p25-receiver-udp.sh

This runs nmossrc (transport=udp) → videoconvertautovideosink, subscribing to the same multicast group. You should see SMPTE color bars when both pipelines are playing.

Troubleshooting: example scripts call require_nvnmosd and exit if /tmp/nvnmosd.sock is not listening (default daemon socket; override with --uds or NVNMOSD_UDS — see nvnmosd/README.md). Use RUST_LOG=info on the daemon for verbose logs. Headless hosts: set DEMO_VIDEO_SINK=fakesink before the receiver script.

Next steps: The interactive lab with IS-05 menu actions is gst-nmos-rs/scripts/gst-nmos-rs-demo.sh. For MXL (transport=mxl), build the MXL SDK first, then see pipeline-examples.md for example pipelines. For compliant ST 2110 (transport=nvdsudp) using DeepStream and Rivermax SDK, see gst-nmos-rs/pipeline-examples.md.

Crates

CrateKindPurpose
nvnmos-syslibrarybindgen-generated FFI to the C libnvnmos API in ../src/nvnmos.h.
nvnmoslibrarySafe Rust wrapper over nvnmos-sys: RAII NodeServer, Result errors, deterministic id accessors.
nvnmos-rpclibrarygRPC protocol crate (nvnmosd.proto + tonic-generated stubs).
nvnmosdbinaryNMOS daemon — see nvnmosd/README.md.
nvnmosd-examplebinaryExample/regression client modelled on the C nvnmos-example.
nvnmosd-benchbinaryScale smoke / benchmark client for nvnmosd.
gst-nmos-rsGStreamer plugin (cdylib)nmos plugin — nmossrc / nmossink elements. See gst-nmos-rs/README.md.
gst-avsynctest-rsGStreamer plugin (cdylib)avsynctest plugin — avsyncvideotestsrc / avsyncaudiotestsrc phase-locked A/V test sources (with a public analyze module) used by the gst-nmos-rs sync test.

Container Image

For a combined image with nvnmosd + gst-nmos-rs + plugins for transport=mxl and transport=udp/udp2, suitable for gst-launch-1.0 in Kubernetes or Docker, see docker/gst-nmos-rs/README.md.

Building

The workspace MSRV is Rust 1.87 (rust-version in Cargo.toml). Development, CI, and container builds pin 1.92 in rust-toolchain.toml — the minimum needed for gst-plugins-rs 0.15.2 in docker/gst-nmos-rs.

nvnmos-sys links against a pre-built libnvnmos.so. The Rust crate does not build the C library itself; use the CMake/Conan flow under ../src/ (Quick start — Build libnvnmos.so), then:

export NVNMOS_LIB_DIR=/absolute/path/to/build     # contains libnvnmos.so
cargo build --workspace

NVNMOS_LIB_DIR is optional when libnvnmos.so is installed on the system linker path.

NVNMOS_INCLUDE_DIR defaults to ../src/ (where nvnmos.h lives in-tree). Override only to bindgen against a different header location.

protoc is vendored via protobuf-src — no system protoc is required.

Scale Benchmark (nvnmosd-bench)

Measures daemon memory usage and RPC/HTTP latencies across preset scenarios. See doc/designs/nvnmosd/scale-smoke.md.

# Build libnvnmos first (../build/libnvnmos.so), then:
export NVNMOS_LIB_DIR=/absolute/path/to/build
./rust/nvnmosd-bench/scripts/run-nvnmosd-scale-smoke.sh              # default: small preset
PRESETS="medium large" ./rust/nvnmosd-bench/scripts/run-nvnmosd-scale-smoke.sh

Results land in rust/nvnmosd-bench/results/ (gitignored).

Running the Smoke Test

RPC-only regression client — no GStreamer. For a full media walk-through, use Quick start instead.

# Terminal 1: daemon (with NVNMOS_LIB_DIR / LD_LIBRARY_PATH set)
cargo run --bin nvnmosd

# Terminal 2: example client
cargo run --bin nvnmosd-example

The example exercises every RPC the daemon currently implements: both Node lifetimes (session-refcounted and persistent), session attachment/refcounting, resource registration with namex-nvnmos-name mismatch detection, the activations stream (SubscribeActivations opened for the resource phase with a background auto-ack task for AckActivation), out-of-band SyncResourceState (activate with an updated transport file + deactivate), an in-band IS-05 PATCH activate/deactivate round-trip against libnvnmos's HTTP server, resource removal, and session-close-time resource cleanup. The resource phase deliberately registers a Sender and a Receiver under the same name to exercise the side-disambiguated namespace: the daemon's by_name index is keyed on (node_seed, side, name) and ActivationEvent.side plus the safe wrapper's Side enum let the client tell apart activations that share a name. Successful output is visible in both terminals via the tracing log.

By default the example autodetects a local interface IP via the routing table. Override with --interface-ip <ip> if the autodetect picks the wrong one (or fails on a sandboxed network).

The example sets NodeConfig.http_port to a fixed value (8010 by default, configurable via --http-port); libnvnmos collapses every HTTP API (Node, Connection, ...) onto that single port, so the in-band Connection API PATCH round-trip also targets it: http://<interface-ip>:8010/x-nmos/connection/v1.1/single/senders/<sender_resource_id>/staged. Pass --skip-connection to disable the round-trip step when libnvnmos's HTTP server isn't reachable from the example.

To poke libnvnmos manually after the automated flow finishes, run with --hold-secs N (e.g. --hold-secs 30); the example will hold the resource-phase session open for N seconds with the receiver still registered and the activations stream still attached, so a curl -X PATCH against the receiver's IS-05 endpoint will round-trip through SubscribeActivations / AckActivation exactly like the automated step did for the sender.