/*
 * FILE:    main.cpp
 * AUTHORS: Colin Perkins    <csp@csperkins.org>
 *          Ladan Gharai     <ladan@isi.edu>
 *          Martin Benes     <martinbenesh@gmail.com>
 *          Lukas Hejtmanek  <xhejtman@ics.muni.cz>
 *          Petr Holub       <hopet@ics.muni.cz>
 *          Milos Liska      <xliska@fi.muni.cz>
 *          Jiri Matela      <matela@ics.muni.cz>
 *          Dalibor Matura   <255899@mail.muni.cz>
 *          Ian Wesley-Smith <iwsmith@cct.lsu.edu>
 *          David Cassany    <david.cassany@i2cat.net>
 *          Ignacio Contreras <ignacio.contreras@i2cat.net>
 *          Gerard Castillo  <gerard.castillo@i2cat.net>
 *          Martin Pulec     <pulec@cesnet.cz>
 *
 * Copyright (c) 2005-2021 CESNET z.s.p.o.
 * Copyright (c) 2005-2014 Fundació i2CAT, Internet I Innovació Digital a Catalunya
 * Copyright (c) 2001-2004 University of Southern California
 * Copyright (c) 2003-2004 University of Glasgow
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *
 *      This product includes software developed by the University of Southern
 *      California Information Sciences Institute. This product also includes
 *      software developed by CESNET z.s.p.o.
 *
 * 4. Neither the name of the University nor of the Institute 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"
#include "config_unix.h"
#include "config_win32.h"
#endif // HAVE_CONFIG_H

#include <array>
#include <chrono>
#include <cstdlib>
#ifndef _WIN32
#include <execinfo.h>
#endif // defined WIN32
#include <getopt.h>
#include <iostream>
#include <list>
#include <memory>
#include <mutex>
#include <pthread.h>
#include <string>
#include <string.h>
#include <thread>
#include <tuple>

#include "compat/platform_pipe.h"
#include "control_socket.h"
#include "debug.h"
#include "host.h"
#include "keyboard_control.h"
#include "lib_common.h"
#include "messaging.h"
#include "module.h"
#include "playback.h"
#include "rtp/rtp.h"
#include "rtsp/rtsp_utils.h"
#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"
#include "utils/udp_holepunch.h"
#include "video.h"
#include "video_capture.h"
#include "video_display.h"
#include "video_compress.h"
#include "export.h"
#include "video_rxtx.h"
#include "audio/audio.h"
#include "audio/audio_capture.h"
#include "audio/audio_playback.h"
#include "audio/codec.h"
#include "audio/utils.h"

#define MOD_NAME                "[main] "
#define PORT_BASE               5004

#define DEFAULT_AUDIO_FEC       "none"
static constexpr const char *DEFAULT_VIDEO_COMPRESSION = "none";
static constexpr const char *DEFAULT_AUDIO_CODEC = "PCM";

#define OPT_AUDIO_CAPTURE_CHANNELS (('a' << 8) | 'c')
#define OPT_AUDIO_CAPTURE_FORMAT (('C' << 8) | 'F')
#define OPT_AUDIO_CHANNEL_MAP (('a' << 8) | 'm')
#define OPT_AUDIO_CODEC (('A' << 8) | 'C')
#define OPT_AUDIO_DELAY (('A' << 8) | 'D')
#define OPT_AUDIO_PROTOCOL (('A' << 8) | 'P')
#define OPT_AUDIO_SCALE (('a' << 8) | 's')
#define OPT_CAPABILITIES (('C' << 8) | 'C')
#define OPT_CAPTURE_FILTER (('O' << 8) | 'F')
#define OPT_CONTROL_PORT (('C' << 8) | 'P')
#define OPT_CUDA_DEVICE (('C' << 8) | 'D')
#define OPT_ECHO_CANCELLATION (('E' << 8) | 'C')
#define OPT_ENCRYPTION (('E' << 8) | 'N')
#define OPT_FULLHELP (('F' << 8u) | 'H')
#define OPT_EXPORT (('E' << 8) | 'X')
#define OPT_IMPORT (('I' << 8) | 'M')
#define OPT_LIST_MODULES (('L' << 8) | 'M')
#define OPT_MCAST_IF (('M' << 8) | 'I')
#define OPT_PARAM (('O' << 8) | 'P')
#define OPT_PIX_FMTS (('P' << 8) | 'F')
#define OPT_PROTOCOL (('P' << 8) | 'R')
#define OPT_START_PAUSED (('S' << 8) | 'P')
#define OPT_VIDEO_CODECS (('V' << 8) | 'C')
#define OPT_VIDEO_PROTOCOL (('V' << 8) | 'P')
#define OPT_WINDOW_TITLE (('W' << 8) | 'T')

#define MAX_CAPTURE_COUNT 17

using namespace std;
using namespace std::chrono;

struct state_uv {
        state_uv() : capture_device{}, display_device{}, audio{}, state_video_rxtx{} {
                if (platform_pipe_init(should_exit_pipe) != 0) {
                        LOG(LOG_LEVEL_ERROR) << MOD_NAME "Cannot create pipe!\n";
                        abort();
                }
                module_init_default(&root_module);
                root_module.cls = MODULE_CLASS_ROOT;
                root_module.new_message = state_uv::new_message;
                root_module.priv_data = this;
                should_exit_thread = thread(should_exit_watcher, this);
        }
        ~state_uv() {
                stop();
        }
        void stop() {
                if (exited) {
                        return;
                }
                broadcast_should_exit();
                should_exit_thread.join();
                module_done(&root_module);
                for (int i = 0; i < 2; ++i) {
                        platform_pipe_close(should_exit_pipe[0]);
                }
                exited = true;
        }
        static void should_exit_watcher(state_uv *s) {
                set_thread_name(__func__);
                char c;
                while (PLATFORM_PIPE_READ(s->should_exit_pipe[0], &c, 1) != 1) {
                        perror("PLATFORM_PIPE_READ");
                }
                unique_lock<mutex> lk(s->lock);
                for (auto c : s->should_exit_callbacks) {
                        get<0>(c)(get<1>(c));
                }
        }
        void broadcast_should_exit() {
                char c = 0;
                unique_lock<mutex> lk(lock);
                if (exited || should_exit_thread_notified) {
                        return;
                }
                should_exit_thread_notified = true;
                while (PLATFORM_PIPE_WRITE(should_exit_pipe[1], &c, 1) != 1) {
                        perror("PLATFORM_PIPE_WRITE");
                }
        }
        static void new_message(struct module *mod)
        {
                auto s = (state_uv *) mod->priv_data;
                struct msg_root *m;
                while ((m = (struct msg_root *) check_message(mod))) {
                        if (m->type != ROOT_MSG_REGISTER_SHOULD_EXIT) {
                                free_message((struct message *) m, new_response(RESPONSE_BAD_REQUEST, NULL));
                                continue;
                        }
                        unique_lock<mutex> lk(s->lock);
                        s->should_exit_callbacks.push_back(make_tuple(m->should_exit_callback, m->udata));
                        lk.unlock();
                        free_message((struct message *) m, new_response(RESPONSE_OK, NULL));
                }
        }

        string capture_device_name;
        struct vidcap *capture_device;
        struct display *display_device;

        struct state_audio *audio;

        struct module root_module;

        video_rxtx *state_video_rxtx;

private:
        mutex lock;
        fd_t should_exit_pipe[2];
        thread should_exit_thread;
        bool should_exit_thread_notified{false};
        bool exited{false};
        list<tuple<void (*)(void *), void *>> should_exit_callbacks;
};


static volatile int exit_status = EXIT_SUCCESS;
static struct state_uv * volatile uv_state;

static void write_all(size_t len, const char *msg) {
        const char *ptr = msg;
        do {
                ssize_t written = write(STDERR_FILENO, ptr, len);
                if (written < 0) {
                        break;
                }
                len -= written;
                ptr += written;
        } while (len > 0);
}

/**
 * Copies string at src to pointer *ptr and increases the pointner.
 * @todo
 * Functions defined in string.h should signal safe in POSIX.1-2008 - check if in glibc, if so, use strcat.
 * @note
 * Strings are not NULL-terminated.
 */
static void append(char **ptr, const char *ptr_end, const char *src) {
        while (*src != '\0') {
                if (*ptr == ptr_end) {
                        return;
                }
                *(*ptr)++ = *src++;
        }
}

static void signal_handler(int signal)
{
        if (log_level >= LOG_LEVEL_VERBOSE) {
                char buf[128];
                char *ptr = buf;
                char *ptr_end = buf + sizeof buf;
                append(&ptr, ptr_end, "Caught signal ");
                if (signal / 10) {
                        *ptr++ = '0' + signal/10;
                }
                *ptr++ = '0' + signal%10;
                *ptr++ = '\n';
                write_all(ptr - buf, buf);
        }
        exit_uv(0);
}

static void crash_signal_handler(int sig)
{
        char buf[1024];
        char *ptr = buf;
        char *ptr_end = buf + sizeof buf;
        append(&ptr, ptr_end, "\n" PACKAGE_NAME " has crashed");
#ifndef WIN32
        char backtrace_msg[] = "Backtrace:\n";
        write_all(sizeof backtrace_msg, backtrace_msg);
        array<void *, 256> addresses{};
        int num_symbols = backtrace(addresses.data(), addresses.size());
        backtrace_symbols_fd(addresses.data(), num_symbols, 2);

#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
        const char *sig_desc = sigdescr_np(sig);
#else
        const char *sig_desc = sys_siglist[sig];
#endif
        if (sig_desc != NULL) {
                append(&ptr, ptr_end, " (");
                append(&ptr, ptr_end, sig_desc);
                append(&ptr, ptr_end, ")");
        }
#endif
        append(&ptr, ptr_end, ".\n\nPlease send a bug report to address " PACKAGE_BUGREPORT ".\n");
        append(&ptr, ptr_end, "You may find some tips how to report bugs in file doc/REPORTING_BUGS.md distributed with " PACKAGE_NAME "\n");
        append(&ptr, ptr_end, "(or available online at https://github.com/CESNET/UltraGrid/blob/master/doc/REPORTING-BUGS.md).\n");

        write_all(ptr - buf, buf);

        signal(sig, SIG_DFL);
        raise(sig);
}

#ifndef WIN32
static void hang_signal_handler(int sig)
{
        assert(sig == SIGALRM);
        char msg[] = "Hang detected - you may continue waiting or kill UltraGrid. Please report if UltraGrid doesn't exit after reasonable amount of time.\n";
        write_all(sizeof msg - 1, msg);
        signal(SIGALRM, SIG_DFL);
}
#endif // ! defined WIN32

void exit_uv(int status) {
        exit_status = status;
        should_exit = true;
        uv_state->broadcast_should_exit();
}

static void print_help_item(const string &name, const vector<string> &help) {
        int help_lines = 0;

        cout << BOLD("\t" << name);

        for (auto line : help) {
                int spaces = help_lines == 0 ? 31 - (int) name.length() : 39;
                for (int i = 0; i < max(spaces, 0) + 1; ++i) {
                        cout << " ";
                }
                cout << line << "\n";
                help_lines += 1;
        }

        if (help_lines == 0) {
                cout << "\n";
        }
        cout << "\n";
}

static void usage(const char *exec_path, bool full = false)
{
        cout << "Usage: " << BOLD(RED((exec_path ? exec_path : "<executable_path>")) << " [options] address\n\n");
        printf("Options:\n");
        print_help_item("-h | --fullhelp", {"show usage (basic/full)"});
        print_help_item("-d <display_device>", {"select display device, use '-d help'",
                        "to get list of supported devices"});
        print_help_item("-t <capture_device>", {"select capture device, use '-t help'",
                        "to get list of supported devices"});
        print_help_item("-c <cfg>", {"video compression (see '-c help')"});
        print_help_item("-r <playback_device>", {"audio playback device (see '-r help')"});
        print_help_item("-s <capture_device>", {"audio capture device (see '-s help')"});
        if (full) {
                print_help_item("--verbose[=<level>]", {"print verbose messages (optionally specify level [0-" + to_string(LOG_LEVEL_MAX) + "])"});
                print_help_item("--list-modules", {"prints list of modules"});
                print_help_item("--control-port <port>[:0|1]", {"set control port (default port: " + to_string(DEFAULT_CONTROL_PORT) + ")",
                                "connection types: 0- Server (default), 1- Client"});
                print_help_item("--video-protocol <proto>", {"transmission protocol, see '--video-protocol help'",
                                "for list. Use --video-protocol rtsp for RTSP server",
                                "(see --video-protocol rtsp:help for usage)"});
                print_help_item("--audio-protocol <proto>[:<settings>]", {"<proto> can be " AUDIO_PROTOCOLS});
                print_help_item("--protocol <proto>", {"shortcut for '--audio-protocol <proto> --video-protocol <proto>'"});
#ifdef HAVE_IPv6
                print_help_item("-4/-6", {"force IPv4/IPv6 resolving"});
#endif //  HAVE_IPv6
                print_help_item("--mcast-if <iface>", {"bind to specified interface for multicast"});
                print_help_item("-m <mtu>", {"set path MTU assumption towards receiver"});
                print_help_item("-M <video_mode>", {"received video mode (eg tiled-4K, 3D,",
                                "dual-link)"});
                print_help_item("-N|--nat-traverse"s, {"try to deploy NAT traversal techniques"s});
                print_help_item("-p <postprocess> | help", {"postprocess module"});
                print_help_item("-T/--ttl <num>", {"Use specified TTL for multicast/unicast (0..255, -1 for default)"});
        }
        print_help_item("-f [A:|V:]<settings>", {"FEC settings (audio or video) - use",
                        "\"none\", \"mult:<nr>\",", "\"ldgm:<max_expected_loss>%%\" or", "\"ldgm:<k>:<m>:<c>\"",
                        "\"rs:<k>:<n>\""});
        print_help_item("-P <port> | <video_rx>:<video_tx>[:<audio_rx>:<audio_tx>]", { "",
                        "<port> is base port number, also 3",
                        "subsequent ports can be used for RTCP",
                        "and audio streams. Default: " + to_string(PORT_BASE) + ".",
                        "You can also specify all two or four", "ports directly."});
        print_help_item("-l <limit_bitrate> | unlimited | auto", {"limit sending bitrate",
                        "to <limit_bitrate> (with optional k/M/G suffix)"});
        if (full) {
                print_help_item("-A <address>", {"audio destination address",
                                "If not specified, will use same as for video"});
        }
        print_help_item("--audio-capture-format <fmt> | help", {"format of captured audio"});
        if (full) {
                print_help_item("--audio-channel-map <mapping> | help", {});
        }
        print_help_item("--audio-codec <codec>[:sample_rate=<sr>][:bitrate=<br>] | help", {"audio codec"});
        if (full) {
                print_help_item("--audio-delay <delay_ms>", {"amount of time audio should be delayed to video",
                                "(may be also negative to delay video)"});
                print_help_item("--audio-scale <factor> | <method> | help",
                                {"scales received audio"});
        }
        printf("\t--echo-cancellation      \tapply acoustic echo cancellation to audio (experimental)\n");
        printf("\n");
        print_help_item("--cuda-device <index> | help", {"use specified CUDA device"});
        if (full) {
                print_help_item("--encryption <passphrase>", {"key material for encryption"});
                print_help_item("--playback <directory> | help", {"replays recorded audio and video"});
                print_help_item("--record[=<directory>]", {"record captured audio and video"});
                print_help_item("--capture-filter <filter> | help",
                                {"capture filter(s), must be given before capture device"});
                print_help_item("--param <params> | help", {"additional advanced parameters, use help for list"});
                print_help_item("--pix-fmts", {"list of pixel formats"});
                print_help_item("--video-codecs", {"list of video codecs"});
        }
        print_help_item("address", {"destination address"});
        printf("\n");
}

static void print_fps(steady_clock::time_point *t0, int *frames, const string &name) {
        *frames += 1;
        steady_clock::time_point t1 = steady_clock::now();
        double seconds = duration_cast<duration<double>>(t1 - *t0).count();
        if (seconds >= 5.0) {
                double fps = *frames / seconds;
                LOG(LOG_LEVEL_INFO) << "[" << name << "] " << *frames << " frames in " << seconds << " seconds = " << BOLD(fps << " FPS\n");
                *t0 = t1;
                *frames = 0;
        }
}

/**
 * This function captures video and possibly compresses it.
 * It then delegates sending to another thread.
 *
 * @param[in] arg pointer to UltraGrid (root) module
 */
static void *capture_thread(void *arg)
{
        set_thread_name(__func__);

        struct module *uv_mod = (struct module *)arg;
        struct state_uv *uv = (struct state_uv *) uv_mod->priv_data;
        struct wait_obj *wait_obj = wait_obj_init();
        steady_clock::time_point t0 = steady_clock::now();
        int frames = 0;
        bool should_print_fps = vidcap_generic_fps(uv->capture_device);

        while (!should_exit) {
                /* Capture and transmit video... */
                struct audio_frame *audio = nullptr;
                struct video_frame *tx_frame = vidcap_grab(uv->capture_device, &audio);

                if (audio != nullptr) {
                        audio_sdi_send(uv->audio, audio);
                        AUDIO_FRAME_DISPOSE(audio);
                }

                if (tx_frame != NULL) {
                        if (should_print_fps) {
                                print_fps(&t0, &frames, uv->capture_device_name);
                        }
                        //tx_frame = vf_get_copy(tx_frame);
                        bool wait_for_cur_uncompressed_frame;
                        shared_ptr<video_frame> frame;
                        if (!tx_frame->callbacks.dispose) {
                                wait_obj_reset(wait_obj);
                                wait_for_cur_uncompressed_frame = true;
                                frame = shared_ptr<video_frame>(tx_frame, [wait_obj](struct video_frame *) {
                                                        wait_obj_notify(wait_obj);
                                                });
                        } else {
                                wait_for_cur_uncompressed_frame = false;
                                frame = shared_ptr<video_frame>(tx_frame, tx_frame->callbacks.dispose);
                        }

                        uv->state_video_rxtx->send(move(frame)); // std::move really important here (!)

                        // wait for frame frame to be processed, eg. by compress
                        // or sender (uncompressed video). Grab invalidates previous frame
                        // (if not defined dispose function).
                        if (wait_for_cur_uncompressed_frame) {
                                wait_obj_wait(wait_obj);
                                tx_frame->callbacks.dispose = NULL;
                                tx_frame->callbacks.dispose_udata = NULL;
                        }
                }
        }

        wait_obj_done(wait_obj);

        return NULL;
}

static bool parse_bitrate(char *optarg, long long int *bitrate) {
        if (strcmp(optarg, "auto") == 0) {
                *bitrate = RATE_AUTO;
                return true;
        }
        if (strcmp(optarg, "help") == 0) {
                const char numeric_pattern[] = "{1-9}{0-9}*[kMG][!][E]";
                cout << "Usage:\n" <<
                        "\tuv " << BOLD("-l [auto | unlimited | " << numeric_pattern << "]\n") <<
                        "\twhere\n"
                        "\t\t" << BOLD("auto") << " - spread packets across frame time\n"
                        "\t\t" << BOLD("unlimited") << " - send packets at a wire speed (in bursts)\n"
                        "\t\t" << BOLD(numeric_pattern) << " - send packets at most at specified bitrate\n\n" <<
                        BOLD("Notes: ") << "Use an exclamation mark to indicate intentionally very low bitrate. 'E' to use the value as a fixed bitrate, not cap /i. e. even the frames that may be sent at lower bitrate are sent at the nominal bitrate)\n" <<
                        "\n";
                return true;
        }
        if (strcmp(optarg, "unlimited") == 0) {
                *bitrate = RATE_UNLIMITED;
                return true;
        }
        bool force = false, fixed = false;
        for (int i = 0; i < 2; ++i) {
                if (optarg[strlen(optarg) - 1] == '!' ||
                                optarg[strlen(optarg) - 1] == 'E') {
                        if (optarg[strlen(optarg) - 1] == '!') {
                                force = true;
                                optarg[strlen(optarg) - 1] = '\0';
                        }
                        if (optarg[strlen(optarg) - 1] == 'E') {
                                fixed = true;
                                optarg[strlen(optarg) - 1] = '\0';
                        }
                }
        }
        *bitrate = unit_evaluate(optarg);
        if (*bitrate <= 0) {
                log_msg(LOG_LEVEL_ERROR, "Invalid bitrate %s!\n", optarg);
                return false;
        }
        long long mb5 = 5ll * 1000 * 1000, // it'll take 6.4 sec to send 4 MB frame at 5 Mbps
             gb100 = 100ll * 1000 * 1000 * 1000; // traffic shaping to eg. 40 Gbps may make sense
        if ((*bitrate < mb5 || *bitrate > gb100) && !force) {
                log_msg(LOG_LEVEL_WARNING, "Bitrate %lld bps seems to be too %s, use \"-l %s!\" to force if this is not a mistake.\n", *bitrate, *bitrate < mb5 ? "low" : "high", optarg);
                return false;
        }
        if (fixed) {
                *bitrate |= RATE_FLAG_FIXED_RATE;
        }
        return true;
}

[[maybe_unused]] static bool parse_holepunch_conf(char *conf, struct Holepunch_config *punch_c){

        char *token = strchr(conf, ':');
        while(token){
                token = token + 1;
                char *next = strchr(token, ':');
                if(next){
                        *next = '\0';
                }

                if(strncmp(token, "help", strlen("help")) == 0){
                        cout << "Usage:\n" <<
                                "\tuv " << BOLD("-Nholepunch:room=<room>:(server=<host> | coord_srv=<host:port>:stun_srv=<host:port>)[:client_name=<name>] \n") <<
                                "\twhere\n"
                                "\t\t" << BOLD("server") << " - used if both stun & coord server are on the same host on standard ports (3478, 12558)\n"
                                "\t\t" << BOLD("room") << " - name of room to join\n"
                                "\t\t" << BOLD("client_name") << " - name to identify as to the coord server, if not specified hostname is used\n"
                                "\n";
                        return false;
                }

                if(strncmp(token, "coord_srv=", strlen("coord_srv=")) == 0){
                        token += strlen("coord_srv=");
                        punch_c->coord_srv_addr = token;

                        if(!next){
                                log_msg(LOG_LEVEL_ERROR, "Missing hole punching coord server port.\n");
                                return false;
                        }

                        char *end;
                        next++;
                        punch_c->coord_srv_port = strtol(next, &end, 10);
                        if(next == end){
                                log_msg(LOG_LEVEL_ERROR, "Failed to parse hole punching coord server port.\n");
                                return false;
                        }
                        next = strchr(next, ':');
                } else if(strncmp(token, "stun_srv=", strlen("stun_srv=")) == 0){
                        token += strlen("stun_srv=");
                        punch_c->stun_srv_addr = token;

                        if(!next){
                                log_msg(LOG_LEVEL_ERROR, "Missing hole punching stun server port.\n");
                                return false;
                        }

                        char *end;
                        next++;
                        punch_c->stun_srv_port = strtol(next, &end, 10);
                        if(next == end){
                                log_msg(LOG_LEVEL_ERROR, "Failed to parse hole punching stun server port.\n");
                                return false;
                        }
                        next = strchr(next, ':');
                } else if(strncmp(token, "server=", strlen("server=")) == 0){
                        token += strlen("server=");
                        punch_c->stun_srv_addr = token;
                        punch_c->coord_srv_addr = token;

                        punch_c->stun_srv_port = 3478;
                        punch_c->coord_srv_port = 12558;
                } else if(strncmp(token, "room=", strlen("room=")) == 0){
                        token += strlen("room=");
                        punch_c->room_name = token;
                } else if(strncmp(token, "client_name=", strlen("client_name=")) == 0){
                        token += strlen("client_name=");
                        punch_c->client_name = token;
                }

                token = next;
        }

        if(!punch_c->stun_srv_addr || !punch_c->coord_srv_addr || !punch_c->room_name)
        {
                log_msg(LOG_LEVEL_ERROR, "Not all hole punch params provided.\n");
                return false;
        }

        return true;
}

struct ug_options {
        ug_options() {
                vidcap_params_set_device(vidcap_params_head, "none");
        }
        ~ug_options() {
                while  (vidcap_params_head != nullptr) {
                        struct vidcap_params *next = vidcap_params_get_next(vidcap_params_head);
                        vidcap_params_free_struct(vidcap_params_head);
                        vidcap_params_head = next;
                }
        }
        struct audio_options audio = {
                .host = nullptr,
                .recv_port = -1,
                .send_port = -1,
                .recv_cfg = "none",
                .send_cfg = "none",
                .proto = "ultragrid_rtp",
                .proto_cfg = "",
                .fec_cfg = DEFAULT_AUDIO_FEC,
                .channel_map = nullptr,
                .scale = "mixauto",
                .echo_cancellation = false,
                .codec_cfg = nullptr
        };
        // NULL terminated array of capture devices
        struct vidcap_params *vidcap_params_head = vidcap_params_allocate();
        struct vidcap_params *vidcap_params_tail = vidcap_params_head;

        const char *display_cfg = "";
        const char *requested_video_fec = "none";
        int port_base = PORT_BASE;
        int requested_ttl = -1;
        int video_rx_port = -1, video_tx_port = -1;

        bool should_export = false;
        char *export_opts = NULL;

        int control_port = 0;
        int connection_type = 0;

        enum video_mode decoder_mode = VIDEO_NORMAL;
        const char *requested_compression = nullptr;

        int force_ip_version = 0;

        const char *requested_mcast_if = NULL;

        unsigned requested_mtu = 0;
        const char *postprocess = NULL;
        const char *requested_display = "none";
        const char *requested_receiver = nullptr;
        const char *requested_encryption = NULL;

        long long int bitrate = RATE_DEFAULT;
        bool is_client = false;
        bool is_server = false;

        bool print_capabilities_req = false;
        bool start_paused = false;

        const char *video_protocol = "ultragrid_rtp";
        const char *video_protocol_opts = "";

        char *nat_traverse_config = nullptr;

        unsigned int video_rxtx_mode = 0;
};

static int parse_options(int argc, char *argv[], struct ug_options *opt) {
        static struct option getopt_options[] = {
                {"display", required_argument, 0, 'd'},
                {"capture", required_argument, 0, 't'},
                {"mtu", required_argument, 0, 'm'},
                {"mode", required_argument, 0, 'M'},
                {"version", no_argument, 0, 'v'},
                {"compress", required_argument, 0, 'c'},
                {"receive", required_argument, 0, 'r'},
                {"send", required_argument, 0, 's'},
                {"help", no_argument, 0, 'h'},
                {"fec", required_argument, 0, 'f'},
                {"port", required_argument, 0, 'P'},
                {"limit-bitrate", required_argument, 0, 'l'},
                {"audio-channel-map", required_argument, 0, OPT_AUDIO_CHANNEL_MAP},
                {"audio-scale", required_argument, 0, OPT_AUDIO_SCALE},
                {"audio-capture-channels", required_argument, 0, OPT_AUDIO_CAPTURE_CHANNELS},
                {"audio-capture-format", required_argument, 0, OPT_AUDIO_CAPTURE_FORMAT},
                {"echo-cancellation", no_argument, 0, OPT_ECHO_CANCELLATION},
                {"fullhelp", no_argument, 0, OPT_FULLHELP},
                {"cuda-device", required_argument, 0, OPT_CUDA_DEVICE},
                {"mcast-if", required_argument, 0, OPT_MCAST_IF},
                {"record", optional_argument, 0, OPT_EXPORT},
                {"playback", required_argument, 0, OPT_IMPORT},
                {"audio-host", required_argument, 0, 'A'},
                {"audio-codec", required_argument, 0, OPT_AUDIO_CODEC},
                {"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, nullptr, 'V'},
                {"window-title", required_argument, 0, OPT_WINDOW_TITLE},
                {"capabilities", no_argument, 0, OPT_CAPABILITIES},
                {"audio-delay", required_argument, 0, OPT_AUDIO_DELAY},
                {"list-modules", no_argument, 0, OPT_LIST_MODULES},
                {"start-paused", no_argument, 0, OPT_START_PAUSED},
                {"audio-protocol", required_argument, 0, OPT_AUDIO_PROTOCOL},
                {"video-protocol", required_argument, 0, OPT_VIDEO_PROTOCOL},
                {"protocol", required_argument, 0, OPT_PROTOCOL},
                {"rtsp-server", optional_argument, 0, 'H'},
                {"param", required_argument, 0, OPT_PARAM},
                {"pix-fmts", no_argument, 0, OPT_PIX_FMTS},
                {"video-codecs", no_argument, 0, OPT_VIDEO_CODECS},
                {"nat-traverse", optional_argument, nullptr, 'N'},
                {"client", no_argument, nullptr, 'C'},
                {"server", no_argument, nullptr, 'S'},
                {"ttl", required_argument, nullptr, 'T'},
                {0, 0, 0, 0}
        };
        const char *optstring = "Cd:t:m:r:s:v46c:hM:N::p:f:P:l:A:VST:";

        int ch = 0;
        while ((ch =
                getopt_long(argc, argv, optstring, getopt_options,
                            NULL)) != -1) {
                switch (ch) {
                case 'd':
                        if (strcmp(optarg, "help") == 0 || strcmp(optarg, "fullhelp") == 0) {
                                list_video_display_devices(strcmp(optarg, "fullhelp") == 0);
                                return 0;
                        }
                        opt->requested_display = optarg;
                        if(strchr(optarg, ':')) {
                                char *delim = strchr(optarg, ':');
                                *delim = '\0';
                                opt->display_cfg = delim + 1;
                        }
                        break;
                case 't':
                        if (strcmp(optarg, "help") == 0 || strcmp(optarg, "fullhelp") == 0) {
                                list_video_capture_devices(strcmp(optarg, "fullhelp") == 0);
                                return 0;
                        }
                        vidcap_params_set_device(opt->vidcap_params_tail, optarg);
                        opt->vidcap_params_tail = vidcap_params_allocate_next(opt->vidcap_params_tail);
                        break;
                case 'm':
                        opt->requested_mtu = atoi(optarg);
                        if (opt->requested_mtu < 576 && optarg[strlen(optarg) - 1] != '!') {
                                log_msg(LOG_LEVEL_WARNING, "MTU %1$u seems to be too low, use \"%1$u!\" to force.\n", opt->requested_mtu);
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case 'M':
                        opt->decoder_mode = get_video_mode_from_str(optarg);
                        if (opt->decoder_mode == VIDEO_UNKNOWN) {
                                return strcasecmp(optarg, "help") == 0 ? EXIT_SUCCESS : EXIT_FAIL_USAGE;
                        }
                        break;
                case 'p':
                        opt->postprocess = optarg;
                        break;
                case 'v':
                        print_configuration();
                        return EXIT_SUCCESS;
                case 'c':
                        if (strcmp(optarg, "help") == 0 || strcmp(optarg, "fullhelp") == 0) {
                                show_compress_help(strcmp(optarg, "fullhelp") == 0);
                                return EXIT_SUCCESS;
                        }
                        opt->requested_compression = optarg;
                        break;
                case 'H':
                        log_msg(LOG_LEVEL_WARNING, "Option \"--rtsp-server[=args]\" "
                                        "is deprecated and will be removed in future.\n"
                                        "Please use \"--video-protocol rtsp[:args]\"instead.\n");
                        opt->video_protocol = "rtsp";
                        opt->video_protocol_opts = optarg ? optarg : "";
                        break;
                case OPT_AUDIO_PROTOCOL:
                        opt->audio.proto = optarg;
                        if (strchr(optarg, ':')) {
                                char *delim = strchr(optarg, ':');
                                *delim = '\0';
                                opt->audio.proto_cfg = delim + 1;
                        }
                        if (strcmp(opt->audio.proto, "help") == 0) {
                                printf("Audio protocol can be one of: " AUDIO_PROTOCOLS "\n");
                                return EXIT_SUCCESS;
                        }
                        break;
                case OPT_VIDEO_PROTOCOL:
                        opt->video_protocol = optarg;
                        if (strchr(optarg, ':')) {
                                char *delim = strchr(optarg, ':');
                                *delim = '\0';
                                opt->video_protocol_opts = delim + 1;
                        }
                        if (strcmp(opt->video_protocol, "help") == 0) {
                                video_rxtx::list(strcmp(optarg, "fullhelp") == 0);
                                return EXIT_SUCCESS;
                        }
                        break;
                case OPT_PROTOCOL:
                        if (strcmp(optarg, "help") == 0 ||
                                        strcmp(optarg, "fullhelp") == 0) {
                                cout << "Specify a " << BOLD("common") << " protocol for both audio and video.\n";
                                cout << "Audio protocol can be one of: " << BOLD(AUDIO_PROTOCOLS "\n");
                                video_rxtx::list(strcmp(optarg, "fullhelp") == 0);
                                return EXIT_SUCCESS;
                        }
                        opt->audio.proto = opt->video_protocol = optarg;
                        if (strchr(optarg, ':')) {
                                char *delim = strchr(optarg, ':');
                                *delim = '\0';
                                opt->audio.proto_cfg = opt->video_protocol_opts = delim + 1;
                        }
                        break;
                case 'r':
                        if (strcmp(optarg, "help") == 0 || strcmp(optarg, "fullhelp") == 0) {
                                audio_playback_help(strcmp(optarg, "full") == 0);
                                return EXIT_SUCCESS;
                        }
                        opt->audio.recv_cfg = optarg;
                        break;
                case 's':
                        if (strcmp(optarg, "help") == 0 || strcmp(optarg, "fullhelp") == 0) {
                                audio_capture_print_help(strcmp(optarg, "full") == 0);
                                return EXIT_SUCCESS;
                        }
                        opt->audio.send_cfg = optarg;
                        break;
                case 'f':
                        if(strlen(optarg) > 2 && optarg[1] == ':' &&
                                        (toupper(optarg[0]) == 'A' || toupper(optarg[0]) == 'V')) {
                                if(toupper(optarg[0]) == 'A') {
                                        opt->audio.fec_cfg = optarg + 2;
                                } else {
                                        opt->requested_video_fec = optarg + 2;
                                }
                        } else {
                                // there should be setting for both audio and video
                                // but we conservativelly expect that the user wants
                                // only vieo and let audio default until explicitly
                                // stated otehrwise
                                opt->requested_video_fec = optarg;
                        }
                        break;
                case 'h':
                        usage(uv_argv[0], false);
                        return 0;
                case 'P':
                        try {
                                if (strchr(optarg, ':') != nullptr) {
                                        char *save_ptr = nullptr;
                                        opt->video_rx_port = stoi(strtok_r(optarg, ":", &save_ptr), nullptr, 0);
                                        opt->video_tx_port = stoi(strtok_r(nullptr, ":", &save_ptr), nullptr, 0);
                                        char *tok = nullptr;
                                        if ((tok = strtok_r(nullptr, ":", &save_ptr)) != nullptr) {
                                                opt->audio.recv_port = stoi(tok, nullptr, 0);
                                                if ((tok = strtok_r(nullptr, ":", &save_ptr)) != nullptr) {
                                                        opt->audio.send_port = stoi(tok, nullptr, 0);
                                                } else {
                                                        usage(uv_argv[0]);
                                                        return EXIT_FAIL_USAGE;
                                                }
                                        }
                                } else {
                                        opt->port_base = stoi(optarg, nullptr, 0);
                                }
                                if (opt->audio.recv_port < -1 || opt->audio.send_port < -1 || opt->video_rx_port < -1 || opt->video_tx_port < -1 || opt->port_base < -1 ||
                                                opt->audio.recv_port > UINT16_MAX || opt->audio.send_port > UINT16_MAX || opt->video_rx_port > UINT16_MAX || opt->video_tx_port > UINT16_MAX || opt->port_base > UINT16_MAX) {
                                        throw ug_runtime_error("Invalid port value, allowed range 1-65535", EXIT_FAIL_USAGE);
                                }
                        } catch (exception const &e) {
                                LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Wrong port specification: " << e.what() << "\n";
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case 'l':
                        if (!parse_bitrate(optarg, &opt->bitrate)) {
                                return EXIT_FAILURE;
                        }
                        if (opt->bitrate == RATE_DEFAULT) {
                                return EXIT_SUCCESS; // help written
                        }
                        break;
                case '4':
                case '6':
                        opt->force_ip_version = ch - '0';
                        break;
                case OPT_AUDIO_CHANNEL_MAP:
                        opt->audio.channel_map = optarg;
                        break;
                case OPT_AUDIO_SCALE:
                        opt->audio.scale = optarg;
                        break;
                case OPT_AUDIO_CAPTURE_CHANNELS:
                        log_msg(LOG_LEVEL_WARNING, "Parameter --audio-capture-channels is deprecated. "
                                        "Use \"--audio-capture-format channels=<count>\" instead.\n");
                        audio_capture_channels = atoi(optarg);
                        if (audio_capture_channels < 1) {
                                log_msg(LOG_LEVEL_ERROR, "Invalid number of channels %d!\n", audio_capture_channels);
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case OPT_AUDIO_CAPTURE_FORMAT:
                        if (!parse_audio_capture_format(optarg)) {
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case OPT_ECHO_CANCELLATION:
                        opt->audio.echo_cancellation = true;
                        break;
                case OPT_FULLHELP:
                        usage(uv_argv[0], true);
                        return EXIT_SUCCESS;
                case OPT_CUDA_DEVICE:
#ifdef HAVE_GPUJPEG
                        if(strcmp("help", optarg) == 0) {
                                struct compress_state *compression;
                                int ret = compress_init(nullptr, "GPUJPEG:list_devices", &compression);
                                if(ret >= 0) {
                                        if(ret == 0) {
                                                module_done(CAST_MODULE(compression));
                                        }
                                        return EXIT_SUCCESS;
                                } else {
                                        return EXIT_FAILURE;
                                }
                        } else {
                                char *item, *save_ptr = NULL;
                                unsigned int i = 0;
                                while((item = strtok_r(optarg, ",", &save_ptr))) {
                                        if(i >= MAX_CUDA_DEVICES) {
                                                fprintf(stderr, "Maximal number of CUDA device exceeded.\n");
                                                return EXIT_FAILURE;
                                        }
                                        cuda_devices[i] = atoi(item);
                                        optarg = NULL;
                                        ++i;
                                }
                                cuda_devices_count = i;
                        }
                        break;
#else
                        fprintf(stderr, "CUDA support is not enabled!\n");
                        return EXIT_FAIL_USAGE;
#endif // HAVE_CUDA
                case OPT_MCAST_IF:
                        opt->requested_mcast_if = optarg;
                        break;
                case 'A':
                        opt->audio.host = optarg;
                        break;
                case OPT_EXPORT:
                        opt->should_export = true;
                        opt->export_opts = optarg;
                        break;
                case OPT_IMPORT:
                        opt->audio.send_cfg = "embedded";
                        {
                                char dev_string[1024];
                                int ret;
                                if ((ret = playback_set_device(dev_string, sizeof dev_string, optarg)) <= 0) {
                                        return ret == 0 ? EXIT_SUCCESS : EXIT_FAIL_USAGE;
                                }
                                vidcap_params_set_device(opt->vidcap_params_tail, dev_string);
                                opt->vidcap_params_tail = vidcap_params_allocate_next(opt->vidcap_params_tail);
                        }
                        break;
                case OPT_AUDIO_CODEC:
                        if(strcmp(optarg, "help") == 0) {
                                list_audio_codecs();
                                return EXIT_SUCCESS;
                        }
                        opt->audio.codec_cfg = optarg;
                        if (!check_audio_codec(optarg)) {
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case OPT_CAPTURE_FILTER:
                        vidcap_params_set_capture_filter(opt->vidcap_params_tail, optarg);
                        break;
                case OPT_ENCRYPTION:
                        opt->requested_encryption = optarg;
                        break;
                case OPT_CONTROL_PORT:
                        if (strchr(optarg, ':')) {
                                char *save_ptr = NULL;
                                char *tok;
                                opt->control_port = atoi(strtok_r(optarg, ":", &save_ptr));
                                opt->connection_type = atoi(strtok_r(NULL, ":", &save_ptr));

                                if (opt->connection_type < 0 || opt->connection_type > 1){
                                        usage(uv_argv[0]);
                                        return EXIT_FAIL_USAGE;
                                }
                                if ((tok = strtok_r(NULL, ":", &save_ptr))) {
                                        usage(uv_argv[0]);
                                        return EXIT_FAIL_USAGE;
                                }
                        } else {
                                opt->control_port = atoi(optarg);
                                opt->connection_type = 0;
                        }
                        break;
                case 'V':
                        break; // already handled in common_preinit()
                case OPT_WINDOW_TITLE:
                        log_msg(LOG_LEVEL_WARNING, "Deprecated option used, please use "
                                        "--param window-title=<title>\n");
                        commandline_params["window-title"] = optarg;
                        break;
                case OPT_CAPABILITIES:
                        opt->print_capabilities_req = true;
                        break;
                case OPT_AUDIO_DELAY:
                        audio_offset = max(atoi(optarg), 0);
                        video_offset = atoi(optarg) < 0 ? abs(atoi(optarg)) : 0;
                        break;
                case OPT_LIST_MODULES:
                        return list_all_modules() ? EXIT_SUCCESS : EXIT_FAILURE;
                case OPT_START_PAUSED:
                        opt->start_paused = true;
                        break;
                case OPT_PARAM:
                        if (!parse_params(optarg)) {
                                return EXIT_SUCCESS;
                        }
                        break;
                case OPT_PIX_FMTS:
                        print_pixel_formats();
                        return EXIT_SUCCESS;
                case OPT_VIDEO_CODECS:
                        print_video_codecs();
                        return EXIT_SUCCESS;
                case 'N':
                        opt->nat_traverse_config = optarg == nullptr ? const_cast<char *>("") : optarg;
                        break;
                case 'C':
                        opt->is_client = true;
                        break;
                case 'S':
                        opt->is_server = true;
                        break;
                case 'T':
                        opt->requested_ttl = stoi(optarg);
                        if (opt->requested_ttl < -1 || opt->requested_ttl >= 255) {
                                LOG(LOG_LEVEL_ERROR) << "TTL must be in range [0..255] or -1!\n";
                                return EXIT_FAIL_USAGE;
                        }
                        break;
                case '?':
                default:
                        usage(uv_argv[0]);
                        return EXIT_FAIL_USAGE;
                }
        }

        argc -= optind;
        argv += optind;

        if (argc > 1) {
                log_msg(LOG_LEVEL_ERROR, "Multiple receivers given!\n");
                usage(uv_argv[0]);
                return EXIT_FAIL_USAGE;
        }

        if (argc > 0) {
                opt->requested_receiver = argv[0];
        }

        return -1;
}

static int adjust_params(struct ug_options *opt) {
        unsigned int audio_rxtx_mode = 0;
        if (opt->is_server) {
                commandline_params["udp-disable-multi-socket"] = string();
                if (opt->requested_receiver != nullptr) {
                        LOG(LOG_LEVEL_ERROR) << "Receiver must not be given in server mode!\n";
                        return EXIT_FAIL_USAGE;
                }
                opt->requested_receiver = IN6_BLACKHOLE_STR;
                if (strcmp(opt->requested_display, "none") == 0 && strcmp("none", vidcap_params_get_driver(opt->vidcap_params_head)) != 0) {
                        opt->requested_display = "dummy";
                }
                if (strcmp(opt->audio.recv_cfg, "none") == 0 && strcmp(opt->audio.send_cfg, "none") != 0) {
                        opt->audio.recv_cfg = "dummy";
                }
        }
        if (opt->is_client) {
                commandline_params["udp-disable-multi-socket"] = string();
                if (opt->requested_receiver == nullptr) {
                        LOG(LOG_LEVEL_ERROR) << "Server address required in client mode!\n";
                        return EXIT_FAIL_USAGE;
                }
                if (strcmp("none", vidcap_params_get_driver(opt->vidcap_params_head)) == 0 && strcmp(opt->requested_display, "none") != 0) {
                        vidcap_params_set_device(opt->vidcap_params_tail, "testcard:2:1:1:UYVY");
                }
                if (strcmp("none", opt->audio.send_cfg) == 0 && strcmp("none", opt->audio.recv_cfg) != 0) {
                        parse_audio_capture_format("sample_rate=1");
                        opt->audio.send_cfg = "testcard:frames=1";
                }
        }

        if (opt->requested_receiver == nullptr) {
                opt->requested_receiver = "localhost";
        }

        if (opt->audio.host == nullptr) {
                opt->audio.host = opt->requested_receiver;
        }

        if (!is_ipv6_supported()) {
                LOG(LOG_LEVEL_WARNING) << "IPv6 support missing, setting IPv4-only mode.\n";
                opt->force_ip_version = 4;
        }

        if (!set_output_buffering()) {
                LOG(LOG_LEVEL_WARNING) << "Cannot set console output buffering!\n";
        }

        // default values for different RXTX protocols
        if (strcasecmp(opt->video_protocol, "rtsp") == 0 || strcasecmp(opt->video_protocol, "sdp") == 0) {
                if (opt->requested_compression == nullptr) {
                        opt->requested_compression = "none"; // will be set later by h264_sdp_video_rxtx::send_frame()
                }
                if (opt->force_ip_version == 0 && strcasecmp(opt->video_protocol, "rtsp") == 0) {
                        opt->force_ip_version = 4;
                }
        } else {
                if (opt->requested_compression == nullptr) {
                        opt->requested_compression = DEFAULT_VIDEO_COMPRESSION;
                }
        }

        if (opt->audio.codec_cfg == nullptr) {
                if (strcasecmp(opt->audio.proto, "rtsp") == 0 || strcasecmp(opt->audio.proto, "sdp") == 0) {
                        opt->audio.codec_cfg = "OPUS:sample_rate=48000";
                } else {
                        opt->audio.codec_cfg = DEFAULT_AUDIO_CODEC;
                }
        }

        if(opt->nat_traverse_config && strncmp(opt->nat_traverse_config, "holepunch", strlen("holepunch")) == 0){
#ifndef HAVE_LIBJUICE
                log_msg(LOG_LEVEL_ERROR, "Ultragrid was compiled without holepunch support\n");
                return EXIT_FAILURE;
#else
                static char punched_host[512];
                Holepunch_config punch_c = {};

                if(!parse_holepunch_conf(opt->nat_traverse_config, &punch_c)){
                        return EXIT_FAILURE;
                }

                commandline_params["udp-disable-multi-socket"] = string();

                if (strcmp("none", vidcap_params_get_driver(opt->vidcap_params_head)) == 0
                                && strcmp("none", opt->requested_display) != 0)
                {
                        vidcap_params_set_device(opt->vidcap_params_tail, "testcard:2:2:1:UYVY");
                        opt->vidcap_params_tail = vidcap_params_allocate_next(opt->vidcap_params_tail);
                }

                if (strcmp("none", opt->audio.send_cfg) == 0
                                && strcmp("none", opt->audio.recv_cfg) != 0)
                {
                        parse_audio_capture_format("sample_rate=5");
                        opt->audio.send_cfg = "testcard:frames=1";
                }

                punch_c.video_rx_port = &opt->video_rx_port;
                punch_c.video_tx_port = &opt->video_tx_port;
                punch_c.audio_rx_port = &opt->audio.recv_port;
                punch_c.audio_tx_port = &opt->audio.send_port;

                punch_c.host_addr = punched_host;
                punch_c.host_addr_len = sizeof(punched_host);

                auto punch_fcn = reinterpret_cast<bool(*)(Holepunch_config)>(
                                const_cast<void *>(
                                        load_library("udp_holepunch", LIBRARY_CLASS_UNDEFINED, HOLEPUNCH_ABI_VERSION)));

                if(!punch_fcn){
                        log_msg(LOG_LEVEL_ERROR, "Failed to load holepunching module\n");
                        return EXIT_FAILURE;
                }

                if(!punch_fcn(punch_c)){
                        log_msg(LOG_LEVEL_ERROR, "Hole punching failed.\n");
                        return EXIT_FAILURE;
                }

                log_msg(LOG_LEVEL_INFO, "[holepunch] remote: %s\n rx: %d\n tx: %d\n",
                                punched_host, opt->video_rx_port, opt->video_tx_port);
                opt->requested_receiver = punched_host;
                opt->audio.host = punched_host;
#endif //HAVE_LIBJUICE
        }


        if (strcmp("none", opt->audio.recv_cfg) != 0) {
                audio_rxtx_mode |= MODE_RECEIVER;
        }

        if (strcmp("none", opt->audio.send_cfg) != 0) {
                audio_rxtx_mode |= MODE_SENDER;
        }

        if (strcmp("none", opt->requested_display) != 0) {
                opt->video_rxtx_mode |= MODE_RECEIVER;
        }
        if (strcmp("none", vidcap_params_get_driver(opt->vidcap_params_head)) != 0) {
                opt->video_rxtx_mode |= MODE_SENDER;
        }

        if (opt->video_rx_port == -1) {
                if ((opt->video_rxtx_mode & MODE_RECEIVER) == 0U) {
                        // do not occupy recv port if we are not receiving (note that this disables communication with
                        // our receiver, because RTCP ports are changed as well)
                        opt->video_rx_port = 0;
                } else {
                        opt->video_rx_port = opt->port_base;
                }
        }

        if (opt->video_tx_port == -1) {
                if ((opt->video_rxtx_mode & MODE_SENDER) == 0U) {
                        opt->video_tx_port = 0; // does not matter, we are receiver
                } else {
                        opt->video_tx_port = opt->port_base;
                }
        }

        if (opt->audio.recv_port == -1) {
                if ((audio_rxtx_mode & MODE_RECEIVER) == 0U) {
                        // do not occupy recv port if we are not receiving (note that this disables communication with
                        // our receiver, because RTCP ports are changed as well)
                        opt->audio.recv_port = 0;
                } else {
                        opt->audio.recv_port = opt->video_rx_port ? opt->video_rx_port + 2 : opt->port_base + 2;
                }
        }

        if (opt->audio.send_port == -1) {
                if ((audio_rxtx_mode & MODE_SENDER) == 0U) {
                        opt->audio.send_port = 0;
                } else {
                        opt->audio.send_port = opt->video_tx_port ? opt->video_tx_port + 2 : opt->port_base + 2;
                }
        }

        // If we are sure that this UltraGrid is sending to itself we can optimize some parameters
        // (aka "-m 9000 -l unlimited"). If ports weren't equal it is possibile that we are sending
        // to a reflector, thats why we require equal ports (we are a receiver as well).
        if (is_host_loopback(opt->requested_receiver) && opt->video_rx_port == opt->video_tx_port &&
                        opt->audio.recv_port == opt->audio.send_port) {
                opt->requested_mtu = opt->requested_mtu == 0 ? min(RTP_MAX_MTU, 65535) : opt->requested_mtu;
                opt->bitrate = opt->bitrate == RATE_DEFAULT ? RATE_UNLIMITED : opt->bitrate;
        } else {
                opt->requested_mtu = opt->requested_mtu == 0 ? 1500 : opt->requested_mtu;
                opt->bitrate = opt->bitrate == RATE_DEFAULT ? RATE_AUTO : opt->bitrate;
        }

        if((strcmp("none", opt->audio.send_cfg) != 0 || strcmp("none", opt->audio.recv_cfg) != 0) && strcmp(opt->video_protocol, "rtsp") == 0){
            //TODO: to implement a high level rxtx struct to manage different standards (i.e.:H264_STD, VP8_STD,...)
            if (strcmp(opt->audio.proto, "rtsp") != 0) {
                    LOG(LOG_LEVEL_WARNING) << "Using RTSP for video but not for audio is not recommended. Consider adding '--audio-protocol rtsp'.\n";
            }
        }
        if (strcmp(opt->audio.proto, "rtsp") == 0 && strcmp(opt->video_protocol, "rtsp") != 0) {
                LOG(LOG_LEVEL_WARNING) << "Using RTSP for audio but not for video is not recommended and might not work.\n";
        }

        return 0;
}

#define EXIT(expr) { int rc = expr; common_cleanup(init); return rc; }

int main(int argc, char *argv[])
{
        struct init_data *init = nullptr;
#if defined HAVE_SCHED_SETSCHEDULER && defined USE_RT
        struct sched_param sp;
#endif
        ug_options opt{};

        pthread_t receiver_thread_id,
                  capture_thread_id;
        bool receiver_thread_started = false,
             capture_thread_started = false;
        unsigned display_flags = 0;
        struct control_state *control = NULL;
        struct exporter *exporter = NULL;
        int ret;

        const chrono::steady_clock::time_point start_time(chrono::steady_clock::now());

        struct ug_nat_traverse *nat_traverse = nullptr;

#ifndef WIN32
        signal(SIGQUIT, crash_signal_handler);
#endif
        signal(SIGABRT, crash_signal_handler);
        signal(SIGFPE, crash_signal_handler);
        signal(SIGILL, crash_signal_handler);
        signal(SIGSEGV, crash_signal_handler);

        if ((init = common_preinit(argc, argv)) == nullptr) {
                log_msg(LOG_LEVEL_FATAL, "common_preinit() failed!\n");
                EXIT(EXIT_FAILURE);
        }

        struct state_uv uv{};
        uv_state = &uv;
        keyboard_control kc{&uv.root_module};

        print_version();
        printf("\n");

        if ((ret = parse_options(argc, argv, &opt)) != -1) {
                EXIT(ret);
        }

        if (int ret = adjust_params(&opt)) {
                EXIT(ret);
        }

        cout << BOLD("Display device   : ") << opt.requested_display << "\n";
        cout << BOLD("Capture device   : ") << vidcap_params_get_driver(opt.vidcap_params_head) << "\n";
        cout << BOLD("Audio capture    : ") << opt.audio.send_cfg << "\n";
        cout << BOLD("Audio playback   : ") << opt.audio.recv_cfg << "\n";
        cout << BOLD("MTU              : ") << opt.requested_mtu << " B\n";
        cout << BOLD("Video compression: ") << opt.requested_compression << "\n";
        cout << BOLD("Audio codec      : ") << get_name_to_audio_codec(get_audio_codec(opt.audio.codec_cfg)) << "\n";
        cout << BOLD("Network protocol : ") << video_rxtx::get_long_name(opt.video_protocol) << "\n";
        cout << BOLD("Audio FEC        : ") << opt.audio.fec_cfg << "\n";
        cout << BOLD("Video FEC        : ") << opt.requested_video_fec << "\n";
        cout << "\n";

        exporter = export_init(&uv.root_module, opt.export_opts, opt.should_export);
        if (!exporter) {
                log_msg(LOG_LEVEL_ERROR, "Export initialization failed.\n");
                EXIT(EXIT_FAILURE);
        }

        if (control_init(opt.control_port, opt.connection_type, &control, &uv.root_module, opt.force_ip_version) != 0) {
                LOG(LOG_LEVEL_FATAL) << "Error: Unable to initialize remote control!\n";
                EXIT(EXIT_FAIL_CONTROL_SOCK);
        }

        if(!opt.nat_traverse_config
                        || strncmp(opt.nat_traverse_config, "holepunch", strlen("holepunch")) != 0){
                nat_traverse = start_nat_traverse(opt.nat_traverse_config, opt.requested_receiver, opt.video_rx_port, opt.audio.recv_port);
                if(!nat_traverse){
                        exit_uv(1);
                        goto cleanup;
                }
        }

        uv.audio = audio_cfg_init (&uv.root_module, &opt.audio,
                        opt.requested_encryption,
                        opt.force_ip_version, opt.requested_mcast_if,
                        opt.bitrate, &audio_offset, &start_time,
                        opt.requested_mtu, opt.requested_ttl, exporter);
        if(!uv.audio) {
                exit_uv(EXIT_FAIL_AUDIO);
                goto cleanup;
        }

        display_flags |= audio_get_display_flags(uv.audio);

        // Display initialization should be prior to modules that may use graphic card (eg. GLSL) in order
        // to initalize shared resource (X display) first
        ret =
             initialize_video_display(&uv.root_module, opt.requested_display, opt.display_cfg, display_flags, opt.postprocess, &uv.display_device);
        if (ret < 0) {
                printf("Unable to open display device: %s\n",
                       opt.requested_display);
                exit_uv(EXIT_FAIL_DISPLAY);
                goto cleanup;
        } else if(ret > 0) {
                exit_uv(EXIT_SUCCESS);
                goto cleanup;
        }

        printf("Display initialized-%s\n", opt.requested_display);

        /* Pass embedded/analog/AESEBU flags to selected vidcap
         * device. */
        if (audio_capture_get_vidcap_flags(opt.audio.send_cfg)) {
                int index = audio_capture_get_vidcap_index(opt.audio.send_cfg);
                int i = 0;
                for (auto *vidcap_params_it = opt.vidcap_params_head; vidcap_params_it != opt.vidcap_params_tail; vidcap_params_it = vidcap_params_get_next(vidcap_params_it)) {
                        if (index == i || index == -1) {
                                unsigned int orig_flags =
                                        vidcap_params_get_flags(vidcap_params_it);
                                vidcap_params_set_flags(vidcap_params_it, orig_flags
                                                | audio_capture_get_vidcap_flags(opt.audio.send_cfg));
                        }
                        i++;
                }
                if (index >= i) {
                        LOG(LOG_LEVEL_ERROR) << "Entered index for non-existing vidcap (audio).\n";
                        exit_uv(EXIT_FAIL_CAPTURE);
                        goto cleanup;
                }
        }

        ret = initialize_video_capture(&uv.root_module, opt.vidcap_params_head, &uv.capture_device);
        if (ret < 0) {
                printf("Unable to open capture device: %s\n",
                                vidcap_params_get_driver(opt.vidcap_params_head));
                exit_uv(EXIT_FAIL_CAPTURE);
                goto cleanup;
        } else if(ret > 0) {
                exit_uv(EXIT_SUCCESS);
                goto cleanup;
        }
        printf("Video capture initialized-%s\n", vidcap_params_get_driver(opt.vidcap_params_head));

        signal(SIGINT, signal_handler);
        signal(SIGTERM, signal_handler);
#ifndef WIN32
        signal(SIGHUP, signal_handler);
#endif

#ifdef USE_RT
#ifdef HAVE_SCHED_SETSCHEDULER
        sp.sched_priority = sched_get_priority_max(SCHED_FIFO);
        if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
                printf("WARNING: Unable to set real-time scheduling\n");
        }
#else
        printf("WARNING: System does not support real-time scheduling\n");
#endif /* HAVE_SCHED_SETSCHEDULER */
#endif /* USE_RT */

        control_start(control);
        kc.start();

        try {
                map<string, param_u> params;

                // common
                params["parent"].ptr = &uv.root_module;
                params["exporter"].ptr = exporter;
                params["compression"].str = opt.requested_compression;
                params["rxtx_mode"].i = opt.video_rxtx_mode;
                params["paused"].b = opt.start_paused;

                // iHDTV
                params["argc"].i = argc;
                params["argv"].ptr = argv;
                params["capture_device"].ptr = (opt.video_rxtx_mode & MODE_SENDER) != 0U ? uv.capture_device : nullptr;
                params["display_device"].ptr = (opt.video_rxtx_mode & MODE_RECEIVER) != 0U ? uv.display_device : nullptr;

                //RTP
                params["mtu"].i = opt.requested_mtu;
                params["ttl"].i = opt.requested_ttl;
                params["receiver"].str = opt.requested_receiver;
                params["rx_port"].i = opt.video_rx_port;
                params["tx_port"].i = opt.video_tx_port;
                params["force_ip_version"].i = opt.force_ip_version;
                params["mcast_if"].str = opt.requested_mcast_if;
                params["mtu"].i = opt.requested_mtu;
                params["fec"].str = opt.requested_video_fec;
                params["encryption"].str = opt.requested_encryption;
                params["bitrate"].ll = opt.bitrate;
                params["start_time"].cptr = (const void *) &start_time;
                params["video_delay"].vptr = (volatile void *) &video_offset;

                // UltraGrid RTP
                params["decoder_mode"].l = (long) opt.decoder_mode;
                params["display_device"].ptr = uv.display_device;

                // SAGE + RTSP
                params["opts"].str = opt.video_protocol_opts;

                // RTSP/SDP
                params["audio_codec"].l = get_audio_codec(opt.audio.codec_cfg);
                params["audio_sample_rate"].i = get_audio_codec_sample_rate(opt.audio.codec_cfg) ? get_audio_codec_sample_rate(opt.audio.codec_cfg) : 48000;
                params["audio_channels"].i = audio_capture_channels;
                params["audio_bps"].i = 2;
                params["a_rx_port"].i = opt.audio.recv_port;
                params["a_tx_port"].i = opt.audio.send_port;

                if (strcmp(opt.video_protocol, "rtsp") == 0) {
                        rtps_types_t avType;
                        if(strcmp("none", vidcap_params_get_driver(opt.vidcap_params_head)) != 0 && (strcmp("none",opt.audio.send_cfg) != 0)) avType = av; //AVStream
                        else if((strcmp("none",opt.audio.send_cfg) != 0)) avType = audio; //AStream
                        else if(strcmp("none", vidcap_params_get_driver(opt.vidcap_params_head))) avType = video; //VStream
                        else {
                                printf("[RTSP SERVER CHECK] no stream type... check capture devices input...\n");
                                avType = none;
                        }

                        params["avType"].l = (long) avType;
                }

                uv.state_video_rxtx = video_rxtx::create(opt.video_protocol, params);
                if (!uv.state_video_rxtx) {
                        if (strcmp(opt.video_protocol, "help") != 0) {
                                throw string("Requested RX/TX cannot be created (missing library?)");
                        } else {
                                throw 0;
                        }
                }

                if ((opt.video_rxtx_mode & MODE_RECEIVER) != 0U) {
                        if (!uv.state_video_rxtx->supports_receiving()) {
                                fprintf(stderr, "Selected RX/TX mode doesn't support receiving.\n");
                                exit_uv(EXIT_FAILURE);
                                goto cleanup;
                        }
                        // init module here so as it is capable of receiving messages
                        if (pthread_create
                                        (&receiver_thread_id, NULL, video_rxtx::receiver_thread,
                                         (void *) uv.state_video_rxtx) != 0) {
                                perror("Unable to create display thread!\n");
                                exit_uv(EXIT_FAILURE);
                                goto cleanup;
                        } else {
                                receiver_thread_started = true;
                        }
                }

                if ((opt.video_rxtx_mode & MODE_SENDER) != 0U) {
                        uv.capture_device_name = vidcap_params_get_driver(opt.vidcap_params_head);
                        if (pthread_create
                                        (&capture_thread_id, NULL, capture_thread,
                                         (void *) &uv.root_module) != 0) {
                                perror("Unable to create capture thread!\n");
                                exit_uv(EXIT_FAILURE);
                                goto cleanup;
                        } else {
                                capture_thread_started = true;
                        }
                }

                if(audio_get_display_flags(uv.audio)) {
                        audio_register_display_callbacks(uv.audio,
                                       uv.display_device,
                                       (void (*)(void *, struct audio_frame *)) display_put_audio_frame,
                                       (int (*)(void *, int, int, int)) display_reconfigure_audio,
                                       (int (*)(void *, int, void *, size_t *)) display_ctl_property);
                }

                audio_start(uv.audio);

                // This has to be run after start of capture thread since it may request
                // captured video format information.
                if (opt.print_capabilities_req) {
                        print_capabilities(&uv.root_module, strcmp("none", vidcap_params_get_driver(opt.vidcap_params_head)) != 0);
                        exit_uv(EXIT_SUCCESS);
                        goto cleanup;
                }

                if (display_needs_mainloop(uv.display_device) && mainloop) {
                        throw string("Cannot run display when "
                                        "another mainloop registered!\n");
                }
                display_run(uv.display_device);
                if (mainloop) {
                        mainloop(mainloop_udata);
                }
                display_join(uv.display_device);
        } catch (ug_no_error const &e) {
                exit_uv(0);
        } catch (ug_runtime_error const &e) {
                cerr << e.what() << endl;
                exit_uv(e.get_code());
        } catch (runtime_error const &e) {
                cerr << e.what() << endl;
                exit_uv(EXIT_FAILURE);
        } catch (exception const &e) {
                cerr << e.what() << endl;
                exit_uv(EXIT_FAILURE);
        } catch (string const &str) {
                cerr << str << endl;
                exit_uv(EXIT_FAILURE);
        } catch (int i) {
                exit_uv(i);
        }

cleanup:
        if (strcmp("none", opt.requested_display) != 0 &&
                        receiver_thread_started)
                pthread_join(receiver_thread_id, NULL);

        if ((opt.video_rxtx_mode & MODE_SENDER) != 0U
                        && capture_thread_started) {
                pthread_join(capture_thread_id, NULL);
        }

        /* also wait for audio threads */
        audio_join(uv.audio);
        if (uv.state_video_rxtx)
                uv.state_video_rxtx->join();

        export_destroy(exporter);

        signal(SIGINT, SIG_DFL);
        signal(SIGTERM, SIG_DFL);
#ifndef WIN32
        signal(SIGHUP, SIG_DFL);
        signal(SIGALRM, hang_signal_handler);
#endif
        alarm(5); // prevent exit hangs

        if(uv.audio)
                audio_done(uv.audio);
        delete uv.state_video_rxtx;

        if (uv.capture_device)
                vidcap_done(uv.capture_device);
        if (uv.display_device)
                display_done(uv.display_device);

        stop_nat_traverse(nat_traverse);

        kc.stop();
        control_done(control);

        uv.stop();
        common_cleanup(init);

        printf("Exit\n");

        return exit_status;
}

/* vim: set expandtab sw=8: */
