/* * 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. * Sydra is based on GStreamer and is written in C. * * * 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_; const char* src_str_; GstElement* src_; GstElement* tee_raw_; const char* enc_str_; GstElement* enc_; GstElement* tee_enc_; const char* payloader_str_; GstElement* payloader_; }; static gboolean create_av_elements(struct av_elements *ave, GstElement* pipeline, GstElement *rtp, uint32_t session) { char bin_name[32]; snprintf(bin_name, sizeof(bin_name), "%s source", ave->name_); ave->src_ = sydra_create_bin_from_desc(bin_name, ave->src_str_); ave->tee_raw_ = sydra_create_element("tee", NULL); GstElement *qr = sydra_create_element("queue", NULL); snprintf(bin_name, sizeof(bin_name), "%s encoder", ave->name_); ave->enc_ = sydra_create_bin_from_desc(bin_name, ave->enc_str_); 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->src_ || !ave->tee_raw_ || !qr || !ave->enc_ || !ave->tee_enc_ || !qe || !ave->payloader_) { return FALSE; } log_printf(DEBUG, "%s path created successfully!", ave->name_); gst_bin_add_many (GST_BIN(pipeline), ave->src_, ave->tee_raw_, qr, ave->enc_, ave->tee_enc_, qe, ave->payloader_, NULL); gst_element_link(ave->src_, ave->tee_raw_); gst_element_link_many(qr, ave->enc_, ave->tee_enc_, NULL); gst_element_link(qe, ave->payloader_); char pad_name[32]; snprintf(pad_name, sizeof(bin_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(DEBUG, "%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(DEBUG, "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(DEBUG, "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(DEBUG, "udp sinks linked successfully!"); return TRUE; } static gboolean create_udp_sources(options_t* opt, GstElement* pipeline, GstElement* rtp, struct udp_sources *sources) { sources->rtp_video_ = sydra_create_element("udpsrc", "udprtpv"); sources->rtcp_video_ = sydra_create_element("udpsrc", "udprtcpv"); sources->rtp_audio_ = sydra_create_element("udpsrc", "udprtpa"); sources->rtcp_audio_ = sydra_create_element("udpsrc", "udprtcpa"); if(!(sources->rtp_video_) || !(sources->rtcp_video_) || !(sources->rtp_audio_) || !(sources->rtcp_audio_)) return FALSE; log_printf(DEBUG, "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(DEBUG, "udp sources configured successfully!"); gst_bin_add_many(GST_BIN(pipeline), sources->rtp_video_, sources->rtcp_video_, sources->rtp_audio_, sources->rtcp_audio_, NULL); if(!sydra_link_static_request(sources->rtp_video_, "src", rtp, "recv_rtp_sink_0") || !sydra_link_static_request(sources->rtcp_video_, "src", rtp, "recv_rtcp_sink_0") || !sydra_link_static_request(sources->rtp_audio_, "src", rtp, "recv_rtp_sink_1") || !sydra_link_static_request(sources->rtcp_audio_, "src", rtp, "recv_rtcp_sink_1")) return FALSE; log_printf(DEBUG, "udp sources linked successfully!"); 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", preview_bin_desc); if(!qr || !preview_bin) { return FALSE; } log_printf(DEBUG, "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(DEBUG, "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", opt->video_enc_rec_); 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", opt->audio_enc_rec_); if(!ea) return FALSE; ta = ae->tee_raw_; } log_printf(DEBUG, "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]; //TODO: fix this hardcoded length 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(DEBUG, "recorder path linked successfully!"); return TRUE; } GstElement* create_sender_pipeline(options_t* opt, struct udp_sinks *udp) { GstElement *pipeline = gst_pipeline_new ("sydra"); 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(DEBUG, "rtpbin created successfully!"); struct av_elements video = { "video", opt->video_src_, NULL, NULL, opt->video_enc_, NULL, NULL, opt->video_payloader_, NULL }; struct av_elements audio = { "audio", opt->audio_src_, NULL, NULL, opt->audio_enc_, NULL, NULL, opt->audio_payloader_, NULL }; if(!create_av_elements(&video, pipeline, rtp, 0) || !create_av_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(DEBUG, "sender pipeline created successfully!"); return pipeline; } /* V_RTP_CAPS='application/x-rtp,media=video,clock-rate=90000,encoding-name=VP8-DRAFT-IETF-01,caps="video/x-vp8"' V_DEPAY='rtpvp8depay' V_DECODER='vp8dec' V_SINK='videoconvert ! videoscale add-borders=true ! xvimagesink' A_RTP_CAPS='application/x-rtp,media=audio,clock-rate=48000,payload=96,encoding-name=X-GST-OPUS-DRAFT-SPITTKA-00,caps="audio/x-opus"' A_DEPAY='rtpopusdepay' A_DECODER='opusdec' A_SINK='audioconvert ! autoaudiosink' udpsrc caps=$V_RTP_CAPS port=$PORT_RTP_V ! rtpbin.recv_rtp_sink_0 udpsrc caps=$A_RTP_CAPS port=$PORT_RTP_A ! rtpbin.recv_rtp_sink_1 udpsrc port=$PORT_RTCP_V ! rtpbin.recv_rtcp_sink_0 udpsrc port=$PORT_RTCP_A ! rtpbin.recv_rtcp_sink_1 rtpbin. ! $V_DEPAY ! tee ! queue ! $V_DECODER ! tee ! queue ! $V_SINK rtpbin. ! $A_DEPAY ! tee ! queue ! $A_DECODER ! tee ! queue ! $A_SINK */ 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(DEBUG, "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 element found for pad %s", src_pad_name); } g_free(src_pad_name); } GstElement* create_receiver_pipeline(options_t* opt, struct udp_sources *udp) { GstElement *pipeline = gst_pipeline_new ("sydra"); 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(DEBUG, "rtpbin created successfully!"); GstElement **depays = g_new(GstElement*, 2); depays[0] = sydra_create_element(opt->video_depayloader_, NULL); depays[1] = sydra_create_element(opt->audio_depayloader_, NULL); if(!depays[0] || !depays[1]) { log_printf(ERROR, "creating depayloaders failed"); return NULL; } gst_bin_add_many(GST_BIN(pipeline), depays[0], depays[1], NULL); g_signal_connect_closure(rtp, "pad-added", g_cclosure_new(G_CALLBACK(rtpbin_pad_added), depays, NULL), FALSE); if(!create_udp_sources(opt, pipeline, rtp, udp)) { return NULL; } log_printf(DEBUG, "receiver pipeline created successfully!"); return pipeline; }