d677b654f0b3314c5f0a78490c76bac3386af6a2
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.
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
~/vcpkgor setCMAKE_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-enginebuild/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
Description
Languages
C++
92.8%
Shell
3.6%
CMake
3.6%