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