Commit Graph

16 Commits

Author SHA1 Message Date
Johanness a3cd45cc49 decklink-out: add mode support check, limit CreateVideoFrame error spam 2026-05-30 23:18:02 +03:00
Johanness b4f766fb9d add HRESULT logging to CreateVideoFrame for debug 2026-05-30 23:15:00 +03:00
Johanness b5d9c7cd3f fix decklink-out: init grains_read from current time to avoid epoch realignment 2026-05-30 23:13:24 +03:00
Johanness c2e823beda fix realignment 2026-05-30 23:10:35 +03:00
Johanness 003279669e fix output res&frame rate 2026-05-30 23:07:15 +03:00
Johanness 2206fd71b3 some fixes 2026-05-30 22:12:47 +03:00
Johanness 8b5fb3dc36 fix skipped write_index 2026-05-28 23:55:16 +03:00
Johanness 34f72c22f0 hardcoded decklink mode fix 2026-05-28 23:43:26 +03:00
Johanness d02223224f feat: add DeckLink input and output nodes
decklink-in:
- IDeckLinkInputCallback::VideoInputFrameArrived captures V210 frames
- Frame data stored with mutex, copied to MXL grain in process thread
- Uses mxlSleepUntil for TAI-time-based grain pacing
- Configurable: device_index (int), mode (1080i50/1080p50/etc)
- Auto-detect input format via bmdVideoInputEnableFormatDetection
- 1 output port: video_out (V210)

decklink-out:
- Reads V210 grains from MXL, schedules playback via DeckLink output
- Creates IDeckLinkMutableVideoFrame, copies grain data, schedules
- Uses ScheduledFrameCompleted callback for frame completion
- Configurable: device_index (int), mode (1080i50/1080p50/etc)
- 1 input port: video_in (V210)

Both nodes:
- Use DeckLinkAPIDispatch.cpp for CreateDeckLinkIteratorInstance
- Access pixel data via IDeckLinkVideoBuffer (latest SDK API)
- Graceful device open/close on writer/reader add/remove
- Build conditionally via DMF_BUILD_DECKLINK + DECKLINK_SDK_DIR
2026-05-28 22:23:00 +03:00
Johanness 4b50f49172 chore: phase 1 cleanup — proper shutdown, flow resource cleanup, test fix
- node_runner: track all readers/writers in flow_resources vector,
  release them via mxlReleaseFlowWriter/mxlReleaseFlowReader before
  destroying the MXL instance (fixes 'leaked flow writer' warning)
- node_runner: remove_writer/remove_reader commands now also release
  the MXL flow resources, not just reset the node's optional<>
- engine: call process_manager.stop_all(graph) on shutdown to kill
  child node processes (prevents orphaned passthrough processes)
- graph: add get_node_mut() for process_manager to update node state
- process_manager: implement stop_all(Graph&) that SIGTERMs all
  running node processes
- passthrough: reduce logging to first-grain and realign-once only
- tests: update flow format assertion to match NMOS
  (urn:x-nmos:format:video instead of video/v210)
2026-05-26 23:04:16 +03:00
Johanness 5b48d86c6e fix: separate grain processing thread from LWS event loop
The fundamental issue: mxlSleepUntil/mxlSleepForNs blocks the entire
thread, making LWS unresponsive and causing control commands to hang.
Meanwhile, LWS poll(1) adds latency that makes grain timing unreliable.

Solution: run node->process() in a dedicated thread that can freely use
mxlSleepUntil for precise grain timing, while the main thread runs
control_server->poll(10) for LWS event handling.

Passthrough now uses mxlSleepUntil(deadline) matching the mxl-gst-sink
Cursor pattern, with 2-grain read delay for buffering.
2026-05-26 22:55:57 +03:00
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
Johanness 503023f3e5 fix: use mxlSleepUntil delivery deadline pattern for grain timing
Match the mxl-gst-sink pattern: sleep until the next grain's delivery
deadline using mxlSleepUntil, then read with a short 5ms timeout.
Uses 2-grain read delay (40ms at 50fps) for buffering headroom.

Previous approach of mxlSleepForNs + 20ms GetGrain timeout caused the
passthrough to fall behind: each iteration took ~25ms (poll+sleep+read),
missing grains and perpetually chasing headIndex via realign.
2026-05-26 22:46:33 +03:00
Johanness 2c712592dd fix: use TAI-time-based grain index alignment in passthrough
Instead of chasing headIndex from mxlFlowReaderGetRuntimeInfo (which
points to the NEXT grain to be written, causing perpetual TOO_EARLY),
the passthrough now uses mxlTimestampToIndex + mxlGetNsUntilIndex for
proper timing alignment, matching the pattern used by mxl-gst-sink.

Key changes:
- realign() computes read_index from current TAI time minus 1 grain delay
- process() uses mxlSleepForNs to wait until the target grain is due
- on_add_reader fetches grain_rate from mxlFlowConfigInfo
- Removed separate write_index_ (uses grain_info.index for writer)
2026-05-26 22:40:46 +03:00
Johanness 3e1477c56c fix: LWS HTTP connection leak, grain index alignment, and reader head tracking
- control_server: add Connection: close header + always return -1 after
  HTTP response to force-close connection. Without this, lws_service()
  blocks forever after the first POST /cmd, freezing the node process loop.
- passthrough: initialize read_index_ to runtime.headIndex when reader
  is added (prevents TOO_EARLY errors on first read)
- passthrough: handle MXL_ERR_OUT_OF_RANGE_TOO_EARLY in addition to
  TOO_LATE (both jump to head index)
- passthrough: use grain_info.index as writer index instead of separate
  write_index_ counter (MXL writers must use TAI-based grain indices)
- passthrough: reduce grain read timeout from 100ms to 20ms for tighter
  loop with LWS poll
- node_runner: add 'status' command handler that sends node status
  back via control server
2026-05-26 22:22:46 +03:00
Johanness 5b5ffa1308 feat: scaffold project framework with engine, node skeleton, and passthrough node
- CMake build system with vcpkg dependency management
- libdmf-node: Node interface, port definitions, WebSocket control
  server, node runner with MXL lifecycle management
- libdmf-engine: Graph model (nodes/edges CRUD, serialization),
  FlowManager (UUID + V210 NMOS flow definitions), ProcessManager
  (fork/exec node processes), ApiServer (REST API for graph control)
- Passthrough node: reads V210 grains from MXL, copies, writes to MXL
- Unit tests for Graph and FlowManager (6 cases, 21 assertions)
- MXL SDK as git submodule (symlinked from extern/mxl)
2026-05-26 01:32:50 +03:00