#include #include #include #include #include #include "NodeBase.hpp" #include "FlowDef.hpp" #include "NDIReceiver.hpp" class NDIInNode : public dmf::NodeBase { void run() override { const auto source_num = static_cast(config().value("source_num", 0)); dmf::NDIReceiver ndi; dmf::NDIReceiver::SourceInfo src; try { auto sources = ndi.find_sources(5000); log("Available NDI sources:"); for (const auto& name : sources) log(" %s", name.c_str()); ndi.connect(source_num); src = ndi.probe(); } catch (const std::runtime_error& e) { log("Error: %s", e.what()); return; } const auto flow_info = config().at("flow_id"); const auto flow_id = flow_info.at("id").get(); const int width = flow_info.value("width", src.width); const int height = flow_info.value("height", src.height); const int fps_num = flow_info.value("fps_num", src.fps_num); const int fps_den = flow_info.value("fps_den", src.fps_den); log("flow=%s %dx%d @ %d/%d fps", flow_id.c_str(), width, height, fps_num, fps_den); const std::string flow_def = dmf::make_video_flow_def(flow_id, node_id(), width, height, fps_num, fps_den); mxlFlowWriter writer{}; mxlFlowConfigInfo cfg_info{}; bool created = false; mxlStatus st = mxlCreateFlowWriter( instance(), flow_def.c_str(), nullptr, &writer, &cfg_info, &created); if (st != MXL_STATUS_OK) { log("mxlCreateFlowWriter failed (status=%d)", st); return; } const uint32_t stride = cfg_info.discrete.sliceSizes[0]; log("stride=%u B/line grain=%u B ring=%u grains", stride, stride * static_cast(height), cfg_info.discrete.grainCount); const mxlRational rate = {fps_num, fps_den}; uint64_t index = mxlGetCurrentIndex(&rate); log("start index=%llu", index); // NDI can drop to 1fps for static content — hold last valid frame std::vector latest_frame(stride * height); bool have_frame = false; while (dmf::g_running.load(std::memory_order_relaxed)) { try { if (ndi.capture_v210(latest_frame.data(), stride)) have_frame = true; } catch (const std::runtime_error& e) { log("NDI error: %s — stopping", e.what()); break; } mxlGrainInfo grain{}; uint8_t* buf = nullptr; st = mxlFlowWriterOpenGrain(writer, index, &grain, &buf); if (st != MXL_STATUS_OK) { log("OpenGrain failed (status=%d), skipping index=%llu", st, index); index++; continue; } if (have_frame) { std::memcpy(buf, latest_frame.data(), latest_frame.size()); grain.flags = 0; } else { grain.flags = MXL_GRAIN_FLAG_INVALID; } grain.validSlices = grain.totalSlices; mxlFlowWriterCommitGrain(writer, &grain); const uint64_t ns = mxlGetNsUntilIndex(index + 1, &rate); if (ns > 0 && ns < 2'000'000'000ULL) mxlSleepForNs(ns); index++; } log("stopped at index=%llu", index); mxlReleaseFlowWriter(instance(), writer); } }; int main() { NDIInNode node; return node.execute(); }