· 8 years ago · Feb 18, 2018, 01:36 PM
1/* GStreamer
2 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.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#include <stdio.h>
21#include <string.h>
22#include <stdlib.h>
23#include <getopt.h>
24#include <gst/gst.h>
25#include <gst/app/gstappsrc.h>
26#include <gst/app/gstappsink.h>
27#include <gst/rtsp-server/rtsp-server.h>
28#include <macqc-core.h>
29
30typedef struct _App App;
31struct _App
32{
33 GstElement *videosink;
34};
35App s_app;
36
37typedef struct
38{
39 App *globalApp;
40 GstClockTime timestamp;
41} MyContext;
42
43GMainLoop *loop;
44GstClockTime base_time, minlat, maxlat;
45gint fps_n, fps_d;
46
47void usage()
48{
49 printf("Usage: macq-gst-rtsp-server -f ini_file [-p port]\n");
50 printf("Any appsrc launch line with property \"name=mysrc\" works as long as it contains elements named pay%%d.\n");
51 printf("Each element with pay%%d names will be a stream.\n");
52 exit(0);
53}
54
55const char* get_in_section(miniconfiguration* conf, const char* section, const char* key)
56{
57 const char* value;
58 if (!miniconfig_get_in_section(conf, section, key, &value, ""))
59 {
60 g_print ("[rtsp-server] failed to load parameter %s. exiting.\n", key);
61 exit(1);
62 }
63 return value;
64}
65
66void check_port(const char* port)
67{
68 int port_nb = atoi(port);
69 if ((port_nb < 1024 || port_nb > 65535) && port_nb != 554)
70 {
71 g_print("Port number must be 554 or between 1024 and 65535\n");
72 exit(1);
73 }
74}
75
76/* called when we need to give data to appsrc */
77static void
78need_data (GstElement * appsrc, guint unused, MyContext * ctx)
79{
80 GstFlowReturn ret;
81 GstSample *sample = gst_app_sink_pull_sample (GST_APP_SINK(ctx->globalApp->videosink));
82 if (sample != NULL)
83 {
84 GstBuffer *buffer = gst_sample_get_buffer(sample);
85 gst_sample_unref (sample);
86 GST_BUFFER_PTS (buffer) = ctx->timestamp;
87 GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
88 ctx->timestamp += GST_BUFFER_DURATION (buffer);
89 g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
90 }
91}
92
93/* called when a new media src_pipeline is constructed. We can query the
94 * src_pipeline and configure our appsrc */
95static void
96media_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media,
97 App *app)
98{
99 GstElement *element, *appsrc;
100 GstClock *clock;
101 MyContext *ctx;
102
103 /* get the element used for providing the streams of the media */
104 element = gst_rtsp_media_get_element (media);
105
106 /* set appsrc pipeline clock to the same clock as appsink */
107 clock = gst_system_clock_obtain();
108 gst_pipeline_use_clock(GST_PIPELINE(element), clock);
109 gst_object_unref(clock);
110 gst_element_set_base_time(element, base_time);
111 gst_element_set_start_time(element, GST_CLOCK_TIME_NONE);
112
113 /* get our appsrc, we named it 'mysrc' with the name property */
114 appsrc = gst_bin_get_by_name_recurse_up (GST_BIN (element), "mysrc");
115
116 gst_rtsp_media_set_reusable(media, TRUE);
117
118 /* this instructs appsrc that we will be dealing with timed buffer */
119 gst_util_set_object_arg (G_OBJECT (appsrc), "format", "time");
120
121 /* configure the caps of the video */
122 g_object_set (G_OBJECT (appsrc), "max-bytes", gst_app_src_get_max_bytes(GST_APP_SRC(appsrc)), NULL);
123
124 /* configure the min and max latencies */
125 g_object_set (G_OBJECT (appsrc), "min-latency", minlat, NULL);
126 g_object_set (G_OBJECT (appsrc), "max-latency", maxlat, NULL);
127
128 ctx = g_new0 (MyContext, 1);
129 ctx->globalApp = app;
130 ctx->timestamp = 0;
131
132 /* make sure the data is freed when the media is gone */
133 g_object_set_data_full (G_OBJECT (media), "my-extra-data", ctx,
134 (GDestroyNotify) g_free);
135
136 /* install the callback that will be called when a buffer is needed */
137 g_signal_connect (appsrc, "need-data", (GCallback) need_data, ctx);
138 gst_object_unref(appsrc);
139 gst_object_unref(element);
140}
141
142// Bus message handler
143static gboolean
144bus_callback(GstBus *bus, GstMessage *msg, gpointer data)
145{
146 GstElement *sink_pipeline = GST_ELEMENT(data);
147 switch (GST_MESSAGE_TYPE(msg)) {
148 case GST_MESSAGE_EOS:
149 if (!gst_element_seek(sink_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
150 GST_SEEK_TYPE_SET, 1000000000,
151 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
152 g_message("Seek failed!");
153 }
154 g_printerr("End of stream\n");
155 g_main_loop_quit(loop);
156 break;
157 case GST_MESSAGE_ERROR: {
158 gchar *debug;
159 GError *error;
160 gst_message_parse_error(msg, &error, &debug);
161 g_free(debug);
162 g_printerr("Error in bus: %s\n", error->message);
163 g_error_free(error);
164 g_main_loop_quit(loop);
165 break;
166 }
167 default:
168 break;
169 }
170 return TRUE;
171}
172
173int
174main (int argc, char *argv[])
175{
176 int c;
177 const char *file = NULL;
178 char *port = NULL;
179
180 static struct option long_options[] =
181 {
182 {"file", required_argument, 0, 'f'},
183 {"port", optional_argument, 0, 'p'},
184 {"help", no_argument, 0, 'h'},
185 {0, 0, 0, 0}
186 };
187
188 while (1)
189 {
190
191 c = getopt_long (argc, argv, "hf:p:", long_options, NULL);
192 if (c == -1)
193 break;
194
195 switch (c)
196 {
197 case 'f':
198 file = optarg;
199 break;
200
201 case 'p':
202 port = optarg;
203 break;
204
205 default:
206 usage();
207 }
208 }
209
210 if (!file)
211 {
212 usage();
213 }
214
215 GstRTSPServer *server;
216 GstRTSPMountPoints *mounts;
217 GstRTSPMediaFactory *factory;
218 GstElement *sink_pipeline;
219 GstBus *bus;
220 GstClock *clock;
221 App *app = &s_app;
222
223 /* read config file */
224 const char *appsink_chain = NULL;
225 const char *appsrc_chain = NULL;
226 const char *rtsp_stream_name = NULL;
227 const char *port_from_file = NULL;
228
229 miniconfiguration* conf = miniconfig_create();
230 if (mfile_exists(file))
231 miniconfig_load_file(conf, file, 0);
232 else
233 {
234 g_print ("Can't find configuration file: \"%s\"\n", file);
235 exit(1);
236 }
237
238 appsrc_chain = get_in_section(conf, "general", "appsrc-chain");
239 appsink_chain = get_in_section(conf, "general", "appsink-chain");
240 rtsp_stream_name = get_in_section(conf, "general", "rtsp-stream-name");
241 miniconfig_get_in_section(conf, "general", "port", &port_from_file, "");
242
243 gst_init (&argc, &argv);
244
245 loop = g_main_loop_new (NULL, FALSE);
246
247
248 /* create sink_pipeline */
249 GError *error = NULL;
250 sink_pipeline = gst_parse_launch(appsink_chain, &error);
251 if(error != NULL)
252 g_printerr("Error in appsink_chain: %s\n", error->message);
253
254 clock = gst_system_clock_obtain();
255 gst_pipeline_use_clock(GST_PIPELINE(sink_pipeline), clock);
256 gst_object_unref(clock);
257 base_time = gst_clock_get_time(gst_system_clock_obtain());
258 gst_element_set_base_time(sink_pipeline, base_time);
259 gst_element_set_start_time(sink_pipeline, GST_CLOCK_TIME_NONE);
260
261 app->videosink = gst_bin_get_by_name(GST_BIN(sink_pipeline), "mysink");
262 if(!app->videosink) {
263 g_printerr("Failed to get sink element by name.\n");
264 return -1;
265 }
266
267 /* parse pipeline string for any framerate property */
268 GstIterator *it = gst_bin_iterate_recurse(GST_BIN(sink_pipeline));
269 GValue item = G_VALUE_INIT;
270 GstElement *element;
271 GstCaps *caps;
272 const GstStructure *structure;
273 const GValue *framerate;
274 gboolean done = FALSE;
275 fps_n = 1, fps_d = 1;
276
277 while (!done)
278 {
279 switch(gst_iterator_next (it, &item))
280 {
281 case GST_ITERATOR_OK:
282 element = g_value_get_object(&item);
283 if (strcmp(G_OBJECT_TYPE_NAME(element),"GstCapsFilter") == 0)
284 {
285 g_object_get(G_OBJECT(element), "caps", &caps, NULL);
286 structure = gst_caps_get_structure(caps, 0);
287 framerate = gst_structure_get_value(structure, "framerate");
288 if (framerate != NULL)
289 {
290 fps_n = gst_value_get_fraction_numerator(framerate);
291 fps_d = gst_value_get_fraction_denominator(framerate);
292 }
293 }
294 g_value_reset(&item);
295 break;
296 case GST_ITERATOR_RESYNC:
297 gst_iterator_resync(it);
298 break;
299 default:
300 done = TRUE;
301 break;
302 }
303 }
304 g_value_unset(&item);
305 gst_iterator_free(it);
306
307 bus = gst_pipeline_get_bus (GST_PIPELINE (sink_pipeline));
308 gst_bus_add_watch (bus, bus_callback, sink_pipeline);
309 gst_object_unref(bus);
310
311 /* start playing sink_pipeline */
312 gst_element_set_state (sink_pipeline, GST_STATE_PLAYING);
313
314 /* wait until it's up and running or failed */
315 if (gst_element_get_state(sink_pipeline, NULL, NULL, -1) == GST_STATE_CHANGE_FAILURE)
316 {
317 g_error("Failed to go into PLAYING state");
318 }
319
320 GstQuery *query = gst_query_new_latency();
321 if (gst_element_query(sink_pipeline, query))
322 {
323 gboolean live;
324 gst_query_parse_latency(query, &live, &minlat, &maxlat);
325 g_print("LIVE: %d\n", live);
326 printf("Minimum latency: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(minlat));
327 printf("Maximum latency: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(maxlat));
328 }
329 gst_query_unref(query);
330
331 /* create a server instance */
332 server = gst_rtsp_server_new ();
333
334 if (!port)
335 {
336 if (port_from_file[0])
337 port = strdup(port_from_file);
338 else
339 port = "8554";
340 }
341 check_port(port);
342 gst_rtsp_server_set_service(server, port);
343
344 /* get the mount points for this server, every server has a default object
345 * that be used to map uri mount points to media factories */
346 mounts = gst_rtsp_server_get_mount_points (server);
347
348 /* make a media factory for a test stream. The default media factory can use
349 * gst-launch syntax to create src_pipelines.
350 * any launch line works as long as it contains elements named pay%d. Each
351 * element with pay%d names will be a stream */
352 factory = gst_rtsp_media_factory_new ();
353
354 gst_rtsp_media_factory_set_shared (factory, TRUE);
355 gst_rtsp_media_factory_set_eos_shutdown(factory, TRUE);
356
357 gst_rtsp_media_factory_set_launch (factory, appsrc_chain);
358
359 /* notify when our media is ready, This is called whenever someone asks for
360 * the media and a new src_pipeline with our appsrc is created */
361 g_signal_connect (factory, "media-configure", (GCallback) media_configure,
362 app);
363
364 char *url = malloc(strlen(rtsp_stream_name)+2);
365 sprintf(url, "/%s", rtsp_stream_name);
366
367 /* destroy config file */
368 miniconfig_destroy(conf);
369
370 /* attach the appsrc_chain factory to the /rtsp_stream_name url */
371 gst_rtsp_mount_points_add_factory (mounts, url, factory);
372
373 /* don't need the ref to the mapper anymore */
374 g_object_unref (mounts);
375
376 /* attach the server to the default maincontext */
377 gst_rtsp_server_attach (server, NULL);
378
379 /* start serving */
380 g_print ("stream ready at rtsp://127.0.0.1:%s%s\n", port, url);
381
382 g_main_loop_run (loop);
383
384 g_print("Returned, stopping playback\n");
385 gst_element_set_state (sink_pipeline, GST_STATE_NULL);
386
387 g_print("Deleting sink_pipeline\n");
388 gst_object_unref (GST_OBJECT(sink_pipeline));
389 g_main_loop_unref(loop);
390
391 return 0;
392}