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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>