-base: port elements to new video caps
[platform/upstream/gstreamer.git] / gst / playback / gstplaysinkvideoconvert.c
1 /* GStreamer
2  * Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "gstplaysinkvideoconvert.h"
25
26 #include <gst/pbutils/pbutils.h>
27 #include <gst/gst-i18n-plugin.h>
28
29 GST_DEBUG_CATEGORY_STATIC (gst_play_sink_video_convert_debug);
30 #define GST_CAT_DEFAULT gst_play_sink_video_convert_debug
31
32 #define parent_class gst_play_sink_video_convert_parent_class
33
34 G_DEFINE_TYPE (GstPlaySinkVideoConvert, gst_play_sink_video_convert,
35     GST_TYPE_BIN);
36
37 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
38     GST_PAD_SRC,
39     GST_PAD_ALWAYS,
40     GST_STATIC_CAPS_ANY);
41
42 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
43     GST_PAD_SINK,
44     GST_PAD_ALWAYS,
45     GST_STATIC_CAPS_ANY);
46
47 static gboolean
48 is_raw_caps (GstCaps * caps)
49 {
50   gint i, n;
51   GstStructure *s;
52   const gchar *name;
53
54   n = gst_caps_get_size (caps);
55   for (i = 0; i < n; i++) {
56     s = gst_caps_get_structure (caps, i);
57     name = gst_structure_get_name (s);
58     if (!g_str_has_prefix (name, "video/x-raw"))
59       return FALSE;
60   }
61
62   return TRUE;
63 }
64
65 static void
66 post_missing_element_message (GstPlaySinkVideoConvert * self,
67     const gchar * name)
68 {
69   GstMessage *msg;
70
71   msg = gst_missing_element_message_new (GST_ELEMENT_CAST (self), name);
72   gst_element_post_message (GST_ELEMENT_CAST (self), msg);
73 }
74
75 static GstProbeReturn
76 pad_blocked_cb (GstPad * pad, GstProbeType type, gpointer type_data,
77     gpointer user_data)
78 {
79   GstPlaySinkVideoConvert *self = user_data;
80   GstPad *peer;
81   GstCaps *caps;
82   gboolean raw;
83
84   GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
85   GST_DEBUG_OBJECT (self, "Pad blocked");
86
87   /* There must be a peer at this point */
88   peer = gst_pad_get_peer (self->sinkpad);
89   caps = gst_pad_get_negotiated_caps (peer);
90   if (!caps)
91     caps = gst_pad_get_caps (peer, NULL);
92   gst_object_unref (peer);
93
94   raw = is_raw_caps (caps);
95   GST_DEBUG_OBJECT (self, "Caps %" GST_PTR_FORMAT " are raw: %d", caps, raw);
96   gst_caps_unref (caps);
97
98   if (raw == self->raw)
99     goto unblock;
100   self->raw = raw;
101
102   if (raw) {
103     GstBin *bin = GST_BIN_CAST (self);
104     GstElement *head = NULL, *prev = NULL;
105     GstPad *pad;
106
107     GST_DEBUG_OBJECT (self, "Creating raw conversion pipeline");
108
109     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
110     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
111
112     self->conv = gst_element_factory_make ("videoconvert", "conv");
113     if (self->conv == NULL) {
114       post_missing_element_message (self, "videoconvert");
115       GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
116           (_("Missing element '%s' - check your GStreamer installation."),
117               "videoconvert"), ("video rendering might fail"));
118     } else {
119       gst_bin_add (bin, self->conv);
120       gst_element_sync_state_with_parent (self->conv);
121       prev = head = self->conv;
122     }
123
124     self->scale = gst_element_factory_make ("videoscale", "scale");
125     if (self->scale == NULL) {
126       post_missing_element_message (self, "videoscale");
127       GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
128           (_("Missing element '%s' - check your GStreamer installation."),
129               "videoscale"), ("possibly a liboil version mismatch?"));
130     } else {
131       /* Add black borders if necessary to keep the DAR */
132       g_object_set (self->scale, "add-borders", TRUE, NULL);
133       gst_bin_add (bin, self->scale);
134       gst_element_sync_state_with_parent (self->scale);
135       if (prev) {
136         if (!gst_element_link_pads_full (prev, "src", self->scale, "sink",
137                 GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
138           goto link_failed;
139       } else {
140         head = self->scale;
141       }
142       prev = self->scale;
143     }
144
145     if (head) {
146       pad = gst_element_get_static_pad (head, "sink");
147       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad);
148       gst_object_unref (pad);
149     }
150
151     if (prev) {
152       pad = gst_element_get_static_pad (prev, "src");
153       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad);
154       gst_object_unref (pad);
155     }
156
157     if (!head && !prev) {
158       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
159           self->sink_proxypad);
160     }
161
162     GST_DEBUG_OBJECT (self, "Raw conversion pipeline created");
163   } else {
164     GstBin *bin = GST_BIN_CAST (self);
165
166     GST_DEBUG_OBJECT (self, "Removing raw conversion pipeline");
167
168     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
169     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
170
171     if (self->conv) {
172       gst_element_set_state (self->conv, GST_STATE_NULL);
173       gst_bin_remove (bin, self->conv);
174       self->conv = NULL;
175     }
176     if (self->scale) {
177       gst_element_set_state (self->scale, GST_STATE_NULL);
178       gst_bin_remove (bin, self->scale);
179       self->scale = NULL;
180     }
181
182     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
183         self->sink_proxypad);
184
185     GST_DEBUG_OBJECT (self, "Raw conversion pipeline removed");
186   }
187
188 unblock:
189   self->sink_proxypad_block_id = 0;
190   GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
191
192   return GST_PROBE_REMOVE;
193
194 link_failed:
195   {
196     GST_ELEMENT_ERROR (self, CORE, PAD,
197         (NULL), ("Failed to configure the video converter."));
198     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
199         self->sink_proxypad);
200     self->sink_proxypad_block_id = 0;
201     GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
202
203     return GST_PROBE_REMOVE;
204   }
205 }
206
207 static void
208 block_proxypad (GstPlaySinkVideoConvert * self)
209 {
210   if (self->sink_proxypad_block_id == 0) {
211     self->sink_proxypad_block_id =
212         gst_pad_add_probe (self->sink_proxypad, GST_PROBE_TYPE_BLOCK,
213         pad_blocked_cb, gst_object_ref (self),
214         (GDestroyNotify) gst_object_unref);
215   }
216 }
217
218 static void
219 unblock_proxypad (GstPlaySinkVideoConvert * self)
220 {
221   if (self->sink_proxypad_block_id != 0) {
222     gst_pad_remove_probe (self->sink_proxypad, self->sink_proxypad_block_id);
223     self->sink_proxypad_block_id = 0;
224   }
225 }
226
227 static gboolean
228 gst_play_sink_video_convert_sink_setcaps (GstPlaySinkVideoConvert * self,
229     GstCaps * caps)
230 {
231   GstStructure *s;
232   const gchar *name;
233   gboolean reconfigure = FALSE;
234
235   GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
236   s = gst_caps_get_structure (caps, 0);
237   name = gst_structure_get_name (s);
238
239   if (g_str_has_prefix (name, "video/x-raw")) {
240     if (!self->raw && !gst_pad_is_blocked (self->sink_proxypad)) {
241       GST_DEBUG_OBJECT (self, "Changing caps from non-raw to raw");
242       reconfigure = TRUE;
243       block_proxypad (self);
244     }
245   } else {
246     if (self->raw && !gst_pad_is_blocked (self->sink_proxypad)) {
247       GST_DEBUG_OBJECT (self, "Changing caps from raw to non-raw");
248       reconfigure = TRUE;
249       block_proxypad (self);
250     }
251   }
252
253   /* Otherwise the setcaps below fails */
254   if (reconfigure) {
255     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
256     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
257   }
258   GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
259
260   GST_DEBUG_OBJECT (self, "Setting sink caps %" GST_PTR_FORMAT, caps);
261
262   return TRUE;
263 }
264
265 static gboolean
266 gst_play_sink_video_convert_sink_event (GstPad * pad, GstEvent * event)
267 {
268   GstPlaySinkVideoConvert *self =
269       GST_PLAY_SINK_VIDEO_CONVERT (gst_pad_get_parent (pad));
270   gboolean ret;
271
272   switch (GST_EVENT_TYPE (event)) {
273     case GST_EVENT_CAPS:
274     {
275       GstCaps *caps;
276
277       gst_event_parse_caps (event, &caps);
278       ret = gst_play_sink_video_convert_sink_setcaps (self, caps);
279       break;
280     }
281     default:
282       break;
283   }
284
285   ret = gst_proxy_pad_event_default (pad, gst_event_ref (event));
286
287   switch (GST_EVENT_TYPE (event)) {
288     case GST_EVENT_SEGMENT:
289       GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
290       GST_DEBUG_OBJECT (self, "Segment before %" GST_SEGMENT_FORMAT,
291           &self->segment);
292       gst_event_copy_segment (event, &self->segment);
293       GST_DEBUG_OBJECT (self, "Segment after %" GST_SEGMENT_FORMAT,
294           &self->segment);
295       GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
296       break;
297     case GST_EVENT_FLUSH_STOP:
298       GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
299       GST_DEBUG_OBJECT (self, "Resetting segment");
300       gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
301       GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
302       break;
303     default:
304       break;
305   }
306
307   gst_event_unref (event);
308   gst_object_unref (self);
309
310   return ret;
311 }
312
313 static GstCaps *
314 gst_play_sink_video_convert_getcaps (GstPad * pad, GstCaps * filter)
315 {
316   GstPlaySinkVideoConvert *self =
317       GST_PLAY_SINK_VIDEO_CONVERT (gst_pad_get_parent (pad));
318   GstCaps *ret;
319   GstPad *otherpad, *peer;
320
321   GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
322   if (pad == self->srcpad)
323     otherpad = gst_object_ref (self->sinkpad);
324   else
325     otherpad = gst_object_ref (self->srcpad);
326   GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
327
328   peer = gst_pad_get_peer (otherpad);
329   if (peer) {
330     ret = gst_pad_get_caps (peer, filter);
331     gst_object_unref (peer);
332   } else {
333     ret = (filter ? gst_caps_ref (filter) : gst_caps_new_any ());
334   }
335
336   gst_object_unref (otherpad);
337   gst_object_unref (self);
338
339   return ret;
340 }
341
342 static void
343 gst_play_sink_video_convert_finalize (GObject * object)
344 {
345   GstPlaySinkVideoConvert *self = GST_PLAY_SINK_VIDEO_CONVERT_CAST (object);
346
347   gst_object_unref (self->sink_proxypad);
348   g_mutex_free (self->lock);
349
350   G_OBJECT_CLASS (parent_class)->finalize (object);
351 }
352
353 static GstStateChangeReturn
354 gst_play_sink_video_convert_change_state (GstElement * element,
355     GstStateChange transition)
356 {
357   GstStateChangeReturn ret;
358   GstPlaySinkVideoConvert *self = GST_PLAY_SINK_VIDEO_CONVERT_CAST (element);
359
360   switch (transition) {
361     case GST_STATE_CHANGE_PAUSED_TO_READY:
362       GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
363       unblock_proxypad (self);
364       GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
365       break;
366     default:
367       break;
368   }
369
370   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
371   if (ret == GST_STATE_CHANGE_FAILURE)
372     return ret;
373
374   switch (transition) {
375     case GST_STATE_CHANGE_PAUSED_TO_READY:
376       GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
377       gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
378       if (self->conv) {
379         gst_element_set_state (self->conv, GST_STATE_NULL);
380         gst_bin_remove (GST_BIN_CAST (self), self->conv);
381         self->conv = NULL;
382       }
383       if (self->scale) {
384         gst_element_set_state (self->scale, GST_STATE_NULL);
385         gst_bin_remove (GST_BIN_CAST (self), self->scale);
386         self->scale = NULL;
387       }
388       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
389           self->sink_proxypad);
390       self->raw = FALSE;
391       GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
392       break;
393     case GST_STATE_CHANGE_READY_TO_PAUSED:
394       GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self);
395       if (!gst_pad_is_blocked (self->sink_proxypad))
396         block_proxypad (self);
397       GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self);
398     default:
399       break;
400   }
401
402   return ret;
403 }
404
405 static void
406 gst_play_sink_video_convert_class_init (GstPlaySinkVideoConvertClass * klass)
407 {
408   GObjectClass *gobject_class;
409   GstElementClass *gstelement_class;
410
411   GST_DEBUG_CATEGORY_INIT (gst_play_sink_video_convert_debug,
412       "playsinkvideoconvert", 0, "play bin");
413
414   gobject_class = (GObjectClass *) klass;
415   gstelement_class = (GstElementClass *) klass;
416
417   gobject_class->finalize = gst_play_sink_video_convert_finalize;
418
419   gst_element_class_add_pad_template (gstelement_class,
420       gst_static_pad_template_get (&srctemplate));
421   gst_element_class_add_pad_template (gstelement_class,
422       gst_static_pad_template_get (&sinktemplate));
423   gst_element_class_set_details_simple (gstelement_class,
424       "Player Sink Video Converter", "Video/Bin/Converter",
425       "Convenience bin for video conversion",
426       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
427
428   gstelement_class->change_state =
429       GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_change_state);
430 }
431
432 static void
433 gst_play_sink_video_convert_init (GstPlaySinkVideoConvert * self)
434 {
435   GstPadTemplate *templ;
436
437   self->lock = g_mutex_new ();
438   gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
439
440   templ = gst_static_pad_template_get (&sinktemplate);
441   self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
442   gst_pad_set_event_function (self->sinkpad,
443       GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_sink_event));
444   gst_pad_set_getcaps_function (self->sinkpad,
445       GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_getcaps));
446
447   self->sink_proxypad =
448       GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->sinkpad)));
449
450   gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad);
451   gst_object_unref (templ);
452
453   templ = gst_static_pad_template_get (&srctemplate);
454   self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ);
455   gst_pad_set_getcaps_function (self->srcpad,
456       GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_getcaps));
457   gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
458   gst_object_unref (templ);
459
460   gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
461       self->sink_proxypad);
462 }