Feature/ndi out node #1

Merged
itten merged 15 commits from feature/ndi-out-node into main 2026-07-01 18:22:26 +03:00
Owner
No description provided.
itten added 15 commits 2026-07-01 18:22:04 +03:00
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>
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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
itten merged commit b5806f841b into main 2026-07-01 18:22:26 +03:00
itten deleted branch feature/ndi-out-node 2026-07-01 18:22:27 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: itten/dmf-studio-rnd#1