#include #include #include #include "NodeBase.hpp" #include "FlowDef.hpp" #include "V210.hpp" #include "NDIHelper.hpp" #include class NDIInNode : public dmf::NodeBase { void run() override { try { dmf::NDIHelper ndi_helper; } catch (const std::runtime_error& e) { log("Error: %s", e.what()); return; } // Create a finder NDIlib_find_instance_t pNDI_find = NDIlib_find_create_v2(); if (!pNDI_find) { log("Cannot create NDI find instance"); return; } // Wait until there is one source uint32_t NDI_sources_amount = 0; const NDIlib_source_t* p_sources = NULL; while (!NDI_sources_amount) { // Wait until the sources on the network have changed log("Looking for NDI sources ..."); NDIlib_find_wait_for_sources(pNDI_find, 1000/* One second */); p_sources = NDIlib_find_get_current_sources(pNDI_find, &NDI_sources_amount); } log("Found %i NDI sources:", NDI_sources_amount); for (int i = 0; i < NDI_sources_amount; ++i){ log("%i) %s (%s)", i + 1, p_sources[i].p_ndi_name, p_sources[i].p_url_address); } NDIlib_recv_instance_t pNDI_recv = NDIlib_recv_create_v3(); if (!pNDI_recv) { log("Cannot create NDI recieve instance"); return; } NDIlib_recv_connect(pNDI_recv, p_sources + 0); // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] NDIlib_find_destroy(pNDI_find); // Recieve first frame to get info about NDI source data NDIlib_video_frame_v2_t NDI_video_frame; NDIlib_frame_type_e NDI_frame_type = NDIlib_recv_capture_v2(pNDI_recv, &NDI_video_frame, nullptr, nullptr, 1000); if (NDI_frame_type == NDIlib_frame_type_none) { log("Can't recieve NDI frame data"); NDIlib_recv_destroy(pNDI_recv); NDIlib_destroy(); return; } else if (NDI_frame_type == NDIlib_frame_type_audio) { log("Audio still not supported"); NDIlib_recv_destroy(pNDI_recv); NDIlib_destroy(); return; } log("NDI params: %i %i", NDI_video_frame.frame_rate_N, NDI_video_frame.frame_rate_D); char fourcc_str[5]; uint32_t fourcc = (uint32_t)NDI_video_frame.FourCC; fourcc_str[0] = (fourcc >> 0) & 0xFF; fourcc_str[1] = (fourcc >> 8) & 0xFF; fourcc_str[2] = (fourcc >> 16) & 0xFF; fourcc_str[3] = (fourcc >> 24) & 0xFF; fourcc_str[4] = '\0'; log("NDI fourCC=%s (0x%08x)", fourcc_str, fourcc); const auto flow_info = config().at("flow_id"); const auto flow_id = flow_info.at("id").get(); const int width = flow_info.value("width", NDI_video_frame.xres); const int height = flow_info.value("height", NDI_video_frame.yres); // const int fps_num = flow_info.value("fps_num", NDI_video_frame.frame_rate_N); // const int fps_den = flow_info.value("fps_den", NDI_video_frame.frame_rate_D); const int fps_num = flow_info.value("fps_num", 25); const int fps_den = flow_info.value("fps_den", 1); const uint32_t ndi_stride = NDI_video_frame.line_stride_in_bytes > 0 ? static_cast(NDI_video_frame.line_stride_in_bytes) : static_cast(width * 2); NDIlib_recv_free_video_v2(pNDI_recv, &NDI_video_frame); 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); // in case of P216 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 = stride; // for UYVY and 1 FPS ndi static frames const size_t frame_bytes = ndi_stride * height; uint8_t* latest_buffer = (uint8_t*)malloc(frame_bytes); bool last_ndi_frame_valid = false; while (dmf::g_running.load(std::memory_order_relaxed)) { if (NDIlib_recv_capture_v2(pNDI_recv, &NDI_video_frame, nullptr, nullptr, 5) == NDIlib_frame_type_video) { memcpy(latest_buffer, NDI_video_frame.p_data, frame_bytes); NDIlib_recv_free_video_v2(pNDI_recv, &NDI_video_frame); last_ndi_frame_valid = true; } 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 (last_ndi_frame_valid) { log("stride: %i ndi_stride: %i", stride, ndi_stride); // UYVY -> v210 const uint8_t* src = latest_buffer; uint8_t* dst = buf; const int blocks = width / 6; for (int y = 0; y < height; y++) { for (int b = 0; b < blocks; b++) { const uint8_t* mp = src + b * 12; // 3 macropixels = 12 bytes // mp[0]=U0, mp[1]=Y0, mp[2]=V0, mp[3]=Y1 // mp[4]=U1, mp[5]=Y2, mp[6]=V1, mp[7]=Y3 // mp[8]=U2, mp[9]=Y4, mp[10]=V2, mp[11]=Y5 dmf::v210::pack_block(dst + b * 16, {0, (uint16_t)(mp[0]<<2), (uint16_t)(mp[2]<<2)}, (uint16_t)(mp[1]<<2), (uint16_t)(mp[3]<<2), {0, (uint16_t)(mp[4]<<2), (uint16_t)(mp[6]<<2)}, (uint16_t)(mp[5]<<2), (uint16_t)(mp[7]<<2), {0, (uint16_t)(mp[8]<<2), (uint16_t)(mp[10]<<2)}, (uint16_t)(mp[9]<<2), (uint16_t)(mp[11]<<2) ); } src += ndi_stride; dst += stride; } grain.flags = 0; } else { grain.flags = MXL_GRAIN_FLAG_INVALID; } grain.validSlices = grain.totalSlices; // mark grain complete so readers can consume it 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); free(latest_buffer); mxlReleaseFlowWriter(instance(), writer); NDIlib_recv_destroy(pNDI_recv); NDIlib_destroy(); } }; int main() { NDIInNode node; return node.execute(); }