2 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
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.
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.
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.
23 #include <gst/rtsp-server/rtsp-server.h>
25 #include "test-onvif-server.h"
27 GST_DEBUG_CATEGORY_STATIC (onvif_server_debug);
28 #define GST_CAT_DEFAULT (onvif_server_debug)
30 #define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
32 if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
33 GST_ERROR ("Could not create element %s", name); \
36 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
37 GST_ERROR ("Could not add element %s", name); \
42 /* This simulates an archive of recordings running from 01-01-1900 to 01-01-2000.
44 * This is implemented by repeating the file provided at the command line, with
45 * an empty interval of 5 seconds in-between. We intercept relevant events to
46 * translate them, and update the timestamps on the output buffers.
49 #define INTERVAL (5 * GST_SECOND)
51 /* January the first, 2000 */
52 #define END_DATE 3155673600 * GST_SECOND
54 static gchar *filename;
60 GstEvent *incoming_seek;
61 GstEvent *outgoing_seek;
62 GstClockTime trickmode_interval;
65 const GstSegment *incoming_segment;
66 gboolean sent_segment;
67 GstClockTime ts_offset;
72 G_DEFINE_TYPE (ReplayBin, replay_bin, GST_TYPE_BIN);
75 replay_bin_init (ReplayBin * self)
77 self->incoming_seek = NULL;
78 self->outgoing_seek = NULL;
79 self->trickmode_interval = 0;
81 self->sent_segment = FALSE;
82 self->min_pts = GST_CLOCK_TIME_NONE;
86 replay_bin_class_init (ReplayBinClass * klass)
93 return GST_ELEMENT (g_object_new (replay_bin_get_type (), NULL));
97 demux_pad_added_cb (GstElement * demux, GstPad * pad, GstGhostPad * ghost)
99 GstCaps *caps = gst_pad_get_current_caps (pad);
100 GstStructure *s = gst_caps_get_structure (caps, 0);
102 if (gst_structure_has_name (s, "video/x-h264")) {
103 gst_ghost_pad_set_target (ghost, pad);
106 gst_caps_unref (caps);
110 query_seekable (GstPad * ghost, gint64 * start, gint64 * stop)
117 target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghost));
119 query = gst_query_new_seeking (GST_FORMAT_TIME);
121 gst_pad_query (target, query);
123 gst_query_parse_seeking (query, &format, &seekable, start, stop);
127 gst_object_unref (target);
131 translate_seek (ReplayBin * self, GstPad * pad, GstEvent * ievent)
133 GstEvent *oevent = NULL;
137 GstSeekType start_type, stop_type;
139 gint64 istart, istop; /* Incoming */
140 gint64 ustart, ustop; /* Upstream */
141 gint64 ostart, ostop; /* Outgoing */
142 guint32 seqnum = gst_event_get_seqnum (ievent);
144 gst_event_parse_seek (ievent, &rate, &format, &flags, &start_type, &start,
147 if (!GST_CLOCK_TIME_IS_VALID (stop))
150 gst_event_parse_seek_trickmode_interval (ievent, &self->trickmode_interval);
155 query_seekable (pad, &ustart, &ustop);
158 /* First, from where we should seek the file */
159 ostart = istart % (ustop + INTERVAL);
161 /* This may end up in our empty interval */
162 if (ostart > ustop) {
163 istart += ostart - ustop;
167 /* Then, up to where we should seek it */
168 ostop = MIN (ustop, ostart + (istop - istart));
170 /* First up to where we should seek the file */
171 ostop = istop % (ustop + INTERVAL);
173 /* This may end up in our empty interval */
175 istop -= ostop - ustop;
179 ostart = MAX (0, ostop - (istop - istart));
182 /* We may be left with nothing to actually play, in this
183 * case we won't seek upstream, and emit the expected events
185 if (istart > istop) {
190 event = gst_event_new_flush_start ();
191 gst_event_set_seqnum (event, seqnum);
192 gst_pad_push_event (pad, event);
194 event = gst_event_new_flush_stop (TRUE);
195 gst_event_set_seqnum (event, seqnum);
196 gst_pad_push_event (pad, event);
198 gst_segment_init (&segment, format);
199 gst_segment_do_seek (&segment, rate, format, flags, start_type, start,
200 stop_type, stop, &update);
202 event = gst_event_new_segment (&segment);
203 gst_event_set_seqnum (event, seqnum);
204 gst_pad_push_event (pad, event);
206 event = gst_event_new_eos ();
207 gst_event_set_seqnum (event, seqnum);
208 gst_pad_push_event (pad, event);
213 /* Lastly, how much will remain to play back (this remainder includes the interval) */
214 if (stop - start > ostop - ostart)
215 self->remainder = (stop - start) - (ostop - ostart);
217 flags |= GST_SEEK_FLAG_SEGMENT;
220 gst_event_new_seek (rate, format, flags, start_type, ostart, stop_type,
222 gst_event_set_seek_trickmode_interval (oevent, self->trickmode_interval);
223 gst_event_set_seqnum (oevent, seqnum);
225 GST_DEBUG ("Translated event to %" GST_PTR_FORMAT
226 " (remainder: %" G_GINT64_FORMAT ")", oevent, self->remainder);
233 replay_bin_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
235 ReplayBin *self = REPLAY_BIN (parent);
237 gboolean forward = TRUE;
239 switch (GST_EVENT_TYPE (event)) {
242 GST_DEBUG ("Processing seek event %" GST_PTR_FORMAT, event);
244 self->incoming_seek = event;
246 gst_event_replace (&self->outgoing_seek, NULL);
247 self->sent_segment = FALSE;
249 event = translate_seek (self, pad, event);
254 self->outgoing_seek = gst_event_ref (event);
262 return gst_pad_event_default (pad, parent, event);
268 replay_bin_query_func (GstPad * pad, GstObject * parent, GstQuery * query)
270 ReplayBin *self = REPLAY_BIN (parent);
272 gboolean forward = TRUE;
274 switch (GST_QUERY_TYPE (query)) {
275 case GST_QUERY_SEEKING:
276 /* We are seekable from the beginning till the end of time */
277 gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0,
278 GST_CLOCK_TIME_NONE);
281 case GST_QUERY_SEGMENT:
282 gst_query_set_segment (query, self->segment.rate, self->segment.format,
283 self->segment.start, self->segment.stop);
289 GST_DEBUG ("Processed query %" GST_PTR_FORMAT, query);
292 return gst_pad_query_default (pad, parent, query);
298 translate_segment (GstPad * pad, GstEvent * ievent)
300 ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
302 gdouble irate, orate;
303 GstFormat iformat, oformat;
304 GstSeekFlags iflags, oflags;
305 GstSeekType istart_type, ostart_type, istop_type, ostop_type;
306 gint64 istart, ostart, istop, ostop;
309 gst_event_parse_segment (ievent, &self->incoming_segment);
311 if (!self->outgoing_seek) {
315 gst_segment_init (&segment, GST_FORMAT_TIME);
317 gst_segment_do_seek (&segment, 1.0, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_SET,
318 0, GST_SEEK_TYPE_SET, END_DATE, &update);
320 ret = gst_event_new_segment (&segment);
321 gst_event_unref (ievent);
325 if (!self->sent_segment) {
326 gst_event_parse_seek (self->incoming_seek, &irate, &iformat, &iflags,
327 &istart_type, &istart, &istop_type, &istop);
328 gst_event_parse_seek (self->outgoing_seek, &orate, &oformat, &oflags,
329 &ostart_type, &ostart, &ostop_type, &ostop);
334 if (self->incoming_segment->rate > 0)
335 self->ts_offset = istart - ostart;
337 self->ts_offset = istop - ostop;
339 istart += self->incoming_segment->start - ostart;
340 istop += self->incoming_segment->stop - ostop;
342 gst_segment_init (&self->segment, self->incoming_segment->format);
344 gst_segment_do_seek (&self->segment, self->incoming_segment->rate,
345 self->incoming_segment->format,
346 (GstSeekFlags) self->incoming_segment->flags, GST_SEEK_TYPE_SET,
347 (guint64) istart, GST_SEEK_TYPE_SET, (guint64) istop, &update);
349 self->min_pts = istart;
351 ret = gst_event_new_segment (&self->segment);
353 self->sent_segment = TRUE;
355 gst_event_unref (ievent);
357 GST_DEBUG ("Translated segment: %" GST_PTR_FORMAT ", "
358 "ts_offset: %" G_GUINT64_FORMAT, ret, self->ts_offset);
368 handle_segment_done (ReplayBin * self, GstPad * pad)
372 if (self->remainder < INTERVAL) {
374 event = gst_event_new_eos ();
375 gst_event_set_seqnum (event, gst_event_get_seqnum (self->incoming_seek));
376 gst_pad_push_event (pad, event);
378 gint64 ustart, ustop;
379 gint64 ostart, ostop;
383 /* Signify the end of a contiguous section of recording */
384 s = gst_structure_new ("GstOnvifTimestamp",
385 "ntp-offset", G_TYPE_UINT64, 0, "discont", G_TYPE_BOOLEAN, TRUE, NULL);
387 event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);
389 gst_pad_push_event (pad, event);
391 query_seekable (pad, &ustart, &ustop);
393 self->remainder -= INTERVAL;
395 if (self->incoming_segment->rate > 0) {
397 ostop = MIN (ustop, self->remainder);
399 ostart = MAX (ustop - self->remainder, 0);
403 self->remainder = MAX (self->remainder - ostop - ostart, 0);
406 gst_event_new_seek (self->segment.rate, self->segment.format,
407 self->segment.flags & ~GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, ostart,
408 GST_SEEK_TYPE_SET, ostop);
409 gst_event_set_seek_trickmode_interval (event, self->trickmode_interval);
411 if (self->incoming_segment->rate > 0)
412 self->ts_offset += INTERVAL + ustop;
414 self->ts_offset -= INTERVAL + ustop;
416 GST_DEBUG ("New offset: %" GST_TIME_FORMAT,
417 GST_TIME_ARGS (self->ts_offset));
419 GST_DEBUG ("Seeking to %" GST_PTR_FORMAT, event);
420 target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
421 gst_pad_send_event (target, event);
422 gst_object_unref (target);
426 static GstPadProbeReturn
427 replay_bin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
429 ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
430 GstPadProbeReturn ret = GST_PAD_PROBE_OK;
432 GST_DEBUG ("Probed %" GST_PTR_FORMAT, info->data);
434 switch (GST_EVENT_TYPE (info->data)) {
435 case GST_EVENT_SEGMENT:
437 GstEvent *translated;
439 GST_DEBUG ("Probed segment %" GST_PTR_FORMAT, info->data);
441 translated = translate_segment (pad, GST_EVENT (info->data));
443 info->data = translated;
445 ret = GST_PAD_PROBE_HANDLED;
449 case GST_EVENT_SEGMENT_DONE:
451 handle_segment_done (self, pad);
452 ret = GST_PAD_PROBE_HANDLED;
462 static GstPadProbeReturn
463 replay_bin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
465 ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
466 GstPadProbeReturn ret = GST_PAD_PROBE_OK;
468 if (GST_BUFFER_PTS (info->data) > self->incoming_segment->stop) {
469 ret = GST_PAD_PROBE_DROP;
473 if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (info->data)))
474 GST_BUFFER_PTS (info->data) += self->ts_offset;
475 if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (info->data)))
476 GST_BUFFER_DTS (info->data) += self->ts_offset;
478 GST_LOG ("Pushing buffer %" GST_PTR_FORMAT, info->data);
485 create_replay_bin (GstElement * parent)
487 GstElement *ret, *src, *demux;
490 ret = replay_bin_new ();
491 if (!gst_bin_add (GST_BIN (parent), ret)) {
492 gst_object_unref (ret);
496 MAKE_AND_ADD (src, ret, "filesrc", fail, NULL);
497 MAKE_AND_ADD (demux, ret, "qtdemux", fail, NULL);
499 ghost = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC);
500 gst_element_add_pad (ret, ghost);
502 gst_pad_set_event_function (ghost, replay_bin_event_func);
503 gst_pad_add_probe (ghost, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
504 replay_bin_event_probe, NULL, NULL);
505 gst_pad_add_probe (ghost, GST_PAD_PROBE_TYPE_BUFFER, replay_bin_buffer_probe,
507 gst_pad_set_query_function (ghost, replay_bin_query_func);
509 if (!gst_element_link (src, demux))
512 g_object_set (src, "location", filename, NULL);
513 g_signal_connect (demux, "pad-added", G_CALLBACK (demux_pad_added_cb), ghost);
523 /* A simple factory to set up our replay bin */
527 GstRTSPOnvifMediaFactory parent;
530 G_DEFINE_TYPE (OnvifFactory, onvif_factory, GST_TYPE_RTSP_MEDIA_FACTORY);
533 onvif_factory_init (OnvifFactory * factory)
538 onvif_factory_create_element (GstRTSPMediaFactory * factory,
539 const GstRTSPUrl * url)
541 GstElement *replay_bin, *q1, *parse, *pay, *onvifts, *q2;
542 GstElement *ret = gst_bin_new (NULL);
543 GstElement *pbin = gst_bin_new ("pay0");
544 GstPad *sinkpad, *srcpad;
546 if (!(replay_bin = create_replay_bin (ret)))
549 MAKE_AND_ADD (q1, pbin, "queue", fail, NULL);
550 MAKE_AND_ADD (parse, pbin, "h264parse", fail, NULL);
551 MAKE_AND_ADD (pay, pbin, "rtph264pay", fail, NULL);
552 MAKE_AND_ADD (onvifts, pbin, "rtponviftimestamp", fail, NULL);
553 MAKE_AND_ADD (q2, pbin, "queue", fail, NULL);
555 gst_bin_add (GST_BIN (ret), pbin);
557 if (!gst_element_link_many (q1, parse, pay, onvifts, q2, NULL))
560 sinkpad = gst_element_get_static_pad (q1, "sink");
561 gst_element_add_pad (pbin, gst_ghost_pad_new ("sink", sinkpad));
562 gst_object_unref (sinkpad);
564 if (!gst_element_link (replay_bin, pbin))
567 srcpad = gst_element_get_static_pad (q2, "src");
568 gst_element_add_pad (pbin, gst_ghost_pad_new ("src", srcpad));
569 gst_object_unref (srcpad);
571 g_object_set (onvifts, "set-t-bit", TRUE, "set-e-bit", TRUE, "ntp-offset",
572 G_GUINT64_CONSTANT (0), "drop-out-of-segment", FALSE, NULL);
574 gst_element_set_clock (onvifts, gst_system_clock_obtain ());
580 gst_object_unref (ret);
586 onvif_factory_class_init (OnvifFactoryClass * klass)
588 GstRTSPMediaFactoryClass *mf_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);
590 mf_class->create_element = onvif_factory_create_element;
593 static GstRTSPMediaFactory *
594 onvif_factory_new (void)
596 GstRTSPMediaFactory *result;
599 GST_RTSP_MEDIA_FACTORY (g_object_new (onvif_factory_get_type (), NULL));
605 main (int argc, char *argv[])
608 GstRTSPServer *server;
609 GstRTSPMountPoints *mounts;
610 GstRTSPMediaFactory *factory;
611 GOptionContext *optctx;
612 GError *error = NULL;
615 optctx = g_option_context_new ("<filename.mp4> - ONVIF RTSP Server, MP4");
616 g_option_context_add_group (optctx, gst_init_get_option_group ());
617 if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
618 g_printerr ("Error parsing options: %s\n", error->message);
619 g_option_context_free (optctx);
620 g_clear_error (&error);
624 g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
628 g_option_context_free (optctx);
630 GST_DEBUG_CATEGORY_INIT (onvif_server_debug, "onvif-server", 0,
633 loop = g_main_loop_new (NULL, FALSE);
635 server = gst_rtsp_onvif_server_new ();
637 mounts = gst_rtsp_server_get_mount_points (server);
639 factory = onvif_factory_new ();
640 gst_rtsp_media_factory_set_media_gtype (factory, GST_TYPE_RTSP_ONVIF_MEDIA);
642 gst_rtsp_mount_points_add_factory (mounts, "/test", factory);
644 g_object_unref (mounts);
646 gst_rtsp_server_attach (server, NULL);
648 service = gst_rtsp_server_get_service (server);
649 g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", service);
651 g_main_loop_run (loop);