1 /* GStreamer
2  * Copyright (C) 2013 Collabora Ltd.
3  *   @author Torrie Fischer <torrie.fischer@collabora.co.uk>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 #include <gst/gst.h>
21 #include <gst/rtp/rtp.h>
22 
23 /*
24  * An RTP server
25  *  creates two sessions and streams audio on one, video on the other, with RTCP
26  *  on both sessions. The destination is 127.0.0.1.
27  *
28  *  In both sessions, we set "rtprtxsend" as the session's "aux" element
29  *  in rtpbin, which enables RFC4588 retransmission for that session.
30  *
31  *  .-------.    .-------.    .-------.      .------------.       .-------.
32  *  |audiots|    |alawenc|    |pcmapay|      | rtpbin     |       |udpsink|
33  *  |      src->sink    src->sink    src->send_rtp_0 send_rtp_0->sink     |
34  *  '-------'    '-------'    '-------'      |            |       '-------'
35  *                                           |            |
36  *  .-------.    .---------.    .---------.  |            |       .-------.
37  *  |audiots|    |theoraenc|    |theorapay|  |            |       |udpsink|
38  *  |      src->sink      src->sink  src->send_rtp_1 send_rtp_1->sink     |
39  *  '-------'    '---------'    '---------'  |            |       '-------'
40  *                                           |            |
41  *                               .------.    |            |
42  *                               |udpsrc|    |            |       .-------.
43  *                               |     src->recv_rtcp_0   |       |udpsink|
44  *                               '------'    |       send_rtcp_0->sink    |
45  *                                           |            |       '-------'
46  *                               .------.    |            |
47  *                               |udpsrc|    |            |       .-------.
48  *                               |     src->recv_rtcp_1   |       |udpsink|
49  *                               '------'    |       send_rtcp_1->sink    |
50  *                                           '------------'       '-------'
51  *
52  * To keep the set of ports consistent across both this server and the
53  * corresponding client, a SessionData struct maps a rtpbin session number to
54  * a GstBin and is used to create the corresponding udp sinks with correct
55  * ports.
56  */
57 
58 typedef struct _SessionData
59 {
60   int ref;
61   guint sessionNum;
62   GstElement *input;
63 } SessionData;
64 
65 static SessionData *
session_ref(SessionData * data)66 session_ref (SessionData * data)
67 {
68   g_atomic_int_inc (&data->ref);
69   return data;
70 }
71 
72 static void
session_unref(gpointer data)73 session_unref (gpointer data)
74 {
75   SessionData *session = (SessionData *) data;
76   if (g_atomic_int_dec_and_test (&session->ref)) {
77     g_free (session);
78   }
79 }
80 
81 static SessionData *
session_new(guint sessionNum)82 session_new (guint sessionNum)
83 {
84   SessionData *ret = g_new0 (SessionData, 1);
85   ret->sessionNum = sessionNum;
86   return session_ref (ret);
87 }
88 
89 /*
90  * Used to generate informative messages during pipeline startup
91  */
92 static void
cb_state(GstBus * bus,GstMessage * message,gpointer data)93 cb_state (GstBus * bus, GstMessage * message, gpointer data)
94 {
95   GstObject *pipe = GST_OBJECT (data);
96   GstState old, new, pending;
97   gst_message_parse_state_changed (message, &old, &new, &pending);
98   if (message->src == pipe) {
99     g_print ("Pipeline %s changed state from %s to %s\n",
100         GST_OBJECT_NAME (message->src),
101         gst_element_state_get_name (old), gst_element_state_get_name (new));
102   }
103 }
104 
105 /*
106  * Creates a GstGhostPad named "src" on the given bin, pointed at the "src" pad
107  * of the given element
108  */
109 static void
setup_ghost(GstElement * src,GstBin * bin)110 setup_ghost (GstElement * src, GstBin * bin)
111 {
112   GstPad *srcPad = gst_element_get_static_pad (src, "src");
113   GstPad *binPad = gst_ghost_pad_new ("src", srcPad);
114   gst_element_add_pad (GST_ELEMENT (bin), binPad);
115 }
116 
117 static SessionData *
make_audio_session(guint sessionNum)118 make_audio_session (guint sessionNum)
119 {
120   SessionData *session;
121   GstBin *audioBin = GST_BIN (gst_bin_new (NULL));
122   GstElement *audioSrc = gst_element_factory_make ("audiotestsrc", NULL);
123   GstElement *encoder = gst_element_factory_make ("alawenc", NULL);
124   GstElement *payloader = gst_element_factory_make ("rtppcmapay", NULL);
125   g_object_set (audioSrc, "is-live", TRUE, NULL);
126 
127   gst_bin_add_many (audioBin, audioSrc, encoder, payloader, NULL);
128   gst_element_link_many (audioSrc, encoder, payloader, NULL);
129 
130   setup_ghost (payloader, audioBin);
131 
132   session = session_new (sessionNum);
133   session->input = GST_ELEMENT (audioBin);
134 
135   return session;
136 }
137 
138 static SessionData *
make_video_session(guint sessionNum)139 make_video_session (guint sessionNum)
140 {
141   GstBin *videoBin = GST_BIN (gst_bin_new (NULL));
142   GstElement *videoSrc = gst_element_factory_make ("videotestsrc", NULL);
143   GstElement *encoder = gst_element_factory_make ("theoraenc", NULL);
144   GstElement *payloader = gst_element_factory_make ("rtptheorapay", NULL);
145   GstCaps *videoCaps;
146   SessionData *session;
147   g_object_set (videoSrc, "is-live", TRUE, "horizontal-speed", 1, NULL);
148   g_object_set (payloader, "config-interval", 2, NULL);
149 
150   gst_bin_add_many (videoBin, videoSrc, encoder, payloader, NULL);
151   videoCaps = gst_caps_new_simple ("video/x-raw",
152       "width", G_TYPE_INT, 352,
153       "height", G_TYPE_INT, 288, "framerate", GST_TYPE_FRACTION, 15, 1, NULL);
154   gst_element_link_filtered (videoSrc, encoder, videoCaps);
155   gst_element_link (encoder, payloader);
156 
157   setup_ghost (payloader, videoBin);
158 
159   session = session_new (sessionNum);
160   session->input = GST_ELEMENT (videoBin);
161 
162   return session;
163 }
164 
165 static GstElement *
request_aux_sender(GstElement * rtpbin,guint sessid,SessionData * session)166 request_aux_sender (GstElement * rtpbin, guint sessid, SessionData * session)
167 {
168   GstElement *rtx, *bin;
169   GstPad *pad;
170   gchar *name;
171   GstStructure *pt_map;
172 
173   GST_INFO ("creating AUX sender");
174   bin = gst_bin_new (NULL);
175   rtx = gst_element_factory_make ("rtprtxsend", NULL);
176   pt_map = gst_structure_new ("application/x-rtp-pt-map",
177       "8", G_TYPE_UINT, 98, "96", G_TYPE_UINT, 99, NULL);
178   g_object_set (rtx, "payload-type-map", pt_map, NULL);
179   gst_structure_free (pt_map);
180   gst_bin_add (GST_BIN (bin), rtx);
181 
182   pad = gst_element_get_static_pad (rtx, "src");
183   name = g_strdup_printf ("src_%u", sessid);
184   gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
185   g_free (name);
186   gst_object_unref (pad);
187 
188   pad = gst_element_get_static_pad (rtx, "sink");
189   name = g_strdup_printf ("sink_%u", sessid);
190   gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
191   g_free (name);
192   gst_object_unref (pad);
193 
194   return bin;
195 }
196 
197 /*
198  * This function sets up the UDP sinks and sources for RTP/RTCP, adds the
199  * given session's bin into the pipeline, and links it to the properly numbered
200  * pads on the rtpbin
201  */
202 static void
add_stream(GstPipeline * pipe,GstElement * rtpBin,SessionData * session)203 add_stream (GstPipeline * pipe, GstElement * rtpBin, SessionData * session)
204 {
205   GstElement *rtpSink = gst_element_factory_make ("udpsink", NULL);
206   GstElement *rtcpSink = gst_element_factory_make ("udpsink", NULL);
207   GstElement *rtcpSrc = gst_element_factory_make ("udpsrc", NULL);
208   GstElement *identity = gst_element_factory_make ("identity", NULL);
209   int basePort;
210   gchar *padName;
211 
212   basePort = 5000 + (session->sessionNum * 6);
213 
214   gst_bin_add_many (GST_BIN (pipe), rtpSink, rtcpSink, rtcpSrc, identity,
215       session->input, NULL);
216 
217   /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
218   g_signal_connect (rtpBin, "request-aux-sender",
219       (GCallback) request_aux_sender, session);
220 
221   g_object_set (rtpSink, "port", basePort, "host", "127.0.0.1", NULL);
222   g_object_set (rtcpSink, "port", basePort + 1, "host", "127.0.0.1", "sync",
223       FALSE, "async", FALSE, NULL);
224   g_object_set (rtcpSrc, "port", basePort + 5, NULL);
225 
226   /* this is just to drop some rtp packets at random, to demonstrate
227    * that rtprtxsend actually works */
228   g_object_set (identity, "drop-probability", 0.01, NULL);
229 
230   padName = g_strdup_printf ("send_rtp_sink_%u", session->sessionNum);
231   gst_element_link_pads (session->input, "src", rtpBin, padName);
232   g_free (padName);
233 
234   /* link rtpbin to udpsink directly here if you don't want
235    * artificial packet loss */
236   padName = g_strdup_printf ("send_rtp_src_%u", session->sessionNum);
237   gst_element_link_pads (rtpBin, padName, identity, "sink");
238   gst_element_link (identity, rtpSink);
239   g_free (padName);
240 
241   padName = g_strdup_printf ("send_rtcp_src_%u", session->sessionNum);
242   gst_element_link_pads (rtpBin, padName, rtcpSink, "sink");
243   g_free (padName);
244 
245   padName = g_strdup_printf ("recv_rtcp_sink_%u", session->sessionNum);
246   gst_element_link_pads (rtcpSrc, "src", rtpBin, padName);
247   g_free (padName);
248 
249   g_print ("New RTP stream on %i/%i/%i\n", basePort, basePort + 1,
250       basePort + 5);
251 
252   session_unref (session);
253 }
254 
255 int
main(int argc,char ** argv)256 main (int argc, char **argv)
257 {
258   GstPipeline *pipe;
259   GstBus *bus;
260   SessionData *videoSession;
261   SessionData *audioSession;
262   GstElement *rtpBin;
263   GMainLoop *loop;
264 
265   gst_init (&argc, &argv);
266 
267   loop = g_main_loop_new (NULL, FALSE);
268 
269   pipe = GST_PIPELINE (gst_pipeline_new (NULL));
270   bus = gst_element_get_bus (GST_ELEMENT (pipe));
271   g_signal_connect (bus, "message::state-changed", G_CALLBACK (cb_state), pipe);
272   gst_bus_add_signal_watch (bus);
273   gst_object_unref (bus);
274 
275   rtpBin = gst_element_factory_make ("rtpbin", NULL);
276   g_object_set (rtpBin, "rtp-profile", GST_RTP_PROFILE_AVPF, NULL);
277 
278   gst_bin_add (GST_BIN (pipe), rtpBin);
279 
280   videoSession = make_video_session (0);
281   audioSession = make_audio_session (1);
282   add_stream (pipe, rtpBin, videoSession);
283   add_stream (pipe, rtpBin, audioSession);
284 
285   g_print ("starting server pipeline\n");
286   gst_element_set_state (GST_ELEMENT (pipe), GST_STATE_PLAYING);
287 
288   g_main_loop_run (loop);
289 
290   g_print ("stopping server pipeline\n");
291   gst_element_set_state (GST_ELEMENT (pipe), GST_STATE_NULL);
292 
293   gst_object_unref (pipe);
294   g_main_loop_unref (loop);
295 
296   return 0;
297 }
298