Commit Graph

34 Commits

Author SHA1 Message Date
JohannesItten 32fe6aa22a fix: DeckLink macOS build — CFStringRef and CoreFoundation
GetDisplayName returns CFStringRef on macOS (not const char*).
Add #ifdef __APPLE__ handling in DeckLinkSender.hpp and
DeckLinkReceiver.hpp, and link -framework CoreFoundation in
decklinkin/decklinkout CMakeLists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:58:46 +03:00
JohannesItten 370cbd89a2 fixes + cmake fixes for macos 2026-07-07 19:33:55 +03:00
JohannesItten 31346c48a6 NDIReceiver probe fix 2026-07-07 19:21:13 +03:00
JohannesItten cbe933eca3 and another one 2026-07-07 19:11:09 +03:00
JohannesItten 32b608ed3c DeckLinkSender green screen fix 2026-07-07 19:09:48 +03:00
JohannesItten 2d154e90dc fixes + refactoring + plan 2026-07-07 17:26:47 +03:00
itten 2faf2f9076 decklink + audio 2026-07-07 16:46:03 +03:00
JohannesItten 94da6f3283 audio support 2026-07-06 01:51:47 +03:00
JohannesItten 7847a8b70d fix: use IDeckLinkVideoBuffer::GetBytes for frame access
On Linux, GetBytes lives on IDeckLinkVideoBuffer (via QueryInterface),
not directly on IDeckLinkVideoInputFrame. Also restores StartAccess/
EndAccess around the copy for correct buffer lifecycle management.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 01:42:32 +03:00
JohannesItten e335461aac refactor: DeckLinkReceiver readability and error handling
DeckLinkReceiver:
- Group private members with section comments (SDK objects / sync / frame state)
- Change wait_for_format timeout param from uint64_t to int (matches
  std::chrono::milliseconds and all call sites)

decklinkin main.cpp:
- Wrap start_capture in try/catch — logs error and returns cleanly on
  device init failure (consistent with NDIInNode pattern)
- Remove redundant zero-init on frame_buf (vector<uint8_t> zero-inits anyway)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 01:38:13 +03:00
JohannesItten 640271384e refactor: DeckLinkReceiver cleanup and encapsulation
DeckLinkReceiver:
- Make InputCallback a private nested class — no longer exposed publicly
- DeckLinkReceiver owns all shared state (mutex, CVs, frame buffer)
- Replace get_input_callback() with clean wait_for_frame() API
- Fix device list leak: enumerate_devices stores raw IDeckLink* in
  raw_devices; destructor releases all of them + selected_device's AddRef
- Remove dead members: device_config, device_status, deckLink_notification
- Remove dead SourceInfo::stride field; rename SourceInfo → VideoInfo
- Remove dead video_source_info public member
- Remove printf; use no logging in receiver (caller logs)
- Remove commented-out notification code
- Fix dead return false after throw in enumerate_devices
- Fix dead null check after new InputCallback
- Replace IDeckLinkVideoBuffer QueryInterface with simpler GetBytes()
- Replace plain bool frame_ready/format_detected with consistent usage
  under mutex (no longer mixing atomic + CV pattern)
- Call StopStreams/DisableVideoInput in destructor
- Consistent snake_case naming throughout

decklinkin main.cpp:
- Rename NodeDeckLinkIn → DeckLinkInNode
- Get device_index from config().value("device_index", 0u)
- Remove unused includes: <time.h>, <algorithm>, <cstdio>, <DeckLinkAPI.h>
- Use clean receiver.wait_for_frame() instead of reaching into callback
- Use mxlGetCurrentIndex resync after sleep (consistent with other nodes)
- Fix return node.execute() (was node.execute(); return 0)
- Fix main() spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 01:34:01 +03:00
itten c60c5645ed working video setup 2026-07-06 01:27:19 +03:00
JohannesItten fe6b7b10ed fix: VideoReader crash on video-only file at EOF and dead bool return
- Guard swr_close/swr_init at EOF inside if (has_audio) — calling
  swr_init(nullptr) on a video-only file crashed at first loop
- Change open_file from bool to void — it never returned false, only
  threw, so the if (!open_file()) check in the constructor was dead code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 12:46:00 +03:00
JohannesItten 0298cf9b48 refactor: VideoReader and videoin post-audio review cleanup
VideoReader:
- Remove unused #include <iostream>
- Remove dead AudioInfo::samples and ::channel_stride fields
- Rename have_video → has_video (consistent with has_audio)
- Rename get_next_frame → read_next (returns audio too, not just frames)
- Fix outdated comment on read_next
- Remove trailing blank line in allocate_audio_conversion_buffers

videoin:
- Remove redundant FFmpeg includes (VideoReader.hpp provides them)
- Fix bug: mxlFlowWriterGetMaxWriteLengthSamples called with invalid
  audio_writer when mxlCreateFlowWriter fails — moved inside else branch
- Rename call site: get_next_frame → read_next
- Rename have_video → has_video at call sites
- Use = nullptr for audio_writer (consistent with video_writer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 12:44:35 +03:00
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 32a8fa9837 refactored videoin to NodeBase 2026-07-03 18:48:11 +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
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 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 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 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