37a383dcb1f8848e8b790d7bc1f248082015d564
[platform/upstream/gstreamer.git] / gst / smpte / gstsmpte.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
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 /**
21  * SECTION:element-smpte
22  *
23  * smpte can accept I420 video streams with the same width, height and
24  * framerate. The two incomming buffers are blended together using an effect
25  * specific alpha mask. 
26  *
27  * The #GstSmpte:depth property defines the presision in bits of the mask. A
28  * higher presision will create a mask with smoother gradients in order to avoid
29  * banding.
30  *
31  * <refsect2>
32  * <title>Sample pipelines</title>
33  * |[
34  * gst-launch-1.0 -v videotestsrc pattern=1 ! smpte name=s border=20000 type=234 duration=2000000000 ! videoconvert ! ximagesink videotestsrc ! s.
35  * ]| A pipeline to demonstrate the smpte transition.
36  * It shows a pinwheel transition a from a snow videotestsrc to an smpte
37  * pattern videotestsrc. The transition will take 2 seconds to complete. The
38  * edges of the transition are smoothed with a 20000 big border.
39  * </refsect2>
40  */
41
42 #ifdef HAVE_CONFIG_H
43 #include "config.h"
44 #endif
45 #include <string.h>
46 #include "gstsmpte.h"
47 #include "paint.h"
48
49 GST_DEBUG_CATEGORY_STATIC (gst_smpte_debug);
50 #define GST_CAT_DEFAULT gst_smpte_debug
51
52 static GstStaticPadTemplate gst_smpte_src_template =
53 GST_STATIC_PAD_TEMPLATE ("src",
54     GST_PAD_SRC,
55     GST_PAD_ALWAYS,
56     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")
57     )
58     );
59
60 static GstStaticPadTemplate gst_smpte_sink1_template =
61 GST_STATIC_PAD_TEMPLATE ("sink1",
62     GST_PAD_SINK,
63     GST_PAD_ALWAYS,
64     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")
65     )
66     );
67
68 static GstStaticPadTemplate gst_smpte_sink2_template =
69 GST_STATIC_PAD_TEMPLATE ("sink2",
70     GST_PAD_SINK,
71     GST_PAD_ALWAYS,
72     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")
73     )
74     );
75
76
77 /* SMPTE signals and args */
78 enum
79 {
80   /* FILL ME */
81   LAST_SIGNAL
82 };
83
84 #define DEFAULT_PROP_TYPE       1
85 #define DEFAULT_PROP_BORDER     0
86 #define DEFAULT_PROP_DEPTH      16
87 #define DEFAULT_PROP_DURATION   GST_SECOND
88 #define DEFAULT_PROP_INVERT   FALSE
89
90 enum
91 {
92   PROP_0,
93   PROP_TYPE,
94   PROP_BORDER,
95   PROP_DEPTH,
96   PROP_DURATION,
97   PROP_INVERT,
98   PROP_LAST,
99 };
100
101 /* FIXME: should use video meta etc. */
102 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
103 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
104 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
105
106 #define I420_Y_OFFSET(w,h) (0)
107 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
108 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
109
110 #define I420_SIZE(w,h)     (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
111
112
113 #define GST_TYPE_SMPTE_TRANSITION_TYPE (gst_smpte_transition_type_get_type())
114 static GType
115 gst_smpte_transition_type_get_type (void)
116 {
117   static GType smpte_transition_type = 0;
118   GEnumValue *smpte_transitions;
119
120   if (!smpte_transition_type) {
121     const GList *definitions;
122     gint i = 0;
123
124     definitions = gst_mask_get_definitions ();
125     smpte_transitions =
126         g_new0 (GEnumValue, g_list_length ((GList *) definitions) + 1);
127
128     while (definitions) {
129       GstMaskDefinition *definition = (GstMaskDefinition *) definitions->data;
130
131       definitions = g_list_next (definitions);
132
133       smpte_transitions[i].value = definition->type;
134       /* older GLib versions have the two fields as non-const, hence the cast */
135       smpte_transitions[i].value_nick = (gchar *) definition->short_name;
136       smpte_transitions[i].value_name = (gchar *) definition->long_name;
137
138       i++;
139     }
140
141     smpte_transition_type =
142         g_enum_register_static ("GstSMPTETransitionType", smpte_transitions);
143   }
144   return smpte_transition_type;
145 }
146
147
148 static void gst_smpte_finalize (GstSMPTE * smpte);
149
150 static GstFlowReturn gst_smpte_collected (GstCollectPads * pads,
151     GstSMPTE * smpte);
152
153 static void gst_smpte_set_property (GObject * object, guint prop_id,
154     const GValue * value, GParamSpec * pspec);
155 static void gst_smpte_get_property (GObject * object, guint prop_id,
156     GValue * value, GParamSpec * pspec);
157
158 static GstStateChangeReturn gst_smpte_change_state (GstElement * element,
159     GstStateChange transition);
160
161 /*static guint gst_smpte_signals[LAST_SIGNAL] = { 0 }; */
162
163 #define gst_smpte_parent_class parent_class
164 G_DEFINE_TYPE (GstSMPTE, gst_smpte, GST_TYPE_ELEMENT);
165
166 static void
167 gst_smpte_class_init (GstSMPTEClass * klass)
168 {
169   GObjectClass *gobject_class;
170   GstElementClass *gstelement_class;
171
172   gobject_class = (GObjectClass *) klass;
173   gstelement_class = (GstElementClass *) klass;
174
175   parent_class = g_type_class_peek_parent (klass);
176
177   gobject_class->set_property = gst_smpte_set_property;
178   gobject_class->get_property = gst_smpte_get_property;
179   gobject_class->finalize = (GObjectFinalizeFunc) gst_smpte_finalize;
180
181   _gst_mask_init ();
182
183   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TYPE,
184       g_param_spec_enum ("type", "Type", "The type of transition to use",
185           GST_TYPE_SMPTE_TRANSITION_TYPE, DEFAULT_PROP_TYPE,
186           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
187   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BORDER,
188       g_param_spec_int ("border", "Border",
189           "The border width of the transition", 0, G_MAXINT,
190           DEFAULT_PROP_BORDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
191   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DEPTH,
192       g_param_spec_int ("depth", "Depth", "Depth of the mask in bits", 1, 24,
193           DEFAULT_PROP_DEPTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
194   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DURATION,
195       g_param_spec_uint64 ("duration", "Duration",
196           "Duration of the transition effect in nanoseconds", 0, G_MAXUINT64,
197           DEFAULT_PROP_DURATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
198   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INVERT,
199       g_param_spec_boolean ("invert", "Invert",
200           "Invert transition mask", DEFAULT_PROP_INVERT,
201           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
202
203   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_smpte_change_state);
204
205   gst_element_class_add_pad_template (gstelement_class,
206       gst_static_pad_template_get (&gst_smpte_sink1_template));
207   gst_element_class_add_pad_template (gstelement_class,
208       gst_static_pad_template_get (&gst_smpte_sink2_template));
209   gst_element_class_add_pad_template (gstelement_class,
210       gst_static_pad_template_get (&gst_smpte_src_template));
211   gst_element_class_set_static_metadata (gstelement_class, "SMPTE transitions",
212       "Filter/Editor/Video",
213       "Apply the standard SMPTE transitions on video images",
214       "Wim Taymans <wim.taymans@chello.be>");
215 }
216
217 /*                              wht  yel  cya  grn  mag  red  blu  blk   -I    Q */
218 static const int y_colors[] = { 255, 226, 179, 150, 105, 76, 29, 16, 16, 0 };
219 static const int u_colors[] = { 128, 0, 170, 46, 212, 85, 255, 128, 0, 128 };
220 static const int v_colors[] = { 128, 155, 0, 21, 235, 255, 107, 128, 128, 255 };
221
222 static void
223 fill_i420 (guint8 * data, gint width, gint height, gint color)
224 {
225   gint size = I420_Y_ROWSTRIDE (width) * GST_ROUND_UP_2 (height);
226   gint size4 = size >> 2;
227   guint8 *yp = data;
228   guint8 *up = data + I420_U_OFFSET (width, height);
229   guint8 *vp = data + I420_V_OFFSET (width, height);
230
231   memset (yp, y_colors[color], size);
232   memset (up, u_colors[color], size4);
233   memset (vp, v_colors[color], size4);
234 }
235
236 static gboolean
237 gst_smpte_update_mask (GstSMPTE * smpte, gint type, gboolean invert,
238     gint depth, gint width, gint height)
239 {
240   GstMask *newmask;
241
242   if (smpte->mask) {
243     if (smpte->type == type &&
244         smpte->invert == invert &&
245         smpte->depth == depth &&
246         smpte->width == width && smpte->height == height)
247       return TRUE;
248   }
249
250   newmask = gst_mask_factory_new (type, invert, depth, width, height);
251   if (newmask) {
252     if (smpte->mask) {
253       gst_mask_destroy (smpte->mask);
254     }
255     smpte->mask = newmask;
256     smpte->type = type;
257     smpte->invert = invert;
258     smpte->depth = depth;
259     smpte->width = width;
260     smpte->height = height;
261
262     return TRUE;
263   }
264   return FALSE;
265 }
266
267 static gboolean
268 gst_smpte_setcaps (GstPad * pad, GstCaps * caps)
269 {
270   GstSMPTE *smpte;
271   gboolean ret;
272   GstVideoInfo vinfo;
273
274   smpte = GST_SMPTE (GST_PAD_PARENT (pad));
275
276   gst_video_info_init (&vinfo);
277   if (!gst_video_info_from_caps (&vinfo, caps))
278     return FALSE;
279
280   smpte->width = GST_VIDEO_INFO_WIDTH (&vinfo);
281   smpte->height = GST_VIDEO_INFO_HEIGHT (&vinfo);
282   smpte->fps_num = GST_VIDEO_INFO_FPS_N (&vinfo);
283   smpte->fps_denom = GST_VIDEO_INFO_FPS_D (&vinfo);
284
285   /* figure out the duration in frames */
286   smpte->end_position = gst_util_uint64_scale (smpte->duration,
287       smpte->fps_num, GST_SECOND * smpte->fps_denom);
288
289   GST_DEBUG_OBJECT (smpte, "duration: %d frames", smpte->end_position);
290
291   ret =
292       gst_smpte_update_mask (smpte, smpte->type, smpte->invert, smpte->depth,
293       smpte->width, smpte->height);
294
295   if (pad == smpte->sinkpad1) {
296     GST_DEBUG_OBJECT (smpte, "setting pad1 info");
297     smpte->vinfo1 = vinfo;
298   } else {
299     GST_DEBUG_OBJECT (smpte, "setting pad2 info");
300     smpte->vinfo2 = vinfo;
301   }
302
303   return ret;
304 }
305
306 static gboolean
307 gst_smpte_sink_event (GstCollectPads * pads,
308     GstCollectData * data, GstEvent * event, gpointer user_data)
309 {
310   GstPad *pad;
311   gboolean ret = FALSE;
312
313   pad = data->pad;
314
315   switch (GST_EVENT_TYPE (event)) {
316     case GST_EVENT_CAPS:
317     {
318       GstCaps *caps;
319
320       gst_event_parse_caps (event, &caps);
321       ret = gst_smpte_setcaps (pad, caps);
322       gst_event_unref (event);
323       event = NULL;
324       break;
325     }
326     default:
327       break;
328   }
329
330   if (event != NULL)
331     return gst_collect_pads_event_default (pads, data, event, FALSE);
332
333   return ret;
334 }
335
336 static void
337 gst_smpte_init (GstSMPTE * smpte)
338 {
339   smpte->sinkpad1 =
340       gst_pad_new_from_static_template (&gst_smpte_sink1_template, "sink1");
341   GST_PAD_SET_PROXY_CAPS (smpte->sinkpad1);
342   gst_element_add_pad (GST_ELEMENT (smpte), smpte->sinkpad1);
343
344   smpte->sinkpad2 =
345       gst_pad_new_from_static_template (&gst_smpte_sink2_template, "sink2");
346   GST_PAD_SET_PROXY_CAPS (smpte->sinkpad2);
347   gst_element_add_pad (GST_ELEMENT (smpte), smpte->sinkpad2);
348
349   smpte->srcpad =
350       gst_pad_new_from_static_template (&gst_smpte_src_template, "src");
351   gst_element_add_pad (GST_ELEMENT (smpte), smpte->srcpad);
352
353   smpte->collect = gst_collect_pads_new ();
354   gst_collect_pads_set_function (smpte->collect,
355       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_smpte_collected), smpte);
356   gst_collect_pads_set_event_function (smpte->collect,
357       GST_DEBUG_FUNCPTR (gst_smpte_sink_event), smpte);
358
359   gst_collect_pads_add_pad (smpte->collect, smpte->sinkpad1,
360       sizeof (GstCollectData), NULL, TRUE);
361   gst_collect_pads_add_pad (smpte->collect, smpte->sinkpad2,
362       sizeof (GstCollectData), NULL, TRUE);
363
364   smpte->type = DEFAULT_PROP_TYPE;
365   smpte->border = DEFAULT_PROP_BORDER;
366   smpte->depth = DEFAULT_PROP_DEPTH;
367   smpte->duration = DEFAULT_PROP_DURATION;
368   smpte->invert = DEFAULT_PROP_INVERT;
369   smpte->fps_num = 0;
370   smpte->fps_denom = 1;
371 }
372
373 static void
374 gst_smpte_finalize (GstSMPTE * smpte)
375 {
376   if (smpte->collect) {
377     gst_object_unref (smpte->collect);
378   }
379
380   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) smpte);
381 }
382
383 static void
384 gst_smpte_reset (GstSMPTE * smpte)
385 {
386   smpte->width = -1;
387   smpte->height = -1;
388   smpte->position = 0;
389   smpte->end_position = 0;
390   smpte->send_stream_start = TRUE;
391 }
392
393 static void
394 gst_smpte_blend_i420 (GstVideoFrame * frame1, GstVideoFrame * frame2,
395     GstVideoFrame * oframe, GstMask * mask, gint border, gint pos)
396 {
397   guint32 *maskp;
398   gint value;
399   gint i, j;
400   gint min, max;
401   guint8 *in1, *in2, *out, *in1u, *in1v, *in2u, *in2v, *outu, *outv;
402   gint width, height;
403
404   if (border == 0)
405     border++;
406
407   min = pos - border;
408   max = pos;
409
410   width = GST_VIDEO_FRAME_WIDTH (frame1);
411   height = GST_VIDEO_FRAME_HEIGHT (frame1);
412
413   in1 = GST_VIDEO_FRAME_COMP_DATA (frame1, 0);
414   in2 = GST_VIDEO_FRAME_COMP_DATA (frame2, 0);
415   out = GST_VIDEO_FRAME_COMP_DATA (oframe, 0);
416
417   in1u = GST_VIDEO_FRAME_COMP_DATA (frame1, 1);
418   in1v = GST_VIDEO_FRAME_COMP_DATA (frame1, 2);
419   in2u = GST_VIDEO_FRAME_COMP_DATA (frame2, 1);
420   in2v = GST_VIDEO_FRAME_COMP_DATA (frame2, 2);
421   outu = GST_VIDEO_FRAME_COMP_DATA (oframe, 1);
422   outv = GST_VIDEO_FRAME_COMP_DATA (oframe, 2);
423
424   maskp = mask->data;
425
426   for (i = 0; i < height; i++) {
427     for (j = 0; j < width; j++) {
428       value = *maskp++;
429       value = ((CLAMP (value, min, max) - min) << 8) / border;
430
431       out[j] = ((in1[j] * value) + (in2[j] * (256 - value))) >> 8;
432       if (!(i & 1) && !(j & 1)) {
433         outu[j / 2] =
434             ((in1u[j / 2] * value) + (in2u[j / 2] * (256 - value))) >> 8;
435         outv[j / 2] =
436             ((in1v[j / 2] * value) + (in2v[j / 2] * (256 - value))) >> 8;
437       }
438     }
439
440     in1 += GST_VIDEO_FRAME_COMP_STRIDE (frame1, 0);
441     in2 += GST_VIDEO_FRAME_COMP_STRIDE (frame2, 0);
442     out += GST_VIDEO_FRAME_COMP_STRIDE (oframe, 0);
443
444     if (!(i & 1)) {
445       in1u += GST_VIDEO_FRAME_COMP_STRIDE (frame1, 1);
446       in2u += GST_VIDEO_FRAME_COMP_STRIDE (frame2, 1);
447       in1v += GST_VIDEO_FRAME_COMP_STRIDE (frame1, 2);
448       in2v += GST_VIDEO_FRAME_COMP_STRIDE (frame1, 2);
449       outu += GST_VIDEO_FRAME_COMP_STRIDE (oframe, 1);
450       outv += GST_VIDEO_FRAME_COMP_STRIDE (oframe, 2);
451     }
452   }
453 }
454
455 static GstFlowReturn
456 gst_smpte_collected (GstCollectPads * pads, GstSMPTE * smpte)
457 {
458   GstBuffer *outbuf;
459   GstClockTime ts;
460   GstBuffer *in1 = NULL, *in2 = NULL;
461   GSList *collected;
462   GstMapInfo map;
463   GstVideoFrame frame1, frame2, oframe;
464
465   if (G_UNLIKELY (smpte->fps_num == 0))
466     goto not_negotiated;
467
468   if (!gst_pad_has_current_caps (smpte->sinkpad1) ||
469       !gst_pad_has_current_caps (smpte->sinkpad2))
470     goto not_negotiated;
471
472   if (smpte->send_stream_start) {
473     gchar s_id[32];
474
475     /* stream-start (FIXME: create id based on input ids) */
476     g_snprintf (s_id, sizeof (s_id), "smpte-%08x", g_random_int ());
477     gst_pad_push_event (smpte->srcpad, gst_event_new_stream_start (s_id));
478     smpte->send_stream_start = FALSE;
479   }
480
481   ts = gst_util_uint64_scale_int (smpte->position * GST_SECOND,
482       smpte->fps_denom, smpte->fps_num);
483
484   for (collected = pads->data; collected; collected = g_slist_next (collected)) {
485     GstCollectData *data;
486
487     data = (GstCollectData *) collected->data;
488
489     if (data->pad == smpte->sinkpad1)
490       in1 = gst_collect_pads_pop (pads, data);
491     else if (data->pad == smpte->sinkpad2)
492       in2 = gst_collect_pads_pop (pads, data);
493   }
494
495   if (in1 == NULL) {
496     /* if no input, make picture black */
497     in1 = gst_buffer_new_and_alloc (I420_SIZE (smpte->width, smpte->height));
498     gst_buffer_map (in1, &map, GST_MAP_WRITE);
499     fill_i420 (map.data, smpte->width, smpte->height, 7);
500     gst_buffer_unmap (in1, &map);
501   }
502   if (in2 == NULL) {
503     /* if no input, make picture white */
504     in2 = gst_buffer_new_and_alloc (I420_SIZE (smpte->width, smpte->height));
505     gst_buffer_map (in2, &map, GST_MAP_WRITE);
506     fill_i420 (map.data, smpte->width, smpte->height, 0);
507     gst_buffer_unmap (in1, &map);
508   }
509
510   if (GST_VIDEO_INFO_WIDTH (&smpte->vinfo1) !=
511       GST_VIDEO_INFO_WIDTH (&smpte->vinfo2) ||
512       GST_VIDEO_INFO_HEIGHT (&smpte->vinfo1) !=
513       GST_VIDEO_INFO_HEIGHT (&smpte->vinfo2))
514     goto input_formats_do_not_match;
515
516   if (smpte->position < smpte->end_position) {
517     outbuf = gst_buffer_new_and_alloc (I420_SIZE (smpte->width, smpte->height));
518
519     /* set caps if not done yet */
520     if (!gst_pad_has_current_caps (smpte->srcpad)) {
521       GstCaps *caps;
522       GstSegment segment;
523
524       caps =
525           gst_caps_make_writable (gst_static_caps_get
526           (&gst_smpte_src_template.static_caps));
527       gst_caps_set_simple (caps, "width", G_TYPE_INT, smpte->width, "height",
528           G_TYPE_INT, smpte->height, "framerate", GST_TYPE_FRACTION,
529           smpte->fps_num, smpte->fps_denom, NULL);
530
531       gst_pad_set_caps (smpte->srcpad, caps);
532       gst_caps_unref (caps);
533
534       gst_segment_init (&segment, GST_FORMAT_TIME);
535       gst_pad_push_event (smpte->srcpad, gst_event_new_segment (&segment));
536     }
537
538     gst_video_frame_map (&frame1, &smpte->vinfo1, in1, GST_MAP_READ);
539     gst_video_frame_map (&frame2, &smpte->vinfo2, in2, GST_MAP_READ);
540     /* re-use either info, now know they are essentially identical */
541     gst_video_frame_map (&oframe, &smpte->vinfo1, outbuf, GST_MAP_WRITE);
542     gst_smpte_blend_i420 (&frame1, &frame2, &oframe, smpte->mask, smpte->border,
543         ((1 << smpte->depth) + smpte->border) *
544         smpte->position / smpte->end_position);
545     gst_video_frame_unmap (&frame1);
546     gst_video_frame_unmap (&frame2);
547     gst_video_frame_unmap (&oframe);
548   } else {
549     outbuf = in2;
550     gst_buffer_ref (in2);
551   }
552
553   smpte->position++;
554
555   if (in1)
556     gst_buffer_unref (in1);
557   if (in2)
558     gst_buffer_unref (in2);
559
560   GST_BUFFER_TIMESTAMP (outbuf) = ts;
561
562   return gst_pad_push (smpte->srcpad, outbuf);
563
564   /* ERRORS */
565 not_negotiated:
566   {
567     GST_ELEMENT_ERROR (smpte, CORE, NEGOTIATION, (NULL),
568         ("No input format negotiated"));
569     return GST_FLOW_NOT_NEGOTIATED;
570   }
571 input_formats_do_not_match:
572   {
573     GstCaps *caps1, *caps2;
574
575     caps1 = gst_pad_get_current_caps (smpte->sinkpad1);
576     caps2 = gst_pad_get_current_caps (smpte->sinkpad2);
577     GST_ELEMENT_ERROR (smpte, CORE, NEGOTIATION, (NULL),
578         ("input formats don't match: %" GST_PTR_FORMAT " vs. %" GST_PTR_FORMAT,
579             caps1, caps2));
580     gst_caps_unref (caps1);
581     gst_caps_unref (caps2);
582     return GST_FLOW_ERROR;
583   }
584 }
585
586 static void
587 gst_smpte_set_property (GObject * object, guint prop_id,
588     const GValue * value, GParamSpec * pspec)
589 {
590   GstSMPTE *smpte;
591
592   smpte = GST_SMPTE (object);
593
594   switch (prop_id) {
595     case PROP_TYPE:
596       smpte->type = g_value_get_enum (value);
597       break;
598     case PROP_BORDER:
599       smpte->border = g_value_get_int (value);
600       break;
601     case PROP_DEPTH:
602       smpte->depth = g_value_get_int (value);
603       break;
604     case PROP_DURATION:
605       smpte->duration = g_value_get_uint64 (value);
606       break;
607     case PROP_INVERT:
608       smpte->invert = g_value_get_boolean (value);
609       break;
610     default:
611       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
612       break;
613   }
614 }
615
616 static void
617 gst_smpte_get_property (GObject * object, guint prop_id,
618     GValue * value, GParamSpec * pspec)
619 {
620   GstSMPTE *smpte;
621
622   smpte = GST_SMPTE (object);
623
624   switch (prop_id) {
625     case PROP_TYPE:
626       g_value_set_enum (value, smpte->type);
627       break;
628     case PROP_BORDER:
629       g_value_set_int (value, smpte->border);
630       break;
631     case PROP_DEPTH:
632       g_value_set_int (value, smpte->depth);
633       break;
634     case PROP_DURATION:
635       g_value_set_uint64 (value, smpte->duration);
636       break;
637     case PROP_INVERT:
638       g_value_set_boolean (value, smpte->invert);
639       break;
640     default:
641       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
642       break;
643   }
644 }
645
646 static GstStateChangeReturn
647 gst_smpte_change_state (GstElement * element, GstStateChange transition)
648 {
649   GstStateChangeReturn ret;
650   GstSMPTE *smpte;
651
652   smpte = GST_SMPTE (element);
653
654   switch (transition) {
655     case GST_STATE_CHANGE_READY_TO_PAUSED:
656       gst_smpte_reset (smpte);
657       GST_LOG_OBJECT (smpte, "starting collectpads");
658       gst_collect_pads_start (smpte->collect);
659       break;
660     case GST_STATE_CHANGE_PAUSED_TO_READY:
661       GST_LOG_OBJECT (smpte, "stopping collectpads");
662       gst_collect_pads_stop (smpte->collect);
663       break;
664     default:
665       break;
666   }
667
668   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
669
670   switch (transition) {
671     case GST_STATE_CHANGE_PAUSED_TO_READY:
672       gst_smpte_reset (smpte);
673       break;
674     default:
675       break;
676   }
677   return ret;
678 }
679
680 gboolean
681 gst_smpte_plugin_init (GstPlugin * plugin)
682 {
683   GST_DEBUG_CATEGORY_INIT (gst_smpte_debug, "smpte", 0,
684       "SMPTE transition effect");
685
686   return gst_element_register (plugin, "smpte", GST_RANK_NONE, GST_TYPE_SMPTE);
687 }