The Networked Media Open Specifications (NMOS) enable the registration, discovery and management of Media Nodes.
The NVIDIA NMOS control plane library, NvNmos, provides the APIs to create, destroy and internally manage an NMOS Node for a Media Node application. It is intended to be integrated with an ST 2110 data plane library such as NVIDIA Rivermax or NVIDIA DeepStream.
The library can automatically discover and register with an NMOS Registry on the network using the AMWA IS-04 Registration API.
The library provides callbacks for NMOS events such as AMWA IS-05 Connection API requests from an NMOS Controller. These callbacks can be used to update running DeepStream pipelines with new transport parameters, for example.
NvNmos currently supports Senders and Receivers for video, audio, and ancillary data flows over RTP (i.e., SMPTE ST 2110-20, -22, -30, and -40 streams) and over the Media eXchange Layer (MXL).
The NvNmos library supports the following specifications, using the Sony nmos-cpp implementation internally:
- AMWA IS-04 NMOS Discovery and Registration Specification v1.3
- AMWA IS-05 NMOS Device Connection Management Specification v1.1 and v1.2-dev (for MXL)
- AMWA IS-09 NMOS System Parameters Specification v1.0
- AMWA BCP-002-01 Natural Grouping of NMOS Resources v1.0
- AMWA BCP-002-02 NMOS Asset Distinguishing Information v1.0
- AMWA BCP-004-01 NMOS Receiver Capabilities v1.0
- AMWA BCP-006-01 NMOS With JPEG XS v1.0
- AMWA BCP-007-03 NMOS With MXL v1.0-dev
- Session Description Protocol conforming to SMPTE ST 2110-20, -22, -30, -40, and ST 2022-7
- MXL flow definition JSON as consumed by the MXL SDK
The library is intended to be portable to different environments. The following operating systems and compilers have been tested.
- Ubuntu 24.04 with GCC 13
- Windows 10 with Visual Studio 2022
NvNmos consists of a single shared library (libnvnmos.so on Linux, nvnmos.dll on Windows). The API is specified by the nvnmos.h header file.
The nvnmos-example application demonstrates use of the library.
Each NvNmosSenderConfig and NvNmosReceiverConfig includes a transport field (an NvNmosTransport enum) that selects the transport. A zero-initialised configuration defaults to RTP. The transport_file field then holds the transport file as the appropriate text:
transport |
transport_file format |
Reference |
|---|---|---|
NVNMOS_TRANSPORT_RTP |
Session Description Protocol (SDP) per SMPTE ST 2110 / IETF RFCs | RFC 4566 |
NVNMOS_TRANSPORT_MXL |
MXL flow definition (JSON) as consumed by the MXL SDK | MXL SDK |
NvNmos uses a small set of extensions in the transport file to convey configuration that the standard transport file format does not carry. The same conceptual extensions are carried differently in the two transport file formats:
- For RTP (SDP), as custom
a=x-nvnmos-*:<value>attributes. - For MXL flow definitions (JSON), as entries in the standard
tagsproperty keyed byurn:x-nvnmos:tag:*URN strings. The tag's value is an array of strings; the first element is used.
| Concept | SDP attribute (RTP) | MXL flow_def tag key (MXL) | Applies to | Description |
|---|---|---|---|---|
| Name | a=x-nvnmos-name:<v> |
urn:x-nvnmos:tag:name |
Senders and Receivers (required) | The application's caller-chosen name for the Sender or Receiver, unique within the Node for the given side (Sender or Receiver). A Sender and a Receiver may share the same name. Used in all NvNmos API callbacks (paired with the NvNmosSide) |
| Group hint | a=x-nvnmos-group-hint:<v> |
standard urn:x-nmos:tag:grouphint/v1.0 |
Senders and Receivers (optional) | A group hint tag advertised via urn:x-nmos:tag:grouphint/v1.0 on the NMOS resource |
| Suppress narrow Receiver Caps | a=x-nvnmos-caps:<v> (media-level) |
urn:x-nvnmos:tag:caps |
Receivers (optional) | An empty string value selects a fully-flexible Receiver, with format-derived Capabilities omitted. Non-empty strings are reserved for future capability; today any value is treated the same. |
| Interface IP | a=x-nvnmos-iface-ip:<v> |
n/a | Senders and Receivers (RTP only) | The interface IP address used for IS-05 transport parameters (source_ip / interface_ip) |
| Interface metadata | a=x-nvnmos-iface:<name> [<chassis-id>] <port-id> [<attached-chassis-id> <attached-port-id>] |
n/a | Senders and Receivers (RTP only) | IS-04 nmos::node_interface fields for interface_bindings and Node interfaces; used when present in the transport file, otherwise the library derives the binding from host interfaces (design) |
| Source port | a=x-nvnmos-src-port:<v> |
n/a | Senders (RTP only) | The source port from which the stream is transmitted |
| MXL domain id | n/a | urn:x-nvnmos:tag:mxl-domain-id |
Senders and Receivers (MXL only, required) | The MXL domain identity (UUID) for the Sender or Receiver; the IS-05 mxl_domain_id transport parameter defaults to "auto" and is resolved at activation time from this value |
For an MXL flow definition, the tag entries are stored alongside (and follow the same shape as) the standard urn:x-nmos:tag:grouphint/v1.0 tag, e.g.:
"tags": {
"urn:x-nmos:tag:grouphint/v1.0": [ "video-sender-1:Video" ],
"urn:x-nvnmos:tag:name": [ "video-sender-1" ],
"urn:x-nvnmos:tag:mxl-domain-id": [ "1ac254d9-c9be-475a-93a7-f80b9c1063a8" ]
}NvNmos also publishes the urn:x-nvnmos:tag:name tag on the corresponding NMOS resources (visible to controllers via IS-04), so the URN is shared between the two artifacts.
For MXL Senders, the top-level id field of the flow definition (if present, a UUID) is used as the MXL flow identity (i.e. the mxl_flow_id IS-05 transport parameter); if absent, the generated NMOS Flow id is used in its place. The NMOS Flow id itself is always derived from the seed and the name (urn:x-nvnmos:tag:name value) and is independent of the flow definition's id field. For MXL Receivers, the MXL flow identity is supplied dynamically through IS-05 Connection Management, so the id field of the flow definition is ignored.
When an IS-05 Connection API activation occurs, the library invokes the application's connection_activated callback with an NvNmosSide (Sender or Receiver), the application's name, and an updated transport_file reflecting the new active transport parameters. For an RTP Sender or Receiver, the callback receives an SDP file; for an MXL Sender or Receiver, the callback receives an MXL flow definition (JSON) with the new active mxl_domain_id and mxl_flow_id spliced in (as the urn:x-nvnmos:tag:mxl-domain-id tag value and the top-level id field, respectively). The application is expected to dispatch on (side, name) to identify the Sender or Receiver and react accordingly (for example, by reconfiguring its data plane). Conversely, if an activation (or deactivation) has already occurred in the application's data plane by some other means, outside the NMOS API, the application calls nmos_connection_activate (also passing the side) to update the IS-04 and IS-05 model to reflect it. The library does not initiate any activation on the application's behalf.
Existing application code needs to be updated as itemised below - mostly possible with search-and-replace.
NvNmosSenderConfig::sdpandNvNmosReceiverConfig::sdp(const char *) are nowNvNmosSenderConfig::transport_fileandNvNmosReceiverConfig::transport_file. A new siblingNvNmosTransport transportfield selects the format:NVNMOS_TRANSPORT_RTPfor SDP (the zero-initialised default),NVNMOS_TRANSPORT_MXLfor an MXL flow definition (JSON).- The SDP identity attribute
a=x-nvnmos-id:<id>is nowa=x-nvnmos-name:<name>β renamed for clarity, since its value has always been the caller-chosen name (unique within the Node), not a UUID. The NMOS resource UUIDs are generated deterministically by the library fromNvNmosNodeConfig::seed+ name and are now also exposed via the new ID accessors below. remove_nmos_sender_from_node_serverandremove_nmos_receiver_from_node_servernow take a caller-chosen name (sender_name/receiver_name) instead of a UUID.
- The IS-05 activation callback typedef
nmos_connection_rtp_activation_callbackis nownmos_connection_activation_callback, and its signature changed from(server, id, sdp)to(server, side, name, transport_file). The matchingNvNmosNodeConfig::rtp_connection_activatedfield is nowconnection_activated. The newNvNmosSideparameter (NVNMOS_SIDE_SENDER/NVNMOS_SIDE_RECEIVER) disambiguates a name that may now be shared between a Sender and a Receiver on the same Node β names are scoped per side. nmos_connection_rtp_activate(server, id, sdp)is nownmos_connection_activate(server, side, name, transport_file).
The NMOS resource UUIDs are deterministic pure functions of (seed, side, name). Three pure accessors compute them without a server:
nmos_make_node_id(seed, out, out_len)nmos_make_sender_id(seed, sender_name, out, out_len)nmos_make_receiver_id(seed, receiver_name, out, out_len)
Three live accessors look them up on a running server:
nmos_get_node_id(server, out, out_len)nmos_get_sender_id(server, sender_name, out, out_len)nmos_get_receiver_id(server, receiver_name, out, out_len)
All write a null-terminated UUID into a buffer of at least NVNMOS_ID_LEN bytes (37, including the terminator). Each returns bool.
For NVNMOS_TRANSPORT_MXL the transport file is an MXL flow definition JSON (the form consumed by the MXL SDK), with NvNmos extensions carried as entries in the standard tags property keyed by urn:x-nvnmos:tag:* URN strings. See NvNmos Extensions to the Transport File above for the full set.
For the full per-field documentation see src/nvnmos.h.
On IS-05 activation, "auto" values for RTP Senders are now resolved based on the config SDP:
source_ipis resolved to the SDPa=x-nvnmos-iface-ip:ora=source-filter:source address as before.destination_ipis resolved to the SDPc=connection address if not0.0.0.0; otherwise, a source-specific multicast address is generated as before.destination_portis resolved to the SDPm=port if non-zero; otherwise, the IS-05 default (5004) is used as before.source_portis resolved to thea=x-nvnmos-src-port:port if present; otherwise, the IS-05 default (5004) is used as before.
A Dockerfile is provided which builds, packages and tests the library and application from source.
docker build -t nvnmos .The package can then be copied directly to the host system.
docker create --name nvnmos-test nvnmos
docker cp nvnmos-test:/nvnmos-ubuntu-24.04.tar.gz .
docker rm nvnmos-testThe runtime image includes dbus, avahi-daemon, and avahi-utils. entrypoint.sh (and entrypoint-setup.sh) start D-Bus and Avahi without systemd or chroot, run the C nvnmos-example and the Rust nvnmosd / nvnmosd-example pair, then exec your command.
docker run -it nvnmos /bin/bashThe following build arguments are available.
| Argument | Explanation |
|---|---|
| BASE_IMAGE | Controls the base container image and therefore the compatibility of the created package. Default is ubuntu:24.04. |
| PACKAGE_SUFFIX | Controls the package filename, which will be nvnmos<suffix>.tar.gz. Default is based on the base image, e.g. -ubuntu-24.04. |
| NMOS_CPP_REF | Git commit of sony/nmos-cpp to build via add_subdirectory. Default is the same hash pinned in .github/workflows/ci.yml. |
The image clones that nmos-cpp revision, runs conan install with nmos_cpp_from_source=True (no input lockfile), configures with -DUSE_ADD_SUBDIRECTORY=ON, and writes a fresh conan.lock into the package tarball from the resolved dependency graph. Also builds the Rust workspace (nvnmosd, gst-nmos-rs, β¦) against the built libnvnmos.so.
If this isn't sufficient for your purposes, read on for manual build instructions.
Having Python 3 isn't an absolute requirement but it makes the subsequent steps to install the dependencies easier.
Linux
Use the system package manager to install Python 3 and the venv module (on Debian and Ubuntu the package is python3-venv). A system-wide python3-pip install is optional if you follow the recommended virtual environment below: the virtual environment gets its own pip, which avoids touching the distribution's managed environment (PEP 668).
π¬ Note: The
-yoption allowsapt installto run non-interactively.
sudo apt install -y python3 python3-venvWindows
Download the Python 3 installer from python.org and run it manually, or use the following PowerShell commands. Python 3.14.4 (64-bit Windows, latest stable release at the time) has been tested.
π¬ Note: The
`is the PowerShell line continuation character.
Invoke-WebRequest `
https://www.python.org/ftp/python/3.14.4/python-3.14.4-amd64.exe `
-OutFile python-3.14.4-amd64.exe
./python-3.14.4-amd64.exe /quiet PrependPath=1 Include_tcltk=0 Include_test=0Confirm this Python is available on the PATH. Try closing and reopening the terminal if not.
python --versionFor manual builds, a virtual environment in the repository root (for example, .venv) is recommended. It isolates the Python environment used for Conan and other pip-installed build tools from the system interpreter, pins compatible tool versions on PATH, and on Linux avoids PEP 668 "externally managed" errors when using the system Python.
Linux
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pipWindows
From PowerShell, use the following commands.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
# If PowerShell reports that "running scripts is disabled on this system"
# you can adjust the execution policy as follows and try again.
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
python -m pip install --upgrade pipOr use Command Prompt instead.
python -m venv .venv
.\.venv\Scripts\activate.bat
python -m pip install --upgrade pipTo exit the virtual environment later, run deactivate (same on Linux and Windows).
The project requires CMake 3.17 or higher; dependencies may require a higher version.
There are x86_64 and arm64 packages for CMake 3.30.0 on the Python Package Index (PyPI) which have been tested.
Linux
pip install cmake~=3.17Windows
pip install cmake~=3.17Using Conan simplifies fetching, building, and installing the required C++ dependencies from Conan Center.
The project requires Conan 2.2 or higher; dependencies may require a higher version. Conan 2.28.0 (latest release at the time) has been tested.
Linux
pip install conan~=2.2 --upgrade
conan profile detectThe detected profile is displayed, along with some WARN messages.
For example, the following profile has been tested.
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu17
compiler.libcxx=libstdc++11
compiler.version=13
os=LinuxWindows
pip install conan~=2.2 --upgrade
conan profile detectThe detected profile is displayed, along with some WARN messages.
For example, the following profile has been tested.
[settings]
arch=x86_64
build_type=Release
compiler=msvc
compiler.cppstd=14
compiler.runtime=dynamic
compiler.version=193
os=WindowsLinux
Prepare a build directory adjacent to the src directory.
mkdir buildTo install the dependencies using Conan, use the following command.
π¬ Note: Replace
<Release-or-Debug>with the necessary value. Passing--lockfile=src/conan.lockpins dependency versions per src/conan.lock (Conan Centernmos-cpppackage graph). Do not pass-g CMakeToolchainor-g CMakeDeps;src/conanfile.pyalready generates them, and Conan fails if they are duplicated (for example when copying older command lines).
conan install src \
--settings:all build_type=<Release-or-Debug> \
--build=missing \
--output-folder=src/conan \
--lockfile=src/conan.lockUse the following CMake command to configure the build.
π¬ Note: Replace
<Release-or-Debug>with the necessary value. TheCMAKE_TOOLCHAIN_FILEpath is resolved relative to the top-level src directory passed as the last argument.
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=conan/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE=<Release-or-Debug> \
srcBuild the library and example application.
cmake --build build --parallelWindows
Prepare a build directory adjacent to the src directory.
mkdir buildTo install the dependencies using Conan, use the following command.
π¬ Note: The
`is the PowerShell line continuation character. In the Windows command prompt, use^instead. Replace<Release-or-Debug>with the necessary value. Passing--lockfile=src/conan.lockpins dependency versions per src/conan.lock (Conan Centernmos-cpppackage graph). Do not pass-g CMakeToolchainor-g CMakeDeps;src/conanfile.pyalready generates them, and Conan fails if they are duplicated (for example when copying older command lines).
conan install src `
--settings:all build_type=<Release-or-Debug> `
--build=missing `
--output-folder=src/conan `
--lockfile=src/conan.lockRepeat the command for both Debug and Release if required.
Use the following CMake command to configure the build.
cmake -B build `
-G "Visual Studio 17 2022" `
-DCMAKE_TOOLCHAIN_FILE="conan/conan_toolchain.cmake" `
-DCMAKE_CONFIGURATION_TYPES="Debug;Release" `
srcBuild the library and application with the following command or manually using the generated Visual Studio solution.
π¬ Note: Replace
<Release-or-Debug>with the necessary value.
cmake --build build --config <Release-or-Debug> --parallelTo build against a clone of nmos-cpp while using Conan to resolve Boost, cpprestsdk, Avahi or mDNSResponder, and other dependencies:
-
Clone it into a directory
nmos-cppin the same parent directory asnvnmos. -
Install Conan dependencies with the consumer option
nmos_cpp_from_source=Trueand without the default lockfile, which is for the packagednmos-cppgraph:Linux
conan install src \ --settings:all build_type=<Release-or-Debug> \ --build=missing \ --output-folder=src/conan \ --lockfile="" \ -o "&:nmos_cpp_from_source=True"
Windows
conan install src ` --settings:all build_type=<Release-or-Debug> ` --build=missing ` --output-folder=src/conan ` --lockfile="" ` -o "&:nmos_cpp_from_source=True"
π¬ Note: With
--lockfile="", versions of dependencies such as Boost and OpenSSL are not pinned. You may choose to create a lockfile fornmos_cpp_from_source=Trueinstalls (conan lock createper profile, thenconan lock merge), then pass--lockfile=<path>onconan install. -
Configure and build as in Building the NvNmos Library above, adding
-DUSE_ADD_SUBDIRECTORY=ONto thecmakeconfigure step. (If you followed step 1, you do not need-DNMOS_CPP_DIRECTORY=<path-to-nmos-cpp/Development>.)
Linux
Install and run the Avahi Daemon.
apt update
apt install -y dbus avahi-daemon
/etc/init.d/dbus start
/etc/init.d/avahi-daemon start㪠Note: Since Ubuntu 24.04, an init script is not provided for the Avahi daemon; run
avahi-daemon --daemonizeinstead.
Windows
Install and start the Bonjour Service.
See Download Bonjour Print Services for Windows v2.0.2.
Run the nvnmos-example app specifying host name and port, optionally an interface IP, and optionally a log level.
For example:
nvnmos-example nmos-api.local 8080 192.0.2.0
nvnmos-example nmos-api.local 8080
nvnmos-example nmos-api.local 8080 0The host name can be a .local name, in which case the Node will attempt to discover a Registry being advertised via multicast DNS-SD (mDNS). When a fully-qualified domain name is specified, e.g. "api.example.com", the NMOS Node will instead use unicast DNS-SD discovery in the relevant domain, e.g. "example.com".
The port is used to serve the HTTP APIs.
The IP address identifies the local interface to be used for the mock RTP Senders and Receivers.
When omitted, the example uses documentation addresses and also emits a=x-nvnmos-iface interface metadata to populate Node interfaces.
The log level ranges between -40 (most verbose) and 40 (least verbose), as per the NvNmos API. Values greater than zero are warnings and errors. Values less than zero are debugging or trace messages.
The nvnmos-example app runs through the following steps which are output independent of the log level:
Creating NvNmos server...
Removing some senders and receivers...
Adding back some senders and receivers...
Activating senders and receivers...
Deactivating senders and receivers...
Destroying NvNmos server...
Finished
After each step, the app prompts before moving on to the next step:
Continue ([y]/n)?
If the app runs successfully to completion, the process exits with code 0. If any step fails, or the user responds negatively to a prompt, the process exits immediately with code 1.
The example application also creates two MXL Senders and two MXL Receivers (uncompressed video/v210 and audio/float32) and exercises the same add/remove/activate/deactivate cycle for them, alongside the RTP Senders and Receivers.
While the app is running, the IS-04 Node API, the IS-05 Connection API, etc., are available for an NMOS Controller to use. The HTTP APIs can be accessed at:
http://<host-address>:<port>/
When running multiple NMOS Node instances, each process must be configured to use different ports, i.e., with a unique port value.
When the port is already in use, at start-up, the application may show a message like the following:
asio listen error: system:98 (Address already in use)
When using Avahi for DNS-SD, shortly after start-up the following lines may be displayed in the log. They do not indicate a problem and can be ignored.
*** WARNING *** The program 'nvnmos-example' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/blog/projects/avahi-compat.html>
The application may show messages like the following shortly after start-up:
DNSServiceRegister reported error: -65537 while registering advertisement for: nmos-cpp_node_192-168-1-194:12345._nmos-node._tcp
DNSServiceBrowse reported error: -65537
In this case, the NMOS Node will not be able to discover or register with the NMOS Registry.
One reason for these errors is that the DNS-SD daemon/service is not running.
Linux
When using Avahi, check that the avahi-daemon is running.
Windows
When using mDNSResponder/Bonjour, check that the Bonjour Service is running.