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/rtsp.h>
39 SeekParameters *seek_params;
50 gboolean has_argument;
52 gboolean (*func) (Context * ctx, gchar * arg, gboolean * async);
55 static gboolean cmd_help (Context * ctx, gchar * arg, gboolean * async);
56 static gboolean cmd_pause (Context * ctx, gchar * arg, gboolean * async);
57 static gboolean cmd_play (Context * ctx, gchar * arg, gboolean * async);
58 static gboolean cmd_reverse (Context * ctx, gchar * arg, gboolean * async);
59 static gboolean cmd_range (Context * ctx, gchar * arg, gboolean * async);
60 static gboolean cmd_speed (Context * ctx, gchar * arg, gboolean * async);
61 static gboolean cmd_frames (Context * ctx, gchar * arg, gboolean * async);
62 static gboolean cmd_rate_control (Context * ctx, gchar * arg, gboolean * async);
63 static gboolean cmd_step_forward (Context * ctx, gchar * arg, gboolean * async);
65 static Command commands[] = {
66 {"help", FALSE, "Display list of valid commands", cmd_help},
67 {"pause", FALSE, "Pause playback", cmd_pause},
68 {"play", FALSE, "Resume playback", cmd_play},
69 {"reverse", FALSE, "Reverse playback direction", cmd_reverse},
71 "Seek to the specified range, example: \"range: 19000101T000000Z-19000101T000200Z\"",
73 {"speed", TRUE, "Set the playback speed, example: \"speed: 1.0\"", cmd_speed},
75 "Set the frames trickmode, example: \"frames: intra\", \"frames: predicted\", \"frames: intra/1000\"",
77 {"rate-control", TRUE,
78 "Set the rate control mode, example: \"rate-control: no\"",
80 {"s", FALSE, "Step to the following frame (in current playback direction)",
85 static gchar *rtsp_address;
87 #define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
89 if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
90 GST_ERROR ("Could not create element %s", name); \
93 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
94 GST_ERROR ("Could not add element %s", name); \
99 #define DEFAULT_RANGE "19000101T000000Z-19000101T000200Z"
100 #define DEFAULT_SPEED 1.0
101 #define DEFAULT_FRAMES "none"
102 #define DEFAULT_RATE_CONTROL "yes"
103 #define DEFAULT_REVERSE FALSE
106 pad_added_cb (GstElement * src, GstPad * srcpad, GstElement * peer)
108 GstPad *sinkpad = gst_element_get_static_pad (peer, "sink");
110 gst_pad_link (srcpad, sinkpad);
112 gst_object_unref (sinkpad);
116 setup (Context * ctx)
118 GstElement *onvifparse, *queue, *vdepay, *vdec, *vconv, *toverlay, *tee,
120 gboolean ret = FALSE;
122 MAKE_AND_ADD (ctx->src, ctx->pipe, "rtspsrc", done, NULL);
123 MAKE_AND_ADD (queue, ctx->pipe, "queue", done, NULL);
124 MAKE_AND_ADD (onvifparse, ctx->pipe, "rtponvifparse", done, NULL);
125 MAKE_AND_ADD (vdepay, ctx->pipe, "rtph264depay", done, NULL);
126 MAKE_AND_ADD (vdec, ctx->pipe, "avdec_h264", done, NULL);
127 MAKE_AND_ADD (vconv, ctx->pipe, "videoconvert", done, NULL);
128 MAKE_AND_ADD (toverlay, ctx->pipe, "timeoverlay", done, NULL);
129 MAKE_AND_ADD (tee, ctx->pipe, "tee", done, NULL);
130 MAKE_AND_ADD (vqueue, ctx->pipe, "queue", done, NULL);
131 MAKE_AND_ADD (ctx->sink, ctx->pipe, "xvimagesink", done, NULL);
133 g_object_set (ctx->src, "location", rtsp_address, NULL);
134 g_object_set (ctx->src, "onvif-mode", TRUE, NULL);
135 g_object_set (ctx->src, "tcp-timeout", 0, NULL);
136 g_object_set (toverlay, "show-times-as-dates", TRUE, NULL);
138 g_object_set (toverlay, "datetime-format", "%a %d, %b %Y - %T", NULL);
140 g_signal_connect (ctx->src, "pad-added", G_CALLBACK (pad_added_cb), queue);
142 if (!gst_element_link_many (queue, onvifparse, vdepay, vdec, vconv, toverlay,
143 tee, vqueue, ctx->sink, NULL)) {
147 g_object_set (ctx->src, "onvif-rate-control", FALSE, "is-live", FALSE, NULL);
149 if (!g_strcmp0 (ctx->seek_params->rate_control, "no")) {
150 g_object_set (ctx->sink, "sync", FALSE, NULL);
160 get_current_position (Context * ctx, gboolean reverse)
166 g_object_get (ctx->sink, "last-sample", &sample, NULL);
168 buffer = gst_sample_get_buffer (sample);
170 ret = GST_BUFFER_PTS (buffer);
172 if (reverse && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer)))
173 ret += GST_BUFFER_DURATION (buffer);
175 gst_sample_unref (sample);
181 translate_seek_parameters (Context * ctx, SeekParameters * seek_params)
183 GstEvent *ret = NULL;
184 gchar *range_str = NULL;
185 GstRTSPTimeRange *rtsp_range;
186 GstSeekType start_type, stop_type;
187 GstClockTime start, stop;
190 gchar **split = NULL;
191 GstClockTime trickmode_interval = 0;
193 range_str = g_strdup_printf ("clock=%s", seek_params->range);
195 if (gst_rtsp_range_parse (range_str, &rtsp_range) != GST_RTSP_OK) {
196 GST_ERROR ("Failed to parse range %s", range_str);
200 gst_rtsp_range_get_times (rtsp_range, &start, &stop);
203 GST_ERROR ("Invalid range, start > stop: %s", seek_params->range);
207 start_type = GST_SEEK_TYPE_SET;
208 stop_type = GST_SEEK_TYPE_SET;
210 if (!ctx->new_range) {
211 GstClockTime current_position =
212 get_current_position (ctx, seek_params->reverse);
214 if (seek_params->reverse) {
215 stop_type = GST_SEEK_TYPE_SET;
216 stop = current_position;
218 start_type = GST_SEEK_TYPE_SET;
219 start = current_position;
223 ctx->new_range = FALSE;
225 flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE;
227 split = g_strsplit (seek_params->frames, "/", 2);
229 if (!g_strcmp0 (split[0], "intra")) {
234 interval = g_ascii_strtoull (split[1], &end, 10);
236 if (!end || *end != '\0') {
237 GST_ERROR ("Unexpected interval value %s", split[1]);
241 trickmode_interval = interval * GST_MSECOND;
243 flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
244 } else if (!g_strcmp0 (split[0], "predicted")) {
246 GST_ERROR ("Predicted frames mode does not allow an interval (%s)",
247 seek_params->frames);
250 flags |= GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED;
251 } else if (g_strcmp0 (split[0], "none")) {
252 GST_ERROR ("Invalid frames mode (%s)", seek_params->frames);
256 if (seek_params->reverse) {
257 rate = -1.0 * seek_params->speed;
259 rate = 1.0 * seek_params->speed;
262 ret = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
263 start_type, start, stop_type, stop);
265 if (trickmode_interval)
266 gst_event_set_seek_trickmode_interval (ret, trickmode_interval);
275 static void prompt_on (Context * ctx);
276 static void prompt_off (Context * ctx);
279 cmd_help (Context * ctx, gchar * arg, gboolean * async)
286 for (i = 0; commands[i].name; i++) {
287 g_print ("%s: %s\n", commands[i].name, commands[i].help);
294 cmd_pause (Context * ctx, gchar * arg, gboolean * async)
297 GstStateChangeReturn state_ret;
299 g_print ("Pausing\n");
301 state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PAUSED);
303 *async = state_ret == GST_STATE_CHANGE_ASYNC;
304 ret = state_ret != GST_STATE_CHANGE_FAILURE;
310 cmd_play (Context * ctx, gchar * arg, gboolean * async)
313 GstStateChangeReturn state_ret;
315 g_print ("Playing\n");
317 state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PLAYING);
319 *async = state_ret == GST_STATE_CHANGE_ASYNC;
320 ret = state_ret != GST_STATE_CHANGE_FAILURE;
326 do_seek (Context * ctx)
328 gboolean ret = FALSE;
331 if (!(event = translate_seek_parameters (ctx, ctx->seek_params))) {
332 GST_ERROR ("Failed to create seek event");
336 if (ctx->seek_params->reverse)
337 g_object_set (ctx->src, "onvif-rate-control", FALSE, NULL);
339 if (ctx->reset_sync) {
340 g_object_set (ctx->sink, "sync", TRUE, NULL);
341 ctx->reset_sync = FALSE;
344 if (!gst_element_send_event (ctx->src, event)) {
345 GST_ERROR ("Failed to seek rtspsrc");
346 g_main_loop_quit (ctx->loop);
357 cmd_reverse (Context * ctx, gchar * arg, gboolean * async)
361 g_print ("Reversing playback direction\n");
363 ctx->seek_params->reverse = !ctx->seek_params->reverse;
367 *async = ret == TRUE;
373 cmd_range (Context * ctx, gchar * arg, gboolean * async)
377 g_print ("Switching to new range\n");
379 g_free (ctx->seek_params->range);
380 ctx->seek_params->range = g_strdup (arg);
381 ctx->new_range = TRUE;
385 *async = ret == TRUE;
391 cmd_speed (Context * ctx, gchar * arg, gboolean * async)
393 gboolean ret = FALSE;
394 gchar *endptr = NULL;
397 new_speed = g_ascii_strtod (arg, &endptr);
399 g_print ("Switching gears\n");
401 if (endptr == NULL || *endptr != '\0' || new_speed <= 0.0) {
402 GST_ERROR ("Invalid value for speed: %s", arg);
406 ctx->seek_params->speed = new_speed;
410 *async = ret == TRUE;
415 cmd_frames (Context * ctx, gchar * arg, gboolean * async)
419 g_print ("Changing Frames trickmode\n");
421 g_free (ctx->seek_params->frames);
422 ctx->seek_params->frames = g_strdup (arg);
424 *async = ret == TRUE;
430 cmd_rate_control (Context * ctx, gchar * arg, gboolean * async)
432 gboolean ret = FALSE;
436 if (!g_strcmp0 (arg, "no")) {
437 g_object_set (ctx->sink, "sync", FALSE, NULL);
439 } else if (!g_strcmp0 (arg, "yes")) {
440 /* TODO: there probably is a solution that doesn't involve sending
441 * a request to the server to reset our position */
442 ctx->reset_sync = TRUE;
446 GST_ERROR ("Invalid rate-control: %s", arg);
457 cmd_step_forward (Context * ctx, gchar * arg, gboolean * async)
459 gboolean ret = FALSE;
462 event = gst_event_new_step (GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE);
464 g_print ("Stepping\n");
466 if (!gst_element_send_event (ctx->sink, event)) {
467 GST_ERROR ("Failed to step forward");
474 *async = ret == TRUE;
479 handle_command (Context * ctx, gchar * cmd)
483 gboolean valid_command = FALSE;
485 split = g_strsplit (cmd, ":", 0);
487 cmd = g_strstrip (split[0]);
489 if (cmd == NULL || *cmd == '\0') {
494 for (i = 0; commands[i].name; i++) {
495 if (!g_strcmp0 (commands[i].name, cmd)) {
496 valid_command = TRUE;
497 if (commands[i].has_argument && g_strv_length (split) != 2) {
498 g_print ("Command %s expects exactly one argument:\n%s: %s\n", cmd,
499 commands[i].name, commands[i].help);
500 } else if (!commands[i].has_argument && g_strv_length (split) != 1) {
501 g_print ("Command %s expects no argument:\n%s: %s\n", cmd,
502 commands[i].name, commands[i].help);
504 gboolean async = FALSE;
506 if (commands[i].func (ctx,
507 commands[i].has_argument ? g_strstrip (split[1]) : NULL, &async)
517 if (!valid_command) {
518 g_print ("Invalid command %s\n> ", cmd);
526 io_callback (GIOChannel * io, GIOCondition condition, Context * ctx)
530 GError *error = NULL;
535 switch (g_io_channel_read_line (io, &str, NULL, NULL, &error)) {
536 case G_IO_STATUS_ERROR:
537 GST_ERROR ("Failed to read commands from stdin: %s", error->message);
538 g_clear_error (&error);
539 g_main_loop_quit (ctx->loop);
541 case G_IO_STATUS_EOF:
542 g_print ("EOF received, bye\n");
543 g_main_loop_quit (ctx->loop);
545 case G_IO_STATUS_AGAIN:
547 case G_IO_STATUS_NORMAL:
548 handle_command (ctx, str);
555 GST_ERROR ("Failed to read commands from stdin");
556 g_main_loop_quit (ctx->loop);
568 #define STDIN_FILENO _fileno(stdin)
569 #else /* !G_OS_WIN32 */
570 #define STDIN_FILENO 0
571 #endif /* G_OS_WIN32 */
572 #endif /* STDIN_FILENO */
575 prompt_on (Context * ctx)
578 ctx->io = g_io_channel_unix_new (STDIN_FILENO);
580 g_io_add_watch (ctx->io, G_IO_IN, (GIOFunc) io_callback, ctx);
585 prompt_off (Context * ctx)
588 g_source_remove (ctx->io_watch_id);
589 g_io_channel_unref (ctx->io);
594 bus_message_cb (GstBus * bus, GstMessage * message, Context * ctx)
596 switch (GST_MESSAGE_TYPE (message)) {
597 case GST_MESSAGE_STATE_CHANGED:{
598 GstState olds, news, pendings;
600 if (GST_MESSAGE_SRC (message) == GST_OBJECT (ctx->pipe)) {
601 gst_message_parse_state_changed (message, &olds, &news, &pendings);
602 GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (ctx->pipe),
603 GST_DEBUG_GRAPH_SHOW_ALL, "playing");
607 case GST_MESSAGE_ERROR:{
608 GError *error = NULL;
611 gst_message_parse_error (message, &error, &debug);
613 gst_printerr ("Error: %s (%s)\n", error->message, debug);
614 g_clear_error (&error);
616 g_main_loop_quit (ctx->loop);
619 case GST_MESSAGE_LATENCY:{
620 gst_bin_recalculate_latency (GST_BIN (ctx->pipe));
623 case GST_MESSAGE_ASYNC_DONE:{
634 main (int argc, char **argv)
636 GOptionContext *optctx;
637 Context ctx = { 0, };
640 GError *error = NULL;
641 const gchar *range = NULL;
642 const gchar *frames = NULL;
643 const gchar *rate_control = NULL;
644 gchar *default_speed =
645 g_strdup_printf ("Speed to request (default: %.1f)", DEFAULT_SPEED);
646 SeekParameters seek_params =
647 { NULL, DEFAULT_SPEED, NULL, NULL, DEFAULT_REVERSE };
648 GOptionEntry entries[] = {
649 {"range", 0, 0, G_OPTION_ARG_STRING, &range,
650 "Range to seek (default: " DEFAULT_RANGE ")", "RANGE"},
651 {"speed", 0, 0, G_OPTION_ARG_DOUBLE, &seek_params.speed,
652 default_speed, "SPEED"},
653 {"frames", 0, 0, G_OPTION_ARG_STRING, &frames,
654 "Frames to request (default: " DEFAULT_FRAMES ")", "FRAMES"},
655 {"rate-control", 0, 0, G_OPTION_ARG_STRING, &rate_control,
656 "Apply rate control on the client side (default: "
657 DEFAULT_RATE_CONTROL ")", "RATE_CONTROL"},
658 {"reverse", 0, 0, G_OPTION_ARG_NONE, &seek_params.reverse,
659 "Playback direction", ""},
663 optctx = g_option_context_new ("<rtsp-url> - ONVIF RTSP Client");
664 g_option_context_add_main_entries (optctx, entries, NULL);
665 g_option_context_add_group (optctx, gst_init_get_option_group ());
666 if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
667 g_printerr ("Error parsing options: %s\n", error->message);
668 g_option_context_free (optctx);
669 g_clear_error (&error);
673 g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
676 rtsp_address = argv[1];
677 g_option_context_free (optctx);
679 seek_params.range = g_strdup (range ? range : DEFAULT_RANGE);
680 seek_params.frames = g_strdup (frames ? frames : DEFAULT_FRAMES);
681 seek_params.rate_control =
682 g_strdup (rate_control ? rate_control : DEFAULT_RATE_CONTROL);
684 if (seek_params.speed <= 0.0) {
685 GST_ERROR ("SPEED must be a positive number");
689 ctx.seek_params = &seek_params;
690 ctx.new_range = TRUE;
691 ctx.reset_sync = FALSE;
693 ctx.pipe = gst_pipeline_new (NULL);
695 g_printerr ("Damn\n");
699 g_print ("Type help for the list of available commands\n");
703 ctx.loop = g_main_loop_new (NULL, FALSE);
705 bus = gst_pipeline_get_bus (GST_PIPELINE (ctx.pipe));
706 gst_bus_add_watch (bus, (GstBusFunc) bus_message_cb, &ctx);
708 /* This will make rtspsrc progress to the OPEN state, at which point we can seek it */
709 if (!gst_element_set_state (ctx.pipe, GST_STATE_PLAYING))
712 g_main_loop_run (ctx.loop);
714 g_main_loop_unref (ctx.loop);
716 gst_bus_remove_watch (bus);
717 gst_object_unref (bus);
718 gst_element_set_state (ctx.pipe, GST_STATE_NULL);
719 gst_object_unref (ctx.pipe);
724 g_free (seek_params.range);
725 g_free (seek_params.frames);
726 g_free (seek_params.rate_control);
727 g_free (default_speed);