• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (c) 2005 Edward Hervey <bilboed@bilboed.com>
3  * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
4  * Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-imagefreeze
24  * @title: imagefreeze
25  *
26  * The imagefreeze element generates a still frame video stream from
27  * the input. It duplicates the first frame with the framerate requested
28  * by downstream, allows seeking and answers queries.
29  *
30  * ## Example launch line
31  * |[
32  * gst-launch-1.0 -v filesrc location=some.png ! decodebin ! videoconvert ! imagefreeze ! autovideosink
33  * ]| This pipeline shows a still frame stream of a PNG file.
34  *
35  */
36 
37 /* This is based on the imagefreeze element from PiTiVi:
38  * http://git.gnome.org/browse/pitivi/tree/pitivi/elements/imagefreeze.py
39  */
40 
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44 
45 #include <gst/glib-compat-private.h>
46 
47 #include "gstimagefreeze.h"
48 
49 #define DEFAULT_NUM_BUFFERS     -1
50 #define DEFAULT_ALLOW_REPLACE   FALSE
51 #define DEFAULT_IS_LIVE         FALSE
52 
53 enum
54 {
55   PROP_0,
56   PROP_NUM_BUFFERS,
57   PROP_ALLOW_REPLACE,
58   PROP_IS_LIVE,
59 };
60 
61 static void gst_image_freeze_finalize (GObject * object);
62 
63 static void gst_image_freeze_reset (GstImageFreeze * self);
64 
65 static GstStateChangeReturn gst_image_freeze_change_state (GstElement * element,
66     GstStateChange transition);
67 static GstClock *gst_image_freeze_provide_clock (GstElement * element);
68 
69 static void gst_image_freeze_set_property (GObject * object, guint prop_id,
70     const GValue * value, GParamSpec * pspec);
71 static void gst_image_freeze_get_property (GObject * object, guint prop_id,
72     GValue * value, GParamSpec * pspec);
73 static GstFlowReturn gst_image_freeze_sink_chain (GstPad * pad,
74     GstObject * parent, GstBuffer * buffer);
75 static gboolean gst_image_freeze_sink_event (GstPad * pad, GstObject * parent,
76     GstEvent * event);
77 static gboolean gst_image_freeze_sink_setcaps (GstImageFreeze * self,
78     GstCaps * caps);
79 static GstCaps *gst_image_freeze_query_caps (GstImageFreeze * self,
80     GstPad * pad, GstCaps * filter);
81 static gboolean gst_image_freeze_sink_query (GstPad * pad, GstObject * parent,
82     GstQuery * query);
83 static void gst_image_freeze_src_loop (GstPad * pad);
84 static gboolean gst_image_freeze_src_event (GstPad * pad, GstObject * parent,
85     GstEvent * event);
86 static gboolean gst_image_freeze_src_query (GstPad * pad, GstObject * parent,
87     GstQuery * query);
88 
89 static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE ("sink",
90     GST_PAD_SINK,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS ("video/x-raw(ANY)"));
93 
94 static GstStaticPadTemplate src_pad_template =
95 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
96     GST_STATIC_CAPS ("video/x-raw(ANY)"));
97 
98 GST_DEBUG_CATEGORY_STATIC (gst_image_freeze_debug);
99 #define GST_CAT_DEFAULT gst_image_freeze_debug
100 
101 #define gst_image_freeze_parent_class parent_class
102 G_DEFINE_TYPE (GstImageFreeze, gst_image_freeze, GST_TYPE_ELEMENT);
103 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (imagefreeze, "imagefreeze",
104     GST_RANK_NONE, GST_TYPE_IMAGE_FREEZE,
105     GST_DEBUG_CATEGORY_INIT (gst_image_freeze_debug, "imagefreeze", 0,
106         "imagefreeze element");
107     );
108 
109 static void
gst_image_freeze_class_init(GstImageFreezeClass * klass)110 gst_image_freeze_class_init (GstImageFreezeClass * klass)
111 {
112   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
113   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
114 
115   gobject_class->finalize = gst_image_freeze_finalize;
116   gobject_class->set_property = gst_image_freeze_set_property;
117   gobject_class->get_property = gst_image_freeze_get_property;
118 
119   g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
120       g_param_spec_int ("num-buffers", "Number of buffers",
121           "Number of buffers to output before sending EOS (-1 = unlimited)",
122           -1, G_MAXINT, DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE |
123           G_PARAM_STATIC_STRINGS));
124 
125   g_object_class_install_property (gobject_class, PROP_ALLOW_REPLACE,
126       g_param_spec_boolean ("allow-replace", "Allow Replace",
127           "Allow replacing the input buffer and always output the latest",
128           DEFAULT_ALLOW_REPLACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
129 
130   /**
131    * GstImageFreeze:is-live
132    *
133    * Selects whether the output stream should be a non-live stream based on
134    * the segment configured via a %GST_EVENT_SEEK, or whether the output
135    * stream should be a live stream with the negotiated framerate.
136    *
137    * Since: 1.18
138    */
139   g_object_class_install_property (gobject_class, PROP_IS_LIVE,
140       g_param_spec_boolean ("is-live", "Is Live",
141           "Whether to output a live video stream",
142           DEFAULT_IS_LIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
143 
144   gstelement_class->change_state =
145       GST_DEBUG_FUNCPTR (gst_image_freeze_change_state);
146   gstelement_class->provide_clock =
147       GST_DEBUG_FUNCPTR (gst_image_freeze_provide_clock);
148 
149   gst_element_class_set_static_metadata (gstelement_class,
150       "Still frame stream generator",
151       "Filter/Video",
152       "Generates a still frame stream from an image",
153       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
154 
155   gst_element_class_add_static_pad_template (gstelement_class,
156       &sink_pad_template);
157   gst_element_class_add_static_pad_template (gstelement_class,
158       &src_pad_template);
159 }
160 
161 static void
gst_image_freeze_init(GstImageFreeze * self)162 gst_image_freeze_init (GstImageFreeze * self)
163 {
164   self->sinkpad = gst_pad_new_from_static_template (&sink_pad_template, "sink");
165   gst_pad_set_chain_function (self->sinkpad,
166       GST_DEBUG_FUNCPTR (gst_image_freeze_sink_chain));
167   gst_pad_set_event_function (self->sinkpad,
168       GST_DEBUG_FUNCPTR (gst_image_freeze_sink_event));
169   gst_pad_set_query_function (self->sinkpad,
170       GST_DEBUG_FUNCPTR (gst_image_freeze_sink_query));
171   GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
172   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
173 
174   self->srcpad = gst_pad_new_from_static_template (&src_pad_template, "src");
175   gst_pad_set_event_function (self->srcpad,
176       GST_DEBUG_FUNCPTR (gst_image_freeze_src_event));
177   gst_pad_set_query_function (self->srcpad,
178       GST_DEBUG_FUNCPTR (gst_image_freeze_src_query));
179   gst_pad_use_fixed_caps (self->srcpad);
180   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
181 
182   g_mutex_init (&self->lock);
183   g_cond_init (&self->blocked_cond);
184 
185   self->num_buffers = DEFAULT_NUM_BUFFERS;
186   self->allow_replace = DEFAULT_ALLOW_REPLACE;
187   self->is_live = DEFAULT_IS_LIVE;
188 
189   gst_image_freeze_reset (self);
190 }
191 
192 static void
gst_image_freeze_finalize(GObject * object)193 gst_image_freeze_finalize (GObject * object)
194 {
195   GstImageFreeze *self = GST_IMAGE_FREEZE (object);
196 
197   self->num_buffers = DEFAULT_NUM_BUFFERS;
198 
199   gst_image_freeze_reset (self);
200 
201   g_mutex_clear (&self->lock);
202   g_cond_clear (&self->blocked_cond);
203 
204   G_OBJECT_CLASS (parent_class)->finalize (object);
205 }
206 
207 static void
gst_image_freeze_reset(GstImageFreeze * self)208 gst_image_freeze_reset (GstImageFreeze * self)
209 {
210   GST_DEBUG_OBJECT (self, "Resetting internal state");
211 
212   g_mutex_lock (&self->lock);
213   gst_buffer_replace (&self->buffer, NULL);
214   gst_caps_replace (&self->buffer_caps, NULL);
215   gst_caps_replace (&self->current_caps, NULL);
216   self->num_buffers_left = self->num_buffers;
217 
218   gst_segment_init (&self->segment, GST_FORMAT_TIME);
219   self->need_segment = TRUE;
220   self->flushing = TRUE;
221 
222   self->negotiated_framerate = FALSE;
223   self->fps_n = self->fps_d = 0;
224   self->offset = 0;
225   self->seqnum = 0;
226   g_mutex_unlock (&self->lock);
227 }
228 
229 static gboolean
gst_image_freeze_sink_setcaps(GstImageFreeze * self,GstCaps * caps)230 gst_image_freeze_sink_setcaps (GstImageFreeze * self, GstCaps * caps)
231 {
232   gboolean ret = FALSE;
233   GstStructure *s;
234   gint fps_n, fps_d;
235   GstCaps *othercaps, *intersection, *pad_current_caps;
236   guint i, n;
237   GstPad *pad;
238 
239   pad = self->sinkpad;
240 
241   caps = gst_caps_copy (caps);
242 
243   /* If we already negotiated a framerate then only update for the
244    * caps of the new buffer */
245   if (self->negotiated_framerate) {
246     gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, self->fps_n,
247         self->fps_d, NULL);
248     pad_current_caps = gst_pad_get_current_caps (self->srcpad);
249     if (pad_current_caps && !gst_caps_is_equal (caps, pad_current_caps)) {
250       GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, caps);
251       gst_pad_set_caps (self->srcpad, caps);
252     }
253     gst_caps_unref (pad_current_caps);
254     gst_caps_unref (caps);
255     return TRUE;
256   }
257 
258   /* Else negotiate a framerate with downstream */
259 
260   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
261 
262   s = gst_caps_get_structure (caps, 0);
263 
264   /* 1. Remove framerate */
265   gst_structure_remove_field (s, "framerate");
266   gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1,
267       NULL);
268 
269   /* 2. Intersect with template caps */
270   othercaps = (GstCaps *) gst_pad_get_pad_template_caps (pad);
271   intersection = gst_caps_intersect (caps, othercaps);
272   GST_DEBUG_OBJECT (pad, "Intersecting: %" GST_PTR_FORMAT, caps);
273   GST_DEBUG_OBJECT (pad, "with: %" GST_PTR_FORMAT, othercaps);
274   GST_DEBUG_OBJECT (pad, "gave: %" GST_PTR_FORMAT, intersection);
275   gst_caps_unref (caps);
276   gst_caps_unref (othercaps);
277   caps = intersection;
278   intersection = othercaps = NULL;
279 
280   /* 3. Intersect with downstream peer caps */
281   othercaps = gst_pad_peer_query_caps (self->srcpad, caps);
282   GST_DEBUG_OBJECT (pad, "Peer query resulted: %" GST_PTR_FORMAT, othercaps);
283   gst_caps_unref (caps);
284   caps = othercaps;
285   othercaps = NULL;
286 
287   /* 4. For every candidate try to use it downstream with framerate as
288    * near as possible to 25/1 */
289   n = gst_caps_get_size (caps);
290   for (i = 0; i < n; i++) {
291     GstCaps *candidate = gst_caps_new_empty ();
292     GstStructure *s = gst_structure_copy (gst_caps_get_structure (caps, i));
293     GstCapsFeatures *f =
294         gst_caps_features_copy (gst_caps_get_features (caps, i));
295 
296     gst_caps_append_structure_full (candidate, s, f);
297     if (gst_structure_has_field_typed (s, "framerate", GST_TYPE_FRACTION) ||
298         gst_structure_fixate_field_nearest_fraction (s, "framerate", 25, 1)) {
299       gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d);
300       if (fps_d != 0) {
301         gst_pad_set_caps (self->srcpad, candidate);
302         g_mutex_lock (&self->lock);
303         self->fps_n = fps_n;
304         self->fps_d = fps_d;
305         g_mutex_unlock (&self->lock);
306         self->negotiated_framerate = TRUE;
307         GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, candidate);
308         ret = TRUE;
309         gst_caps_unref (candidate);
310         break;
311       } else {
312         GST_WARNING_OBJECT (pad, "Invalid caps with framerate %d/%d", fps_n,
313             fps_d);
314       }
315     }
316     gst_caps_unref (candidate);
317   }
318 
319   if (!ret)
320     GST_ERROR_OBJECT (pad, "No usable caps found");
321 
322   gst_caps_unref (caps);
323 
324   return ret;
325 }
326 
327 /* remove framerate in writable @caps */
328 static void
gst_image_freeze_remove_fps(GstImageFreeze * self,GstCaps * caps)329 gst_image_freeze_remove_fps (GstImageFreeze * self, GstCaps * caps)
330 {
331   gint i, n;
332 
333   n = gst_caps_get_size (caps);
334   for (i = 0; i < n; i++) {
335     GstStructure *s = gst_caps_get_structure (caps, i);
336 
337     gst_structure_remove_field (s, "framerate");
338     gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT,
339         1, NULL);
340   }
341 }
342 
343 static GstCaps *
gst_image_freeze_query_caps(GstImageFreeze * self,GstPad * pad,GstCaps * filter)344 gst_image_freeze_query_caps (GstImageFreeze * self, GstPad * pad,
345     GstCaps * filter)
346 {
347   GstCaps *ret, *tmp, *templ;
348   GstPad *otherpad;
349 
350   otherpad = (pad == self->srcpad) ? self->sinkpad : self->srcpad;
351 
352   if (filter) {
353     filter = gst_caps_copy (filter);
354     gst_image_freeze_remove_fps (self, filter);
355   }
356   templ = gst_pad_get_pad_template_caps (pad);
357   tmp = gst_pad_peer_query_caps (otherpad, filter);
358   if (tmp) {
359     GST_LOG_OBJECT (otherpad, "peer caps %" GST_PTR_FORMAT, tmp);
360     ret = gst_caps_intersect (tmp, templ);
361     gst_caps_unref (tmp);
362   } else {
363     GST_LOG_OBJECT (otherpad, "going to copy");
364     ret = gst_caps_copy (templ);
365   }
366   if (templ)
367     gst_caps_unref (templ);
368   if (filter)
369     gst_caps_unref (filter);
370 
371   ret = gst_caps_make_writable (ret);
372   gst_image_freeze_remove_fps (self, ret);
373 
374   GST_LOG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
375 
376   return ret;
377 }
378 
379 static gboolean
gst_image_freeze_sink_query(GstPad * pad,GstObject * parent,GstQuery * query)380 gst_image_freeze_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
381 {
382   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
383   gboolean ret;
384 
385   GST_LOG_OBJECT (pad, "Handling query of type '%s'",
386       gst_query_type_get_name (GST_QUERY_TYPE (query)));
387 
388   switch (GST_QUERY_TYPE (query)) {
389     case GST_QUERY_CAPS:
390     {
391       GstCaps *caps;
392 
393       gst_query_parse_caps (query, &caps);
394       caps = gst_image_freeze_query_caps (self, pad, caps);
395       gst_query_set_caps_result (query, caps);
396       gst_caps_unref (caps);
397       ret = TRUE;
398       break;
399     }
400     default:
401       ret = gst_pad_query_default (pad, parent, query);
402   }
403 
404   return ret;
405 }
406 
407 static gboolean
gst_image_freeze_convert(GstImageFreeze * self,GstFormat src_format,gint64 src_value,GstFormat * dest_format,gint64 * dest_value)408 gst_image_freeze_convert (GstImageFreeze * self,
409     GstFormat src_format, gint64 src_value,
410     GstFormat * dest_format, gint64 * dest_value)
411 {
412   gboolean ret = FALSE;
413 
414   if (src_format == *dest_format) {
415     *dest_value = src_value;
416     return TRUE;
417   }
418 
419   if (src_value == -1) {
420     *dest_value = -1;
421     return TRUE;
422   }
423 
424   switch (src_format) {
425     case GST_FORMAT_DEFAULT:{
426       switch (*dest_format) {
427         case GST_FORMAT_TIME:
428           g_mutex_lock (&self->lock);
429           if (self->fps_n == 0)
430             *dest_value = -1;
431           else
432             *dest_value =
433                 gst_util_uint64_scale (src_value, GST_SECOND * self->fps_d,
434                 self->fps_n);
435           g_mutex_unlock (&self->lock);
436           ret = TRUE;
437           break;
438         default:
439           break;
440       }
441       break;
442     }
443     case GST_FORMAT_TIME:{
444       switch (*dest_format) {
445         case GST_FORMAT_DEFAULT:
446           g_mutex_lock (&self->lock);
447           *dest_value =
448               gst_util_uint64_scale (src_value, self->fps_n,
449               self->fps_d * GST_SECOND);
450           g_mutex_unlock (&self->lock);
451           ret = TRUE;
452           break;
453         default:
454           break;
455       }
456       break;
457     }
458     default:
459       break;
460   }
461 
462   return ret;
463 }
464 
465 static gboolean
gst_image_freeze_src_query(GstPad * pad,GstObject * parent,GstQuery * query)466 gst_image_freeze_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
467 {
468   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
469   gboolean ret = FALSE;
470 
471   GST_LOG_OBJECT (pad, "Handling query of type '%s'",
472       gst_query_type_get_name (GST_QUERY_TYPE (query)));
473 
474   switch (GST_QUERY_TYPE (query)) {
475     case GST_QUERY_CONVERT:{
476       GstFormat src_format, dest_format;
477       gint64 src_value, dest_value;
478 
479       gst_query_parse_convert (query, &src_format, &src_value, &dest_format,
480           &dest_value);
481       ret =
482           gst_image_freeze_convert (self, src_format, src_value, &dest_format,
483           &dest_value);
484       if (ret)
485         gst_query_set_convert (query, src_format, src_value, dest_format,
486             dest_value);
487       break;
488     }
489     case GST_QUERY_POSITION:{
490       GstFormat format;
491       gint64 position;
492 
493       gst_query_parse_position (query, &format, NULL);
494       switch (format) {
495         case GST_FORMAT_DEFAULT:{
496           g_mutex_lock (&self->lock);
497           position = self->offset;
498           g_mutex_unlock (&self->lock);
499           ret = TRUE;
500           break;
501         }
502         case GST_FORMAT_TIME:{
503           g_mutex_lock (&self->lock);
504           position = self->segment.position;
505           g_mutex_unlock (&self->lock);
506           ret = TRUE;
507           break;
508         }
509         default:
510           break;
511       }
512 
513       if (ret) {
514         gst_query_set_position (query, format, position);
515         GST_DEBUG_OBJECT (pad,
516             "Returning position %" G_GINT64_FORMAT " in format %s", position,
517             gst_format_get_name (format));
518       } else {
519         GST_DEBUG_OBJECT (pad, "Position query failed");
520       }
521       break;
522     }
523     case GST_QUERY_DURATION:{
524       GstFormat format;
525       gint64 duration;
526 
527       gst_query_parse_duration (query, &format, NULL);
528       switch (format) {
529         case GST_FORMAT_TIME:{
530           g_mutex_lock (&self->lock);
531           duration = self->segment.stop;
532           g_mutex_unlock (&self->lock);
533           ret = TRUE;
534           break;
535         }
536         case GST_FORMAT_DEFAULT:{
537           g_mutex_lock (&self->lock);
538           duration = self->segment.stop;
539           if (duration != -1)
540             duration =
541                 gst_util_uint64_scale (duration, self->fps_n,
542                 GST_SECOND * self->fps_d);
543           g_mutex_unlock (&self->lock);
544           ret = TRUE;
545           break;
546         }
547         default:
548           break;
549       }
550 
551       if (ret) {
552         gst_query_set_duration (query, format, duration);
553         GST_DEBUG_OBJECT (pad,
554             "Returning duration %" G_GINT64_FORMAT " in format %s", duration,
555             gst_format_get_name (format));
556       } else {
557         GST_DEBUG_OBJECT (pad, "Duration query failed");
558       }
559       break;
560     }
561     case GST_QUERY_SEEKING:{
562       GstFormat format;
563       gboolean seekable;
564 
565       gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
566       seekable = !self->is_live && (format == GST_FORMAT_TIME
567           || format == GST_FORMAT_DEFAULT);
568 
569       gst_query_set_seeking (query, format, seekable, (seekable ? 0 : -1), -1);
570       ret = TRUE;
571       break;
572     }
573     case GST_QUERY_LATENCY:
574       if (self->is_live) {
575         /* If we run live, we output the buffer without any latency but allow
576          * for at most one frame of latency. If downstream takes longer to
577          * consume out frame we would skip ahead */
578         if (self->fps_n > 0 && self->fps_d > 0)
579           gst_query_set_latency (query, TRUE, 0,
580               gst_util_uint64_scale_ceil (GST_SECOND, self->fps_d,
581                   self->fps_n));
582         else
583           gst_query_set_latency (query, TRUE, 0, GST_CLOCK_TIME_NONE);
584       } else {
585         /* If we don't run live, even if upstream is live, we never output any
586          * buffers with latency but immediately generate buffers as fast as we
587          * can according to the negotiated framerate */
588         gst_query_set_latency (query, FALSE, 0, GST_CLOCK_TIME_NONE);
589       }
590       ret = TRUE;
591       break;
592     case GST_QUERY_CAPS:
593     {
594       GstCaps *caps;
595       gst_query_parse_caps (query, &caps);
596       caps = gst_image_freeze_query_caps (self, pad, caps);
597       gst_query_set_caps_result (query, caps);
598       gst_caps_unref (caps);
599       ret = TRUE;
600       break;
601     }
602     default:
603       ret = gst_pad_query_default (pad, parent, query);
604       break;
605   }
606 
607   return ret;
608 }
609 
610 
611 static gboolean
gst_image_freeze_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)612 gst_image_freeze_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
613 {
614   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
615   gboolean ret;
616 
617   GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
618 
619   switch (GST_EVENT_TYPE (event)) {
620     case GST_EVENT_CAPS:
621     {
622       GstCaps *caps;
623 
624       g_mutex_lock (&self->lock);
625       gst_event_parse_caps (event, &caps);
626       gst_caps_replace (&self->current_caps, caps);
627       g_mutex_unlock (&self->lock);
628       gst_event_unref (event);
629       ret = TRUE;
630       break;
631     }
632     case GST_EVENT_EOS:
633       if (!self->buffer) {
634         /* if we receive EOS before a buffer arrives, then let it pass */
635         GST_DEBUG_OBJECT (self, "EOS without input buffer, passing on");
636         ret = gst_pad_push_event (self->srcpad, event);
637         break;
638       }
639       /* fall-through */
640     case GST_EVENT_SEGMENT:
641       GST_DEBUG_OBJECT (pad, "Dropping event");
642       gst_event_unref (event);
643       ret = TRUE;
644       break;
645     case GST_EVENT_FLUSH_START:
646       gst_image_freeze_reset (self);
647       /* fall through */
648     default:
649       ret = gst_pad_push_event (self->srcpad, gst_event_ref (event));
650       if (GST_EVENT_IS_STICKY (event))
651         ret = TRUE;
652       gst_event_unref (event);
653       break;
654   }
655 
656   return ret;
657 }
658 
659 static gboolean
gst_image_freeze_src_event(GstPad * pad,GstObject * parent,GstEvent * event)660 gst_image_freeze_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
661 {
662   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
663   gboolean ret;
664 
665   GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
666 
667   switch (GST_EVENT_TYPE (event)) {
668     case GST_EVENT_NAVIGATION:
669     case GST_EVENT_QOS:
670     case GST_EVENT_LATENCY:
671     case GST_EVENT_STEP:
672       GST_DEBUG_OBJECT (pad, "Dropping event");
673       gst_event_unref (event);
674       ret = TRUE;
675       break;
676     case GST_EVENT_SEEK:{
677       gdouble rate;
678       GstFormat format;
679       GstSeekFlags flags;
680       GstSeekType start_type, stop_type;
681       gint64 start, stop;
682       gint64 last_stop;
683       gboolean start_task;
684       gboolean flush;
685       guint32 seqnum;
686 
687       if (self->is_live) {
688         GST_ERROR_OBJECT (pad, "Can't seek in live mode");
689         ret = FALSE;
690         gst_event_unref (event);
691         break;
692       }
693 
694       seqnum = gst_event_get_seqnum (event);
695       gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
696           &stop_type, &stop);
697       gst_event_unref (event);
698 
699       flush = ! !(flags & GST_SEEK_FLAG_FLUSH);
700 
701       if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) {
702         GST_ERROR_OBJECT (pad, "Seek in invalid format: %s",
703             gst_format_get_name (format));
704         ret = FALSE;
705         break;
706       }
707 
708       if (format == GST_FORMAT_DEFAULT) {
709         format = GST_FORMAT_TIME;
710         if (!gst_image_freeze_convert (self, GST_FORMAT_DEFAULT, start, &format,
711                 &start)
712             || !gst_image_freeze_convert (self, GST_FORMAT_DEFAULT, stop,
713                 &format, &stop)
714             || start == -1 || stop == -1) {
715           GST_ERROR_OBJECT (pad,
716               "Failed to convert seek from DEFAULT format into TIME format");
717           ret = FALSE;
718           break;
719         }
720       }
721 
722       if (flush) {
723         GstEvent *e;
724 
725         g_mutex_lock (&self->lock);
726         self->flushing = TRUE;
727         g_mutex_unlock (&self->lock);
728 
729         e = gst_event_new_flush_start ();
730         gst_event_set_seqnum (e, seqnum);
731         gst_pad_push_event (self->srcpad, e);
732       } else {
733         gst_pad_pause_task (self->srcpad);
734       }
735 
736       GST_PAD_STREAM_LOCK (self->srcpad);
737 
738       g_mutex_lock (&self->lock);
739 
740       gst_segment_do_seek (&self->segment, rate, format, flags, start_type,
741           start, stop_type, stop, NULL);
742       self->need_segment = TRUE;
743       last_stop = self->segment.position;
744 
745       start_task = self->buffer != NULL;
746       self->flushing = FALSE;
747       g_mutex_unlock (&self->lock);
748 
749       if (flush) {
750         GstEvent *e;
751 
752         e = gst_event_new_flush_stop (TRUE);
753         gst_event_set_seqnum (e, seqnum);
754         gst_pad_push_event (self->srcpad, e);
755       }
756 
757       if (flags & GST_SEEK_FLAG_SEGMENT) {
758         GstMessage *m;
759 
760         m = gst_message_new_segment_start (GST_OBJECT (self),
761             format, last_stop);
762         gst_element_post_message (GST_ELEMENT (self), m);
763       }
764 
765       self->seqnum = seqnum;
766       GST_PAD_STREAM_UNLOCK (self->srcpad);
767 
768       GST_DEBUG_OBJECT (pad, "Seek successful");
769 
770       if (start_task) {
771         g_mutex_lock (&self->lock);
772 
773         if (self->buffer != NULL)
774           gst_pad_start_task (self->srcpad,
775               (GstTaskFunction) gst_image_freeze_src_loop, self->srcpad, NULL);
776 
777         g_mutex_unlock (&self->lock);
778       }
779 
780       ret = TRUE;
781       break;
782     }
783     case GST_EVENT_FLUSH_START:
784       g_mutex_lock (&self->lock);
785       self->flushing = TRUE;
786       g_mutex_unlock (&self->lock);
787       ret = gst_pad_push_event (self->sinkpad, event);
788       break;
789     case GST_EVENT_FLUSH_STOP:
790       gst_image_freeze_reset (self);
791       g_mutex_lock (&self->lock);
792       self->flushing = FALSE;
793       g_mutex_unlock (&self->lock);
794       ret = gst_pad_push_event (self->sinkpad, event);
795       break;
796     default:
797       ret = gst_pad_push_event (self->sinkpad, event);
798       break;
799   }
800 
801   return ret;
802 }
803 
804 static void
gst_image_freeze_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)805 gst_image_freeze_set_property (GObject * object, guint prop_id,
806     const GValue * value, GParamSpec * pspec)
807 {
808   GstImageFreeze *self;
809 
810   self = GST_IMAGE_FREEZE (object);
811 
812   switch (prop_id) {
813     case PROP_NUM_BUFFERS:
814       self->num_buffers = g_value_get_int (value);
815       break;
816     case PROP_ALLOW_REPLACE:
817       self->allow_replace = g_value_get_boolean (value);
818       break;
819     case PROP_IS_LIVE:
820       self->is_live = g_value_get_boolean (value);
821       break;
822     default:
823       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
824       break;
825   }
826 }
827 
828 static void
gst_image_freeze_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)829 gst_image_freeze_get_property (GObject * object, guint prop_id, GValue * value,
830     GParamSpec * pspec)
831 {
832   GstImageFreeze *self;
833 
834   self = GST_IMAGE_FREEZE (object);
835 
836   switch (prop_id) {
837     case PROP_NUM_BUFFERS:
838       g_value_set_int (value, self->num_buffers);
839       break;
840     case PROP_ALLOW_REPLACE:
841       g_value_set_boolean (value, self->allow_replace);
842       break;
843     case PROP_IS_LIVE:
844       g_value_set_boolean (value, self->is_live);
845       break;
846     default:
847       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
848       break;
849   }
850 }
851 
852 static GstFlowReturn
gst_image_freeze_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)853 gst_image_freeze_sink_chain (GstPad * pad, GstObject * parent,
854     GstBuffer * buffer)
855 {
856   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
857   GstFlowReturn flow_ret;
858 
859   g_mutex_lock (&self->lock);
860   if (self->buffer && !self->allow_replace) {
861     GST_DEBUG_OBJECT (pad, "Already have a buffer, dropping");
862     gst_buffer_unref (buffer);
863     g_mutex_unlock (&self->lock);
864     return GST_FLOW_EOS;
865   }
866 
867   if (!self->current_caps) {
868     GST_ERROR_OBJECT (pad, "Not negotiated yet");
869     g_mutex_unlock (&self->lock);
870     return GST_FLOW_NOT_NEGOTIATED;
871   }
872 
873   gst_buffer_replace (&self->buffer, buffer);
874   if (!self->buffer_caps
875       || !gst_caps_is_equal (self->buffer_caps, self->current_caps))
876     gst_pad_mark_reconfigure (self->srcpad);
877   gst_caps_replace (&self->buffer_caps, self->current_caps);
878   gst_buffer_unref (buffer);
879 
880   gst_pad_start_task (self->srcpad, (GstTaskFunction) gst_image_freeze_src_loop,
881       self->srcpad, NULL);
882   flow_ret = self->allow_replace ? GST_FLOW_OK : GST_FLOW_EOS;
883   g_mutex_unlock (&self->lock);
884   return flow_ret;
885 }
886 
887 static void
gst_image_freeze_src_loop(GstPad * pad)888 gst_image_freeze_src_loop (GstPad * pad)
889 {
890   GstImageFreeze *self = GST_IMAGE_FREEZE (GST_PAD_PARENT (pad));
891   GstBuffer *buffer;
892   guint64 offset;
893   GstClockTime timestamp, timestamp_end;
894   guint64 cstart, cstop;
895   gboolean in_seg, eos;
896   GstFlowReturn flow_ret = GST_FLOW_OK;
897   gboolean first = FALSE;
898 
899   g_mutex_lock (&self->lock);
900   if (self->flushing) {
901     GST_DEBUG_OBJECT (pad, "Flushing");
902     flow_ret = GST_FLOW_FLUSHING;
903     g_mutex_unlock (&self->lock);
904     goto pause_task;
905   } else if (!self->buffer) {
906     GST_ERROR_OBJECT (pad, "Have no buffer yet");
907     flow_ret = GST_FLOW_ERROR;
908     g_mutex_unlock (&self->lock);
909     goto pause_task;
910   }
911 
912   g_assert (self->buffer);
913 
914   /* Take a new reference of the buffer here so we're guaranteed to have one
915    * in all the following code even if it disappears while we temporarily
916    * unlock the mutex */
917   buffer = gst_buffer_ref (self->buffer);
918 
919   if (gst_pad_check_reconfigure (self->srcpad)) {
920     GstCaps *buffer_caps = gst_caps_ref (self->buffer_caps);
921     g_mutex_unlock (&self->lock);
922     if (!gst_image_freeze_sink_setcaps (self, buffer_caps)) {
923       gst_caps_unref (buffer_caps);
924       gst_buffer_unref (buffer);
925       gst_pad_mark_reconfigure (self->srcpad);
926       flow_ret = GST_FLOW_NOT_NEGOTIATED;
927       goto pause_task;
928     }
929     gst_caps_unref (buffer_caps);
930     g_mutex_lock (&self->lock);
931   }
932 
933   /* normally we don't count buffers */
934   if (G_UNLIKELY (self->num_buffers_left >= 0)) {
935     GST_DEBUG_OBJECT (pad, "Buffers left %d", self->num_buffers_left);
936     if (self->num_buffers_left == 0) {
937       flow_ret = GST_FLOW_EOS;
938       gst_buffer_unref (buffer);
939       g_mutex_unlock (&self->lock);
940       goto pause_task;
941     } else {
942       self->num_buffers_left--;
943     }
944   }
945   buffer = gst_buffer_make_writable (buffer);
946   g_mutex_unlock (&self->lock);
947 
948   if (self->need_segment) {
949     GstEvent *e;
950 
951     GST_DEBUG_OBJECT (pad, "Pushing SEGMENT event: %" GST_SEGMENT_FORMAT,
952         &self->segment);
953     e = gst_event_new_segment (&self->segment);
954 
955     if (self->seqnum)
956       gst_event_set_seqnum (e, self->seqnum);
957 
958     g_mutex_lock (&self->lock);
959     if (self->segment.rate >= 0) {
960       self->offset =
961           gst_util_uint64_scale (self->segment.start, self->fps_n,
962           self->fps_d * GST_SECOND);
963     } else {
964       self->offset =
965           gst_util_uint64_scale (self->segment.stop, self->fps_n,
966           self->fps_d * GST_SECOND);
967     }
968     g_mutex_unlock (&self->lock);
969 
970     self->need_segment = FALSE;
971     first = TRUE;
972 
973     gst_pad_push_event (self->srcpad, e);
974   }
975 
976   g_mutex_lock (&self->lock);
977   offset = self->offset;
978   if (self->is_live) {
979     GstClockTime base_time, clock_time;
980     GstClockTimeDiff jitter;
981     GstClockReturn clock_ret;
982     GstClock *clock;
983 
984     clock = gst_element_get_clock (GST_ELEMENT (self));
985 
986     /* Wait until the element went to PLAYING or flushing */
987     while ((!clock || self->blocked) && !self->flushing) {
988       g_cond_wait (&self->blocked_cond, &self->lock);
989       gst_clear_object (&clock);
990       clock = gst_element_get_clock (GST_ELEMENT (self));
991     }
992 
993     if (self->flushing) {
994       g_mutex_unlock (&self->lock);
995       gst_buffer_unref (buffer);
996       flow_ret = GST_FLOW_FLUSHING;
997       gst_clear_object (&clock);
998       goto pause_task;
999     }
1000 
1001     /* Wait on the clock until the time for our current frame is reached */
1002     base_time = gst_element_get_base_time (GST_ELEMENT (self));
1003     if (self->fps_n != 0) {
1004       clock_time =
1005           base_time + gst_util_uint64_scale (offset, self->fps_d * GST_SECOND,
1006           self->fps_n);
1007     } else {
1008       clock_time = base_time;
1009     }
1010 
1011     self->clock_id = gst_clock_new_single_shot_id (clock, clock_time);
1012     g_mutex_unlock (&self->lock);
1013     GST_TRACE_OBJECT (self,
1014         "Waiting for %" GST_TIME_FORMAT ", now %" GST_TIME_FORMAT,
1015         GST_TIME_ARGS (clock_time), GST_TIME_ARGS (gst_clock_get_time (clock)));
1016     clock_ret = gst_clock_id_wait (self->clock_id, &jitter);
1017     GST_TRACE_OBJECT (self,
1018         "Waited for %" GST_TIME_FORMAT ", clock ret %d, jitter %"
1019         GST_STIME_FORMAT, GST_TIME_ARGS (clock_time), clock_ret,
1020         GST_STIME_ARGS (jitter));
1021     g_mutex_lock (&self->lock);
1022     gst_clock_id_unref (self->clock_id);
1023     self->clock_id = NULL;
1024     gst_object_unref (clock);
1025 
1026     if (self->flushing || clock_ret == GST_CLOCK_UNSCHEDULED) {
1027       g_mutex_unlock (&self->lock);
1028       gst_buffer_unref (buffer);
1029       flow_ret = GST_FLOW_FLUSHING;
1030       goto pause_task;
1031     }
1032 
1033     /* If we were late, adjust our offset and jump ahead if needed */
1034     if (self->fps_n != 0) {
1035       if (jitter > 0) {
1036         guint64 new_offset =
1037             gst_util_uint64_scale (clock_time + jitter - base_time, self->fps_n,
1038             self->fps_d * GST_SECOND);
1039 
1040         if (new_offset != offset) {
1041           GST_INFO_OBJECT (self,
1042               "Late by %" GST_TIME_FORMAT ", old offset %" G_GUINT64_FORMAT
1043               ", new offset %" G_GUINT64_FORMAT, GST_TIME_ARGS (jitter), offset,
1044               new_offset);
1045           self->offset = offset = new_offset;
1046         }
1047       }
1048 
1049       timestamp =
1050           gst_util_uint64_scale (offset, self->fps_d * GST_SECOND, self->fps_n);
1051       timestamp_end =
1052           gst_util_uint64_scale (offset + 1, self->fps_d * GST_SECOND,
1053           self->fps_n);
1054     } else {
1055       /* If we have no framerate then we output a single frame now */
1056       if (jitter > 0)
1057         timestamp = jitter;
1058       else
1059         timestamp = 0;
1060 
1061       timestamp_end = GST_CLOCK_TIME_NONE;
1062     }
1063   } else {
1064     if (self->fps_n != 0) {
1065       timestamp =
1066           gst_util_uint64_scale (offset, self->fps_d * GST_SECOND, self->fps_n);
1067       timestamp_end =
1068           gst_util_uint64_scale (offset + 1, self->fps_d * GST_SECOND,
1069           self->fps_n);
1070     } else {
1071       timestamp = self->segment.start;
1072       timestamp_end = GST_CLOCK_TIME_NONE;
1073     }
1074   }
1075 
1076   eos = (self->fps_n == 0 && offset > 0) ||
1077       (self->segment.rate >= 0 && self->segment.stop != -1
1078       && timestamp > self->segment.stop) || (self->segment.rate < 0
1079       && offset == 0) || (self->segment.rate < 0
1080       && self->segment.start != -1 && timestamp_end < self->segment.start);
1081 
1082   if (self->fps_n == 0 && offset > 0)
1083     in_seg = FALSE;
1084   else
1085     in_seg =
1086         gst_segment_clip (&self->segment, GST_FORMAT_TIME, timestamp,
1087         timestamp_end, &cstart, &cstop);
1088 
1089   if (in_seg) {
1090     self->segment.position = cstart;
1091     if (self->segment.rate >= 0)
1092       self->segment.position = cstop;
1093   }
1094 
1095   if (self->segment.rate >= 0)
1096     self->offset++;
1097   else
1098     self->offset--;
1099   g_mutex_unlock (&self->lock);
1100 
1101   GST_DEBUG_OBJECT (pad, "Handling buffer with timestamp %" GST_TIME_FORMAT,
1102       GST_TIME_ARGS (timestamp));
1103 
1104   if (in_seg) {
1105     GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE;
1106     GST_BUFFER_PTS (buffer) = cstart;
1107     GST_BUFFER_DURATION (buffer) = cstop - cstart;
1108     GST_BUFFER_OFFSET (buffer) = offset;
1109     GST_BUFFER_OFFSET_END (buffer) = offset + 1;
1110     if (first)
1111       GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
1112     else
1113       GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
1114     flow_ret = gst_pad_push (self->srcpad, buffer);
1115     GST_DEBUG_OBJECT (pad, "Pushing buffer resulted in %s",
1116         gst_flow_get_name (flow_ret));
1117     if (flow_ret != GST_FLOW_OK)
1118       goto pause_task;
1119   } else {
1120     gst_buffer_unref (buffer);
1121   }
1122 
1123   if (eos) {
1124     flow_ret = GST_FLOW_EOS;
1125     goto pause_task;
1126   }
1127 
1128   return;
1129 
1130 pause_task:
1131   {
1132     const gchar *reason = gst_flow_get_name (flow_ret);
1133 
1134     GST_LOG_OBJECT (self, "pausing task, reason %s", reason);
1135     gst_pad_pause_task (pad);
1136 
1137     if (flow_ret == GST_FLOW_EOS) {
1138       if ((self->segment.flags & GST_SEEK_FLAG_SEGMENT)) {
1139         GstMessage *m;
1140         GstEvent *e;
1141 
1142         GST_DEBUG_OBJECT (pad, "Sending segment done at end of segment");
1143         if (self->segment.rate >= 0) {
1144           m = gst_message_new_segment_done (GST_OBJECT_CAST (self),
1145               GST_FORMAT_TIME, self->segment.stop);
1146           e = gst_event_new_segment_done (GST_FORMAT_TIME, self->segment.stop);
1147         } else {
1148           m = gst_message_new_segment_done (GST_OBJECT_CAST (self),
1149               GST_FORMAT_TIME, self->segment.start);
1150           e = gst_event_new_segment_done (GST_FORMAT_TIME, self->segment.start);
1151         }
1152         gst_element_post_message (GST_ELEMENT_CAST (self), m);
1153         gst_pad_push_event (self->srcpad, e);
1154       } else {
1155         GstEvent *e = gst_event_new_eos ();
1156 
1157         GST_DEBUG_OBJECT (pad, "Sending EOS at end of segment");
1158 
1159         if (self->seqnum)
1160           gst_event_set_seqnum (e, self->seqnum);
1161         gst_pad_push_event (self->srcpad, e);
1162       }
1163     } else if (flow_ret == GST_FLOW_NOT_LINKED || flow_ret < GST_FLOW_EOS) {
1164       GstEvent *e = gst_event_new_eos ();
1165 
1166       GST_ELEMENT_FLOW_ERROR (self, flow_ret);
1167 
1168       if (self->seqnum)
1169         gst_event_set_seqnum (e, self->seqnum);
1170 
1171       gst_pad_push_event (self->srcpad, e);
1172     }
1173     return;
1174   }
1175 }
1176 
1177 static GstStateChangeReturn
gst_image_freeze_change_state(GstElement * element,GstStateChange transition)1178 gst_image_freeze_change_state (GstElement * element, GstStateChange transition)
1179 {
1180   GstImageFreeze *self = GST_IMAGE_FREEZE (element);
1181   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1182   gboolean no_preroll = FALSE;
1183 
1184   switch (transition) {
1185     case GST_STATE_CHANGE_READY_TO_PAUSED:
1186       gst_image_freeze_reset (self);
1187       g_mutex_lock (&self->lock);
1188       self->flushing = FALSE;
1189       self->blocked = TRUE;
1190       g_mutex_unlock (&self->lock);
1191       if (self->is_live)
1192         no_preroll = TRUE;
1193       break;
1194     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1195       g_mutex_lock (&self->lock);
1196       self->blocked = FALSE;
1197       g_cond_signal (&self->blocked_cond);
1198       g_mutex_unlock (&self->lock);
1199       break;
1200     case GST_STATE_CHANGE_PAUSED_TO_READY:
1201       g_mutex_lock (&self->lock);
1202       self->flushing = TRUE;
1203       if (self->clock_id) {
1204         GST_DEBUG_OBJECT (self, "unlock clock wait");
1205         gst_clock_id_unschedule (self->clock_id);
1206       }
1207       self->blocked = FALSE;
1208       g_cond_signal (&self->blocked_cond);
1209       g_mutex_unlock (&self->lock);
1210       gst_image_freeze_reset (self);
1211       gst_pad_stop_task (self->srcpad);
1212       break;
1213     default:
1214       break;
1215   }
1216 
1217   if (GST_ELEMENT_CLASS (parent_class)->change_state)
1218     ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1219 
1220   switch (transition) {
1221     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1222       g_mutex_lock (&self->lock);
1223       self->blocked = TRUE;
1224       g_mutex_unlock (&self->lock);
1225       if (self->is_live)
1226         no_preroll = TRUE;
1227       break;
1228     default:
1229       break;
1230   }
1231 
1232   if (no_preroll && ret == GST_STATE_CHANGE_SUCCESS)
1233     ret = GST_STATE_CHANGE_NO_PREROLL;
1234 
1235   return ret;
1236 }
1237 
1238 /* FIXME: GStreamer 2.0 */
1239 static GstClock *
gst_image_freeze_provide_clock(GstElement * element)1240 gst_image_freeze_provide_clock (GstElement * element)
1241 {
1242   return gst_system_clock_obtain ();
1243 }
1244 
1245 static gboolean
plugin_init(GstPlugin * plugin)1246 plugin_init (GstPlugin * plugin)
1247 {
1248   return GST_ELEMENT_REGISTER (imagefreeze, plugin);
1249 }
1250 
1251 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1252     GST_VERSION_MINOR,
1253     imagefreeze,
1254     "Still frame stream generator",
1255     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1256