Commit Graph

39 Commits

Author SHA1 Message Date
itten 166efc3c59 Some fixes. For a now we fully support only .ts, due to huge problem with h264/hevc audio bursts 2026-07-05 12:39:06 +03:00
itten b2534efb0f audio works, holy shit 2026-07-04 03:24:20 +03:00
JohannesItten 96475acf62 refactor: VideoReader and videoin cleanup
VideoReader:
- Guard get_source_info/allocate_conversion_buffers behind have_video
  check — prevents crash on audio-only files
- Remove unused audiobuf parameter from get_next_frame
- Remove dead return statements after throw
- Remove unused SourceInfo::stride field and AudioInfo struct
- Pass const std::string& instead of by value in constructor/open_file
- Remove redundant struct keyword on SourceInfo source_info{}
- Fix video_stream_index never guarded against -1 in open_file
- Check av_image_alloc and avcodec_parameters_to_context return values
- Remove extra av_packet_unref after seek (was harmless but confusing)
- Use SWS_BILINEAR for sws_getContext flags instead of 0
- Replace NULL with nullptr in sws_getContext
- Remove unused #include <libavutil/pixdesc.h>

videoin main.cpp:
- Early return if have_video is false after open
- Inline make_video_flow_def call (remove intermediate variable)
- Add grain count log line matching other nodes
- Change continue to break when get_next_frame returns false
- Make video_rate const
- Remove double blank line before main()
- Remove trailing spaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 19:13:19 +03:00
JohannesItten bb7d1bb5a3 fix: VideoReader bugs — stride, memory, audio detection, UB
Pass MXL grain stride (from sliceSizes[0]) into get_next_frame so
YUV422P10toV210 writes with the correct line width instead of a
self-computed value that may not match the MXL buffer.

Fix audio stream detection: condition was inverted (!= -1 → == -1),
so the first audio stream was never picked up.

Add return false at end of get_next_frame to fix UB when g_running
goes false and the loop exits without returning.

Replace av_frame_unref/av_packet_unref with av_frame_free/av_packet_free
in destructor — unref only releases data, not the struct itself.

Add av_freep(&p10_data[0]) in destructor to free av_image_alloc memory.

Remove unused p10_buffer and v210_buffer allocations.

Read filename from config("file") instead of hardcoded path.
Add early return if mxlCreateFlowWriter fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 19:11:18 +03:00
itten 0b5b113d4e fixed ndiout flow_id -> video_flow_id 2026-07-03 18:51:10 +03:00
itten 32a8fa9837 refactored videoin to NodeBase 2026-07-03 18:48:11 +03:00
itten 2c43f356d1 it's alive 2026-07-03 14:01:13 +03:00
JohannesItten a422cd832a feat: graph-level fanout — reuse flow UUID for same from_node+from_port
Edges that share the same source node and port now get the same MXL flow
UUID. One writer, multiple independent readers — each tracks its own
position in the ring buffer. Enables 1-to-N routing in graph.json without
any splitter node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:43:41 +03:00
JohannesItten 2b0dfb10c6 feat: load pipeline graph from graph.json
studio-manager now accepts an optional path argument:
  dmf-studio-manager graph.json

load_graph() parses nodes and edges from JSON, generating fresh UUIDs
for each MXL flow on every launch. Falls back to hardcoded build_graph()
when no argument is given.

graph.json at repo root defines the current testpattern→ndiout pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:36:20 +03:00
JohannesItten f2515b7ce2 fix: MXL_ERR_FLOW_INVALID reconnect, nullptr→"" options, null guards
Readers (ndiout, fakesink) now handle MXL_ERR_FLOW_INVALID by releasing
and recreating the flow reader, then realigning the index to current time.
This lets consumer nodes survive a producer restart without exiting.

All mxlCreateInstance/FlowWriter/FlowReader options args changed from
nullptr to "" to match MXL reference implementation style.

mxlReleaseFlowReader calls guarded with null checks so cleanup is safe
when a mid-run reconnect attempt fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:28:30 +03:00
JohannesItten 21a8ee4ba0 feat: garbage collection on startup, readable status strings, audio/float32
studio-manager: call mxlGarbageCollectFlows before launching nodes to
clean up stale flow directories left by previous crashed runs.

shared/NodeBase.hpp: add mxl_status_str(mxlStatus) — converts error
codes to readable names (e.g. MXL_ERR_OUT_OF_RANGE_TOO_LATE). All
nodes now log these names instead of raw integers.

shared/FlowDef.hpp: use "audio/float32" as media_type for 32-bit audio
flows, matching the MXL SDK examples. audio/L{n} kept for other depths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:20:53 +03:00
itten 39ba8add1a Merge pull request 'Feature/ndi out node' (#2) from feature/ndi-out-node into main
Reviewed-on: #2
2026-07-03 10:13:24 +03:00
JohannesItten 5504b8983e feat: ndiout supports audio-only mode (video optional)
flow_id is now optional (config().contains). When absent: skip the
MXL reader, active-wait, and NDI video path entirely. Video buffers
and NDI video frames are heap-allocated only when has_video.

Loop pacing: video branch sleeps 1ms on TOO_EARLY as before; audio-only
path sleeps 1ms when no chunk was available (audio_advanced == false)
to avoid busy-spinning.

NDI sender name changed from the video flow UUID to node_id(), which
is stable and human-readable for both video and audio-only modes.

To use audio-only: wire only audio_flow_id in build_graph, omit flow_id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:11:06 +03:00
JohannesItten 9fd41b0312 fix: samples_per_frame must multiply fps_den for fractional framerates
sample_rate / fps_num gave 48000/30000 = 1 for 29.97 fps, producing
silence. Correct formula is sample_rate * fps_den / fps_num, matching
the calculation already used in testpattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:06:43 +03:00
JohannesItten 2b11411423 fix: set p216_frame FourCC; scope audio_rate to has_audio branch
ndiout: p216_frame.FourCC was zero-initialized — NDI needs it explicitly
set to NDIlib_FourCC_video_type_P216 to send the correct format.

All three nodes: audio_rate = {sample_rate, 1} was always declared even
when has_audio is false (sample_rate = 0 in ndiout). Scoped into the
has_audio block so the bad rate can never be passed to mxlGetCurrentIndex.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:01:39 +03:00
JohannesItten 2a5d3758b6 refactor: align testpattern naming with ndiin/ndiout
cfg_info → video_cfg, stride → video_stride, writer → video_writer,
grain/buf → video_grain/video_buf, index → video_index, st → vst,
audio_info/audio_id → audio_flow_info/audio_flow_id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 09:59:36 +03:00
JohannesItten b7fb54e2bc refactor: clean up ndiout — fix hardcoded 1920, consistent naming
Fix: c * 1920 → c * samples_per_frame (broke non-25fps or non-48kHz).
Fix: audio channel loop now iterates `channels` not `audio_slices.count`.

Rename: mxl_stride → video_stride, no_samples → samples_per_frame,
ndi_frame_10bit/16bit → v210_frame/p216_frame, ndi_audio_frame → ndi_audio.
Scope `ast` locally to its use block.
Drop unused #include "V210.hpp" and #include "FlowDef.hpp".
ndi_audio struct zero-initialized then filled only when has_audio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 09:58:06 +03:00
itten ba3c2994ee ndi out before refactoring 2026-07-03 09:52:29 +03:00
itten b5806f841b Merge pull request 'Feature/ndi out node' (#1) from feature/ndi-out-node into main
Reviewed-on: #1
2026-07-01 18:22:25 +03:00
JohannesItten bb4d527cbe feat: sine tone audio in testpattern node
testpattern now optionally writes an audio flow when audio_flow_id is
wired in the graph. Each channel generates a sine wave at (c+1)*1000 Hz
(ch0=1kHz, ch1=2kHz, ...) at -18 dBFS broadcast reference level.

Phase is derived from the absolute audio_index so there are no clicks
at frame boundaries. One audio chunk per video frame (1920 samples at
48kHz/25fps), written via mxlFlowWriterOpenSamples with ring-buffer
fragment and channel stride handling matching ndiin.

build_graph: separate UUIDs for testpattern and ndiin flows; wire
testpattern audio_flow_id (no sink yet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 13:26:22 +03:00
JohannesItten 42507886fe feat: multiple test patterns in testpattern node
V210.hpp: add fill_colorbars, fill_ire_ramp, fill_black, fill_white.
Shared fill_solid helper stamps the first line across all rows via
memcpy. Generic write_palette_line template handles both SMPTE and
IRE bar palettes. fill_frame kept as a backward-compat alias.

testpattern/main.cpp: read "pattern" from node config and dispatch
to the appropriate fill function (bars/ire/black/white).

build_graph: pass {"pattern","bars"} to testpattern node params.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 13:24:06 +03:00
JohannesItten 4245284382 fix: skip non-FLTP audio frames in NDIReceiver
NDIlib_audio_frame_v3_t carries a FourCC. Only FLTP (float32 planar)
is handled — skip and free any other format rather than misinterpreting
compressed or integer audio bytes as floats.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 13:19:16 +03:00
JohannesItten 31b57f7a33 fix: cast NDI p_data to float* before copying audio samples
NDIlib_audio_frame_v3_t.p_data is uint8_t*, not float*. Calling
vector<float>::assign(uint8_t*, uint8_t*+N) converted each individual
byte (0-255) into a separate float instead of reinterpreting 4 bytes
as one float sample — producing complete noise on playback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 13:08:04 +03:00
JohannesItten aa39a821db fix: use continuous sample API for audio flow in ndiin
Audio flows in MXL use mxlFlowWriterOpenSamples/CommitSamples, not
the grain API (mxlFlowWriterOpenGrain is for discrete video flows).
Using the grain API silently did nothing — hence "last published index: 0".

MXL audio is float32 planar: each channel occupies its own ring buffer
region accessed at base.fragments[*].pointer + c * slice.stride.
Two fragments handle ring buffer wraparound. No int32 conversion needed.

Also log continuous.channelCount/bufferLength instead of the discrete
sliceSizes field (wrong union member for audio).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:56:55 +03:00
JohannesItten 72a554a172 fix: add channel_count to make_audio_flow_def
MXL reads channel_count (plain integer) to determine grain buffer
geometry, defaulting to 1 if absent. The NMOS IS-04 channels array
is ignored by MXL. Without channel_count, stereo audio grains were
half the required size.

Also: MXL only accepts bit_depth 32 or 64 for audio flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:51:13 +03:00
JohannesItten fe3b5969ca fix: add required grouphint tag to make_audio_flow_def
MXL FlowParser requires urn:x-nmos:tag:grouphint/v1.0 in the tags
object of every flow definition. make_audio_flow_def was missing tags
entirely, causing FlowParser.cpp:192 "Invalid group hint tag".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:47:40 +03:00
JohannesItten cc1011cced feat: NDI in node receives and writes audio flow
- FlowDef.hpp: add make_audio_flow_def (NMOS IS-04 audio/Lnn flow)
- NDIReceiver: replace capture_v210 with capture() — single recv call
  dispatches to FrameKind::{Video,Audio,None}; AudioInfo struct carries
  sample_rate/channels/samples/channel_stride
- ndiin: optional audio writer when graph wires audio_flow_id; single
  loop with 5ms NDI poll; audio grains written immediately on arrival;
  video grains written when mxlGetCurrentIndex reaches video_index
- build_graph: rename ndiin video port flow_id → video_flow_id; add
  audio_flow_id edge (no sink yet — wired to ndiin only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:39:35 +03:00
JohannesItten ee47d85cbd feat: exit node on NDI source format change
On status_change, probe() the new format and compare against the
format used to create the MXL flow. If resolution or fps changed,
throw with a descriptive message so the caller exits cleanly.

The existing catch in NDIInNode::run() logs the reason and breaks
out of the loop, causing the process to exit. Studio-manager detects
the exit and logs it — operator can restart to pick up the new format.

Same-format status changes (e.g. metadata only) still return false
and repeat the last frame as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:27:06 +03:00
JohannesItten 10d1282059 feat: fan-out ndiin video flow to both fakesink and ndiout
Share a single UUID across both edges so both readers connect to the
same MXL flow. MXL supports multiple readers per flow natively — each
reader has its own read pointer into the ring buffer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:18:12 +03:00
JohannesItten e6d72ab431 fix: ndiout — RAII for NDI lifecycle, vectors instead of malloc
- NDIContext struct handles NDIlib_initialize/send_create/send_destroy/destroy
  so NDIlib_destroy() is guaranteed even if send_create fails (was leaked before)
- malloc/free for 10-bit and 16-bit frame buffers → std::vector<uint8_t>
- NDI frame structs point into vector data, no manual lifetime management
- static_cast for FourCC instead of C-style cast
- Tidy: ndi_frame_count replaces ndi_frame_counter, ++prefix form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:15:44 +03:00
JohannesItten 12c87db4e2 refactor: NDIHelper → NDIReceiver with cleaner API and naming
NDIReceiver (shared/NDIReceiver.hpp):
- Rename class NDIHelper → NDIReceiver, file NDIHelper.hpp → NDIReceiver.hpp
- Add SourceInfo struct (width, height, fps_num, fps_den, stride, fourcc)
  replacing raw public member variables (xres, yres, frame_N, frame_D, stride)
- find_sources() now returns std::vector<std::string> instead of output pointer
- select_source() + get_source_info() → connect() + probe() (cleaner sequence,
  probe() returns SourceInfo and stores it internally for capture_v210)
- getV210_video_frame() → capture_v210() — removes unused source_num parameter
- get_bytes_per_pixel, fourCCtoStr → private static bytes_per_pixel, fourcc_str
- u_int32_t → uint32_t; (uint32_t) casts → static_cast
- probe() checks g_running to avoid hanging if source never sends video

ndiin/main.cpp:
- Update to new NDIReceiver API
- malloc/free latest_buffer → std::vector<uint8_t> latest_frame
- Remove unused #include <Processing.NDI.Lib.h> and V210.hpp
- memcpy uses latest_frame.size() instead of separate frame_bytes variable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:13:36 +03:00
JohannesItten 7a32fa20af fix: NDI node correctness and robustness fixes
NDIHelper:
- find_sources: check g_running each attempt to avoid 50s block on shutdown
- getV210_video_frame: fix P216 branch (fall-through + local pointer reassignment bug)
- getV210_video_frame: status_change re-queries source info instead of throwing
- select_source: fix typo "recieve" → "receive"

ndiout:
- set frame_rate_N/frame_rate_D on the NDI send frame (was 0/0)
- add per-second stats logging (matching fakesink pattern)
- add missing <chrono> include
- fix typo "reciever" → "receiver"

ndiin:
- read source_num from NODE_CONFIG (key: "source_num", default 0)
- wrap hot-loop NDI call in try/catch so source-lost terminates cleanly
- pass source_num through to getV210_video_frame

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:07:45 +03:00
itten 19ea14a61c NDI in node looks almost done 2026-07-01 11:55:04 +03:00
itten 51a1b1b2c8 looks like i forgot about commits 2026-06-25 12:43:53 +03:00
itten 49ead70b10 IRE palette for bars pattern 2026-06-23 18:36:39 +03:00
JohannesItten b960c413b2 build: enable CMAKE_POSITION_INDEPENDENT_CODE globally
On Linux, spdlog and fmt are compiled as static libs then linked into
libmxl.so. Without -fPIC the linker rejects TPOFF32 relocations inside
a shared object. Setting PIC globally fixes this for all fetched deps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 13:56:24 +03:00
JohannesItten bf45fe4fc0 build: disable UUID_SYSTEM_GENERATOR to remove libuuid dependency on Linux
UUID_SYSTEM_GENERATOR=ON requires libuuid on Linux and CoreFoundation
on macOS. Turning it off uses stduuid's built-in mt19937 generator,
which needs no system packages and works identically on all platforms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 13:52:15 +03:00
JohannesItten 8646a4d2a7 build: replace brew find_package with FetchContent for fmt and spdlog
Removes the Homebrew dependency so the build works on any platform
with only CMake 3.24+ and a C++20 compiler. fmt and spdlog are now
fetched and compiled from source alongside the other FetchContent deps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 13:38:52 +03:00
JohannesItten ca682eaf0a Initial commit: testpattern → fakesink pipeline over MXL shared memory
- NodeBase, Signal, FlowDef, V210 shared headers
- testpattern node: SMPTE 75% color bars writer at 25fps
- fakesink node: non-blocking MXL reader with per-second stats
- studio-manager: FlowGraph data model, graph-driven fork/exec launcher
- mxl pinned as submodule at 0ae1dc5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 13:35:01 +03:00