Merging gst-editing-services
[platform/upstream/gstreamer.git] / subprojects / gst-rtsp-server / examples / test-onvif-client.c
1 /* GStreamer
2  * Copyright (C) 2019 Mathieu Duponchelle <mathieu@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 #include <stdio.h>
21
22 #include <gst/gst.h>
23 #include <gst/rtsp/rtsp.h>
24
25 typedef struct
26 {
27   gchar *range;
28   gdouble speed;
29   gchar *frames;
30   gchar *rate_control;
31   gboolean reverse;
32 } SeekParameters;
33
34 typedef struct
35 {
36   GstElement *src;
37   GstElement *sink;
38   GstElement *pipe;
39   SeekParameters *seek_params;
40   GMainLoop *loop;
41   GIOChannel *io;
42   gboolean new_range;
43   guint io_watch_id;
44   gboolean reset_sync;
45 } Context;
46
47 typedef struct
48 {
49   const gchar *name;
50   gboolean has_argument;
51   const gchar *help;
52     gboolean (*func) (Context * ctx, gchar * arg, gboolean * async);
53 } Command;
54
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);
64
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},
70   {"range", TRUE,
71         "Seek to the specified range, example: \"range: 19000101T000000Z-19000101T000200Z\"",
72       cmd_range},
73   {"speed", TRUE, "Set the playback speed, example: \"speed: 1.0\"", cmd_speed},
74   {"frames", TRUE,
75         "Set the frames trickmode, example: \"frames: intra\", \"frames: predicted\", \"frames: intra/1000\"",
76       cmd_frames},
77   {"rate-control", TRUE,
78         "Set the rate control mode, example: \"rate-control: no\"",
79       cmd_rate_control},
80   {"s", FALSE, "Step to the following frame (in current playback direction)",
81       cmd_step_forward},
82   {NULL},
83 };
84
85 static gchar *rtsp_address;
86
87 #define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
88 G_STMT_START { \
89   if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
90     GST_ERROR ("Could not create element %s", name); \
91     goto label; \
92   } \
93   if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
94     GST_ERROR ("Could not add element %s", name); \
95     goto label; \
96   } \
97 } G_STMT_END
98
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
104
105 static void
106 pad_added_cb (GstElement * src, GstPad * srcpad, GstElement * peer)
107 {
108   GstPad *sinkpad = gst_element_get_static_pad (peer, "sink");
109
110   gst_pad_link (srcpad, sinkpad);
111
112   gst_object_unref (sinkpad);
113 }
114
115 static gboolean
116 setup (Context * ctx)
117 {
118   GstElement *onvifparse, *queue, *vdepay, *vdec, *vconv, *toverlay, *tee,
119       *vqueue;
120   gboolean ret = FALSE;
121
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);
132
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);
137
138   g_object_set (toverlay, "datetime-format", "%a %d, %b %Y - %T", NULL);
139
140   g_signal_connect (ctx->src, "pad-added", G_CALLBACK (pad_added_cb), queue);
141
142   if (!gst_element_link_many (queue, onvifparse, vdepay, vdec, vconv, toverlay,
143           tee, vqueue, ctx->sink, NULL)) {
144     goto done;
145   }
146
147   g_object_set (ctx->src, "onvif-rate-control", FALSE, "is-live", FALSE, NULL);
148
149   if (!g_strcmp0 (ctx->seek_params->rate_control, "no")) {
150     g_object_set (ctx->sink, "sync", FALSE, NULL);
151   }
152
153   ret = TRUE;
154
155 done:
156   return ret;
157 }
158
159 static GstClockTime
160 get_current_position (Context * ctx, gboolean reverse)
161 {
162   GstSample *sample;
163   GstBuffer *buffer;
164   GstClockTime ret;
165
166   g_object_get (ctx->sink, "last-sample", &sample, NULL);
167
168   buffer = gst_sample_get_buffer (sample);
169
170   ret = GST_BUFFER_PTS (buffer);
171
172   if (reverse && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer)))
173     ret += GST_BUFFER_DURATION (buffer);
174
175   gst_sample_unref (sample);
176
177   return ret;
178 }
179
180 static GstEvent *
181 translate_seek_parameters (Context * ctx, SeekParameters * seek_params)
182 {
183   GstEvent *ret = NULL;
184   gchar *range_str = NULL;
185   GstRTSPTimeRange *rtsp_range;
186   GstSeekType start_type, stop_type;
187   GstClockTime start, stop;
188   gdouble rate;
189   GstSeekFlags flags;
190   gchar **split = NULL;
191   GstClockTime trickmode_interval = 0;
192
193   range_str = g_strdup_printf ("clock=%s", seek_params->range);
194
195   if (gst_rtsp_range_parse (range_str, &rtsp_range) != GST_RTSP_OK) {
196     GST_ERROR ("Failed to parse range %s", range_str);
197     goto done;
198   }
199
200   gst_rtsp_range_get_times (rtsp_range, &start, &stop);
201
202   if (start > stop) {
203     GST_ERROR ("Invalid range, start > stop: %s", seek_params->range);
204     goto done;
205   }
206
207   start_type = GST_SEEK_TYPE_SET;
208   stop_type = GST_SEEK_TYPE_SET;
209
210   if (!ctx->new_range) {
211     GstClockTime current_position =
212         get_current_position (ctx, seek_params->reverse);
213
214     if (seek_params->reverse) {
215       stop_type = GST_SEEK_TYPE_SET;
216       stop = current_position;
217     } else {
218       start_type = GST_SEEK_TYPE_SET;
219       start = current_position;
220     }
221   }
222
223   ctx->new_range = FALSE;
224
225   flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE;
226
227   split = g_strsplit (seek_params->frames, "/", 2);
228
229   if (!g_strcmp0 (split[0], "intra")) {
230     if (split[1]) {
231       guint64 interval;
232       gchar *end;
233
234       interval = g_ascii_strtoull (split[1], &end, 10);
235
236       if (!end || *end != '\0') {
237         GST_ERROR ("Unexpected interval value %s", split[1]);
238         goto done;
239       }
240
241       trickmode_interval = interval * GST_MSECOND;
242     }
243     flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
244   } else if (!g_strcmp0 (split[0], "predicted")) {
245     if (split[1]) {
246       GST_ERROR ("Predicted frames mode does not allow an interval (%s)",
247           seek_params->frames);
248       goto done;
249     }
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);
253     goto done;
254   }
255
256   if (seek_params->reverse) {
257     rate = -1.0 * seek_params->speed;
258   } else {
259     rate = 1.0 * seek_params->speed;
260   }
261
262   ret = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
263       start_type, start, stop_type, stop);
264
265   if (trickmode_interval)
266     gst_event_set_seek_trickmode_interval (ret, trickmode_interval);
267
268 done:
269   if (split)
270     g_strfreev (split);
271   g_free (range_str);
272   return ret;
273 }
274
275 static void prompt_on (Context * ctx);
276 static void prompt_off (Context * ctx);
277
278 static gboolean
279 cmd_help (Context * ctx, gchar * arg, gboolean * async)
280 {
281   gboolean ret = TRUE;
282   guint i;
283
284   *async = FALSE;
285
286   for (i = 0; commands[i].name; i++) {
287     g_print ("%s: %s\n", commands[i].name, commands[i].help);
288   }
289
290   return ret;
291 }
292
293 static gboolean
294 cmd_pause (Context * ctx, gchar * arg, gboolean * async)
295 {
296   gboolean ret;
297   GstStateChangeReturn state_ret;
298
299   g_print ("Pausing\n");
300
301   state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PAUSED);
302
303   *async = state_ret == GST_STATE_CHANGE_ASYNC;
304   ret = state_ret != GST_STATE_CHANGE_FAILURE;
305
306   return ret;
307 }
308
309 static gboolean
310 cmd_play (Context * ctx, gchar * arg, gboolean * async)
311 {
312   gboolean ret;
313   GstStateChangeReturn state_ret;
314
315   g_print ("Playing\n");
316
317   state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PLAYING);
318
319   *async = state_ret == GST_STATE_CHANGE_ASYNC;
320   ret = state_ret != GST_STATE_CHANGE_FAILURE;
321
322   return ret;
323 }
324
325 static gboolean
326 do_seek (Context * ctx)
327 {
328   gboolean ret = FALSE;
329   GstEvent *event;
330
331   if (!(event = translate_seek_parameters (ctx, ctx->seek_params))) {
332     GST_ERROR ("Failed to create seek event");
333     goto done;
334   }
335
336   if (ctx->seek_params->reverse)
337     g_object_set (ctx->src, "onvif-rate-control", FALSE, NULL);
338
339   if (ctx->reset_sync) {
340     g_object_set (ctx->sink, "sync", TRUE, NULL);
341     ctx->reset_sync = FALSE;
342   }
343
344   if (!gst_element_send_event (ctx->src, event)) {
345     GST_ERROR ("Failed to seek rtspsrc");
346     g_main_loop_quit (ctx->loop);
347     goto done;
348   }
349
350   ret = TRUE;
351
352 done:
353   return ret;
354 }
355
356 static gboolean
357 cmd_reverse (Context * ctx, gchar * arg, gboolean * async)
358 {
359   gboolean ret = TRUE;
360
361   g_print ("Reversing playback direction\n");
362
363   ctx->seek_params->reverse = !ctx->seek_params->reverse;
364
365   ret = do_seek (ctx);
366
367   *async = ret == TRUE;
368
369   return ret;
370 }
371
372 static gboolean
373 cmd_range (Context * ctx, gchar * arg, gboolean * async)
374 {
375   gboolean ret = TRUE;
376
377   g_print ("Switching to new range\n");
378
379   g_free (ctx->seek_params->range);
380   ctx->seek_params->range = g_strdup (arg);
381   ctx->new_range = TRUE;
382
383   ret = do_seek (ctx);
384
385   *async = ret == TRUE;
386
387   return ret;
388 }
389
390 static gboolean
391 cmd_speed (Context * ctx, gchar * arg, gboolean * async)
392 {
393   gboolean ret = FALSE;
394   gchar *endptr = NULL;
395   gdouble new_speed;
396
397   new_speed = g_ascii_strtod (arg, &endptr);
398
399   g_print ("Switching gears\n");
400
401   if (endptr == NULL || *endptr != '\0' || new_speed <= 0.0) {
402     GST_ERROR ("Invalid value for speed: %s", arg);
403     goto done;
404   }
405
406   ctx->seek_params->speed = new_speed;
407   ret = do_seek (ctx);
408
409 done:
410   *async = ret == TRUE;
411   return ret;
412 }
413
414 static gboolean
415 cmd_frames (Context * ctx, gchar * arg, gboolean * async)
416 {
417   gboolean ret = TRUE;
418
419   g_print ("Changing Frames trickmode\n");
420
421   g_free (ctx->seek_params->frames);
422   ctx->seek_params->frames = g_strdup (arg);
423   ret = do_seek (ctx);
424   *async = ret == TRUE;
425
426   return ret;
427 }
428
429 static gboolean
430 cmd_rate_control (Context * ctx, gchar * arg, gboolean * async)
431 {
432   gboolean ret = FALSE;
433
434   *async = FALSE;
435
436   if (!g_strcmp0 (arg, "no")) {
437     g_object_set (ctx->sink, "sync", FALSE, NULL);
438     ret = TRUE;
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;
443     ret = do_seek (ctx);
444     *async = TRUE;
445   } else {
446     GST_ERROR ("Invalid rate-control: %s", arg);
447     goto done;
448   }
449
450   ret = TRUE;
451
452 done:
453   return ret;
454 }
455
456 static gboolean
457 cmd_step_forward (Context * ctx, gchar * arg, gboolean * async)
458 {
459   gboolean ret = FALSE;
460   GstEvent *event;
461
462   event = gst_event_new_step (GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE);
463
464   g_print ("Stepping\n");
465
466   if (!gst_element_send_event (ctx->sink, event)) {
467     GST_ERROR ("Failed to step forward");
468     goto done;
469   }
470
471   ret = TRUE;
472
473 done:
474   *async = ret == TRUE;
475   return ret;
476 }
477
478 static void
479 handle_command (Context * ctx, gchar * cmd)
480 {
481   gchar **split;
482   guint i;
483   gboolean valid_command = FALSE;
484
485   split = g_strsplit (cmd, ":", 0);
486
487   cmd = g_strstrip (split[0]);
488
489   if (cmd == NULL || *cmd == '\0') {
490     g_print ("> ");
491     goto done;
492   }
493
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);
503       } else {
504         gboolean async = FALSE;
505
506         if (commands[i].func (ctx,
507                 commands[i].has_argument ? g_strstrip (split[1]) : NULL, &async)
508             && async)
509           prompt_off (ctx);
510         else
511           g_print ("> ");
512       }
513       break;
514     }
515   }
516
517   if (!valid_command) {
518     g_print ("Invalid command %s\n> ", cmd);
519   }
520
521 done:
522   g_strfreev (split);
523 }
524
525 static gboolean
526 io_callback (GIOChannel * io, GIOCondition condition, Context * ctx)
527 {
528   gboolean ret = TRUE;
529   gchar *str;
530   GError *error = NULL;
531
532   switch (condition) {
533     case G_IO_PRI:
534     case G_IO_IN:
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);
540           break;
541         case G_IO_STATUS_EOF:
542           g_print ("EOF received, bye\n");
543           g_main_loop_quit (ctx->loop);
544           break;
545         case G_IO_STATUS_AGAIN:
546           break;
547         case G_IO_STATUS_NORMAL:
548           handle_command (ctx, str);
549           g_free (str);
550           break;
551       }
552       break;
553     case G_IO_ERR:
554     case G_IO_HUP:
555       GST_ERROR ("Failed to read commands from stdin");
556       g_main_loop_quit (ctx->loop);
557       break;
558     case G_IO_OUT:
559     default:
560       break;
561   }
562
563   return ret;
564 }
565
566 #ifndef STDIN_FILENO
567 #ifdef G_OS_WIN32
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 */
573
574 static void
575 prompt_on (Context * ctx)
576 {
577   g_assert (!ctx->io);
578   ctx->io = g_io_channel_unix_new (STDIN_FILENO);
579   ctx->io_watch_id =
580       g_io_add_watch (ctx->io, G_IO_IN, (GIOFunc) io_callback, ctx);
581   g_print ("> ");
582 }
583
584 static void
585 prompt_off (Context * ctx)
586 {
587   g_assert (ctx->io);
588   g_source_remove (ctx->io_watch_id);
589   g_io_channel_unref (ctx->io);
590   ctx->io = NULL;
591 }
592
593 static gboolean
594 bus_message_cb (GstBus * bus, GstMessage * message, Context * ctx)
595 {
596   switch (GST_MESSAGE_TYPE (message)) {
597     case GST_MESSAGE_STATE_CHANGED:{
598       GstState olds, news, pendings;
599
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");
604       }
605       break;
606     }
607     case GST_MESSAGE_ERROR:{
608       GError *error = NULL;
609       gchar *debug;
610
611       gst_message_parse_error (message, &error, &debug);
612
613       gst_printerr ("Error: %s (%s)\n", error->message, debug);
614       g_clear_error (&error);
615       g_free (debug);
616       g_main_loop_quit (ctx->loop);
617       break;
618     }
619     case GST_MESSAGE_LATENCY:{
620       gst_bin_recalculate_latency (GST_BIN (ctx->pipe));
621       break;
622     }
623     case GST_MESSAGE_ASYNC_DONE:{
624       prompt_on (ctx);
625     }
626     default:
627       break;
628   }
629
630   return TRUE;
631 }
632
633 int
634 main (int argc, char **argv)
635 {
636   GOptionContext *optctx;
637   Context ctx = { 0, };
638   GstBus *bus;
639   gint ret = 1;
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", ""},
660     {NULL}
661   };
662
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);
670     return -1;
671   }
672   if (argc < 2) {
673     g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
674     return 1;
675   }
676   rtsp_address = argv[1];
677   g_option_context_free (optctx);
678
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);
683
684   if (seek_params.speed <= 0.0) {
685     GST_ERROR ("SPEED must be a positive number");
686     return 1;
687   }
688
689   ctx.seek_params = &seek_params;
690   ctx.new_range = TRUE;
691   ctx.reset_sync = FALSE;
692
693   ctx.pipe = gst_pipeline_new (NULL);
694   if (!setup (&ctx)) {
695     g_printerr ("Damn\n");
696     goto done;
697   }
698
699   g_print ("Type help for the list of available commands\n");
700
701   do_seek (&ctx);
702
703   ctx.loop = g_main_loop_new (NULL, FALSE);
704
705   bus = gst_pipeline_get_bus (GST_PIPELINE (ctx.pipe));
706   gst_bus_add_watch (bus, (GstBusFunc) bus_message_cb, &ctx);
707
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))
710     goto done;
711
712   g_main_loop_run (ctx.loop);
713
714   g_main_loop_unref (ctx.loop);
715
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);
720
721   ret = 0;
722
723 done:
724   g_free (seek_params.range);
725   g_free (seek_params.frames);
726   g_free (seek_params.rate_control);
727   g_free (default_speed);
728   return ret;
729 }