diff --git a/nodes/ndiin/main.cpp b/nodes/ndiin/main.cpp index ddf1bd1..c33f691 100644 --- a/nodes/ndiin/main.cpp +++ b/nodes/ndiin/main.cpp @@ -6,89 +6,32 @@ #include "V210.hpp" #include "NDIHelper.hpp" #include +#include class NDIInNode : public dmf::NodeBase { void run() override { + dmf::NDIHelper ndi_helper; try { - dmf::NDIHelper ndi_helper; + std::vector ndi_sources; + ndi_helper.find_sources(&ndi_sources, 5000); + log("Available NDI sources:"); + for (const auto& source_name : ndi_sources){ + log("%s", source_name.c_str()); + } + uint32_t source_num = 0; + ndi_helper.select_source(source_num); + ndi_helper.get_source_info(source_num); } 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); + const int width = flow_info.value("width", ndi_helper.xres); + const int height = flow_info.value("height", ndi_helper.yres); + const int fps_num = flow_info.value("fps_num", ndi_helper.frame_N); + const int fps_den = flow_info.value("fps_den", ndi_helper.frame_D); log("flow=%s %dx%d @ %d/%d fps", flow_id.c_str(), width, height, fps_num, fps_den); @@ -114,26 +57,18 @@ class NDIInNode : public dmf::NodeBase { 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; + // because NDI can drop to 1 FPS for static frames, even if source is 29.97p + const size_t frame_bytes = 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); + if (ndi_helper.getV210_video_frame(0, latest_buffer, stride)) { last_ndi_frame_valid = true; } + + uint8_t* buf = nullptr; mxlGrainInfo grain{}; - uint8_t* buf = nullptr; st = mxlFlowWriterOpenGrain(writer, index, &grain, &buf); if (st != MXL_STATUS_OK) { @@ -141,29 +76,9 @@ class NDIInNode : public dmf::NodeBase { 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; - } + std::memcpy(buf, latest_buffer, frame_bytes); grain.flags = 0; } else { grain.flags = MXL_GRAIN_FLAG_INVALID; @@ -180,8 +95,6 @@ class NDIInNode : public dmf::NodeBase { log("stopped at index=%llu", index); free(latest_buffer); mxlReleaseFlowWriter(instance(), writer); - NDIlib_recv_destroy(pNDI_recv); - NDIlib_destroy(); } }; diff --git a/shared/NDIHelper.hpp b/shared/NDIHelper.hpp index dff91da..6db5a89 100644 --- a/shared/NDIHelper.hpp +++ b/shared/NDIHelper.hpp @@ -1,24 +1,28 @@ #include #include #include -#include #include +#include "V210.hpp" namespace dmf { class NDIHelper { public: + + int xres = 0, yres = 0, frame_D = 0, frame_N = 0, stride = 0; + NDIHelper() { + if (!NDIlib_is_supported_CPU()) { + throw std::runtime_error("CPU is not sufficient for NDI"); + } if (!NDIlib_initialize()) { throw std::runtime_error("NDI lib init failed"); - if (!NDIlib_is_supported_CPU()) { - throw std::runtime_error("CPU is not sufficient for NDI"); - } } } ~NDIHelper() { + if (pNDI_recv) NDIlib_recv_destroy(pNDI_recv); + if (pNDI_find) NDIlib_find_destroy(pNDI_find); NDIlib_destroy(); - NDIlib_recv_destroy(pNDI_recv); } void find_sources(std::vector* sources, u_int32_t timeout_ms) { @@ -27,46 +31,164 @@ class NDIHelper { throw std::runtime_error("Cannot create NDI finder"); } - while (!sources_amount) { + const int max_attempts = 10; + uint32_t sources_amount = 0; + const NDIlib_source_t* p_sources = nullptr; + for (int attempt = 0; !sources_amount && attempt < max_attempts; ++attempt) { NDIlib_find_wait_for_sources(pNDI_find, timeout_ms); p_sources = NDIlib_find_get_current_sources(pNDI_find, &sources_amount); } - - for (int i = 0; i < sources_amount; ++i){ - sources->push_back(p_sources[i].p_ndi_name); + if (!sources_amount) { + NDIlib_find_destroy(pNDI_find); + pNDI_find = nullptr; + throw std::runtime_error("No NDI sources found after timeout"); } + + // Copy while finder is alive: p_sources points into finder-owned memory + for (uint32_t i = 0; i < sources_amount; ++i) { + sources->push_back(p_sources[i].p_ndi_name); + cached_names.emplace_back(p_sources[i].p_ndi_name); + cached_urls.emplace_back(p_sources[i].p_url_address); + } + cached_sources.reserve(cached_names.size()); + for (uint32_t i = 0; i < cached_names.size(); ++i) { + cached_sources.push_back({cached_names[i].c_str(), cached_urls[i].c_str()}); + } + NDIlib_find_destroy(pNDI_find); + pNDI_find = nullptr; } void select_source(uint32_t source_num) { - if (sources_amount == 0) { + if (cached_sources.empty()) { throw std::runtime_error("0 sources found"); - } else if (source_num > sources_amount) { + } else if (source_num >= cached_sources.size()) { throw std::runtime_error("Source_num bigger that sources amount"); } pNDI_recv = NDIlib_recv_create_v3(); if (!pNDI_recv) { - NDIlib_recv_destroy(pNDI_recv); throw std::runtime_error("Cannot create NDI recieve instance"); } - NDIlib_recv_connect(pNDI_recv, p_sources + source_num); + NDIlib_recv_connect(pNDI_recv, &cached_sources[source_num]); + } + + void get_source_info(uint32_t source_num) { + NDIlib_video_frame_v2_t video_frame; + NDIlib_frame_type_e frame_type; + bool is_got_info = false; + while(!is_got_info) + { + frame_type = NDIlib_recv_capture_v3(pNDI_recv, &video_frame, nullptr, nullptr, 1000); + switch(frame_type) + { + case NDIlib_frame_type_video: + is_got_info = true; + xres = video_frame.xres; + yres = video_frame.yres; + frame_D = video_frame.frame_rate_D; + frame_N = video_frame.frame_rate_N; + fourCC = video_frame.FourCC; + stride = video_frame.line_stride_in_bytes; + if (stride == 0) { + stride = xres * get_bytes_per_pixel(fourCC); + } + break; + case NDIlib_frame_type_error: + is_got_info = true; + throw std::runtime_error("Selected NDI source is lost"); + break; + } + } + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + } + + int get_bytes_per_pixel(NDIlib_FourCC_video_type_e fourCC) { + switch (fourCC) { + case NDIlib_FourCC_video_type_UYVY: // Standard 8-bit YUV 4:2:2 + case NDIlib_FourCC_video_type_YV12: // 8-bit YUV 4:2:0 + case NDIlib_FourCC_video_type_I420: // 8-bit YUV 4:2:0 + case NDIlib_FourCC_video_type_NV12: // 8-bit YUV 4:2:0 + // These are 4:2:2 or 4:2:0 formats. + // On average, they use 2 bytes (16 bits) per pixel across the macroblock. + return 2; + + case NDIlib_FourCC_video_type_BGRA: // 8-bit RGB with Alpha + case NDIlib_FourCC_video_type_RGBA: // 8-bit RGB with Alpha + // 4 channels (Red, Green, Blue, Alpha) * 1 byte each + return 4; + + case NDIlib_FourCC_video_type_BGRX: // 8-bit RGB (Padding) + case NDIlib_FourCC_video_type_RGBX: // 8-bit RGB (Padding) + // 4 channels (Red, Green, Blue, Empty) * 1 byte each + return 4; + + case NDIlib_FourCC_video_type_UYVA: // 8-bit YUV 4:2:2 + Alpha channel + // 2 bytes for YUV + 1 byte for Alpha split + return 3; + + case NDIlib_FourCC_video_type_P216: // 16-bit YUV 4:2:2 (High bit depth) + // 2 channels packed at 2 bytes (16-bits) per sample = 4 bytes per pixel + return 4; + + case NDIlib_FourCC_video_type_PA16: // 16-bit YUV 4:2:2 + 16-bit Alpha + return 6; + + default: + return 2; // Safe NDI default fallback + } + } + + std::string fourCCtoStr() { + char fourcc_str[5]; + uint32_t fourcc = (uint32_t)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'; + return std::string(fourcc_str); + } + + bool getV210_video_frame(uint32_t source_num, uint8_t* frame_buffer, uint32_t frame_stride) { + NDIlib_video_frame_v2_t video_frame; + NDIlib_frame_type_e frame_type; + frame_type = NDIlib_recv_capture_v3(pNDI_recv, &video_frame, nullptr, nullptr, 5); + switch(frame_type) + { + case NDIlib_frame_type_error: + throw std::runtime_error("NDI source lost"); + case NDIlib_frame_type_status_change: + throw std::runtime_error("NDI source resolution or framerate are changed"); + + } + if (frame_type != NDIlib_frame_type_video) { + return false; + } + switch(fourCC) + { + case NDIlib_FourCC_type_UYVY: + v210::UYVYtoV210(video_frame.p_data, frame_buffer, xres, yres, stride, frame_stride); + break; + case NDIlib_FourCC_type_P216: + NDIlib_video_frame_v2_t video_frame_10bit; + NDIlib_util_P216_to_V210(&video_frame, &video_frame_10bit); + frame_buffer = video_frame.p_data; + default: + throw std::runtime_error("Color format is not supported yet"); + } + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + return true; } private: // receive NDIlib_find_instance_t pNDI_find = nullptr; - uint32_t sources_amount = 0; - const NDIlib_source_t* p_sources = NULL; NDIlib_recv_instance_t pNDI_recv = nullptr; - - void get_source_info(uint32_t source_num) { - NDIlib_video_frame_v2_t video_frame; - uint8_t frames = 0; - while (frames < 2) { - NDIlib_recv_capture_v2(pNDI_recv, &video_frame, nullptr, nullptr, 1000); - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } - } + NDIlib_FourCC_type_e fourCC; + // owned copies so finder can be destroyed early + std::vector cached_names; + std::vector cached_urls; + std::vector cached_sources; }; } \ No newline at end of file diff --git a/shared/V210.hpp b/shared/V210.hpp index f4ee4e8..e3e9fd4 100644 --- a/shared/V210.hpp +++ b/shared/V210.hpp @@ -90,4 +90,28 @@ inline void fill_frame(uint8_t* buf, int width, int height, uint32_t stride) } } +inline void UYVYtoV210(uint8_t* src_buf, uint8_t* dst_buf, int width, int height, uint32_t src_stride, uint32_t dst_stride) +{ + const uint8_t* src = src_buf; + uint8_t* dst = 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 += src_stride; + dst += dst_stride; + } +} + } // namespace dmf::v210