From e6d72ab4312608b7cffa38a6acd396f79c9e111a Mon Sep 17 00:00:00 2001 From: JohannesItten Date: Wed, 1 Jul 2026 12:15:44 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20ndiout=20=E2=80=94=20RAII=20for=20NDI=20?= =?UTF-8?q?lifecycle,=20vectors=20instead=20of=20malloc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 - 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 --- nodes/ndiout/main.cpp | 114 ++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/nodes/ndiout/main.cpp b/nodes/ndiout/main.cpp index 51fb3d7..a0ed70a 100644 --- a/nodes/ndiout/main.cpp +++ b/nodes/ndiout/main.cpp @@ -1,5 +1,8 @@ #include +#include +#include #include +#include #include #include #include "NodeBase.hpp" @@ -7,6 +10,29 @@ #include "V210.hpp" #include +// RAII wrapper: init NDI, create sender, destroy both on scope exit. +struct NDIContext { + NDIlib_send_instance_t sender = nullptr; + + explicit NDIContext(const char* ndi_name) { + if (!NDIlib_is_supported_CPU()) + throw std::runtime_error("CPU not sufficient for NDI"); + if (!NDIlib_initialize()) + throw std::runtime_error("NDI lib init failed"); + NDIlib_send_create_t desc{}; + desc.p_ndi_name = ndi_name; + sender = NDIlib_send_create(&desc); + if (!sender) { + NDIlib_destroy(); + throw std::runtime_error("Cannot create NDI send instance"); + } + } + ~NDIContext() { + NDIlib_send_destroy(sender); + NDIlib_destroy(); + } +}; + class NDIOutNode : public dmf::NodeBase { void run() override { const auto flow_info = config().at("flow_id"); @@ -37,51 +63,36 @@ class NDIOutNode : public dmf::NodeBase { mxlFlowReaderGetConfigInfo(reader, &cfg_info); const uint32_t mxl_stride = cfg_info.discrete.sliceSizes[0]; - const mxlRational rate = {fps_num, fps_den}; + NDIContext ndi(flow_id.c_str()); + // V210 (10-bit) intermediate and P216 (16-bit) send buffers + std::vector buf_10bit(mxl_stride * height); + std::vector buf_16bit(width * sizeof(uint16_t) * 2 * height); + + NDIlib_video_frame_v2_t ndi_frame_10bit{}; + ndi_frame_10bit.xres = width; + ndi_frame_10bit.yres = height; + ndi_frame_10bit.FourCC = static_cast(NDI_LIB_FOURCC('V','2','1','0')); + ndi_frame_10bit.line_stride_in_bytes = mxl_stride; + ndi_frame_10bit.p_data = buf_10bit.data(); + + NDIlib_video_frame_v2_t ndi_frame_16bit{}; + ndi_frame_16bit.xres = width; + ndi_frame_16bit.yres = height; + ndi_frame_16bit.frame_rate_N = fps_num; + ndi_frame_16bit.frame_rate_D = fps_den; + ndi_frame_16bit.line_stride_in_bytes = width * static_cast(sizeof(uint16_t)); + ndi_frame_16bit.p_data = buf_16bit.data(); + + const mxlRational rate = {fps_num, fps_den}; uint64_t index = mxlGetCurrentIndex(&rate); uint64_t frame_count = 0; uint64_t invalid_count = 0; uint64_t late_count = 0; + uint64_t ndi_frame_count = 0; auto wall_start = std::chrono::steady_clock::now(); auto last_log_time = wall_start; - // NDI part - if (!NDIlib_initialize()) { - // Cannot run NDI. Most likely because the CPU is not sufficient (see SDK documentation). - log("Cannot run NDI"); - if (!NDIlib_is_supported_CPU()) { - log("CPU is not sufficient for NDI"); - } - return; - } - - NDIlib_send_create_t NDI_send_create_desc; - NDI_send_create_desc.p_ndi_name = flow_id.c_str(); - - NDIlib_send_instance_t pNDI_send = NDIlib_send_create(&NDI_send_create_desc); - if (!pNDI_send) { - log("Cannot create NDI send instance"); - return; - } - - NDIlib_video_frame_v2_t NDI_video_frame_10bit; - NDI_video_frame_10bit.xres = width; - NDI_video_frame_10bit.yres = height; - NDI_video_frame_10bit.FourCC = (NDIlib_FourCC_video_type_e)NDI_LIB_FOURCC('V', '2', '1', '0'); - - NDI_video_frame_10bit.line_stride_in_bytes = mxl_stride; - NDI_video_frame_10bit.p_data = (uint8_t*)malloc(NDI_video_frame_10bit.line_stride_in_bytes * NDI_video_frame_10bit.yres); - - NDIlib_video_frame_v2_t NDI_video_frame_16bit; - NDI_video_frame_16bit.xres = NDI_video_frame_10bit.xres; - NDI_video_frame_16bit.yres = NDI_video_frame_10bit.yres; - NDI_video_frame_16bit.frame_rate_N = fps_num; - NDI_video_frame_16bit.frame_rate_D = fps_den; - NDI_video_frame_16bit.line_stride_in_bytes = NDI_video_frame_16bit.xres * sizeof(uint16_t); - NDI_video_frame_16bit.p_data = (uint8_t*)malloc(NDI_video_frame_16bit.line_stride_in_bytes * 2 * NDI_video_frame_16bit.yres); - // - uint64_t ndi_frame_counter = 0; while (dmf::g_running.load(std::memory_order_relaxed)) { mxlGrainInfo grain{}; uint8_t* buf = nullptr; @@ -92,25 +103,26 @@ class NDIOutNode : public dmf::NodeBase { frame_count++; if (grain.flags & MXL_GRAIN_FLAG_INVALID) invalid_count++; index++; - if (NDIlib_send_get_no_connections(pNDI_send,0) == 0) { - ndi_frame_counter = 0; - continue; + + if (NDIlib_send_get_no_connections(ndi.sender, 0) > 0) { + std::memcpy(ndi_frame_10bit.p_data, buf, mxl_stride * height); + NDIlib_util_V210_to_P216(&ndi_frame_10bit, &ndi_frame_16bit); + NDIlib_send_send_video_v2(ndi.sender, &ndi_frame_16bit); + if (++ndi_frame_count == 1) + log("NDI receiver connected"); + } else { + ndi_frame_count = 0; } - memcpy(NDI_video_frame_10bit.p_data, buf, mxl_stride * height); - NDIlib_util_V210_to_P216(&NDI_video_frame_10bit, &NDI_video_frame_16bit); - NDIlib_send_send_video_v2(pNDI_send, &NDI_video_frame_16bit); - ndi_frame_counter++; - if (ndi_frame_counter == 1) - log("NDI receiver connected"); + } else if (st == MXL_ERR_OUT_OF_RANGE_TOO_EARLY) { - mxlSleepForNs(1'000'000); // 1 ms poll + mxlSleepForNs(1'000'000); } else if (st == MXL_ERR_OUT_OF_RANGE_TOO_LATE) { late_count++; - // Jump to the most recent frame in the ring buffer mxlFlowRuntimeInfo ri{}; mxlFlowReaderGetRuntimeInfo(reader, &ri); index = ri.headIndex; + } else { log("unexpected status=%d on index=%llu", st, index); break; @@ -129,14 +141,10 @@ class NDIOutNode : public dmf::NodeBase { log("stopped — total frames=%llu invalid=%llu late=%llu", frame_count, invalid_count, late_count); mxlReleaseFlowReader(instance(), reader); - free(NDI_video_frame_10bit.p_data); - free(NDI_video_frame_16bit.p_data); - NDIlib_send_destroy(pNDI_send); - NDIlib_destroy(); } }; int main() { NDIOutNode node; return node.execute(); -} \ No newline at end of file +}