/* * 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"); 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"); 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"); 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"); 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, NULL); g_object_set(G_OBJECT(rtcp_video), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, "sync", FALSE, NULL); g_object_set(G_OBJECT(rtp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, "sync", FALSE, NULL); g_object_set(G_OBJECT(rtcp_audio), "host", opt->rtp_host_reflector_, "port", rtp_port_reflector++, "sync", 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"); GstElement* qrtpv = sydra_create_element("queue", NULL); sources->rtcp_video_ = sydra_create_element("udpsrc", "udprtcpv"); tees.rtcpv_ = sydra_create_element("tee", "rtcpvt"); GstElement* qrtcpv = sydra_create_element("queue", NULL); sources->rtp_audio_ = sydra_create_element("udpsrc", "udprtpa"); tees.rtpa_ = sydra_create_element("tee", "rtpat"); GstElement* qrtpa = sydra_create_element("queue", NULL); sources->rtcp_audio_ = sydra_create_element("udpsrc", "udprtcpa"); tees.rtcpa_ = sydra_create_element("tee", "rtcpat"); GstElement* qrtcpa = sydra_create_element("queue", NULL); if(!(sources->rtp_video_) || !(sources->rtcp_video_) || !(sources->rtp_audio_) || !(sources->rtcp_audio_) || !(tees.rtpv_) || !(tees.rtcpv_) || !(tees.rtpa_) || !(tees.rtcpa_) || !qrtpv || !qrtcpv || !qrtpa || !qrtcpa) 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_bin_add_many(GST_BIN(pipeline), qrtpv, qrtcpv, qrtpa, qrtcpa, 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_static(tees.rtpv_, "src_%u", qrtpv, "sink") || !sydra_link_static_request(qrtpv, "src", rtp, "recv_rtp_sink_0") || !sydra_link_request_static(tees.rtcpv_, "src_%u", qrtcpv, "sink") || !sydra_link_static_request(qrtcpv, "src", rtp, "recv_rtcp_sink_0") || !sydra_link_request_static(tees.rtpa_, "src_%u", qrtpa, "sink") || !sydra_link_static_request(qrtpa, "src", rtp, "recv_rtp_sink_1") || !sydra_link_request_static(tees.rtcpa_, "src_%u", qrtcpa, "sink") || !sydra_link_static_request(qrtcpa, "src", 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", 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-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); if(opt->rec_mux_) { if(!create_recorder_elements(opt, pipeline, &video, &audio)) return NULL; } log_printf(INFO, "receiver pipeline created successfully!"); return pipeline; }