From 93097a178c6d0d50cbcfdf26a1a63fd571138484 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Tue, 7 Oct 2014 23:21:34 +0200 Subject: cleanup for multiple binaries --- src/Makefile | 8 +- src/options-rtp.c | 460 ++++++++++++++++++++++++++++++++++++++++ src/options-rtp.h | 92 ++++++++ src/options.c | 460 ---------------------------------------- src/options.h | 92 -------- src/pipelines-rtp.c | 589 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pipelines-rtp.h | 45 ++++ src/pipelines.c | 588 --------------------------------------------------- src/pipelines.h | 45 ---- src/sydra-rtp.c | 4 +- 10 files changed, 1192 insertions(+), 1191 deletions(-) create mode 100644 src/options-rtp.c create mode 100644 src/options-rtp.h delete mode 100644 src/options.c delete mode 100644 src/options.h create mode 100644 src/pipelines-rtp.c create mode 100644 src/pipelines-rtp.h delete mode 100644 src/pipelines.c delete mode 100644 src/pipelines.h diff --git a/src/Makefile b/src/Makefile index a45140b..0553f08 100644 --- a/src/Makefile +++ b/src/Makefile @@ -40,12 +40,12 @@ EXECUTABLE_RTP := sydra-rtp EXECUTABLES := $(EXECUTABLE_RTP) C_OBJS_COMMON := log.o \ - options.o \ - utils.o \ - pipelines.o \ - udp.o + utils.o C_OBJS_RTP := $(C_OBJS_COMMON) \ + options-rtp.o \ + pipelines-rtp.o \ + udp.o \ sydra-rtp.o C_SRCS_RTP := $(C_OBJS_RTP:%.o=%.c) diff --git a/src/options-rtp.c b/src/options-rtp.c new file mode 100644 index 0000000..70cc5fc --- /dev/null +++ b/src/options-rtp.c @@ -0,0 +1,460 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * + * + * Copyright (C) 2014 Christian Pointner + * + * This file is part of sydra. + * + * sydra 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 + * any later version. + * + * sydra 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 sydra. If not, see . + * + * In addition, as a special exception, the copyright holders hereby + * grant permission for non-GPL-compatible GStreamer plugins to be used + * and distributed together with GStreamer and sydra. + * This permission goes above and beyond the permissions granted by the + * GPL license sydra is covered by. + */ + +#include "datatypes.h" +#include "config-rtp.h" + +#include "options-rtp.h" +#include "log.h" + +#include +#include +#include +#include + +static void options_defaults(options_t* opt) +{ + if(!opt) + return; + + opt->progname_ = g_strdup("sydra-rtp"); + opt->daemonize_ = TRUE; + opt->username_ = NULL; + opt->groupname_ = NULL; + opt->chroot_dir_ = NULL; + opt->pid_file_ = NULL; + opt->log_targets_ = NULL; + opt->debug_ = FALSE; + + opt->appname_ = NULL; + opt->mode_ = SENDER; + + opt->source_ = g_strdup("v4l2src ! videoconvert ! videoscale ! video/x-raw,format=I420,width=864,height=480,framerate=25/1,pixel-aspect-ratio=1/1 ! identity name=\"videosrc\" " \ + "autoaudiosrc ! audio/x-raw,format=S16LE,channels=1,rate=48000 ! identity name=\"audiosrc\""); + opt->sink_ = g_strdup("videoconvert name=\"videosink\" ! videoscale add-borders=true ! xvimagesink " \ + "audioconvert name=\"audiosink\" ! autoaudiosink"); + + opt->video_enc_ = g_strdup("vp8enc keyframe-max-dist=25 error-resilient=2 end-usage=1 target-bitrate=1200000 cpu-used=4 deadline=1000000 threads=2"); + opt->video_payloader_ = g_strdup("rtpvp8pay"); + opt->video_caps_ = g_strdup("application/x-rtp,media=video,clock-rate=90000,encoding-name=VP8-DRAFT-IETF-01,caps=\"video/x-vp8\""); + opt->video_depayloader_ = g_strdup("rtpvp8depay"); + opt->video_dec_ = g_strdup("vp8dec"); + + opt->audio_enc_ = g_strdup("opusenc bitrate=64000 cbr=true packet-loss-percentage=0 inband-fec=false"); + opt->audio_payloader_ = g_strdup("rtpopuspay"); + opt->audio_caps_ = g_strdup("application/x-rtp,media=audio,clock-rate=48000,payload=96,encoding-name=X-GST-OPUS-DRAFT-SPITTKA-00,caps=\"audio/x-opus\""); + opt->audio_depayloader_ = g_strdup("rtpopusdepay"); + opt->audio_dec_ = g_strdup("opusdec"); + + opt->rtp_host_ = NULL; + opt->rtp_port_base_ = 5000; + opt->rtp_addr_local_ = NULL; + opt->rtp_port_base_local_ = 5000; + opt->rtp_host_reflector_ = NULL; + opt->rtp_port_base_reflector_ = 6000; + opt->auto_client_ = TRUE; + opt->timeout_ = 30; + opt->keepalive_int_ = 10; + + opt->preview_videosink_ = NULL; + + opt->video_enc_rec_ = NULL; + opt->audio_enc_rec_ = NULL; + opt->rec_mux_ = NULL; + opt->rec_name_format_ = g_strdup("./recordings/%Y-%m-%d_%H-%M-%S"); +} + +static GQuark options_error_quark() +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string("sydra_options_error"); + + return quark; +} + +static GOptionGroup* options_get_avsend_group(options_t* opt) +{ + GOptionEntry avsend_entries[] = { + { "source", 0, 0, G_OPTION_ARG_STRING, &opt->source_, + "pipeline for raw video and audio", "BIN DESCRIPTION" }, + { "video-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_enc_, + "pipeline for video encoder (i.e. videoconvert ! vp8enc)", "BIN DESCRIPTION" }, + { "video-payloader", 0, 0, G_OPTION_ARG_STRING, &opt->video_payloader_, + "video RTP payloader element (i.e. rtpvp8pay)", "ELEMENT" }, + { "previewsink", 0, 0, G_OPTION_ARG_STRING, &opt->preview_videosink_, + "video sink element for local preview (i.e. textoverlay text=\" preview \" ! xvimagesink) - leave empty to disable preview", "BIN DESCRIPTION" }, + { "audio-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_enc_, + "pipeline for audio encoder (i.e. audioconvert ! opusenc)", "BIN DESCRIPTION" }, + { "audio-payloader", 0, 0, G_OPTION_ARG_STRING, &opt->audio_payloader_, + "audio RTP payloader element (i.e. rtpopuspay)", "ELEMENT" }, + { NULL } + }; + GOptionGroup* avsend_group = g_option_group_new ("avsend", "Audio/Video Sender Options", + "Show Audio/Video Sender Options", opt, NULL); + if(!avsend_group) return NULL; + g_option_group_add_entries(avsend_group, avsend_entries); + + return avsend_group; +} + +static GOptionGroup* options_get_avrecv_group(options_t* opt) +{ + GOptionEntry avrecv_entries[] = { + { "video-caps", 0, 0, G_OPTION_ARG_STRING, &opt->video_caps_, + "Caps for incoming Video RTP packets", "CAPS" }, + { "video-depayloader", 0, 0, G_OPTION_ARG_STRING, &opt->video_depayloader_, + "video RTP depayloader element (i.e. rtpvp8depay)", "ELEMENT" }, + { "video-decoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_dec_, + "pipeline for video decoder (i.e. vp8dec)", "BIN DESCRIPTION" }, + { "audio-caps", 0, 0, G_OPTION_ARG_STRING, &opt->audio_caps_, + "Caps for incoming Audio RTP packets", "CAPS" }, + { "audio-depayloader", 0, 0, G_OPTION_ARG_STRING, &opt->audio_depayloader_, + "audio RTP depayloader element (i.e. rtpopusdepay)", "ELEMENT" }, + { "audio-decoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_dec_, + "pipeline for audio decoder (i.e. opusdnc)", "BIN DESCRIPTION" }, + { "sink", 0, 0, G_OPTION_ARG_STRING, &opt->sink_, + "video and audio sink bin", "BIN DESCRIPTION" }, + { NULL } + }; + GOptionGroup* avrecv_group = g_option_group_new ("avrecv", "Audio/Video Receiver Options", + "Show Audio/Video Receiver Options", opt, NULL); + if(!avrecv_group) return NULL; + g_option_group_add_entries(avrecv_group, avrecv_entries); + + return avrecv_group; +} + +static GOptionGroup* options_get_rtp_group(options_t* opt) +{ + GOptionEntry rtp_entries[] = { + { "rtp-host", 'a', 0, G_OPTION_ARG_STRING, &opt->rtp_host_, + "remote host for RTP packets", "HOST" }, + { "rtp-port-base", 'o', 0, G_OPTION_ARG_INT, &opt->rtp_port_base_, + "base number for remote ports", "PORT" }, + { "rtp-addr-local", 'A', 0, G_OPTION_ARG_STRING, &opt->rtp_addr_local_, + "local address to bind to", "ADDRESS" }, + { "rtp-port-base-local", 'O', 0, G_OPTION_ARG_INT, &opt->rtp_port_base_local_, + "base number for local ports to bind to", "PORT" }, + { "rtp-host-reflector", 0, 0, G_OPTION_ARG_STRING, &opt->rtp_host_reflector_, + "remote host where incoming RTP packets should be reflected to - receiver mode only!", "HOST" }, + { "rtp-port-base-reflector", 0, 0, G_OPTION_ARG_INT, &opt->rtp_port_base_reflector_, + "base number for ports where incoming RTP packets should be reflected to - receiver mode only!", "PORT" }, + { "no-auto-client", 'c', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt->auto_client_, + "disable auto-detection for clients (aka. ignore incoming keepalives) - sender mode only!", NULL }, + { "timeout", 't', 0, G_OPTION_ARG_INT, &opt->timeout_, + "client timeout in seconds (0 means no timeout) - sender mode only!", "VALUE" }, + { "keepalive-interval", 'k', 0, G_OPTION_ARG_INT, &opt->keepalive_int_, + "interval in seconds for sending out keep alive messages (0 means no keep alives) - receiver mode only!", "VALUE" }, + { NULL } + }; + GOptionGroup* rtp_group = g_option_group_new ("rtp", "RTP Options", + "Show RTP Options", opt, NULL); + if(!rtp_group) return NULL; + g_option_group_add_entries(rtp_group, rtp_entries); + + return rtp_group; +} + +static GOptionGroup* options_get_rec_group(options_t* opt) +{ + GOptionEntry rec_entries[] = { + { "rec-video-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_enc_rec_, + "pipeline for video encoder for recording (i.e. videoconvert ! jpegenc) - leave empty to use same as for RTP", "BIN DESCRIPTION" }, + { "rec-audio-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_enc_rec_, + "pipeline for audio encoder for recording (i.e. audioconvert ! vorbisenc) - leave empty to use same as for RTP", "BIN DESCRIPTION" }, + { "rec-mux", 0, 0, G_OPTION_ARG_STRING, &opt->rec_mux_, + "muxer element for recording (i.e. matroskamux) - leave empty to disable recording", "ELEMENT" }, + { "rec-name-format", 0, 0, G_OPTION_ARG_STRING, &opt->rec_name_format_, + "the recording file name format string, see manpage of strftime for the syntax", "FORMATSTRING" }, + { NULL } + }; + GOptionGroup* rec_group = g_option_group_new ("rec", "Recording Options", + "Show Recording Options", opt, NULL); + if(!rec_group) return NULL; + g_option_group_add_entries(rec_group, rec_entries); + + return rec_group; +} + +static gboolean options_parse_mode(const gchar *option_name, const gchar *value, gpointer data, GError **error) +{ + options_t* opt = (options_t*)data; + + if(!strcmp(value, "sender")) + opt->mode_ = SENDER; + else if(!strcmp(value, "receiver")) + opt->mode_ = RECEIVER; + else { + g_set_error(error, options_error_quark(), G_OPTION_ERROR_FAILED, + "Invalid value for mode parameter '%s' - allowed values are: sender, receiver", value); + return FALSE; + } + return TRUE; +} + +static int options_parse_post(options_t* opt); + +int options_parse(options_t* opt, int argc, char* argv[]) +{ + if(!opt) + return -1; + + options_defaults(opt); + + g_free(opt->progname_); + opt->progname_ = g_strdup(argv[0]); + if(!opt->progname_) + return -127; + + gboolean show_version = FALSE; + GOptionEntry main_entries[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, + "print version info and exit", NULL }, + { "nodaemonize", 'D', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt->daemonize_, + "don't run in background", NULL }, + { "username", 'u', 0, G_OPTION_ARG_STRING, &opt->username_, + "change to this user", "USERNAME" }, + { "group", 'g', 0, G_OPTION_ARG_STRING, &opt->groupname_, + "change to this group", "GROUP" }, + { "chroot", 'C', 0, G_OPTION_ARG_STRING, &opt->chroot_dir_, + "chroot to this directory", "PATH" }, + { "write-pid", 'P', 0, G_OPTION_ARG_STRING, &opt->pid_file_, + "write pid to this file", "PATH" }, + { "log", 'L', 0, G_OPTION_ARG_STRING_ARRAY, &opt->log_targets_, + "add a log target, can be invoked several times", ":[,[,..]]" }, + { "debug", 'U', 0, G_OPTION_ARG_NONE, &opt->debug_, + "don't daemonize and log to stdout with maximum log level", NULL }, + { "appname", 'n', 0, G_OPTION_ARG_STRING, &opt->appname_, + "set the application name (will be used by xvimagesink for window title)", "NAME" }, + { "mode", 'm', 0, G_OPTION_ARG_CALLBACK, options_parse_mode, + "the main operation mode", "(sender|receiver)" }, + { NULL } + }; + GOptionContext *ctx = g_option_context_new("- spreadspace streaming hydra "); + GOptionGroup* main_group = g_option_group_new ("main", "Application Options", + "Show Application Options", opt, NULL); + if(main_group) + g_option_group_add_entries(main_group, main_entries); + + GOptionGroup* avsend_group = options_get_avsend_group(opt); + GOptionGroup* avrecv_group = options_get_avrecv_group(opt); + GOptionGroup* rtp_group = options_get_rtp_group(opt); + GOptionGroup* rec_group = options_get_rec_group(opt); + GOptionGroup* gst_group = gst_init_get_option_group(); + if(!main_group || !avsend_group || !avrecv_group || !rtp_group || !rec_group || !gst_group) { + printf("ERROR: Failed to initialize: memory error\n"); + return -127; + } + g_option_context_set_main_group(ctx, main_group); + g_option_context_add_group(ctx, avsend_group); + g_option_context_add_group(ctx, avrecv_group); + g_option_context_add_group(ctx, rtp_group); + g_option_context_add_group(ctx, rec_group); + g_option_context_add_group(ctx, gst_group); + + GError *err = NULL; + if(!g_option_context_parse(ctx, &argc, &argv, &err)) { + printf("ERROR: Failed to initialize: %s\n", err->message); + g_error_free(err); + return 1; + } + + if(show_version) + return -1; + + return options_parse_post(opt); +} + +static int options_parse_post(options_t* opt) +{ + if(opt->rtp_port_base_ < 1 || opt->rtp_port_base_ > 65535 || + opt->rtp_port_base_local_ < 1 || opt->rtp_port_base_local_ > 65535 || + opt->rtp_port_base_reflector_ < 1 || opt->rtp_port_base_reflector_ > 65535) { + printf("ERROR: Failed to initialize: rtp port is invalid\n"); + return -2; + } + + if(opt->timeout_ < 0) { + printf("ERROR: Failed to initialize: timeout is invalid\n"); + return -3; + } + if(opt->keepalive_int_ < 0) { + printf("ERROR: Failed to initialize: keep alive interval is invalid\n"); + return -3; + } + + if(opt->mode_ == SENDER) { + if(opt->rtp_host_ && opt->auto_client_) { + printf("WARNING: you have set a remote RTP host and the automatic client handling is enabled.\n" \ + " Please mind that the remote RTP host is always added as a receiver and it shouldn't be\n" \ + " configured to send keepalives. This would re-add the receiver to the list of clients\n" \ + " and as a result duplicate packages will be sent.\n" \ + " Also the remote RTP host is excluded from client timeout handling and remove requests\n" \ + " will get ignored.\n\n"); + } else if(!opt->rtp_host_ && !opt->auto_client_) { + printf("WARNING: both, the remote RTP host, as well as the automatic client detection are disabled\n" \ + " this means that no streaming will be done. Recording will work but this is most probably\n" \ + " not what you intended - please check your configuration.\n\n"); + } + + if(opt->rtp_host_reflector_) { + printf("WARNING: The mode of operation is set to sender and the RTP packet reflector has a remote\n" \ + " host configured. Mind that in sender mode the reflector will not be used!\n\n"); + } + } + if(opt->debug_) { + opt->daemonize_ = 0; + g_strfreev(opt->log_targets_); + opt->log_targets_ = g_new(gchar*, 2); + if(!opt->log_targets_) return -127; + opt->log_targets_[0] = g_strdup("stdout:5"); + if(!(opt->log_targets_[0])) return -127; + opt->log_targets_[1] = NULL; + } + + if(!opt->log_targets_ || !g_strv_length(opt->log_targets_)) { + opt->log_targets_ = g_new(gchar*, 2); + if(!opt->log_targets_) return -127; + opt->log_targets_[0] = g_strdup("syslog:3,sydra-rtp,daemon"); + if(!(opt->log_targets_[0])) return -127; + opt->log_targets_[1] = NULL; + } + + return 0; +} + +void options_clear(options_t* opt) +{ + if(!opt) + return; + + g_free(opt->progname_); + g_free(opt->username_); + g_free(opt->groupname_); + g_free(opt->chroot_dir_); + g_free(opt->pid_file_); + g_strfreev(opt->log_targets_); + g_free(opt->appname_); + g_free(opt->source_); + g_free(opt->sink_); + g_free(opt->video_enc_); + g_free(opt->video_payloader_); + g_free(opt->video_caps_); + g_free(opt->video_depayloader_); + g_free(opt->video_dec_); + g_free(opt->audio_enc_); + g_free(opt->audio_payloader_); + g_free(opt->audio_caps_); + g_free(opt->audio_depayloader_); + g_free(opt->audio_dec_); + g_free(opt->rtp_host_); + g_free(opt->rtp_addr_local_); + g_free(opt->rtp_host_reflector_); + g_free(opt->preview_videosink_); + g_free(opt->video_enc_rec_); + g_free(opt->audio_enc_rec_); + g_free(opt->rec_mux_); + g_free(opt->rec_name_format_); +} + +void options_print_version() +{ + printf("%s\n", VERSION_STRING_0); +#if defined(__clang__) + printf("%s, using CLANG %s\n", VERSION_STRING_1, __clang_version__); +#elif defined(__GNUC__) + printf("%s, using GCC %d.%d.%d\n", VERSION_STRING_1, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#else + printf("%s\n", VERSION_STRING_1); +#endif + const gchar *nano_str; + guint major, minor, micro, nano; + gst_version(&major, &minor, µ, &nano); + if (nano == 1) + nano_str = " (CVS)"; + else if (nano == 2) + nano_str = " (Prerelease)"; + else + nano_str = ""; + printf(" linked against GStreamer %d.%d.%d%s\n", major, minor, micro, nano_str); +} + +void options_print(options_t* opt) +{ + if(!opt) + return; + + printf(" progname: '%s'\n", opt->progname_); + printf(" daemonize: %s\n", opt->daemonize_ ? "true" : "false"); + printf(" username: '%s'\n", opt->username_); + printf(" groupname: '%s'\n", opt->groupname_); + printf(" chroot_dir: '%s'\n", opt->chroot_dir_); + printf(" pid_file: '%s'\n", opt->pid_file_); + printf(" log_targets: \n"); + gchar* lt = opt->log_targets_ ? g_strjoinv ("'\n '", opt->log_targets_) : NULL; + if(lt) { + printf(" '%s'\n", lt); + g_free(lt); + } + printf(" debug: %s\n", opt->debug_ ? "true" : "false"); + printf(" appname: >>%s<<\n", opt->appname_); + printf(" mode: >>%s<<\n", opt->mode_ == SENDER ? "sender" : "receiver"); + printf(" source: >>%s<<\n", opt->source_); + printf(" sink: >>%s<<\n", opt->sink_); + printf(" video_enc: >>%s<<\n", opt->video_enc_); + printf(" video_payloader: >>%s<<\n", opt->video_payloader_); + printf(" video_caps: >>%s<<\n", opt->video_caps_); + printf(" video_depayloader: >>%s<<\n", opt->video_depayloader_); + printf(" video_dec: >>%s<<\n", opt->video_dec_); + printf(" audio_enc: >>%s<<\n", opt->audio_enc_); + printf(" audio_payloader: >>%s<<\n", opt->audio_payloader_); + printf(" audio_caps: >>%s<<\n", opt->audio_caps_); + printf(" audio_depayloader: >>%s<<\n", opt->audio_depayloader_); + printf(" audio_dec: >>%s<<\n", opt->audio_dec_); + printf(" rtp_host: >>%s<<\n", opt->rtp_host_); + printf(" rtp_port_base: %d\n", opt->rtp_port_base_); + printf(" rtp_addr_local: >>%s<<\n", opt->rtp_addr_local_); + printf(" rtp_port_base_local: %d\n", opt->rtp_port_base_local_); + printf(" rtp_host_reflector: >>%s<<\n", opt->rtp_host_reflector_); + printf(" rtp_port_base_reflector: %d\n", opt->rtp_port_base_reflector_); + printf(" auto_client: %s\n", opt->auto_client_ ? "true" : "false"); + printf(" timeout: %d\n", opt->timeout_); + printf(" keepalive_int: %d\n", opt->keepalive_int_); + printf(" preview_video_sink: >>%s<<\n", opt->preview_videosink_); + printf(" video_enc_rec: >>%s<<\n", opt->video_enc_rec_); + printf(" audio_enc_rec: >>%s<<\n", opt->audio_enc_rec_); + printf(" rec_mux: >>%s<<\n", opt->rec_mux_); + printf(" rec_name_format: '%s'\n", opt->rec_name_format_); +} diff --git a/src/options-rtp.h b/src/options-rtp.h new file mode 100644 index 0000000..d7f7daf --- /dev/null +++ b/src/options-rtp.h @@ -0,0 +1,92 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * + * + * Copyright (C) 2014 Christian Pointner + * + * This file is part of sydra. + * + * sydra 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 + * any later version. + * + * sydra 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 sydra. If not, see . + * + * In addition, as a special exception, the copyright holders hereby + * grant permission for non-GPL-compatible GStreamer plugins to be used + * and distributed together with GStreamer and sydra. + * This permission goes above and beyond the permissions granted by the + * GPL license sydra is covered by. + */ + +#ifndef SYDRA_options_h_INCLUDED +#define SYDRA_options_h_INCLUDED + +#include "datatypes.h" + +struct options_struct { + gchar* progname_; + gboolean daemonize_; + gchar* username_; + gchar* groupname_; + gchar* chroot_dir_; + gchar* pid_file_; + gchar** log_targets_; + gboolean debug_; + + gchar* appname_; + sydra_rtp_mode_t mode_; + + gchar* source_; + gchar* sink_; + + gchar* video_enc_; + gchar* video_payloader_; + gchar* video_caps_; + gchar* video_depayloader_; + gchar* video_dec_; + + gchar* audio_enc_; + gchar* audio_payloader_; + gchar* audio_caps_; + gchar* audio_depayloader_; + gchar* audio_dec_; + + gchar* rtp_host_; + gint rtp_port_base_; + gchar* rtp_addr_local_; + gint rtp_port_base_local_; + gchar* rtp_host_reflector_; + gint rtp_port_base_reflector_; + gboolean auto_client_; + gint timeout_; + gint keepalive_int_; + + gchar* preview_videosink_; + + gchar* video_enc_rec_; + gchar* audio_enc_rec_; + gchar* rec_mux_; + gchar* rec_name_format_; +}; +typedef struct options_struct options_t; + +int options_parse(options_t* opt, int argc, char* argv[]); +void options_clear(options_t* opt); +void options_print_version(); +void options_print(options_t* opt); + +#endif diff --git a/src/options.c b/src/options.c deleted file mode 100644 index 9361759..0000000 --- a/src/options.c +++ /dev/null @@ -1,460 +0,0 @@ -/* - * sydra - * - * sydra is a toolbox which allows you to set up multiple bidirectional - * Video/Audio streams from external locations. - * sydra has been written to be used for the Elevate Festival in Graz - * Austria in order to involve external locations to present themselves - * at the festival. - * - * - * Copyright (C) 2014 Christian Pointner - * - * This file is part of sydra. - * - * sydra 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 - * any later version. - * - * sydra 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 sydra. If not, see . - * - * In addition, as a special exception, the copyright holders hereby - * grant permission for non-GPL-compatible GStreamer plugins to be used - * and distributed together with GStreamer and sydra. - * This permission goes above and beyond the permissions granted by the - * GPL license sydra is covered by. - */ - -#include "datatypes.h" -#include "config-rtp.h" - -#include "options.h" -#include "log.h" - -#include -#include -#include -#include - -static void options_defaults(options_t* opt) -{ - if(!opt) - return; - - opt->progname_ = g_strdup("sydra-rtp"); - opt->daemonize_ = TRUE; - opt->username_ = NULL; - opt->groupname_ = NULL; - opt->chroot_dir_ = NULL; - opt->pid_file_ = NULL; - opt->log_targets_ = NULL; - opt->debug_ = FALSE; - - opt->appname_ = NULL; - opt->mode_ = SENDER; - - opt->source_ = g_strdup("v4l2src ! videoconvert ! videoscale ! video/x-raw,format=I420,width=864,height=480,framerate=25/1,pixel-aspect-ratio=1/1 ! identity name=\"videosrc\" " \ - "autoaudiosrc ! audio/x-raw,format=S16LE,channels=1,rate=48000 ! identity name=\"audiosrc\""); - opt->sink_ = g_strdup("videoconvert name=\"videosink\" ! videoscale add-borders=true ! xvimagesink " \ - "audioconvert name=\"audiosink\" ! autoaudiosink"); - - opt->video_enc_ = g_strdup("vp8enc keyframe-max-dist=25 error-resilient=2 end-usage=1 target-bitrate=1200000 cpu-used=4 deadline=1000000 threads=2"); - opt->video_payloader_ = g_strdup("rtpvp8pay"); - opt->video_caps_ = g_strdup("application/x-rtp,media=video,clock-rate=90000,encoding-name=VP8-DRAFT-IETF-01,caps=\"video/x-vp8\""); - opt->video_depayloader_ = g_strdup("rtpvp8depay"); - opt->video_dec_ = g_strdup("vp8dec"); - - opt->audio_enc_ = g_strdup("opusenc bitrate=64000 cbr=true packet-loss-percentage=0 inband-fec=false"); - opt->audio_payloader_ = g_strdup("rtpopuspay"); - opt->audio_caps_ = g_strdup("application/x-rtp,media=audio,clock-rate=48000,payload=96,encoding-name=X-GST-OPUS-DRAFT-SPITTKA-00,caps=\"audio/x-opus\""); - opt->audio_depayloader_ = g_strdup("rtpopusdepay"); - opt->audio_dec_ = g_strdup("opusdec"); - - opt->rtp_host_ = NULL; - opt->rtp_port_base_ = 5000; - opt->rtp_addr_local_ = NULL; - opt->rtp_port_base_local_ = 5000; - opt->rtp_host_reflector_ = NULL; - opt->rtp_port_base_reflector_ = 6000; - opt->auto_client_ = TRUE; - opt->timeout_ = 30; - opt->keepalive_int_ = 10; - - opt->preview_videosink_ = NULL; - - opt->video_enc_rec_ = NULL; - opt->audio_enc_rec_ = NULL; - opt->rec_mux_ = NULL; - opt->rec_name_format_ = g_strdup("./recordings/%Y-%m-%d_%H-%M-%S"); -} - -static GQuark options_error_quark() -{ - static GQuark quark = 0; - if (!quark) - quark = g_quark_from_static_string("sydra_options_error"); - - return quark; -} - -static GOptionGroup* options_get_avsend_group(options_t* opt) -{ - GOptionEntry avsend_entries[] = { - { "source", 0, 0, G_OPTION_ARG_STRING, &opt->source_, - "pipeline for raw video and audio", "BIN DESCRIPTION" }, - { "video-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_enc_, - "pipeline for video encoder (i.e. videoconvert ! vp8enc)", "BIN DESCRIPTION" }, - { "video-payloader", 0, 0, G_OPTION_ARG_STRING, &opt->video_payloader_, - "video RTP payloader element (i.e. rtpvp8pay)", "ELEMENT" }, - { "previewsink", 0, 0, G_OPTION_ARG_STRING, &opt->preview_videosink_, - "video sink element for local preview (i.e. textoverlay text=\" preview \" ! xvimagesink) - leave empty to disable preview", "BIN DESCRIPTION" }, - { "audio-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_enc_, - "pipeline for audio encoder (i.e. audioconvert ! opusenc)", "BIN DESCRIPTION" }, - { "audio-payloader", 0, 0, G_OPTION_ARG_STRING, &opt->audio_payloader_, - "audio RTP payloader element (i.e. rtpopuspay)", "ELEMENT" }, - { NULL } - }; - GOptionGroup* avsend_group = g_option_group_new ("avsend", "Audio/Video Sender Options", - "Show Audio/Video Sender Options", opt, NULL); - if(!avsend_group) return NULL; - g_option_group_add_entries(avsend_group, avsend_entries); - - return avsend_group; -} - -static GOptionGroup* options_get_avrecv_group(options_t* opt) -{ - GOptionEntry avrecv_entries[] = { - { "video-caps", 0, 0, G_OPTION_ARG_STRING, &opt->video_caps_, - "Caps for incoming Video RTP packets", "CAPS" }, - { "video-depayloader", 0, 0, G_OPTION_ARG_STRING, &opt->video_depayloader_, - "video RTP depayloader element (i.e. rtpvp8depay)", "ELEMENT" }, - { "video-decoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_dec_, - "pipeline for video decoder (i.e. vp8dec)", "BIN DESCRIPTION" }, - { "audio-caps", 0, 0, G_OPTION_ARG_STRING, &opt->audio_caps_, - "Caps for incoming Audio RTP packets", "CAPS" }, - { "audio-depayloader", 0, 0, G_OPTION_ARG_STRING, &opt->audio_depayloader_, - "audio RTP depayloader element (i.e. rtpopusdepay)", "ELEMENT" }, - { "audio-decoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_dec_, - "pipeline for audio decoder (i.e. opusdnc)", "BIN DESCRIPTION" }, - { "sink", 0, 0, G_OPTION_ARG_STRING, &opt->sink_, - "video and audio sink bin", "BIN DESCRIPTION" }, - { NULL } - }; - GOptionGroup* avrecv_group = g_option_group_new ("avrecv", "Audio/Video Receiver Options", - "Show Audio/Video Receiver Options", opt, NULL); - if(!avrecv_group) return NULL; - g_option_group_add_entries(avrecv_group, avrecv_entries); - - return avrecv_group; -} - -static GOptionGroup* options_get_rtp_group(options_t* opt) -{ - GOptionEntry rtp_entries[] = { - { "rtp-host", 'a', 0, G_OPTION_ARG_STRING, &opt->rtp_host_, - "remote host for RTP packets", "HOST" }, - { "rtp-port-base", 'o', 0, G_OPTION_ARG_INT, &opt->rtp_port_base_, - "base number for remote ports", "PORT" }, - { "rtp-addr-local", 'A', 0, G_OPTION_ARG_STRING, &opt->rtp_addr_local_, - "local address to bind to", "ADDRESS" }, - { "rtp-port-base-local", 'O', 0, G_OPTION_ARG_INT, &opt->rtp_port_base_local_, - "base number for local ports to bind to", "PORT" }, - { "rtp-host-reflector", 0, 0, G_OPTION_ARG_STRING, &opt->rtp_host_reflector_, - "remote host where incoming RTP packets should be reflected to - receiver mode only!", "HOST" }, - { "rtp-port-base-reflector", 0, 0, G_OPTION_ARG_INT, &opt->rtp_port_base_reflector_, - "base number for ports where incoming RTP packets should be reflected to - receiver mode only!", "PORT" }, - { "no-auto-client", 'c', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt->auto_client_, - "disable auto-detection for clients (aka. ignore incoming keepalives) - sender mode only!", NULL }, - { "timeout", 't', 0, G_OPTION_ARG_INT, &opt->timeout_, - "client timeout in seconds (0 means no timeout) - sender mode only!", "VALUE" }, - { "keepalive-interval", 'k', 0, G_OPTION_ARG_INT, &opt->keepalive_int_, - "interval in seconds for sending out keep alive messages (0 means no keep alives) - receiver mode only!", "VALUE" }, - { NULL } - }; - GOptionGroup* rtp_group = g_option_group_new ("rtp", "RTP Options", - "Show RTP Options", opt, NULL); - if(!rtp_group) return NULL; - g_option_group_add_entries(rtp_group, rtp_entries); - - return rtp_group; -} - -static GOptionGroup* options_get_rec_group(options_t* opt) -{ - GOptionEntry rec_entries[] = { - { "rec-video-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->video_enc_rec_, - "pipeline for video encoder for recording (i.e. videoconvert ! jpegenc) - leave empty to use same as for RTP", "BIN DESCRIPTION" }, - { "rec-audio-encoder", 0, 0, G_OPTION_ARG_STRING, &opt->audio_enc_rec_, - "pipeline for audio encoder for recording (i.e. audioconvert ! vorbisenc) - leave empty to use same as for RTP", "BIN DESCRIPTION" }, - { "rec-mux", 0, 0, G_OPTION_ARG_STRING, &opt->rec_mux_, - "muxer element for recording (i.e. matroskamux) - leave empty to disable recording", "ELEMENT" }, - { "rec-name-format", 0, 0, G_OPTION_ARG_STRING, &opt->rec_name_format_, - "the recording file name format string, see manpage of strftime for the syntax", "FORMATSTRING" }, - { NULL } - }; - GOptionGroup* rec_group = g_option_group_new ("rec", "Recording Options", - "Show Recording Options", opt, NULL); - if(!rec_group) return NULL; - g_option_group_add_entries(rec_group, rec_entries); - - return rec_group; -} - -static gboolean options_parse_mode(const gchar *option_name, const gchar *value, gpointer data, GError **error) -{ - options_t* opt = (options_t*)data; - - if(!strcmp(value, "sender")) - opt->mode_ = SENDER; - else if(!strcmp(value, "receiver")) - opt->mode_ = RECEIVER; - else { - g_set_error(error, options_error_quark(), G_OPTION_ERROR_FAILED, - "Invalid value for mode parameter '%s' - allowed values are: sender, receiver", value); - return FALSE; - } - return TRUE; -} - -static int options_parse_post(options_t* opt); - -int options_parse(options_t* opt, int argc, char* argv[]) -{ - if(!opt) - return -1; - - options_defaults(opt); - - g_free(opt->progname_); - opt->progname_ = g_strdup(argv[0]); - if(!opt->progname_) - return -127; - - gboolean show_version = FALSE; - GOptionEntry main_entries[] = { - { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, - "print version info and exit", NULL }, - { "nodaemonize", 'D', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt->daemonize_, - "don't run in background", NULL }, - { "username", 'u', 0, G_OPTION_ARG_STRING, &opt->username_, - "change to this user", "USERNAME" }, - { "group", 'g', 0, G_OPTION_ARG_STRING, &opt->groupname_, - "change to this group", "GROUP" }, - { "chroot", 'C', 0, G_OPTION_ARG_STRING, &opt->chroot_dir_, - "chroot to this directory", "PATH" }, - { "write-pid", 'P', 0, G_OPTION_ARG_STRING, &opt->pid_file_, - "write pid to this file", "PATH" }, - { "log", 'L', 0, G_OPTION_ARG_STRING_ARRAY, &opt->log_targets_, - "add a log target, can be invoked several times", ":[,[,..]]" }, - { "debug", 'U', 0, G_OPTION_ARG_NONE, &opt->debug_, - "don't daemonize and log to stdout with maximum log level", NULL }, - { "appname", 'n', 0, G_OPTION_ARG_STRING, &opt->appname_, - "set the application name (will be used by xvimagesink for window title)", "NAME" }, - { "mode", 'm', 0, G_OPTION_ARG_CALLBACK, options_parse_mode, - "the main operation mode", "(sender|receiver)" }, - { NULL } - }; - GOptionContext *ctx = g_option_context_new("- spreadspace streaming hydra "); - GOptionGroup* main_group = g_option_group_new ("main", "Application Options", - "Show Application Options", opt, NULL); - if(main_group) - g_option_group_add_entries(main_group, main_entries); - - GOptionGroup* avsend_group = options_get_avsend_group(opt); - GOptionGroup* avrecv_group = options_get_avrecv_group(opt); - GOptionGroup* rtp_group = options_get_rtp_group(opt); - GOptionGroup* rec_group = options_get_rec_group(opt); - GOptionGroup* gst_group = gst_init_get_option_group(); - if(!main_group || !avsend_group || !avrecv_group || !rtp_group || !rec_group || !gst_group) { - printf("ERROR: Failed to initialize: memory error\n"); - return -127; - } - g_option_context_set_main_group(ctx, main_group); - g_option_context_add_group(ctx, avsend_group); - g_option_context_add_group(ctx, avrecv_group); - g_option_context_add_group(ctx, rtp_group); - g_option_context_add_group(ctx, rec_group); - g_option_context_add_group(ctx, gst_group); - - GError *err = NULL; - if(!g_option_context_parse(ctx, &argc, &argv, &err)) { - printf("ERROR: Failed to initialize: %s\n", err->message); - g_error_free(err); - return 1; - } - - if(show_version) - return -1; - - return options_parse_post(opt); -} - -static int options_parse_post(options_t* opt) -{ - if(opt->rtp_port_base_ < 1 || opt->rtp_port_base_ > 65535 || - opt->rtp_port_base_local_ < 1 || opt->rtp_port_base_local_ > 65535 || - opt->rtp_port_base_reflector_ < 1 || opt->rtp_port_base_reflector_ > 65535) { - printf("ERROR: Failed to initialize: rtp port is invalid\n"); - return -2; - } - - if(opt->timeout_ < 0) { - printf("ERROR: Failed to initialize: timeout is invalid\n"); - return -3; - } - if(opt->keepalive_int_ < 0) { - printf("ERROR: Failed to initialize: keep alive interval is invalid\n"); - return -3; - } - - if(opt->mode_ == SENDER) { - if(opt->rtp_host_ && opt->auto_client_) { - printf("WARNING: you have set a remote RTP host and the automatic client handling is enabled.\n" \ - " Please mind that the remote RTP host is always added as a receiver and it shouldn't be\n" \ - " configured to send keepalives. This would re-add the receiver to the list of clients\n" \ - " and as a result duplicate packages will be sent.\n" \ - " Also the remote RTP host is excluded from client timeout handling and remove requests\n" \ - " will get ignored.\n\n"); - } else if(!opt->rtp_host_ && !opt->auto_client_) { - printf("WARNING: both, the remote RTP host, as well as the automatic client detection are disabled\n" \ - " this means that no streaming will be done. Recording will work but this is most probably\n" \ - " not what you intended - please check your configuration.\n\n"); - } - - if(opt->rtp_host_reflector_) { - printf("WARNING: The mode of operation is set to sender and the RTP packet reflector has a remote\n" \ - " host configured. Mind that in sender mode the reflector will not be used!\n\n"); - } - } - if(opt->debug_) { - opt->daemonize_ = 0; - g_strfreev(opt->log_targets_); - opt->log_targets_ = g_new(gchar*, 2); - if(!opt->log_targets_) return -127; - opt->log_targets_[0] = g_strdup("stdout:5"); - if(!(opt->log_targets_[0])) return -127; - opt->log_targets_[1] = NULL; - } - - if(!opt->log_targets_ || !g_strv_length(opt->log_targets_)) { - opt->log_targets_ = g_new(gchar*, 2); - if(!opt->log_targets_) return -127; - opt->log_targets_[0] = g_strdup("syslog:3,sydra-rtp,daemon"); - if(!(opt->log_targets_[0])) return -127; - opt->log_targets_[1] = NULL; - } - - return 0; -} - -void options_clear(options_t* opt) -{ - if(!opt) - return; - - g_free(opt->progname_); - g_free(opt->username_); - g_free(opt->groupname_); - g_free(opt->chroot_dir_); - g_free(opt->pid_file_); - g_strfreev(opt->log_targets_); - g_free(opt->appname_); - g_free(opt->source_); - g_free(opt->sink_); - g_free(opt->video_enc_); - g_free(opt->video_payloader_); - g_free(opt->video_caps_); - g_free(opt->video_depayloader_); - g_free(opt->video_dec_); - g_free(opt->audio_enc_); - g_free(opt->audio_payloader_); - g_free(opt->audio_caps_); - g_free(opt->audio_depayloader_); - g_free(opt->audio_dec_); - g_free(opt->rtp_host_); - g_free(opt->rtp_addr_local_); - g_free(opt->rtp_host_reflector_); - g_free(opt->preview_videosink_); - g_free(opt->video_enc_rec_); - g_free(opt->audio_enc_rec_); - g_free(opt->rec_mux_); - g_free(opt->rec_name_format_); -} - -void options_print_version() -{ - printf("%s\n", VERSION_STRING_0); -#if defined(__clang__) - printf("%s, using CLANG %s\n", VERSION_STRING_1, __clang_version__); -#elif defined(__GNUC__) - printf("%s, using GCC %d.%d.%d\n", VERSION_STRING_1, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); -#else - printf("%s\n", VERSION_STRING_1); -#endif - const gchar *nano_str; - guint major, minor, micro, nano; - gst_version(&major, &minor, µ, &nano); - if (nano == 1) - nano_str = " (CVS)"; - else if (nano == 2) - nano_str = " (Prerelease)"; - else - nano_str = ""; - printf(" linked against GStreamer %d.%d.%d%s\n", major, minor, micro, nano_str); -} - -void options_print(options_t* opt) -{ - if(!opt) - return; - - printf(" progname: '%s'\n", opt->progname_); - printf(" daemonize: %s\n", opt->daemonize_ ? "true" : "false"); - printf(" username: '%s'\n", opt->username_); - printf(" groupname: '%s'\n", opt->groupname_); - printf(" chroot_dir: '%s'\n", opt->chroot_dir_); - printf(" pid_file: '%s'\n", opt->pid_file_); - printf(" log_targets: \n"); - gchar* lt = opt->log_targets_ ? g_strjoinv ("'\n '", opt->log_targets_) : NULL; - if(lt) { - printf(" '%s'\n", lt); - g_free(lt); - } - printf(" debug: %s\n", opt->debug_ ? "true" : "false"); - printf(" appname: >>%s<<\n", opt->appname_); - printf(" mode: >>%s<<\n", opt->mode_ == SENDER ? "sender" : "receiver"); - printf(" source: >>%s<<\n", opt->source_); - printf(" sink: >>%s<<\n", opt->sink_); - printf(" video_enc: >>%s<<\n", opt->video_enc_); - printf(" video_payloader: >>%s<<\n", opt->video_payloader_); - printf(" video_caps: >>%s<<\n", opt->video_caps_); - printf(" video_depayloader: >>%s<<\n", opt->video_depayloader_); - printf(" video_dec: >>%s<<\n", opt->video_dec_); - printf(" audio_enc: >>%s<<\n", opt->audio_enc_); - printf(" audio_payloader: >>%s<<\n", opt->audio_payloader_); - printf(" audio_caps: >>%s<<\n", opt->audio_caps_); - printf(" audio_depayloader: >>%s<<\n", opt->audio_depayloader_); - printf(" audio_dec: >>%s<<\n", opt->audio_dec_); - printf(" rtp_host: >>%s<<\n", opt->rtp_host_); - printf(" rtp_port_base: %d\n", opt->rtp_port_base_); - printf(" rtp_addr_local: >>%s<<\n", opt->rtp_addr_local_); - printf(" rtp_port_base_local: %d\n", opt->rtp_port_base_local_); - printf(" rtp_host_reflector: >>%s<<\n", opt->rtp_host_reflector_); - printf(" rtp_port_base_reflector: %d\n", opt->rtp_port_base_reflector_); - printf(" auto_client: %s\n", opt->auto_client_ ? "true" : "false"); - printf(" timeout: %d\n", opt->timeout_); - printf(" keepalive_int: %d\n", opt->keepalive_int_); - printf(" preview_video_sink: >>%s<<\n", opt->preview_videosink_); - printf(" video_enc_rec: >>%s<<\n", opt->video_enc_rec_); - printf(" audio_enc_rec: >>%s<<\n", opt->audio_enc_rec_); - printf(" rec_mux: >>%s<<\n", opt->rec_mux_); - printf(" rec_name_format: '%s'\n", opt->rec_name_format_); -} diff --git a/src/options.h b/src/options.h deleted file mode 100644 index d7f7daf..0000000 --- a/src/options.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * sydra - * - * sydra is a toolbox which allows you to set up multiple bidirectional - * Video/Audio streams from external locations. - * sydra has been written to be used for the Elevate Festival in Graz - * Austria in order to involve external locations to present themselves - * at the festival. - * - * - * Copyright (C) 2014 Christian Pointner - * - * This file is part of sydra. - * - * sydra 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 - * any later version. - * - * sydra 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 sydra. If not, see . - * - * In addition, as a special exception, the copyright holders hereby - * grant permission for non-GPL-compatible GStreamer plugins to be used - * and distributed together with GStreamer and sydra. - * This permission goes above and beyond the permissions granted by the - * GPL license sydra is covered by. - */ - -#ifndef SYDRA_options_h_INCLUDED -#define SYDRA_options_h_INCLUDED - -#include "datatypes.h" - -struct options_struct { - gchar* progname_; - gboolean daemonize_; - gchar* username_; - gchar* groupname_; - gchar* chroot_dir_; - gchar* pid_file_; - gchar** log_targets_; - gboolean debug_; - - gchar* appname_; - sydra_rtp_mode_t mode_; - - gchar* source_; - gchar* sink_; - - gchar* video_enc_; - gchar* video_payloader_; - gchar* video_caps_; - gchar* video_depayloader_; - gchar* video_dec_; - - gchar* audio_enc_; - gchar* audio_payloader_; - gchar* audio_caps_; - gchar* audio_depayloader_; - gchar* audio_dec_; - - gchar* rtp_host_; - gint rtp_port_base_; - gchar* rtp_addr_local_; - gint rtp_port_base_local_; - gchar* rtp_host_reflector_; - gint rtp_port_base_reflector_; - gboolean auto_client_; - gint timeout_; - gint keepalive_int_; - - gchar* preview_videosink_; - - gchar* video_enc_rec_; - gchar* audio_enc_rec_; - gchar* rec_mux_; - gchar* rec_name_format_; -}; -typedef struct options_struct options_t; - -int options_parse(options_t* opt, int argc, char* argv[]); -void options_clear(options_t* opt); -void options_print_version(); -void options_print(options_t* opt); - -#endif diff --git a/src/pipelines-rtp.c b/src/pipelines-rtp.c new file mode 100644 index 0000000..1a04c78 --- /dev/null +++ b/src/pipelines-rtp.c @@ -0,0 +1,589 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * + * + * Copyright (C) 2014 Christian Pointner + * + * This file is part of sydra. + * + * sydra 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 + * any later version. + * + * sydra 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 sydra. If not, see . + * + * In addition, as a special exception, the copyright holders hereby + * grant permission for non-GPL-compatible GStreamer plugins to be used + * and distributed together with GStreamer and sydra. + * This permission goes above and beyond the permissions granted by the + * GPL license sydra is covered by. + */ + +#include "datatypes.h" + +#include +#include +#include + +#include "pipelines-rtp.h" +#include "options-rtp.h" +#include "utils.h" +#include "log.h" + +struct av_elements { + const char* name_; + + GstElement* srcsink_; + GstElement* tee_raw_; + + const char* encdec_str_; + GstElement* encdec_; + + GstElement* tee_enc_; + + const char* payloader_str_; + GstElement* payloader_; +}; + +static GstElement* create_avsend_src(const char* desc, GstElement* pipeline) +{ + GstElement* src = sydra_create_bin_from_desc("AV source bin", desc, FALSE); + if(!src) return NULL; + gst_bin_add(GST_BIN(pipeline), src); + + GstElement* element = gst_bin_get_by_name(GST_BIN(src), "videosrc"); + if(!element) { + log_printf(ERROR, "can't find an element 'videosrc' inside source bin"); + return NULL; + } + GstPad* pad = gst_element_get_static_pad(element, "src"); + if(!gst_element_add_pad(src, gst_ghost_pad_new("video", pad))) { + log_printf(ERROR, "can't create video ghost pad for source bin"); + return NULL; + } + gst_object_unref(GST_OBJECT(pad)); + gst_object_unref(GST_OBJECT(element)); + + element = gst_bin_get_by_name(GST_BIN(src), "audiosrc"); + if(!element) { + log_printf(ERROR, "can't find an element 'audiosrc' inside source bin"); + return NULL; + } + pad = gst_element_get_static_pad(element, "src"); + if(!gst_element_add_pad(src, gst_ghost_pad_new("audio", pad))) { + log_printf(ERROR, "can't create audio ghost pad for source bin"); + return NULL; + } + gst_object_unref(GST_OBJECT(pad)); + gst_object_unref(GST_OBJECT(element)); + + return src; +} + +static gboolean create_avsend_elements(struct av_elements *ave, GstElement* pipeline, GstElement *rtp, uint32_t session) +{ + ave->tee_raw_ = sydra_create_element("tee", NULL); + GstElement *qr = sydra_create_element("queue", NULL); + + char bin_name[32]; + snprintf(bin_name, sizeof(bin_name), "%s encoder bin", ave->name_); + ave->encdec_ = sydra_create_bin_from_desc(bin_name, ave->encdec_str_, TRUE); + ave->tee_enc_ = sydra_create_element("tee", NULL); + GstElement *qe = sydra_create_element("queue", NULL); + ave->payloader_ = sydra_create_element(ave->payloader_str_, NULL); + + if(!ave->tee_raw_ || !qr || !ave->encdec_ || !ave->tee_enc_ || !qe || !ave->payloader_) { + return FALSE; + } + + log_printf(INFO, "%s path created successfully!", ave->name_); + + gst_bin_add_many(GST_BIN(pipeline), ave->tee_raw_, qr, ave->encdec_, ave->tee_enc_, qe, ave->payloader_, NULL); + if(!sydra_link_static_static(ave->srcsink_, ave->name_, ave->tee_raw_, "sink")) return FALSE; + gst_element_link_many(qr, ave->encdec_, ave->tee_enc_, NULL); + gst_element_link(qe, ave->payloader_); + + char pad_name[32]; + snprintf(pad_name, sizeof(pad_name), "send_rtp_sink_%u", session); + if(!sydra_link_request_static(ave->tee_raw_, "src_%u", qr, "sink") || + !sydra_link_request_static(ave->tee_enc_, "src_%u", qe, "sink") || + !sydra_link_static_request(ave->payloader_, "src", rtp, pad_name)) { + return FALSE; + } + + log_printf(INFO, "%s path linked successfully!", ave->name_); + return TRUE; +} + +static GstElement* create_avrecv_sink(const char* desc, GstElement* pipeline) +{ + GstElement* sink = sydra_create_bin_from_desc("AV sink bin", desc, FALSE); + if(!sink) return NULL; + gst_bin_add(GST_BIN(pipeline), sink); + + GstElement* element = gst_bin_get_by_name(GST_BIN(sink), "videosink"); + if(!element) { + log_printf(ERROR, "can't find an element 'videosink' inside sink bin"); + return NULL; + } + GstPad* pad = gst_element_get_static_pad(element, "sink"); + if(!gst_element_add_pad(sink, gst_ghost_pad_new("video", pad))) { + log_printf(ERROR, "can't create video ghost pad for source bin"); + return NULL; + } + gst_object_unref(GST_OBJECT(pad)); + gst_object_unref(GST_OBJECT(element)); + + element = gst_bin_get_by_name(GST_BIN(sink), "audiosink"); + if(!element) { + log_printf(ERROR, "can't find an element 'audiosink' inside sink bin"); + return NULL; + } + pad = gst_element_get_static_pad(element, "sink"); + if(!gst_element_add_pad(sink, gst_ghost_pad_new("audio", pad))) { + log_printf(ERROR, "can't create audio ghost pad for source bin"); + return NULL; + } + gst_object_unref(GST_OBJECT(pad)); + gst_object_unref(GST_OBJECT(element)); + + return sink; +} + +static gboolean create_avrecv_elements(struct av_elements *ave, GstElement* pipeline) +{ + ave->tee_raw_ = sydra_create_element("tee", NULL); + GstElement *qr = sydra_create_element("queue", NULL); + + char bin_name[32]; + snprintf(bin_name, sizeof(bin_name), "%s decoder bin", ave->name_); + ave->encdec_ = sydra_create_bin_from_desc(bin_name, ave->encdec_str_, TRUE); + ave->tee_enc_ = sydra_create_element("tee", NULL); + GstElement *qe = sydra_create_element("queue", NULL); + ave->payloader_ = sydra_create_element(ave->payloader_str_, NULL); + + if(!ave->tee_raw_ || !qr || !ave->encdec_ || !ave->tee_enc_ || !qe || !ave->payloader_) { + return FALSE; + } + + log_printf(INFO, "%s path created successfully!", ave->name_); + + gst_bin_add_many (GST_BIN(pipeline), ave->tee_raw_, qr, ave->encdec_, ave->tee_enc_, qe, ave->payloader_, NULL); + gst_element_link_many(ave->payloader_, ave->tee_enc_, qe, ave->encdec_, ave->tee_raw_, qr, NULL); + if(!sydra_link_static_static(qr, "src", ave->srcsink_, ave->name_)) return FALSE; + + log_printf(INFO, "%s path linked successfully!", ave->name_); + return TRUE; +} + +static gboolean create_udp_sinks(options_t* opt, GstElement* pipeline, GstElement* rtp, struct udp_sinks *sinks) +{ + sinks->rtp_video_.udp_ = sydra_create_element("multiudpsink", "udprtpv"); + sinks->rtcp_video_.udp_ = sydra_create_element("multiudpsink", "udprtcpv"); + sinks->rtp_audio_.udp_ = sydra_create_element("multiudpsink", "udprtpa"); + sinks->rtcp_audio_.udp_ = sydra_create_element("multiudpsink", "udprtcpa"); + + if(!(sinks->rtp_video_.udp_) || !(sinks->rtcp_video_.udp_) || !(sinks->rtp_audio_.udp_) || !(sinks->rtcp_audio_.udp_)) + return FALSE; + + log_printf(INFO, "udp sinks created successfully!"); + + int rtp_port_local = opt->rtp_port_base_local_; + g_object_set(G_OBJECT(sinks->rtp_video_.udp_), "bind-port", rtp_port_local++, NULL); + g_object_set(G_OBJECT(sinks->rtcp_video_.udp_), "bind-port", rtp_port_local++, "sync", FALSE, "async", FALSE, NULL); + g_object_set(G_OBJECT(sinks->rtp_audio_.udp_), "bind-port", rtp_port_local++, NULL); + g_object_set(G_OBJECT(sinks->rtcp_audio_.udp_), "bind-port", rtp_port_local++, "sync", FALSE, "async", FALSE, NULL); + + if(opt->rtp_host_) { + int rtp_port = opt->rtp_port_base_; + g_signal_emit_by_name(G_OBJECT(sinks->rtp_video_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); + g_signal_emit_by_name(G_OBJECT(sinks->rtcp_video_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); + g_signal_emit_by_name(G_OBJECT(sinks->rtp_audio_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); + g_signal_emit_by_name(G_OBJECT(sinks->rtcp_audio_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); + } + if(opt->rtp_addr_local_) { + g_object_set(G_OBJECT(sinks->rtp_video_.udp_), "bind-address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sinks->rtcp_video_.udp_), "bind-address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sinks->rtp_audio_.udp_), "bind-address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sinks->rtcp_audio_.udp_), "bind-address", opt->rtp_addr_local_, NULL); + } + + log_printf(INFO, "udp sinks configured successfully!"); + + gst_bin_add_many(GST_BIN(pipeline), sinks->rtp_video_.udp_, sinks->rtcp_video_.udp_, sinks->rtp_audio_.udp_, sinks->rtcp_audio_.udp_, NULL); + + if(!sydra_link_static_static(rtp, "send_rtp_src_0", sinks->rtp_video_.udp_, "sink") || + !sydra_link_request_static(rtp, "send_rtcp_src_0", sinks->rtcp_video_.udp_, "sink") || + !sydra_link_static_static(rtp, "send_rtp_src_1", sinks->rtp_audio_.udp_, "sink") || + !sydra_link_request_static(rtp, "send_rtcp_src_1", sinks->rtcp_audio_.udp_, "sink")) + return FALSE; + + log_printf(INFO, "udp sinks linked successfully!"); + + return TRUE; +} + +struct udpsrc_tees { + GstElement* rtpv_; + GstElement* rtcpv_; + GstElement* rtpa_; + GstElement* rtcpa_; +}; + +static gboolean create_rtp_reflector(options_t* opt, GstElement* pipeline, struct udpsrc_tees *tees) +{ + GstElement* rtp_video = sydra_create_element("udpsink", "udprtpvref"); + GstElement* qrtpv = sydra_create_element("queue", NULL); + GstElement* rtcp_video = sydra_create_element("udpsink", "udprtcpvref"); + GstElement* qrtcpv = sydra_create_element("queue", NULL); + GstElement* rtp_audio = sydra_create_element("udpsink", "udprtparef"); + GstElement* qrtpa = sydra_create_element("queue", NULL); + GstElement* rtcp_audio = sydra_create_element("udpsink", "udprtcparef"); + GstElement* qrtcpa = sydra_create_element("queue", NULL); + + if(!rtp_video || !rtcp_video || !rtp_audio || !rtcp_audio || !qrtpv || !qrtcpv || !qrtpa || !qrtcpa) + return FALSE; + + log_printf(INFO, "udp reflector sinks created successfully!"); + + int rtp_port_reflector = opt->rtp_port_base_reflector_; + g_object_set(G_OBJECT(rtp_video), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, + "sync", FALSE, "async", FALSE, NULL); + g_object_set(G_OBJECT(rtcp_video), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, + "sync", FALSE, "async", FALSE, NULL); + g_object_set(G_OBJECT(rtp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, + "sync", FALSE, "async", FALSE, NULL); + g_object_set(G_OBJECT(rtcp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, + "sync", FALSE, "async", FALSE, NULL); + + log_printf(INFO, "udp reflector sinks configured successfully!"); + + gst_bin_add_many(GST_BIN(pipeline), rtp_video, rtcp_video, rtp_audio, rtcp_audio, NULL); + gst_bin_add_many(GST_BIN(pipeline), qrtpv, qrtcpv, qrtpa, qrtcpa, NULL); + + gst_element_link(qrtpv, rtp_video); + gst_element_link(qrtcpv, rtcp_video); + gst_element_link(qrtpa, rtp_audio); + gst_element_link(qrtcpa, rtcp_audio); + + if(!sydra_link_request_static(tees->rtpv_, "src_%u", qrtpv, "sink") || + !sydra_link_request_static(tees->rtcpv_, "src_%u", qrtcpv, "sink") || + !sydra_link_request_static(tees->rtpa_, "src_%u", qrtpa, "sink") || + !sydra_link_request_static(tees->rtcpa_, "src_%u", qrtcpa, "sink")) + return FALSE; + + log_printf(INFO, "udp reflector sinks linked successfully!"); + + return TRUE; +} + +static gboolean create_udp_sources(options_t* opt, GstElement* pipeline, GstElement* rtp, struct udp_sources *sources) +{ + struct udpsrc_tees tees; + + sources->rtp_video_ = sydra_create_element("udpsrc", "udprtpv"); + tees.rtpv_ = sydra_create_element("tee", "rtpvt"); + sources->rtcp_video_ = sydra_create_element("udpsrc", "udprtcpv"); + tees.rtcpv_ = sydra_create_element("tee", "rtcpvt"); + sources->rtp_audio_ = sydra_create_element("udpsrc", "udprtpa"); + tees.rtpa_ = sydra_create_element("tee", "rtpat"); + sources->rtcp_audio_ = sydra_create_element("udpsrc", "udprtcpa"); + tees.rtcpa_ = sydra_create_element("tee", "rtcpat"); + + if(!(sources->rtp_video_) || !(sources->rtcp_video_) || !(sources->rtp_audio_) || !(sources->rtcp_audio_) || + !(tees.rtpv_) || !(tees.rtcpv_) || !(tees.rtpa_) || !(tees.rtcpa_)) + return FALSE; + + log_printf(INFO, "udp sources created successfully!"); + + GstCaps *video_caps = gst_caps_from_string(opt->video_caps_); + GstCaps *audio_caps = gst_caps_from_string(opt->audio_caps_); + if(!video_caps || !audio_caps) { + log_printf(ERROR, "parsing video or audio caps failed!"); + return FALSE; + } + int rtp_port_local = opt->rtp_port_base_local_; + g_object_set(G_OBJECT(sources->rtp_video_), "port", rtp_port_local++, "caps", video_caps, NULL); + g_object_set(G_OBJECT(sources->rtcp_video_), "port", rtp_port_local++, NULL); + g_object_set(G_OBJECT(sources->rtp_audio_), "port", rtp_port_local++, "caps", audio_caps, NULL); + g_object_set(G_OBJECT(sources->rtcp_audio_), "port", rtp_port_local++, NULL); + gst_caps_unref(video_caps); + gst_caps_unref(audio_caps); + + if(opt->rtp_addr_local_) { + g_object_set(G_OBJECT(sources->rtp_video_), "address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sources->rtcp_video_), "address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sources->rtp_audio_), "address", opt->rtp_addr_local_, NULL); + g_object_set(G_OBJECT(sources->rtcp_audio_), "address", opt->rtp_addr_local_, NULL); + } + + log_printf(INFO, "udp sources configured successfully!"); + + gst_bin_add_many(GST_BIN(pipeline), sources->rtp_video_, sources->rtcp_video_, sources->rtp_audio_, sources->rtcp_audio_, NULL); + gst_bin_add_many(GST_BIN(pipeline), tees.rtpv_, tees.rtcpv_, tees.rtpa_, tees.rtcpa_, NULL); + + gst_element_link(sources->rtp_video_, tees.rtpv_); + gst_element_link(sources->rtcp_video_, tees.rtcpv_); + gst_element_link(sources->rtp_audio_, tees.rtpa_); + gst_element_link(sources->rtcp_audio_, tees.rtcpa_); + + if(!sydra_link_request_request(tees.rtpv_, "src_%u", rtp, "recv_rtp_sink_0") || + !sydra_link_request_request(tees.rtcpv_, "src_%u", rtp, "recv_rtcp_sink_0") || + !sydra_link_request_request(tees.rtpa_, "src_%u", rtp, "recv_rtp_sink_1") || + !sydra_link_request_request(tees.rtcpa_, "src_%u", rtp, "recv_rtcp_sink_1")) + return FALSE; + + log_printf(INFO, "udp sources linked successfully!"); + + if(opt->rtp_host_reflector_) + return create_rtp_reflector(opt, pipeline, &tees); + + return TRUE; +} + +static gboolean create_preview_elements(const char* preview_bin_desc, GstElement* pipeline, GstElement* tee) +{ + GstElement *qr = sydra_create_element("queue", NULL); + GstElement *preview_bin = sydra_create_bin_from_desc("preview sink bin", preview_bin_desc, TRUE); + + if(!qr || !preview_bin) { + return FALSE; + } + + log_printf(INFO, "preview path created successfully!"); + + gst_bin_add_many (GST_BIN(pipeline), qr, preview_bin, NULL); + gst_element_link(qr, preview_bin); + + if(!sydra_link_request_static(tee, "src_%u", qr, "sink")) { + return FALSE; + } + + log_printf(INFO, "preview path linked successfully!"); + return TRUE; +} + +static gboolean create_recorder_elements(options_t* opt, GstElement* pipeline, struct av_elements *ve, struct av_elements *ae) +{ + GstElement *qv = sydra_create_element("queue", NULL); + GstElement *qa = sydra_create_element("queue", NULL); + GstElement *mux = sydra_create_element(opt->rec_mux_, NULL); + GstElement *sink = sydra_create_element("filesink", NULL); + + if(!qv || !qa || !mux || !sink) { + return FALSE; + } + + GstElement *ev = NULL, *tv = ve->tee_enc_; + if(opt->video_enc_rec_) { + ev = sydra_create_bin_from_desc("record video encoder bin", opt->video_enc_rec_, TRUE); + if(!ev) return FALSE; + tv = ve->tee_raw_; + } + GstElement *ea = NULL, *ta = ae->tee_enc_; + if(opt->audio_enc_rec_) { + ea = sydra_create_bin_from_desc("record audio encoder bin", opt->audio_enc_rec_, TRUE); + if(!ea) return FALSE; + ta = ae->tee_raw_; + } + + log_printf(INFO, "recorder path created successfully!"); + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + struct tm bd_time; + localtime_r(&(now.tv_sec), &bd_time); + char recfile[1024]; + recfile[0] = 0; + strftime(recfile, sizeof(recfile), opt->rec_name_format_, &bd_time); + g_object_set(G_OBJECT(sink), "location", recfile, NULL); + + gst_bin_add_many(GST_BIN(pipeline), qv, qa, mux, sink, NULL); + gst_element_link(mux, sink); + GstElement* sv = qv; + if(ev) { + gst_bin_add(GST_BIN(pipeline), ev); + gst_element_link(qv, ev); + sv = ev; + } + GstElement* sa = qa; + if(ev) { + gst_bin_add(GST_BIN(pipeline), ea); + gst_element_link(qa, ea); + sa = ea; + } + + if(!sydra_link_request_static(tv, "src_%u", qv, "sink") || + !sydra_link_static_compatible(sv, "src", mux) || + !sydra_link_request_static(ta, "src_%u", qa, "sink") || + !sydra_link_static_compatible(sa, "src", mux)) { + return FALSE; + } + + log_printf(INFO, "recorder path linked successfully!"); + return TRUE; +} + +GstElement* create_sender_pipeline(options_t* opt, struct udp_sinks *udp) +{ + GstElement *pipeline = gst_pipeline_new ("sydra-rtp-sender"); + if(!pipeline) { + log_printf(ERROR, "Creating pipeline failed!"); + return NULL; + } + GstElement *rtp = sydra_create_element("rtpbin", "rtpbin"); + if(!rtp || !gst_bin_add(GST_BIN(pipeline), rtp)) return NULL; + log_printf(INFO, "rtpbin created successfully!"); + + GstElement* src = create_avsend_src(opt->source_, pipeline); + if(!src) return NULL; + log_printf(INFO, "source bin created successfully!"); + + struct av_elements video = { "video", src, NULL, opt->video_enc_, NULL, NULL, + opt->video_payloader_, NULL }; + struct av_elements audio = { "audio", src, NULL, opt->audio_enc_, NULL, NULL, + opt->audio_payloader_, NULL }; + if(!create_avsend_elements(&video, pipeline, rtp, 0) || + !create_avsend_elements(&audio, pipeline, rtp, 1) || + !create_udp_sinks(opt, pipeline, rtp, udp)) { + return NULL; + } + + if(opt->preview_videosink_) { + if(!create_preview_elements(opt->preview_videosink_, pipeline, video.tee_raw_)) + return NULL; + } + + if(opt->rec_mux_) { + if(!create_recorder_elements(opt, pipeline, &video, &audio)) + return NULL; + } + + log_printf(INFO, "sender pipeline created successfully!"); + return pipeline; +} + +static void rtpbin_pad_added(GstElement* rtp, GstPad* pad, gpointer user_data) +{ + GstElement **depays = (GstElement**)user_data; + + GstPadTemplate *pad_template = gst_pad_get_pad_template(pad); + if(pad_template == NULL) return; + if(strcmp("recv_rtp_src_%u_%u_%u", GST_PAD_TEMPLATE_NAME_TEMPLATE(pad_template))) { + gst_object_unref(GST_OBJECT(pad_template)); + return; + } + gst_object_unref(GST_OBJECT(pad_template)); + + gchar* src_pad_name = gst_element_get_name(pad); + log_printf(INFO, "rtpbin: new pad created %s", src_pad_name); + + guint i; + for(i = 0; i < 2; i++) { + GstPad *sink_pad = gst_element_get_static_pad(depays[i], "sink"); + if(gst_pad_is_linked(sink_pad) || !gst_pad_can_link(pad, sink_pad)) { + gst_object_unref(GST_OBJECT(sink_pad)); + continue; + } + GstPadLinkReturn ret = gst_pad_link(pad, sink_pad); + gst_object_unref(GST_OBJECT(sink_pad)); + + if(GST_PAD_LINK_FAILED(ret)) { + gchar* src_name = gst_element_get_name(rtp); + gchar* sink_name = gst_element_get_name(depays[i]); + log_printf(ERROR, "Error linking pad '%s' of '%s' with pad '%s' of '%s'", + src_pad_name, src_name, "sink", sink_name); + g_free(src_name); + g_free(sink_name); + continue; + } + break; + } + if(!gst_pad_is_linked(pad)) { + log_printf(ERROR, "rtpbin: no compatible depayloader found for pad %s (or all depayloader already connected)", src_pad_name); + GstStructure* ms = gst_structure_new("sydra-rtp", "quit", G_TYPE_BOOLEAN, TRUE, "reason", + G_TYPE_STRING, "New RTP Session but all depayloader are already connected or none compatible found", NULL); + GstMessage* msg = gst_message_new_application(GST_OBJECT(rtp), ms); + if(!ms || ! msg) + log_printf(ERROR, "rtpbin: message creation failed!"); + else { + if(!gst_element_post_message(rtp, msg)) + log_printf(ERROR, "rtpbin: sending message to the application failed: no bus"); + } + } + g_free(src_pad_name); +} + +static void rtp_new_ssrc(GstElement *rtp, guint session, guint ssrc, gpointer user_data) +{ + log_printf(INFO, "rtpbin: new SSRC %u for session %u", ssrc, session); +} + +static void rtp_ssrc_timeout(GstElement *rtp, guint session, guint ssrc, gpointer user_data) +{ + log_printf(INFO, "rtpbin: SSRC %u of session %u timed out", ssrc, session); + GstStructure* ms = gst_structure_new("sydra-rtp", "quit", G_TYPE_BOOLEAN, TRUE, "reason", + G_TYPE_STRING, "RTP session timed out", NULL); + GstMessage* msg = gst_message_new_application(GST_OBJECT(rtp), ms); + if(!ms || ! msg) + log_printf(ERROR, "rtpbin: message creation failed!"); + else { + if(!gst_element_post_message(rtp, msg)) + log_printf(ERROR, "rtpbin: sending message to the application failed: no bus"); + } +} + +GstElement* create_receiver_pipeline(options_t* opt, struct udp_sources *udp) +{ + GstElement *pipeline = gst_pipeline_new ("sydra-rtp-receiver"); + if(!pipeline) { + log_printf(ERROR, "Creating pipeline failed!"); + return NULL; + } + GstElement *rtp = sydra_create_element("rtpbin", "rtpbin"); + if(!rtp || !gst_bin_add(GST_BIN(pipeline), rtp)) { + return NULL; + } + log_printf(INFO, "rtpbin created successfully!"); + + GstElement* sink = create_avrecv_sink(opt->sink_, pipeline); + if(!sink) return NULL; + log_printf(INFO, "sink bin created successfully!"); + + struct av_elements video = { "video", sink, NULL, opt->video_dec_, NULL, NULL, + opt->video_depayloader_, NULL }; + struct av_elements audio = { "audio", sink, NULL, opt->audio_dec_, NULL, NULL, + opt->audio_depayloader_, NULL }; + if(!create_udp_sources(opt, pipeline, rtp, udp) || + !create_avrecv_elements(&video, pipeline) || + !create_avrecv_elements(&audio, pipeline)) { + return NULL; + } + GstElement **depays = g_new(GstElement*, 2); + depays[0] = video.payloader_; + depays[1] = audio.payloader_; + g_signal_connect_closure(rtp, "pad-added", g_cclosure_new(G_CALLBACK(rtpbin_pad_added), depays, NULL), FALSE); + g_signal_connect(rtp, "on-new-ssrc", G_CALLBACK(rtp_new_ssrc), NULL); + g_signal_connect(rtp, "on-timeout", G_CALLBACK(rtp_ssrc_timeout), NULL); + + if(opt->rec_mux_) { + if(!create_recorder_elements(opt, pipeline, &video, &audio)) + return NULL; + } + + log_printf(INFO, "receiver pipeline created successfully!"); + return pipeline; +} diff --git a/src/pipelines-rtp.h b/src/pipelines-rtp.h new file mode 100644 index 0000000..a7ba5b6 --- /dev/null +++ b/src/pipelines-rtp.h @@ -0,0 +1,45 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * + * + * Copyright (C) 2014 Christian Pointner + * + * This file is part of sydra. + * + * sydra 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 + * any later version. + * + * sydra 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 sydra. If not, see . + * + * In addition, as a special exception, the copyright holders hereby + * grant permission for non-GPL-compatible GStreamer plugins to be used + * and distributed together with GStreamer and sydra. + * This permission goes above and beyond the permissions granted by the + * GPL license sydra is covered by. + */ + +#ifndef SYDRA_pipelines_h_INCLUDED +#define SYDRA_pipelines_h_INCLUDED + +#include "datatypes.h" +#include +#include "options-rtp.h" + +GstElement* create_sender_pipeline(options_t* opt, struct udp_sinks *udp); +GstElement* create_receiver_pipeline(options_t* opt, struct udp_sources *udp); + +#endif diff --git a/src/pipelines.c b/src/pipelines.c deleted file mode 100644 index 440f183..0000000 --- a/src/pipelines.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * sydra - * - * sydra is a toolbox which allows you to set up multiple bidirectional - * Video/Audio streams from external locations. - * sydra has been written to be used for the Elevate Festival in Graz - * Austria in order to involve external locations to present themselves - * at the festival. - * - * - * Copyright (C) 2014 Christian Pointner - * - * This file is part of sydra. - * - * sydra 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 - * any later version. - * - * sydra 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 sydra. If not, see . - * - * In addition, as a special exception, the copyright holders hereby - * grant permission for non-GPL-compatible GStreamer plugins to be used - * and distributed together with GStreamer and sydra. - * This permission goes above and beyond the permissions granted by the - * GPL license sydra is covered by. - */ - -#include "datatypes.h" - -#include -#include -#include - -#include "options.h" -#include "utils.h" -#include "log.h" - -struct av_elements { - const char* name_; - - GstElement* srcsink_; - GstElement* tee_raw_; - - const char* encdec_str_; - GstElement* encdec_; - - GstElement* tee_enc_; - - const char* payloader_str_; - GstElement* payloader_; -}; - -static GstElement* create_avsend_src(const char* desc, GstElement* pipeline) -{ - GstElement* src = sydra_create_bin_from_desc("AV source bin", desc, FALSE); - if(!src) return NULL; - gst_bin_add(GST_BIN(pipeline), src); - - GstElement* element = gst_bin_get_by_name(GST_BIN(src), "videosrc"); - if(!element) { - log_printf(ERROR, "can't find an element 'videosrc' inside source bin"); - return NULL; - } - GstPad* pad = gst_element_get_static_pad(element, "src"); - if(!gst_element_add_pad(src, gst_ghost_pad_new("video", pad))) { - log_printf(ERROR, "can't create video ghost pad for source bin"); - return NULL; - } - gst_object_unref(GST_OBJECT(pad)); - gst_object_unref(GST_OBJECT(element)); - - element = gst_bin_get_by_name(GST_BIN(src), "audiosrc"); - if(!element) { - log_printf(ERROR, "can't find an element 'audiosrc' inside source bin"); - return NULL; - } - pad = gst_element_get_static_pad(element, "src"); - if(!gst_element_add_pad(src, gst_ghost_pad_new("audio", pad))) { - log_printf(ERROR, "can't create audio ghost pad for source bin"); - return NULL; - } - gst_object_unref(GST_OBJECT(pad)); - gst_object_unref(GST_OBJECT(element)); - - return src; -} - -static gboolean create_avsend_elements(struct av_elements *ave, GstElement* pipeline, GstElement *rtp, uint32_t session) -{ - ave->tee_raw_ = sydra_create_element("tee", NULL); - GstElement *qr = sydra_create_element("queue", NULL); - - char bin_name[32]; - snprintf(bin_name, sizeof(bin_name), "%s encoder bin", ave->name_); - ave->encdec_ = sydra_create_bin_from_desc(bin_name, ave->encdec_str_, TRUE); - ave->tee_enc_ = sydra_create_element("tee", NULL); - GstElement *qe = sydra_create_element("queue", NULL); - ave->payloader_ = sydra_create_element(ave->payloader_str_, NULL); - - if(!ave->tee_raw_ || !qr || !ave->encdec_ || !ave->tee_enc_ || !qe || !ave->payloader_) { - return FALSE; - } - - log_printf(INFO, "%s path created successfully!", ave->name_); - - gst_bin_add_many(GST_BIN(pipeline), ave->tee_raw_, qr, ave->encdec_, ave->tee_enc_, qe, ave->payloader_, NULL); - if(!sydra_link_static_static(ave->srcsink_, ave->name_, ave->tee_raw_, "sink")) return FALSE; - gst_element_link_many(qr, ave->encdec_, ave->tee_enc_, NULL); - gst_element_link(qe, ave->payloader_); - - char pad_name[32]; - snprintf(pad_name, sizeof(pad_name), "send_rtp_sink_%u", session); - if(!sydra_link_request_static(ave->tee_raw_, "src_%u", qr, "sink") || - !sydra_link_request_static(ave->tee_enc_, "src_%u", qe, "sink") || - !sydra_link_static_request(ave->payloader_, "src", rtp, pad_name)) { - return FALSE; - } - - log_printf(INFO, "%s path linked successfully!", ave->name_); - return TRUE; -} - -static GstElement* create_avrecv_sink(const char* desc, GstElement* pipeline) -{ - GstElement* sink = sydra_create_bin_from_desc("AV sink bin", desc, FALSE); - if(!sink) return NULL; - gst_bin_add(GST_BIN(pipeline), sink); - - GstElement* element = gst_bin_get_by_name(GST_BIN(sink), "videosink"); - if(!element) { - log_printf(ERROR, "can't find an element 'videosink' inside sink bin"); - return NULL; - } - GstPad* pad = gst_element_get_static_pad(element, "sink"); - if(!gst_element_add_pad(sink, gst_ghost_pad_new("video", pad))) { - log_printf(ERROR, "can't create video ghost pad for source bin"); - return NULL; - } - gst_object_unref(GST_OBJECT(pad)); - gst_object_unref(GST_OBJECT(element)); - - element = gst_bin_get_by_name(GST_BIN(sink), "audiosink"); - if(!element) { - log_printf(ERROR, "can't find an element 'audiosink' inside sink bin"); - return NULL; - } - pad = gst_element_get_static_pad(element, "sink"); - if(!gst_element_add_pad(sink, gst_ghost_pad_new("audio", pad))) { - log_printf(ERROR, "can't create audio ghost pad for source bin"); - return NULL; - } - gst_object_unref(GST_OBJECT(pad)); - gst_object_unref(GST_OBJECT(element)); - - return sink; -} - -static gboolean create_avrecv_elements(struct av_elements *ave, GstElement* pipeline) -{ - ave->tee_raw_ = sydra_create_element("tee", NULL); - GstElement *qr = sydra_create_element("queue", NULL); - - char bin_name[32]; - snprintf(bin_name, sizeof(bin_name), "%s decoder bin", ave->name_); - ave->encdec_ = sydra_create_bin_from_desc(bin_name, ave->encdec_str_, TRUE); - ave->tee_enc_ = sydra_create_element("tee", NULL); - GstElement *qe = sydra_create_element("queue", NULL); - ave->payloader_ = sydra_create_element(ave->payloader_str_, NULL); - - if(!ave->tee_raw_ || !qr || !ave->encdec_ || !ave->tee_enc_ || !qe || !ave->payloader_) { - return FALSE; - } - - log_printf(INFO, "%s path created successfully!", ave->name_); - - gst_bin_add_many (GST_BIN(pipeline), ave->tee_raw_, qr, ave->encdec_, ave->tee_enc_, qe, ave->payloader_, NULL); - gst_element_link_many(ave->payloader_, ave->tee_enc_, qe, ave->encdec_, ave->tee_raw_, qr, NULL); - if(!sydra_link_static_static(qr, "src", ave->srcsink_, ave->name_)) return FALSE; - - log_printf(INFO, "%s path linked successfully!", ave->name_); - return TRUE; -} - -static gboolean create_udp_sinks(options_t* opt, GstElement* pipeline, GstElement* rtp, struct udp_sinks *sinks) -{ - sinks->rtp_video_.udp_ = sydra_create_element("multiudpsink", "udprtpv"); - sinks->rtcp_video_.udp_ = sydra_create_element("multiudpsink", "udprtcpv"); - sinks->rtp_audio_.udp_ = sydra_create_element("multiudpsink", "udprtpa"); - sinks->rtcp_audio_.udp_ = sydra_create_element("multiudpsink", "udprtcpa"); - - if(!(sinks->rtp_video_.udp_) || !(sinks->rtcp_video_.udp_) || !(sinks->rtp_audio_.udp_) || !(sinks->rtcp_audio_.udp_)) - return FALSE; - - log_printf(INFO, "udp sinks created successfully!"); - - int rtp_port_local = opt->rtp_port_base_local_; - g_object_set(G_OBJECT(sinks->rtp_video_.udp_), "bind-port", rtp_port_local++, NULL); - g_object_set(G_OBJECT(sinks->rtcp_video_.udp_), "bind-port", rtp_port_local++, "sync", FALSE, "async", FALSE, NULL); - g_object_set(G_OBJECT(sinks->rtp_audio_.udp_), "bind-port", rtp_port_local++, NULL); - g_object_set(G_OBJECT(sinks->rtcp_audio_.udp_), "bind-port", rtp_port_local++, "sync", FALSE, "async", FALSE, NULL); - - if(opt->rtp_host_) { - int rtp_port = opt->rtp_port_base_; - g_signal_emit_by_name(G_OBJECT(sinks->rtp_video_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); - g_signal_emit_by_name(G_OBJECT(sinks->rtcp_video_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); - g_signal_emit_by_name(G_OBJECT(sinks->rtp_audio_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); - g_signal_emit_by_name(G_OBJECT(sinks->rtcp_audio_.udp_), "add", opt->rtp_host_, rtp_port++, NULL); - } - if(opt->rtp_addr_local_) { - g_object_set(G_OBJECT(sinks->rtp_video_.udp_), "bind-address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sinks->rtcp_video_.udp_), "bind-address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sinks->rtp_audio_.udp_), "bind-address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sinks->rtcp_audio_.udp_), "bind-address", opt->rtp_addr_local_, NULL); - } - - log_printf(INFO, "udp sinks configured successfully!"); - - gst_bin_add_many(GST_BIN(pipeline), sinks->rtp_video_.udp_, sinks->rtcp_video_.udp_, sinks->rtp_audio_.udp_, sinks->rtcp_audio_.udp_, NULL); - - if(!sydra_link_static_static(rtp, "send_rtp_src_0", sinks->rtp_video_.udp_, "sink") || - !sydra_link_request_static(rtp, "send_rtcp_src_0", sinks->rtcp_video_.udp_, "sink") || - !sydra_link_static_static(rtp, "send_rtp_src_1", sinks->rtp_audio_.udp_, "sink") || - !sydra_link_request_static(rtp, "send_rtcp_src_1", sinks->rtcp_audio_.udp_, "sink")) - return FALSE; - - log_printf(INFO, "udp sinks linked successfully!"); - - return TRUE; -} - -struct udpsrc_tees { - GstElement* rtpv_; - GstElement* rtcpv_; - GstElement* rtpa_; - GstElement* rtcpa_; -}; - -static gboolean create_rtp_reflector(options_t* opt, GstElement* pipeline, struct udpsrc_tees *tees) -{ - GstElement* rtp_video = sydra_create_element("udpsink", "udprtpvref"); - GstElement* qrtpv = sydra_create_element("queue", NULL); - GstElement* rtcp_video = sydra_create_element("udpsink", "udprtcpvref"); - GstElement* qrtcpv = sydra_create_element("queue", NULL); - GstElement* rtp_audio = sydra_create_element("udpsink", "udprtparef"); - GstElement* qrtpa = sydra_create_element("queue", NULL); - GstElement* rtcp_audio = sydra_create_element("udpsink", "udprtcparef"); - GstElement* qrtcpa = sydra_create_element("queue", NULL); - - if(!rtp_video || !rtcp_video || !rtp_audio || !rtcp_audio || !qrtpv || !qrtcpv || !qrtpa || !qrtcpa) - return FALSE; - - log_printf(INFO, "udp reflector sinks created successfully!"); - - int rtp_port_reflector = opt->rtp_port_base_reflector_; - g_object_set(G_OBJECT(rtp_video), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, - "sync", FALSE, "async", FALSE, NULL); - g_object_set(G_OBJECT(rtcp_video), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, - "sync", FALSE, "async", FALSE, NULL); - g_object_set(G_OBJECT(rtp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, - "sync", FALSE, "async", FALSE, NULL); - g_object_set(G_OBJECT(rtcp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, - "sync", FALSE, "async", FALSE, NULL); - - log_printf(INFO, "udp reflector sinks configured successfully!"); - - gst_bin_add_many(GST_BIN(pipeline), rtp_video, rtcp_video, rtp_audio, rtcp_audio, NULL); - gst_bin_add_many(GST_BIN(pipeline), qrtpv, qrtcpv, qrtpa, qrtcpa, NULL); - - gst_element_link(qrtpv, rtp_video); - gst_element_link(qrtcpv, rtcp_video); - gst_element_link(qrtpa, rtp_audio); - gst_element_link(qrtcpa, rtcp_audio); - - if(!sydra_link_request_static(tees->rtpv_, "src_%u", qrtpv, "sink") || - !sydra_link_request_static(tees->rtcpv_, "src_%u", qrtcpv, "sink") || - !sydra_link_request_static(tees->rtpa_, "src_%u", qrtpa, "sink") || - !sydra_link_request_static(tees->rtcpa_, "src_%u", qrtcpa, "sink")) - return FALSE; - - log_printf(INFO, "udp reflector sinks linked successfully!"); - - return TRUE; -} - -static gboolean create_udp_sources(options_t* opt, GstElement* pipeline, GstElement* rtp, struct udp_sources *sources) -{ - struct udpsrc_tees tees; - - sources->rtp_video_ = sydra_create_element("udpsrc", "udprtpv"); - tees.rtpv_ = sydra_create_element("tee", "rtpvt"); - sources->rtcp_video_ = sydra_create_element("udpsrc", "udprtcpv"); - tees.rtcpv_ = sydra_create_element("tee", "rtcpvt"); - sources->rtp_audio_ = sydra_create_element("udpsrc", "udprtpa"); - tees.rtpa_ = sydra_create_element("tee", "rtpat"); - sources->rtcp_audio_ = sydra_create_element("udpsrc", "udprtcpa"); - tees.rtcpa_ = sydra_create_element("tee", "rtcpat"); - - if(!(sources->rtp_video_) || !(sources->rtcp_video_) || !(sources->rtp_audio_) || !(sources->rtcp_audio_) || - !(tees.rtpv_) || !(tees.rtcpv_) || !(tees.rtpa_) || !(tees.rtcpa_)) - return FALSE; - - log_printf(INFO, "udp sources created successfully!"); - - GstCaps *video_caps = gst_caps_from_string(opt->video_caps_); - GstCaps *audio_caps = gst_caps_from_string(opt->audio_caps_); - if(!video_caps || !audio_caps) { - log_printf(ERROR, "parsing video or audio caps failed!"); - return FALSE; - } - int rtp_port_local = opt->rtp_port_base_local_; - g_object_set(G_OBJECT(sources->rtp_video_), "port", rtp_port_local++, "caps", video_caps, NULL); - g_object_set(G_OBJECT(sources->rtcp_video_), "port", rtp_port_local++, NULL); - g_object_set(G_OBJECT(sources->rtp_audio_), "port", rtp_port_local++, "caps", audio_caps, NULL); - g_object_set(G_OBJECT(sources->rtcp_audio_), "port", rtp_port_local++, NULL); - gst_caps_unref(video_caps); - gst_caps_unref(audio_caps); - - if(opt->rtp_addr_local_) { - g_object_set(G_OBJECT(sources->rtp_video_), "address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sources->rtcp_video_), "address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sources->rtp_audio_), "address", opt->rtp_addr_local_, NULL); - g_object_set(G_OBJECT(sources->rtcp_audio_), "address", opt->rtp_addr_local_, NULL); - } - - log_printf(INFO, "udp sources configured successfully!"); - - gst_bin_add_many(GST_BIN(pipeline), sources->rtp_video_, sources->rtcp_video_, sources->rtp_audio_, sources->rtcp_audio_, NULL); - gst_bin_add_many(GST_BIN(pipeline), tees.rtpv_, tees.rtcpv_, tees.rtpa_, tees.rtcpa_, NULL); - - gst_element_link(sources->rtp_video_, tees.rtpv_); - gst_element_link(sources->rtcp_video_, tees.rtcpv_); - gst_element_link(sources->rtp_audio_, tees.rtpa_); - gst_element_link(sources->rtcp_audio_, tees.rtcpa_); - - if(!sydra_link_request_request(tees.rtpv_, "src_%u", rtp, "recv_rtp_sink_0") || - !sydra_link_request_request(tees.rtcpv_, "src_%u", rtp, "recv_rtcp_sink_0") || - !sydra_link_request_request(tees.rtpa_, "src_%u", rtp, "recv_rtp_sink_1") || - !sydra_link_request_request(tees.rtcpa_, "src_%u", rtp, "recv_rtcp_sink_1")) - return FALSE; - - log_printf(INFO, "udp sources linked successfully!"); - - if(opt->rtp_host_reflector_) - return create_rtp_reflector(opt, pipeline, &tees); - - return TRUE; -} - -static gboolean create_preview_elements(const char* preview_bin_desc, GstElement* pipeline, GstElement* tee) -{ - GstElement *qr = sydra_create_element("queue", NULL); - GstElement *preview_bin = sydra_create_bin_from_desc("preview sink bin", preview_bin_desc, TRUE); - - if(!qr || !preview_bin) { - return FALSE; - } - - log_printf(INFO, "preview path created successfully!"); - - gst_bin_add_many (GST_BIN(pipeline), qr, preview_bin, NULL); - gst_element_link(qr, preview_bin); - - if(!sydra_link_request_static(tee, "src_%u", qr, "sink")) { - return FALSE; - } - - log_printf(INFO, "preview path linked successfully!"); - return TRUE; -} - -static gboolean create_recorder_elements(options_t* opt, GstElement* pipeline, struct av_elements *ve, struct av_elements *ae) -{ - GstElement *qv = sydra_create_element("queue", NULL); - GstElement *qa = sydra_create_element("queue", NULL); - GstElement *mux = sydra_create_element(opt->rec_mux_, NULL); - GstElement *sink = sydra_create_element("filesink", NULL); - - if(!qv || !qa || !mux || !sink) { - return FALSE; - } - - GstElement *ev = NULL, *tv = ve->tee_enc_; - if(opt->video_enc_rec_) { - ev = sydra_create_bin_from_desc("record video encoder bin", opt->video_enc_rec_, TRUE); - if(!ev) return FALSE; - tv = ve->tee_raw_; - } - GstElement *ea = NULL, *ta = ae->tee_enc_; - if(opt->audio_enc_rec_) { - ea = sydra_create_bin_from_desc("record audio encoder bin", opt->audio_enc_rec_, TRUE); - if(!ea) return FALSE; - ta = ae->tee_raw_; - } - - log_printf(INFO, "recorder path created successfully!"); - - struct timespec now; - clock_gettime(CLOCK_REALTIME, &now); - struct tm bd_time; - localtime_r(&(now.tv_sec), &bd_time); - char recfile[1024]; - recfile[0] = 0; - strftime(recfile, sizeof(recfile), opt->rec_name_format_, &bd_time); - g_object_set(G_OBJECT(sink), "location", recfile, NULL); - - gst_bin_add_many(GST_BIN(pipeline), qv, qa, mux, sink, NULL); - gst_element_link(mux, sink); - GstElement* sv = qv; - if(ev) { - gst_bin_add(GST_BIN(pipeline), ev); - gst_element_link(qv, ev); - sv = ev; - } - GstElement* sa = qa; - if(ev) { - gst_bin_add(GST_BIN(pipeline), ea); - gst_element_link(qa, ea); - sa = ea; - } - - if(!sydra_link_request_static(tv, "src_%u", qv, "sink") || - !sydra_link_static_compatible(sv, "src", mux) || - !sydra_link_request_static(ta, "src_%u", qa, "sink") || - !sydra_link_static_compatible(sa, "src", mux)) { - return FALSE; - } - - log_printf(INFO, "recorder path linked successfully!"); - return TRUE; -} - -GstElement* create_sender_pipeline(options_t* opt, struct udp_sinks *udp) -{ - GstElement *pipeline = gst_pipeline_new ("sydra-rtp-sender"); - if(!pipeline) { - log_printf(ERROR, "Creating pipeline failed!"); - return NULL; - } - GstElement *rtp = sydra_create_element("rtpbin", "rtpbin"); - if(!rtp || !gst_bin_add(GST_BIN(pipeline), rtp)) return NULL; - log_printf(INFO, "rtpbin created successfully!"); - - GstElement* src = create_avsend_src(opt->source_, pipeline); - if(!src) return NULL; - log_printf(INFO, "source bin created successfully!"); - - struct av_elements video = { "video", src, NULL, opt->video_enc_, NULL, NULL, - opt->video_payloader_, NULL }; - struct av_elements audio = { "audio", src, NULL, opt->audio_enc_, NULL, NULL, - opt->audio_payloader_, NULL }; - if(!create_avsend_elements(&video, pipeline, rtp, 0) || - !create_avsend_elements(&audio, pipeline, rtp, 1) || - !create_udp_sinks(opt, pipeline, rtp, udp)) { - return NULL; - } - - if(opt->preview_videosink_) { - if(!create_preview_elements(opt->preview_videosink_, pipeline, video.tee_raw_)) - return NULL; - } - - if(opt->rec_mux_) { - if(!create_recorder_elements(opt, pipeline, &video, &audio)) - return NULL; - } - - log_printf(INFO, "sender pipeline created successfully!"); - return pipeline; -} - -static void rtpbin_pad_added(GstElement* rtp, GstPad* pad, gpointer user_data) -{ - GstElement **depays = (GstElement**)user_data; - - GstPadTemplate *pad_template = gst_pad_get_pad_template(pad); - if(pad_template == NULL) return; - if(strcmp("recv_rtp_src_%u_%u_%u", GST_PAD_TEMPLATE_NAME_TEMPLATE(pad_template))) { - gst_object_unref(GST_OBJECT(pad_template)); - return; - } - gst_object_unref(GST_OBJECT(pad_template)); - - gchar* src_pad_name = gst_element_get_name(pad); - log_printf(INFO, "rtpbin: new pad created %s", src_pad_name); - - guint i; - for(i = 0; i < 2; i++) { - GstPad *sink_pad = gst_element_get_static_pad(depays[i], "sink"); - if(gst_pad_is_linked(sink_pad) || !gst_pad_can_link(pad, sink_pad)) { - gst_object_unref(GST_OBJECT(sink_pad)); - continue; - } - GstPadLinkReturn ret = gst_pad_link(pad, sink_pad); - gst_object_unref(GST_OBJECT(sink_pad)); - - if(GST_PAD_LINK_FAILED(ret)) { - gchar* src_name = gst_element_get_name(rtp); - gchar* sink_name = gst_element_get_name(depays[i]); - log_printf(ERROR, "Error linking pad '%s' of '%s' with pad '%s' of '%s'", - src_pad_name, src_name, "sink", sink_name); - g_free(src_name); - g_free(sink_name); - continue; - } - break; - } - if(!gst_pad_is_linked(pad)) { - log_printf(ERROR, "rtpbin: no compatible depayloader found for pad %s (or all depayloader already connected)", src_pad_name); - GstStructure* ms = gst_structure_new("sydra-rtp", "quit", G_TYPE_BOOLEAN, TRUE, "reason", - G_TYPE_STRING, "New RTP Session but all depayloader are already connected or none compatible found", NULL); - GstMessage* msg = gst_message_new_application(GST_OBJECT(rtp), ms); - if(!ms || ! msg) - log_printf(ERROR, "rtpbin: message creation failed!"); - else { - if(!gst_element_post_message(rtp, msg)) - log_printf(ERROR, "rtpbin: sending message to the application failed: no bus"); - } - } - g_free(src_pad_name); -} - -static void rtp_new_ssrc(GstElement *rtp, guint session, guint ssrc, gpointer user_data) -{ - log_printf(INFO, "rtpbin: new SSRC %u for session %u", ssrc, session); -} - -static void rtp_ssrc_timeout(GstElement *rtp, guint session, guint ssrc, gpointer user_data) -{ - log_printf(INFO, "rtpbin: SSRC %u of session %u timed out", ssrc, session); - GstStructure* ms = gst_structure_new("sydra-rtp", "quit", G_TYPE_BOOLEAN, TRUE, "reason", - G_TYPE_STRING, "RTP session timed out", NULL); - GstMessage* msg = gst_message_new_application(GST_OBJECT(rtp), ms); - if(!ms || ! msg) - log_printf(ERROR, "rtpbin: message creation failed!"); - else { - if(!gst_element_post_message(rtp, msg)) - log_printf(ERROR, "rtpbin: sending message to the application failed: no bus"); - } -} - -GstElement* create_receiver_pipeline(options_t* opt, struct udp_sources *udp) -{ - GstElement *pipeline = gst_pipeline_new ("sydra-rtp-receiver"); - if(!pipeline) { - log_printf(ERROR, "Creating pipeline failed!"); - return NULL; - } - GstElement *rtp = sydra_create_element("rtpbin", "rtpbin"); - if(!rtp || !gst_bin_add(GST_BIN(pipeline), rtp)) { - return NULL; - } - log_printf(INFO, "rtpbin created successfully!"); - - GstElement* sink = create_avrecv_sink(opt->sink_, pipeline); - if(!sink) return NULL; - log_printf(INFO, "sink bin created successfully!"); - - struct av_elements video = { "video", sink, NULL, opt->video_dec_, NULL, NULL, - opt->video_depayloader_, NULL }; - struct av_elements audio = { "audio", sink, NULL, opt->audio_dec_, NULL, NULL, - opt->audio_depayloader_, NULL }; - if(!create_udp_sources(opt, pipeline, rtp, udp) || - !create_avrecv_elements(&video, pipeline) || - !create_avrecv_elements(&audio, pipeline)) { - return NULL; - } - GstElement **depays = g_new(GstElement*, 2); - depays[0] = video.payloader_; - depays[1] = audio.payloader_; - g_signal_connect_closure(rtp, "pad-added", g_cclosure_new(G_CALLBACK(rtpbin_pad_added), depays, NULL), FALSE); - g_signal_connect(rtp, "on-new-ssrc", G_CALLBACK(rtp_new_ssrc), NULL); - g_signal_connect(rtp, "on-timeout", G_CALLBACK(rtp_ssrc_timeout), NULL); - - if(opt->rec_mux_) { - if(!create_recorder_elements(opt, pipeline, &video, &audio)) - return NULL; - } - - log_printf(INFO, "receiver pipeline created successfully!"); - return pipeline; -} diff --git a/src/pipelines.h b/src/pipelines.h deleted file mode 100644 index 7f04813..0000000 --- a/src/pipelines.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * sydra - * - * sydra is a toolbox which allows you to set up multiple bidirectional - * Video/Audio streams from external locations. - * sydra has been written to be used for the Elevate Festival in Graz - * Austria in order to involve external locations to present themselves - * at the festival. - * - * - * Copyright (C) 2014 Christian Pointner - * - * This file is part of sydra. - * - * sydra 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 - * any later version. - * - * sydra 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 sydra. If not, see . - * - * In addition, as a special exception, the copyright holders hereby - * grant permission for non-GPL-compatible GStreamer plugins to be used - * and distributed together with GStreamer and sydra. - * This permission goes above and beyond the permissions granted by the - * GPL license sydra is covered by. - */ - -#ifndef SYDRA_pipelines_h_INCLUDED -#define SYDRA_pipelines_h_INCLUDED - -#include "datatypes.h" -#include -#include "options.h" - -GstElement* create_sender_pipeline(options_t* opt, struct udp_sinks *udp); -GstElement* create_receiver_pipeline(options_t* opt, struct udp_sources *udp); - -#endif diff --git a/src/sydra-rtp.c b/src/sydra-rtp.c index dc6c3c7..ce5cda6 100644 --- a/src/sydra-rtp.c +++ b/src/sydra-rtp.c @@ -45,11 +45,11 @@ #include #include "datatypes.h" -#include "options.h" +#include "options-rtp.h" #include "log.h" #include "daemon.h" #include "utils.h" -#include "pipelines.h" +#include "pipelines-rtp.h" #include "udp.h" -- cgit v1.2.3