Johanness d677b654f0 fix: non-blocking grain timing — use mxlGetNsUntilIndex instead of mxlSleepUntil
mxlSleepUntil blocks the entire thread including LWS poll, causing the
control server to become unresponsive and grains to be missed. Instead,
check mxlGetNsUntilIndex and skip process() if the grain isn't due yet
(>2ms away). The node loop spins with poll(1) keeping LWS responsive,
and only attempts a grain read when it's nearly due.
2026-05-26 22:51:57 +03:00
2026-05-25 22:21:24 +03:00
2026-05-25 22:21:24 +03:00
2026-05-25 22:21:24 +03:00

DMF Studio

Node-based visual production platform for the Dynamic Media Facility architecture.

Prerequisites

  • CMake 3.24+
  • C++20 compiler (GCC 12+, Clang 15+)
  • vcpkg (at ~/vcpkg or set CMAKE_TOOLCHAIN_FILE)
  • GStreamer (for MXL test tools only)

Build

# Configure (from project root)
cmake -B build \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_TOOLCHAIN_FILE=$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake \
  -DBUILD_SHARED_LIBS=OFF \
  -DBUILD_DOCS=OFF \
  -DBUILD_TESTS=OFF \
  -DBUILD_TOOLS=OFF \
  -DBUILD_UTILS=OFF

# Build all
cmake --build build -j$(nproc)

Binaries end up in:

  • build/engine/dmf-studio-engine
  • build/nodes/passthrough/dmf-node-passthrough

Rebuild after changes

# Full rebuild
cmake --build build -j$(nproc)

# Rebuild only one target (faster)
cmake --build build -j$(nproc) --target dmf-node-passthrough
cmake --build build -j$(nproc) --target dmf-studio-engine

Clean rebuild

rm -rf build
# Then re-run the configure + build steps above

Run unit tests

build/tests/dmf-test-graph

Test with MXL

1. Create MXL domain

mkdir -p /tmp/dmf-mxl

2. Start MXL test source (writes V210 video flow)

Create a flow config file:

cat > /tmp/v210_50p.json << 'EOF'
{
  "id": "a0000001-0000-0000-0000-000000000001",
  "description": "DMF Studio test video flow",
  "format": "urn:x-nmos:format:video",
  "label": "DMF Studio Test Video",
  "tags": {
    "urn:x-nmos:tag:grouphint/v1.0": ["dmf-studio:Video"]
  },
  "media_type": "video/v210",
  "grain_rate": {"numerator": 50, "denominator": 1},
  "frame_width": 1920,
  "frame_height": 1080,
  "interlace_mode": "progressive",
  "colorspace": "BT709",
  "components": [
    {"name": "Y", "width": 1920, "height": 1080, "bit_depth": 10},
    {"name": "Cb", "width": 960, "height": 1080, "bit_depth": 10},
    {"name": "Cr", "width": 960, "height": 1080, "bit_depth": 10}
  ]
}
EOF

Start test source (needs GStreamer + MXL tools built separately):

mxl-gst-testsrc -d /tmp/dmf-mxl -v /tmp/v210_50p.json --pattern smpte &

Check active flows:

mxl-info --domain /tmp/dmf-mxl

3. Start passthrough node

build/nodes/passthrough/dmf-node-passthrough \
  --node-id pass1 \
  --control-port 9100 \
  --mxl-domain /tmp/dmf-mxl

Options:

  • --node-id, -n — Unique node instance ID (required)
  • --control-port, -p — WebSocket control port (required)
  • --mxl-domain, -d — MXL domain path (default: /dev/shm/mxl)
  • --config, -c — Node config as JSON string

4. Start engine

export DMF_STUDIO_BIN_DIR=build/nodes/passthrough
build/engine/dmf-studio-engine --port 8080

5. Control via REST API

# Add nodes
curl -X POST http://localhost:8080/api/graph/nodes \
  -H "Content-Type: application/json" \
  -d '{"type":"passthrough","id":"pass1"}'

curl -X POST http://localhost:8080/api/graph/nodes \
  -H "Content-Type: application/json" \
  -d '{"type":"passthrough","id":"pass2"}'

# Connect nodes (creates MXL flow between them)
curl -X POST http://localhost:8080/api/graph/edges \
  -H "Content-Type: application/json" \
  -d '{"from_node":"pass1","from_port":"video_out","to_node":"pass2","to_port":"video_in"}'

# View graph
curl http://localhost:8080/api/graph

# Start all nodes
curl -X POST http://localhost:8080/api/graph/start

# Stop all nodes
curl -X POST http://localhost:8080/api/graph/stop

# Remove node
curl -X DELETE http://localhost:8080/api/graph/nodes/pass1

# Remove edge
curl -X DELETE http://localhost:8080/api/graph/edges/pass1:video_out->pass2:video_in

6. Control node directly via WebSocket

Connect to ws://localhost:9100 with subprotocol dmf-control:

wscat -c ws://localhost:9100 -s dmf-control

Commands:

{"cmd": "add_reader", "flow_id": "<uuid>", "port_id": "video_in"}
{"cmd": "add_writer", "flow_id": "<uuid>", "port_id": "video_out", "flow_def": {<NMOS flow JSON>}}
{"cmd": "remove_reader", "port_id": "video_in"}
{"cmd": "remove_writer", "port_id": "video_out"}
{"cmd": "configure", "params": {}}
{"cmd": "shutdown"}

Project structure

libs/dmf-node/     — Node skeleton library (interface, WS control, MXL lifecycle)
libs/dmf-engine/   — Engine library (graph model, flow manager, process manager, REST API)
nodes/passthrough/ — Passthrough node (1 MXL in → 1 MXL out, memcpy)
engine/            — Engine binary
tests/             — Unit tests
S
Description
No description provided
Readme 284 KiB
Languages
C++ 92.8%
Shell 3.6%
CMake 3.6%