Dedicated NDI receiver / HDMI decoder for Linux appliances.
1.3.0 adds installable plugins, experimental Tessera network audio,
per-output audio routing and delays, recoverable management/media VLAN
assignments, and refreshed output and splash controls. It retains backup/restore
and scheduled reboot from the 1.2 beta line. The installer writes
1.3.0 to /etc/ndimon-firmware-version. See the changelog for changes
since the last documented 1.2 beta baseline and the documentation index
for operator guides. Network audio remains experimental.
It takes a live NDI stream off the network, decodes it, and scans it out to HDMI or DisplayPort with DRM/KMS. Audio goes to ALSA. A web UI on port 80 is the day-to-day control surface. The decoder core is C++ because the path is memory-bandwidth limited on ARM boards.
This is a decoder, not an encoder. It does not publish NDI.
Official NDI documentation: docs.ndi.video.
- Changes since 1.2 beta
- Documentation index
- What it does
- Requirements
- Install
- First boot
- Features
- Receive path
- Hardware and codecs
- Services
- Recovery
- Web UI
- HTTP API
- Plugins
- Backup file
- Internal IPC
- Configuration
- Update
- Troubleshooting
- Layout
- Limits
- License
NDIMon-R sits on a monitor or in a rack, remembers the last source, and comes back by itself after a reboot or a dropped sender.
| Incoming stream | What happens |
|---|---|
| Standard NDI (SpeedHQ / UYVY / NV12) | The NDI SDK decodes SpeedHQ. FrameSync pulls video and audio to the local HDMI and ALSA clock. Colour conversion uses ARM NEON. Rockchip VOP2 can scan UYVY natively when rotation is 0. |
| NDI HX (H.264 / H.265) | The Standard SDK decodes HX in software and hands the application uncompressed frames, where the SDK's codec runtime (libavcodec) is available. Hardware HX decode is not available; NDIMon-R does not use the Advanced SDK. |
Each connector gets its own DRM lease, NDI receiver and display thread. With the Standard SDK, both Standard NDI and HX arrive as uncompressed frames and use FrameSync. The retained compressed-frame decoder factory can fall back to FFmpeg, but MPP/V4L2/VAAPI HX decoding is dormant on this SDK path.
- OS: Debian Bookworm/Trixie, Ubuntu 24.04, Armbian, or Raspberry Pi OS (64-bit).
- CPU: aarch64 or x86-64.
- Display: HDMI or DisplayPort. Do not run a desktop session that holds DRM master on that connector.
- Network: Avahi (
avahi-daemon) for mDNS. An NDI Discovery Server is optional, for other subnets and for remote source control. - Privileges: a full install is root (
sudo). The decoder stays root (DRM). The API and finder run as userndimon. - Build: on the target. Do not cross-compile from Windows or macOS.
These are NDIMon's host requirements. Tessera plugin bundles have their own OS/architecture requirements: Debian 13 amd64 or experimental Ubuntu 24.04 arm64. See Tessera setup before installing it.
Installing accepts the NDI SDK License Agreement. The SDK tarball (~60 MB) is downloaded from NDI on the first setup-deps.
On the device:
sudo apt-get update
sudo apt-get install -y git
git clone https://github.com/markfen88/NDIMon-R.git
cd NDIMon-R
sudo bash install.shThat pulls dependencies, the NDI SDK v6 line, a CMake build, binaries, systemd units, and the Node API. When it finishes, open http://<device-ip>/. The address is also drawn on the splash when the output is idle.
| Command | When to use |
|---|---|
sudo bash install.sh |
First install, or after a distro change |
sudo bash install.sh --no-deps |
Code update: rebuild and reinstall, keep packages |
sudo bash install.sh --no-build |
Units and API only; binaries already in build/ |
The top-level install.sh always installs system units under /etc/systemd/system. Config files already in /etc/ are never overwritten.
scripts/setup-deps.sh— build tools, libdrm, ALSA, Avahi, FFmpeg headers, Node.js 20,python3,python3-yaml,iputils-arping, nftables, and the NDI SDK into/usr/local. Rockchip MPP on RK boards. VAAPI drivers on x86 (Intel iHD/i965 and Mesa for AMD).scripts/build.sh—cmake -B buildand compilendimon-randndimon-finder.scripts/install.sh—/usr/local/bin,/opt/ndimon-r/api, userndimon,/usr/local/sbin/ndimon-priv,/usr/local/lib/ndimon/ndimon-net, sudoers,/var/lib/ndimon/.ndi(shared NDI home), enable and start the services.
Optional checksum pin: NDI_SDK_SHA256=<hex> sudo bash install.sh.
CMake switches, set on the device: ENABLE_MPP, ENABLE_V4L2, ENABLE_FFMPEG, ENABLE_VAAPI.
sudo systemctl status ndimon-r ndimon-finder ndimon-api ndimon-watchdog
sudo journalctl -u ndimon-r -f
sudo bash scripts/status.sh- Browse to
http://<device-ip>/. - Log in. The default password is
ndimon. Change it under Settings → Security before the box is on a real network. The UI keeps a warning up until you do. - Open NDI and pick a source. That choice is written to
/etc/ndimon-dec{N}-settings.jsonand survives reboot. - Optional, on NDI: Discovery Server IP, NTP host, groups, extra IPs, transport, and HX decode mode.
- Optional, on Settings → Network: define named untagged/tagged networks, choose management, and review/apply the draft. Select the shared NDI network on NDI and the audio network inside Plugins → Tessera. Keep the change explicitly at the new management address within 60 seconds once it is ready.
- Optional, on System: scheduled reboot, watchdog mode, backup.
- For network audio, install/enable the Tessera plugin, configure its device, flows and channel names, then open each Output → Advanced → Audio to choose inputs, destinations, gains and delays. Saving routes leaves the current audio engine selected; switching to experimental v2 requires a separate confirmation and restarts the decoder. See the setup guide.
Factory watchdog mode is active (reconnect after about 30 s of stall). An existing /etc/ndimon-device-settings.json is not rewritten on update. Change an older box from passive under System → Watchdog Mode.
- Dual-mode NDI: Standard and SDK-decoded HX, both received through FrameSync.
- One source per connector. HDMI-A-1, HDMI-A-2, and DP-1 keep channels 1, 2, and 3. Further connectors take the lowest free channel, up to 8.
- Scale: letterbox, stretch, crop. Rotation follows
/modes.supported_rotations(0; 180 on compatible planes). - HDMI mode auto-matched to the source, or a locked resolution from the UI.
- Default audio engine v1 plays NDI audio through one ALSA owner.
- Opt-in v2 audio engine: multiple inputs and destinations per output, input gains, separate input/destination delays, shared physical sink mixing, drift compensation and memory-capped primary video delay. Routes can send the same mix to HDMI and Tessera together. The default remains v1 pending soak.
- Full-width output cards, Current Connections and Source Presets collapse independently. Each output's blue Advanced control starts collapsed.
- Custom splash and OSD when idle or disconnected.
- Monitor hotplug: the receiver still advertises if no display is present at boot. The picture starts when HDMI lands.
- Live address on the splash, refreshed about every 2 s. A short banner appears while a network change is waiting to be confirmed. The confirmation token is not drawn on HDMI.
- Last source auto-connects after reboot.
- Disconnect keeps the saved source so it can reconnect. Forget source is an explicit UI action (
None). - The API reconnects with exponential backoff (5 s → 30 s). Odd tries use the saved IP; even tries use the name only.
- Watchdog polls
/api/health. Active mode disconnects a stalled output so the API reconnects the saved source. It does not restart the decoder process. ndimon-risType=notifywithWatchdogSec=30andRestart=always.- Optional NTP host writes a timesyncd or chrony drop-in and reapplies on API start. NDI has no time server of its own.
- Optional scheduled reboot (System page): local time and weekdays, as a systemd timer with
Persistent=false. A missed window does not fire on the next boot. - Backup and restore of user settings. Wired DHCP or static, with automatic rollback unless you confirm from the new address.
- Named management, shared NDI and Tessera network assignments on one wired port, with untagged or 802.1Q VLANs, read-only Used by links, deletion guards, and a device-side 60-second Keep/Revert window once activation is ready.
- Installable plugin archives, feed updates, enable/disable/remove controls and plugin backup extensions. Tessera is optional; its runtime lives in its own repo.
- mDNS via Avahi, plus an optional NDI Discovery Server. A blank IP turns it off. There is no separate enable flag.
- Receiver advertiser so the box shows up as a destination.
allow_controllinglets the SDK switch sources. The app does not callconnect()from a routing callback. - NDI groups are case-sensitive (
Productionandproductionare different). Empty becomespublic. - Extra IPv4 addresses for senders off the local subnet.
- Transport: RUDP by default (the NDI SDK’s own default and the one the NDI docs recommend). TCP, Multi-TCP, UDP and Multicast are selectable; each enables exactly one of the SDK's
rudp/tcp/unicast/multicastreceive keys (Multicast also keeps RUDP as its fallback, and needs a multicasting sender). A change rewritesndi-config.v1.jsonand restarts the decoder service, because the SDK only reads transport settings at startup (a few seconds of outage; the saved source reconnects). A savedTCPthat was never chosen in the UI (noRxpm_user_setmarker) is migrated to RUDP once at API start. - Named source presets (up to 32). Recall connects immediately, without a rescan.
- Device name is the OS hostname (
hostnamectl). It is not written into the SDK config asmachinename. That override causes mDNS name clashes.
This is a LAN appliance, not a public service.
- Password is scrypt in
/etc/ndimon-auth.json. Until that file exists the password isndimon. - Settings are atomically replaced by a root helper limited to named appliance files. The API and finder retain their unprivileged accounts. Config files are
root:ndimon, mode0640. - Session cookie (
HttpOnly,SameSite=Lax, 7 days) orAuthorization: Bearer. Sessions live in memory and disappear whenndimon-apirestarts. - Five failed logins per IP per minute returns 429.
- Same-origin only. Mutating requests must send an
Originwhose host matchesHost. CORS is not enabled. - GET does not change state (
connectTo, reboot, finder reset, and similar return 405 on GET). - The API and finder run as
ndimon. Reboot, hostname, NTP, the reboot timer, service restarts, and network changes go through/usr/local/sbin/ndimon-priv. - IPC socket
/tmp/ndi-decoder.sockis mode0660, groupndimon. Do not expose it on the LAN. GET /api/healthis unauthenticated from loopback only, for the watchdog.POST /api/net-confirmis unauthenticated. The token is the credential, because the session cookie belongs to the old address.
NDI network
│
├─ ndimon-finder → /var/lib/ndimon/ndimon-sources.json
│
└─ ndimon-r (DRM master)
NDIReceiver → SDK-decoded frames (Standard or HX)
├─ FrameSync → DRM (+ CPU conversion/native UYVY)
├─ audio v1 → one ALSA output
└─ audio v2 → central graph → HDMI / Tessera PCM
│
optional tesserad
IPC /tmp/ndi-decoder.sock
│
ndimon-api Web UI + REST (port 80)
ndimon-watchdog → GET /api/health
| Standard NDI | NDI HX | |
|---|---|---|
| Payload | SpeedHQ decoded inside the SDK to uncompressed frames | H.264/H.265 decoded inside the Standard SDK to uncompressed frames |
| Capture | FrameSync pull | FrameSync pull after SDK software decode |
| Timing | A/V sync, duplication/drop and silence insertion | Same uncompressed timing path |
| Picture | CPU conversion or supported native UYVY scanout | Same display path; allocating a hardware decoder does not make it active |
| Copy | Application-owned frame planes survive SDK receiver replacement | Same ownership rule |
The build links the Standard SDK and creates v3 receivers in fastest mode. On SDK 6.3.2, NDI Screen Capture HX arrives as uncompressed UYVY: the SDK decodes the H.264/H.265 itself, in software, on the appliance CPU. The old global codec.*.passthrough keys (which exist as strings in libndi 6.3.2) did not enable compressed receive on that SDK and are no longer written; whether any earlier SDK build honoured them is unverified.
Per-output compressed_frame_observed reports whether a compressed frame has
actually reached the application (it stays false on the Standard SDK). Use
decode_backend and hw_decode for the active backend. SDK-decoded UYVY does
not use the compressed-stream saturation detector. HX consumes CPU inside the
SDK, but the historical drop bursts did not establish a decode-speed ceiling;
inspect source continuity and receiver diagnostics when investigating drops.
VideoDecoder::create() reads decode_mode (auto, hardware, software) from device settings.
This table describes the retained compressed-frame path. It does not select hardware HX decoding when the Standard SDK returns uncompressed frames.
| Mode | Behaviour |
|---|---|
software |
FFmpeg only |
auto / hardware |
Rockchip MPP, then Pi 4 V4L2 M2M, then x86 VAAPI when a render node exists and the GPU is not NVIDIA, then FFmpeg |
hardware with no device logs and falls through to software. If hardware init fails on the first HX frame, the worker replaces the decoder with FFmpeg. The NDI page shows an HW / SW badge. SATURATED means an HX stream’s decoded FPS stayed under 85% of the source FPS for 3 seconds. Uncompressed FrameSync never trips that.
| Backend | Where | Scanout |
|---|---|---|
| MPP | RK3588 / RK3399 | NV12 DMA-BUF into the plane |
| V4L2 M2M | Pi 4 H.264 (/dev/video10) |
Exported DMA-BUF, or CPU if export fails |
| VAAPI | Intel iHD/i965, AMD Mesa | DRM PRIME with modifiers. CPU NV12 if export fails |
| FFmpeg | Pi 5, generic ARM, NVIDIA | CPU NV12. One low-latency thread on ARM; up to 16 on x86 |
DRM leases give each connector its own master. Queues hold two frames and drop the oldest. Idle or disconnected outputs draw a splash: a gradient or flat backdrop in the chosen colours, a status pill with a glowing dot, the source count, device name and URL, and the logo. Text is anti-aliased Inter (/usr/local/share/ndimon/fonts, SIL OFL); if the font files are missing the embedded 8x8 bitmap font is used. An OSD string can sit on the live picture in a translucent pill.
In the default v1 path, NDI planar float is downmixed to interleaved S16LE stereo at 48 kHz. Layouts of 5.1 and above use an ITU-R BS.775 mix. The buffer is four periods of 1024 frames. FrameSync requests samples from elapsed time so the HDMI clock paces playback. Experimental v2 uses a central graph and its selected master, with drift compensation for other clocks; see per-output audio.
Per-output status includes total_frames and dropped_frames from the NDI SDK receive counters, stream_type, rotation, and the receiver's live audio_enabled state. Receive counters reset when a receiver is recreated; they do not measure display-side frame loss.
With v1, audio_available identifies the worker with the legacy ALSA writer.
With v2 it can report the shared engine attachment; audio_enabled reports the
receiver's current capture state. Use /v1/Audio/status and endpoint readiness
for the graph, rather than treating this legacy field as physical sink status.
An uncompressed SDK receive path reports decode_backend: ndi-sdk and hw_decode: false; allocating an unused hardware decoder does not count as active hardware decoding.
NDI packed YUV software conversion uses video range and the source-size matrix specified by the NDI SDK (BT.601 SD, BT.709 HD, BT.2020 UHD). Selection uses original dimensions before cropping/scaling. The native UYVY path is used only when the actual scanout plane already reports the required encoding and limited range; otherwise software conversion preserves colour correctness. This can reduce native acceleration on drivers whose plane colour defaults do not match. UHD matrix selection does not add HDMI gamut signalling or HDR tone mapping. NV12/HX metadata handling is unchanged by this fix.
Rotation depends on the renderer and selected DRM plane. The modes API exposes the angles this renderer supports, rather than the raw driver enum. Quarter turns (90/270) are unavailable until the renderer supports a swapped logical canvas. Rockchip Cluster planes and unidentified Rockchip planes expose only 0 degrees: their linear framebuffers cannot safely use the AFBC transform path, even when a BSP kernel accepts TEST_ONLY. Other planes may offer 180 degrees when the driver advertises the required rotation or reflection bits; TEST_ONLY can still reject a format/scaling combination. Unsupported requests return HTTP 400 and preserve the output; busy requests return HTTP 409 without applying later. Unsupported saved angles are reset to the effective angle on startup, reload and hotplug. Software rotation is not implemented. See the Rockchip driver format/rotation check.
Source freshness follows incoming SDK frame-counter progress, so FrameSync duplicates cannot hide a frozen sender. In active watchdog mode a source that stays connected but sends no video for roughly 35 seconds is reconnected; paused/on-change sources may prefer passive or disabled watchdog mode. Receiver counter resets report zero FPS for the reset sample.
| Board | HX H.264 | HX H.265 | Standard NDI |
|---|---|---|---|
| Radxa Rock 5B (RK3588) | SDK software | SDK software | SDK + NEON, or native UYVY |
| Radxa Rock 4C+ / 4B+ (RK3399) | SDK software | SDK software | SDK + NEON, or native UYVY |
| Raspberry Pi 4 / 5 | SDK software | SDK software | SDK + NEON |
| Other aarch64 | SDK software | SDK software | SDK + NEON |
| Intel / AMD / NVIDIA Linux | SDK software | SDK software | SDK + CPU conversion |
HX requires the SDK's compatible codec runtime. These describe the current
receive paths, not certification of every board/image. MPP, V4L2 and VAAPI
implementations remain in the source but are not active HX decoding with the
Standard SDK. Pi HEVC's stateless path and NVIDIA NVDEC are not implemented.
The x86 System page's install-time vainfo report does not prove that NDI is
using hardware decoding.
| Unit | User | Role |
|---|---|---|
ndimon-r |
root | Capture, decode, DRM, ALSA, IPC. Type=notify, WatchdogSec=30, Nice=-10 |
ndimon-finder |
ndimon |
Continuous NDI Find. Rewrites /var/lib/ndimon/ndimon-sources.json when the list changes |
ndimon-api |
ndimon |
Express on port 80, session auth, SSE, reconnect loops |
ndimon-watchdog |
root | Every 15 s: process liveness, then the health policy |
ndimon-net-boot |
root | Oneshot. If a network change was still unconfirmed at boot, roll it back |
ndimon-update |
root | Oneshot, started by the Update button. Not left running |
tesserad |
tessera |
Optional plugin runtime; network audio, flows and its virtual PTP media clock |
Finder and decoder share one NDI config file, /var/lib/ndimon/.ndi/ndi-config.v1.json, selected with NDI_CONFIG_DIR (set in both units and before SDK initialization). Pinning HOME is not enough. Both processes emit the same transport keys from Rxpm. Transport is read at SDK/process startup, so applying a transport change restarts the decoder service; recreating just its receiver is insufficient.
On RK3588 boards with the global ondemand governor interface, the root decoder service reduces sampling_rate to 10 ms while it runs. This shortens CPU demand sampling for bursty decoding work and affects all CPU policies and processes. The governor and frequency limits stay as configured. Stop/crash cleanup restores the saved interval when its applied value is still current; a shorter existing interval or a later operator change is preserved. Other boards, other governors and kernels exposing only per-policy tunables are skipped, with the reason recorded in the service journal.
To disable this tuning, run sudo systemctl edit ndimon-r and add:
[Service]
Environment=NDIMON_CPU_SAMPLING=offThen restart ndimon-r. Before rolling back to older service units, stop the receiver with the current unit so its cleanup restores the interval. If the unit was already replaced, run sudo /usr/bin/python3 -I /usr/local/lib/ndimon/ndimon-cpu-sampling restore, or reboot.
sudo systemctl restart ndimon-r ndimon-finder ndimon-api ndimon-watchdog
sudo journalctl -u ndimon-r -u ndimon-api -fThe C++ health check records state and resets a stuck DRM flip. It does not reconnect by itself. Node.js schedules retries. Active watchdog recovers a stalled output. systemd covers a dead or hung process.
Health is sampled every 500 ms in DisplayWorker::tick(), after a 10 s grace from connect.
| Layer | Trigger | Action |
|---|---|---|
| Display flip reset | Frames arriving, no DRM commit for 3 s | reset_flip_pending() inside the worker |
| API reconnect | IPC says disconnected, and a source is saved | Backoff 5 s, 10 s, 20 s, then 30 s |
| Active watchdog | stall_count ≥ 60 (about 30 s), mode active |
IPC disconnect. The saved source stays. The API connects again |
| systemd notify | No WATCHDOG=1 within 30 s |
Kills ndimon-r. Restart=always brings it back in 3 s |
| Process liveness | ndimon-r, the API, or the finder down for 5 watchdog passes |
systemctl restart, then a 60 s cooldown |
| Signal | Unhealthy when |
|---|---|
| Recv thread heartbeat | Older than 5 s |
| Video frames | None arrived, and the connect is older than 10 s |
| Decoder | Video younger than 2 s, decoded output older than 5 s |
| Display commit | Frames flowing, last commit older than 3 s |
| HX saturation | Decoded FPS under 85% of source FPS for 3 s |
/api/health reports stalled when stall_count is at least 60, degraded for any stall, and idle when not connected.
| Watchdog mode | Behaviour |
|---|---|
active |
Factory default. Disconnects a stalled output |
passive |
Logs counters only |
disabled |
Skips the health poll |
Process restarts still run in every mode.
Single page at / (api/public/index.html). Dark theme, Inter typeface served
from /fonts/. Sidebar pages are Dashboard, Outputs, NDI, Settings, System and
Plugins; phones use a bottom tab bar. Page hashes preserve navigation on reload.
Unsaved edits are marked, and live state comes from EventSource /api/events
about every 1.5 s. The page closes an old stream before reconnecting. Bootstrap
and icons are served locally from /vendor/..., with a CDN fallback.
| Chrome | Behaviour |
|---|---|
| Login overlay | Password field. POST /api/login |
| Top bar | Device name, live count across outputs (or LIVE / IDLE with one output), sources, display mode, live-feed indicator, sign out |
| Password warning | Shown while the password is still ndimon |
| Saturated warning | HX decode FPS under 85% of the source for 3 s |
| Connection warning | The event stream dropped; values may be stale until it reconnects |
Warnings stack in the page flow under the top bar. Destructive actions (reboot, restart, delete, update) confirm in a modal.
One tile per output (LIVE/IDLE, source, input and output resolution, frame rate, codec, HW/SW decode, SATURATED), a system card (CPU, memory, thermals, platform, uptime, audio, frame counters), Rescan sources, Change source, and the source list with a Connect button per source. With more than one output, a selector in the card header picks which output the button connects.
Current Connections is a compact full-width card with output summaries side by side. Source Presets and every full-width output card collapse independently, so one output can stay open while the others are hidden. Within an output, Advanced is bright blue and starts collapsed after a page load; it contains video settings and the audio route editor. Live status does not replace unsaved audio drafts.
| Control | Options | API |
|---|---|---|
| Current connections | Per-output LIVE/IDLE, codec, HW/SW, SATURATED | SSE /api/events |
| Presets | Save, recall, delete | /v1/Presets/* |
| Output name | Free text | POST /v1/DeviceSettings/output-alias |
| NDI source | None, discovered, or manual name and IP. Connect / Disconnect | POST /v1/NDIDecode/connectTo |
| Resolution | Auto, or a mode from the EDID list | POST /v1/VideoOutput/resolution |
| Scale | letterbox, stretch, crop | POST /v1/VideoOutput/scaleMode |
| Rotation | 0; 180 on compatible planes | POST /v1/VideoOutput/rotation |
| Audio inputs | Multiple NDI/Tessera endpoints, each with gain and delay | GET /v1/Audio/endpoints, GET/PUT /v1/Audio/outputs/:ch |
| Audio destinations | Multiple HDMI/Tessera endpoints, each with delay | Same output audio API |
| Output audio enabled | Off retains selections; no active contributions | Same output audio API |
| Audio engine | Explicit v1/v2 confirmation; decoder restarts | GET/POST /v1/Audio/engine |
Open Outputs → an output → Advanced → Audio. Add any available NDI or Tessera inputs and HDMI or Tessera destinations. Each input feeds every selected destination: for example, NDI audio can feed both the TV's HDMI and a Tessera network output. Tessera supplies reusable full-device and named mono endpoints; configure their labels and network flows in its plugin. Two mono selections are mixed together, rather than interpreted as a left/right pair.
Input gain is −60 to +12 dB (−60 mutes). Input and destination delays are whole milliseconds from 0–5000; the sum for an active input/destination pair must fit 5000 ms. These are manual delays. There is no automatic HDMI/Dante latency measurement or per-output video delay.
Save output audio affects that output and preserves other outputs. It does not select v2 or restart the decoder. Explicitly choose Use experimental audio engine v2… to activate saved routes; confirmation restarts the decoder and may briefly interrupt video. Fresh installs default to v1. Return to audio engine v1… restores the legacy path and retains saved choices. Disabling an output retains its choices without contributing audio. Known offline endpoints stay visible and are retried by the control side.
One physical destination has one writer and can mix different receiver sources.
Duplicate active contributions from the same physical source to the same sink,
and overlapping channel writers, are refused. If another browser has saved
audio, reload the saved output before retrying a revision conflict. Resource or
busy errors preserve the previous graph; an explicit restart_required error
may require returning to v1, saving, then selecting v2 again.
See the API contract. Audio v2 remains experimental; sustained clock stability and external hardware interoperability are not certified.
Device-wide receive settings.
| Control | Options | API |
|---|---|---|
| Discovery Server IP | Blank = off | POST /v1/NDIFinder/NDIDisServer |
| NDI groups | Case-sensitive tags | POST /v1/NDIFinder/NdiGrpName |
| Off-subnet IPs | Comma-separated IPv4 | POST /v1/NDIFinder/NdiOffSnSrc |
| Device name | Receiver name. Also sets the hostname | POST /v1/DeviceSettings/ndi-alias |
| Transport | TCP, UDP, Multicast, M-TCP, RUDP | POST /v1/NDIDecode/decodeTransport |
| NDI network | One configured network shared by discovery and every receiver | Complete /v1/Network/profiles/* transaction |
| HX decode | auto, hardware, software | POST /v1/DeviceSettings/decode-mode |
| Tally | TallyOff, TallyOn, VideoMode |
POST /v1/NDIDecode/decodesetup |
| Audio | NDIAudioEn, NDIAudioDis (applies on change) |
same |
Output on connect and preset recall is 1–8. VideoOutput query output is 0-based (channel = index + 1).
Transport, HX decode preference and tally remain global. Independent transport or NDI VLAN selection per output is deferred with receiver-process work. Per-output audio routing is independent of those settings.
Screensaver and colour space are stored and sent in connect metadata. CaptureSS is stored; the core does not freeze a frame for it. TallyMode VideoMode is stored; program tally is applied when the mode is TallyOn.
| Control | API |
|---|---|
| Change password (current, new, confirm, minimum 6 characters) | POST /api/password |
| Named wired networks: untagged/VLAN, DHCP/static, management assignment, read-only usage | GET /v1/Network/profiles, POST /v1/Network/profiles/preview, /apply |
| Splash colours (with one-click palettes), gradient or flat backdrop, logo position, text, visibility, with an in-page preview that follows the controls | POST /v1/Splash/config |
| Logo upload (PNG or JPEG) | POST /v1/Splash/uploadLogo |
| Splash preview | POST /v1/Splash/preview |
| OSD enable and text (max 128 characters) | POST /v1/Splash/osd |
Define networks here and select Management / Control. The NDI page and Tessera plugin select their roles from the same network IDs. The table's read-only Used by links show assignments. An assigned network cannot be removed until a replacement is selected. A blank tag means the switch port's untagged/native network; tags are 1–4094, with at most eight tagged networks on one wired port. This lists appliance configuration, not discovered switch VLANs.
Edits are drafts until Apply network settings. Review the impact, then open the displayed management address and click Keep changes within 60 seconds once activation is ready. All applies use this protection, including media-role changes. Page loads never confirm. Revert, expiry, failure or boot recovery restores the prior settings; timers run independently of the browser/API. Only management supplies gateway/DNS. Wi-Fi is unchanged. See network profiles and VLANs for switch, backend and recovery details.
| Control | API |
|---|---|
| Device information | GET /v1/AboutMe and /api/status |
| GPU decode (x86) | GET /v1/System/vaapi-info |
| Check for updates / Update now | GET /v1/System/version, POST /v1/System/update |
| Backup and restore | /v1/Backup/* |
| NTP server (hostname or IP; blank keeps the OS default) | GET / POST /v1/DeviceSettings/ntp |
| Scheduled reboot | GET / POST /v1/DeviceSettings/reboot-schedule |
| Restart one service | POST /api/services/:name/restart |
| Restart decoder and finder | Restarts those two units. The API and watchdog stay up |
| Reboot | POST /v1/System/reboot |
| Watchdog mode and counters | POST /v1/DeviceSettings/watchdog-mode, GET /api/health, GET /api/watchdog-stats |
System → Backup & Restore downloads one JSON file. Include network settings is on by default. Include admin password is off. Turn the password on only when restoring this same device.
Restore shows where the file came from, then one row per section with a Changed or Unchanged badge. Unchanged sections start unticked. You choose what to import.
Network settings are mentioned when addressing, configured networks or role assignments differ. A profile comparison includes every network and the Management/NDI/Tessera assignments, even when the management IP stays the same. The following table describes older backups containing only primary addressing.
| This device | Backup file | What you see |
|---|---|---|
| DHCP | DHCP | Nothing. Network is left alone |
| Static A | The same static address, prefix, gateway, and DNS | Nothing |
| DHCP | Static B | A dialog naming B, the prefix, gateway, and DNS, and the current DHCP address |
| Static A | Static B | The same dialog, listing the fields that differ |
| Static A | DHCP | A dialog saying the new address will come from DHCP and will show on the display |
| Any | No network section | Nothing |
| Any | Network present, but this manager cannot apply it | An information line. No dialog. dhcpcd is read-only |
Keep current network settings is the default action. Use backup network settings applies the file. If another device already answers ARP for that address, Use stays disabled until you tick the acknowledgement.
Leave Device name unticked when cloning a second unit. It is pre-ticked only when the backup hostname matches this box and the name actually differs. Groups are restored with their original case.
Restored networks and role assignments use one protected profile transaction, with 60 seconds to Keep once activation is ready. Open the management address shown in the dialog and click Keep changes; its transaction token works without the old login cookie. Revert, expiry or an unconfirmed reboot restores the prior configuration. For DHCP the address appears after a lease is acquired; if you lose contact before seeing it, let the device revert.
Audio graphs and enabled plugin backup extensions are included. Restore skips
missing plugins with a warning; it does not install their runtimes. Restoring
Device settings can also restore audio_engine and restart the decoder, so
review that selection before applying a backup from a v2 appliance.
Undo last restore puts settings back to the automatic snapshot taken just before the import, including a network change if that address differs.
Open Plugins to upload a trusted plugin archive or choose a release from a
configured feed. Enter the plugin name, choose its .tar.gz file, and select
Upload and install. Installation restarts the API briefly, then enables the plugin; its tab opens
on the Plugins page. Use Disable, Update, or Remove
to manage it. A failed removal keeps files for Retry remove or Force remove;
force removal skips the plugin's cleanup script. Normal removal keeps configuration.
The installer accepts repeatable options:
sudo bash install.sh --with-plugin hello=/path/to/hello-plugin_1.0.0-1_amd64.tar.gz
sudo bash install.sh --plugin-feed https://forge.example/team/plugins/index.json --with-plugin hello--with-plugin NAME=URL also accepts an HTTP(S) archive URL. A bare name uses
configured plugin_feeds in /etc/ndimon-device-settings.json. Feed downloads
select the appliance architecture and check SHA256; an empty feed list shows
upload only. Install only publishers you trust: plugin scripts run as root.
Backups include enabled plugin settings; restore skips absent plugins with a
warning. The framework installs no tessera packages by itself.
See the plugin authoring guide for manifests, feed formats,
privileged hooks, purge and API details.
For Tessera, use a matching plugin archive containing its precompiled bundle. Installing/enabling the plugin exposes its page; selecting experimental network audio is separate. Configure device/clock settings, static flows, reusable channel names and the Tessera network there. Configure each output's audio mix under Outputs → Advanced → Audio. Tessera setup describes compatibility, ownership and current limitations.
Base URL http://<device-ip>/. Prefer /v1/…. The same routers are also mounted without /v1 for NDIDecode, NDIFinder, VideoOutput, DeviceSettings, System, AboutMe, and NDIEncoder. Splash, Presets, Backup, Network, and ApiSettings are /v1 only.
All /v1/* and /api/* routes need a session except:
POST /api/loginGET /api/auth-statusGET /api/healthfrom loopback onlyPOST /api/net-confirm(the token is the credential)
Login:
curl -c cookies.txt -H 'Content-Type: application/json' \
-d '{"password":"ndimon"}' http://DEVICE/api/loginThen send the cookie, or Authorization: Bearer <token> from the login JSON.
| Method | Path | Body | Response |
|---|---|---|---|
| POST | /api/login |
{ "password" } |
{ ok, token, default_password } and Set-Cookie |
| POST | /api/logout |
— | Clears the session |
| GET | /api/auth-status |
— | { auth_required, logged_in, default_password } |
| POST | /api/password |
{ "current", "password" } |
Password at least 6 characters. Clears other sessions |
| Method | Path | Notes |
|---|---|---|
| GET | /api/status |
UI status: stream, system, sources, decoder, discovery, outputs[] |
| GET | /api/health |
IPC health. 503 if the decoder socket is down |
| GET | /api/watchdog-stats |
Counters from /tmp/ndimon-watchdog-stats.json |
| GET | /api/events |
SSE. First event connected, then type: status about every 1.5 s. At most 20 clients |
| GET | /api/services |
systemd state of the four long-running units |
| POST | /api/services/:name/restart |
ndimon-r, ndimon-finder, ndimon-api, or ndimon-watchdog |
| POST | /api/net-pending |
{ "token" } — pending profile status without an old login cookie |
| POST | /api/net-confirm |
{ "token", "profiles": true } for profile changes; explicit Keep through the new management socket address |
| POST | /api/net-revert |
{ "token" } — revert that profile transaction without an old login cookie |
/api/health includes alive, uptime_s, and outputs[] with output, connected, fps, health (idle, ok, degraded, stalled), stale-age fields, and stall_count.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET | /decodestatus |
— | Reads /etc/ndimon-dec1-status.json |
| GET | /decodesetup |
— | Reads /etc/ndimon-dec1-settings.json |
| POST | /decodesetup |
NDIAudio, ScreenSaverMode, TallyMode, ColorSpace, ChNum |
Writes allowed fields, then reload_config |
| GET | /decodeTransport |
— | { Rxpm } |
| POST | /decodeTransport |
{ "Rxpm" } |
TCP, UDP, Multicast, M-TCP, or RUDP. Restarts the decoder service so the SDK rereads transport |
| POST | /connectTo |
{ "SourceName", "SourceIP", "Output" } |
Output is 1–8. Empty or None forgets the source. Otherwise saves it and connects. Starts a 30 s grace so a Discovery Server route does not immediately undo a local pick |
| GET | /connectTo |
— | 405 |
| GET | /capture |
— | Touches a capture flag and returns text. Not a freeze-frame |
Allowed decodesetup values: NDIAudioEn or NDIAudioDis; BlackSS, SplashSS, or CaptureSS; TallyOn, TallyOff, or VideoMode; ColorSpace YUV or RGB.
| Method | Path | Body | Effect |
|---|---|---|---|
| POST | /refresh |
— | Restarts the finder. Text success |
| GET | /refresh |
— | 405 |
| POST | /reset |
— | Clears the source cache to None |
| GET | /List |
— | { count, list } name → URL |
| GET | /NdiOffSnSrc |
— | Plain text, comma-separated IPv4 |
| POST | /NdiOffSnSrc |
text/plain IP list | Validates and writes /etc/ndi-config.json as a JSON string |
| GET | /NdiGrpName |
— | { ndi_groups } |
| POST | /NdiGrpName |
string or { ndi_groups } |
Case preserved. reload_config |
| GET | /NDIDisServer |
— | IP plus derived NDIDisServEn or NDIDisServDis |
| POST | /NDIDisServer |
{ "NDIDisServIP" } |
Blank disables. reload_config and a finder restart |
output is 0-based.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET | /modes?output=0 |
— | { modes, current, auto_resolution, rotation, supported_rotations } |
| GET | /resolution?output=0 |
— | Current mode |
| POST | /resolution |
{ "auto": true, "output" } or { "width", "height", "refresh_hz", "output" } |
Auto-match, or lock a mode |
| POST | /scaleMode |
{ "scale_mode", "output" } |
letterbox, stretch, or crop |
| POST | /rotation |
{ "degrees", "output" } |
Use supported_rotations from /modes; unsupported angles return 400 |
| Method | Path | Body | Effect |
|---|---|---|---|
| GET, POST | /operationmode |
plain encode or decode |
Stored only. Nothing encodes. Not in the UI |
| GET | /hostname |
— | { hostname } |
| GET, POST | /ndi-alias |
{ "ndi_recv_name" } |
Saves the alias and sets the OS hostname |
| GET, POST | /output-alias |
GET ?ch=1. POST { "ch", "output_alias" } |
Per-output alias |
| GET, POST | /decode-mode |
{ "decode_mode" } |
auto, hardware, or software. Rebuilds the decoder |
| GET, POST | /watchdog-mode |
{ "watchdog_mode" } |
disabled, passive, or active |
| GET, POST | /ntp |
{ "ntp_server" } |
GET also returns synchronized and active_server |
| GET, POST | /reboot-schedule |
{ "enabled", "time": "HH:MM", "days": ["sun", …] } |
systemd timer, Persistent=false |
| Method | Path | Notes |
|---|---|---|
| GET | /endpoints |
Endpoint catalog with labels, channel/rate metadata and readiness |
| GET, PUT | /outputs/:ch |
Stable one-based output 1–8; enabled, inputs, destinations and global revision |
| GET, POST | /engine |
Read engine; explicitly select v1/v2 with confirm:true, restarting the decoder |
| GET, PUT | /settings |
Full audio document; legacy projection is limited to the primary output |
| PATCH | /sources/:id, /sinks/:id |
Legacy endpoint overlay preserving other outputs; ambiguous edits refused |
| GET | /status |
Runtime master, period timing, endpoint readiness, xruns and drift compensation |
Output writes require the current revision; stale writes return 409
revision_conflict. Saving routes never switches the engine. See
the detailed output-audio API for request shapes,
delay limits, resource conflicts and legacy migration behavior.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET, POST | /config |
colours, backdrop (gradient or flat), logo percentages, text, show_* |
logo_path is only an /etc/ndi-splash-logo.* path, or empty. A stored show_box from older versions is ignored |
| POST | /preview |
{ "source_available": true/false } |
Splash on idle outputs |
| POST | /uploadLogo |
{ "data", "filename" } |
PNG or JPEG magic bytes. Writes /etc/ndi-splash-logo.png or .jpg |
| GET, POST | /osd |
{ "enabled", "text" } |
Text max 128 characters |
Splash defaults: idle background #0D1B2A, live background #0D2B1A, idle accent #4488CC, live accent #22FF88, logo at 50/50/50 percent, text “No Signal” / “Signal Available”, text height 4 percent of the screen.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET | /list |
— | { presets: [{ name, source, ip }] }, at most 32 |
| POST | /save |
{ "name", "source", "ip" } |
Upsert by name |
| POST | /delete |
{ "name" } |
Remove |
| POST | /recall |
{ "name", "output" } |
output is 1–8. Saves the source and connects |
| Method | Path | Notes |
|---|---|---|
| GET | /version |
firmware, commit, build_date, ndi_version, update_supported, updates_available |
| POST | /update |
Detached git pull --ff-only and install.sh --no-deps. 409 if one is already running. 501 if this install has no source directory |
| POST | /reboot |
Acknowledges, then reboots |
| POST | /softreboot |
Restarts the stack. Not in the UI |
| GET | /vaapi-info |
{ available, driver, decode_profiles } from the install-time vainfo capture |
| GET | /status |
Raw IPC status. /api/status is the one the UI uses |
| Method | Path | Notes |
|---|---|---|
| GET | /v1/AboutMe/ |
Device name, hostname, IP, firmware, model, NDI version, build date, per-channel info |
| GET, POST | /v1/ApiSettings/ |
Legacy whitelist (GroupName, MACAddress, device_name, host_name). The UI does not call it |
Session required. Inspect accepts an 8 MB JSON body. The global JSON limit stays 4 MB.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET | /export?network=1&password=0 |
— | Downloads ndimon-<alias>-<YYYYMMDD-HHMM>.json. network=0 omits network. password=1 includes the scrypt hash |
| POST | /inspect |
The backup document | Validates schema and checksum. Returns import_id, source device, per-section diffs, and the network comparison (needs_prompt, message, warnings, duplicate) |
| POST | /apply |
{ "import_id", "sections": [], "apply_network": false } |
Writes the ticked sections. Stops the decoder only when a file section is selected. Network is applied last, through the confirm-or-revert path |
| GET | /snapshots |
— | Automatic pre-restore files, newest first. Five are kept |
| POST | /undo |
{ "name" } |
Restores that snapshot. Applies network only when it differs. Undo takes its own snapshot first |
sections may be outputs, presets, discovery, transport, display,
device, identity, auth, audio, plugins. Network is selected separately
with apply_network and restored through a protected profile transaction.
A damaged checksum is refused. A newer schema_version is refused. Unknown keys are refused. Groups are not lowercased. Discovery Server is enabled only when the address is non-empty. The device name uses hostnamectl, never machinename. ndi-config.v1.json is not imported.
One physical wired port hosts named networks and role assignments. Wi-Fi and
switch trunk configuration are outside this API. The full profile document has
version, revision, interface, backend, networks, and network IDs for
management, ndi, tessera. See the profile API.
| Method | Path | Body | Effect |
|---|---|---|---|
| GET | /profiles |
— | Committed profiles, live status, assignments and pending transaction |
| POST | /profiles/preview |
Complete candidate document | Validate revision and impact without changing the OS |
| POST | /profiles/apply |
Complete candidate document | Stage one recoverable change to networks and assignments |
| POST | /profiles/rollback |
— | Revert the pending profile transaction |
| GET | /status |
— | Compatibility status view: backend, configured/live addressing and pending information |
| POST | /check |
{ "address" } |
Duplicate-address probe (arping). Advisory |
| POST | /apply |
{ "mode", "address", "prefix", "gateway", "dns", "dry_run" } |
Compatibility wrapper for management addressing; non-dry-run applies use profile transactions |
| GET | /vlans |
— | Compatibility VLAN status view |
| PUT, DELETE | /vlans/:id |
— | Refused; use a complete profile transaction |
Before activation the helper journals previous settings and arms a 150-second
guard. The apply worker is limited to 90 seconds. Once required addresses and
guards are ready, a separate 60-second Keep window begins. Confirm using
POST /api/net-confirm with { "token": "...", "profiles": true } through
the new management address. Status polls do not confirm. Use
/profiles/rollback or token-scoped /api/net-revert to revert this transaction;
the retained /rollback endpoint belongs to the older primary-network helper.
Supported managers, in detection order: NetworkManager, netplan (networkd renderer), systemd-networkd, ifupdown. dhcpcd is reported and not written. Anything else is unsupported and apply returns 409.
| Path | Response |
|---|---|
/v1/NDIEncoder/encodesetup |
404 { "status": "Command not supported" } |
/v1/NDIEncoder/encodeTransport |
404, same body |
| UI control | HTTP | Then |
|---|---|---|
| Login / logout / password | /api/login, /api/logout, /api/password |
— |
| Live status | GET /api/events |
IPC subscribe and status |
| Refresh sources | POST /v1/NDIFinder/refresh |
Restarts the finder |
| Apply source | POST /v1/NDIDecode/connectTo |
IPC connect |
| Source = None | connectTo with None |
IPC forget_source |
| Recall preset | POST /v1/Presets/recall |
IPC connect |
| Resolution auto / manual | POST /v1/VideoOutput/resolution |
auto_resolution or set_resolution |
| Scale / rotation | /v1/VideoOutput/scaleMode, /rotation |
set_scale_mode, set_rotation |
| Tally, audio, screensaver, colour | POST /v1/NDIDecode/decodesetup |
reload_config |
| Transport | POST /v1/NDIDecode/decodeTransport |
Restart decoder service and reconnect saved source |
| Output audio | PUT /v1/Audio/outputs/:ch |
Validate revision, compile and apply graph; retain selected engine |
| Audio engine | POST /v1/Audio/engine |
Confirm and restart decoder |
| HX decode mode | POST /v1/DeviceSettings/decode-mode |
reload_config |
| Discovery Server | POST /v1/NDIFinder/NDIDisServer |
reload_config and restart the finder |
| Groups | POST /v1/NDIFinder/NdiGrpName |
reload_config |
| Device name | POST /v1/DeviceSettings/ndi-alias |
reload_config and hostnamectl |
| NTP | POST /v1/DeviceSettings/ntp |
ndimon-priv set-ntp |
| Splash / OSD | POST /v1/Splash/config or /osd |
reload_config |
| Splash preview | POST /v1/Splash/preview |
show_splash |
| Watchdog mode | POST /v1/DeviceSettings/watchdog-mode |
The watchdog reads the file |
| Scheduled reboot | POST /v1/DeviceSettings/reboot-schedule |
ndimon-priv set-reboot-schedule |
| Network apply | POST /v1/Network/profiles/apply |
Privileged profile transaction, then explicit /api/net-confirm |
| Update | POST /v1/System/update |
Detached pull and rebuild |
| Reboot | POST /v1/System/reboot |
ndimon-priv reboot |
One UTF-8 JSON document, pretty-printed.
| Field | Meaning |
|---|---|
format |
ndimon-backup |
schema_version |
1. A newer file is refused |
created_at |
ISO 8601 UTC |
app |
Firmware, build commit, NDI SDK version. Display only |
source_device |
Hostname, NDI alias, platform, last three MAC bytes. Display only. Never applied as a MAC |
sections |
The settings below. A section left out of the export is absent |
checksum |
SHA-256 of the canonical JSON of sections (keys sorted, no whitespace) |
| Section | Contents | Files |
|---|---|---|
outputs |
Source name and IP, alias, scale, rotation, mode, audio, tally, screensaver, colour. Channels 1–8 that exist on disk | /etc/ndimon-dec{N}-settings.json |
presets |
Named sources | /etc/ndimon-presets.json |
discovery |
Discovery Server IP, groups, extra IPs | ndimon-find-settings.json, ndi-group.json, ndi-config.json |
transport |
Rxpm |
/etc/ndimon-rx-settings.json |
display |
Splash, OSD, logo as base64 | splash and OSD JSON, /etc/ndi-splash-logo.png or .jpg |
device |
Watchdog mode, decode mode, audio engine, NTP, reboot schedule | device settings, those keys only |
identity |
ndi_recv_name and hostname |
device settings and hostnamectl |
network |
Complete network profiles and Management/NDI/Tessera assignments, plus compatibility addressing fields | Applied together through protected network recovery; revision/interface/backend adapted to this device |
audio |
Audio settings/graph, gains, delays and output choices | /etc/ndimon-audio-settings.json |
plugins |
Enabled plugins' versioned backup extensions | Installed plugin handlers; absent plugins skipped with a warning |
auth |
scrypt salt and hash. Only when the password checkbox was ticked | /etc/ndimon-auth.json |
Not exported: the finder cache, status files, ndi-config.v1.json, build and version files, device_ip, and any other runtime key.
On import the API writes only allow-listed keys, merging into the current file so keys from sections you did not select stay. The logo is checked for PNG or JPEG magic bytes and written to a fixed path. A snapshot is saved under /var/lib/ndimon/backups/ before anything changes. The decoder is stopped only while file sections are written, then started again so it reloads sources, groups, transport, and discovery. NTP and the reboot timer are reapplied. Network is last.
/tmp/ndi-decoder.sock, AF_UNIX SOCK_STREAM, mode 0660, group ndimon. One JSON object per line. The API keeps a subscribe connection and uses short request/response calls (2 s timeout). The watchdog may send disconnect itself.
Do not expose this socket. Use the HTTP API.
| Action | Effect |
|---|---|
subscribe |
Keeps the connection. Replays the last routing and connection state. Pushes connection and routing events |
status / get_status_all / health / get_modes |
Queries |
connect |
source_name, source_ip, output (0-based) |
disconnect |
Drops the picture. Keeps the saved source |
forget_source |
Disconnects and clears the saved source |
reload_config |
Reread /etc JSON and apply it |
set_scale_mode, set_rotation, auto_resolution, set_resolution |
Picture controls. output is 0-based |
set_tally |
tally_program, tally_preview |
show_splash |
source_available. Idle outputs only |
connection events carry output (0-based), connected, source, and drm_ready. routing events carry output, source, and url. The API persists a Discovery Server route. It does not send connect for it.
Worker status includes ch_num, connector, output_alias, connected, source_name, input size, fps, codec, decode_backend, hw_decode, decode_fps, decode_saturated, scale_mode, rotation, and stream_type (Standard, HX, or unknown).
Templates in config/ are copied to /etc/ only when the file is missing. Writes are a temp file, fsync, rename, then fsync of the directory.
| File | Writer | Contents |
|---|---|---|
ndimon-dec{N}-settings.json |
API and ndimon-r |
Source, scale, rotation, alias, audio, tally. N is 1–8 |
ndimon-device-settings.json |
API, plus alias init in C++ | Alias, watchdog mode, NTP, decode/audio engine modes, reboot schedule, plugin feeds |
ndimon-rx-settings.json |
API | Transport Rxpm (default RUDP) and the Rxpm_user_set marker |
ndimon-find-settings.json |
API | Discovery Server IP. Non-empty means enabled |
ndi-group.json |
API | Groups, case-sensitive |
ndi-config.json |
API | Extra finder IPs, stored as a JSON string |
ndimon-presets.json |
API | Named presets |
ndimon-audio-settings.json |
API | Audio graph, output choices, gains and delays |
ndimon-plugins.json |
API | Plugin enabled/version state |
ndimon-auth.json |
API | Password hash, mode 0600 |
ndimon-splash-settings.json, ndimon-osd-settings.json |
API | Splash and OSD |
ndimon-sources.json |
Finder | Name-to-URL cache |
/var/lib/ndimon/.ndi/ndi-config.v1.json |
Both C++ processes | SDK home: groups, discovery and transport |
/var/lib/ndimon/backups/ |
API | Pre-restore snapshots |
/var/lib/ndimon-network/ |
Root network helper | Private profiles, journal and recovery state |
/etc/ndimon-network-interfaces.json |
Root network helper | Public interface mapping for the selected roles |
| Wired network | ndimon-net via ndimon-priv |
Management/media networks and bindings; API stages complete transactions |
| Tessera flow/PCM files | Declared Tessera privileged hooks | Device flows and ALSA bridges; see the plugin guide |
The splash IP is a runtime value. C++ does not write it back to disk.
Prefer the web UI. It writes JSON and tells the decoder to reload. If you edit /etc/ by hand:
sudo systemctl restart ndimon-r ndimon-finder ndimon-apiSystem → Check for Updates, then Update Now. That runs from the checkout recorded in /etc/ndimon-source-dir:
cd /path/to/NDIMon-R
sudo bash install.sh --no-depsOr sudo bash scripts/update.sh. /etc JSON is left alone.
Core updates and plugin updates are separate. Update a Tessera plugin with its
matching precompiled bundle through Plugins; a change in the Tessera source
repository does not automatically replace the installed daemon or archive.
The current host manifest checks min_ndimon; it does not yet show a tested-build
compatibility matrix. See release/upgrade notes.
Network-profile upgrades also require the new nftables dependency; run the
updated dependency setup if the host does not already have it.
No picture after connect. sudo journalctl -u ndimon-r -f. Confirm a desktop does not own DRM. Standard NDI and HX H.264/H.265 are the supported payloads. HDR P216 / PA16 is logged and not converted.
Empty source list. systemctl is-active ndimon-finder. The Discovery Server IP must be reachable. Senders on another subnet need that server or extra IPs under NDI. Groups are case-sensitive.
HX is black. The Standard SDK decodes HX inside the SDK and needs its codec runtime (libavcodec.so.61 on the tested builds). Check journalctl -u ndimon-r for the first-frame line and confirm the runtime is installed. Hardware HX decode is not available.
Web UI down. systemctl status ndimon-api. To use another port, set Environment=PORT=8080 on the unit. Loopback health: curl -s http://127.0.0.1/api/health.
Rockchip MPP missing. ldconfig -p | grep librockchip_mpp, then sudo bash scripts/setup-deps.sh on an image that ships MPP.
NDI library missing. ldconfig -p | grep libndi, then sudo bash scripts/setup-deps.sh.
Source jumps. The saved source is in ndimon-dec{N}-settings.json. A Discovery Server with controlling enabled can change it. That is the SDK, not a reconnect bug. A local connect is protected for 30 seconds.
Network change did not stick. Open the displayed management address directly and click Keep within the ready-state 60-second window. Proxy headers and opening the page alone do not confirm. An unconfirmed reboot or failed apply restores the old settings. dhcpcd is read-only; check supported backends and adoption limits.
Lost the confirm page. Reopen the pending transaction's management link before its countdown ends. If the new address is unknown or unreachable, leave it unconfirmed and let the device restore the previous configuration. Opening the link alone never keeps the change.
Saved audio is silent. Check the selected engine and endpoint readiness in
Outputs → Advanced → Audio. Saving choices leaves v1 active until you
explicitly select v2. A disabled output retains its routes without playing them.
Check Tessera flows, 48 kHz agreement and the selected audio network; review
GET /v1/Audio/status for endpoint errors/xruns. Returning to v1 restores the
legacy path. Persistent drift pinned at its limit needs investigation.
Plugin settings are missing. Installing/enabling the plugin exposes its
tab on Plugins; network-audio activation is a second action. A core-only update
does not install Tessera. Check the archive's OS/architecture and min_ndimon.
| Path | Role |
|---|---|
src/main.cpp |
Process entry, one worker per connector, health tick, systemd notify, IPC wiring |
src/NDIReceiver.cpp |
SDK receive: advertiser, connect, FrameSync vs retained compressed capture, tally |
src/VideoDecoder.cpp |
Factory: MPP, V4L2, VAAPI, FFmpeg, honoring decode_mode |
src/MppDecoder.cpp |
Rockchip H.264/H.265 to NV12 DMA-BUF |
src/V4L2Decoder.cpp |
Pi 4 M2M H.264 |
src/VAAPIDecoder.cpp |
Intel/AMD VAAPI, DRM PRIME modifiers |
src/SoftwareDecoder.cpp |
FFmpeg NV12 |
src/DRMDisplay.cpp |
KMS scanout, scale, rotation, colour convert, splash, OSD, hotplug |
src/AlsaAudio.cpp |
Planar float to stereo S16LE. One output |
src/audio/ |
Optional central v2 audio graph, route delays, ALSA endpoints and drift compensation |
src/IPCServer.cpp |
Unix socket JSON |
src/Config.cpp |
Reads /etc JSON. C++ writes alias init and per-output source/mode only |
finder/main.cpp |
NDI Find. Shares the NDI home with the decoder |
api/server.js |
Express, auth guard, route mounts |
api/auth.js |
Scrypt, sessions, login rate limit |
api/public/index.html |
The web UI |
api/routes/ |
REST modules, including Backup.js and Network.js |
api/audio-settings.js, api/network-profiles.js |
Revision-guarded audio graphs and typed network transactions |
api/plugins.js, plugins/tessera/ |
Host plugin loader/lifecycle and Tessera adapter/UI |
scripts/ndimon-priv.sh |
Root-only actions |
scripts/ndimon-net.py |
Wired DHCP/static helper, installed as /usr/local/lib/ndimon/ndimon-net |
scripts/ndimon_network_profiles.py |
Management/media profile activation, isolation and recovery |
scripts/ndimon-watchdog.sh |
Process liveness, and active-mode stall recovery |
systemd/ |
Long-running units, the update oneshot, and boot rollback |
config/ |
Templates copied to /etc only when missing |
- There is no encoder.
/v1/NDIEncoder/*returns 404. - With default engine v1, one output owns ALSA. Experimental v2 adds the independently configured audio routes described above.
- Standard NDI frames are copied. At 1080p and 4K that is several megabytes per frame.
- HDR
P216andPA16are not converted to SDR. - Tally, screensaver, colour and the legacy NDI-audio switch remain decoder-wide. V2 audio choices are per output. Transport, HX decode preference and the NDI network assignment remain global; separate receiver processes are deferred.
- Sessions do not survive an API restart.
- VLANs are supported on one physical wired port. Wi-Fi, IPv6 configuration and multiple physical port management remain outside this feature.
- dhcpcd (older Pi OS) can be read. It cannot be changed from this release.
- The Standard SDK uses software HX decoding even when VAAPI drivers are installed.
- Audio v2 and the current Ubuntu ARM Tessera bundle remain experimental. Sustained clock stability, external hardware interoperability, HDMI/Dante delay matching and hardware PTP support are not certified. No automatic lip-sync is provided.
- Bluetooth speaker routing/latency calibration and HDMI ARC/eARC capture are planned research, with no implementation in this release.
- This integration configures static flows and exposes no automatic vendor-controller subscription setup. Use the established controller workflow where supported; ordinary Dante Controller discovery/routing interoperability was not established by the local PCM checks.
MIT — LICENSE.
The NDI SDK is Vizrt’s, not MIT. Running install.sh means you accept the NDI SDK License Agreement.