mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 12:40:05 +00:00
NDI: open dynamically on runtime
To avoid adding NDI library yet allowing user to install it separately and use it with UG.
This commit is contained in:
17
configure.ac
17
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
|
||||
|
||||
|
||||
154
src/ndi_common.h
Normal file
154
src/ndi_common.h
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @file ndi_common.h
|
||||
* @author Martin Pulec <pulec@cesnet.cz>
|
||||
*/
|
||||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#define NDILIB_CPP_DEFAULT_CONSTRUCTORS 0
|
||||
#include <Processing.NDI.Lib.h>
|
||||
|
||||
#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 "=<NDI_SDK_PATH>/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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @author Martin Pulec <pulec@cesnet.cz>
|
||||
*/
|
||||
/*
|
||||
* 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 <Processing.NDI.Lib.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits> // 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<struct audio_frame, 2> 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=<n>][:url=<u>][:audio_level=<l>][:color=<c>][:extra_ips=<ip>][: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<int>(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<dispose_udata_t *>(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<dispose_udata_t *>(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<std::remove_pointer<LIB_HANDLE>::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;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @author Martin Pulec <pulec@cesnet.cz>
|
||||
*/
|
||||
/*
|
||||
* 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 <Processing.NDI.Lib.h>
|
||||
|
||||
#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)
|
||||
|
||||
Reference in New Issue
Block a user