diff --git a/configure.ac b/configure.ac index 9d82c0f5c..710154b49 100644 --- a/configure.ac +++ b/configure.ac @@ -3205,25 +3205,16 @@ AC_ARG_ENABLE(ndi, [ndi_req=$build_default] ) -if test $system = Windows; then - NDI_LIB=Processing.NDI.Lib.x64 -else - NDI_LIB=ndi -fi - AC_CHECK_HEADER(Processing.NDI.Lib.h, FOUND_NDI_H=yes, FOUND_NDI_H=no) -AC_CHECK_LIB($NDI_LIB, NDIlib_initialize, FOUND_NDI_L=yes, FOUND_NDI_L=no) # if NDI_SDK_DIR is defined in Windows, ignore autoconf tests -if test $ndi_req != no -a \( \( "$FOUND_NDI_H" = yes -a "$FOUND_NDI_L" = yes \) -o \( -n "$NDI_SDK_DIR" -a $system = Windows \) \) +if test $ndi_req != no && ( test "$FOUND_NDI_H" = yes || ( test -n "$NDI_SDK_DIR" && test $system = Windows ) ) then - if test -n "$NDI_SDK_DIR" -a $system = Windows; then + if test -n "$NDI_SDK_DIR" && test $system = Windows; then COMMON_FLAGS="$COMMON_FLAGS -I\"$NDI_SDK_DIR/Include\"" - NDI_LIB_PATH="-L\"$NDI_SDK_DIR/Lib/x64\"" - DLL_LIBS="$DLL_LIBS \"$NDI_SDK_DIR/Bin/x64/Processing.NDI.Lib.x64.dll\"" fi - ADD_MODULE("vidcap_ndi", "src/video_capture/ndi.o", "-l$NDI_LIB $NDI_LIB_PATH") - ADD_MODULE("display_ndi", "src/video_display/ndi.o", "-l$NDI_LIB $NDI_LIB_PATH") + ADD_MODULE("vidcap_ndi", "src/video_capture/ndi.o", "") + ADD_MODULE("display_ndi", "src/video_display/ndi.o", "") ndi=yes fi diff --git a/src/ndi_common.h b/src/ndi_common.h new file mode 100644 index 000000000..370ddf3ff --- /dev/null +++ b/src/ndi_common.h @@ -0,0 +1,154 @@ +/** + * @file ndi_common.h + * @author Martin Pulec + */ +/* + * Copyright (c) 2022 CESNET, z. s. p. o. + * All rights reserved. + * + * Using sample code from NDI. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NDI_COMMON_H_1A76D048_695C_4247_A24A_583C29010FC4 +#define NDI_COMMON_H_1A76D048_695C_4247_A24A_583C29010FC4 + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#define NDILIB_CPP_DEFAULT_CONSTRUCTORS 0 +#include + +#include "debug.h" +#include "lib_common.h" // LIB_HANDLE, dlclose/dlerror abstraction + +#define NDILIB_NDI_LOAD "NDIlib_v5_load" +typedef NDIlib_v5 NDIlib_t; +typedef const NDIlib_t* NDIlib_load_f(void); + +static const NDIlib_t *NDIlib_load(LIB_HANDLE *lib) { +#ifdef _WIN32 + // We check whether the NDI run-time is installed + const char* p_ndi_runtime = getenv(NDILIB_REDIST_FOLDER); + if (!p_ndi_runtime) { // The NDI run-time is not yet installed. Let the user know and take them to the download URL. + //MessageBoxA(NULL, "Please install the NewTek NDI Runtimes to use this application from " NDILIB_REDIST_URL ".", "Runtime Warning.", MB_OK); + //ShellExecuteA(NULL, "open", NDILIB_REDIST_URL, 0, 0, SW_SHOWNORMAL); + log_msg(LOG_LEVEL_WARNING, "[NDI] " NDILIB_REDIST_FOLDER " environment variable not defined. " + "Please install the NewTek NDI Runtimes to use this application from " NDILIB_REDIST_URL ".\n"); + return 0; + } + + // We now load the DLL as it is installed + char *ndi_path = (char *) alloca(strlen(p_ndi_runtime) + 2 + strlen(NDILIB_LIBRARY_NAME) + 1); + strcpy(ndi_path, p_ndi_runtime); // NOLINT (security.insecureAPI.strcpy) + strcat(ndi_path, "\\"); // NOLINT (security.insecureAPI.strcpy) + strcat(ndi_path, NDILIB_LIBRARY_NAME); // NOLINT (security.insecureAPI.strcpy) + + // Try to load the library + HMODULE hNDILib = LoadLibraryA(ndi_path); + + // The main NDI entry point for dynamic loading if we got the library + const NDIlib_t* (*NDIlib_load)(void) = NULL; + if (hNDILib) { + *((FARPROC*)&NDIlib_load) = GetProcAddress(hNDILib, NDILIB_NDI_LOAD); + } + + // If we failed to load the library then we tell people to re-install it + if (!NDIlib_load) { // Unload the DLL if we loaded it + // The NDI run-time is not installed correctly. Let the user know and take them to the download URL. + log_msg(LOG_LEVEL_ERROR, "[NDI] Failed to load " NDILIB_NDI_LOAD " from NDI: %s.\n" + "Try to reinstall the NewTek NDI Runtimes to use this application from " NDILIB_REDIST_URL ".\n", dlerror()); + if (hNDILib) { + FreeLibrary(hNDILib); + } + + return 0; + } +#else + const char* p_NDI_runtime_folder = getenv(NDILIB_REDIST_FOLDER); + char *ndi_path = (char *) alloca((p_NDI_runtime_folder != NULL ? strlen(p_NDI_runtime_folder) + 1 : 0)+ strlen(NDILIB_LIBRARY_NAME) + 1); + if (p_NDI_runtime_folder) { + strcpy(ndi_path, p_NDI_runtime_folder); // NOLINT (security.insecureAPI.strcpy) + strcat(ndi_path, "/"); // NOLINT (security.insecureAPI.strcpy) + } else { + ndi_path[0] = '\0'; + } + strcat(ndi_path, NDILIB_LIBRARY_NAME); // NOLINT (security.insecureAPI.strcpy) + + // Try to load the library + void *hNDILib = dlopen(ndi_path, RTLD_LOCAL | RTLD_LAZY); + + // The main NDI entry point for dynamic loading if we got the library + const NDIlib_t* (*NDIlib_load)(void) = NULL; + if (hNDILib) { + *((void**)&NDIlib_load) = dlsym(hNDILib, NDILIB_NDI_LOAD); + } + + // If we failed to load the library then we tell people to re-install it + if (!NDIlib_load) { // Unload the library if we loaded it + log_msg(LOG_LEVEL_ERROR, "[NDI] Failed to open the library: %s\n", dlerror()); + if (strlen(NDILIB_REDIST_URL) != 0) { + log_msg(LOG_LEVEL_ERROR, "[NDI] Please re-install the NewTek NDI Runtimes from " NDILIB_REDIST_URL " to use this application.\n"); + } else { // NDILIB_REDIST_URL is set to "" in Linux + log_msg(LOG_LEVEL_ERROR, "[NDI] Please install " NDILIB_LIBRARY_NAME " from the NewTek NDI SDK either to system libraries' path or set " + NDILIB_REDIST_FOLDER " environment " + "variable to a path containing the libndi.so file (eg. \"export " NDILIB_REDIST_FOLDER "=/lib/x86_64-linux-gnu\").\n"); + } + + if (hNDILib) { + dlclose(hNDILib); + } + return 0; + } +#endif + const NDIlib_t *ret = NDIlib_load(); + if (ret == NULL) { + dlclose(hNDILib); + } else { + *lib = hNDILib; + } + return ret; +} + +static void close_ndi_library(LIB_HANDLE hNDILib) { + if (!hNDILib) { + return; + } + dlclose(hNDILib); +} + +#endif // defined NDI_COMMON_H_1A76D048_695C_4247_A24A_583C29010FC4 + diff --git a/src/video_capture/ndi.cpp b/src/video_capture/ndi.cpp index fe4491fcd..0545357cd 100644 --- a/src/video_capture/ndi.cpp +++ b/src/video_capture/ndi.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2018-2021 CESNET, z. s. p. o. + * Copyright (c) 2018-2022 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,26 +48,19 @@ #include "config_win32.h" #endif -#include #include #include #include #include +#include #include #include // static_assert -#ifdef _WIN32 -#ifdef _WIN64 -#pragma comment(lib, "Processing.NDI.Lib.x64.lib") -#else // _WIN64 -#pragma comment(lib, "Processing.NDI.Lib.x86.lib") -#endif // _WIN64 -#endif // _WIN32 - #include "audio/types.h" #include "audio/utils.h" #include "debug.h" #include "lib_common.h" +#include "ndi_common.h" #include "rang.hpp" #include "video.h" #include "video_capture.h" @@ -87,8 +80,12 @@ using std::string; using std::chrono::duration_cast; using std::chrono::steady_clock; +static void vidcap_ndi_done(void *state); + struct vidcap_state_ndi { - static_assert(NDILIB_CPP_DEFAULT_CONSTRUCTORS == 1, "Use default C++ NDI constructors"); + static_assert(NDILIB_CPP_DEFAULT_CONSTRUCTORS == 0, "Don't use default C++ NDI constructors - we are using run-time dynamic lib load"); + LIB_HANDLE lib{}; + const NDIlib_t *NDIlib{}; NDIlib_recv_instance_t pNDI_recv = nullptr; NDIlib_find_instance_t pNDI_find = nullptr; array audio; @@ -96,7 +93,7 @@ struct vidcap_state_ndi { bool capture_audio = false; struct video_desc last_desc{}; - NDIlib_video_frame_v2_t field_0; ///< stored to asssemble interleaved interlaced video together with field 1 + NDIlib_video_frame_v2_t field_0{}; ///< stored to asssemble interleaved interlaced video together with field 1 string requested_name; // if not empty recv from requested NDI name string requested_url; // if not empty recv from requested URL (either addr or addr:port) @@ -124,7 +121,7 @@ struct vidcap_state_ndi { } }; -static void show_help(const NDIlib_find_create_t *find_create_settings) { +static void show_help(struct vidcap_state_ndi *s) { cout << "Usage:\n" "\t" << rang::style::bold << rang::fg::red << "-t ndi" << rang::fg::reset << "[:help][:name=][:url=][:audio_level=][:color=][:extra_ips=][:progressive]\n" << rang::style::reset << @@ -146,7 +143,7 @@ static void show_help(const NDIlib_find_create_t *find_create_settings) { "\n"; cout << "\tavailable sources (tentative, format: name - url):\n"; - auto *pNDI_find = NDIlib_find_create_v2(find_create_settings); + auto *pNDI_find = s->NDIlib->find_create_v2(&s->find_create_settings); if (pNDI_find == nullptr) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create finder object!\n"; return; @@ -159,7 +156,7 @@ static void show_help(const NDIlib_find_create_t *find_create_settings) { // we do not usea NDIlib_find_wait_for_sources() here because: 1) if there is // no source, it will still wait requested amount of time and 2) if there are // more sources, it will continue after first source found while there can be more - p_sources = NDIlib_find_get_current_sources(pNDI_find, &nr_sources); + p_sources = s->NDIlib->find_get_current_sources(pNDI_find, &nr_sources); for (int i = 0; i < static_cast(nr_sources); ++i) { cout << "\t\t" << p_sources[i].p_ndi_name << " - " << p_sources[i].p_url_address << "\n"; } @@ -167,7 +164,7 @@ static void show_help(const NDIlib_find_create_t *find_create_settings) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "No sources found!\n"; } cout << "\n"; - NDIlib_find_destroy(pNDI_find); + s->NDIlib->find_destroy(pNDI_find); #ifdef NDI_VERSION cout << NDI_VERSION "\n"; #endif @@ -178,11 +175,18 @@ static int vidcap_ndi_init(struct vidcap_params *params, void **state) using namespace std::string_literals; using std::stoi; // Not required, but "correct" (see the SDK documentation) - if (!NDIlib_initialize()) { - LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot initialize NDI!\n"; + auto s = new vidcap_state_ndi(); + s->NDIlib = NDIlib_load(&s->lib); + if (s->NDIlib == NULL) { + LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot open NDI library!\n"; + delete s; + return VIDCAP_INIT_FAIL; + } + if (!s->NDIlib->initialize()) { + LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot initialize NDI!\n"; + delete s; return VIDCAP_INIT_FAIL; } - auto s = new vidcap_state_ndi(); if ((vidcap_params_get_flags(params) & VIDCAP_FLAG_AUDIO_ANY) != 0u) { s->capture_audio = true; } @@ -239,8 +243,8 @@ static int vidcap_ndi_init(struct vidcap_params *params, void **state) } if (req_show_help) { - show_help(&s->find_create_settings); - delete s; + show_help(s); + vidcap_ndi_done(s); return VIDCAP_INIT_NOERR; } @@ -257,16 +261,17 @@ static void vidcap_ndi_done(void *state) } if (s->field_0.p_data != nullptr) { - NDIlib_recv_free_video_v2(s->pNDI_recv, &s->field_0); + s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0); } // Destroy the NDI finder. - NDIlib_find_destroy(s->pNDI_find); + s->NDIlib->find_destroy(s->pNDI_find); - NDIlib_recv_destroy(s->pNDI_recv); + s->NDIlib->recv_destroy(s->pNDI_recv); // Not required, but nice - NDIlib_destroy(); + s->NDIlib->destroy(); + close_ndi_library(s->lib); delete s; } @@ -389,7 +394,7 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud if (s->pNDI_find == nullptr) { // Create a finder - s->pNDI_find = NDIlib_find_create_v2(&s->find_create_settings); + s->pNDI_find = s->NDIlib->find_create_v2(&s->find_create_settings); if (s->pNDI_find == nullptr) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create object!\n"; return nullptr; @@ -402,8 +407,8 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud const NDIlib_source_t* p_sources = nullptr; // Wait until the sources on the nwtork have changed LOG(LOG_LEVEL_INFO) << MOD_NAME << "Looking for source(s)...\n"; - NDIlib_find_wait_for_sources(s->pNDI_find, 100 /* 100 ms */); - p_sources = NDIlib_find_get_current_sources(s->pNDI_find, &nr_sources); + s->NDIlib->find_wait_for_sources(s->pNDI_find, 100 /* 100 ms */); + p_sources = s->NDIlib->find_get_current_sources(s->pNDI_find, &nr_sources); if (nr_sources == 0) { LOG(LOG_LEVEL_WARNING) << MOD_NAME << "No sources.\n"; return nullptr; @@ -415,13 +420,13 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud } // We now have at least one source, so we create a receiver to look at it. - s->pNDI_recv = NDIlib_recv_create_v3(&s->create_settings); + s->pNDI_recv = s->NDIlib->recv_create_v3(&s->create_settings); if (s->pNDI_recv == nullptr) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unable to create receiver!\n"; return nullptr; } // Connect to our sources - NDIlib_recv_connect(s->pNDI_recv, source); + s->NDIlib->recv_connect(s->pNDI_recv, source); LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Receiving from source: " << source->p_ndi_name << ", URL: " << source->p_url_address << "\n"; } @@ -432,7 +437,7 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud struct video_frame *out = nullptr; video_desc out_desc; - switch (NDIlib_recv_capture_v2(s->pNDI_recv, &video_frame, &audio_frame, nullptr, 200)) + switch (s->NDIlib->recv_capture_v2(s->pNDI_recv, &video_frame, &audio_frame, nullptr, 200)) { // No data case NDIlib_frame_type_none: cout << "No data received.\n"; @@ -481,8 +486,8 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Received video changed: " << out_desc << "\n"; s->last_desc = out_desc; if (s->field_0.p_data != nullptr) { - NDIlib_recv_free_video_v2(s->pNDI_recv, &s->field_0); - s->field_0 = NDIlib_video_frame_v2_t(); + s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0); + s->field_0 = NDIlib_video_frame_v2_t{}; } } @@ -505,14 +510,14 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Missing corresponding field!\n"; } else { convert(out, s->field_0.p_data, stride, 0, field_count); - NDIlib_recv_free_video_v2(s->pNDI_recv, &s->field_0); - s->field_0 = NDIlib_video_frame_v2_t(); + s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0); + s->field_0 = NDIlib_video_frame_v2_t{}; } convert(out, video_frame.p_data, stride, 1, field_count); } else { convert(out, video_frame.p_data, stride, 0, 1); } - NDIlib_recv_free_video_v2(s->pNDI_recv, &video_frame); + s->NDIlib->recv_free_video_v2(s->pNDI_recv, &video_frame); out->callbacks.dispose = vf_free; } else { out = vf_alloc_desc(out_desc); @@ -520,13 +525,15 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud struct dispose_udata_t { NDIlib_video_frame_v2_t video_frame; NDIlib_recv_instance_t pNDI_recv; + void(*recv_free_video_v2)(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); }; - out->callbacks.dispose_udata = new dispose_udata_t{video_frame, s->pNDI_recv}; - out->callbacks.dispose = [](struct video_frame *f) { auto du = static_cast(f->callbacks.dispose_udata); - NDIlib_recv_free_video_v2(du->pNDI_recv, &du->video_frame); + out->callbacks.dispose_udata = new dispose_udata_t{video_frame, s->pNDI_recv, s->NDIlib->recv_free_video_v2}; + static auto dispose = [](struct video_frame *f) { auto du = static_cast(f->callbacks.dispose_udata); + du->recv_free_video_v2(du->pNDI_recv, &du->video_frame); delete du; free(f); }; + out->callbacks.dispose = dispose; } s->frames += 1; s->print_stats(); @@ -543,7 +550,7 @@ static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **aud } else { *audio = nullptr; } - NDIlib_recv_free_audio_v2(s->pNDI_recv, &audio_frame); + s->NDIlib->recv_free_audio_v2(s->pNDI_recv, &audio_frame); break; case NDIlib_frame_type_metadata: @@ -577,7 +584,15 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void return vt; } - auto pNDI_find = NDIlib_find_create_v2(); + LIB_HANDLE tmp = nullptr; + const NDIlib_t *NDIlib = NDIlib_load(&tmp); + if (NDIlib == nullptr) { + LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot open NDI library!\n"; + return vt; + } + std::unique_ptr::type, void (*)(LIB_HANDLE)> lib(tmp, close_ndi_library); + + auto pNDI_find = NDIlib->find_create_v2(nullptr); if (pNDI_find == nullptr) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create finder object!\n"; return vt; @@ -590,11 +605,11 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void // we do not usea NDIlib_find_wait_for_sources() here because: 1) if there is // no source, it will still wait requested amount of time and 2) if there are // more sources, it will continue after first source found while there can be more - p_sources = NDIlib_find_get_current_sources(pNDI_find, &nr_sources); + p_sources = NDIlib->find_get_current_sources(pNDI_find, &nr_sources); vt->cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info)); if (vt->cards == nullptr) { - NDIlib_find_destroy(pNDI_find); + NDIlib->find_destroy(pNDI_find); return vt; } vt->card_count = nr_sources; @@ -605,7 +620,7 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void vt->cards[i].repeatable = true; } - NDIlib_find_destroy(pNDI_find); + NDIlib->find_destroy(pNDI_find); return vt; } diff --git a/src/video_display/ndi.c b/src/video_display/ndi.c index 10d226369..fd098f60a 100644 --- a/src/video_display/ndi.c +++ b/src/video_display/ndi.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2019-2021 CESNET, z. s. p. o. + * Copyright (c) 2019-2022 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,12 +51,12 @@ #include "config_win32.h" #endif // HAVE_CONFIG_H -#include #include "audio/types.h" #include "audio/utils.h" #include "debug.h" #include "lib_common.h" +#include "ndi_common.h" #include "types.h" #include "utils/color_out.h" #include "utils/misc.h" @@ -67,6 +67,8 @@ #define MOD_NAME "[NDI disp.] " struct display_ndi { + LIB_HANDLE lib; + const NDIlib_t *NDIlib; int audio_level; ///< audio reference level - usually 0 or 20 NDIlib_send_instance_t pNDI_send; struct video_desc desc; @@ -165,7 +167,14 @@ static void *display_ndi_init(struct module *parent, const char *fmt, unsigned i tmp = NULL; } - if (!NDIlib_initialize()) { + s->NDIlib = NDIlib_load(&s->lib); + if (s->NDIlib == NULL) { + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot open NDI library!\n"); + THROW(FAIL); + } + + if (!s->NDIlib->initialize()) { + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot initialize NDI library!\n"); THROW(FAIL); } @@ -173,7 +182,7 @@ static void *display_ndi_init(struct module *parent, const char *fmt, unsigned i NDI_send_create_desc.clock_video = false; NDI_send_create_desc.clock_audio = false; NDI_send_create_desc.p_ndi_name = ndi_name; - s->pNDI_send = NDIlib_send_create(&NDI_send_create_desc); + s->pNDI_send = s->NDIlib->send_create(&NDI_send_create_desc); if (s->pNDI_send == NULL) { THROW(FAIL); } @@ -197,10 +206,11 @@ static void display_ndi_done(void *state) { struct display_ndi *s = (struct display_ndi *) state; - NDIlib_send_destroy(s->pNDI_send); + s->NDIlib->send_destroy(s->pNDI_send); free(s->convert_buffer); + s->NDIlib->destroy(); + close_ndi_library(s->lib); free(s); - NDIlib_destroy(); } static struct video_frame *display_ndi_getf(void *state) @@ -281,7 +291,7 @@ static int display_ndi_putf(void *state, struct video_frame *frame, int flag) NDI_video_frame.p_data = (uint8_t *) frame->tiles[0].data; } - NDIlib_send_send_video_v2(s->pNDI_send, &NDI_video_frame); + s->NDIlib->send_send_video_v2(s->pNDI_send, &NDI_video_frame); vf_free(frame); return TRUE; @@ -354,7 +364,7 @@ static int display_ndi_get_property(void *state, int property, void *val, size_t NDI_audio_frame.p_data = (int ## bit_depth ## _t *)(void *) (frame)->data; \ NDI_audio_frame.no_samples = (frame)->data_len / (frame)->ch_count / ((bit_depth) / 8); \ \ - NDIlib_util_send_send_audio_interleaved_ ## bit_depth ## s(s->pNDI_send, &NDI_audio_frame); \ + s->NDIlib->util_send_send_audio_interleaved_ ## bit_depth ## s(s->pNDI_send, &NDI_audio_frame); \ } while(0) static void display_ndi_put_audio_frame(void *state, struct audio_frame *frame)