2013-11-13 20:11:35 +00:00
|
|
|
/* GStreamer
|
|
|
|
* Copyright (C) 2013 Collabora Ltd.
|
|
|
|
* @author Torrie Fischer <torrie.fischer@collabora.co.uk>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Library General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Library General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Library General Public
|
|
|
|
* License along with this library; if not, write to the
|
|
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include <gst/gst.h>
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An RTP server
|
|
|
|
* creates two sessions and streams audio on one, video on the other, with RTCP
|
|
|
|
* on both sessions. The destination is 127.0.0.1.
|
|
|
|
*
|
|
|
|
* In both sessions, we set "rtprtxsend" as the session's "aux" element
|
|
|
|
* in rtpbin, which enables RFC4588 retransmission for that session.
|
|
|
|
*
|
|
|
|
* .-------. .-------. .-------. .------------. .-------.
|
|
|
|
* |audiots| |alawenc| |pcmapay| | rtpbin | |udpsink|
|
|
|
|
* | src->sink src->sink src->send_rtp_0 send_rtp_0->sink |
|
|
|
|
* '-------' '-------' '-------' | | '-------'
|
|
|
|
* | |
|
|
|
|
* .-------. .---------. .---------. | | .-------.
|
|
|
|
* |audiots| |theoraenc| |theorapay| | | |udpsink|
|
|
|
|
* | src->sink src->sink src->send_rtp_1 send_rtp_1->sink |
|
|
|
|
* '-------' '---------' '---------' | | '-------'
|
|
|
|
* | |
|
|
|
|
* .------. | |
|
|
|
|
* |udpsrc| | | .-------.
|
|
|
|
* | src->recv_rtcp_0 | |udpsink|
|
|
|
|
* '------' | send_rtcp_0->sink |
|
|
|
|
* | | '-------'
|
|
|
|
* .------. | |
|
|
|
|
* |udpsrc| | | .-------.
|
|
|
|
* | src->recv_rtcp_1 | |udpsink|
|
|
|
|
* '------' | send_rtcp_1->sink |
|
|
|
|
* '------------' '-------'
|
|
|
|
*
|
|
|
|
* To keep the set of ports consistent across both this server and the
|
|
|
|
* corresponding client, a SessionData struct maps a rtpbin session number to
|
|
|
|
* a GstBin and is used to create the corresponding udp sinks with correct
|
|
|
|
* ports.
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef struct _SessionData
|
|
|
|
{
|
|
|
|
int ref;
|
|
|
|
guint sessionNum;
|
|
|
|
GstElement *input;
|
|
|
|
} SessionData;
|
|
|
|
|
|
|
|
static SessionData *
|
|
|
|
session_ref (SessionData * data)
|
|
|
|
{
|
|
|
|
g_atomic_int_inc (&data->ref);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
session_unref (gpointer data)
|
|
|
|
{
|
|
|
|
SessionData *session = (SessionData *) data;
|
|
|
|
if (g_atomic_int_dec_and_test (&session->ref)) {
|
|
|
|
g_free (session);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static SessionData *
|
|
|
|
session_new (guint sessionNum)
|
|
|
|
{
|
|
|
|
SessionData *ret = g_new0 (SessionData, 1);
|
|
|
|
ret->sessionNum = sessionNum;
|
|
|
|
return session_ref (ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Used to generate informative messages during pipeline startup
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
cb_state (GstBus * bus, GstMessage * message, gpointer data)
|
|
|
|
{
|
|
|
|
GstObject *pipe = GST_OBJECT (data);
|
|
|
|
GstState old, new, pending;
|
|
|
|
gst_message_parse_state_changed (message, &old, &new, &pending);
|
|
|
|
if (message->src == pipe) {
|
|
|
|
g_print ("Pipeline %s changed state from %s to %s\n",
|
|
|
|
GST_OBJECT_NAME (message->src),
|
|
|
|
gst_element_state_get_name (old), gst_element_state_get_name (new));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Creates a GstGhostPad named "src" on the given bin, pointed at the "src" pad
|
|
|
|
* of the given element
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
setup_ghost (GstElement * src, GstBin * bin)
|
|
|
|
{
|
|
|
|
GstPad *srcPad = gst_element_get_static_pad (src, "src");
|
|
|
|
GstPad *binPad = gst_ghost_pad_new ("src", srcPad);
|
|
|
|
gst_element_add_pad (GST_ELEMENT (bin), binPad);
|
|
|
|
}
|
|
|
|
|
|
|
|
static SessionData *
|
|
|
|
make_audio_session (guint sessionNum)
|
|
|
|
{
|
|
|
|
SessionData *session;
|
|
|
|
GstBin *audioBin = GST_BIN (gst_bin_new (NULL));
|
|
|
|
GstElement *audioSrc = gst_element_factory_make ("audiotestsrc", NULL);
|
|
|
|
GstElement *encoder = gst_element_factory_make ("alawenc", NULL);
|
|
|
|
GstElement *payloader = gst_element_factory_make ("rtppcmapay", NULL);
|
|
|
|
g_object_set (audioSrc, "is-live", TRUE, NULL);
|
|
|
|
|
|
|
|
gst_bin_add_many (audioBin, audioSrc, encoder, payloader, NULL);
|
|
|
|
gst_element_link_many (audioSrc, encoder, payloader, NULL);
|
|
|
|
|
|
|
|
setup_ghost (payloader, audioBin);
|
|
|
|
|
|
|
|
session = session_new (sessionNum);
|
|
|
|
session->input = GST_ELEMENT (audioBin);
|
|
|
|
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
|
|
|
static SessionData *
|
|
|
|
make_video_session (guint sessionNum)
|
|
|
|
{
|
|
|
|
GstBin *videoBin = GST_BIN (gst_bin_new (NULL));
|
|
|
|
GstElement *videoSrc = gst_element_factory_make ("videotestsrc", NULL);
|
|
|
|
GstElement *encoder = gst_element_factory_make ("theoraenc", NULL);
|
|
|
|
GstElement *payloader = gst_element_factory_make ("rtptheorapay", NULL);
|
|
|
|
GstCaps *videoCaps;
|
|
|
|
SessionData *session;
|
|
|
|
g_object_set (videoSrc, "is-live", TRUE, "horizontal-speed", 1, NULL);
|
|
|
|
g_object_set (payloader, "config-interval", 2, NULL);
|
|
|
|
|
|
|
|
gst_bin_add_many (videoBin, videoSrc, encoder, payloader, NULL);
|
|
|
|
videoCaps = gst_caps_new_simple ("video/x-raw",
|
|
|
|
"width", G_TYPE_INT, 352,
|
|
|
|
"height", G_TYPE_INT, 288, "framerate", GST_TYPE_FRACTION, 15, 1, NULL);
|
|
|
|
gst_element_link_filtered (videoSrc, encoder, videoCaps);
|
|
|
|
gst_element_link (encoder, payloader);
|
|
|
|
|
|
|
|
setup_ghost (payloader, videoBin);
|
|
|
|
|
|
|
|
session = session_new (sessionNum);
|
|
|
|
session->input = GST_ELEMENT (videoBin);
|
|
|
|
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstElement *
|
|
|
|
request_aux_sender (GstElement * rtpbin, guint sessid, SessionData * session)
|
|
|
|
{
|
|
|
|
GstElement *rtx, *bin;
|
|
|
|
GstPad *pad;
|
|
|
|
gchar *name;
|
2014-01-02 14:18:52 +00:00
|
|
|
GstStructure *pt_map;
|
2013-11-13 20:11:35 +00:00
|
|
|
|
|
|
|
GST_INFO ("creating AUX sender");
|
|
|
|
bin = gst_bin_new (NULL);
|
|
|
|
rtx = gst_element_factory_make ("rtprtxsend", NULL);
|
2014-01-02 14:18:52 +00:00
|
|
|
pt_map = gst_structure_new ("application/x-rtp-pt-map",
|
2014-01-14 14:56:42 +00:00
|
|
|
"8", G_TYPE_UINT, 98, "96", G_TYPE_UINT, 99, NULL);
|
2014-01-02 14:18:52 +00:00
|
|
|
g_object_set (rtx, "payload-type-map", pt_map, NULL);
|
|
|
|
gst_structure_free (pt_map);
|
2013-11-13 20:11:35 +00:00
|
|
|
gst_bin_add (GST_BIN (bin), rtx);
|
|
|
|
|
|
|
|
pad = gst_element_get_static_pad (rtx, "src");
|
|
|
|
name = g_strdup_printf ("src_%u", sessid);
|
|
|
|
gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
|
|
|
|
g_free (name);
|
|
|
|
gst_object_unref (pad);
|
|
|
|
|
|
|
|
pad = gst_element_get_static_pad (rtx, "sink");
|
|
|
|
name = g_strdup_printf ("sink_%u", sessid);
|
|
|
|
gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
|
|
|
|
g_free (name);
|
|
|
|
gst_object_unref (pad);
|
|
|
|
|
|
|
|
return bin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function sets up the UDP sinks and sources for RTP/RTCP, adds the
|
|
|
|
* given session's bin into the pipeline, and links it to the properly numbered
|
|
|
|
* pads on the rtpbin
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
add_stream (GstPipeline * pipe, GstElement * rtpBin, SessionData * session)
|
|
|
|
{
|
|
|
|
GstElement *rtpSink = gst_element_factory_make ("udpsink", NULL);
|
|
|
|
GstElement *rtcpSink = gst_element_factory_make ("udpsink", NULL);
|
|
|
|
GstElement *rtcpSrc = gst_element_factory_make ("udpsrc", NULL);
|
|
|
|
GstElement *identity = gst_element_factory_make ("identity", NULL);
|
|
|
|
int basePort;
|
|
|
|
gchar *padName;
|
|
|
|
|
|
|
|
basePort = 5000 + (session->sessionNum * 6);
|
|
|
|
|
|
|
|
gst_bin_add_many (GST_BIN (pipe), rtpSink, rtcpSink, rtcpSrc, identity,
|
|
|
|
session->input, NULL);
|
|
|
|
|
|
|
|
/* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
|
|
|
|
g_signal_connect (rtpBin, "request-aux-sender",
|
|
|
|
(GCallback) request_aux_sender, session);
|
|
|
|
|
|
|
|
g_object_set (rtpSink, "port", basePort, "host", "127.0.0.1", NULL);
|
|
|
|
g_object_set (rtcpSink, "port", basePort + 1, "host", "127.0.0.1", "sync",
|
|
|
|
FALSE, "async", FALSE, NULL);
|
|
|
|
g_object_set (rtcpSrc, "port", basePort + 5, NULL);
|
|
|
|
|
|
|
|
/* this is just to drop some rtp packets at random, to demonstrate
|
|
|
|
* that rtprtxsend actually works */
|
|
|
|
g_object_set (identity, "drop-probability", 0.01, NULL);
|
|
|
|
|
|
|
|
padName = g_strdup_printf ("send_rtp_sink_%u", session->sessionNum);
|
|
|
|
gst_element_link_pads (session->input, "src", rtpBin, padName);
|
|
|
|
g_free (padName);
|
|
|
|
|
|
|
|
/* link rtpbin to udpsink directly here if you don't want
|
|
|
|
* artificial packet loss */
|
|
|
|
padName = g_strdup_printf ("send_rtp_src_%u", session->sessionNum);
|
|
|
|
gst_element_link_pads (rtpBin, padName, identity, "sink");
|
|
|
|
gst_element_link (identity, rtpSink);
|
|
|
|
g_free (padName);
|
|
|
|
|
|
|
|
padName = g_strdup_printf ("send_rtcp_src_%u", session->sessionNum);
|
|
|
|
gst_element_link_pads (rtpBin, padName, rtcpSink, "sink");
|
|
|
|
g_free (padName);
|
|
|
|
|
|
|
|
padName = g_strdup_printf ("recv_rtcp_sink_%u", session->sessionNum);
|
|
|
|
gst_element_link_pads (rtcpSrc, "src", rtpBin, padName);
|
|
|
|
g_free (padName);
|
|
|
|
|
|
|
|
g_print ("New RTP stream on %i/%i/%i\n", basePort, basePort + 1,
|
|
|
|
basePort + 5);
|
|
|
|
|
|
|
|
session_unref (session);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main (int argc, char **argv)
|
|
|
|
{
|
|
|
|
GstPipeline *pipe;
|
|
|
|
GstBus *bus;
|
|
|
|
SessionData *videoSession;
|
|
|
|
SessionData *audioSession;
|
|
|
|
GstElement *rtpBin;
|
|
|
|
GMainLoop *loop;
|
|
|
|
|
|
|
|
gst_init (&argc, &argv);
|
|
|
|
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
|
|
|
|
pipe = GST_PIPELINE (gst_pipeline_new (NULL));
|
|
|
|
bus = gst_element_get_bus (GST_ELEMENT (pipe));
|
|
|
|
g_signal_connect (bus, "message::state-changed", G_CALLBACK (cb_state), pipe);
|
|
|
|
gst_bus_add_signal_watch (bus);
|
|
|
|
gst_object_unref (bus);
|
|
|
|
|
|
|
|
rtpBin = gst_element_factory_make ("rtpbin", NULL);
|
|
|
|
|
|
|
|
gst_bin_add (GST_BIN (pipe), rtpBin);
|
|
|
|
|
|
|
|
videoSession = make_video_session (0);
|
|
|
|
audioSession = make_audio_session (1);
|
|
|
|
add_stream (pipe, rtpBin, videoSession);
|
|
|
|
add_stream (pipe, rtpBin, audioSession);
|
|
|
|
|
|
|
|
g_print ("starting server pipeline\n");
|
|
|
|
gst_element_set_state (GST_ELEMENT (pipe), GST_STATE_PLAYING);
|
|
|
|
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
|
|
|
|
g_print ("stopping server pipeline\n");
|
|
|
|
gst_element_set_state (GST_ELEMENT (pipe), GST_STATE_NULL);
|
|
|
|
|
|
|
|
gst_object_unref (pipe);
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|