From 9f19f265726140d95d9afcd85edd1ce04e4100f7 Mon Sep 17 00:00:00 2001 From: Ben Roeder Date: Sat, 24 Oct 2020 23:47:07 +0100 Subject: [PATCH 01/61] increase hardcoded limit to 128 seems that the actual max is 65535 --- src/audio/wav_reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/wav_reader.c b/src/audio/wav_reader.c index ab1973900..30e6b4000 100644 --- a/src/audio/wav_reader.c +++ b/src/audio/wav_reader.c @@ -61,7 +61,7 @@ static int read_fmt_chunk(FILE *wav_file, struct wav_metadata *metadata) uint16_t ch_count; READ_N(&ch_count, 2); - if (ch_count > 100) { + if (ch_count > 128) { return WAV_HDR_PARSE_INVALID_PARAM; } metadata->ch_count = ch_count; From fd97253105131fdcb87c18ee895aaf8d5919692c Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 23 Oct 2020 11:23:26 +0200 Subject: [PATCH 02/61] Pipe: option to specify output pixfmt --- src/video_display/pipe.cpp | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/video_display/pipe.cpp b/src/video_display/pipe.cpp index 7bba268b1..15b57635d 100644 --- a/src/video_display/pipe.cpp +++ b/src/video_display/pipe.cpp @@ -39,6 +39,7 @@ #include "config_unix.h" #include "config_win32.h" +#include #include #include @@ -50,6 +51,7 @@ #include "video_display.h" #include "video_display/pipe.hpp" +using std::cout; using std::list; using std::mutex; using std::lock_guard; @@ -57,9 +59,10 @@ using std::lock_guard; struct state_pipe { struct module *parent; frame_recv_delegate *delegate; - struct video_desc desc; - list audio_frames; - mutex audio_lock; + codec_t decode_to; + struct video_desc desc{}; + list audio_frames{}; + mutex audio_lock{}; }; static struct display *display_pipe_fork(void *state) @@ -74,6 +77,11 @@ static struct display *display_pipe_fork(void *state) if (rc == 0) return out; else return NULL; } +static void display_pipe_usage() { + cout << "Usage:\n" + "\t-d pipe:[:codec=]\n"; +} + /** * @note * Audio is always received regardless if enabled in flags. @@ -81,16 +89,34 @@ static struct display *display_pipe_fork(void *state) static void *display_pipe_init(struct module *parent, const char *fmt, unsigned int flags) { UNUSED(flags); + codec_t decode_to = UYVY; frame_recv_delegate *delegate; if (!fmt || strlen(fmt) == 0 || strcmp(fmt, "help") == 0) { fprintf(stderr, "Pipe dummy video driver. For internal usage - please do not use.\n"); + if (fmt != nullptr && strcmp(fmt, "help") == 0) { + display_pipe_usage(); + } return nullptr; } sscanf(fmt, "%p", &delegate); + if (strchr(fmt, ':') != nullptr) { + fmt = strchr(fmt, ':') + 1; + if (strstr(fmt, "codec=") == fmt) { + const char *codec_name = fmt + strlen("codec="); + decode_to = get_codec_from_name(codec_name); + if (decode_to == VIDEO_CODEC_NONE) { + LOG(LOG_LEVEL_ERROR) << "Wrong codec name: " << codec_name << "\n"; + return nullptr; + } + } else { + display_pipe_usage(); + return nullptr; + } + } - struct state_pipe *s = new state_pipe{parent, delegate, video_desc(), {}, {}}; + auto *s = new state_pipe{parent, delegate, decode_to}; return s; } @@ -180,20 +206,17 @@ static void display_pipe_run(void *state) static int display_pipe_get_property(void *state, int property, void *val, size_t *len) { - UNUSED(state); - codec_t codecs[] = {UYVY}; + auto *s = static_cast(state); enum interlacing_t supported_il_modes[] = {PROGRESSIVE, INTERLACED_MERGED, SEGMENTED_FRAME}; int rgb_shift[] = {0, 8, 16}; switch (property) { case DISPLAY_PROPERTY_CODECS: - if(sizeof(codecs) <= *len) { - memcpy(val, codecs, sizeof(codecs)); - } else { + if(sizeof(codec_t) > *len) { return FALSE; } - - *len = sizeof(codecs); + memcpy(val, &s->decode_to, sizeof(s->decode_to)); + *len = sizeof s->decode_to; break; case DISPLAY_PROPERTY_RGB_SHIFT: if(sizeof(rgb_shift) > *len) { From 8aa20b599f95c0be6de9f02e30b11fb2c4e650da Mon Sep 17 00:00:00 2001 From: Ben Roeder Date: Thu, 29 Oct 2020 19:50:40 +0000 Subject: [PATCH 03/61] =?UTF-8?q?LDGM=20to=20Reed=E2=80=93Solomon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/transmit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transmit.cpp b/src/transmit.cpp index 8db1a90b6..5712a2560 100644 --- a/src/transmit.cpp +++ b/src/transmit.cpp @@ -284,7 +284,7 @@ static bool set_fec(struct tx *tx, const char *fec_const) } } else if(strcasecmp(fec, "RS") == 0) { if(tx->media_type == TX_MEDIA_AUDIO) { - fprintf(stderr, "LDGM is not currently supported for audio!\n"); + fprintf(stderr, "Reed–Solomon is not currently supported for audio!\n"); ret = false; } else { snprintf(msg->fec_cfg, sizeof(msg->fec_cfg), "RS cfg %s", From e80c7321e9fe7af3d1b8f917c6c6068eef52deb2 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 27 Oct 2020 09:12:13 +0100 Subject: [PATCH 04/61] Libavcodec: more fine grain verbosity setting --- src/audio/codec/libavcodec.cpp | 4 +--- src/video_compress/libavcodec.cpp | 4 +--- src/video_decompress/libavcodec.c | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/audio/codec/libavcodec.cpp b/src/audio/codec/libavcodec.cpp index acae2bf9d..ad98dd125 100644 --- a/src/audio/codec/libavcodec.cpp +++ b/src/audio/codec/libavcodec.cpp @@ -164,9 +164,7 @@ ADD_TO_PARAM("audioenc-frame-duration", "* audioenc-frame-duration=\n" static void *libavcodec_init(audio_codec_t audio_codec, audio_codec_direction_t direction, bool silent, int bitrate) { - if (log_level >= LOG_LEVEL_VERBOSE) { - av_log_set_level(AV_LOG_VERBOSE); - } + av_log_set_level((log_level - 1) * 8); enum AVCodecID codec_id = AV_CODEC_ID_NONE; diff --git a/src/video_compress/libavcodec.cpp b/src/video_compress/libavcodec.cpp index 1ecce00c9..3eaa55fdf 100644 --- a/src/video_compress/libavcodec.cpp +++ b/src/video_compress/libavcodec.cpp @@ -501,9 +501,7 @@ struct module * libavcodec_compress_init(struct module *parent, const char *opts s = new state_video_compress_libav(); s->lavcd_global_lock = rm_acquire_shared_lock(LAVCD_LOCK_NAME); - if (log_level >= LOG_LEVEL_VERBOSE) { - av_log_set_level(AV_LOG_VERBOSE); - } + av_log_set_level((log_level - 1) * 8); #if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58, 9, 100) /* register all the codecs (you can also register only the codec * you wish to have smaller code */ diff --git a/src/video_decompress/libavcodec.c b/src/video_decompress/libavcodec.c index 3d210b305..8ac186b3c 100644 --- a/src/video_decompress/libavcodec.c +++ b/src/video_decompress/libavcodec.c @@ -375,9 +375,7 @@ static void * libavcodec_decompress_init(void) calloc(1, sizeof(struct state_libavcodec_decompress)); s->global_lavcd_lock = rm_acquire_shared_lock(LAVCD_LOCK_NAME); - if (log_level >= LOG_LEVEL_VERBOSE) { - av_log_set_level(AV_LOG_VERBOSE); - } + av_log_set_level((log_level - 1) * 8); #if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58, 9, 100) /* register all the codecs (you can also register only the codec From 1f89109796e064828f7ef8d82d4cc21a94ea0025 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 2 Nov 2020 10:36:44 +0100 Subject: [PATCH 05/61] Updated scree-capture-recorder-x64.dll --- data/README.md | 1 + data/screen-capture-recorder-x64.dll | Bin 2 files changed, 1 insertion(+) create mode 100644 data/README.md mode change 100755 => 100644 data/screen-capture-recorder-x64.dll diff --git a/data/README.md b/data/README.md new file mode 100644 index 000000000..5f4f34bf5 --- /dev/null +++ b/data/README.md @@ -0,0 +1 @@ +screen-capture-recorder-x64.dll is taken from https://sourceforge.net/projects/screencapturer/ diff --git a/data/screen-capture-recorder-x64.dll b/data/screen-capture-recorder-x64.dll old mode 100755 new mode 100644 From 2af03f79cab133c6655fa489a8617427b70f5a54 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 2 Nov 2020 11:42:52 +0100 Subject: [PATCH 06/61] Dshow: compute data len Screen capture returns wrong buffer length so we override it with the expected value. --- src/video_capture/DirectShowGrabber.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_capture/DirectShowGrabber.cpp b/src/video_capture/DirectShowGrabber.cpp index 4739dac17..1c234d229 100644 --- a/src/video_capture/DirectShowGrabber.cpp +++ b/src/video_capture/DirectShowGrabber.cpp @@ -1317,7 +1317,8 @@ static struct video_frame * vidcap_dshow_grab(void *state, struct audio_frame ** s->frame->tiles[0].data = (char *) s->returnBuffer; //fprintf(stderr, "[dshow] s: %p\n", s); //s->tile->data_len = s->width * s->height * 3; - s->frame->tiles[0].data_len = s->returnBufferLen; + s->frame->tiles[0].data_len = is_codec_opaque(s->frame->color_spec) ? s->returnBufferLen : + vc_get_datalen(s->frame->tiles[0].width, s->frame->tiles[0].height, s->frame->color_spec); /* fprintf(stderr, "[dshow] s5: %p\n", s); From 35a90038f36cc58dc0ef4fdcdb14758c447e183c Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 2 Nov 2020 11:47:06 +0100 Subject: [PATCH 07/61] Dshow: convert ABGR to RGBA and from bottom-top --- src/video_capture/DirectShowGrabber.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/video_capture/DirectShowGrabber.cpp b/src/video_capture/DirectShowGrabber.cpp index 1c234d229..c72a6d1d6 100644 --- a/src/video_capture/DirectShowGrabber.cpp +++ b/src/video_capture/DirectShowGrabber.cpp @@ -167,6 +167,12 @@ public: (char *) buffer + (s->desc.height - i - 1) * linesize, linesize); } + } else if (s->desc.color_spec == RGBA) { // convert from ABGR and bottom-to-top + for(unsigned int i = 0; i < s->desc.height; ++i) { + vc_copylineRGBA(s->grabBuffer + i * linesize, + buffer + (s->desc.height - i - 1) * linesize, + linesize, 16, 8, 0); + } } else { memcpy((char *) s->grabBuffer, (char *) buffer, len); } From 438fab21172bc6edb9c0b9dd5927126207e1c4e9 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 2 Nov 2020 14:45:22 +0100 Subject: [PATCH 08/61] GitHub CI: Win build fix CineForm MSBuild fails because there is already defined a variable named "temp" interfering with "TEMP" defined by build. Closes #99 --- .github/scripts/Windows/prepare_msys.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index 8272833d0..46a03f411 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -27,7 +27,10 @@ if test -d "$JACK_D"; then export LIBRARY_PATH=$LIBRARY_PATH:$JACK_D/lib fi +unset temp tmp # defined by /etc/profile, causes CineForm MSBuild fail (GitHub issue #99) + cd `cygpath $GITHUB_WORKSPACE` + EOF . ~/.bash_profile From 3698bd7547d061f770958a0a9717289cf136c60b Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 2 Nov 2020 16:04:53 +0100 Subject: [PATCH 09/61] GitHub CI [Win]: missing dir name for CineForm CMake --- .github/scripts/Windows/prepare_msys.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index 46a03f411..e0991647f 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -77,5 +77,5 @@ wget --no-verbose https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared. ( wget --no-verbose https://github.com/CESNET/GPUJPEG/releases/download/continuous/GPUJPEG.zip && unzip GPUJPEG.zip && cp -r GPUJPEG/* /usr/local || exit 1 ) # Build CineForm -( git submodule update --init cineform-sdk && cd cineform-sdk && cmake -DBUILD_STATIC=false -DBUILD_TOOLS=false -A x64 && MSBuild.exe CineFormSDK.sln -property:Configuration=Release && cp Release/CFHDCodec.dll /usr/local/bin && cp Release/CFHDCodec.lib /usr/local/lib && cp Common/* /usr/local/include && cp libcineformsdk.pc /usr/local/lib/pkgconfig || exit 1 ) +( git submodule update --init cineform-sdk && cd cineform-sdk && cmake -DBUILD_STATIC=false -DBUILD_TOOLS=false -A x64 . && MSBuild.exe CineFormSDK.sln -property:Configuration=Release && cp Release/CFHDCodec.dll /usr/local/bin && cp Release/CFHDCodec.lib /usr/local/lib && cp Common/* /usr/local/include && cp libcineformsdk.pc /usr/local/lib/pkgconfig || exit 1 ) From bda23ce4df2a28988658767d7f1fa855c4e5ad23 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 08:48:16 +0100 Subject: [PATCH 10/61] Fixed % in URL --- src/video_rxtx/rtp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_rxtx/rtp.cpp b/src/video_rxtx/rtp.cpp index 5635515fb..f2fbad35a 100644 --- a/src/video_rxtx/rtp.cpp +++ b/src/video_rxtx/rtp.cpp @@ -271,7 +271,7 @@ void rtp_video_rxtx::display_buf_increase_warning(int size) log_msg(LOG_LEVEL_VERBOSE, "\n***\n" "Unable to set buffer size to %d B.\n" #if defined WIN32 - "See https://github.com/CESNET/UltraGrid/wiki/Extending-Network-Buffers-%28Windows%29 for details.\n", + "See https://github.com/CESNET/UltraGrid/wiki/Extending-Network-Buffers-%%28Windows%%29 for details.\n", #else "Please set net.core.rmem_max value to %d or greater. (see also\n" "https://github.com/CESNET/UltraGrid/wiki/OS-Setup-UltraGrid)\n" From 913d0266498268c1170ea8a3fddcdc4e2f45260c Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 11:01:56 +0100 Subject: [PATCH 11/61] Traffic shaper: correctly clear fixed-rate flag --- src/transmit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transmit.cpp b/src/transmit.cpp index 5712a2560..a20da2cb4 100644 --- a/src/transmit.cpp +++ b/src/transmit.cpp @@ -651,7 +651,7 @@ tx_send_base(struct tx *tx, struct video_frame *frame, struct rtp *rtp_session, if (tx->bitrate == RATE_AUTO) { // adaptive (spread packets to 75% frame time) packet_rate = packet_rate_auto; } else { // bitrate given manually - long long int bitrate = tx->bitrate | RATE_FLAG_FIXED_RATE; + long long int bitrate = tx->bitrate & ~RATE_FLAG_FIXED_RATE; int avg_packet_size = tile->data_len / packet_count; packet_rate = 1000ll * 1000 * 1000 * avg_packet_size * 8 / bitrate; // fixed rate if ((tx->bitrate & RATE_FLAG_FIXED_RATE) == 0) { // adaptive capped rate From 932f96b62ab3f900d265fbb33fcbde48343c2aec Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 11:59:01 +0100 Subject: [PATCH 12/61] rang.hpp: suppress compiler warning --- src/rang.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rang.hpp b/src/rang.hpp index f3eaa56fc..de5646fad 100644 --- a/src/rang.hpp +++ b/src/rang.hpp @@ -174,9 +174,9 @@ namespace rang_implementation { { // Dynamic load for binary compability with old Windows const auto ptrGetFileInformationByHandleEx - = reinterpret_cast( + = reinterpret_cast(reinterpret_cast( GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), - "GetFileInformationByHandleEx")); + "GetFileInformationByHandleEx"))); if (!ptrGetFileInformationByHandleEx) { return false; } From 0e8712c703af147a22aaa64a5aedbf7f7ec38c00 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 14:09:44 +0100 Subject: [PATCH 13/61] Makefile: do not rebuild CUDA DLL repeatedly [Win] Make tend to recompile CUDA module DLLs repeatedly because the compiler doesn't update .lib file modification timestamp therefor make thinks that the library needs to be recompiled. --- Makefile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.in b/Makefile.in index 513d24efb..2396fa55b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -263,6 +263,7 @@ $(REFLECTOR_TARGET): src/dir-stamp $(OBJS) $(GENERATED_HEADERS) $(REFLECTOR_OBJS %.cu.lib: %.cu $(ALL_INCLUDES) $(MKDIR_P) $(dir $@) "$(CUDA_COMPILER)" $(CUDA_FLAGS) -DEXPORT_DLL_SYMBOLS $(INC) --shared $< -o $<.dll + touch $@ src/libavcodec_common.o: src/libavcodec_common.c $(ALL_INCLUDES) $(MKDIR_P) $(dir $@) From b1c96985d69e54e7d14d37993f05e00a051e0e91 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 15:12:52 +0100 Subject: [PATCH 14/61] Logger: use ostringstream + clog --- src/debug.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/debug.h b/src/debug.h index d42526875..82aadb197 100644 --- a/src/debug.h +++ b/src/debug.h @@ -81,6 +81,7 @@ void log_msg(int log_level, const char *format, ...) ATTRIBUTE(format (printf, 2 #ifdef __cplusplus #include #include +#include #include "compat/platform_time.h" #include "rang.hpp" @@ -90,9 +91,6 @@ class Logger public: inline Logger(int l) : level(l) {} inline ~Logger() { - std::cerr << rang::style::reset << rang::fg::reset; - } - inline std::ostream& Get() { rang::fg color = rang::fg::reset; rang::style style = rang::style::reset; @@ -102,20 +100,20 @@ public: case LOG_LEVEL_WARNING: color = rang::fg::yellow; break; case LOG_LEVEL_NOTICE: color = rang::fg::green; break; } - std::cerr << style << color; + std::ostringstream timestamp; if (log_level >= LOG_LEVEL_VERBOSE) { - unsigned long long time_ms = time_since_epoch_in_ms(); - auto flags = std::cerr.flags(); - auto precision = std::cerr.precision(); - std::cerr << "[" << std::fixed << std::setprecision(3) << time_ms / 1000.0 << "] "; - std::cerr.precision(precision); - std::cerr.flags(flags); + auto time_ms = time_since_epoch_in_ms(); + timestamp << "[" << std::fixed << std::setprecision(3) << time_ms / 1000.0 << "] "; } - return std::cerr; + std::clog << style << color << timestamp.str() << oss.str() << rang::style::reset << rang::fg::reset; + } + inline std::ostream& Get() { + return oss; } private: int level; + std::ostringstream oss; }; #define LOG(level) \ From a36d5a008e6322ff8215c951f3cc9562ec67a8b8 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 Nov 2020 15:38:29 +0100 Subject: [PATCH 15/61] Rang: force control In order to control sequences work with ostringstream logger. --- src/debug.h | 14 ++++++++++++++ src/host.cpp | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/debug.h b/src/debug.h index 82aadb197..381ee80d1 100644 --- a/src/debug.h +++ b/src/debug.h @@ -89,6 +89,20 @@ void log_msg(int log_level, const char *format, ...) ATTRIBUTE(format (printf, 2 class Logger { public: + static void preinit() { + if (!rang::rang_implementation::supportsColor() + || !rang::rang_implementation::isTerminal(std::clog.rdbuf())) { + return; + } + // force ANSI sequences even when written to ostringstream + rang::setControlMode(rang::control::Force); +#ifdef _WIN32 + // ANSI control sequences need to be explicitly set in Windows + if (rang::rang_implementation::setWinTermAnsiColors(std::clog.rdbuf())) { + rang::setWinTermMode(rang::winTerm::Ansi); + } +#endif + } inline Logger(int l) : level(l) {} inline ~Logger() { rang::fg color = rang::fg::reset; diff --git a/src/host.cpp b/src/host.cpp index f5953dd13..8db0ec4ff 100644 --- a/src/host.cpp +++ b/src/host.cpp @@ -240,6 +240,8 @@ struct init_data *common_preinit(int argc, char *argv[]) perf_init(); perf_record(UVP_INIT, 0); + Logger::preinit(); + return init; } From c2ff176904661553db51360947a2c01796ac6bc7 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 4 Nov 2020 10:04:46 +0100 Subject: [PATCH 16/61] Logger: suppress repeated messages --- src/debug.cpp | 2 ++ src/debug.h | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/debug.cpp b/src/debug.cpp index 67899a681..9cd3a6d70 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -179,3 +179,5 @@ void debug_dump(void *lp, int len) start = i; /* next line starting byte */ } } + +std::atomic Logger::last_msg{}; diff --git a/src/debug.h b/src/debug.h index 381ee80d1..1cc900544 100644 --- a/src/debug.h +++ b/src/debug.h @@ -79,6 +79,7 @@ void log_msg(int log_level, const char *format, ...) ATTRIBUTE(format (printf, 2 #endif #ifdef __cplusplus +#include #include #include #include @@ -108,19 +109,39 @@ public: rang::fg color = rang::fg::reset; rang::style style = rang::style::reset; + std::string msg = oss.str(); + + // check for repeated message + if (rang::rang_implementation::isTerminal(std::clog.rdbuf())) { + auto last = last_msg.exchange(nullptr); + if (last != nullptr && last->msg == msg) { + int count = last->count += 1; + auto current = last_msg.exchange(last); + delete current; + std::clog << " Last message repeated " << count << " times\r"; + return; + } + delete last; + } + switch (level) { case LOG_LEVEL_FATAL: color = rang::fg::red; style = rang::style::bold; break; case LOG_LEVEL_ERROR: color = rang::fg::red; break; case LOG_LEVEL_WARNING: color = rang::fg::yellow; break; case LOG_LEVEL_NOTICE: color = rang::fg::green; break; } + std::ostringstream timestamp; if (log_level >= LOG_LEVEL_VERBOSE) { auto time_ms = time_since_epoch_in_ms(); timestamp << "[" << std::fixed << std::setprecision(3) << time_ms / 1000.0 << "] "; } - std::clog << style << color << timestamp.str() << oss.str() << rang::style::reset << rang::fg::reset; + std::clog << style << color << timestamp.str() << msg << rang::style::reset << rang::fg::reset; + + auto *lmsg = new last_message{std::move(msg)}; + auto current = last_msg.exchange(lmsg); + delete current; } inline std::ostream& Get() { return oss; @@ -128,6 +149,12 @@ public: private: int level; std::ostringstream oss; + + struct last_message { + std::string msg; + int count{0}; + }; + static std::atomic last_msg; // leaks last message upon exit }; #define LOG(level) \ From 024494f4b858aa904dfa45fcec3658ec77030338 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 4 Nov 2020 13:47:42 +0100 Subject: [PATCH 17/61] Option to disable message repeats suppressing --- src/debug.cpp | 64 +++++++++++++++++++++ src/debug.h | 32 ++++++----- src/hd-rum-translator/hd-rum-translator.cpp | 2 +- src/host.cpp | 13 ++++- src/host.h | 2 +- src/main.cpp | 8 ++- test/run_tests.cpp | 2 +- 7 files changed, 101 insertions(+), 22 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index 9cd3a6d70..f80604396 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -180,4 +180,68 @@ void debug_dump(void *lp, int len) } } +bool set_log_level(const char *optarg, bool *logger_repeat_msgs) { + using namespace std::string_literals; + using std::clog; + using std::cout; + + *logger_repeat_msgs = false; + if (optarg == nullptr) { + log_level = LOG_LEVEL_VERBOSE; + return true; + } + + static const struct { const char *name; int level; } mapping[] = { + { "quiet", LOG_LEVEL_QUIET }, + { "fatal", LOG_LEVEL_FATAL }, + { "error", LOG_LEVEL_ERROR }, + { "warning", LOG_LEVEL_WARNING}, + { "notice", LOG_LEVEL_NOTICE}, + { "info", LOG_LEVEL_INFO }, + { "verbose", LOG_LEVEL_VERBOSE}, + { "debug", LOG_LEVEL_DEBUG }, + { "debug2", LOG_LEVEL_DEBUG2 }, + }; + + if ("help"s == optarg) { + cout << "log level: [0-8"; + for (auto m : mapping) { + cout << "|" << m.name; + } + cout << "][+repeat]\n"; + cout << "\trepeat - print repeating log messages\n"; + return false; + } + + if (strstr(optarg, "+repeat") != nullptr) { + *logger_repeat_msgs = true; + } + + if (optarg[0] == '+') { + return true; + } + + if (isdigit(optarg[0])) { + long val = strtol(optarg, nullptr, 0); + if (val < 0 || val > LOG_LEVEL_MAX) { + clog << "Log: wrong value: " << log_level << "\n"; + return false; + } + log_level = val; + return true; + } + + for (auto m : mapping) { + if (strstr(optarg, m.name) == optarg) { + log_level = m.level; + return true; + } + } + + LOG(LOG_LEVEL_ERROR) << "Wrong log level specification: " << optarg << "\n"; + return false; +} + std::atomic Logger::last_msg{}; +bool Logger::skip_repeated = true; + diff --git a/src/debug.h b/src/debug.h index 1cc900544..5e4aaacc6 100644 --- a/src/debug.h +++ b/src/debug.h @@ -40,6 +40,10 @@ #ifndef _RAT_DEBUG_H #define _RAT_DEBUG_H +#ifndef __cplusplus +#include +#endif // ! defined __cplusplus + #define UNUSED(x) (x=x) #define LOG_LEVEL_QUIET 0 ///< suppress all logging @@ -74,6 +78,8 @@ void debug_dump(void*lp, int len); #define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, __VA_ARGS__) void log_msg(int log_level, const char *format, ...) ATTRIBUTE(format (printf, 2, 3)); +bool set_log_level(const char *optarg, bool *logger_repeat_msgs); + #ifdef __cplusplus } #endif @@ -90,19 +96,19 @@ void log_msg(int log_level, const char *format, ...) ATTRIBUTE(format (printf, 2 class Logger { public: - static void preinit() { - if (!rang::rang_implementation::supportsColor() - || !rang::rang_implementation::isTerminal(std::clog.rdbuf())) { - return; - } - // force ANSI sequences even when written to ostringstream - rang::setControlMode(rang::control::Force); + static void preinit(bool skip_repeated) { + Logger::skip_repeated = skip_repeated; + if (rang::rang_implementation::supportsColor() + && rang::rang_implementation::isTerminal(std::clog.rdbuf())) { + // force ANSI sequences even when written to ostringstream + rang::setControlMode(rang::control::Force); #ifdef _WIN32 - // ANSI control sequences need to be explicitly set in Windows - if (rang::rang_implementation::setWinTermAnsiColors(std::clog.rdbuf())) { - rang::setWinTermMode(rang::winTerm::Ansi); - } + // ANSI control sequences need to be explicitly set in Windows + if (rang::rang_implementation::setWinTermAnsiColors(std::clog.rdbuf())) { + rang::setWinTermMode(rang::winTerm::Ansi); + } #endif + } } inline Logger(int l) : level(l) {} inline ~Logger() { @@ -111,8 +117,7 @@ public: std::string msg = oss.str(); - // check for repeated message - if (rang::rang_implementation::isTerminal(std::clog.rdbuf())) { + if (skip_repeated && rang::rang_implementation::isTerminal(std::clog.rdbuf())) { auto last = last_msg.exchange(nullptr); if (last != nullptr && last->msg == msg) { int count = last->count += 1; @@ -150,6 +155,7 @@ private: int level; std::ostringstream oss; + static bool skip_repeated; struct last_message { std::string msg; int count{0}; diff --git a/src/hd-rum-translator/hd-rum-translator.cpp b/src/hd-rum-translator/hd-rum-translator.cpp index e67ae1d3b..9b614a986 100644 --- a/src/hd-rum-translator/hd-rum-translator.cpp +++ b/src/hd-rum-translator/hd-rum-translator.cpp @@ -696,7 +696,7 @@ int main(int argc, char **argv) int i; struct cmdline_parameters params; - if ((init = common_preinit(argc, argv)) == nullptr) { + if ((init = common_preinit(argc, argv, nullptr)) == nullptr) { EXIT(EXIT_FAILURE); } diff --git a/src/host.cpp b/src/host.cpp index 8db0ec4ff..9c4218190 100644 --- a/src/host.cpp +++ b/src/host.cpp @@ -44,6 +44,8 @@ #include "config_win32.h" #endif +#include + #include "host.h" #include "audio/audio_capture.h" @@ -179,13 +181,20 @@ static int x11_error_handler(Display *d, XErrorEvent *e) { } #endif -struct init_data *common_preinit(int argc, char *argv[]) +struct init_data *common_preinit(int argc, char *argv[], const char *log_opt) { struct init_data *init; + bool logger_repeat_msgs = false; uv_argc = argc; uv_argv = argv; + if (!set_log_level(log_opt, &logger_repeat_msgs)) { + return nullptr; + } + + Logger::preinit(!logger_repeat_msgs); + #ifdef HAVE_X void *handle = dlopen(X11_LIB_NAME, RTLD_NOW); @@ -240,8 +249,6 @@ struct init_data *common_preinit(int argc, char *argv[]) perf_init(); perf_record(UVP_INIT, 0); - Logger::preinit(); - return init; } diff --git a/src/host.h b/src/host.h index 980c7e5fd..a8fa12c2d 100644 --- a/src/host.h +++ b/src/host.h @@ -117,7 +117,7 @@ void set_audio_delay(int val); #define RATE_FLAG_FIXED_RATE (1ll<<62ll) ///< use the bitrate as fixed, not capped struct init_data; -struct init_data *common_preinit(int argc, char *argv[]); +struct init_data *common_preinit(int argc, char *argv[], const char *log_opt); void common_cleanup(struct init_data *init_data); /** diff --git a/src/main.cpp b/src/main.cpp index 2d31f7704..8b92ab2f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -739,6 +739,8 @@ int main(int argc, char *argv[]) const char *video_protocol = "ultragrid_rtp"; const char *video_protocol_opts = ""; + const char *log_opt = nullptr; + // First we need to set verbosity level prior to everything else. // common_preinit() uses the verbosity level. while ((ch = @@ -747,9 +749,9 @@ int main(int argc, char *argv[]) switch (ch) { case OPT_VERBOSE: if (optarg) { - log_level = atoi(optarg); + log_opt = optarg; } else { - log_level = LOG_LEVEL_VERBOSE; + log_opt = "verbose"; } break; default: @@ -758,7 +760,7 @@ int main(int argc, char *argv[]) } optind = 1; - if ((init = common_preinit(argc, argv)) == nullptr) { + if ((init = common_preinit(argc, argv, log_opt)) == nullptr) { log_msg(LOG_LEVEL_FATAL, "common_preinit() failed!\n"); EXIT(EXIT_FAILURE); } diff --git a/test/run_tests.cpp b/test/run_tests.cpp index c81a3971c..25917046a 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -144,7 +144,7 @@ static bool run_unit_tests() int main(int argc, char **argv) { struct init_data *init = nullptr; - if ((init = common_preinit(argc, argv)) == nullptr) { + if ((init = common_preinit(argc, argv, nullptr)) == nullptr) { return 2; } From 266b87fe09f81eaff2405900de1733eb47472e7d Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 4 Nov 2020 14:04:29 +0100 Subject: [PATCH 18/61] Logger: force color output if both out&err are term Force color output if both stdout and stderr are connected to terminal. The setting is global and it is possible that only stdout is redirected (eg. to pager) while the check here was for clog (cerr) only. Thus escape sequences had been unintentionally output to the pipe. --- src/debug.cpp | 18 ++++++++++++++++++ src/debug.h | 15 +-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index f80604396..5bd7e545f 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -242,6 +242,24 @@ bool set_log_level(const char *optarg, bool *logger_repeat_msgs) { return false; } +void Logger::preinit(bool skip_repeated) +{ + Logger::skip_repeated = skip_repeated; + if (rang::rang_implementation::supportsColor() + && rang::rang_implementation::isTerminal(std::cout.rdbuf()) + && rang::rang_implementation::isTerminal(std::cerr.rdbuf())) { + // force ANSI sequences even when written to ostringstream + rang::setControlMode(rang::control::Force); +#ifdef _WIN32 + // ANSI control sequences need to be explicitly set in Windows + if (rang::rang_implementation::setWinTermAnsiColors(std::cout.rdbuf()) && + rang::rang_implementation::setWinTermAnsiColors(std::cerr.rdbuf())) { + rang::setWinTermMode(rang::winTerm::Ansi); + } +#endif + } +} + std::atomic Logger::last_msg{}; bool Logger::skip_repeated = true; diff --git a/src/debug.h b/src/debug.h index 5e4aaacc6..723fb4ef8 100644 --- a/src/debug.h +++ b/src/debug.h @@ -96,20 +96,7 @@ bool set_log_level(const char *optarg, bool *logger_repeat_msgs); class Logger { public: - static void preinit(bool skip_repeated) { - Logger::skip_repeated = skip_repeated; - if (rang::rang_implementation::supportsColor() - && rang::rang_implementation::isTerminal(std::clog.rdbuf())) { - // force ANSI sequences even when written to ostringstream - rang::setControlMode(rang::control::Force); -#ifdef _WIN32 - // ANSI control sequences need to be explicitly set in Windows - if (rang::rang_implementation::setWinTermAnsiColors(std::clog.rdbuf())) { - rang::setWinTermMode(rang::winTerm::Ansi); - } -#endif - } - } + static void preinit(bool skip_repeated); inline Logger(int l) : level(l) {} inline ~Logger() { rang::fg color = rang::fg::reset; From 820d5c9c836d5c02e394cfab06ce4e18a3f3ad07 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 4 Nov 2020 15:03:31 +0100 Subject: [PATCH 19/61] Logger: new line after last repeat count To keep track about repetitions in output. --- src/debug.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/debug.h b/src/debug.h index 723fb4ef8..445ae6dc9 100644 --- a/src/debug.h +++ b/src/debug.h @@ -113,7 +113,10 @@ public: std::clog << " Last message repeated " << count << " times\r"; return; } - delete last; + if (last != nullptr && last->count > 0) { + std::clog << "\n"; + delete last; + } } switch (level) { From bd552be4309ea5ed62bc9f537b8a48c888a79ebd Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 5 Nov 2020 08:49:16 +0100 Subject: [PATCH 20/61] Allow verbosity increase with -V + small updates --- src/debug.cpp | 11 ++++------- src/host.cpp | 2 +- src/main.cpp | 11 +++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index 5bd7e545f..50e5b7e22 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -181,16 +181,13 @@ void debug_dump(void *lp, int len) } bool set_log_level(const char *optarg, bool *logger_repeat_msgs) { + assert(optarg != nullptr); + assert(logger_repeat_msgs != nullptr); + using namespace std::string_literals; using std::clog; using std::cout; - *logger_repeat_msgs = false; - if (optarg == nullptr) { - log_level = LOG_LEVEL_VERBOSE; - return true; - } - static const struct { const char *name; int level; } mapping[] = { { "quiet", LOG_LEVEL_QUIET }, { "fatal", LOG_LEVEL_FATAL }, @@ -204,7 +201,7 @@ bool set_log_level(const char *optarg, bool *logger_repeat_msgs) { }; if ("help"s == optarg) { - cout << "log level: [0-8"; + cout << "log level: [0-" << LOG_LEVEL_MAX; for (auto m : mapping) { cout << "|" << m.name; } diff --git a/src/host.cpp b/src/host.cpp index 9c4218190..ce70b7289 100644 --- a/src/host.cpp +++ b/src/host.cpp @@ -189,7 +189,7 @@ struct init_data *common_preinit(int argc, char *argv[], const char *log_opt) uv_argc = argc; uv_argv = argv; - if (!set_log_level(log_opt, &logger_repeat_msgs)) { + if (log_opt != nullptr && !set_log_level(log_opt, &logger_repeat_msgs)) { return nullptr; } diff --git a/src/main.cpp b/src/main.cpp index 8b92ab2f7..84bcae192 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,7 +133,6 @@ static constexpr const char *DEFAULT_AUDIO_CODEC = "PCM"; #define OPT_PIX_FMTS (('P' << 8) | 'F') #define OPT_PROTOCOL (('P' << 8) | 'R') #define OPT_START_PAUSED (('S' << 8) | 'P') -#define OPT_VERBOSE (('V' << 8) | 'E') #define OPT_VIDEO_CODECS (('V' << 8) | 'C') #define OPT_VIDEO_PROTOCOL (('V' << 8) | 'P') #define OPT_WINDOW_TITLE (('W' << 8) | 'T') @@ -716,7 +715,7 @@ int main(int argc, char *argv[]) {"capture-filter", required_argument, 0, OPT_CAPTURE_FILTER}, {"control-port", required_argument, 0, OPT_CONTROL_PORT}, {"encryption", required_argument, 0, OPT_ENCRYPTION}, - {"verbose", optional_argument, 0, OPT_VERBOSE}, + {"verbose", optional_argument, nullptr, 'V'}, {"window-title", required_argument, 0, OPT_WINDOW_TITLE}, {"capabilities", no_argument, 0, OPT_CAPABILITIES}, {"audio-delay", required_argument, 0, OPT_AUDIO_DELAY}, @@ -731,7 +730,7 @@ int main(int argc, char *argv[]) {"video-codecs", no_argument, 0, OPT_VIDEO_CODECS}, {0, 0, 0, 0} }; - const char optstring[] = "d:t:m:r:s:v46c:hM:p:f:P:l:A:"; + const char optstring[] = "d:t:m:r:s:v46c:hM:p:f:P:l:A:V"; const char *audio_protocol = "ultragrid_rtp"; const char *audio_protocol_opts = ""; @@ -747,11 +746,11 @@ int main(int argc, char *argv[]) getopt_long(argc, argv, optstring, getopt_options, NULL)) != -1) { switch (ch) { - case OPT_VERBOSE: + case 'V': if (optarg) { log_opt = optarg; } else { - log_opt = "verbose"; + log_level += 1; } break; default: @@ -1059,7 +1058,7 @@ int main(int argc, char *argv[]) connection_type = 0; } break; - case OPT_VERBOSE: + case 'V': break; // already handled earlier case OPT_WINDOW_TITLE: log_msg(LOG_LEVEL_WARNING, "Deprecated option used, please use " From af9aa46115ee1fd34b462ffbf8693c591da06bc0 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 5 Nov 2020 10:36:15 +0100 Subject: [PATCH 21/61] Print cumulative audio playback stats on exit * removed that summary from ALSA --- src/audio/audio_playback.c | 30 ++++++++++++++++++++++-------- src/audio/playback/alsa.c | 14 -------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/audio/audio_playback.c b/src/audio/audio_playback.c index 540bb3de4..d85e2c06e 100644 --- a/src/audio/audio_playback.c +++ b/src/audio/audio_playback.c @@ -45,21 +45,22 @@ #include #include -#include "debug.h" -#include "host.h" - #include "audio/audio.h" #include "audio/audio_playback.h" #include "audio/playback/sdi.h" - +#include "debug.h" +#include "host.h" #include "lib_common.h" - +#include "tv.h" #include "video_display.h" /* flags */ struct state_audio_playback { char name[128]; const struct audio_playback_info *funcs; void *state; + + struct timeval t0; + long long int samples_played; }; void audio_playback_help(bool full) @@ -73,6 +74,7 @@ int audio_playback_init(const char *device, const char *cfg, struct state_audio_ struct state_audio_playback *s; s = calloc(1, sizeof(struct state_audio_playback)); + gettimeofday(&s->t0, NULL); s->funcs = load_library(device, LIBRARY_CLASS_AUDIO_PLAYBACK, AUDIO_PLAYBACK_ABI_VERSION); if (s->funcs == NULL) { @@ -111,10 +113,21 @@ struct state_audio_playback *audio_playback_init_null_device(void) void audio_playback_done(struct state_audio_playback *s) { - if(s) { - s->funcs->done(s->state); - free(s); + if (!s) { + return; } + + if (s->samples_played > 0) { + struct timeval t1; + gettimeofday(&t1, NULL); + + log_msg(LOG_LEVEL_INFO, "Played %lld audio samples in %f seconds (%f samples per second).\n", + s->samples_played, tv_diff(t1, s->t0), + s->samples_played / tv_diff(t1, s->t0)); + } + + s->funcs->done(s->state); + free(s); } unsigned int audio_playback_get_display_flags(struct state_audio_playback *s) @@ -135,6 +148,7 @@ unsigned int audio_playback_get_display_flags(struct state_audio_playback *s) void audio_playback_put_frame(struct state_audio_playback *s, struct audio_frame *frame) { + s->samples_played += frame->data_len / frame->ch_count / frame->bps; s->funcs->write(s->state, frame); } diff --git a/src/audio/playback/alsa.c b/src/audio/playback/alsa.c index 6f6ec8f94..77feca0bb 100644 --- a/src/audio/playback/alsa.c +++ b/src/audio/playback/alsa.c @@ -103,9 +103,6 @@ struct state_alsa_playback { snd_pcm_t *handle; struct audio_desc desc; - struct timeval start_time; - long long int played_samples; - /* Local configuration with handle_underrun workaround set for PulseAudio ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the workaround is not required. */ @@ -834,8 +831,6 @@ static void * audio_play_alsa_init(const char *cfg) log_msg(LOG_LEVEL_WARNING, MOD_NAME "Async API is experimental, in case of problems use either \"thread\" or \"sync\" API\n"); } - gettimeofday(&s->start_time, NULL); - if(cfg && strlen(cfg) > 0) { if(strcmp(cfg, "help") == 0) { printf("Usage\n"); @@ -964,8 +959,6 @@ static void audio_play_alsa_write_frame(void *state, struct audio_frame *frame) signed2unsigned(frame->data, frame->data, frame->data_len); } - s->played_samples += frame->data_len / frame->bps / frame->ch_count; - int frames = frame->data_len / (frame->bps * frame->ch_count); rc = write_samples(s->handle, frame, frames, s->non_interleaved, s->playback_mode); if (rc == -EPIPE) { @@ -1038,8 +1031,6 @@ static void audio_play_alsa_done(void *state) { struct state_alsa_playback *s = (struct state_alsa_playback *) state; - struct timeval t; - if (s->playback_mode == THREAD && s->thread_started) { pthread_mutex_lock(&s->lock); s->should_exit_thread = true; @@ -1051,11 +1042,6 @@ static void audio_play_alsa_done(void *state) snd_async_del_handler(s->pcm_callback); } - gettimeofday(&t, NULL); - log_msg(LOG_LEVEL_INFO, MOD_NAME "Played %lld samples in %f seconds (%f samples per second).\n", - s->played_samples, tv_diff(t, s->start_time), - s->played_samples / tv_diff(t, s->start_time)); - snd_pcm_drain(s->handle); snd_pcm_close(s->handle); if (s->local_config) { From 8f296fb81559520ab4b314e47733ca345ef2f828 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 6 Nov 2020 14:48:05 +0100 Subject: [PATCH 22/61] Transmit: do not exceed MTU for audio with encryption Encryption overhead (nonce etc.) was not taken into account when computing package sizes. --- src/transmit.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/transmit.cpp b/src/transmit.cpp index a20da2cb4..a730298ea 100644 --- a/src/transmit.cpp +++ b/src/transmit.cpp @@ -772,26 +772,22 @@ void audio_tx_send(struct tx* tx, struct rtp *rtp_session, const audio_frame2 * int mult_pos[FEC_MAX_MULT]; int mult_index = 0; int mult_first_sent = 0; - int rtp_hdr_len; fec_check_messages(tx); timestamp = get_local_mediatime(); perf_record(UVP_SEND, timestamp); - if(tx->encryption) { - rtp_hdr_len = sizeof(crypto_payload_hdr_t) + sizeof(audio_payload_hdr_t); + int rtp_hdr_len = sizeof(audio_payload_hdr_t); + int hdrs_len = (rtp_is_ipv6(rtp_session) ? 40 : 20) + 8 + 12 + sizeof(audio_payload_hdr_t); // MTU - IP hdr - UDP hdr - RTP hdr - payload_hdr + if (tx->encryption) { + hdrs_len += sizeof(crypto_payload_hdr_t) + tx->enc_funcs->get_overhead(tx->encryption); + rtp_hdr_len += sizeof(crypto_payload_hdr_t); pt = PT_ENCRYPT_AUDIO; } else { - rtp_hdr_len = sizeof(audio_payload_hdr_t); pt = PT_AUDIO; /* PT set for audio in our packet format */ } - int hdrs_len = (rtp_is_ipv6(rtp_session) ? 40 : 20) + 8 + 12 + sizeof(audio_payload_hdr_t); // MTU - IP hdr - UDP hdr - RTP hdr - payload_hdr - if(tx->encryption) { - hdrs_len += sizeof(crypto_payload_hdr_t); - } - for(channel = 0; channel < buffer->get_channel_count(); ++channel) { chan_data = buffer->get_data(channel); From 4554f956ef566ac51ffb19b2dadec971822453fb Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 5 Nov 2020 16:24:31 +0100 Subject: [PATCH 23/61] Platform pipe: add timeouts Do not hang if anything goes wrong (eg. loopback doesn't connect). --- src/compat/platform_pipe.cpp | 44 +++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/compat/platform_pipe.cpp b/src/compat/platform_pipe.cpp index 92beec118..9b786d53a 100644 --- a/src/compat/platform_pipe.cpp +++ b/src/compat/platform_pipe.cpp @@ -42,11 +42,25 @@ #include "config_win32.h" #include "compat/platform_pipe.h" +#include "debug.h" +#include "rtp/net_udp.h" // socket_error #include using std::thread; +#ifdef _WIN32 +#define DECLARE_TIMEOUT(name, init_val_s) DWORD name = init_val_s * 1000 +typedef char *sockopt_t; +#else +#define DECLARE_TIMEOUT(name, init_val_s) struct timeval name = { init_val_s, 0 } +typedef void *sockopt_t; +#endif + +#ifndef HAVE_CONFIG_H // compiled outside of UltraGrid +#define socket_error(...) fprintf(stderr, __VA_ARGS__) +#endif + static fd_t open_socket(int *port) { fd_t sock; @@ -90,12 +104,26 @@ static fd_t connect_to_socket(int local_port) return INVALID_SOCKET; } int ret; + + DECLARE_TIMEOUT(timeout, 1); + DECLARE_TIMEOUT(old_timeout, 0); + socklen_t old_timeout_len = 0; + if (getsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&old_timeout), &old_timeout_len) != 0) { + socket_error("pipe getsockopt"); + } + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeout), sizeof timeout) != 0) { + socket_error("pipe setsockopt"); + } ret = connect(fd, (struct sockaddr *) &s_in, sizeof(s_in)); if (ret != 0) { + socket_error("pipe connect"); CLOSESOCKET(fd); return INVALID_SOCKET; } + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&old_timeout), sizeof old_timeout) != 0) { + socket_error("pipe setsockopt"); + } return fd; } @@ -152,15 +180,29 @@ int platform_pipe_init(fd_t p[2]) return -1; } + DECLARE_TIMEOUT(timeout, 1); + DECLARE_TIMEOUT(old_timeout, 0); + socklen_t old_timeout_len = 0; + if (getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), &old_timeout_len) != 0) { + socket_error("pipe getsockopt"); + } + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof timeout) != 0) { + socket_error("pipe setsockopt"); + } + thread thr(worker, &par); p[0] = accept(sock, NULL, NULL); if (p[0] == INVALID_SOCKET) { - perror("accept"); + perror("pipe accept"); + thr.join(); CLOSESOCKET(sock); return -1; } thr.join(); + if (setsockopt(p[0], SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), sizeof old_timeout) != 0) { + socket_error("pipe setsockopt"); + } p[1] = par.sock; if (p[1] == INVALID_SOCKET) { CLOSESOCKET(sock); From efd2c70235ade1c1d1bd36b7a307027a71923ff5 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 6 Nov 2020 10:01:20 +0100 Subject: [PATCH 24/61] Platform pipe: use plain pipe if TCP compat fails Use plain pipe if the TCP compat fails in Linux. This fixes a problem when there is unavailable loopback connection (eg. running in a separate network namespace with unassigned loopback address). --- src/compat/platform_pipe.cpp | 18 ++++++++++++++---- src/compat/platform_pipe.h | 20 +++++++++++++++++++- src/control_socket.cpp | 4 ++-- src/main.cpp | 8 ++++++-- src/rtp/net_udp.cpp | 2 +- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/compat/platform_pipe.cpp b/src/compat/platform_pipe.cpp index 9b786d53a..4fee04807 100644 --- a/src/compat/platform_pipe.cpp +++ b/src/compat/platform_pipe.cpp @@ -42,7 +42,6 @@ #include "config_win32.h" #include "compat/platform_pipe.h" -#include "debug.h" #include "rtp/net_udp.h" // socket_error #include @@ -141,6 +140,17 @@ static void * worker(void *args) return NULL; } +/// @brief return regular pipe if available if our implementation fails +static int system_pipe(fd_t p[2]) +{ +#ifdef _WIN32 + (void) p; + return -1; +#else + fprintf(stderr, "Using native pipe instead of custom implementaton.\n"); + return pipe(p); +#endif +} int platform_pipe_init(fd_t p[2]) { @@ -177,7 +187,7 @@ int platform_pipe_init(fd_t p[2]) fd_t sock = open_socket(&par.port); if (sock == INVALID_SOCKET) { perror("open_socket"); - return -1; + return system_pipe(p); } DECLARE_TIMEOUT(timeout, 1); @@ -197,7 +207,7 @@ int platform_pipe_init(fd_t p[2]) perror("pipe accept"); thr.join(); CLOSESOCKET(sock); - return -1; + return system_pipe(p); } thr.join(); if (setsockopt(p[0], SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), sizeof old_timeout) != 0) { @@ -207,7 +217,7 @@ int platform_pipe_init(fd_t p[2]) if (p[1] == INVALID_SOCKET) { CLOSESOCKET(sock); perror("accept"); - return -1; + return system_pipe(p); } CLOSESOCKET(sock); diff --git a/src/compat/platform_pipe.h b/src/compat/platform_pipe.h index da3daed7a..34b0eaaa8 100644 --- a/src/compat/platform_pipe.h +++ b/src/compat/platform_pipe.h @@ -38,6 +38,9 @@ #ifndef platform_pipe_h #define platform_pipe_h +#include "config_unix.h" +#include "config_win32.h" + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -45,8 +48,23 @@ extern "C" { int platform_pipe_init(fd_t pipe[2]); void platform_pipe_close(fd_t pipe); +#ifdef _WIN32 +#define PLATFORM_PIPE_READ(fd, buf, len) recv(fd, buf, len, 0) +#define PLATFORM_PIPE_WRITE(fd, buf, len) send(fd, buf, len, 0) +#else +// the fallback implementation of platform_pipe is a plain pipe for which +// doesn't work send()/recv(). read() and write(), however, works for both +// socket and pipe (but doesn't so in Windows, therefore there is used +// send()/recv()). +// +// PLATFORM_PIPE_READ()/PLATFORM_PIPE_WRITE() can be also safely be used if +// unsure if underlying fd is a socket or a pipe. +#define PLATFORM_PIPE_READ read +#define PLATFORM_PIPE_WRITE write +#endif + #ifdef __cplusplus } #endif // __cplusplus -#endif /* _PLATFORM_SEMAPHORE_H */ +#endif /* defined platform_pipe_h */ diff --git a/src/control_socket.cpp b/src/control_socket.cpp index 501be6918..786c66b65 100644 --- a/src/control_socket.cpp +++ b/src/control_socket.cpp @@ -806,8 +806,8 @@ static void * control_thread(void *args) while(cur) { if(FD_ISSET(cur->fd, &fds)) { - ssize_t ret = recv(cur->fd, cur->buff + cur->buff_len, - sizeof(cur->buff) - cur->buff_len, 0); + ssize_t ret = PLATFORM_PIPE_READ(cur->fd, cur->buff + cur->buff_len, + sizeof(cur->buff) - cur->buff_len); if(ret == -1) { fprintf(stderr, "Error reading socket, closing!!!\n"); } diff --git a/src/main.cpp b/src/main.cpp index 84bcae192..56f85182f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -172,7 +172,9 @@ struct state_uv { static void should_exit_watcher(state_uv *s) { set_thread_name(__func__); char c; - while (recv(s->should_exit_pipe[0], &c, 1, 0) != 1) perror("recv"); + while (PLATFORM_PIPE_READ(s->should_exit_pipe[0], &c, 1) != 1) { + perror("PLATFORM_PIPE_READ"); + } unique_lock lk(s->lock); for (auto c : s->should_exit_callbacks) { get<0>(c)(get<1>(c)); @@ -185,7 +187,9 @@ struct state_uv { return; } should_exit_thread_notified = true; - while (send(should_exit_pipe[1], &c, 1, 0) != 1) perror("send"); + while (PLATFORM_PIPE_WRITE(should_exit_pipe[1], &c, 1) != 1) { + perror("PLATFORM_PIPE_WRITE"); + } } static void new_message(struct module *mod) { diff --git a/src/rtp/net_udp.cpp b/src/rtp/net_udp.cpp index 1d98bec2a..34ca19c30 100644 --- a/src/rtp/net_udp.cpp +++ b/src/rtp/net_udp.cpp @@ -967,7 +967,7 @@ void udp_exit(socket_udp * s) if (!s->local_is_slave) { if (s->local->multithreaded) { char c = 0; - int ret = send(s->local->should_exit_fd[1], &c, 1, 0); + int ret = PLATFORM_PIPE_WRITE(s->local->should_exit_fd[1], &c, 1); assert (ret == 1); s->local->should_exit = true; s->local->reader_cv.notify_one(); From 7889b1b972a3d4ccd769c26799a2a1a24e7bae39 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 11 Nov 2020 15:56:16 +0100 Subject: [PATCH 25/61] Bump to new GPUJPEG --- gpujpeg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpujpeg b/gpujpeg index d277a02f5..3d3731f30 160000 --- a/gpujpeg +++ b/gpujpeg @@ -1 +1 @@ -Subproject commit d277a02f517814c12279c55186af842a5564a18c +Subproject commit 3d3731f30c6e03683a4b08d718a24b7cd8028cec From e4a15e5f461ed91b3fd663f1904ec40e6d3a1bad Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 10 Nov 2020 09:27:28 +0100 Subject: [PATCH 26/61] Configure: check GPUJPEG version --- configure.ac | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 1a350f492..2f6192484 100644 --- a/configure.ac +++ b/configure.ac @@ -2175,9 +2175,16 @@ AC_ARG_ENABLE(gpujpeg, [gpujpeg_req=$build_default]) -PKG_CHECK_MODULES([LIBGPUJPEG], [libgpujpeg >= 0.0.2 ], [found_gpujpeg=yes], [found_gpujpeg=no]) +PKG_CHECK_MODULES([LIBGPUJPEG_ANY], [ libgpujpeg ], [ found_gpujpeg_any=yes ], [ found_gpujpeg_any=no ]) +PKG_CHECK_MODULES([LIBGPUJPEG], [ libgpujpeg >= 0.11 ], [ found_gpujpeg=yes ], [ found_gpujpeg=no ]) -if test "$found_gpujpeg" != yes +if test "$found_gpujpeg_any" = yes -a "$found_gpujpeg" = no; then + AC_MSG_WARN([Old GPUJPEG version found, please upgrade]); +fi + +# try to find without pkg-config (but only if old version was not detected - to avoid side-loading +# that version without pkg-config version check) +if test "$found_gpujpeg_any" != yes -a "$found_gpujpeg" != yes then GPUJPEG_LIB="$GPUJPEG_LIB -lgpujpeg" SAVED_LIBS=$LIBS From 49dd33263970aded7b328c429851722b71d83b80 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 11 Nov 2020 13:29:51 +0100 Subject: [PATCH 27/61] SDP: Set correct IP version + some documentation --- src/main.cpp | 2 +- src/utils/sdp.c | 6 +++--- src/video_rxtx/h264_sdp.cpp | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 56f85182f..f5b9ca99b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1131,7 +1131,7 @@ int main(int argc, char *argv[]) audio_codec = "OPUS:sample_rate=48000"; } if (requested_compression == nullptr) { - requested_compression = "none"; // will be set later + requested_compression = "none"; // will be set later by h264_sdp_video_rxtx::send_frame() } if (force_ip_version == 0 && strcasecmp(video_protocol, "rtsp") == 0) { force_ip_version = 4; diff --git a/src/utils/sdp.c b/src/utils/sdp.c index 7416f1841..6e95e2d8b 100644 --- a/src/utils/sdp.c +++ b/src/utils/sdp.c @@ -106,7 +106,7 @@ struct sdp { }; struct sdp *new_sdp(int ip_version, const char *receiver) { - assert(ip_version == 0 || ip_version == 4 || ip_version == 6); + assert(ip_version == 4 || ip_version == 6); struct sdp *sdp; sdp = calloc(1, sizeof(struct sdp)); assert(sdp != NULL); @@ -137,9 +137,9 @@ struct sdp *new_sdp(int ip_version, const char *receiver) { connection_address = receiver; } strncpy(sdp->version, "v=0\n", STR_LENGTH - 1); - snprintf(sdp->origin, STR_LENGTH, "o=- 0 0 IN IP%d %s\n", ip_version == 0 ? 4 : 6, origin_address); + snprintf(sdp->origin, STR_LENGTH, "o=- 0 0 IN IP%d %s\n", ip_version, origin_address); strncpy(sdp->session_name, "s=Ultragrid streams\n", STR_LENGTH - 1); - snprintf(sdp->connection, STR_LENGTH, "c=IN IP%d %s\n", ip_version == 0 ? 4 : 6, connection_address); + snprintf(sdp->connection, STR_LENGTH, "c=IN IP%d %s\n", ip_version, connection_address); strncpy(sdp->times, "t=0 0\n", STR_LENGTH - 1); return sdp; diff --git a/src/video_rxtx/h264_sdp.cpp b/src/video_rxtx/h264_sdp.cpp index 4714d64c6..c0c93a58b 100644 --- a/src/video_rxtx/h264_sdp.cpp +++ b/src/video_rxtx/h264_sdp.cpp @@ -82,7 +82,7 @@ h264_sdp_video_rxtx::h264_sdp_video_rxtx(std::map const &p } LOG(LOG_LEVEL_WARNING) << "Warning: SDP support is experimental only. Things may be broken - feel free to report them but the support may be limited.\n"; - m_sdp = new_sdp(params.at("force_ip_version").i, m_requested_receiver.c_str()); + m_sdp = new_sdp(rtp_is_ipv6(m_network_devices[0]) ? 6 : 4 , m_requested_receiver.c_str()); m_saved_addr = m_requested_receiver; if (m_sdp == nullptr) { throw string("[SDP] SDP creation failed\n"); @@ -162,6 +162,12 @@ void h264_sdp_video_rxtx::sdp_add_video(codec_t codec) #endif } +/** + * @note + * This function sets compression just after first frame is received. The delayed initialization is to allow devices + * producing H.264/JPEG natively (eg. v4l2) to be passed untouched to transport. Fallback H.264 is applied when uncompressed + * stream is detected. + */ void h264_sdp_video_rxtx::send_frame(shared_ptr tx_frame) { if (!is_codec_opaque(tx_frame->color_spec)) { From a0f48ad2557b5a890726d066b2543591cd452632 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 13 Nov 2020 13:25:05 +0100 Subject: [PATCH 28/61] Lavc: try to set CQP or CRF for all codecs Try to set CQP or CRF for all codecs if user requests it. Eg. NVENC positively supports QP. --- src/video_compress/libavcodec.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/video_compress/libavcodec.cpp b/src/video_compress/libavcodec.cpp index 3eaa55fdf..241056096 100644 --- a/src/video_compress/libavcodec.cpp +++ b/src/video_compress/libavcodec.cpp @@ -673,29 +673,26 @@ bool set_codec_ctx_params(struct state_video_compress_libav *s, AVPixelFormat pi // set bitrate if ((s->requested_bitrate > 0 || s->requested_bpp > 0.0) - || !is_x264_x265) { + || (!is_x264_x265 && s->requested_crf == -1 && s->requested_cqp == -1)) { s->codec_ctx->bit_rate = bitrate; s->codec_ctx->bit_rate_tolerance = bitrate / desc.fps * 6; LOG(LOG_LEVEL_INFO) << MOD_NAME << "Setting bitrate to " << bitrate << " bps.\n"; - } - - if (is_x264_x265) { + } else { // set CRF unless explicitly specified CQP or ABR (bitrate) if (s->requested_crf >= 0.0 || (s->requested_bitrate == 0 && s->requested_bpp == 0.0 && s->requested_cqp == -1)) { double crf = s->requested_crf >= 0.0 ? s->requested_crf : DEFAULT_X264_X265_CRF; - av_opt_set_double(s->codec_ctx->priv_data, "crf", crf, 0); - log_msg(LOG_LEVEL_INFO, "[lavc] Setting CRF to %.2f.\n", crf); + if (int rc = av_opt_set_double(s->codec_ctx->priv_data, "crf", crf, 0)) { + print_libav_error(LOG_LEVEL_WARNING, MOD_NAME "Warning: Unable to set CRF", rc); + } else { + log_msg(LOG_LEVEL_INFO, "[lavc] Setting CRF to %.2f.\n", crf); + } } if (s->requested_cqp >= 0) { - av_opt_set_int(s->codec_ctx->priv_data, "qp", s->requested_cqp, 0); - log_msg(LOG_LEVEL_INFO, "[lavc] Setting CQP to %d.\n", s->requested_cqp); - } - } else { - if (s->requested_crf >= 0.0) { - log_msg(LOG_LEVEL_WARNING, "[lavc] Unable to set CRF! Not supported for this encoder, ignored.\n"); - } - if (s->requested_cqp >= 0.0) { - log_msg(LOG_LEVEL_WARNING, "[lavc] Unable to set CQP! Not supported for this encoder, ignored.\n"); + if (int rc = av_opt_set_int(s->codec_ctx->priv_data, "qp", s->requested_cqp, 0)) { + print_libav_error(LOG_LEVEL_WARNING, MOD_NAME "Warning: Unable to set CQP", rc); + } else { + log_msg(LOG_LEVEL_INFO, "[lavc] Setting CQP to %d.\n", s->requested_cqp); + } } } From b888e09800176eccd72cf5aed59dfcbfa1a66783 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 13 Nov 2020 14:42:32 +0100 Subject: [PATCH 29/61] Key control: shortcut to control repeating of msgs --- src/debug.cpp | 2 +- src/debug.h | 6 +++++- src/keyboard_control.cpp | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index 50e5b7e22..38bb52c16 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -258,5 +258,5 @@ void Logger::preinit(bool skip_repeated) } std::atomic Logger::last_msg{}; -bool Logger::skip_repeated = true; +std::atomic Logger::skip_repeated{true}; diff --git a/src/debug.h b/src/debug.h index 445ae6dc9..e342c09a4 100644 --- a/src/debug.h +++ b/src/debug.h @@ -92,6 +92,8 @@ bool set_log_level(const char *optarg, bool *logger_repeat_msgs); #include "compat/platform_time.h" #include "rang.hpp" +class keyboard_control; // friend + // Log, version 0.1: a simple logging class class Logger { @@ -145,12 +147,14 @@ private: int level; std::ostringstream oss; - static bool skip_repeated; + static std::atomic skip_repeated; struct last_message { std::string msg; int count{0}; }; static std::atomic last_msg; // leaks last message upon exit + + friend class keyboard_control; }; #define LOG(level) \ diff --git a/src/keyboard_control.cpp b/src/keyboard_control.cpp index 69691fd45..35bd550bc 100644 --- a/src/keyboard_control.cpp +++ b/src/keyboard_control.cpp @@ -121,7 +121,7 @@ keyboard_control::keyboard_control(struct module *parent) : m_should_exit(false), m_started(false), m_locked_against_changes(true), - guarded_keys { '*', '/', '9', '0', 'c', 'C', 'm', 'M', '>', '<', 'e', 's', 'S', 'v', 'V' } + guarded_keys { '*', '/', '9', '0', 'c', 'C', 'm', 'M', '>', '<', 'e', 's', 'S', 'v', 'V', 'r' } { m_start_time = time(NULL); @@ -540,6 +540,10 @@ void keyboard_control::run() cout << "Log level: " << log_level << "\n"; break; } + case 'r': + Logger::skip_repeated = !Logger::skip_repeated; + cout << "Skip repeated messages: " << std::boolalpha << Logger::skip_repeated << std::noboolalpha << "\n"; + break; case 's': if (saved_log_level == -1) { saved_log_level = log_level; @@ -672,6 +676,7 @@ void keyboard_control::usage() BOLD("\t M ") << "- mute/unmute sender" << G('M') << "\n" << BOLD("\t v ") << "- increase verbosity level" << G('v') << "\n" << BOLD("\t V ") << "- decrease verbosity level" << G('V') << "\n" << + BOLD("\t r ") << "- skip repeated messages (toggle) " << G('r') << "\n" << BOLD("\t e ") << "- record captured content (toggle)" << G('e') << "\n" << BOLD("\t h ") << "- show help" << G('h') << "\n" << BOLD("\t i ") << "- show various information" << G('i') << "\n" << From 87d3d370e0cdd6859b593068ca5523e53e50ae57 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 20 Nov 2020 13:11:05 +0100 Subject: [PATCH 30/61] NDI audio playback: fixed multiple channels playback Refers to GitHub issue #101. --- src/video_display/ndi.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/video_display/ndi.c b/src/video_display/ndi.c index 2485589a4..606a439ea 100644 --- a/src/video_display/ndi.c +++ b/src/video_display/ndi.c @@ -273,18 +273,14 @@ static void display_ndi_put_audio_frame(void *state, struct audio_frame *frame) { struct display_ndi *s = (struct display_ndi *) state; assert(frame->bps == 4); - float *tmp = malloc(frame->data_len); - NDIlib_audio_frame_v2_t NDI_audio_frame = { 0 }; + NDIlib_audio_frame_interleaved_32s_t NDI_audio_frame = { 0 }; NDI_audio_frame.sample_rate = frame->sample_rate; NDI_audio_frame.no_channels = frame->ch_count; NDI_audio_frame.timecode = NDIlib_send_timecode_synthesize; - NDI_audio_frame.p_data = tmp; - NDI_audio_frame.channel_stride_in_bytes = frame->data_len / frame->ch_count; - NDI_audio_frame.no_samples = frame->data_len / frame->ch_count / sizeof(float); - int2float((char *) tmp, frame->data, frame->data_len); + NDI_audio_frame.p_data = (int32_t *) frame->data; + NDI_audio_frame.no_samples = frame->data_len / frame->ch_count / sizeof NDI_audio_frame.p_data[0]; - NDIlib_send_send_audio_v2(s->pNDI_send, &NDI_audio_frame); - free(tmp); + NDIlib_util_send_send_audio_interleaved_32s(s->pNDI_send, &NDI_audio_frame); } static int display_ndi_reconfigure_audio(void *state, int quant_samples, int channels, From f364cc0a8c9103d61a350b145245218ae6d6e0f7 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 19 Nov 2020 15:27:19 +0100 Subject: [PATCH 31/61] Added NAT PMP --- COPYRIGHT | 33 +- Makefile.in | 13 + ext-deps/libnatpmp-20150609/.gitignore | 7 + ext-deps/libnatpmp-20150609/Changelog.txt | 98 +++ ext-deps/libnatpmp-20150609/JavaTest.java | 42 ++ ext-deps/libnatpmp-20150609/LICENSE | 26 + ext-deps/libnatpmp-20150609/Makefile | 177 ++++++ ext-deps/libnatpmp-20150609/README | 7 + ext-deps/libnatpmp-20150609/build.bat | 30 + ext-deps/libnatpmp-20150609/declspec.h | 21 + .../miniupnp/libnatpmp/LibraryExtractor.java | 238 ++++++++ .../fr/free/miniupnp/libnatpmp/NatPmp.java | 50 ++ .../miniupnp/libnatpmp/NatPmpResponse.java | 28 + .../fr/free/miniupnp/libnatpmp/URLUtils.java | 81 +++ ext-deps/libnatpmp-20150609/getgateway.c | 573 ++++++++++++++++++ ext-deps/libnatpmp-20150609/getgateway.h | 49 ++ ext-deps/libnatpmp-20150609/libnatpmpmodule.c | 281 +++++++++ .../libnatpmp-20150609/msvc/libnatpmp.sln | 29 + .../libnatpmp-20150609/msvc/libnatpmp.vcproj | 195 ++++++ .../msvc/natpmpc-static.vcproj | 195 ++++++ ext-deps/libnatpmp-20150609/natpmp-jni.c | 157 +++++ ext-deps/libnatpmp-20150609/natpmp.c | 379 ++++++++++++ ext-deps/libnatpmp-20150609/natpmp.def | 11 + ext-deps/libnatpmp-20150609/natpmp.h | 219 +++++++ ext-deps/libnatpmp-20150609/natpmpc.1 | 19 + ext-deps/libnatpmp-20150609/natpmpc.c | 244 ++++++++ ext-deps/libnatpmp-20150609/setup.py | 18 + ext-deps/libnatpmp-20150609/setupmingw32.py | 17 + ext-deps/libnatpmp-20150609/testgetgateway.c | 57 ++ ext-deps/libnatpmp-20150609/wingettimeofday.c | 60 ++ ext-deps/libnatpmp-20150609/wingettimeofday.h | 39 ++ src/main.cpp | 16 +- src/utils/nat.c | 204 +++++++ src/utils/nat.h | 60 ++ 34 files changed, 3671 insertions(+), 2 deletions(-) create mode 100644 ext-deps/libnatpmp-20150609/.gitignore create mode 100644 ext-deps/libnatpmp-20150609/Changelog.txt create mode 100644 ext-deps/libnatpmp-20150609/JavaTest.java create mode 100644 ext-deps/libnatpmp-20150609/LICENSE create mode 100644 ext-deps/libnatpmp-20150609/Makefile create mode 100644 ext-deps/libnatpmp-20150609/README create mode 100644 ext-deps/libnatpmp-20150609/build.bat create mode 100644 ext-deps/libnatpmp-20150609/declspec.h create mode 100644 ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/LibraryExtractor.java create mode 100644 ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmp.java create mode 100644 ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmpResponse.java create mode 100644 ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/URLUtils.java create mode 100644 ext-deps/libnatpmp-20150609/getgateway.c create mode 100644 ext-deps/libnatpmp-20150609/getgateway.h create mode 100644 ext-deps/libnatpmp-20150609/libnatpmpmodule.c create mode 100644 ext-deps/libnatpmp-20150609/msvc/libnatpmp.sln create mode 100644 ext-deps/libnatpmp-20150609/msvc/libnatpmp.vcproj create mode 100644 ext-deps/libnatpmp-20150609/msvc/natpmpc-static.vcproj create mode 100644 ext-deps/libnatpmp-20150609/natpmp-jni.c create mode 100644 ext-deps/libnatpmp-20150609/natpmp.c create mode 100644 ext-deps/libnatpmp-20150609/natpmp.def create mode 100644 ext-deps/libnatpmp-20150609/natpmp.h create mode 100644 ext-deps/libnatpmp-20150609/natpmpc.1 create mode 100644 ext-deps/libnatpmp-20150609/natpmpc.c create mode 100644 ext-deps/libnatpmp-20150609/setup.py create mode 100644 ext-deps/libnatpmp-20150609/setupmingw32.py create mode 100644 ext-deps/libnatpmp-20150609/testgetgateway.c create mode 100644 ext-deps/libnatpmp-20150609/wingettimeofday.c create mode 100644 ext-deps/libnatpmp-20150609/wingettimeofday.h create mode 100644 src/utils/nat.c create mode 100644 src/utils/nat.h diff --git a/COPYRIGHT b/COPYRIGHT index 1148178a2..bf3e3a757 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ - UltraGrid - A High Definition Collaboratory +=========================================== Copyright (c) 2005-2018 CESNET z.s.p.o. Copyright (c) 2013-2014 FundaciĂ³ i2CAT, Internet I InnovaciĂ³ Digital a Catalunya @@ -60,3 +60,34 @@ UltraGrid - A High Definition Collaboratory for use in the OpenSSL Toolkit. (http://www.openssl.org/) This product includes EmbeddableWebServer created by Forrest Heller. +External libraries +------------------ + +### libnatpmp + +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. + diff --git a/Makefile.in b/Makefile.in index 2396fa55b..6a74f77c6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -137,6 +137,7 @@ COMMON_OBJS = \ src/utils/jpeg_reader.o \ src/utils/list.o \ src/utils/misc.o \ + src/utils/nat.o \ src/utils/net.o \ src/utils/packet_counter.o \ src/utils/resource_manager.o \ @@ -193,6 +194,7 @@ COMMON_OBJS = \ ldgm/matrix-gen/matrix-generator.o \ ldgm/matrix-gen/ldpc-matrix.o \ rs/fec.o \ + ext-deps/libnatpmp-20150609/libnatpmp.a \ OBJS = @OBJS@ \ $(COMMON_OBJS) @@ -265,6 +267,16 @@ $(REFLECTOR_TARGET): src/dir-stamp $(OBJS) $(GENERATED_HEADERS) $(REFLECTOR_OBJS "$(CUDA_COMPILER)" $(CUDA_FLAGS) -DEXPORT_DLL_SYMBOLS $(INC) --shared $< -o $<.dll touch $@ +ext-deps/libnatpmp-20150609/libnatpmp.a: + $(MKDIR_P) $(dir $@) +ifeq ($(SYSTEM),Windows) + cd $(srcdir)/ext-deps/libnatpmp-20150609 && cmd /c build.bat + mv $(srcdir)/ext-deps/libnatpmp-20150609/natpmp.a $(dir $@)/libnatpmp.a +else + make -C $(srcdir)/ext-deps/libnatpmp-20150609 libnatpmp.a + [ -f $@ ] || mv $(srcdir)/ext-deps/libnatpmp-20150609/libnatpmp.a $(dir $@) +endif + src/libavcodec_common.o: src/libavcodec_common.c $(ALL_INCLUDES) $(MKDIR_P) $(dir $@) $(CC) $(CFLAGS) -Ofast $(INC) -c $< -o $@ @@ -688,3 +700,4 @@ uninstall: $(RM) $(DESTDIR)$(datadir)/pixmaps/ultragrid.png;\ fi +# vim: set noexpandtab diff --git a/ext-deps/libnatpmp-20150609/.gitignore b/ext-deps/libnatpmp-20150609/.gitignore new file mode 100644 index 000000000..f63281e14 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/.gitignore @@ -0,0 +1,7 @@ +!* +*.a +*.o +*.so +natpmpc-shared +natpmpc-static +testgetgateway diff --git a/ext-deps/libnatpmp-20150609/Changelog.txt b/ext-deps/libnatpmp-20150609/Changelog.txt new file mode 100644 index 000000000..be75a0b6d --- /dev/null +++ b/ext-deps/libnatpmp-20150609/Changelog.txt @@ -0,0 +1,98 @@ +$Id: Changelog.txt,v 1.33 2013/11/26 08:47:36 nanard Exp $ + +2013/11/26: + enforce strict aliasing rules. + +2013/09/10: + small patch for MSVC >= 16 + rename win32 implementation of gettimeofday() to natpmp_gettimeofday() + +2012/08/21: + Little change in Makefile + removed warnings in testgetgateway.c + Fixed bugs in command line argumentparsing in natpmpc.c + +2011/08/07: + Patch to build on debian/kFreeBSD. + +2011/07/15: + Put 3 clauses BSD licence at the top of source files. + +2011/06/18: + --no-undefined => -Wl,--no-undefined + adding a natpmpc.1 man page + +2011/05/19: + Small fix in libnatpmpmodule.c thanks to Manuel Mausz + +2011/01/03: + Added an argument to initnatpmp() in order to force the gateway to be used + +2011/01/01: + fix in make install + +2010/05/21: + make install now working under MacOSX (and BSD) + +2010/04/12: + cplusplus stuff in natpmp.h + +2010/02/02: + Fixed compilation under Mac OS X + +2009/12/19: + improve and fix building under Windows. + Project files for MS Visual Studio 2008 + More simple (and working) code for Win32. + More checks in the /proc/net/route parsing. Add some comments. + +2009/08/04: + improving getgateway.c for windows + +2009/07/13: + Adding Haiku code in getgateway.c + +2009/06/04: + Adding Python module thanks to David Wu + +2009/03/10: + Trying to have windows get gateway working if not using DHCP + +2009/02/27: + dont include declspec.h if not under WIN32. + +2009/01/23: + Prefixed the libraries name with lib + +2008/10/06: + Fixed a memory leak in getdefaultgateway() (USE_SYSCTL_NET_ROUTE) + +2008/07/03: + Adding WIN32 code from Robbie Hanson + +2008/06/30: + added a Solaris implementation for getgateway(). + added a LICENCE file to the distribution + +2008/05/29: + Anonymous unions are forbidden in ANSI C. That was causing problems with + non-GCC compilers. + +2008/04/28: + introduced strnatpmperr() + improved natpmpc.c sample + make install now install the binary + +2007/12/13: + Fixed getgateway.c for working under OS X ;) + Fixed values for NATPMP_PROTOCOL_TCP and NATPMP_PROTOCOL_UDP + +2007/12/11: + Fixed getgateway.c for compilation under Mac OS X + +2007/12/01: + added some comments in .h + +2007/11/30: + implemented almost everything + diff --git a/ext-deps/libnatpmp-20150609/JavaTest.java b/ext-deps/libnatpmp-20150609/JavaTest.java new file mode 100644 index 000000000..0379c1821 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/JavaTest.java @@ -0,0 +1,42 @@ +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import fr.free.miniupnp.libnatpmp.NatPmp; +import fr.free.miniupnp.libnatpmp.NatPmpResponse; + +class JavaTest { + public static void main(String[] args) { + NatPmp natpmp = new NatPmp(); + + natpmp.sendPublicAddressRequest(); + NatPmpResponse response = new NatPmpResponse(); + + int result = -1; + do{ + result = natpmp.readNatPmpResponseOrRetry(response); + try { + Thread.sleep(4000); + } catch (InterruptedException e) { + //fallthrough + } + } while (result != 0); + + byte[] bytes = intToByteArray(response.addr); + + try { + InetAddress inetAddress = InetAddress.getByAddress(bytes); + System.out.println("Public address is " + inetAddress); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + public static final byte[] intToByteArray(int value) { + return new byte[] { + (byte)value, + (byte)(value >>> 8), + (byte)(value >>> 16), + (byte)(value >>> 24)}; + } +} diff --git a/ext-deps/libnatpmp-20150609/LICENSE b/ext-deps/libnatpmp-20150609/LICENSE new file mode 100644 index 000000000..7fff2c26a --- /dev/null +++ b/ext-deps/libnatpmp-20150609/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. + diff --git a/ext-deps/libnatpmp-20150609/Makefile b/ext-deps/libnatpmp-20150609/Makefile new file mode 100644 index 000000000..b67b3e85c --- /dev/null +++ b/ext-deps/libnatpmp-20150609/Makefile @@ -0,0 +1,177 @@ +# $Id: Makefile,v 1.23 2013/11/26 16:38:15 nanard Exp $ +# This Makefile is designed for use with GNU make +# libnatpmp +# (c) 2007-2013 Thomas Bernard +# http://miniupnp.free.fr/libnatpmp.html + +OS = $(shell uname -s) +CC = gcc +INSTALL = install -p +ARCH = $(shell uname -m | sed -e s/i.86/i686/) +VERSION = $(shell cat VERSION) + +ifeq ($(OS), Darwin) +JARSUFFIX=mac +endif +ifeq ($(OS), Linux) +JARSUFFIX=linux +endif +ifneq (,$(findstring WIN,$(OS))) +JARSUFFIX=win32 +endif + +# APIVERSION is used in soname +APIVERSION = 1 +#LDFLAGS = -Wl,--no-undefined +CFLAGS ?= -Os +#CFLAGS = -g -O0 +CFLAGS += -fPIC +CFLAGS += -Wall +#CFLAGS += -Wextra +CFLAGS += -DENABLE_STRNATPMPERR +#CFLAGS += -Wstrict-aliasing + +LIBOBJS = natpmp.o getgateway.o + +OBJS = $(LIBOBJS) testgetgateway.o natpmpc.o natpmp-jni.o + +STATICLIB = libnatpmp.a +ifeq ($(OS), Darwin) + SHAREDLIB = libnatpmp.dylib + JNISHAREDLIB = libjninatpmp.dylib + SONAME = $(basename $(SHAREDLIB)).$(APIVERSION).dylib + CFLAGS := -DMACOSX -D_DARWIN_C_SOURCE $(CFLAGS) + SONAMEFLAGS=-Wl,-install_name,$(JNISHAREDLIB) -dynamiclib -framework JavaVM +else +ifneq (,$(findstring WIN,$(OS))) + SHAREDLIB = natpmp.dll + JNISHAREDLIB = jninatpmp.dll + CC = i686-w64-mingw32-gcc + EXTRA_LD = -lws2_32 -lIphlpapi -Wl,--no-undefined -Wl,--enable-runtime-pseudo-reloc --Wl,kill-at +else + SHAREDLIB = libnatpmp.so + JNISHAREDLIB = libjninatpmp.so + SONAME = $(SHAREDLIB).$(APIVERSION) + SONAMEFLAGS=-Wl,-soname,$(JNISHAREDLIB) +endif +endif + +HEADERS = natpmp.h + +EXECUTABLES = testgetgateway natpmpc-shared natpmpc-static + +INSTALLPREFIX ?= $(PREFIX)/usr +INSTALLDIRINC = $(INSTALLPREFIX)/include +INSTALLDIRLIB = $(INSTALLPREFIX)/lib +INSTALLDIRBIN = $(INSTALLPREFIX)/bin + +JAVA ?= java +JAVAC ?= javac +JAVAH ?= javah +JAVAPACKAGE = fr/free/miniupnp/libnatpmp +JAVACLASSES = $(JAVAPACKAGE)/NatPmp.class $(JAVAPACKAGE)/NatPmpResponse.class $(JAVAPACKAGE)/LibraryExtractor.class $(JAVAPACKAGE)/URLUtils.class +JNIHEADERS = fr_free_miniupnp_libnatpmp_NatPmp.h + +.PHONY: all clean depend install cleaninstall installpythonmodule + +all: $(STATICLIB) $(SHAREDLIB) $(EXECUTABLES) + +pythonmodule: $(STATICLIB) libnatpmpmodule.c setup.py + python setup.py build + touch $@ + +installpythonmodule: pythonmodule + python setup.py install + +clean: + $(RM) $(OBJS) $(EXECUTABLES) $(STATICLIB) $(SHAREDLIB) $(JAVACLASSES) $(JNISHAREDLIB) + $(RM) pythonmodule + $(RM) -r build/ dist/ libraries/ + +depend: + makedepend -f$(MAKEFILE_LIST) -Y $(OBJS:.o=.c) 2>/dev/null + +install: $(HEADERS) $(STATICLIB) $(SHAREDLIB) natpmpc-shared + $(INSTALL) -d $(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(INSTALLDIRINC) + $(INSTALL) -d $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(STATICLIB) $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(SHAREDLIB) $(INSTALLDIRLIB)/$(SONAME) + $(INSTALL) -d $(INSTALLDIRBIN) + $(INSTALL) -m 755 natpmpc-shared $(INSTALLDIRBIN)/natpmpc + ln -s -f $(SONAME) $(INSTALLDIRLIB)/$(SHAREDLIB) + +$(JNIHEADERS): fr/free/miniupnp/libnatpmp/NatPmp.class + $(JAVAH) -jni fr.free.miniupnp.libnatpmp.NatPmp + +%.class: %.java + $(JAVAC) -cp . $< + +$(JNISHAREDLIB): $(SHAREDLIB) $(JNIHEADERS) $(JAVACLASSES) +ifeq (,$(JAVA_HOME)) + @echo "Check your JAVA_HOME environement variable" && false +endif +ifneq (,$(findstring WIN,$(OS))) + $(CC) -m32 -D_JNI_Implementation_ -Wl,--kill-at \ + -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" \ + natpmp-jni.c -shared \ + -o $(JNISHAREDLIB) -L. -lnatpmp -lws2_32 -lIphlpapi +else + $(CC) $(CFLAGS) -c -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" natpmp-jni.c + $(CC) $(CFLAGS) -o $(JNISHAREDLIB) -shared $(SONAMEFLAGS) natpmp-jni.o -lc -L. -lnatpmp +endif + +jar: $(JNISHAREDLIB) + find fr -name '*.class' -print > classes.list + $(eval JNISHAREDLIBPATH := $(shell java fr.free.miniupnp.libnatpmp.LibraryExtractor)) + mkdir -p libraries/$(JNISHAREDLIBPATH) + mv $(JNISHAREDLIB) libraries/$(JNISHAREDLIBPATH)/$(JNISHAREDLIB) + jar cf natpmp_$(JARSUFFIX).jar @classes.list libraries/$(JNISHAREDLIBPATH)/$(JNISHAREDLIB) + $(RM) classes.list + +jnitest: $(JNISHAREDLIB) JavaTest.class + $(RM) libjninatpmp.so + $(JAVA) -Djna.nosys=true -cp . JavaTest + +mvn_install: + mvn install:install-file -Dfile=java/natpmp_$(JARSUFFIX).jar \ + -DgroupId=com.github \ + -DartifactId=natpmp \ + -Dversion=$(VERSION) \ + -Dpackaging=jar \ + -Dclassifier=$(JARSUFFIX) \ + -DgeneratePom=true \ + -DcreateChecksum=true + +cleaninstall: + $(RM) $(addprefix $(INSTALLDIRINC), $(HEADERS)) + $(RM) $(INSTALLDIRLIB)/$(SONAME) + $(RM) $(INSTALLDIRLIB)/$(SHAREDLIB) + $(RM) $(INSTALLDIRLIB)/$(STATICLIB) + +testgetgateway: testgetgateway.o getgateway.o + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +natpmpc-static: natpmpc.o $(STATICLIB) + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +natpmpc-shared: natpmpc.o $(SHAREDLIB) + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +$(STATICLIB): $(LIBOBJS) + $(AR) crs $@ $? + +$(SHAREDLIB): $(LIBOBJS) +ifeq ($(OS), Darwin) + $(CC) -dynamiclib -Wl,-install_name,$(SONAME) -o $@ $^ +else + $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $^ $(EXTRA_LD) +endif + + +# DO NOT DELETE + +natpmp.o: natpmp.h getgateway.h declspec.h +getgateway.o: getgateway.h declspec.h +testgetgateway.o: getgateway.h declspec.h +natpmpc.o: natpmp.h diff --git a/ext-deps/libnatpmp-20150609/README b/ext-deps/libnatpmp-20150609/README new file mode 100644 index 000000000..269392d2a --- /dev/null +++ b/ext-deps/libnatpmp-20150609/README @@ -0,0 +1,7 @@ +libnatpmp (c) 2007-2009 Thomas Bernard +contact : miniupnp@free.fr + +see http://miniupnp.free.fr/libnatpmp.html +or http://miniupnp.tuxfamily.org/libnatpmp.html +for some documentation and code samples. + diff --git a/ext-deps/libnatpmp-20150609/build.bat b/ext-deps/libnatpmp-20150609/build.bat new file mode 100644 index 000000000..2d2f27cd7 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/build.bat @@ -0,0 +1,30 @@ +@echo Compiling with MinGW +@SET LIBS=-lws2_32 -liphlpapi + +@echo Compile getgateway +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR getgateway.c +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR testgetgateway.c +gcc -o testgetgateway getgateway.o testgetgateway.o %LIBS% +del testgetgateway.o + +@echo Compile natpmp-static: +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR getgateway.c +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR natpmp.c +gcc -c -Wall -Os -DWIN32 wingettimeofday.c +ar cr natpmp.a getgateway.o natpmp.o wingettimeofday.o +del getgateway.o natpmp.o +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR natpmpc.c +gcc -o natpmpc-static natpmpc.o natpmp.a %LIBS% +upx --best natpmpc-static.exe +del natpmpc.o + +@echo Create natpmp.dll: +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS getgateway.c +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS natpmp.c +dllwrap -k --driver-name gcc --def natpmp.def --output-def natpmp.dll.def --implib natpmp.lib -o natpmp.dll getgateway.o natpmp.o wingettimeofday.o %LIBS% + +@echo Compile natpmp-shared: +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS natpmpc.c +gcc -o natpmpc-shared natpmpc.o natpmp.lib -lws2_32 +upx --best natpmpc-shared.exe +del *.o diff --git a/ext-deps/libnatpmp-20150609/declspec.h b/ext-deps/libnatpmp-20150609/declspec.h new file mode 100644 index 000000000..a76be021c --- /dev/null +++ b/ext-deps/libnatpmp-20150609/declspec.h @@ -0,0 +1,21 @@ +#ifndef DECLSPEC_H_INCLUDED +#define DECLSPEC_H_INCLUDED + +#if defined(WIN32) && !defined(STATICLIB) + /* for windows dll */ + #ifdef NATPMP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #if defined(__GNUC__) && __GNUC__ >= 4 + /* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */ + #define LIBSPEC __attribute__ ((visibility ("default"))) + #else + #define LIBSPEC + #endif +#endif + +#endif + diff --git a/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/LibraryExtractor.java b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/LibraryExtractor.java new file mode 100644 index 000000000..5491d9408 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/LibraryExtractor.java @@ -0,0 +1,238 @@ +package fr.free.miniupnp.libnatpmp; + +/** I (Leah X Schmidt) copied this code from jnaerator, because +JNAerator's extractor requires you to buy into the whole JNA +concept. + +JNAErator is +Copyright (c) 2009 Olivier Chafik, All Rights Reserved + +JNAerator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +JNAerator is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with JNAerator. If not, see . + +*/ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class LibraryExtractor { + + private static boolean libPathSet = false; + + public static String getLibraryPath(String libraryName, boolean extractAllLibraries, Class cl) { + try { + String customPath = System.getProperty("library." + libraryName); + if (customPath == null) + customPath = System.getenv(libraryName.toUpperCase() + "_LIBRARY"); + if (customPath != null) { + File f = new File(customPath); + if (!f.exists()) + System.err.println("Library file '" + customPath + "' does not exist !"); + else + return f.getAbsolutePath(); + } + //ClassLoader cl = LibraryExtractor.class.getClassLoader(); + String prefix = "(?i)" + (isWindows() ? "" : "lib") + libraryName + "[^A-Za-z_].*"; + String libsuffix = "(?i).*\\.(so|dll|dylib|jnilib)"; + //String othersuffix = "(?i).*\\.(pdb)"; + + URL sourceURL = null; + List otherURLs = new ArrayList(); + + + String arch = getCurrentOSAndArchString(); + //System.out.println("libURL = " + libURL); + List list = URLUtils.listFiles(URLUtils.getResource(cl, "libraries/" + arch)), + noArchList = URLUtils.listFiles(URLUtils.getResource(cl, "libraries/noarch")); + + Set names = new HashSet(); + for (URL url : list) { + String name = getFileName(url); + names.add(name); + } + for (URL url : noArchList) { + String name = getFileName(url); + if (names.add(name)) + list.add(url); + } + + for (File f : new File(".").listFiles()) + if (f.isFile()) + list.add(f.toURI().toURL()); + + for (URL url : list) { + String name = getFileName(url); + boolean pref = name.matches(prefix), suff = name.matches(libsuffix); + if (pref && suff) + sourceURL = url; + else //if (suff || fileName.matches(othersuffix)) + otherURLs.add(url); + } + List files = new ArrayList(); + if (extractAllLibraries) { + for (URL url : otherURLs) + files.add(extract(url)); + } + + if (System.getProperty("javawebstart.version") != null) { + if (isWindows()) { + //File f = new File("c:\\Windows\\" + (Platform.is64Bit() ? "SysWOW64\\" : "System32\\") + libraryName + ".dll"); + File f = new File("c:\\Windows\\" + "System32\\" + libraryName + ".dll"); + if (f.exists()) + return f.toString(); + } else if (isMac()) { + File f = new File("/System/Library/Frameworks/" + libraryName + ".framework/" + libraryName); + if (f.exists()) + return f.toString(); + } + } + + if (sourceURL == null) + return libraryName; + else { + File file = extract(sourceURL); + files.add(file); + + int lastSize; + do { + lastSize = files.size(); + for (Iterator it = files.iterator(); it.hasNext();) { + File f = it.next(); + if (!f.getName().matches(libsuffix)) + continue; + + try { + System.load(f.toString()); + it.remove(); + } catch (Throwable ex) { + System.err.println("Loading " + f.getName() + " failed (" + ex + ")"); + } + } + } while (files.size() < lastSize); + + return file.getCanonicalPath(); + } + } catch (Throwable ex) { + System.err.println("ERROR: Failed to extract library " + libraryName); + ex.printStackTrace(); + return libraryName; + } + } + + public static final boolean isWindows() { + String osName = System.getProperty("os.name"); + return osName.startsWith("Windows"); + } + + public static final boolean isMac() { + String osName = System.getProperty("os.name"); + return osName.startsWith("Mac") || osName.startsWith("Darwin"); + } + + //this code is from JNA, but JNA has a fallback to some native + //stuff in case this doesn't work. Since sun.arch.data.model is + //defined for Sun and IBM, this should work nearly everywhere. + public static final boolean is64Bit() { + String model = System.getProperty("sun.arch.data.model", + System.getProperty("com.ibm.vm.bitmode")); + if (model != null) { + return "64".equals(model); + } + String arch = System.getProperty("os.arch").toLowerCase(); + if ("x86_64".equals(arch) + || "ia64".equals(arch) + || "ppc64".equals(arch) + || "sparcv9".equals(arch) + || "amd64".equals(arch)) { + return true; + } + + return false; + } + + public static String getCurrentOSAndArchString() { + String os = System.getProperty("os.name"), arch = System.getProperty("os.arch"); + if (os.equals("Mac OS X")) { + os = "darwin"; + arch = "fat"; + } else if (os.startsWith("Windows")) { + return "win" + (is64Bit() ? "64" : "32"); + } else if (os.matches("SunOS|Solaris")) + os = "solaris"; + return os + "-" + arch; + } + + private static File extract(URL url) throws IOException { + File localFile; + if ("file".equals(url.getProtocol())) + localFile = new File(URLDecoder.decode(url.getFile(), "UTF-8")); + else { + File f = new File(System.getProperty("user.home")); + f = new File(f, ".jnaerator"); + f = new File(f, "extractedLibraries"); + if (!f.exists()) + f.mkdirs(); + + if (!libPathSet) { + String path = System.getProperty("java.library.path"); + if (path == null) { + System.setProperty("java.library.path", f.toString()); + } else { + System.setProperty("java.library.path", path + ":" + f); + } + + libPathSet = true; + } + localFile = new File(f, new File(url.getFile()).getName()); + URLConnection c = url.openConnection(); + if (localFile.exists() && localFile.lastModified() > c.getLastModified()) { + c.getInputStream().close(); + } else { + System.out.println("Extracting " + url); + InputStream in = c.getInputStream(); + OutputStream out = new FileOutputStream(localFile); + int len; + byte[] b = new byte[1024]; + while ((len = in.read(b)) > 0) + out.write(b, 0, len); + out.close(); + in.close(); + } + } + return localFile; + } + + private static String getFileName(URL url) { + return new File(url.getFile()).getName(); + } + + public static void main(String[] args) { + System.out.println(getCurrentOSAndArchString()); + } +} \ No newline at end of file diff --git a/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmp.java b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmp.java new file mode 100644 index 000000000..2f1ddd3d4 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmp.java @@ -0,0 +1,50 @@ +package fr.free.miniupnp.libnatpmp; + +import java.nio.ByteBuffer; + + +public class NatPmp { + private static final String JNA_LIBRARY_NAME = LibraryExtractor.getLibraryPath("jninatpmp", true, NatPmp.class); + + static { + String s = JNA_LIBRARY_NAME; + startup(); + } + + public ByteBuffer natpmp; + + public NatPmp() { + init(0, 0); + } + + public NatPmp(int forcedgw) { + init(1, forcedgw); + } + + /** Cleans up the native resources used by this object. + Attempting to use the object after this has been called + will lead to crashes.*/ + public void dispose() { + free(); + } + + + protected void finalize() { + if (natpmp != null) + free(); + } + + private native void init(int forcegw, int forcedgw); + private native void free(); + + private static native void startup(); + + public native int sendPublicAddressRequest(); + public native int sendNewPortMappingRequest(int protocol, int privateport, int publicport, int lifetime); + + //returns a number of milliseconds, in accordance with Java convention + public native long getNatPmpRequestTimeout(); + + public native int readNatPmpResponseOrRetry(NatPmpResponse response); + +} diff --git a/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmpResponse.java b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmpResponse.java new file mode 100644 index 000000000..35c87eab0 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/NatPmpResponse.java @@ -0,0 +1,28 @@ +package fr.free.miniupnp.libnatpmp; + +public class NatPmpResponse { + + public static final int TYPE_PUBLICADDRESS=0; + public static final int TYPE_UDPPORTMAPPING=1; + public static final int TYPE_TCPPORTMAPPING=2; + + /** see TYPE_* constants */ + public short type; + /** NAT-PMP response code */ + public short resultcode; + /** milliseconds since start of epoch */ + public long epoch; + + /** only defined if type == 0*/ + public int addr; + + /** only defined if type != 0 */ + public int privateport; + + /** only defined if type != 0 */ + public int mappedpublicport; + + /** only defined if type != 0 */ + public long lifetime; //milliseconds + +} \ No newline at end of file diff --git a/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/URLUtils.java b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/URLUtils.java new file mode 100644 index 000000000..5b419ab3a --- /dev/null +++ b/ext-deps/libnatpmp-20150609/fr/free/miniupnp/libnatpmp/URLUtils.java @@ -0,0 +1,81 @@ +package fr.free.miniupnp.libnatpmp; + +/** I (Leah X Schmidt) copied this code from jnaerator, because +JNAerator's extractor requires you to buy into the whole JNA +concept. + +JNAErator is +Copyright (c) 2009 Olivier Chafik, All Rights Reserved + +JNAerator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +JNAerator is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with JNAerator. If not, see . + +*/ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +public class URLUtils { + + public static URL getResource(Class cl, String path) throws IOException { + String clp = cl.getName().replace('.', '/') + ".class"; + URL clu = cl.getClassLoader().getResource(clp); + String s = clu.toString(); + if (s.endsWith(clp)) + return new URL(s.substring(0, s.length() - clp.length()) + path); + + if (s.startsWith("jar:")) { + String[] ss = s.split("!"); + return new URL(ss[1] + "!/" + path); + } + return null; + } + + public static List listFiles(URL directory) throws IOException { + List ret = new ArrayList(); + String s = directory.toString(); + if (s.startsWith("jar:")) { + String[] ss = s.substring("jar:".length()).split("!"); + String path = ss[1]; + URL target = new URL(ss[0]); + InputStream tin = target.openStream(); + try { + JarInputStream jin = new JarInputStream(tin); + JarEntry je; + while ((je = jin.getNextJarEntry()) != null) { + String p = "/" + je.getName(); + if (p.startsWith(path) && p.indexOf('/', path.length() + 1) < 0) + + ret.add(new URL("jar:" + target + "!" + p)); + } + } finally { + tin.close(); + } + } else if (s.startsWith("file:")) { + File f = new File(directory.getFile()); + File[] ffs = f.listFiles(); + if (ffs != null) + for (File ff : ffs) + ret.add(ff.toURI().toURL()); + } else + throw new IOException("Cannot list contents of " + directory); + + return ret; + } +} \ No newline at end of file diff --git a/ext-deps/libnatpmp-20150609/getgateway.c b/ext-deps/libnatpmp-20150609/getgateway.c new file mode 100644 index 000000000..dfb9f3e21 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/getgateway.c @@ -0,0 +1,573 @@ +/* $Id: getgateway.c,v 1.25 2014/04/22 10:28:57 nanard Exp $ */ +/* libnatpmp + +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#include +#include +#ifndef WIN32 +#include +#endif +#if !defined(_MSC_VER) +#include +#endif +/* There is no portable method to get the default route gateway. + * So below are four (or five ?) differents functions implementing this. + * Parsing /proc/net/route is for linux. + * sysctl is the way to access such informations on BSD systems. + * Many systems should provide route information through raw PF_ROUTE + * sockets. + * In MS Windows, default gateway is found by looking into the registry + * or by using GetBestRoute(). */ +#ifdef __linux__ +#define USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#if defined(BSD) || defined(__FreeBSD_kernel__) +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef __APPLE__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#define USE_SYSCTL_NET_ROUTE +#endif + +#if (defined(sun) && defined(__SVR4)) +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef WIN32 +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +//#define USE_WIN32_CODE +#define USE_WIN32_CODE_2 +#endif + +#ifdef __CYGWIN__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#define USE_WIN32_CODE +#include +#include +#include +#include +#endif + +#ifdef __HAIKU__ +#include +#include +#include +#include +#define USE_HAIKU_CODE +#endif + +#ifdef USE_SYSCTL_NET_ROUTE +#include +#include +#include +#include +#endif +#ifdef USE_SOCKET_ROUTE +#include +#include +#include +#include +#include +#endif + +#ifdef USE_WIN32_CODE +#include +#include +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_LENGTH 16383 +#endif + +#ifdef USE_WIN32_CODE_2 +#include +#include +#endif + +#include "getgateway.h" + +#ifndef WIN32 +#define SUCCESS (0) +#define FAILED (-1) +#endif + +#ifdef USE_PROC_NET_ROUTE +/* + parse /proc/net/route which is as follow : + +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +wlan0 0001A8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +eth0 0000FEA9 00000000 0001 0 0 0 0000FFFF 0 0 0 +wlan0 00000000 0101A8C0 0003 0 0 0 00000000 0 0 0 +eth0 00000000 00000000 0001 0 0 1000 00000000 0 0 0 + + One header line, and then one line by route by route table entry. +*/ +int getdefaultgateway(in_addr_t * addr) +{ + unsigned long d, g; + char buf[256]; + int line = 0; + FILE * f; + char * p; + f = fopen("/proc/net/route", "r"); + if(!f) + return FAILED; + while(fgets(buf, sizeof(buf), f)) { + if(line > 0) { /* skip the first line */ + p = buf; + /* skip the interface name */ + while(*p && !isspace(*p)) + p++; + while(*p && isspace(*p)) + p++; + if(sscanf(p, "%lx%lx", &d, &g)==2) { + if(d == 0 && g != 0) { /* default */ + *addr = g; + fclose(f); + return SUCCESS; + } + } + } + line++; + } + /* default route not found ! */ + if(f) + fclose(f); + return FAILED; +} +#endif /* #ifdef USE_PROC_NET_ROUTE */ + + +#ifdef USE_SYSCTL_NET_ROUTE + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + +int getdefaultgateway(in_addr_t * addr) +{ +#if 0 + /* net.route.0.inet.dump.0.0 ? */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_DUMP, 0, 0/*tableid*/}; +#endif + /* net.route.0.inet.flags.gateway */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_FLAGS, RTF_GATEWAY}; + size_t l; + char * buf, * p; + struct rt_msghdr * rt; + struct sockaddr * sa; + struct sockaddr * sa_tab[RTAX_MAX]; + int i; + int r = FAILED; + if(sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0) { + return FAILED; + } + if(l>0) { + buf = malloc(l); + if(sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) { + free(buf); + return FAILED; + } + for(p=buf; prtm_msglen) { + rt = (struct rt_msghdr *)p; + sa = (struct sockaddr *)(rt + 1); + for(i=0; irtm_addrs & (1 << i)) { + sa_tab[i] = sa; + sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len)); + } else { + sa_tab[i] = NULL; + } + } + if( ((rt->rtm_addrs & (RTA_DST|RTA_GATEWAY)) == (RTA_DST|RTA_GATEWAY)) + && sa_tab[RTAX_DST]->sa_family == AF_INET + && sa_tab[RTAX_GATEWAY]->sa_family == AF_INET) { + if(((struct sockaddr_in *)sa_tab[RTAX_DST])->sin_addr.s_addr == 0) { + *addr = ((struct sockaddr_in *)(sa_tab[RTAX_GATEWAY]))->sin_addr.s_addr; + r = SUCCESS; + } + } + } + free(buf); + } + return r; +} +#endif /* #ifdef USE_SYSCTL_NET_ROUTE */ + + +#ifdef USE_SOCKET_ROUTE +/* Thanks to Darren Kenny for this code */ +#define NEXTADDR(w, u) \ + if (rtm_addrs & (w)) {\ + l = sizeof(struct sockaddr); memmove(cp, &(u), l); cp += l;\ + } + +#define rtm m_rtmsg.m_rtm + +struct { + struct rt_msghdr m_rtm; + char m_space[512]; +} m_rtmsg; + +int getdefaultgateway(in_addr_t *addr) +{ + int s, seq, l, rtm_addrs, i; + pid_t pid; + struct sockaddr so_dst, so_mask; + char *cp = m_rtmsg.m_space; + struct sockaddr *gate = NULL, *sa; + struct rt_msghdr *msg_hdr; + + pid = getpid(); + seq = 0; + rtm_addrs = RTA_DST | RTA_NETMASK; + + memset(&so_dst, 0, sizeof(so_dst)); + memset(&so_mask, 0, sizeof(so_mask)); + memset(&rtm, 0, sizeof(struct rt_msghdr)); + + rtm.rtm_type = RTM_GET; + rtm.rtm_flags = RTF_UP | RTF_GATEWAY; + rtm.rtm_version = RTM_VERSION; + rtm.rtm_seq = ++seq; + rtm.rtm_addrs = rtm_addrs; + + so_dst.sa_family = AF_INET; + so_mask.sa_family = AF_INET; + + NEXTADDR(RTA_DST, so_dst); + NEXTADDR(RTA_NETMASK, so_mask); + + rtm.rtm_msglen = l = cp - (char *)&m_rtmsg; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + + if (write(s, (char *)&m_rtmsg, l) < 0) { + close(s); + return FAILED; + } + + do { + l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid)); + + close(s); + + msg_hdr = &rtm; + + cp = ((char *)(msg_hdr + 1)); + if (msg_hdr->rtm_addrs) { + for (i = 1; i; i <<= 1) + if (i & msg_hdr->rtm_addrs) { + sa = (struct sockaddr *)cp; + if (i == RTA_GATEWAY ) + gate = sa; + + cp += sizeof(struct sockaddr); + } + } else { + return FAILED; + } + + + if (gate != NULL ) { + *addr = ((struct sockaddr_in *)gate)->sin_addr.s_addr; + return SUCCESS; + } else { + return FAILED; + } +} +#endif /* #ifdef USE_SOCKET_ROUTE */ + +#ifdef USE_WIN32_CODE +LIBSPEC int getdefaultgateway(in_addr_t * addr) +{ + HKEY networkCardsKey; + HKEY networkCardKey; + HKEY interfacesKey; + HKEY interfaceKey; + DWORD i = 0; + DWORD numSubKeys = 0; + TCHAR keyName[MAX_KEY_LENGTH]; + DWORD keyNameLength = MAX_KEY_LENGTH; + TCHAR keyValue[MAX_VALUE_LENGTH]; + DWORD keyValueLength = MAX_VALUE_LENGTH; + DWORD keyValueType = REG_SZ; + TCHAR gatewayValue[MAX_VALUE_LENGTH]; + DWORD gatewayValueLength = MAX_VALUE_LENGTH; + DWORD gatewayValueType = REG_MULTI_SZ; + int done = 0; + + //const char * networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + //const char * interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#ifdef UNICODE + LPCTSTR networkCardsPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME L"ServiceName" +#define STR_DHCPDEFAULTGATEWAY L"DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY L"DefaultGateway" +#else + LPCTSTR networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME "ServiceName" +#define STR_DHCPDEFAULTGATEWAY "DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY "DefaultGateway" +#endif + // The windows registry lists its primary network devices in the following location: + // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards + // + // Each network device has its own subfolder, named with an index, with various properties: + // -NetworkCards + // -5 + // -Description = Broadcom 802.11n Network Adapter + // -ServiceName = {E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -8 + // -Description = Marvell Yukon 88E8058 PCI-E Gigabit Ethernet Controller + // -ServiceName = {86226414-5545-4335-A9D1-5BD7120119AD} + // + // The above service name is the name of a subfolder within: + // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces + // + // There may be more subfolders in this interfaces path than listed in the network cards path above: + // -Interfaces + // -{3a539854-6a70-11db-887c-806e6f6e6963} + // -DhcpIPAddress = 0.0.0.0 + // -[more] + // -{E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -DhcpIPAddress = 10.0.1.4 + // -DhcpDefaultGateway = 10.0.1.1 + // -[more] + // -{86226414-5545-4335-A9D1-5BD7120119AD} + // -DhcpIpAddress = 10.0.1.5 + // -DhcpDefaultGateay = 10.0.1.1 + // -[more] + // + // In order to extract this information, we enumerate each network card, and extract the ServiceName value. + // This is then used to open the interface subfolder, and attempt to extract a DhcpDefaultGateway value. + // Once one is found, we're done. + // + // It may be possible to simply enumerate the interface folders until we find one with a DhcpDefaultGateway value. + // However, the technique used is the technique most cited on the web, and we assume it to be more correct. + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predifined key + networkCardsPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &networkCardsKey)) // Pointer to output key + { + // Unable to open network cards keys + return -1; + } + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predefined key + interfacesPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &interfacesKey)) // Pointer to output key + { + // Unable to open interfaces key + RegCloseKey(networkCardsKey); + return -1; + } + + // Figure out how many subfolders are within the NetworkCards folder + RegQueryInfoKey(networkCardsKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + //printf( "Number of subkeys: %u\n", (unsigned int)numSubKeys); + + // Enumrate through each subfolder within the NetworkCards folder + for(i = 0; i < numSubKeys && !done; i++) + { + keyNameLength = MAX_KEY_LENGTH; + if(ERROR_SUCCESS == RegEnumKeyEx(networkCardsKey, // Open registry key + i, // Index of subkey to retrieve + keyName, // Buffer that receives the name of the subkey + &keyNameLength, // Variable that receives the size of the above buffer + NULL, // Reserved - must be NULL + NULL, // Buffer that receives the class string + NULL, // Variable that receives the size of the above buffer + NULL)) // Variable that receives the last write time of subkey + { + if(RegOpenKeyEx(networkCardsKey, keyName, 0, KEY_READ, &networkCardKey) == ERROR_SUCCESS) + { + keyValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(networkCardKey, // Open registry key + STR_SERVICENAME, // Name of key to query + NULL, // Reserved - must be NULL + &keyValueType, // Receives value type + (LPBYTE)keyValue, // Receives value + &keyValueLength)) // Receives value length in bytes + { +// printf("keyValue: %s\n", keyValue); + if(RegOpenKeyEx(interfacesKey, keyValue, 0, KEY_READ, &interfaceKey) == ERROR_SUCCESS) + { + gatewayValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DHCPDEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue, // Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + else if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue,// Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + RegCloseKey(interfaceKey); + } + } + RegCloseKey(networkCardKey); + } + } + } + + RegCloseKey(interfacesKey); + RegCloseKey(networkCardsKey); + + if(done) + { +#if UNICODE + char tmp[32]; + for(i = 0; i < 32; i++) { + tmp[i] = (char)gatewayValue[i]; + if(!tmp[i]) + break; + } + tmp[31] = '\0'; + *addr = inet_addr(tmp); +#else + *addr = inet_addr(gatewayValue); +#endif + return 0; + } + + return -1; +} +#endif /* #ifdef USE_WIN32_CODE */ + +#ifdef USE_WIN32_CODE_2 +int getdefaultgateway(in_addr_t *addr) +{ + MIB_IPFORWARDROW ip_forward; + memset(&ip_forward, 0, sizeof(ip_forward)); + if(GetBestRoute(inet_addr("0.0.0.0"), 0, &ip_forward) != NO_ERROR) + return -1; + *addr = ip_forward.dwForwardNextHop; + return 0; +} +#endif /* #ifdef USE_WIN32_CODE_2 */ + +#ifdef USE_HAIKU_CODE +int getdefaultgateway(in_addr_t *addr) +{ + int fd, ret = -1; + struct ifconf config; + void *buffer = NULL; + struct ifreq *interface; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + return -1; + } + if (ioctl(fd, SIOCGRTSIZE, &config, sizeof(config)) != 0) { + goto fail; + } + if (config.ifc_value < 1) { + goto fail; /* No routes */ + } + if ((buffer = malloc(config.ifc_value)) == NULL) { + goto fail; + } + config.ifc_len = config.ifc_value; + config.ifc_buf = buffer; + if (ioctl(fd, SIOCGRTTABLE, &config, sizeof(config)) != 0) { + goto fail; + } + for (interface = buffer; + (uint8_t *)interface < (uint8_t *)buffer + config.ifc_len; ) { + struct route_entry route = interface->ifr_route; + int intfSize; + if (route.flags & (RTF_GATEWAY | RTF_DEFAULT)) { + *addr = ((struct sockaddr_in *)route.gateway)->sin_addr.s_addr; + ret = 0; + break; + } + intfSize = sizeof(route) + IF_NAMESIZE; + if (route.destination != NULL) { + intfSize += route.destination->sa_len; + } + if (route.mask != NULL) { + intfSize += route.mask->sa_len; + } + if (route.gateway != NULL) { + intfSize += route.gateway->sa_len; + } + interface = (struct ifreq *)((uint8_t *)interface + intfSize); + } +fail: + free(buffer); + close(fd); + return ret; +} +#endif /* #ifdef USE_HAIKU_CODE */ + +#if !defined(USE_PROC_NET_ROUTE) && !defined(USE_SOCKET_ROUTE) && !defined(USE_SYSCTL_NET_ROUTE) && !defined(USE_WIN32_CODE) && !defined(USE_WIN32_CODE_2) && !defined(USE_HAIKU_CODE) +int getdefaultgateway(in_addr_t * addr) +{ + return -1; +} +#endif diff --git a/ext-deps/libnatpmp-20150609/getgateway.h b/ext-deps/libnatpmp-20150609/getgateway.h new file mode 100644 index 000000000..5d3df7312 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/getgateway.h @@ -0,0 +1,49 @@ +/* $Id: getgateway.h,v 1.8 2014/04/22 09:15:40 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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 __GETGATEWAY_H__ +#define __GETGATEWAY_H__ + +#ifdef WIN32 +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +#include +#else +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif +#define in_addr_t uint32_t +#endif +/* #include "declspec.h" */ + +/* getdefaultgateway() : + * return value : + * 0 : success + * -1 : failure */ +/* LIBSPEC */int getdefaultgateway(in_addr_t * addr); + +#endif diff --git a/ext-deps/libnatpmp-20150609/libnatpmpmodule.c b/ext-deps/libnatpmp-20150609/libnatpmpmodule.c new file mode 100644 index 000000000..0fd9914b5 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/libnatpmpmodule.c @@ -0,0 +1,281 @@ +/* $Id: libnatpmpmodule.c,v 1.7 2012/03/05 19:38:37 nanard Exp $ */ +/* libnatpmp + * http://miniupnp.free.fr/libnatpmp.html +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#include +#ifdef WIN32 +#include +#else +#include +#include +#endif + +#define STATICLIB +#include "structmember.h" +#include "natpmp.h" + +/* for compatibility with Python < 2.4 */ +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +typedef struct { + PyObject_HEAD + + /* Type-specific fields go here. */ + unsigned int discoverdelay; + + natpmp_t natpmp; +} NATPMPObject; + +static PyMemberDef NATPMP_members[] = { + {"discoverdelay", T_UINT, offsetof(NATPMPObject, discoverdelay), + 0/*READWRITE*/, "value in ms used to wait for NATPMP responses" + }, + {NULL} +}; + +static PyObject * +NATPMPObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + NATPMPObject *self; + + self = (NATPMPObject *)type->tp_alloc(type, 0); + if (self) { + initnatpmp(&self->natpmp, 0, 0); + } + + return (PyObject *)self; +} + +static void +NATPMPObject_dealloc(NATPMPObject *self) +{ + closenatpmp(&self->natpmp); + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +NATPMP_externalipaddress(NATPMPObject *self) +{ + int r; + struct timeval timeout; + fd_set fds; + natpmpresp_t response; + + r = sendpublicaddressrequest(&self->natpmp); + + if (r < 0) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + + do { + FD_ZERO(&fds); + FD_SET(self->natpmp.s, &fds); + getnatpmprequesttimeout(&self->natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&self->natpmp, &response); + if (r < 0 && r != NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + } while (r == NATPMP_TRYAGAIN); + + return Py_BuildValue("s", inet_ntoa(response.pnu.publicaddress.addr)); +} + +static PyObject * +NATPMP_domapping(natpmp_t *n, unsigned short eport, unsigned short iport, + const char *protocol, unsigned int lifetime) +{ + int proto; + struct timeval timeout; + fd_set fds; + natpmpresp_t response; + int r; + + if (!strncasecmp("tcp", protocol, 3)) { + proto = NATPMP_PROTOCOL_TCP; + } else if (!strncasecmp("udp", protocol, 3)) { + proto = NATPMP_PROTOCOL_UDP; + } else { + PyErr_SetString(PyExc_Exception, "Unknown protocol"); + return NULL; + } + + r = sendnewportmappingrequest(n, proto, iport, eport, + lifetime); + + if (r < 0) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + + do { + FD_ZERO(&fds); + FD_SET(n->s, &fds); + getnatpmprequesttimeout(n, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(n, &response); + if (r < 0 && r != NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + } while (r == NATPMP_TRYAGAIN); + + return Py_BuildValue("H", response.pnu.newportmapping.mappedpublicport); +} + + +/* AddPortMapping(externalPort, protocol, internalPort, lifetime) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +NATPMP_addportmapping(NATPMPObject *self, PyObject *args) +{ + unsigned short eport; + unsigned short iport; + unsigned int lifetime; + const char *protocol; + + if (!PyArg_ParseTuple(args, "HsHI", &eport, &protocol, &iport, &lifetime)) + return NULL; + + return NATPMP_domapping(&self->natpmp, eport, iport, protocol, lifetime); +} + +/* DeletePortMapping(externalPort, protocol, internalPort) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +NATPMP_deleteportmapping(NATPMPObject *self, PyObject *args) +{ + unsigned short eport; + unsigned short iport; + const char *protocol; + + if (!PyArg_ParseTuple(args, "HsH", &eport, &protocol, &iport)) + return NULL; + + return NATPMP_domapping(&self->natpmp, eport, iport, protocol, 0); +} + +/* natpmp.NATPMP object Method Table */ +static PyMethodDef NATPMP_methods[] = { + {"externalipaddress", (PyCFunction)NATPMP_externalipaddress, METH_NOARGS, + "return external IP address" + }, + {"addportmapping", (PyCFunction)NATPMP_addportmapping, METH_VARARGS, + "add a port mapping" + }, + {"deleteportmapping", (PyCFunction)NATPMP_deleteportmapping, METH_VARARGS, + "delete a port mapping" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject NATPMPType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "libnatpmp.NATPMP", /*tp_name*/ + sizeof(NATPMPObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)NATPMPObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "NATPMP objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + NATPMP_methods, /* tp_methods */ + NATPMP_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + NATPMPObject_new, /* tp_new */ +}; + +/* module methods */ +static PyMethodDef libnatpmp_methods[] = { + {NULL} /* Sentinel */ +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +initlibnatpmp(void) +{ + PyObject* m; + + if (PyType_Ready(&NATPMPType) < 0) + return; + + m = Py_InitModule3("libnatpmp", libnatpmp_methods, + "libnatpmp module."); + + Py_INCREF(&NATPMPType); + PyModule_AddObject(m, "NATPMP", (PyObject *)&NATPMPType); +} + diff --git a/ext-deps/libnatpmp-20150609/msvc/libnatpmp.sln b/ext-deps/libnatpmp-20150609/msvc/libnatpmp.sln new file mode 100644 index 000000000..ac746d415 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/msvc/libnatpmp.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnatpmp", "libnatpmp.vcproj", "{D59B6527-F3DE-4D26-A08D-52F1EE989301}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "natpmpc-static", "natpmpc-static.vcproj", "{A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}" + ProjectSection(ProjectDependencies) = postProject + {D59B6527-F3DE-4D26-A08D-52F1EE989301} = {D59B6527-F3DE-4D26-A08D-52F1EE989301} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Debug|Win32.ActiveCfg = Debug|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Debug|Win32.Build.0 = Debug|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Release|Win32.ActiveCfg = Release|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Release|Win32.Build.0 = Release|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Debug|Win32.Build.0 = Debug|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Release|Win32.ActiveCfg = Release|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ext-deps/libnatpmp-20150609/msvc/libnatpmp.vcproj b/ext-deps/libnatpmp-20150609/msvc/libnatpmp.vcproj new file mode 100644 index 000000000..9bae5c185 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/msvc/libnatpmp.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext-deps/libnatpmp-20150609/msvc/natpmpc-static.vcproj b/ext-deps/libnatpmp-20150609/msvc/natpmpc-static.vcproj new file mode 100644 index 000000000..c2052d982 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/msvc/natpmpc-static.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext-deps/libnatpmp-20150609/natpmp-jni.c b/ext-deps/libnatpmp-20150609/natpmp-jni.c new file mode 100644 index 000000000..feec1cea6 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmp-jni.c @@ -0,0 +1,157 @@ +#ifdef __CYGWIN__ +#include +#define __int64 uint64_t +#endif + +#ifdef WIN32 +#include +#include +#include +#endif + +#include +#include "natpmp.h" + +#include "fr_free_miniupnp_libnatpmp_NatPmp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_init (JNIEnv *env, jobject obj, jint forcegw, jint forcedgw) { + natpmp_t *p = malloc (sizeof(natpmp_t)); + if (p == NULL) return; + + initnatpmp(p, forcegw, (in_addr_t) forcedgw); + + jobject wrapped = (*env)->NewDirectByteBuffer(env, p, sizeof(natpmp_t)); + if (wrapped == NULL) return; + + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + if (fid == NULL) return; + (*env)->SetObjectField(env, obj, fid, wrapped); +} + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_free (JNIEnv *env, jobject obj) { + + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + + if (fid == NULL) return; + jobject wrapped = (*env)->GetObjectField(env, obj, fid); + if (wrapped == NULL) return; + + natpmp_t* natpmp = (natpmp_t*) (*env)->GetDirectBufferAddress(env, wrapped); + + closenatpmp(natpmp); + + if (natpmp == NULL) return; + free(natpmp); + + (*env)->SetObjectField(env, obj, fid, NULL); +} + +static natpmp_t* getNatPmp(JNIEnv* env, jobject obj) { + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return NULL; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + + if (fid == NULL) return NULL; + jobject wrapped = (*env)->GetObjectField(env, obj, fid); + if (wrapped == NULL) return NULL; + + natpmp_t* natpmp = (natpmp_t*) (*env)->GetDirectBufferAddress(env, wrapped); + + return natpmp; +} + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_sendPublicAddressRequest(JNIEnv* env, jobject obj) { + natpmp_t* natpmp = getNatPmp(env, obj); + if (natpmp == NULL) return -1; + + return sendpublicaddressrequest(natpmp); +} + + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_startup(JNIEnv* env, jclass cls) { + (void)env; + (void)cls; +#ifdef WIN32 + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); +#endif +} + + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_sendNewPortMappingRequest(JNIEnv* env, jobject obj, jint protocol, jint privateport, jint publicport, jint lifetime) { + natpmp_t* natpmp = getNatPmp(env, obj); + if (natpmp == NULL) return -1; + + return sendnewportmappingrequest(natpmp, protocol, privateport, publicport, lifetime); +} + +JNIEXPORT jlong JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_getNatPmpRequestTimeout(JNIEnv* env, jobject obj) { + natpmp_t* natpmp = getNatPmp(env, obj); + + struct timeval timeout; + + getnatpmprequesttimeout(natpmp, &timeout); + + return ((jlong) timeout.tv_sec) * 1000 + (timeout.tv_usec / 1000); + +} + +#define SET_FIELD(prefix, name, type, longtype) { \ + jfieldID fid = (*env)->GetFieldID(env, thisClass, #name, type); \ + if (fid == NULL) return -1; \ + (*env)->Set ## longtype ## Field(env, response, fid, resp. prefix name); \ +} + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_readNatPmpResponseOrRetry(JNIEnv* env, jobject obj, jobject response) { + + natpmp_t* natpmp = getNatPmp(env, obj); + natpmpresp_t resp; + int result = readnatpmpresponseorretry(natpmp, &resp); + + if (result != 0) { + return result; + } + + jclass thisClass = (*env)->GetObjectClass(env, response); + if (thisClass == NULL) return -1; + + SET_FIELD(,type, "S", Short); + SET_FIELD(,resultcode, "S", Short); + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "epoch", "J"); + if (fid == NULL) return -1; + (*env)->SetLongField(env, response, fid, ((jlong)resp.epoch) * 1000); + + if (resp.type == 0) { + jfieldID fid = (*env)->GetFieldID(env, thisClass, "addr", "I"); + if (fid == NULL) return -1; + (*env)->SetIntField(env, response, fid, resp.pnu.publicaddress.addr.s_addr); + + + } else { + SET_FIELD(pnu.newportmapping., privateport, "I", Int); + SET_FIELD(pnu.newportmapping., mappedpublicport, "I", Int); + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "lifetime", "J"); + if (fid == NULL) return -1; + (*env)->SetLongField(env, response, fid, ((jlong) resp.pnu.newportmapping.lifetime) * 1000 * 1000); + } + return result; +} + + +#ifdef __cplusplus +} +#endif diff --git a/ext-deps/libnatpmp-20150609/natpmp.c b/ext-deps/libnatpmp-20150609/natpmp.c new file mode 100644 index 000000000..3a4981292 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmp.c @@ -0,0 +1,379 @@ +/* $Id: natpmp.c,v 1.20 2015/05/27 12:43:15 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2015, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#ifdef __linux__ +#define _BSD_SOURCE 1 +#endif +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#ifdef WIN32 +#include +#include +#include +#include +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNREFUSED WSAECONNREFUSED +#include "wingettimeofday.h" +#define gettimeofday natpmp_gettimeofday +#else +#include +#include +#include +#include +#include +#define closesocket close +#endif +#include "natpmp.h" +#include "getgateway.h" +#include + +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw) +{ +#ifdef WIN32 + u_long ioctlArg = 1; +#else + int flags; +#endif + struct sockaddr_in addr; + if(!p) + return NATPMP_ERR_INVALIDARGS; + memset(p, 0, sizeof(natpmp_t)); + p->s = socket(PF_INET, SOCK_DGRAM, 0); + if(p->s < 0) + return NATPMP_ERR_SOCKETERROR; +#ifdef WIN32 + if(ioctlsocket(p->s, FIONBIO, &ioctlArg) == SOCKET_ERROR) + return NATPMP_ERR_FCNTLERROR; +#else + if((flags = fcntl(p->s, F_GETFL, 0)) < 0) + return NATPMP_ERR_FCNTLERROR; + if(fcntl(p->s, F_SETFL, flags | O_NONBLOCK) < 0) + return NATPMP_ERR_FCNTLERROR; +#endif + + if(forcegw) { + p->gateway = forcedgw; + } else { + if(getdefaultgateway(&(p->gateway)) < 0) + return NATPMP_ERR_CANNOTGETGATEWAY; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + if(connect(p->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return NATPMP_ERR_CONNECTERR; + return 0; +} + +LIBSPEC int closenatpmp(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + if(closesocket(p->s) < 0) + return NATPMP_ERR_CLOSEERR; + return 0; +} + +int sendpendingrequest(natpmp_t * p) +{ + int r; +/* struct sockaddr_in addr;*/ + if(!p) + return NATPMP_ERR_INVALIDARGS; +/* memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + r = (int)sendto(p->s, p->pending_request, p->pending_request_len, 0, + (struct sockaddr *)&addr, sizeof(addr));*/ + r = (int)send(p->s, (const char *)p->pending_request, p->pending_request_len, 0); + return (r<0) ? NATPMP_ERR_SENDERR : r; +} + +int sendnatpmprequest(natpmp_t * p) +{ + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + /* TODO : check if no request is already pending */ + p->has_pending_request = 1; + p->try_number = 1; + n = sendpendingrequest(p); + gettimeofday(&p->retry_time, NULL); // check errors ! + p->retry_time.tv_usec += 250000; /* add 250ms */ + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + return n; +} + +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout) +{ + struct timeval now; + if(!p || !timeout) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + if(gettimeofday(&now, NULL) < 0) + return NATPMP_ERR_GETTIMEOFDAYERR; + timeout->tv_sec = p->retry_time.tv_sec - now.tv_sec; + timeout->tv_usec = p->retry_time.tv_usec - now.tv_usec; + if(timeout->tv_usec < 0) { + timeout->tv_usec += 1000000; + timeout->tv_sec--; + } + return 0; +} + +LIBSPEC int sendpublicaddressrequest(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + //static const unsigned char request[] = { 0, 0 }; + p->pending_request[0] = 0; + p->pending_request[1] = 0; + p->pending_request_len = 2; + // TODO: return 0 instead of sizeof(request) ?? + return sendnatpmprequest(p); +} + +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime) +{ + if(!p || (protocol!=NATPMP_PROTOCOL_TCP && protocol!=NATPMP_PROTOCOL_UDP)) + return NATPMP_ERR_INVALIDARGS; + p->pending_request[0] = 0; + p->pending_request[1] = protocol; + p->pending_request[2] = 0; + p->pending_request[3] = 0; + /* break strict-aliasing rules : + *((uint16_t *)(p->pending_request + 4)) = htons(privateport); */ + p->pending_request[4] = (privateport >> 8) & 0xff; + p->pending_request[5] = privateport & 0xff; + /* break stric-aliasing rules : + *((uint16_t *)(p->pending_request + 6)) = htons(publicport); */ + p->pending_request[6] = (publicport >> 8) & 0xff; + p->pending_request[7] = publicport & 0xff; + /* break stric-aliasing rules : + *((uint32_t *)(p->pending_request + 8)) = htonl(lifetime); */ + p->pending_request[8] = (lifetime >> 24) & 0xff; + p->pending_request[9] = (lifetime >> 16) & 0xff; + p->pending_request[10] = (lifetime >> 8) & 0xff; + p->pending_request[11] = lifetime & 0xff; + p->pending_request_len = 12; + return sendnatpmprequest(p); +} + +LIBSPEC int readnatpmpresponse(natpmp_t * p, natpmpresp_t * response) +{ + unsigned char buf[16]; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + n = recvfrom(p->s, (char *)buf, sizeof(buf), 0, + (struct sockaddr *)&addr, &addrlen); + if(n<0) +#ifdef WIN32 + switch(WSAGetLastError()) { +#else + switch(errno) { +#endif + /*case EAGAIN:*/ + case EWOULDBLOCK: + n = NATPMP_TRYAGAIN; + break; + case ECONNREFUSED: + n = NATPMP_ERR_NOGATEWAYSUPPORT; + break; + default: + n = NATPMP_ERR_RECVFROM; + } + /* check that addr is correct (= gateway) */ + else if(addr.sin_addr.s_addr != p->gateway) + n = NATPMP_ERR_WRONGPACKETSOURCE; + else { + response->resultcode = ntohs(*((uint16_t *)(buf + 2))); + response->epoch = ntohl(*((uint32_t *)(buf + 4))); + if(buf[0] != 0) + n = NATPMP_ERR_UNSUPPORTEDVERSION; + else if(buf[1] < 128 || buf[1] > 130) + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + else if(response->resultcode != 0) { + switch(response->resultcode) { + case 1: + n = NATPMP_ERR_UNSUPPORTEDVERSION; + break; + case 2: + n = NATPMP_ERR_NOTAUTHORIZED; + break; + case 3: + n = NATPMP_ERR_NETWORKFAILURE; + break; + case 4: + n = NATPMP_ERR_OUTOFRESOURCES; + break; + case 5: + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + break; + default: + n = NATPMP_ERR_UNDEFINEDERROR; + } + } else { + response->type = buf[1] & 0x7f; + if(buf[1] == 128) + //response->publicaddress.addr = *((uint32_t *)(buf + 8)); + response->pnu.publicaddress.addr.s_addr = *((uint32_t *)(buf + 8)); + else { + response->pnu.newportmapping.privateport = ntohs(*((uint16_t *)(buf + 8))); + response->pnu.newportmapping.mappedpublicport = ntohs(*((uint16_t *)(buf + 10))); + response->pnu.newportmapping.lifetime = ntohl(*((uint32_t *)(buf + 12))); + } + n = 0; + } + } + return n; +} + +int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response) +{ + int n; + if(!p || !response) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + n = readnatpmpresponse(p, response); + if(n<0) { + if(n==NATPMP_TRYAGAIN) { + struct timeval now; + gettimeofday(&now, NULL); // check errors ! + if(timercmp(&now, &p->retry_time, >=)) { + int delay, r; + if(p->try_number >= 9) { + return NATPMP_ERR_NOGATEWAYSUPPORT; + } + /*printf("retry! %d\n", p->try_number);*/ + delay = 250 * (1<try_number); // ms + /*for(i=0; itry_number; i++) + delay += delay;*/ + p->retry_time.tv_sec += (delay / 1000); + p->retry_time.tv_usec += (delay % 1000) * 1000; + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + p->try_number++; + r = sendpendingrequest(p); + if(r<0) + return r; + } + } + } else { + p->has_pending_request = 0; + } + return n; +} + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int r) +{ + const char * s; + switch(r) { + case NATPMP_ERR_INVALIDARGS: + s = "invalid arguments"; + break; + case NATPMP_ERR_SOCKETERROR: + s = "socket() failed"; + break; + case NATPMP_ERR_CANNOTGETGATEWAY: + s = "cannot get default gateway ip address"; + break; + case NATPMP_ERR_CLOSEERR: +#ifdef WIN32 + s = "closesocket() failed"; +#else + s = "close() failed"; +#endif + break; + case NATPMP_ERR_RECVFROM: + s = "recvfrom() failed"; + break; + case NATPMP_ERR_NOPENDINGREQ: + s = "no pending request"; + break; + case NATPMP_ERR_NOGATEWAYSUPPORT: + s = "the gateway does not support nat-pmp"; + break; + case NATPMP_ERR_CONNECTERR: + s = "connect() failed"; + break; + case NATPMP_ERR_WRONGPACKETSOURCE: + s = "packet not received from the default gateway"; + break; + case NATPMP_ERR_SENDERR: + s = "send() failed"; + break; + case NATPMP_ERR_FCNTLERROR: + s = "fcntl() failed"; + break; + case NATPMP_ERR_GETTIMEOFDAYERR: + s = "gettimeofday() failed"; + break; + case NATPMP_ERR_UNSUPPORTEDVERSION: + s = "unsupported nat-pmp version error from server"; + break; + case NATPMP_ERR_UNSUPPORTEDOPCODE: + s = "unsupported nat-pmp opcode error from server"; + break; + case NATPMP_ERR_UNDEFINEDERROR: + s = "undefined nat-pmp server error"; + break; + case NATPMP_ERR_NOTAUTHORIZED: + s = "not authorized"; + break; + case NATPMP_ERR_NETWORKFAILURE: + s = "network failure"; + break; + case NATPMP_ERR_OUTOFRESOURCES: + s = "nat-pmp server out of resources"; + break; + default: + s = "Unknown libnatpmp error"; + } + return s; +} +#endif + diff --git a/ext-deps/libnatpmp-20150609/natpmp.def b/ext-deps/libnatpmp-20150609/natpmp.def new file mode 100644 index 000000000..cd110033f --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmp.def @@ -0,0 +1,11 @@ +LIBRARY +; libnatpmp library + +EXPORTS + initnatpmp + closenatpmp + sendpublicaddressrequest + sendnewportmappingrequest + getnatpmprequesttimeout + readnatpmpresponseorretry + strnatpmperr diff --git a/ext-deps/libnatpmp-20150609/natpmp.h b/ext-deps/libnatpmp-20150609/natpmp.h new file mode 100644 index 000000000..7889d206f --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmp.h @@ -0,0 +1,219 @@ +/* $Id: natpmp.h,v 1.20 2014/04/22 09:15:40 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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 __NATPMP_H__ +#define __NATPMP_H__ + +/* NAT-PMP Port as defined by the NAT-PMP draft */ +#define NATPMP_PORT (5351) + +#include +#if !defined(_MSC_VER) +#include +#endif /* !defined(_MSC_VER) */ + +#ifdef WIN32 +#include +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +#include +#else /* !defined(_MSC_VER) || _MSC_VER >= 1600 */ +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif /* !defined(_MSC_VER) || _MSC_VER >= 1600 */ +#define in_addr_t uint32_t +#include "declspec.h" +#else /* WIN32 */ +#define LIBSPEC +#include +#endif /* WIN32 */ + +/* causes problem when installing. Maybe should it be inlined ? */ +/* #include "declspec.h" */ + +typedef struct { + int s; /* socket */ + in_addr_t gateway; /* default gateway (IPv4) */ + int has_pending_request; + unsigned char pending_request[12]; + int pending_request_len; + int try_number; + struct timeval retry_time; +} natpmp_t; + +typedef struct { + uint16_t type; /* NATPMP_RESPTYPE_* */ + uint16_t resultcode; /* NAT-PMP response code */ + uint32_t epoch; /* Seconds since start of epoch */ + union { + struct { + //in_addr_t addr; + struct in_addr addr; + } publicaddress; + struct { + uint16_t privateport; + uint16_t mappedpublicport; + uint32_t lifetime; + } newportmapping; + } pnu; +} natpmpresp_t; + +/* possible values for type field of natpmpresp_t */ +#define NATPMP_RESPTYPE_PUBLICADDRESS (0) +#define NATPMP_RESPTYPE_UDPPORTMAPPING (1) +#define NATPMP_RESPTYPE_TCPPORTMAPPING (2) + +/* Values to pass to sendnewportmappingrequest() */ +#define NATPMP_PROTOCOL_UDP (1) +#define NATPMP_PROTOCOL_TCP (2) + +/* return values */ +/* NATPMP_ERR_INVALIDARGS : invalid arguments passed to the function */ +#define NATPMP_ERR_INVALIDARGS (-1) +/* NATPMP_ERR_SOCKETERROR : socket() failed. check errno for details */ +#define NATPMP_ERR_SOCKETERROR (-2) +/* NATPMP_ERR_CANNOTGETGATEWAY : can't get default gateway IP */ +#define NATPMP_ERR_CANNOTGETGATEWAY (-3) +/* NATPMP_ERR_CLOSEERR : close() failed. check errno for details */ +#define NATPMP_ERR_CLOSEERR (-4) +/* NATPMP_ERR_RECVFROM : recvfrom() failed. check errno for details */ +#define NATPMP_ERR_RECVFROM (-5) +/* NATPMP_ERR_NOPENDINGREQ : readnatpmpresponseorretry() called while + * no NAT-PMP request was pending */ +#define NATPMP_ERR_NOPENDINGREQ (-6) +/* NATPMP_ERR_NOGATEWAYSUPPORT : the gateway does not support NAT-PMP */ +#define NATPMP_ERR_NOGATEWAYSUPPORT (-7) +/* NATPMP_ERR_CONNECTERR : connect() failed. check errno for details */ +#define NATPMP_ERR_CONNECTERR (-8) +/* NATPMP_ERR_WRONGPACKETSOURCE : packet not received from the network gateway */ +#define NATPMP_ERR_WRONGPACKETSOURCE (-9) +/* NATPMP_ERR_SENDERR : send() failed. check errno for details */ +#define NATPMP_ERR_SENDERR (-10) +/* NATPMP_ERR_FCNTLERROR : fcntl() failed. check errno for details */ +#define NATPMP_ERR_FCNTLERROR (-11) +/* NATPMP_ERR_GETTIMEOFDAYERR : gettimeofday() failed. check errno for details */ +#define NATPMP_ERR_GETTIMEOFDAYERR (-12) + +/* */ +#define NATPMP_ERR_UNSUPPORTEDVERSION (-14) +#define NATPMP_ERR_UNSUPPORTEDOPCODE (-15) + +/* Errors from the server : */ +#define NATPMP_ERR_UNDEFINEDERROR (-49) +#define NATPMP_ERR_NOTAUTHORIZED (-51) +#define NATPMP_ERR_NETWORKFAILURE (-52) +#define NATPMP_ERR_OUTOFRESOURCES (-53) + +/* NATPMP_TRYAGAIN : no data available for the moment. try again later */ +#define NATPMP_TRYAGAIN (-100) + +#ifdef __cplusplus +extern "C" { +#endif + +/* initnatpmp() + * initialize a natpmp_t object + * With forcegw=1 the gateway is not detected automaticaly. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SOCKETERROR + * NATPMP_ERR_FCNTLERROR + * NATPMP_ERR_CANNOTGETGATEWAY + * NATPMP_ERR_CONNECTERR */ +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw); + +/* closenatpmp() + * close resources associated with a natpmp_t object + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_CLOSEERR */ +LIBSPEC int closenatpmp(natpmp_t * p); + +/* sendpublicaddressrequest() + * send a public address NAT-PMP request to the network gateway + * Return values : + * 2 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendpublicaddressrequest(natpmp_t * p); + +/* sendnewportmappingrequest() + * send a new port mapping NAT-PMP request to the network gateway + * Arguments : + * protocol is either NATPMP_PROTOCOL_TCP or NATPMP_PROTOCOL_UDP, + * lifetime is in seconds. + * To remove a port mapping, set lifetime to zero. + * To remove all port mappings to the host, set lifetime and both ports + * to zero. + * Return values : + * 12 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime); + +/* getnatpmprequesttimeout() + * fills the timeval structure with the timeout duration of the + * currently pending NAT-PMP request. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_GETTIMEOFDAYERR + * NATPMP_ERR_NOPENDINGREQ */ +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout); + +/* readnatpmpresponseorretry() + * fills the natpmpresp_t structure if possible + * Return values : + * 0 = OK + * NATPMP_TRYAGAIN + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_NOPENDINGREQ + * NATPMP_ERR_NOGATEWAYSUPPORT + * NATPMP_ERR_RECVFROM + * NATPMP_ERR_WRONGPACKETSOURCE + * NATPMP_ERR_UNSUPPORTEDVERSION + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_NOTAUTHORIZED + * NATPMP_ERR_NETWORKFAILURE + * NATPMP_ERR_OUTOFRESOURCES + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_UNDEFINEDERROR */ +LIBSPEC int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response); + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int t); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext-deps/libnatpmp-20150609/natpmpc.1 b/ext-deps/libnatpmp-20150609/natpmpc.1 new file mode 100644 index 000000000..5f0003da6 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmpc.1 @@ -0,0 +1,19 @@ +.TH natpmpc 1 + +.SH NAME +natpmpc \- NAT\-PMP library test client and mapping setter. + +.SH "SYNOPSIS" +Display the public IP address: +.br +\fBnatpmpc\fP + +Add a port mapping: +.br +\fBnatpmpc\fP \-a [lifetime] + +.SH DESCRIPTION + +In order to remove a mapping, set it with a lifetime of 0 seconds. +To remove all mappings for your machine, use 0 as private port and +lifetime. diff --git a/ext-deps/libnatpmp-20150609/natpmpc.c b/ext-deps/libnatpmp-20150609/natpmpc.c new file mode 100644 index 000000000..611bd2d1a --- /dev/null +++ b/ext-deps/libnatpmp-20150609/natpmpc.c @@ -0,0 +1,244 @@ +/* $Id: natpmpc.c,v 1.13 2012/08/21 17:23:38 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#include +#include +#include +#if defined(_MSC_VER) +#if _MSC_VER >= 1400 +#define strcasecmp _stricmp +#else +#define strcasecmp stricmp +#endif +#else +#include +#endif +#ifdef WIN32 +#include +#else +#include +#include +#endif +#include "natpmp.h" + +void usage(FILE * out, const char * argv0) +{ + fprintf(out, "Usage :\n"); + fprintf(out, " %s [options]\n", argv0); + fprintf(out, "\tdisplay the public IP address.\n"); + fprintf(out, " %s -h\n", argv0); + fprintf(out, "\tdisplay this help screen.\n"); + fprintf(out, " %s [options] -a [lifetime]\n", argv0); + fprintf(out, "\tadd a port mapping.\n"); + fprintf(out, "\nOption available :\n"); + fprintf(out, " -g ipv4address\n"); + fprintf(out, "\tforce the gateway to be used as destination for NAT-PMP commands.\n"); + fprintf(out, "\n In order to remove a mapping, set it with a lifetime of 0 seconds.\n"); + fprintf(out, " To remove all mappings for your machine, use 0 as private port and lifetime.\n"); +} + +/* sample code for using libnatpmp */ +int main(int argc, char * * argv) +{ + natpmp_t natpmp; + natpmpresp_t response; + int r; + int sav_errno; + struct timeval timeout; + fd_set fds; + int i; + int protocol = 0; + uint16_t privateport = 0; + uint16_t publicport = 0; + uint32_t lifetime = 3600; + int command = 0; + int forcegw = 0; + in_addr_t gateway = 0; + struct in_addr gateway_in_use; + +#ifdef WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + /* argument parsing */ + for(i=1; i i + 1) { + if(1 != sscanf(argv[i+1], "%u", &lifetime)) { + fprintf(stderr, "%s is not a correct 32bits unsigned integer\n", argv[i]); + } else { + i++; + } + } + break; + default: + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } else { + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } + + /* initnatpmp() */ + r = initnatpmp(&natpmp, forcegw, gateway); + printf("initnatpmp() returned %d (%s)\n", r, r?"FAILED":"SUCCESS"); + if(r<0) + return 1; + + gateway_in_use.s_addr = natpmp.gateway; + printf("using gateway : %s\n", inet_ntoa(gateway_in_use)); + + /* sendpublicaddressrequest() */ + r = sendpublicaddressrequest(&natpmp); + printf("sendpublicaddressrequest returned %d (%s)\n", + r, r==2?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + r = select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + if(r<0) { + fprintf(stderr, "select()"); + return 1; + } + r = readnatpmpresponseorretry(&natpmp, &response); + sav_errno = errno; + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + if(r<0 && r!=NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + fprintf(stderr, " errno=%d '%s'\n", + sav_errno, strerror(sav_errno)); + } + } while(r==NATPMP_TRYAGAIN); + if(r<0) + return 1; + + /* TODO : check that response.type == 0 */ + printf("Public IP address : %s\n", inet_ntoa(response.pnu.publicaddress.addr)); + printf("epoch = %u\n", response.epoch); + + if(command == 'a') { + /* sendnewportmappingrequest() */ + r = sendnewportmappingrequest(&natpmp, protocol, + privateport, publicport, + lifetime); + printf("sendnewportmappingrequest returned %d (%s)\n", + r, r==12?"SUCCESS":"FAILED"); + if(r < 0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + } while(r==NATPMP_TRYAGAIN); + if(r<0) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + return 1; + } + + printf("Mapped public port %hu protocol %s to local port %hu " + "liftime %u\n", + response.pnu.newportmapping.mappedpublicport, + response.type == NATPMP_RESPTYPE_UDPPORTMAPPING ? "UDP" : + (response.type == NATPMP_RESPTYPE_TCPPORTMAPPING ? "TCP" : + "UNKNOWN"), + response.pnu.newportmapping.privateport, + response.pnu.newportmapping.lifetime); + printf("epoch = %u\n", response.epoch); + } + + r = closenatpmp(&natpmp); + printf("closenatpmp() returned %d (%s)\n", r, r==0?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + return 0; +} + diff --git a/ext-deps/libnatpmp-20150609/setup.py b/ext-deps/libnatpmp-20150609/setup.py new file mode 100644 index 000000000..aa774ee73 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/setup.py @@ -0,0 +1,18 @@ +#! /usr/bin/python +# $Id: setup.py,v 1.3 2012/03/05 04:54:01 nanard Exp $ +# +# python script to build the libnatpmp module under unix +# +# replace libnatpmp.a by libnatpmp.so for shared library usage +from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="libnatpmp", version="1.0", + ext_modules=[ + Extension(name="libnatpmp", sources=["libnatpmpmodule.c"], + extra_objects=["libnatpmp.a"], + define_macros=[('ENABLE_STRNATPMPERR', None)] + )] + ) + diff --git a/ext-deps/libnatpmp-20150609/setupmingw32.py b/ext-deps/libnatpmp-20150609/setupmingw32.py new file mode 100644 index 000000000..d02fdfcaa --- /dev/null +++ b/ext-deps/libnatpmp-20150609/setupmingw32.py @@ -0,0 +1,17 @@ +#! /usr/bin/python +# $Id: setupmingw32.py,v 1.3 2012/03/05 04:54:01 nanard Exp $ +# python script to build the miniupnpc module under windows +# +from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="libnatpmp", version="1.0", + ext_modules=[ + Extension(name="libnatpmp", sources=["libnatpmpmodule.c"], + libraries=["ws2_32"], + extra_objects=["libnatpmp.a"], + define_macros=[('ENABLE_STRNATPMPERR', None)] + )] + ) + diff --git a/ext-deps/libnatpmp-20150609/testgetgateway.c b/ext-deps/libnatpmp-20150609/testgetgateway.c new file mode 100644 index 000000000..24cbe7d05 --- /dev/null +++ b/ext-deps/libnatpmp-20150609/testgetgateway.c @@ -0,0 +1,57 @@ +/* $Id: testgetgateway.c,v 1.7 2012/08/21 17:13:31 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#include +#ifdef WIN32 +#include +#else +#include +#include +#endif +#include "getgateway.h" + +int main(int argc, char * * argv) +{ + (void)argc; + (void)argv; + struct in_addr gatewayaddr; + int r; +#ifdef WIN32 + uint32_t temp = 0; + r = getdefaultgateway(&temp); + gatewayaddr.S_un.S_addr = temp; +#else + r = getdefaultgateway(&(gatewayaddr.s_addr)); +#endif + if(r>=0) + printf("default gateway : %s\n", inet_ntoa(gatewayaddr)); + else + fprintf(stderr, "getdefaultgateway() failed\n"); + return 0; +} + diff --git a/ext-deps/libnatpmp-20150609/wingettimeofday.c b/ext-deps/libnatpmp-20150609/wingettimeofday.c new file mode 100644 index 000000000..cb730e17d --- /dev/null +++ b/ext-deps/libnatpmp-20150609/wingettimeofday.c @@ -0,0 +1,60 @@ +/* $Id: wingettimeofday.c,v 1.6 2013/09/10 20:13:26 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2013, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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. +*/ +#ifdef WIN32 +#if defined(_MSC_VER) +struct timeval { + long tv_sec; + long tv_usec; +}; +#else +#include +#endif + +typedef struct _FILETIME { + unsigned long dwLowDateTime; + unsigned long dwHighDateTime; +} FILETIME; + +void __stdcall GetSystemTimeAsFileTime(FILETIME*); + +int natpmp_gettimeofday(struct timeval* p, void* tz /* IGNORED */) { + union { + long long ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if(!p) + return -1; + GetSystemTimeAsFileTime( &(_now.ft) ); + p->tv_usec =(long)((_now.ns100 / 10LL) % 1000000LL ); + p->tv_sec = (long)((_now.ns100-(116444736000000000LL))/10000000LL); + return 0; +} +#endif + diff --git a/ext-deps/libnatpmp-20150609/wingettimeofday.h b/ext-deps/libnatpmp-20150609/wingettimeofday.h new file mode 100644 index 000000000..1d18d9fac --- /dev/null +++ b/ext-deps/libnatpmp-20150609/wingettimeofday.h @@ -0,0 +1,39 @@ +/* $Id: wingettimeofday.h,v 1.5 2013/09/11 07:22:25 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2013, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS 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 COPYRIGHT OWNER 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 __WINGETTIMEOFDAY_H__ +#define __WINGETTIMEOFDAY_H__ +#ifdef WIN32 +#if defined(_MSC_VER) +#include +#else +#include +#endif +int natpmp_gettimeofday(struct timeval* p, void* tz /* IGNORED */); +#endif +#endif diff --git a/src/main.cpp b/src/main.cpp index f5b9ca99b..c1a47659a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,6 +89,7 @@ #include "ug_runtime_error.hpp" #include "utils/color_out.h" #include "utils/misc.h" +#include "utils/nat.h" #include "utils/net.h" #include "utils/thread.h" #include "utils/wait_obj.h" @@ -377,6 +378,7 @@ static void usage(const char *exec_path, bool full = false) print_help_item("-M ", {"received video mode (eg tiled-4K, 3D,", "dual-link)"}); print_help_item("-p | help", {"postprocess module"}); + print_help_item("-N|--nat-traverse"s, {"try to deploy NAT traversal techniques"s}); } print_help_item("-f [A:|V:]", {"FEC settings (audio or video) - use", "\"none\", \"mult:\",", "\"ldgm:%%\" or", "\"ldgm:::\"", @@ -732,9 +734,10 @@ int main(int argc, char *argv[]) {"param", required_argument, 0, OPT_PARAM}, {"pix-fmts", no_argument, 0, OPT_PIX_FMTS}, {"video-codecs", no_argument, 0, OPT_VIDEO_CODECS}, + {"nat-traverse", no_argument, nullptr, 'N'}, {0, 0, 0, 0} }; - const char optstring[] = "d:t:m:r:s:v46c:hM:p:f:P:l:A:V"; + const char *optstring = "d:t:m:r:s:v46c:hM:Np:f:P:l:A:V"; const char *audio_protocol = "ultragrid_rtp"; const char *audio_protocol_opts = ""; @@ -743,6 +746,8 @@ int main(int argc, char *argv[]) const char *video_protocol_opts = ""; const char *log_opt = nullptr; + bool setup_nat_traverse = false; + struct ug_nat_traverse *nat_traverse = nullptr; // First we need to set verbosity level prior to everything else. // common_preinit() uses the verbosity level. @@ -1092,6 +1097,9 @@ int main(int argc, char *argv[]) case OPT_VIDEO_CODECS: print_video_codecs(); EXIT(EXIT_SUCCESS); + case 'N': + setup_nat_traverse = true; + break; case '?': default: usage(uv_argv[0]); @@ -1241,6 +1249,10 @@ int main(int argc, char *argv[]) EXIT(EXIT_FAIL_CONTROL_SOCK); } + if (setup_nat_traverse) { + nat_traverse = start_nat_traverse(video_rx_port, audio_rx_port); + } + uv.audio = audio_cfg_init (&uv.root_module, audio_host, audio_rx_port, audio_tx_port, audio_send, audio_recv, audio_protocol, audio_protocol_opts, @@ -1503,6 +1515,8 @@ cleanup: if (uv.display_device) display_done(uv.display_device); + stop_nat_traverse(nat_traverse); + kc.stop(); control_done(control); diff --git a/src/utils/nat.c b/src/utils/nat.c new file mode 100644 index 000000000..988a7c1e7 --- /dev/null +++ b/src/utils/nat.c @@ -0,0 +1,204 @@ +/** + * @file utils/nat.c + * @author Martin Pulec + */ +/* + * Copyright (c) 2020 CESNET z.s.p.o. + * All rights reserved. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "config_unix.h" +#include "config_win32.h" + +#include "debug.h" +#include "utils/nat.h" + +#define ENABLE_STRNATPMPERR 1 +#define STATICLIB 1 +#include "ext-deps/libnatpmp-20150609/natpmp.h" + +#define ALLOCATION_TIMEOUT (4 * 3600) + +struct ug_nat_traverse { + enum { + UG_NAT_TRAVERSE_NONE, + UG_NAT_TRAVERSE_NAT_PMP, + } traverse; + int audio_rx_port; + int video_rx_port; +}; + +static bool nat_pmp_add_mapping(natpmp_t *natpmp, int privateport, int publicport, int lifetime) +{ + if (privateport == 0 && publicport == 0) { + return true; + } + + int r = 0; + /* sendnewportmappingrequest() */ + r = sendnewportmappingrequest(natpmp, NATPMP_PROTOCOL_UDP, + privateport, publicport, + lifetime); + log_msg(r < 0 ? LOG_LEVEL_ERROR : LOG_LEVEL_VERBOSE, + "[NAT PMP] sendnewportmappingrequest returned %d (%s)\n", + r, r == 12 ? "SUCCESS" : "FAILED"); + if (r < 0) { + return false; + } + + natpmpresp_t response; + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp->s, &fds); + getnatpmprequesttimeout(natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(natpmp, &response); + log_msg(LOG_LEVEL_VERBOSE, "[NAT PMP] readnatpmpresponseorretry returned %d (%s)\n", + r, r == 0 ? "OK" : (r == NATPMP_TRYAGAIN ? "TRY AGAIN" : "FAILED")); + } while(r==NATPMP_TRYAGAIN); + if(r<0) { + log_msg(LOG_LEVEL_ERROR, "[NAT PMP] readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); + return false; + } + + log_msg(LOG_LEVEL_INFO, "[NAT PMP] Mapped public port %hu protocol %s to local port %hu " + "liftime %u\n", + response.pnu.newportmapping.mappedpublicport, + response.type == NATPMP_RESPTYPE_UDPPORTMAPPING ? "UDP" : + (response.type == NATPMP_RESPTYPE_TCPPORTMAPPING ? "TCP" : + "UNKNOWN"), + response.pnu.newportmapping.privateport, + response.pnu.newportmapping.lifetime); + log_msg(LOG_LEVEL_DEBUG, "[NAT PMP] epoch = %u\n", response.epoch); + + return true; +} + +static bool setup_nat_pmp(int video_rx_port, int audio_rx_port, int lifetime) +{ + struct in_addr gateway_in_use = { 0 }; + natpmp_t natpmp; + int r = 0; + r = initnatpmp(&natpmp, 0, 0); + log_msg(r < 0 ? LOG_LEVEL_ERROR : LOG_LEVEL_VERBOSE, "[NAT PMP] initnatpmp returned %d (%s)\n", r, + r ? "FAILED" : "SUCCESS"); + if (r < 0) { + return false; + } + gateway_in_use.s_addr = natpmp.gateway; + log_msg(LOG_LEVEL_NOTICE, "[NAT PMP] using gateway: %s\n", inet_ntoa(gateway_in_use)); + + /* sendpublicaddressrequest() */ + r = sendpublicaddressrequest(&natpmp); + log_msg(r < 0 ? LOG_LEVEL_ERROR : LOG_LEVEL_VERBOSE, "[NAT PMP] sendpublicaddressrequest returned %d (%s)\n", + r, r == 2 ? "SUCCESS" : "FAILED"); + if (r < 0) { + return false; + } + + natpmpresp_t response; + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + r = select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + if(r<0) { + log_msg(LOG_LEVEL_ERROR, "[NAT PMP] select()\n"); + return false; + } + r = readnatpmpresponseorretry(&natpmp, &response); + int sav_errno = errno; + log_msg(r < 0 ? LOG_LEVEL_ERROR : LOG_LEVEL_VERBOSE, "[NAT PMP] readnatpmpresponseorretry returned %d (%s)\n", + r, r == 0 ? "OK" : ( r== NATPMP_TRYAGAIN ? "TRY AGAIN" : "FAILED")); + if (r < 0 && r != NATPMP_TRYAGAIN) { + log_msg(LOG_LEVEL_ERROR, "[NAT_PMP] readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); + log_msg(LOG_LEVEL_ERROR, "[NAT PMP] errno=%d '%s'\n", + sav_errno, strerror(sav_errno)); + } + } while(r==NATPMP_TRYAGAIN); + + if (r < 0) { + return false; + } + + log_msg(LOG_LEVEL_NOTICE, "[NAT PMP] Public IP address: %s\n", inet_ntoa(response.pnu.publicaddress.addr)); + log_msg(LOG_LEVEL_DEBUG, "[NAT PMP] epoch = %u\n", response.epoch); + + if (!nat_pmp_add_mapping(&natpmp, video_rx_port, video_rx_port, lifetime) || + !nat_pmp_add_mapping(&natpmp, audio_rx_port, audio_rx_port, lifetime)) { + return false; + } + + r = closenatpmp(&natpmp); + log_msg(LOG_LEVEL_VERBOSE, "[NAT PMP] closenatpmp() returned %d (%s)\n", r, r==0?"SUCCESS":"FAILED"); + return r >= 0; +} + +struct ug_nat_traverse *start_nat_traverse(int video_rx_port, int audio_rx_port) +{ + assert(video_rx_port >= 0 && video_rx_port <= 65535 && audio_rx_port >= 0 && audio_rx_port <= 65535); + struct ug_nat_traverse s = { .audio_rx_port = audio_rx_port, .video_rx_port = video_rx_port }; + if (setup_nat_pmp(video_rx_port, audio_rx_port, ALLOCATION_TIMEOUT)) { + log_msg(LOG_LEVEL_NOTICE, "Successfully set NAT traversal with NAT PMP. Sender can send to external IP address.\n"); + s.traverse = UG_NAT_TRAVERSE_NAT_PMP; + return memcpy(malloc(sizeof s), &s, sizeof s); + } + // other techniques may follow + return NULL; +} + +void stop_nat_traverse(struct ug_nat_traverse *s) +{ + if (s == NULL) { + return; + } + + switch (s->traverse) { + case UG_NAT_TRAVERSE_NAT_PMP: + setup_nat_pmp(s->video_rx_port, s->audio_rx_port, 0); + break; + default: + break; + } + + free(s); +} + +/* vim: set expandtab sw=8: */ diff --git a/src/utils/nat.h b/src/utils/nat.h new file mode 100644 index 000000000..83f056068 --- /dev/null +++ b/src/utils/nat.h @@ -0,0 +1,60 @@ +/** + * @file utils/nat.h + * @author Martin Pulec + */ +/* + * Copyright (c) 2020 CESNET z.s.p.o. + * All rights reserved. + * + * 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 UTILS_NAT_H_AC13CDA9_2B22_4441_A81A_9858C87CE0AA +#define UTILS_NAT_H_AC13CDA9_2B22_4441_A81A_9858C87CE0AA + +#ifndef __cplusplus +#include +#endif // ! defined __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif + +struct ug_nat_traverse; + +struct ug_nat_traverse *start_nat_traverse(int video_rx_port, int audio_rx_port); +void stop_nat_traverse(struct ug_nat_traverse *); + + +#ifdef __cplusplus +} +#endif + +#endif // defined UTILS_NAT_H_AC13CDA9_2B22_4441_A81A_9858C87CE0AA + From 9db6fa6c831c006bb4814967433ddd5c3c5b7ea5 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 20 Nov 2020 13:46:59 +0100 Subject: [PATCH 32/61] NDI send: handle 16-bit audio "natively" --- src/video_display/ndi.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/video_display/ndi.c b/src/video_display/ndi.c index 606a439ea..d4a073164 100644 --- a/src/video_display/ndi.c +++ b/src/video_display/ndi.c @@ -259,7 +259,7 @@ static int display_ndi_get_property(void *state, int property, void *val, size_t { assert(*len == sizeof(struct audio_desc)); struct audio_desc *desc = (struct audio_desc *) val; - desc->bps = 4; + desc->bps = desc->bps <= 2 ? 2 : 4; desc->codec = AC_PCM; } break; @@ -269,18 +269,28 @@ static int display_ndi_get_property(void *state, int property, void *val, size_t return TRUE; } +#define NDI_SEND_AUDIO(frame, bit_depth) do { \ + assert((frame)->bps * 8 == (bit_depth)); \ + NDIlib_audio_frame_interleaved_ ## bit_depth ## s_t NDI_audio_frame = { 0 }; \ + NDI_audio_frame.sample_rate = (frame)->sample_rate; \ + NDI_audio_frame.no_channels = (frame)->ch_count; \ + NDI_audio_frame.timecode = NDIlib_send_timecode_synthesize; \ + NDI_audio_frame.p_data = (int ## bit_depth ## _t *) (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); \ + } while(0) + static void display_ndi_put_audio_frame(void *state, struct audio_frame *frame) { struct display_ndi *s = (struct display_ndi *) state; - assert(frame->bps == 4); - NDIlib_audio_frame_interleaved_32s_t NDI_audio_frame = { 0 }; - NDI_audio_frame.sample_rate = frame->sample_rate; - NDI_audio_frame.no_channels = frame->ch_count; - NDI_audio_frame.timecode = NDIlib_send_timecode_synthesize; - NDI_audio_frame.p_data = (int32_t *) frame->data; - NDI_audio_frame.no_samples = frame->data_len / frame->ch_count / sizeof NDI_audio_frame.p_data[0]; - - NDIlib_util_send_send_audio_interleaved_32s(s->pNDI_send, &NDI_audio_frame); + switch (frame->bps * 8) { +#define HANDLE_CASE(b) case b: NDI_SEND_AUDIO(frame, b); break; + HANDLE_CASE(16) + HANDLE_CASE(32) + default: + abort(); + } } static int display_ndi_reconfigure_audio(void *state, int quant_samples, int channels, From d70136f68ace122c7d348bf69c8c3559ab227817 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 11:41:33 +0100 Subject: [PATCH 33/61] GUI: updated audio drivers NDI, SPOUT and Syphon are able to capture/playback audio. Decklink audio playback on the other hand is not dependant upon video playback selection (works as a standalone device). --- gui/QT/option/audio_opts.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/QT/option/audio_opts.cpp b/gui/QT/option/audio_opts.cpp index f1cb976d5..7f4b69df1 100644 --- a/gui/QT/option/audio_opts.cpp +++ b/gui/QT/option/audio_opts.cpp @@ -28,14 +28,16 @@ const char * const sdiAudioCards[] = { "decklink", "aja", "dvs", - "deltacast" + "deltacast", + "ndi", + "spout", + "syphon", }; const char * const sdiAudio[] = { "analog", "AESEBU", "embedded", - "decklink" }; static std::vector> getSdiCond(const std::string &opt){ From 8b81ab5736fd9d786fd28e938bd4f900db58fd70 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 12:25:58 +0100 Subject: [PATCH 34/61] Fix passing NDI device IDs to GUI --- gui/QT/option/settings.cpp | 3 ++- src/video_capture/ndi.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/QT/option/settings.cpp b/gui/QT/option/settings.cpp index 1d98f6b77..88f903681 100644 --- a/gui/QT/option/settings.cpp +++ b/gui/QT/option/settings.cpp @@ -214,6 +214,7 @@ const static struct{ {"preview", Option::BoolOpt, "", "t", true, "", ""}, {"vuMeter", Option::BoolOpt, "", "t", true, "", ""}, {"errors_fatal", Option::BoolOpt, " --param errors-fatal", "t", true, "", ""}, + {"ndi.device", Option::StringOpt, ":url=", "", false, "video.source", "ndi"}, }; const struct { @@ -371,4 +372,4 @@ void Settings::changedAll(){ } } - +/* vim: set noexpandtab: */ diff --git a/src/video_capture/ndi.cpp b/src/video_capture/ndi.cpp index 2d13518b7..c26392d19 100644 --- a/src/video_capture/ndi.cpp +++ b/src/video_capture/ndi.cpp @@ -432,13 +432,13 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void // 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); - vt->cards = (struct device_info *) malloc(nr_sources * sizeof(struct device_info)); + vt->cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info)); if (vt->cards == nullptr) { return vt; } vt->card_count = nr_sources; for (int i = 0; i < static_cast(nr_sources); ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "ndi:url=%s", p_sources[i].p_url_address); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%s", p_sources[i].p_url_address); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "%s", p_sources[i].p_ndi_name); vt->cards[i].repeatable = true; } From f8bfcca28e113af608c47e86926be927fe9fba90 Mon Sep 17 00:00:00 2001 From: thpryrchn Date: Mon, 23 Nov 2020 12:11:20 -0500 Subject: [PATCH 35/61] Update Capabilities to have Auto-detect modes. This allows GUI to easily select the Autodetect by connection. The first does Auto by the Decklink, then the UltraGrid is also available for cards that Auto-detect doesn't work on. --- src/video_capture/decklink.cpp | 36 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/video_capture/decklink.cpp b/src/video_capture/decklink.cpp index 2c60b72e8..b61a45545 100644 --- a/src/video_capture/decklink.cpp +++ b/src/video_capture/decklink.cpp @@ -843,17 +843,35 @@ static struct vidcap_type *vidcap_decklink_probe(bool verbose, void (**deleter)( } } - if (i < (int) (sizeof vt->cards[vt->card_count - 1].modes / - sizeof vt->cards[vt->card_count - 1].modes[0])) { - snprintf(vt->cards[vt->card_count - 1].modes[i].id, - sizeof vt->cards[vt->card_count - 1].modes[i].id, - "{\"modeOpt\":\"detect-format\"}"); - snprintf(vt->cards[vt->card_count - 1].modes[i].name, - sizeof vt->cards[vt->card_count - 1].modes[i].name, - "UltraGrid auto-detect"); - i++; + for (auto &c : connections) { + if (i < (int) (sizeof vt->cards[vt->card_count - 1].modes / + sizeof vt->cards[vt->card_count - 1].modes[0])) { + snprintf(vt->cards[vt->card_count - 1].modes[i].id, + sizeof vt->cards[vt->card_count - 1].modes[i].id, + "{\"modeOpt\":\"connection=%s\"}", + c.c_str()); + snprintf(vt->cards[vt->card_count - 1].modes[i].name, + sizeof vt->cards[vt->card_count - 1].modes[i].name, + "Decklink Auto (%s)", c.c_str()); + i++; + } } + for (auto &c : connections) { + if (i < (int) (sizeof vt->cards[vt->card_count - 1].modes / + sizeof vt->cards[vt->card_count - 1].modes[0])) { + snprintf(vt->cards[vt->card_count - 1].modes[i].id, + sizeof vt->cards[vt->card_count - 1].modes[i].id, + "{\"modeOpt\":\"detect-format:connection=%s\"}", + c.c_str()); + snprintf(vt->cards[vt->card_count - 1].modes[i].name, + sizeof vt->cards[vt->card_count - 1].modes[i].name, + "UltraGrid auto-detect (%s)", c.c_str()); + i++; + } + } + + // Increment the total number of DeckLink cards found numDevices++; From f7b21a0a9ad12956a6b50c83ea063de8ac53698f Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Mon, 23 Nov 2020 21:47:57 +0100 Subject: [PATCH 36/61] GUI: Replace save file dialog --- gui/QT/window/ultragrid_window.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gui/QT/window/ultragrid_window.cpp b/gui/QT/window/ultragrid_window.cpp index 519d36c6c..7aff4b6b3 100644 --- a/gui/QT/window/ultragrid_window.cpp +++ b/gui/QT/window/ultragrid_window.cpp @@ -282,16 +282,13 @@ void UltragridWindow::showSettings(){ void UltragridWindow::saveSettings(){ const auto &optMap = settings.getOptionMap(); - QFileDialog fileDialog(this); - fileDialog.setFileMode(QFileDialog::AnyFile); - fileDialog.setNameFilter(tr("Json (*.json)")); - fileDialog.setDefaultSuffix("json"); - QStringList fileNames; - if (fileDialog.exec()) - fileNames = fileDialog.selectedFiles(); + QString filename = QFileDialog::getSaveFileName(this, "Save Settings", "", "Json (*.json)"); + if(!filename.endsWith(QString(".json"))){ + filename.append(QString(".json")); + } - QFile outFile(fileNames.first()); + QFile outFile(filename); if (!outFile.open(QIODevice::WriteOnly)) { qWarning("Couldn't open save file."); @@ -344,6 +341,9 @@ void UltragridWindow::loadSettings(){ if (fileDialog.exec()) fileNames = fileDialog.selectedFiles(); + if(fileNames.empty()) + return; + loadSettingsFile(fileNames.first()); } From dc9ede91f1d6ffc91d0f0073c6369b71f69fdd13 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Tue, 24 Nov 2020 10:44:37 +0100 Subject: [PATCH 37/61] GUI: Fix embedded audio playback devices not appearing --- gui/QT/option/combobox_ui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/QT/option/combobox_ui.cpp b/gui/QT/option/combobox_ui.cpp index 1aad76c22..82b2fd1a3 100644 --- a/gui/QT/option/combobox_ui.cpp +++ b/gui/QT/option/combobox_ui.cpp @@ -40,7 +40,8 @@ static bool conditionsSatisfied( for(const auto &condClause : conds){ bool orRes = false; for(const auto &condItem : condClause){ - bool val = condItem.value.val == settings->getOption(condItem.value.opt).getValue(); + //Check if condItem.value.val is the prefix of the currently set value + bool val = settings->getOption(condItem.value.opt).getValue().rfind(condItem.value.val, 0) == 0; //negate result if negation is true val = val != condItem.negation; From 2c5a9d6c3e52134d8e5306d2bd96316549130ccc Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 14:06:57 +0100 Subject: [PATCH 38/61] Video capture: check pointer to nullptr --- src/video_capture.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/video_capture.cpp b/src/video_capture.cpp index 4a7c11972..7d5b74a24 100644 --- a/src/video_capture.cpp +++ b/src/video_capture.cpp @@ -112,6 +112,9 @@ void print_available_capturers() void (*deleter)(void *) = nullptr; struct vidcap_type *vt = vci->probe(true, &deleter); + if (vt == nullptr) { + continue; + } printf("[cap][capture] %s\n", item.first.c_str()); for (int i = 0; i < vt->card_count; ++i) { printf("[capability][device][v2] {" From 70f98b5ee80667ae80e3b7c874e26e4bcf9bfc99 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 15:25:04 +0100 Subject: [PATCH 39/61] NDI cap.: release correctly lib --- src/video_capture/ndi.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/video_capture/ndi.cpp b/src/video_capture/ndi.cpp index c26392d19..9f0c7eaf6 100644 --- a/src/video_capture/ndi.cpp +++ b/src/video_capture/ndi.cpp @@ -137,6 +137,7 @@ static void show_help() { cout << "\t\t" << p_sources[i].p_ndi_name << " - " << p_sources[i].p_url_address << "\n"; } cout << "\n"; + NDIlib_find_destroy(pNDI_find); } static int vidcap_ndi_init(struct vidcap_params *params, void **state) @@ -434,6 +435,7 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void vt->cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info)); if (vt->cards == nullptr) { + NDIlib_find_destroy(pNDI_find); return vt; } vt->card_count = nr_sources; @@ -444,6 +446,8 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void } } + NDIlib_find_destroy(pNDI_find); + return vt; } From f8d9ac9d300438195e141ac2cf66eb6e288c911e Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 16:05:51 +0100 Subject: [PATCH 40/61] GUI: fix passing options for other capturers --- gui/QT/option/settings.cpp | 7 +++++++ src/video_capture/aja.cpp | 2 +- src/video_capture/deltacast.cpp | 2 +- src/video_capture/screen_win.c | 1 - src/video_capture/syphon.mm | 2 +- src/video_capture/ximea.c | 3 +-- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/gui/QT/option/settings.cpp b/gui/QT/option/settings.cpp index 88f903681..c9cf0f783 100644 --- a/gui/QT/option/settings.cpp +++ b/gui/QT/option/settings.cpp @@ -214,7 +214,14 @@ const static struct{ {"preview", Option::BoolOpt, "", "t", true, "", ""}, {"vuMeter", Option::BoolOpt, "", "t", true, "", ""}, {"errors_fatal", Option::BoolOpt, " --param errors-fatal", "t", true, "", ""}, + {"aja.device", Option::StringOpt, ":device=", "", false, "video.source", "aja"}, + {"bluefish444.device", Option::StringOpt, ":device=", "", false, "video.source", "bluefish444"}, + {"deltacast.device", Option::StringOpt, ":device=", "", false, "video.source", "deltacast"}, + {"deltacast-dv.device", Option::StringOpt, ":device=", "", false, "video.source", "deltacast-dv"}, + {"dvs.device", Option::StringOpt, ":", "", false, "video.source", "dvs"}, {"ndi.device", Option::StringOpt, ":url=", "", false, "video.source", "ndi"}, + {"syphon.device", Option::StringOpt, ":app=", "", false, "video.source", "syphon"}, + {"ximea.device", Option::StringOpt, ":device=", "", false, "video.source", "ximea"}, }; const struct { diff --git a/src/video_capture/aja.cpp b/src/video_capture/aja.cpp index ac6bff5e0..c1bbd9515 100644 --- a/src/video_capture/aja.cpp +++ b/src/video_capture/aja.cpp @@ -1131,7 +1131,7 @@ LINK_SPEC struct vidcap_type *vidcap_aja_probe(bool verbose, void (**deleter)(vo realloc(vt->cards, vt->card_count * sizeof(struct device_info)); memset(&vt->cards[vt->card_count - 1], 0, sizeof(struct device_info)); snprintf(vt->cards[vt->card_count - 1].id, sizeof vt->cards[vt->card_count - 1].id, - "device=%d", i); + "%d", i); snprintf(vt->cards[vt->card_count - 1].name, sizeof vt->cards[vt->card_count - 1].name, "AJA %s", info.deviceIdentifier.c_str()); } diff --git a/src/video_capture/deltacast.cpp b/src/video_capture/deltacast.cpp index fb38f3f09..5f0428830 100644 --- a/src/video_capture/deltacast.cpp +++ b/src/video_capture/deltacast.cpp @@ -133,7 +133,7 @@ vidcap_deltacast_probe(bool verbose, void (**deleter)(void *)) vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); vt->card_count = NbBoards; for (ULONG i = 0; i < NbBoards; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%" PRIu32, i); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST SDI board %" PRIu32, i); } } diff --git a/src/video_capture/screen_win.c b/src/video_capture/screen_win.c index f0e1c3e02..af9c7e0ee 100644 --- a/src/video_capture/screen_win.c +++ b/src/video_capture/screen_win.c @@ -99,7 +99,6 @@ static struct vidcap_type * vidcap_screen_win_probe(bool verbose, void (**delete vt->card_count = 1; vt->cards = calloc(vt->card_count, sizeof(struct device_info)); // vt->cards[0].id can be "" since screen cap. doesn't require parameters - snprintf(vt->cards[0].id, sizeof vt->cards[0].id, "screen"); snprintf(vt->cards[0].name, sizeof vt->cards[0].name, "Screen capture"); return vt; diff --git a/src/video_capture/syphon.mm b/src/video_capture/syphon.mm index a840f0180..fd7d75c7d 100644 --- a/src/video_capture/syphon.mm +++ b/src/video_capture/syphon.mm @@ -474,7 +474,7 @@ static void probe_devices_callback(state_vidcap_syphon *s) s->probed_devices = (struct device_info *) realloc(s->probed_devices, s->probed_devices_count * sizeof(struct device_info)); memset(&s->probed_devices[s->probed_devices_count - 1], 0, sizeof(struct device_info)); snprintf(s->probed_devices[s->probed_devices_count - 1].id, sizeof s->probed_devices[s->probed_devices_count - 1].id, - "app=%s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); + "%s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); snprintf(s->probed_devices[s->probed_devices_count - 1].name, sizeof s->probed_devices[s->probed_devices_count - 1].name, "Syphon %s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); } diff --git a/src/video_capture/ximea.c b/src/video_capture/ximea.c index 061e9ee25..108d2ad47 100644 --- a/src/video_capture/ximea.c +++ b/src/video_capture/ximea.c @@ -351,9 +351,8 @@ static struct vidcap_type *vidcap_ximea_probe(bool verbose, void (**deleter)(voi vt->cards = calloc(count, sizeof(struct device_info)); for (DWORD i = 0; i < count; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "ximea:device=%d", (int) i); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%d", (int) i); char name[256]; - color_out(COLOR_OUT_BOLD, "%d) ", (int) i); if (funcs.xiGetDeviceInfoString(i, XI_PRM_DEVICE_NAME, name, sizeof name) == XI_OK) { strncpy(vt->cards[i].name, name, sizeof vt->cards[i].name); } From 391d927766d9f45b8a6acbb50607718673f5d6a1 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 23 Nov 2020 16:07:15 +0100 Subject: [PATCH 41/61] Reindent probes in capturers --- src/video_capture/bluefish444.cpp | 39 ++++++------ src/video_capture/deltacast.cpp | 32 +++++----- src/video_capture/deltacast_dvi.cpp | 62 ++++++++++--------- src/video_capture/dvs.c | 48 ++++++++------- src/video_capture/ndi.cpp | 52 ++++++++-------- src/video_capture/testcard.cpp | 95 +++++++++++++++-------------- 6 files changed, 174 insertions(+), 154 deletions(-) diff --git a/src/video_capture/bluefish444.cpp b/src/video_capture/bluefish444.cpp index 8247a83c7..36f9e0462 100644 --- a/src/video_capture/bluefish444.cpp +++ b/src/video_capture/bluefish444.cpp @@ -351,24 +351,29 @@ vidcap_bluefish444_probe(bool verbose, void (**deleter)(void *)) *deleter = free; vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type)); - if (vt != NULL) { - vt->name = "bluefish444"; - vt->description = "Bluefish444 video capture"; - - if (verbose) { - BLUE_S32 iDevices; - BLUEVELVETC_HANDLE pSDK = bfcFactory(); - bfcEnumerate(pSDK, &iDevices); - bfcDestroy(pSDK); - - vt->card_count = iDevices; - vt->cards = (struct device_info *) calloc(iDevices, sizeof(struct device_info)); - for (int i = 0; i < iDevices; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%d", i + 1); - snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "Bluefish444 card #%d", i); - } - } + if (vt == NULL) { + return NULL; } + + vt->name = "bluefish444"; + vt->description = "Bluefish444 video capture"; + + if (!verbose) { + return vt; + } + + BLUE_S32 iDevices; + BLUEVELVETC_HANDLE pSDK = bfcFactory(); + bfcEnumerate(pSDK, &iDevices); + bfcDestroy(pSDK); + + vt->card_count = iDevices; + vt->cards = (struct device_info *) calloc(iDevices, sizeof(struct device_info)); + for (int i = 0; i < iDevices; ++i) { + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%d", i + 1); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "Bluefish444 card #%d", i); + } + return vt; } diff --git a/src/video_capture/deltacast.cpp b/src/video_capture/deltacast.cpp index 5f0428830..4a8ec10f2 100644 --- a/src/video_capture/deltacast.cpp +++ b/src/video_capture/deltacast.cpp @@ -122,21 +122,25 @@ vidcap_deltacast_probe(bool verbose, void (**deleter)(void *)) *deleter = free; vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type)); - if (vt != NULL) { - vt->name = "deltacast"; - vt->description = "DELTACAST card"; + if (vt == NULL) { + return NULL; + } - if (verbose) { - ULONG Result,DllVersion,NbBoards; - Result = VHD_GetApiInfo(&DllVersion,&NbBoards); - if (Result == VHDERR_NOERROR) { - vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); - vt->card_count = NbBoards; - for (ULONG i = 0; i < NbBoards; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); - snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST SDI board %" PRIu32, i); - } - } + vt->name = "deltacast"; + vt->description = "DELTACAST card"; + + if (!verbose) { + return vt; + } + + ULONG Result,DllVersion,NbBoards; + Result = VHD_GetApiInfo(&DllVersion,&NbBoards); + if (Result == VHDERR_NOERROR) { + vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); + vt->card_count = NbBoards; + for (ULONG i = 0; i < NbBoards; ++i) { + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST SDI board %" PRIu32, i); } } return vt; diff --git a/src/video_capture/deltacast_dvi.cpp b/src/video_capture/deltacast_dvi.cpp index 63c600c75..f8ca24dcc 100644 --- a/src/video_capture/deltacast_dvi.cpp +++ b/src/video_capture/deltacast_dvi.cpp @@ -165,38 +165,44 @@ vidcap_deltacast_dvi_probe(bool verbose, void (**deleter)(void *)) *deleter = free; vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type)); - if (vt != NULL) { - vt->name = "deltacast-dv"; - vt->description = "DELTACAST DVI/HDMI card"; + if (vt == NULL) { + return NULL; + } - if (verbose) { - ULONG Result,DllVersion,NbBoards; - Result = VHD_GetApiInfo(&DllVersion,&NbBoards); - if (Result == VHDERR_NOERROR) { - vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); - vt->card_count = NbBoards; - for (ULONG i = 0; i < NbBoards; ++i) { - string board{"Unknown board type"}; - ULONG BoardType; - HANDLE BoardHandle = NULL; - Result = VHD_OpenBoardHandle(i, &BoardHandle, NULL, 0); - VHD_GetBoardProperty(BoardHandle, VHD_CORE_BP_BOARD_TYPE, &BoardType); - if (Result == VHDERR_NOERROR) - { - auto it = board_type_map.find(BoardType); - if (it != board_type_map.end()) { - board = it->second; - } - } - VHD_CloseBoardHandle(BoardHandle); + vt->name = "deltacast-dv"; + vt->description = "DELTACAST DVI/HDMI card"; - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%" PRIu32, i); - snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST %s #%" PRIu32, - board.c_str(), i); - } + if (!verbose) { + return vt; + } + + ULONG Result,DllVersion,NbBoards; + Result = VHD_GetApiInfo(&DllVersion,&NbBoards); + if (Result != VHDERR_NOERROR) { + return vt; + } + + vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); + vt->card_count = NbBoards; + for (ULONG i = 0; i < NbBoards; ++i) { + string board{"Unknown board type"}; + ULONG BoardType; + HANDLE BoardHandle = NULL; + Result = VHD_OpenBoardHandle(i, &BoardHandle, NULL, 0); + VHD_GetBoardProperty(BoardHandle, VHD_CORE_BP_BOARD_TYPE, &BoardType); + if (Result == VHDERR_NOERROR) + { + auto it = board_type_map.find(BoardType); + if (it != board_type_map.end()) { + board = it->second; } } - } + VHD_CloseBoardHandle(BoardHandle); + + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST %s #%" PRIu32, + board.c_str(), i); + } return vt; } diff --git a/src/video_capture/dvs.c b/src/video_capture/dvs.c index 475cebb3e..2dd621098 100644 --- a/src/video_capture/dvs.c +++ b/src/video_capture/dvs.c @@ -650,31 +650,33 @@ static struct vidcap_type *vidcap_dvs_probe(bool verbose, void (**deleter)(void *deleter = free; vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type)); - if (vt != NULL) { - vt->name = "dvs"; - vt->description = "DVS (SMPTE 274M/25i)"; + if (vt == NULL) { + return NULL; + } + vt->name = "dvs"; + vt->description = "DVS (SMPTE 274M/25i)"; - if (verbose) { - int card_idx = 0; - sv_handle *sv; - char name[128]; - int res; - snprintf(name, 128, "PCI,card:%d", card_idx); - res = sv_openex(&sv, name, SV_OPENPROGRAM_DEFAULT, SV_OPENTYPE_DEFAULT, 0, 0); - while (res == SV_OK) { - vt->card_count = card_idx + 1; - vt->cards = realloc(vt->cards, vt->card_count * sizeof(struct device_info)); - memset(&vt->cards[card_idx], 0, sizeof(struct device_info)); - strncpy(vt->cards[card_idx].id, name, sizeof vt->cards[card_idx].id - 1); - snprintf(vt->cards[card_idx].name, sizeof vt->cards[card_idx].name, - "DVS card #%d", card_idx); + if (!verbose) { + return vt; + } + int card_idx = 0; + sv_handle *sv; + char name[128]; + int res; + snprintf(name, 128, "PCI,card:%d", card_idx); + res = sv_openex(&sv, name, SV_OPENPROGRAM_DEFAULT, SV_OPENTYPE_DEFAULT, 0, 0); + while (res == SV_OK) { + vt->card_count = card_idx + 1; + vt->cards = realloc(vt->cards, vt->card_count * sizeof(struct device_info)); + memset(&vt->cards[card_idx], 0, sizeof(struct device_info)); + strncpy(vt->cards[card_idx].id, name, sizeof vt->cards[card_idx].id - 1); + snprintf(vt->cards[card_idx].name, sizeof vt->cards[card_idx].name, + "DVS card #%d", card_idx); - sv_close(sv); - card_idx++; - snprintf(name, 128, "PCI,card:%d", card_idx); - res = sv_openex(&sv, name, SV_OPENPROGRAM_DEFAULT, SV_OPENTYPE_DEFAULT, 0, 0); - } - } + sv_close(sv); + card_idx++; + snprintf(name, 128, "PCI,card:%d", card_idx); + res = sv_openex(&sv, name, SV_OPENPROGRAM_DEFAULT, SV_OPENTYPE_DEFAULT, 0, 0); } return vt; } diff --git a/src/video_capture/ndi.cpp b/src/video_capture/ndi.cpp index 9f0c7eaf6..0bbea31af 100644 --- a/src/video_capture/ndi.cpp +++ b/src/video_capture/ndi.cpp @@ -417,33 +417,35 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void vt->name = "ndi"; vt->description = "NDI source"; - if (verbose) { - auto pNDI_find = NDIlib_find_create_v2(); - if (pNDI_find == nullptr) { - LOG(LOG_LEVEL_ERROR) << "[NDI] Cannot create finder object!\n"; - return vt; - } + if (!verbose) { + return vt; + } - uint32_t nr_sources = 0; - const NDIlib_source_t* p_sources = nullptr; - // Give sources some time to occur - usleep(100 * 1000); - // 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); + auto pNDI_find = NDIlib_find_create_v2(); + if (pNDI_find == nullptr) { + LOG(LOG_LEVEL_ERROR) << "[NDI] Cannot create finder object!\n"; + return vt; + } - vt->cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info)); - if (vt->cards == nullptr) { - NDIlib_find_destroy(pNDI_find); - return vt; - } - vt->card_count = nr_sources; - for (int i = 0; i < static_cast(nr_sources); ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%s", p_sources[i].p_url_address); - snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "%s", p_sources[i].p_ndi_name); - vt->cards[i].repeatable = true; - } + uint32_t nr_sources = 0; + const NDIlib_source_t* p_sources = nullptr; + // Give sources some time to occur + usleep(100 * 1000); + // 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); + + vt->cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info)); + if (vt->cards == nullptr) { + NDIlib_find_destroy(pNDI_find); + return vt; + } + vt->card_count = nr_sources; + for (int i = 0; i < static_cast(nr_sources); ++i) { + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%s", p_sources[i].p_url_address); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "%s", p_sources[i].p_ndi_name); + vt->cards[i].repeatable = true; } NDIlib_find_destroy(pNDI_find); diff --git a/src/video_capture/testcard.cpp b/src/video_capture/testcard.cpp index 334254751..0433671c9 100644 --- a/src/video_capture/testcard.cpp +++ b/src/video_capture/testcard.cpp @@ -816,59 +816,60 @@ static struct vidcap_type *vidcap_testcard_probe(bool verbose, void (**deleter)( *deleter = free; vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type)); - if (vt != NULL) { - vt->name = "testcard"; - vt->description = "Video testcard"; + if (vt == NULL) { + return NULL; + } - if (verbose) { - vt->card_count = 1; - vt->cards = (struct device_info *) calloc(vt->card_count, sizeof(struct device_info)); - vt->cards[0].id[0] = '\0'; - snprintf(vt->cards[0].name, sizeof vt->cards[0].name, "Testing signal"); + vt->name = "testcard"; + vt->description = "Video testcard"; - struct { - int width; - int height; - } sizes[] = { - {1280, 720}, - {1920, 1080}, - {3840, 2160}, - }; - int framerates[] = {24, 30, 60}; - const char * const pix_fmts[] = {"UYVY", "RGB"}; + if (!verbose) { + return vt; + } - snprintf(vt->cards[0].modes[0].name, - sizeof vt->cards[0].name, "Default"); - snprintf(vt->cards[0].modes[0].id, - sizeof vt->cards[0].id, - "{\"width\":\"\", " - "\"height\":\"\", " - "\"format\":\"\", " - "\"fps\":\"\"}"); + vt->card_count = 1; + vt->cards = (struct device_info *) calloc(vt->card_count, sizeof(struct device_info)); + snprintf(vt->cards[0].name, sizeof vt->cards[0].name, "Testing signal"); - int i = 1; - for(const auto &pix_fmt : pix_fmts){ - for(const auto &size : sizes){ - for(const auto &fps : framerates){ - snprintf(vt->cards[0].modes[i].name, - sizeof vt->cards[0].name, - "%dx%d@%d %s", - size.width, size.height, - fps, pix_fmt); - snprintf(vt->cards[0].modes[i].id, - sizeof vt->cards[0].id, - "{\"width\":\"%d\", " - "\"height\":\"%d\", " - "\"format\":\"%s\", " - "\"fps\":\"%d\"}", - size.width, size.height, - pix_fmt, fps); - i++; - } - } + struct { + int width; + int height; + } sizes[] = { + {1280, 720}, + {1920, 1080}, + {3840, 2160}, + }; + int framerates[] = {24, 30, 60}; + const char * const pix_fmts[] = {"UYVY", "RGB"}; + snprintf(vt->cards[0].modes[0].name, + sizeof vt->cards[0].name, "Default"); + snprintf(vt->cards[0].modes[0].id, + sizeof vt->cards[0].id, + "{\"width\":\"\", " + "\"height\":\"\", " + "\"format\":\"\", " + "\"fps\":\"\"}"); + + int i = 1; + for(const auto &pix_fmt : pix_fmts){ + for(const auto &size : sizes){ + for(const auto &fps : framerates){ + snprintf(vt->cards[0].modes[i].name, + sizeof vt->cards[0].name, + "%dx%d@%d %s", + size.width, size.height, + fps, pix_fmt); + snprintf(vt->cards[0].modes[i].id, + sizeof vt->cards[0].id, + "{\"width\":\"%d\", " + "\"height\":\"%d\", " + "\"format\":\"%s\", " + "\"fps\":\"%d\"}", + size.width, size.height, + pix_fmt, fps); + i++; } - } } return vt; From 76635d7b69255a47a7f40362620c71471301ccfc Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 08:32:03 +0100 Subject: [PATCH 42/61] GUI: removed vidcaps that fill vidcap_type::cards --- gui/QT/option/video_opts.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/gui/QT/option/video_opts.cpp b/gui/QT/option/video_opts.cpp index 77f0ddc3a..a349244b3 100644 --- a/gui/QT/option/video_opts.cpp +++ b/gui/QT/option/video_opts.cpp @@ -6,11 +6,8 @@ std::vector getVideoSrc(AvailableSettings *availSettings){ const char * const whiteList[] = { - "aja", - "dvs", "bitflow", "spout", - "syphon" }; const std::string optStr = "video.source"; From 0596ff95b0315d60dde9d8b157f423db8ed7478c Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 12:18:01 +0100 Subject: [PATCH 43/61] DeckLink probe: very slight simplification --- src/video_capture/decklink.cpp | 48 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/video_capture/decklink.cpp b/src/video_capture/decklink.cpp index b61a45545..c1889b62f 100644 --- a/src/video_capture/decklink.cpp +++ b/src/video_capture/decklink.cpp @@ -825,10 +825,11 @@ static struct vidcap_type *vidcap_decklink_probe(bool verbose, void (**deleter)( } list> modes = get_input_modes (deckLink); int i = 0; + const int mode_count = sizeof vt->cards[vt->card_count - 1].modes / + sizeof vt->cards[vt->card_count - 1].modes[0]; for (auto &m : modes) { for (auto &c : connections) { - if (i >= (int) (sizeof vt->cards[vt->card_count - 1].modes / - sizeof vt->cards[vt->card_count - 1].modes[0])) { // no space + if (i >= mode_count) { // no space break; } @@ -844,34 +845,29 @@ static struct vidcap_type *vidcap_decklink_probe(bool verbose, void (**deleter)( } for (auto &c : connections) { - if (i < (int) (sizeof vt->cards[vt->card_count - 1].modes / - sizeof vt->cards[vt->card_count - 1].modes[0])) { - snprintf(vt->cards[vt->card_count - 1].modes[i].id, - sizeof vt->cards[vt->card_count - 1].modes[i].id, - "{\"modeOpt\":\"connection=%s\"}", - c.c_str()); - snprintf(vt->cards[vt->card_count - 1].modes[i].name, - sizeof vt->cards[vt->card_count - 1].modes[i].name, - "Decklink Auto (%s)", c.c_str()); - i++; + if (i >= mode_count) { + break; } - } - - for (auto &c : connections) { - if (i < (int) (sizeof vt->cards[vt->card_count - 1].modes / - sizeof vt->cards[vt->card_count - 1].modes[0])) { - snprintf(vt->cards[vt->card_count - 1].modes[i].id, - sizeof vt->cards[vt->card_count - 1].modes[i].id, - "{\"modeOpt\":\"detect-format:connection=%s\"}", - c.c_str()); - snprintf(vt->cards[vt->card_count - 1].modes[i].name, - sizeof vt->cards[vt->card_count - 1].modes[i].name, - "UltraGrid auto-detect (%s)", c.c_str()); - i++; + snprintf(vt->cards[vt->card_count - 1].modes[i].id, + sizeof vt->cards[vt->card_count - 1].modes[i].id, + "{\"modeOpt\":\"connection=%s\"}", + c.c_str()); + snprintf(vt->cards[vt->card_count - 1].modes[i].name, + sizeof vt->cards[vt->card_count - 1].modes[i].name, + "Decklink Auto (%s)", c.c_str()); + if (++i >= mode_count) { + break; } + snprintf(vt->cards[vt->card_count - 1].modes[i].id, + sizeof vt->cards[vt->card_count - 1].modes[i].id, + "{\"modeOpt\":\"detect-format:connection=%s\"}", + c.c_str()); + snprintf(vt->cards[vt->card_count - 1].modes[i].name, + sizeof vt->cards[vt->card_count - 1].modes[i].name, + "UltraGrid auto-detect (%s)", c.c_str()); + i++; } - // Increment the total number of DeckLink cards found numDevices++; From 51f2bda9630fdc7a17b6b9fb226f455284df3817 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 15:47:02 +0100 Subject: [PATCH 44/61] Plaform pipe: fixed getsockopt len --- src/compat/platform_pipe.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compat/platform_pipe.cpp b/src/compat/platform_pipe.cpp index 4fee04807..afb14ae20 100644 --- a/src/compat/platform_pipe.cpp +++ b/src/compat/platform_pipe.cpp @@ -106,7 +106,7 @@ static fd_t connect_to_socket(int local_port) DECLARE_TIMEOUT(timeout, 1); DECLARE_TIMEOUT(old_timeout, 0); - socklen_t old_timeout_len = 0; + socklen_t old_timeout_len = sizeof old_timeout; if (getsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&old_timeout), &old_timeout_len) != 0) { socket_error("pipe getsockopt"); } @@ -120,7 +120,7 @@ static fd_t connect_to_socket(int local_port) CLOSESOCKET(fd); return INVALID_SOCKET; } - if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&old_timeout), sizeof old_timeout) != 0) { + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&old_timeout), old_timeout_len) != 0) { socket_error("pipe setsockopt"); } @@ -192,7 +192,7 @@ int platform_pipe_init(fd_t p[2]) DECLARE_TIMEOUT(timeout, 1); DECLARE_TIMEOUT(old_timeout, 0); - socklen_t old_timeout_len = 0; + socklen_t old_timeout_len = sizeof old_timeout; if (getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), &old_timeout_len) != 0) { socket_error("pipe getsockopt"); } @@ -210,7 +210,7 @@ int platform_pipe_init(fd_t p[2]) return system_pipe(p); } thr.join(); - if (setsockopt(p[0], SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), sizeof old_timeout) != 0) { + if (setsockopt(p[0], SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&old_timeout), old_timeout_len) != 0) { socket_error("pipe setsockopt"); } p[1] = par.sock; From 987bdd5fc62a46383aca5a1c23462727bcdc2a9f Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 14:39:49 +0100 Subject: [PATCH 45/61] Get rid of SPOUT wrapper --- .github/scripts/Windows/prepare_msys.sh | 14 +++++--- configure.ac | 18 +++++++--- data/scripts/build_spout64.sh | 47 ------------------------- src/spout_receiver.cpp | 2 +- src/spout_receiver.h | 16 ++------- src/spout_sender.cpp | 2 +- src/spout_sender.h | 16 ++------- 7 files changed, 31 insertions(+), 84 deletions(-) delete mode 100755 data/scripts/build_spout64.sh diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index e0991647f..28d8e3ba3 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -65,10 +65,16 @@ make install cd - # Install SPOUT -wget --no-verbose https://frakira.fi.muni.cz/~xpulec/SpoutSDK.zip # this is the SDK subdirectory installed by Spout installer -unzip SpoutSDK.zip -d src -MSBuild.exe -p:PlatformToolset=v142 -p:Configuration=Release -p:Platform=x64 src/SpoutSDK/VS2012 -data/scripts/build_spout64.sh src/SpoutSDK/VS2012/x64/Release +git clone --depth 1 https://github.com/leadedge/Spout2.git +mkdir Spout2/SpoutSDK/Source/build +cd Spout2/SpoutSDK/Source/build +cmake -G 'MSYS Makefiles' .. +cmake --build . +cp libSpout.a /usr/local/lib +cd - +mkdir /usr/local/include/SpoutSDK +cp Spout2/SpoutSDK/Source/*.h /usr/local/include/SpoutSDK +rm -rf Spout2 # Install FFMPEG wget --no-verbose https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z && 7z x ffmpeg-release-full-shared.7z && cp -r ffmpeg-*build-shared/{bin,lib,include} /usr/local && rm -rf ffmpeg-* || exit 1 diff --git a/configure.ac b/configure.ac index 2f6192484..9e2963ec6 100644 --- a/configure.ac +++ b/configure.ac @@ -2877,17 +2877,25 @@ AC_ARG_ENABLE(spout, [spout_req=$build_default] ) -FOUND_SPOUT_WRAPPER=no +FOUND_SPOUT=no if test $system = Windows then - AC_CHECK_LIB(spout_wrapper, spout_create_receiver, [FOUND_SPOUT_WRAPPER=yes]) + AC_LANG_PUSH([C++]) + SAVED_LIBS="$LIBS" + SPOUT_LIBS="-lSpout -lgdi32 -lDXGI -lVersion -ld3d9 -ld3d11 -lopengl32 -lShlwapi" + LIBS="$LIBS $SPOUT_LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[SpoutSender *sender = new SpoutSender;]])], + FOUND_SPOUT=yes, FOUND_SPOUT=no) + LIBS=$SAVED_LIBS + AC_LANG_POP([C++]) fi -if test $spout_req != no -a "$OPENGL" = yes -a $FOUND_SPOUT_WRAPPER = yes +if test $spout_req != no -a "$OPENGL" = yes -a $FOUND_SPOUT = yes then AC_DEFINE([HAVE_SPOUT], [1], [Build with Spout support]) - LIBS="$LIBS -lspout_wrapper" - OBJS="$OBJS src/video_capture/spout.o" + LIBS="$LIBS $SPOUT_LIBS" + OBJS="$OBJS src/spout_receiver.o src/spout_sender.o src/video_capture/spout.o" spout=yes fi diff --git a/data/scripts/build_spout64.sh b/data/scripts/build_spout64.sh deleted file mode 100755 index 00f0b3f26..000000000 --- a/data/scripts/build_spout64.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -eu - -####################################### -# USAGE -# -# 1) Copy SpoutSDK to src -# 2) run build_spout.sh (this script) -####################################### - -function run_in_vs_env -{ - eval vssetup=\$$1'\\..\\..\\VC\\bin\\amd64\\vcvars64.bat' - cmd //Q //C call "$vssetup" "&&" "${@:2}" -} - -function run_vs16 -{ - eval vssetup='C:\\Program\ Files\ \(x86\)\\Microsoft\ Visual\ Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat' - cmd //Q //C call "$vssetup" "&&" "$@" -} - -function run_vs12 -{ - run_in_vs_env VS120COMNTOOLS "$@" -} - - -function run_vs11 -{ - run_in_vs_env VS110COMNTOOLS "$@" -} - -function run_vs10 -{ - run_in_vs_env VS100COMNTOOLS "$@" -} - -LIBDIR=${1:-src/SpoutSDK/Binaries/x64} -MSVS_PATH=`/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio/Installer/vswhere.exe -latest -property installationPath` - -eval vssetup=\"$MSVS_PATH\"'\\VC\\Auxiliary\\Build\\vcvars64.bat' -cmd //Q //C call "$vssetup" "&&" cl //DEXPORT_DLL_SYMBOLS src/spout_sender.cpp src/spout_receiver.cpp //LD $LIBDIR/Spout.lib //Fespout_wrapper - -cp spout_wrapper.dll /usr/local/bin -cp spout_wrapper.lib /usr/local/lib -cp $LIBDIR/Spout.dll /usr/local/bin - diff --git a/src/spout_receiver.cpp b/src/spout_receiver.cpp index c0417aaa1..3c280e20c 100644 --- a/src/spout_receiver.cpp +++ b/src/spout_receiver.cpp @@ -34,7 +34,7 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "SpoutSDK/Spout.h" +#include #include "spout_receiver.h" diff --git a/src/spout_receiver.h b/src/spout_receiver.h index 0591ad141..f9be97b07 100644 --- a/src/spout_receiver.h +++ b/src/spout_receiver.h @@ -46,19 +46,9 @@ extern "C" { #endif // __cplusplus -#if defined _MSC_VER || defined __MINGW32__ -#ifdef EXPORT_DLL_SYMBOLS -#define SPOUT_RECEIVER_DLL_API __declspec(dllexport) -#else -#define SPOUT_RECEIVER_DLL_API __declspec(dllimport) -#endif -#else // other platforms -#define SPOUT_RECEIVER_DLL_API -#endif - -SPOUT_RECEIVER_DLL_API void *spout_create_receiver(char *name, unsigned int *width, unsigned int *height); -SPOUT_RECEIVER_DLL_API bool spout_receiver_recvframe(void *s, char *sender_name, unsigned int width, unsigned int height, char *data, GLenum glFormat); -SPOUT_RECEIVER_DLL_API void spout_receiver_delete(void *s); +void *spout_create_receiver(char *name, unsigned int *width, unsigned int *height); +bool spout_receiver_recvframe(void *s, char *sender_name, unsigned int width, unsigned int height, char *data, GLenum glFormat); +void spout_receiver_delete(void *s); #ifdef __cplusplus } diff --git a/src/spout_sender.cpp b/src/spout_sender.cpp index 932e014a8..7cebdd7bb 100644 --- a/src/spout_sender.cpp +++ b/src/spout_sender.cpp @@ -34,7 +34,7 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "SpoutSDK/Spout.h" +#include #include "spout_sender.h" diff --git a/src/spout_sender.h b/src/spout_sender.h index a09da5860..d9fd5dbde 100644 --- a/src/spout_sender.h +++ b/src/spout_sender.h @@ -44,19 +44,9 @@ extern "C" { #endif // __cplusplus -#if defined _MSC_VER || defined __MINGW32__ -#ifdef EXPORT_DLL_SYMBOLS -#define SPOUT_SENDER_DLL_API __declspec(dllexport) -#else -#define SPOUT_SENDER_DLL_API __declspec(dllimport) -#endif -#else // other platforms -#define SPOUT_SENDER_DLL_API -#endif - -SPOUT_SENDER_DLL_API void *spout_sender_register(const char *name, int width, int height); -SPOUT_SENDER_DLL_API void spout_sender_sendframe(void *s, int width, int height, unsigned int id); -SPOUT_SENDER_DLL_API void spout_sender_unregister(void *s); +void *spout_sender_register(const char *name, int width, int height); +void spout_sender_sendframe(void *s, int width, int height, unsigned int id); +void spout_sender_unregister(void *s); #ifdef __cplusplus } From fc7746aaca96e230ef035484086e26736f6818d7 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 15:35:39 +0100 Subject: [PATCH 46/61] SPOUT: move receiver code to capture --- configure.ac | 2 +- src/spout_receiver.cpp | 67 ------------------------------------- src/spout_receiver.h | 58 -------------------------------- src/video_capture/spout.cpp | 27 +++++++++------ 4 files changed, 17 insertions(+), 137 deletions(-) delete mode 100644 src/spout_receiver.cpp delete mode 100644 src/spout_receiver.h diff --git a/configure.ac b/configure.ac index 9e2963ec6..27838004d 100644 --- a/configure.ac +++ b/configure.ac @@ -2895,7 +2895,7 @@ if test $spout_req != no -a "$OPENGL" = yes -a $FOUND_SPOUT = yes then AC_DEFINE([HAVE_SPOUT], [1], [Build with Spout support]) LIBS="$LIBS $SPOUT_LIBS" - OBJS="$OBJS src/spout_receiver.o src/spout_sender.o src/video_capture/spout.o" + OBJS="$OBJS src/spout_sender.o src/video_capture/spout.o" spout=yes fi diff --git a/src/spout_receiver.cpp b/src/spout_receiver.cpp deleted file mode 100644 index 3c280e20c..000000000 --- a/src/spout_receiver.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @file spout_receiver.cpp - * @author Martin Pulec - */ -/* - * Copyright (c) 2018 CESNET, z. s. p. o. - * All rights reserved. - * - * 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. - */ -#include - -#include "spout_receiver.h" - -void *spout_create_receiver(char *name, unsigned int *width, unsigned int *height) { - SpoutReceiver *receiver = new SpoutReceiver; - receiver->CreateReceiver(name, *width, *height); - bool connected; - receiver->CheckReceiver(name, *width, *height, connected); - if (!connected) { - fprintf(stderr, "[SPOUT] Not connected to server '%s'. Is it running?\n", name); - receiver->ReleaseReceiver(); - delete receiver; - return NULL; - } - - return receiver; -} - -bool spout_receiver_recvframe(void *s, char *sender_name, unsigned int width, unsigned int height, char *data, GLenum glFormat) { - return ((SpoutReceiver *)s)->ReceiveImage(sender_name, width, height, (unsigned char *) data, glFormat); -} - -void spout_receiver_delete(void *s) { - if (!s) { - return; - } - ((SpoutReceiver *)s)->ReleaseReceiver(); - delete s; -} - diff --git a/src/spout_receiver.h b/src/spout_receiver.h deleted file mode 100644 index f9be97b07..000000000 --- a/src/spout_receiver.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file spout_receiver.h - * @author Martin Pulec - */ -/* - * Copyright (c) 2018 CESNET, z. s. p. o. - * All rights reserved. - * - * 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 SPOUT_RECEIVER_H_ -#define SPOUT_RECEIVER_H_ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -void *spout_create_receiver(char *name, unsigned int *width, unsigned int *height); -bool spout_receiver_recvframe(void *s, char *sender_name, unsigned int width, unsigned int height, char *data, GLenum glFormat); -void spout_receiver_delete(void *s); - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // SPOUT_RECEIVER_H_ - diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index 6ca96086e..c73de4fb6 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -41,13 +41,14 @@ #include "config_win32.h" #endif +#include "gl_context.h" // it looks like it needs to be included prior to Spout.h + #include +#include #include "debug.h" -#include "gl_context.h" #include "host.h" #include "lib_common.h" -#include "spout_receiver.h" #include "video.h" #include "video_capture.h" @@ -59,7 +60,7 @@ */ struct state_vidcap_spout { struct video_desc desc; - void *spout_state; + SpoutReceiver *spout_state; struct gl_context glc; GLenum gl_format; @@ -139,14 +140,18 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) unsigned int width = 0, height = 0; - s->spout_state = spout_create_receiver(s->server_name, &width, &height); - if (!s->spout_state) { - LOG(LOG_LEVEL_ERROR) << "[SPOUT] Unable to initialize SPOUT state!\n"; + s->spout_state = new SpoutReceiver; + s->spout_state->CreateReceiver(s->server_name, width, height); + bool connected; + s->spout_state->CheckReceiver(s->server_name, width, height, connected); + if (!connected) { + LOG(LOG_LEVEL_ERROR) << "[SPOUT] Not connected to server '" << s->server_name << "'. Is it running?\n"; + s->spout_state->ReleaseReceiver(); + delete s->spout_state; delete s; return VIDCAP_INIT_FAIL; - } else { - LOG(LOG_LEVEL_NOTICE) << "[SPOUT] Initialized successfully - server name: " << s->server_name << ", width: " << width << ", height: " << height << ", fps: " << fps << ", codec: " << get_codec_name_long(codec) << "\n"; } + LOG(LOG_LEVEL_NOTICE) << "[SPOUT] Initialized successfully - server name: " << s->server_name << ", width: " << width << ", height: " << height << ", fps: " << fps << ", codec: " << get_codec_name_long(codec) << "\n"; s->desc = video_desc{width, height, codec, fps, PROGRESSIVE, 1}; @@ -162,7 +167,8 @@ static void vidcap_spout_done(void *state) state_vidcap_spout *s = (state_vidcap_spout *) state; gl_context_make_current(&s->glc); - spout_receiver_delete(s->spout_state); + s->spout_state->ReleaseReceiver(); + delete s->spout_state; destroy_gl_context(&s->glc); delete s; @@ -175,12 +181,11 @@ static struct video_frame *vidcap_spout_grab(void *state, struct audio_frame **a struct video_frame *out = vf_alloc_desc_data(s->desc); out->callbacks.dispose = vf_free; - bool ret; unsigned int width, height; width = s->desc.width; height = s->desc.height; gl_context_make_current(&s->glc); - ret = spout_receiver_recvframe(s->spout_state, s->server_name, width, height, out->tiles[0].data, s->gl_format); + bool ret = s->spout_state->ReceiveImage(s->server_name, width, height, (unsigned char *) out->tiles[0].data, s->gl_format); gl_context_make_current(NULL); if (ret) { // statistics From c085f749472a2e72b42ecc8f8ef2aef311594f16 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 24 Nov 2020 16:15:49 +0100 Subject: [PATCH 47/61] SPOUT: color output --- src/video_capture/spout.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index c73de4fb6..ebbcc10b1 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -44,17 +44,21 @@ #include "gl_context.h" // it looks like it needs to be included prior to Spout.h #include +#include #include #include "debug.h" #include "host.h" #include "lib_common.h" +#include "utils/color_out.h" #include "video.h" #include "video_capture.h" #define DEFAULT_FPS 60.0 #define DEFAULT_CODEC RGB +using std::cout; + /** * Class state_vidcap_spout must be value-initialized */ @@ -73,11 +77,12 @@ struct state_vidcap_spout { static void usage() { - printf("\t-t spout[:name=][:fps=][:codec=]\n"); - printf("\n"); - printf("\tname\n\t\tSPOUT server name\n"); - printf("\tfps\n\t\tFPS count (default: %.2lf)\n", DEFAULT_FPS); - printf("\tcodec\n\t\tvideo codec (default: %s)\n", get_codec_name(DEFAULT_CODEC)); + cout << "Usage:\n"; + cout << "\t" << BOLD(RED("-t spout") << "[:name=][:fps=][:codec=]") << "\n"; + cout << "where\n"; + cout << "\t" << BOLD("name") << "\n\t\tSPOUT server name\n"; + cout << "\t" << BOLD("fps") << "\n\t\tFPS count (default: " << DEFAULT_FPS << ")\n"; + cout << "\t" << BOLD("codec") << "\n\t\tvideo codec (default: " << get_codec_name(DEFAULT_CODEC) << ")\n"; } static int vidcap_spout_init(struct vidcap_params *params, void **state) From 1da8831c1d2036b4a8468722b342805e8474b2a9 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 08:30:43 +0100 Subject: [PATCH 48/61] SPOUT: list servers + option to pass index - use index 0 by default (allows implicit initialization without arguments - "-t spout") --- src/video_capture/spout.cpp | 63 ++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index ebbcc10b1..a63588495 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -43,9 +43,11 @@ #include "gl_context.h" // it looks like it needs to be included prior to Spout.h +#include #include #include #include +#include #include "debug.h" #include "host.h" @@ -57,7 +59,13 @@ #define DEFAULT_FPS 60.0 #define DEFAULT_CODEC RGB +static constexpr const char *MOD_NAME = "[SPOUT] "; + +using std::array; using std::cout; +using std::shared_ptr; +using std::stoi; +using std::string; /** * Class state_vidcap_spout must be value-initialized @@ -78,17 +86,59 @@ struct state_vidcap_spout { static void usage() { cout << "Usage:\n"; - cout << "\t" << BOLD(RED("-t spout") << "[:name=][:fps=][:codec=]") << "\n"; + cout << "\t" << BOLD(RED("-t spout") << "[:name=|device=][:fps=][:codec=]") << "\n"; cout << "where\n"; cout << "\t" << BOLD("name") << "\n\t\tSPOUT server name\n"; cout << "\t" << BOLD("fps") << "\n\t\tFPS count (default: " << DEFAULT_FPS << ")\n"; cout << "\t" << BOLD("codec") << "\n\t\tvideo codec (default: " << get_codec_name(DEFAULT_CODEC) << ")\n"; + cout << "\nServers:\n"; + auto receiver = new SpoutReceiver; + int count = receiver->GetSenderCount(); + + for (int i = 0; i < count; ++i) { + array name{}; + if (!receiver->GetSenderName(i, name.data(), name.size())) { + LOG(LOG_LEVEL_ERROR) << "Cannot get name for server #" << i << "\n"; + continue; + } + unsigned int width = 0; + unsigned int height = 0; + HANDLE dxShareHandle = 0; + DWORD dwFormat = 0; + if (!receiver->GetSenderInfo(name.data(), width, height, dxShareHandle, dwFormat)) { + LOG(LOG_LEVEL_ERROR) << "Cannot get server " << name.data() << "details\n"; + } + cout << "\t" << i << ") " << BOLD(name.data()) << " - width: " << width << ", height: " << height << "\n"; + } + delete receiver; +} + +static string vidcap_spout_get_device_name(int idx) +{ + if (idx < 0) { + LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Negative indices not allowed, given: " << idx << "\n"; + return {}; + } + auto receiver = shared_ptr(new SpoutReceiver); + int count = receiver->GetSenderCount(); + if (idx >= count) { + LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot find server #" << idx << " (total count " << count << ")!\n"; + return {}; + } + + array name{}; + if (!receiver->GetSenderName(idx, name.data(), name.size())) { + LOG(LOG_LEVEL_ERROR) << "Cannot get name for server #" << idx << "\n"; + return {}; + } + return name.data(); } static int vidcap_spout_init(struct vidcap_params *params, void **state) { state_vidcap_spout *s = new state_vidcap_spout(); + int device_idx = 0; double fps = DEFAULT_FPS; codec_t codec = DEFAULT_CODEC; @@ -102,6 +152,8 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) usage(); ret = VIDCAP_INIT_NOERR; break; + } else if (strstr(item, "device=") == item) { + device_idx = stoi(item + strlen("device=")); } else if (strstr(item, "name=") == item) { strncpy(s->server_name, item + strlen("name="), sizeof(s->server_name) - 1); } else if (strstr(item, "fps=") == item) { @@ -136,6 +188,15 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) return VIDCAP_INIT_FAIL; } + if (strlen(s->server_name) == 0) { + const string &name = vidcap_spout_get_device_name(device_idx); + if (name.empty()) { + delete s; + return VIDCAP_INIT_FAIL; + } + strncpy(s->server_name, name.c_str(), sizeof s->server_name - 1); + } + if (!init_gl_context(&s->glc, GL_CONTEXT_ANY)) { LOG(LOG_LEVEL_ERROR) << "[SPOUT] Unable to initialize GL context!\n"; delete s; From c42d6c5bbda7a658e71e0de3aaed878012847e2d Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 09:43:07 +0100 Subject: [PATCH 49/61] GUI: AJA, DELTA, NDI, Syphon, XIMEA - pass opaque cfg This partially reverts f8d9ac9d. Device configuration is now passed as an opaques string rather than to be decorated by GUI (prepended "device=" etc.). --- gui/QT/option/settings.cpp | 14 +++++++------- src/video_capture/aja.cpp | 2 +- src/video_capture/deltacast.cpp | 2 +- src/video_capture/deltacast_dvi.cpp | 2 +- src/video_capture/ndi.cpp | 2 +- src/video_capture/syphon.mm | 2 +- src/video_capture/ximea.c | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gui/QT/option/settings.cpp b/gui/QT/option/settings.cpp index c9cf0f783..b1301bc90 100644 --- a/gui/QT/option/settings.cpp +++ b/gui/QT/option/settings.cpp @@ -214,14 +214,14 @@ const static struct{ {"preview", Option::BoolOpt, "", "t", true, "", ""}, {"vuMeter", Option::BoolOpt, "", "t", true, "", ""}, {"errors_fatal", Option::BoolOpt, " --param errors-fatal", "t", true, "", ""}, - {"aja.device", Option::StringOpt, ":device=", "", false, "video.source", "aja"}, - {"bluefish444.device", Option::StringOpt, ":device=", "", false, "video.source", "bluefish444"}, - {"deltacast.device", Option::StringOpt, ":device=", "", false, "video.source", "deltacast"}, - {"deltacast-dv.device", Option::StringOpt, ":device=", "", false, "video.source", "deltacast-dv"}, + {"aja.device", Option::StringOpt, ":", "", false, "video.source", "aja"}, + {"bluefish444.device", Option::StringOpt, ":", "", false, "video.source", "bluefish444"}, + {"deltacast.device", Option::StringOpt, ":", "", false, "video.source", "deltacast"}, + {"deltacast-dv.device", Option::StringOpt, ":", "", false, "video.source", "deltacast-dv"}, {"dvs.device", Option::StringOpt, ":", "", false, "video.source", "dvs"}, - {"ndi.device", Option::StringOpt, ":url=", "", false, "video.source", "ndi"}, - {"syphon.device", Option::StringOpt, ":app=", "", false, "video.source", "syphon"}, - {"ximea.device", Option::StringOpt, ":device=", "", false, "video.source", "ximea"}, + {"ndi.device", Option::StringOpt, ":", "", false, "video.source", "ndi"}, + {"syphon.device", Option::StringOpt, ":", "", false, "video.source", "syphon"}, + {"ximea.device", Option::StringOpt, ":", "", false, "video.source", "ximea"}, }; const struct { diff --git a/src/video_capture/aja.cpp b/src/video_capture/aja.cpp index c1bbd9515..ac6bff5e0 100644 --- a/src/video_capture/aja.cpp +++ b/src/video_capture/aja.cpp @@ -1131,7 +1131,7 @@ LINK_SPEC struct vidcap_type *vidcap_aja_probe(bool verbose, void (**deleter)(vo realloc(vt->cards, vt->card_count * sizeof(struct device_info)); memset(&vt->cards[vt->card_count - 1], 0, sizeof(struct device_info)); snprintf(vt->cards[vt->card_count - 1].id, sizeof vt->cards[vt->card_count - 1].id, - "%d", i); + "device=%d", i); snprintf(vt->cards[vt->card_count - 1].name, sizeof vt->cards[vt->card_count - 1].name, "AJA %s", info.deviceIdentifier.c_str()); } diff --git a/src/video_capture/deltacast.cpp b/src/video_capture/deltacast.cpp index 4a8ec10f2..eff683470 100644 --- a/src/video_capture/deltacast.cpp +++ b/src/video_capture/deltacast.cpp @@ -139,7 +139,7 @@ vidcap_deltacast_probe(bool verbose, void (**deleter)(void *)) vt->cards = (struct device_info *) calloc(NbBoards, sizeof(struct device_info)); vt->card_count = NbBoards; for (ULONG i = 0; i < NbBoards; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%" PRIu32, i); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST SDI board %" PRIu32, i); } } diff --git a/src/video_capture/deltacast_dvi.cpp b/src/video_capture/deltacast_dvi.cpp index f8ca24dcc..47bf77f7e 100644 --- a/src/video_capture/deltacast_dvi.cpp +++ b/src/video_capture/deltacast_dvi.cpp @@ -199,7 +199,7 @@ vidcap_deltacast_dvi_probe(bool verbose, void (**deleter)(void *)) } VHD_CloseBoardHandle(BoardHandle); - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%" PRIu32, i); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%" PRIu32, i); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "DELTACAST %s #%" PRIu32, board.c_str(), i); } diff --git a/src/video_capture/ndi.cpp b/src/video_capture/ndi.cpp index 0bbea31af..29eafc7b7 100644 --- a/src/video_capture/ndi.cpp +++ b/src/video_capture/ndi.cpp @@ -443,7 +443,7 @@ static struct vidcap_type *vidcap_ndi_probe(bool verbose, void (**deleter)(void } vt->card_count = nr_sources; for (int i = 0; i < static_cast(nr_sources); ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%s", p_sources[i].p_url_address); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "url=%s", p_sources[i].p_url_address); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "%s", p_sources[i].p_ndi_name); vt->cards[i].repeatable = true; } diff --git a/src/video_capture/syphon.mm b/src/video_capture/syphon.mm index fd7d75c7d..a840f0180 100644 --- a/src/video_capture/syphon.mm +++ b/src/video_capture/syphon.mm @@ -474,7 +474,7 @@ static void probe_devices_callback(state_vidcap_syphon *s) s->probed_devices = (struct device_info *) realloc(s->probed_devices, s->probed_devices_count * sizeof(struct device_info)); memset(&s->probed_devices[s->probed_devices_count - 1], 0, sizeof(struct device_info)); snprintf(s->probed_devices[s->probed_devices_count - 1].id, sizeof s->probed_devices[s->probed_devices_count - 1].id, - "%s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); + "app=%s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); snprintf(s->probed_devices[s->probed_devices_count - 1].name, sizeof s->probed_devices[s->probed_devices_count - 1].name, "Syphon %s", [[item objectForKey:@"SyphonServerDescriptionAppNameKey"] UTF8String]); } diff --git a/src/video_capture/ximea.c b/src/video_capture/ximea.c index 108d2ad47..e187f46cf 100644 --- a/src/video_capture/ximea.c +++ b/src/video_capture/ximea.c @@ -351,7 +351,7 @@ static struct vidcap_type *vidcap_ximea_probe(bool verbose, void (**deleter)(voi vt->cards = calloc(count, sizeof(struct device_info)); for (DWORD i = 0; i < count; ++i) { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "%d", (int) i); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%d", (int) i); char name[256]; if (funcs.xiGetDeviceInfoString(i, XI_PRM_DEVICE_NAME, name, sizeof name) == XI_OK) { strncpy(vt->cards[i].name, name, sizeof vt->cards[i].name); From 51b05f5a3142c7cc3365da05cead43dd7d0e5494 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 10:10:12 +0100 Subject: [PATCH 50/61] SPOUT: use shared_ptrs for SpoutReceiver instances --- src/video_capture/spout.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index a63588495..ed748cd0c 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -72,7 +72,7 @@ using std::string; */ struct state_vidcap_spout { struct video_desc desc; - SpoutReceiver *spout_state; + shared_ptr spout_state; struct gl_context glc; GLenum gl_format; @@ -92,7 +92,7 @@ static void usage() cout << "\t" << BOLD("fps") << "\n\t\tFPS count (default: " << DEFAULT_FPS << ")\n"; cout << "\t" << BOLD("codec") << "\n\t\tvideo codec (default: " << get_codec_name(DEFAULT_CODEC) << ")\n"; cout << "\nServers:\n"; - auto receiver = new SpoutReceiver; + auto receiver = shared_ptr(new SpoutReceiver); int count = receiver->GetSenderCount(); for (int i = 0; i < count; ++i) { @@ -110,7 +110,6 @@ static void usage() } cout << "\t" << i << ") " << BOLD(name.data()) << " - width: " << width << ", height: " << height << "\n"; } - delete receiver; } static string vidcap_spout_get_device_name(int idx) @@ -206,14 +205,13 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) unsigned int width = 0, height = 0; - s->spout_state = new SpoutReceiver; + s->spout_state = shared_ptr(new SpoutReceiver); s->spout_state->CreateReceiver(s->server_name, width, height); bool connected; s->spout_state->CheckReceiver(s->server_name, width, height, connected); if (!connected) { LOG(LOG_LEVEL_ERROR) << "[SPOUT] Not connected to server '" << s->server_name << "'. Is it running?\n"; s->spout_state->ReleaseReceiver(); - delete s->spout_state; delete s; return VIDCAP_INIT_FAIL; } @@ -234,7 +232,6 @@ static void vidcap_spout_done(void *state) gl_context_make_current(&s->glc); s->spout_state->ReleaseReceiver(); - delete s->spout_state; destroy_gl_context(&s->glc); delete s; From 13364faa5f8db8add5fc4f0a2742bd73e97e6c5e Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 10:36:54 +0100 Subject: [PATCH 51/61] CI: build SPOUT dynamically To avoid having to specify dependencies in configure. --- .github/scripts/Windows/prepare_msys.sh | 5 +++-- configure.ac | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index 28d8e3ba3..226df5b53 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -68,9 +68,10 @@ cd - git clone --depth 1 https://github.com/leadedge/Spout2.git mkdir Spout2/SpoutSDK/Source/build cd Spout2/SpoutSDK/Source/build -cmake -G 'MSYS Makefiles' .. +cmake -DBUILD_SHARED_LIBS=ON -G 'MSYS Makefiles' .. cmake --build . -cp libSpout.a /usr/local/lib +cp libSpout.dll /usr/local/bin +cp libSpout.dll.a /usr/local/lib cd - mkdir /usr/local/include/SpoutSDK cp Spout2/SpoutSDK/Source/*.h /usr/local/include/SpoutSDK diff --git a/configure.ac b/configure.ac index 27838004d..2499dce05 100644 --- a/configure.ac +++ b/configure.ac @@ -2882,8 +2882,7 @@ if test $system = Windows then AC_LANG_PUSH([C++]) SAVED_LIBS="$LIBS" - SPOUT_LIBS="-lSpout -lgdi32 -lDXGI -lVersion -ld3d9 -ld3d11 -lopengl32 -lShlwapi" - LIBS="$LIBS $SPOUT_LIBS" + LIBS="$LIBS -lSpout" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[SpoutSender *sender = new SpoutSender;]])], FOUND_SPOUT=yes, FOUND_SPOUT=no) @@ -2894,7 +2893,7 @@ fi if test $spout_req != no -a "$OPENGL" = yes -a $FOUND_SPOUT = yes then AC_DEFINE([HAVE_SPOUT], [1], [Build with Spout support]) - LIBS="$LIBS $SPOUT_LIBS" + LIBS="$LIBS -lSpout" OBJS="$OBJS src/spout_sender.o src/video_capture/spout.o" spout=yes fi From 7a3b7a9a7a3817bbdd3ce871f24ae3ce01da6a2b Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 13:29:07 +0100 Subject: [PATCH 52/61] SPOUT: probe devices --- src/video_capture/spout.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index ed748cd0c..367255794 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -288,6 +288,32 @@ static struct vidcap_type *vidcap_spout_probe(bool verbose, void (**deleter)(voi vt->name = "spout"; vt->description = "SPOUT capture client"; } + + if (!verbose) { + return vt; + } + + auto receiver = shared_ptr(new SpoutReceiver); + int count = receiver->GetSenderCount(); + + vt->cards = (struct device_info *) calloc(count, sizeof(struct device_info)); + if (vt->cards == nullptr) { + return vt; + } + vt->card_count = count; + + for (int i = 0; i < count; ++i) { + array name{}; + if (!receiver->GetSenderName(i, name.data(), name.size())) { + LOG(LOG_LEVEL_VERBOSE) << MOD_NAME << "Cannot get name for server #" << i << "\n"; + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%d", i); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT #%d", i); + } else { + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name=", name.data()); + snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT %s", name.data()); + } + vt->cards[i].repeatable = true; + } return vt; } From 5565c569e43a46745b43ea2c5a2e7c8a07a58f10 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 13:54:30 +0100 Subject: [PATCH 53/61] SPOUT: escape potentially harmful symbols for probe Escape potentially harmful symbols for probe (eg. space would make problems when passed to GUI). --- src/utils/misc.c | 85 +++++++++++++++++++++++++++++++++++++ src/utils/misc.h | 7 +++ src/video_capture/spout.cpp | 16 ++++++- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/utils/misc.c b/src/utils/misc.c index 88d769000..29cda702f 100644 --- a/src/utils/misc.c +++ b/src/utils/misc.c @@ -183,3 +183,88 @@ void replace_all(char *in, const char *from, const char *to) { } } +/** + * Replaces all occurences where eval() evaluates to true with %-encoding + * @param in input + * @param out output array + * @param max_len maximal lenght to be written (including terminating NUL) + * @param eval predictor if an input character should be replaced (functions + * from ctype.h may be used) + * @returns bytes written to out + * + * @note + * Symbol ' ' is not treated specially (unlike in classic URL encoding which + * translates it to '+'. + */ +size_t urlencode(char *out, size_t max_len, const char *in, int (*eval)(int c)) +{ + if (max_len == 0 || max_len >= INT_MAX) { // prevent overflow + return 0; + } + size_t len = 0; + while (*in && len < max_len - 1) { + if (eval(*in) != 0) { + *out++ = *in++; + len++; + } else { + if ((int) len < (int) max_len - 3 - 1) { + int ret = sprintf(out, "%%%02X", *in++); + out += ret; + len += ret; + } else { + break; + } + } + } + *out = '\0'; + len++; + + return len; +} + +static inline int ishex(int x) +{ + return (x >= '0' && x <= '9') || + (x >= 'a' && x <= 'f') || + (x >= 'A' && x <= 'F'); +} + +/** + * URL decodes input string (replaces all "%XX" sequences with ASCII representation of 0xXX) + * @param in input + * @param out output array + * @param max_len maximal lenght to be written (including terminating NUL) + * @returns bytes written, 0 on error + * + * @note + * Symbol '+' is not treated specially (unlike in classic URL decoding which + * translates it to ' '. + */ +size_t urldecode(char *out, size_t max_len, const char *in) +{ + if (max_len == 0) { // avoid (uint) -1 cast + return 0; + } + size_t len = 0; + while (*in && len < max_len - 1) { + if (*in != '%') { + *out++ = *in++; + } else { + in++; // skip '%' + if (!ishex(in[0]) || !ishex(in[1])) { + return 0; + } + unsigned int c = 0; + if (sscanf(in, "%2x", &c) != 1) { + return 0; + } + *out++ = c; + in += 2; + } + len++; + } + *out = '\0'; + len++; + + return len; +} diff --git a/src/utils/misc.h b/src/utils/misc.h index ce85aefa2..0003e3b24 100644 --- a/src/utils/misc.h +++ b/src/utils/misc.h @@ -38,7 +38,12 @@ #ifndef UTILS_MISC_H_ #define UTILS_MISC_H_ +#ifdef __cplusplus +#include +#else #include +#include +#endif #ifdef __cplusplus extern "C" { @@ -52,6 +57,8 @@ int get_framerate_d(double framerate); #define DELDEL "\177\177" #define ESCAPED_COLON "\\:" void replace_all(char *in, const char *from, const char *to); +size_t urlencode(char *out, size_t max_len, const char *in, int (*eval)(int c)); +size_t urldecode(char *out, size_t max_len, const char *in); /** * @brief Creates FourCC word diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index 367255794..bc0cc32cb 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -44,6 +44,7 @@ #include "gl_context.h" // it looks like it needs to be included prior to Spout.h #include +#include #include #include #include @@ -53,6 +54,7 @@ #include "host.h" #include "lib_common.h" #include "utils/color_out.h" +#include "utils/misc.h" // urlencode, urldecode #include "video.h" #include "video_capture.h" @@ -109,6 +111,9 @@ static void usage() LOG(LOG_LEVEL_ERROR) << "Cannot get server " << name.data() << "details\n"; } cout << "\t" << i << ") " << BOLD(name.data()) << " - width: " << width << ", height: " << height << "\n"; + if (strchr(name.data(), '%') != nullptr) { + LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Percent sign (\"%\") present in name - replace with \"%25\" in specification.\n"; + } } } @@ -154,7 +159,11 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) } else if (strstr(item, "device=") == item) { device_idx = stoi(item + strlen("device=")); } else if (strstr(item, "name=") == item) { - strncpy(s->server_name, item + strlen("name="), sizeof(s->server_name) - 1); + if (urldecode(s->server_name, sizeof s->server_name, item + strlen("name=")) == 0) { + LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Improperly formatted name: " << item + strlen("name=") << "\n"; + LOG(LOG_LEVEL_INFO) << MOD_NAME << "Interpreting name literally. If server name contains a percent sign (\"%\"), replace with \"%25\"\n"; + strncpy(s->server_name, item + strlen("name="), sizeof(s->server_name) - 1); + } } else if (strstr(item, "fps=") == item) { fps = atof(item + strlen("fps=")); } else if (strstr(item, "codec=") == item) { @@ -309,7 +318,10 @@ static struct vidcap_type *vidcap_spout_probe(bool verbose, void (**deleter)(voi snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%d", i); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT #%d", i); } else { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name=", name.data()); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name="); + urlencode(vt->cards[i].id + strlen(vt->cards[i].id), + sizeof vt->cards[i].id - strlen(vt->cards[i].id), + name.data(), isalnum); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT %s", name.data()); } vt->cards[i].repeatable = true; From 9320d8e9333a0d7f4e18d6af81bf6c945dd9ffd6 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 14:44:46 +0100 Subject: [PATCH 54/61] GUI: unwhitelist SPOUT - now reports servers * pass configuration string * remove SPOUT and Syphon from list of devices supporting audio (included by mistake) --- gui/QT/option/audio_opts.cpp | 2 -- gui/QT/option/settings.cpp | 1 + gui/QT/option/video_opts.cpp | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/gui/QT/option/audio_opts.cpp b/gui/QT/option/audio_opts.cpp index 7f4b69df1..df55c97c2 100644 --- a/gui/QT/option/audio_opts.cpp +++ b/gui/QT/option/audio_opts.cpp @@ -30,8 +30,6 @@ const char * const sdiAudioCards[] = { "dvs", "deltacast", "ndi", - "spout", - "syphon", }; const char * const sdiAudio[] = { diff --git a/gui/QT/option/settings.cpp b/gui/QT/option/settings.cpp index b1301bc90..5b0cd05fc 100644 --- a/gui/QT/option/settings.cpp +++ b/gui/QT/option/settings.cpp @@ -220,6 +220,7 @@ const static struct{ {"deltacast-dv.device", Option::StringOpt, ":", "", false, "video.source", "deltacast-dv"}, {"dvs.device", Option::StringOpt, ":", "", false, "video.source", "dvs"}, {"ndi.device", Option::StringOpt, ":", "", false, "video.source", "ndi"}, + {"spout.device", Option::StringOpt, ":", "", false, "video.source", "spout"}, {"syphon.device", Option::StringOpt, ":", "", false, "video.source", "syphon"}, {"ximea.device", Option::StringOpt, ":", "", false, "video.source", "ximea"}, }; diff --git a/gui/QT/option/video_opts.cpp b/gui/QT/option/video_opts.cpp index a349244b3..830c9bde1 100644 --- a/gui/QT/option/video_opts.cpp +++ b/gui/QT/option/video_opts.cpp @@ -7,7 +7,6 @@ std::vector getVideoSrc(AvailableSettings *availSettings){ const char * const whiteList[] = { "bitflow", - "spout", }; const std::string optStr = "video.source"; From a2bf03a47898db089087d1930fb67f4a3ca48716 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Wed, 25 Nov 2020 14:50:19 +0100 Subject: [PATCH 55/61] Include Speex COPYING directly in UG COPYRIGHT --- COPYRIGHT | 37 +++++++++++++++++++++++++++++++++++++ Makefile.in | 2 -- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index bf3e3a757..d19de2864 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -91,3 +91,40 @@ 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. +### Speex + +Copyright 2002-2008 Xiph.org Foundation +Copyright 2002-2008 Jean-Marc Valin +Copyright 2005-2007 Analog Devices Inc. +Copyright 2005-2008 Commonwealth Scientific and Industrial Research + Organisation (CSIRO) +Copyright 1993, 2002, 2006 David Rowe +Copyright 2003 EpicGames +Copyright 1992-1994 Jutta Degener, Carsten Bormann + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- 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. + +- Neither the name of the Xiph.org Foundation 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 COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS 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 FOUNDATION 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. diff --git a/Makefile.in b/Makefile.in index 6a74f77c6..12e4bc78c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -680,7 +680,6 @@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(docdir) $(INSTALL) -m 644 $(DOCS) $(DESTDIR)$(docdir) $(INSTALL) -m 644 $(srcdir)/CONTRIBUTING.md $(srcdir)/COPYRIGHT $(srcdir)/INSTALL $(srcdir)/NEWS $(srcdir)/README.md $(DESTDIR)$(docdir) - $(CP) $(srcdir)/speex-1.2rc1/COPYING $(DESTDIR)$(docdir)/COPYING.speex $(INSTALL) -m 755 $(srcdir)/data/ultragrid-bugreport-collect.sh $(DESTDIR)$(docdir) $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) $(INSTALL) -m 644 $(srcdir)/data/uv.1 $(srcdir)/data/hd-rum-transcode.1 $(DESTDIR)$(man1dir) @@ -692,7 +691,6 @@ uninstall: if [ -n "@MODULES@" ]; then for n in @MODULES@; do $(RM) $(DESTDIR)$(libdir)/ultragrid/`basename $$n`; done; fi for n in $(DOCS); do $(RM) $(DESTDIR)$(docdir)$$n; done; $(RM) $(DESTDIR)$(docdir)/CONTRIBUTING.md $(DESTDIR)$(docdir)/COPYRIGHT $(DESTDIR)$(docdir)/INSTALL $(DESTDIR)$(docdir)/NEWS $(DESTDIR)$(docdir)/README.md - $(RM) $(DESTDIR)$(docdir)/COPYING.speex $(RM) $(DESTDIR)$(docdir)/ultragrid-bugreport-collect.sh if [ -f "$(GUI_TARGET)" ]; then \ $(RM) $(DESTDIR)$(bindir)/`basename $(GUI_TARGET)`;\ From 294c5fd66f5adf2400675a30b2e6ef97a4b661b5 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 26 Nov 2020 08:31:00 +0100 Subject: [PATCH 56/61] SPOUT: prefix URL encoded name with urlencoded= --- src/video_capture/spout.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index bc0cc32cb..dcbea8c2c 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -111,9 +111,6 @@ static void usage() LOG(LOG_LEVEL_ERROR) << "Cannot get server " << name.data() << "details\n"; } cout << "\t" << i << ") " << BOLD(name.data()) << " - width: " << width << ", height: " << height << "\n"; - if (strchr(name.data(), '%') != nullptr) { - LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Percent sign (\"%\") present in name - replace with \"%25\" in specification.\n"; - } } } @@ -159,9 +156,14 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) } else if (strstr(item, "device=") == item) { device_idx = stoi(item + strlen("device=")); } else if (strstr(item, "name=") == item) { - if (urldecode(s->server_name, sizeof s->server_name, item + strlen("name=")) == 0) { - LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Improperly formatted name: " << item + strlen("name=") << "\n"; - LOG(LOG_LEVEL_INFO) << MOD_NAME << "Interpreting name literally. If server name contains a percent sign (\"%\"), replace with \"%25\"\n"; + char *name = item + strlen("name="); + if (strstr(name, "urlencoded=") == name) { + name += "urlencoded="; + if (urldecode(s->server_name, sizeof s->server_name, name) == 0) { + LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Improperly formatted name: " << item + strlen("name=") << "\n"; + ret = VIDCAP_INIT_FAIL; + } + } else { strncpy(s->server_name, item + strlen("name="), sizeof(s->server_name) - 1); } } else if (strstr(item, "fps=") == item) { @@ -318,7 +320,7 @@ static struct vidcap_type *vidcap_spout_probe(bool verbose, void (**deleter)(voi snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "device=%d", i); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT #%d", i); } else { - snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name="); + snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name=urlencoded="); urlencode(vt->cards[i].id + strlen(vt->cards[i].id), sizeof vt->cards[i].id - strlen(vt->cards[i].id), name.data(), isalnum); From c50a0384ea1363a81c5c9e0b7517e879e0679330 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 26 Nov 2020 08:46:02 +0100 Subject: [PATCH 57/61] URL encode according to RFC 3986 + option for HTML 5 encoding --- src/utils/misc.c | 37 +++++++++++++++++++++++++++++-------- src/utils/misc.h | 5 ++++- src/video_capture/spout.cpp | 4 ++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/utils/misc.c b/src/utils/misc.c index 29cda702f..ad625a40f 100644 --- a/src/utils/misc.c +++ b/src/utils/misc.c @@ -183,27 +183,45 @@ void replace_all(char *in, const char *from, const char *to) { } } +int urlencode_html5_eval(int c) +{ + return isalnum(c) || c == '*' || c == '-' || c == '.' || c == '_'; +} + +int urlencode_rfc3986_eval(int c) +{ + return isalnum(c) || c == '~' || c == '-' || c == '.' || c == '_'; +} + /** * Replaces all occurences where eval() evaluates to true with %-encoding - * @param in input - * @param out output array - * @param max_len maximal lenght to be written (including terminating NUL) - * @param eval predictor if an input character should be replaced (functions - * from ctype.h may be used) + * @param in input + * @param out output array + * @param max_len maximal lenght to be written (including terminating NUL) + * @param eval_pass predictor if an input character should be kept (functions + * from ctype.h may be used) + * @param space_plus_replace replace spaces (' ') with ASCII plus sign - + * should be true for HTML5 URL encoding, false for RFC 3986 * @returns bytes written to out * * @note * Symbol ' ' is not treated specially (unlike in classic URL encoding which * translates it to '+'. + * @todo + * There may be a LUT as in https://rosettacode.org/wiki/URL_encoding#C */ -size_t urlencode(char *out, size_t max_len, const char *in, int (*eval)(int c)) +size_t urlencode(char *out, size_t max_len, const char *in, int (*eval_pass)(int c), + bool space_plus_replace) { if (max_len == 0 || max_len >= INT_MAX) { // prevent overflow return 0; } size_t len = 0; while (*in && len < max_len - 1) { - if (eval(*in) != 0) { + if (*in == ' ' && space_plus_replace) { + *out++ = '+'; + in++; + } else if (eval_pass(*in) != 0) { *out++ = *in++; len++; } else { @@ -247,7 +265,10 @@ size_t urldecode(char *out, size_t max_len, const char *in) } size_t len = 0; while (*in && len < max_len - 1) { - if (*in != '%') { + if (*in == '+') { + *out++ = ' '; + in++; + } else if (*in != '%') { *out++ = *in++; } else { in++; // skip '%' diff --git a/src/utils/misc.h b/src/utils/misc.h index 0003e3b24..76aa2eda4 100644 --- a/src/utils/misc.h +++ b/src/utils/misc.h @@ -57,7 +57,10 @@ int get_framerate_d(double framerate); #define DELDEL "\177\177" #define ESCAPED_COLON "\\:" void replace_all(char *in, const char *from, const char *to); -size_t urlencode(char *out, size_t max_len, const char *in, int (*eval)(int c)); + +int urlencode_html5_eval(int c); +int urlencode_rfc3986_eval(int c); +size_t urlencode(char *out, size_t max_len, const char *in, int (*eval_pass)(int c), bool space_plus_replace); size_t urldecode(char *out, size_t max_len, const char *in); /** diff --git a/src/video_capture/spout.cpp b/src/video_capture/spout.cpp index dcbea8c2c..ffe9bcb2a 100644 --- a/src/video_capture/spout.cpp +++ b/src/video_capture/spout.cpp @@ -158,7 +158,7 @@ static int vidcap_spout_init(struct vidcap_params *params, void **state) } else if (strstr(item, "name=") == item) { char *name = item + strlen("name="); if (strstr(name, "urlencoded=") == name) { - name += "urlencoded="; + name += strlen("urlencoded="); if (urldecode(s->server_name, sizeof s->server_name, name) == 0) { LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Improperly formatted name: " << item + strlen("name=") << "\n"; ret = VIDCAP_INIT_FAIL; @@ -323,7 +323,7 @@ static struct vidcap_type *vidcap_spout_probe(bool verbose, void (**deleter)(voi snprintf(vt->cards[i].id, sizeof vt->cards[i].id, "name=urlencoded="); urlencode(vt->cards[i].id + strlen(vt->cards[i].id), sizeof vt->cards[i].id - strlen(vt->cards[i].id), - name.data(), isalnum); + name.data(), urlencode_rfc3986_eval, false); snprintf(vt->cards[i].name, sizeof vt->cards[i].name, "SPOUT %s", name.data()); } vt->cards[i].repeatable = true; From 9867242252316ede445b9c4a0a9d039b575bb8d3 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Thu, 26 Nov 2020 15:42:23 +0100 Subject: [PATCH 58/61] SDL2: added dummy audio callbacks To avoid NULL pointers in callback structure. Not sure if needed, anyway, however also other modules do have dummy callbacks there. --- src/video_display/sdl2.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/video_display/sdl2.cpp b/src/video_display/sdl2.cpp index 53afd522d..96be70270 100644 --- a/src/video_display/sdl2.cpp +++ b/src/video_display/sdl2.cpp @@ -744,6 +744,16 @@ static void display_sdl2_new_message(struct module *mod) SDL_PushEvent(&event); } +static void display_sdl2_put_audio_frame([[maybe_unused]] void *state, [[maybe_unused]] struct audio_frame *frame) +{ +} + +static int display_sdl2_reconfigure_audio([[maybe_unused]] void *state, [[maybe_unused]] int quant_samples, + [[maybe_unused]] int channels, [[maybe_unused]] int sample_rate) +{ + return FALSE; +} + static const struct video_display_info display_sdl2_info = { [](struct device_info **available_cards, int *count, void (**deleter)(void *)) { UNUSED(deleter); @@ -760,8 +770,8 @@ static const struct video_display_info display_sdl2_info = { display_sdl2_putf, display_sdl2_reconfigure, display_sdl2_get_property, - NULL, - NULL, + display_sdl2_put_audio_frame, + display_sdl2_reconfigure_audio, DISPLAY_NEEDS_MAINLOOP, }; From e8e4c507f3c5a766755c938db1e8712844c87bd8 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Sat, 28 Nov 2020 10:08:31 +0100 Subject: [PATCH 59/61] GUI: Show audio meter even when no playback device selected --- gui/QT/option/settings.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gui/QT/option/settings.cpp b/gui/QT/option/settings.cpp index 5b0cd05fc..f73e8f90f 100644 --- a/gui/QT/option/settings.cpp +++ b/gui/QT/option/settings.cpp @@ -284,7 +284,11 @@ std::string Settings::getLaunchParams() const{ out += getOption("audio.source").getLaunchOption(); out += getOption("audio.source.channels").getLaunchOption(); out += getOption("audio.compress").getLaunchOption(); - out += getOption("audio.playback").getLaunchOption(); + std::string audioPlay = getOption("audio.playback").getLaunchOption(); + if(audioPlay.empty() && getOption("preview").isEnabled()){ + audioPlay = " -r dummy"; + } + out += audioPlay; out += getOption("network.fec").getLaunchOption(); out += getOption("network.port").getLaunchOption(); out += getOption("network.control_port").getLaunchOption(); From 3d31d5447a7ee18026558f3f92329af0f8bbd0a0 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Fri, 27 Nov 2020 11:51:42 +0100 Subject: [PATCH 60/61] GitHub CI: added /usr/local/share/pkgconfig (GPUJPEG Win) --- .github/scripts/Windows/prepare_msys.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index 226df5b53..cf72ce923 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -6,7 +6,7 @@ mkdir -p /usr/local/lib /usr/local/bin /usr/local/include cat >> ~/.bash_profile <<'EOF' export PATH=/mingw64/bin:/usr/local/bin:$PATH export CPATH=/usr/local/include -export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/mingw64/lib/pkgconfig +export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/mingw64/lib/pkgconfig export LIBRARY_PATH=/usr/local/lib CUDA_D=$(ls -d /c/Program\ Files/NVIDIA\ GPU\ Computing\ Toolkit/CUDA/*) From 11a2a9f8767fc8b98a0ffb46ff304e5ea20c7e9f Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 30 Nov 2020 10:10:37 +0100 Subject: [PATCH 61/61] GitHub CI: SPOUT install to separate script The script can be referenced alone in the wiki. --- .github/scripts/Windows/install_spout.sh | 15 +++++++++++++++ .github/scripts/Windows/prepare_msys.sh | 13 +------------ 2 files changed, 16 insertions(+), 12 deletions(-) create mode 100755 .github/scripts/Windows/install_spout.sh diff --git a/.github/scripts/Windows/install_spout.sh b/.github/scripts/Windows/install_spout.sh new file mode 100755 index 000000000..dff5bc204 --- /dev/null +++ b/.github/scripts/Windows/install_spout.sh @@ -0,0 +1,15 @@ +#!/bin/sh -eux +# Install SPOUT + +git clone --depth 1 https://github.com/leadedge/Spout2.git +mkdir Spout2/SpoutSDK/Source/build +cd Spout2/SpoutSDK/Source/build +cmake -DBUILD_SHARED_LIBS=ON -G 'MSYS Makefiles' .. +cmake --build . +cp libSpout.dll /usr/local/bin +cp libSpout.dll.a /usr/local/lib +cd - +mkdir /usr/local/include/SpoutSDK +cp Spout2/SpoutSDK/Source/*.h /usr/local/include/SpoutSDK +rm -rf Spout2 + diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index cf72ce923..16be551b2 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -64,18 +64,7 @@ cd /c/live555 make install cd - -# Install SPOUT -git clone --depth 1 https://github.com/leadedge/Spout2.git -mkdir Spout2/SpoutSDK/Source/build -cd Spout2/SpoutSDK/Source/build -cmake -DBUILD_SHARED_LIBS=ON -G 'MSYS Makefiles' .. -cmake --build . -cp libSpout.dll /usr/local/bin -cp libSpout.dll.a /usr/local/lib -cd - -mkdir /usr/local/include/SpoutSDK -cp Spout2/SpoutSDK/Source/*.h /usr/local/include/SpoutSDK -rm -rf Spout2 +$GITHUB_WORKSPACE/.github/scripts/Windows/install_spout.sh # Install FFMPEG wget --no-verbose https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z && 7z x ffmpeg-release-full-shared.7z && cp -r ffmpeg-*build-shared/{bin,lib,include} /usr/local && rm -rf ffmpeg-* || exit 1