1 /*
2  * Copyright (C) 2016 Sebastian Dröge <sebastian@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "gstsdpsrc.h"
25 #include <gst/app/app.h>
26 #include <string.h>
27 
28 GST_DEBUG_CATEGORY_STATIC (sdp_src_debug);
29 #define GST_CAT_DEFAULT sdp_src_debug
30 
31 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("stream_%u",
32     GST_PAD_SRC,
33     GST_PAD_SOMETIMES,
34     GST_STATIC_CAPS ("application/x-rtp"));
35 
36 enum
37 {
38   PROP_0,
39   PROP_LOCATION,
40   PROP_SDP
41 };
42 
43 static void gst_sdp_src_handler_init (gpointer g_iface, gpointer iface_data);
44 
45 #define gst_sdp_src_parent_class parent_class
46 G_DEFINE_TYPE_WITH_CODE (GstSdpSrc, gst_sdp_src, GST_TYPE_BIN,
47     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_sdp_src_handler_init));
48 
49 static void
gst_sdp_src_finalize(GObject * object)50 gst_sdp_src_finalize (GObject * object)
51 {
52   GstSdpSrc *self = GST_SDP_SRC_CAST (object);
53 
54   if (self->sdp_buffer)
55     gst_buffer_unref (self->sdp_buffer);
56   g_free (self->location);
57   g_free (self->sdp);
58 
59   G_OBJECT_CLASS (parent_class)->finalize (object);
60 }
61 
62 static void
gst_sdp_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)63 gst_sdp_src_get_property (GObject * object, guint prop_id,
64     GValue * value, GParamSpec * pspec)
65 {
66   GstSdpSrc *self = GST_SDP_SRC_CAST (object);
67 
68   switch (prop_id) {
69     case PROP_LOCATION:
70       g_value_set_string (value, self->location);
71       break;
72     case PROP_SDP:
73       g_value_set_string (value, self->sdp);
74       break;
75     default:
76       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
77       break;
78   }
79 }
80 
81 static void
gst_sdp_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)82 gst_sdp_src_set_property (GObject * object, guint prop_id,
83     const GValue * value, GParamSpec * pspec)
84 {
85   GstSdpSrc *self = GST_SDP_SRC_CAST (object);
86 
87   switch (prop_id) {
88     case PROP_LOCATION:
89       g_free (self->location);
90       self->location = g_value_dup_string (value);
91       break;
92     case PROP_SDP:
93       g_free (self->sdp);
94       self->sdp = g_value_dup_string (value);
95       break;
96     default:
97       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
98       break;
99   }
100 }
101 
102 static void
pad_added_cb(GstElement * element,GstPad * pad,gpointer user_data)103 pad_added_cb (GstElement * element, GstPad * pad, gpointer user_data)
104 {
105   GstSdpSrc *self = GST_SDP_SRC_CAST (user_data);
106   GstPad *ghost;
107 
108   ghost =
109       gst_ghost_pad_new_from_template (GST_PAD_NAME (pad), pad,
110       gst_static_pad_template_get (&src_template));
111   gst_pad_set_active (ghost, TRUE);
112   gst_element_add_pad (GST_ELEMENT_CAST (self), ghost);
113 }
114 
115 static void
pad_removed_cb(GstElement * element,GstPad * pad,gpointer user_data)116 pad_removed_cb (GstElement * element, GstPad * pad, gpointer user_data)
117 {
118   GstSdpSrc *self = GST_SDP_SRC_CAST (user_data);
119   GstPad *peer;
120 
121   peer = gst_pad_get_peer (pad);
122   if (peer) {
123     GstPad *ghost =
124         GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (peer)));
125 
126     if (ghost) {
127       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), NULL);
128       gst_element_remove_pad (GST_ELEMENT_CAST (self), ghost);
129       gst_object_unref (ghost);
130     }
131 
132     gst_object_unref (peer);
133   }
134 }
135 
136 static void
no_more_pads_cb(GstElement * element,gpointer user_data)137 no_more_pads_cb (GstElement * element, gpointer user_data)
138 {
139   gst_element_no_more_pads (GST_ELEMENT_CAST (user_data));
140 }
141 
142 static void
remove_pad(const GValue * item,gpointer user_data)143 remove_pad (const GValue * item, gpointer user_data)
144 {
145   GstElement *self = user_data;
146   GstPad *pad = g_value_get_object (item);
147 
148   gst_element_remove_pad (self, pad);
149 }
150 
151 static GstStateChangeReturn
gst_sdp_src_change_state(GstElement * element,GstStateChange transition)152 gst_sdp_src_change_state (GstElement * element, GstStateChange transition)
153 {
154   GstSdpSrc *self = GST_SDP_SRC_CAST (element);
155   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
156 
157   switch (transition) {
158     case GST_STATE_CHANGE_NULL_TO_READY:
159       GST_OBJECT_LOCK (self);
160       if (self->sdp_buffer)
161         gst_buffer_unref (self->sdp_buffer);
162       self->sdp_buffer = NULL;
163 
164       if (self->location && strcmp (self->location, "sdp://") != 0) {
165         /* Do nothing */
166       } else if (self->sdp) {
167         self->sdp_buffer =
168             gst_buffer_new_wrapped (self->sdp, strlen (self->sdp) + 1);
169       } else {
170         ret = GST_STATE_CHANGE_FAILURE;
171       }
172       GST_OBJECT_UNLOCK (self);
173 
174       if (ret != GST_STATE_CHANGE_FAILURE) {
175         if (self->sdp_buffer) {
176           GstCaps *caps = gst_caps_new_empty_simple ("application/sdp");
177 
178           self->src = gst_element_factory_make ("appsrc", NULL);
179           g_object_set (self->src, "caps", caps, "emit-signals", FALSE, NULL);
180           gst_caps_unref (caps);
181         } else {
182           self->src = gst_element_factory_make ("filesrc", NULL);
183           g_object_set (self->src, "location", self->location + 6, NULL);
184         }
185 
186         self->demux = gst_element_factory_make ("sdpdemux", NULL);
187         g_signal_connect (self->demux, "pad-added", G_CALLBACK (pad_added_cb),
188             self);
189         g_signal_connect (self->demux, "pad-removed",
190             G_CALLBACK (pad_removed_cb), self);
191         g_signal_connect (self->demux, "no-more-pads",
192             G_CALLBACK (no_more_pads_cb), self);
193         gst_bin_add_many (GST_BIN_CAST (self), self->src, self->demux, NULL);
194         gst_element_link_pads (self->src, "src", self->demux, "sink");
195       }
196       break;
197     default:
198       break;
199   }
200 
201   if (ret == GST_STATE_CHANGE_FAILURE)
202     return ret;
203   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
204   if (ret == GST_STATE_CHANGE_FAILURE)
205     return ret;
206 
207   switch (transition) {
208     case GST_STATE_CHANGE_READY_TO_NULL:{
209       GstIterator *it;
210 
211       it = gst_element_iterate_src_pads (GST_ELEMENT_CAST (self));
212       while (gst_iterator_foreach (it, remove_pad, self) == GST_ITERATOR_RESYNC)
213         gst_iterator_resync (it);
214       gst_iterator_free (it);
215 
216       if (self->src) {
217         gst_bin_remove (GST_BIN_CAST (self), self->src);
218         self->src = NULL;
219       }
220       if (self->demux) {
221         gst_bin_remove (GST_BIN_CAST (self), self->demux);
222         self->demux = NULL;
223       }
224       break;
225     }
226     case GST_STATE_CHANGE_READY_TO_PAUSED:
227       if (ret != GST_STATE_CHANGE_FAILURE)
228         ret = GST_STATE_CHANGE_NO_PREROLL;
229       if (self->sdp_buffer) {
230         if (gst_app_src_push_buffer (GST_APP_SRC_CAST (self->src),
231                 gst_buffer_ref (self->sdp_buffer)) != GST_FLOW_OK)
232           ret = GST_STATE_CHANGE_FAILURE;
233         else
234           gst_app_src_end_of_stream (GST_APP_SRC_CAST (self->src));
235       }
236       break;
237     default:
238       break;
239   }
240 
241   return ret;
242 }
243 
244 static void
gst_sdp_src_class_init(GstSdpSrcClass * klass)245 gst_sdp_src_class_init (GstSdpSrcClass * klass)
246 {
247   GObjectClass *gobject_class = (GObjectClass *) klass;
248   GstElementClass *element_class = (GstElementClass *) klass;
249 
250   GST_DEBUG_CATEGORY_INIT (sdp_src_debug, "sdpsrc", 0, "SDP Source");
251 
252   gobject_class->finalize = gst_sdp_src_finalize;
253   gobject_class->set_property = gst_sdp_src_set_property;
254   gobject_class->get_property = gst_sdp_src_get_property;
255 
256   g_object_class_install_property (gobject_class, PROP_LOCATION,
257       g_param_spec_string ("location",
258           "Location",
259           "URI to SDP file (sdp:///path/to/file)", NULL,
260           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
261 
262   g_object_class_install_property (gobject_class, PROP_SDP,
263       g_param_spec_string ("sdp",
264           "SDP",
265           "SDP description used instead of location", NULL,
266           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
267 
268   gst_element_class_add_pad_template (element_class,
269       gst_static_pad_template_get (&src_template));
270 
271   gst_element_class_set_static_metadata (element_class, "SDP Source",
272       "Source/Network/RTP",
273       "Stream RTP based on an SDP",
274       "Sebastian Dröge <sebastian@centricular.com>");
275 
276   element_class->change_state = GST_DEBUG_FUNCPTR (gst_sdp_src_change_state);
277 }
278 
279 static void
gst_sdp_src_init(GstSdpSrc * self)280 gst_sdp_src_init (GstSdpSrc * self)
281 {
282 }
283 
284 static GstURIType
gst_sdp_src_get_uri_type(GType type)285 gst_sdp_src_get_uri_type (GType type)
286 {
287   return GST_URI_SRC;
288 }
289 
290 static const gchar *const *
gst_sdp_src_get_protocols(GType type)291 gst_sdp_src_get_protocols (GType type)
292 {
293   static const gchar *protocols[] = { "sdp", 0 };
294 
295   return protocols;
296 }
297 
298 static gchar *
gst_sdp_src_get_uri(GstURIHandler * handler)299 gst_sdp_src_get_uri (GstURIHandler * handler)
300 {
301   gchar *uri = NULL;
302 
303   g_object_get (handler, "location", &uri, NULL);
304 
305   return uri;
306 }
307 
308 static gboolean
gst_sdp_src_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)309 gst_sdp_src_set_uri (GstURIHandler * handler, const gchar * uri,
310     GError ** error)
311 {
312   if (uri && !g_str_has_prefix (uri, "sdp://")) {
313     g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
314         "Invalid SDP URI");
315     return FALSE;
316   }
317 
318   g_object_set (handler, "location", uri, NULL);
319 
320   return TRUE;
321 }
322 
323 static void
gst_sdp_src_handler_init(gpointer g_iface,gpointer iface_data)324 gst_sdp_src_handler_init (gpointer g_iface, gpointer iface_data)
325 {
326   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
327 
328   iface->get_type = gst_sdp_src_get_uri_type;
329   iface->get_protocols = gst_sdp_src_get_protocols;
330   iface->get_uri = gst_sdp_src_get_uri;
331   iface->set_uri = gst_sdp_src_set_uri;
332 }
333