Merge branch 'master' into 0.11
[platform/upstream/gst-plugins-good.git] / gst / videocrop / gstaspectratiocrop.c
1 /* GStreamer video frame cropping to aspect-ratio
2  * Copyright (C) 2009 Thijs Vermeir <thijsvermeir@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-aspectratiocrop
22  * @see_also: #GstVideoCrop
23  *
24  * This element crops video frames to a specified #GstAspectRatioCrop:aspect-ratio.
25  *
26  * If the aspect-ratio is already correct, the element will operate
27  * in pass-through mode.
28  *
29  * <refsect2>
30  * <title>Example launch line</title>
31  * |[
32  * gst-launch -v videotestsrc ! video/x-raw-rgb,height=640,width=480 ! aspectratiocrop aspect-ratio=16/9 ! ximagesink
33  * ]| This pipeline generates a videostream in 4/3 and crops it to 16/9.
34  * </refsect2>
35  */
36
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40
41 #include <gst/gst.h>
42 #include <gst/video/video.h>
43
44 #include "gstaspectratiocrop.h"
45
46 GST_DEBUG_CATEGORY_STATIC (aspect_ratio_crop_debug);
47 #define GST_CAT_DEFAULT aspect_ratio_crop_debug
48
49 enum
50 {
51   ARG_0,
52   ARG_ASPECT_RATIO_CROP,
53 };
54
55 /* we support the same caps as videocrop */
56 #define ASPECT_RATIO_CROP_CAPS                        \
57   GST_VIDEO_CAPS_MAKE ("{ RGBx, xRGB, BGRx, xBGR, "    \
58       "RGBA, ARGB, BGRA, ABGR, RGB, BGR, AYUV, YUY2, " \
59       "YVYU, UYVY, Y800, I420, RGB16, RGB15, GRAY8 }")
60
61 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
62     GST_PAD_SRC,
63     GST_PAD_ALWAYS,
64     GST_STATIC_CAPS (ASPECT_RATIO_CROP_CAPS)
65     );
66
67 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
68     GST_PAD_SINK,
69     GST_PAD_ALWAYS,
70     GST_STATIC_CAPS (ASPECT_RATIO_CROP_CAPS)
71     );
72
73 #define gst_aspect_ratio_crop_parent_class parent_class
74 G_DEFINE_TYPE (GstAspectRatioCrop, gst_aspect_ratio_crop, GST_TYPE_BIN);
75
76 static void gst_aspect_ratio_crop_set_property (GObject * object, guint prop_id,
77     const GValue * value, GParamSpec * pspec);
78 static void gst_aspect_ratio_crop_get_property (GObject * object, guint prop_id,
79     GValue * value, GParamSpec * pspec);
80 static void gst_aspect_ratio_crop_set_cropping (GstAspectRatioCrop *
81     aspect_ratio_crop, gint top, gint right, gint bottom, gint left);
82 static GstCaps *gst_aspect_ratio_crop_get_caps (GstPad * pad, GstCaps * filter);
83 static gboolean gst_aspect_ratio_crop_src_query (GstPad * pad,
84     GstObject * parent, GstQuery * query);
85 static gboolean gst_aspect_ratio_crop_set_caps (GstAspectRatioCrop *
86     aspect_ratio_crop, GstCaps * caps);
87 static gboolean gst_aspect_ratio_crop_sink_event (GstPad * pad,
88     GstObject * parent, GstEvent * evt);
89 static void gst_aspect_ratio_crop_finalize (GObject * object);
90 static void gst_aspect_ratio_transform_structure (GstAspectRatioCrop *
91     aspect_ratio_crop, GstStructure * structure, GstStructure ** new_structure,
92     gboolean set_videocrop);
93
94 static void
95 gst_aspect_ratio_crop_set_cropping (GstAspectRatioCrop * aspect_ratio_crop,
96     gint top, gint right, gint bottom, gint left)
97 {
98   GValue value = { 0 };
99   if (G_UNLIKELY (!aspect_ratio_crop->videocrop)) {
100     GST_WARNING_OBJECT (aspect_ratio_crop,
101         "Can't set the settings if there is no cropping element");
102     return;
103   }
104
105   g_value_init (&value, G_TYPE_INT);
106   g_value_set_int (&value, top);
107   GST_DEBUG_OBJECT (aspect_ratio_crop, "set top cropping to: %d", top);
108   g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "top",
109       &value);
110   g_value_set_int (&value, right);
111   GST_DEBUG_OBJECT (aspect_ratio_crop, "set right cropping to: %d", right);
112   g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "right",
113       &value);
114   g_value_set_int (&value, bottom);
115   GST_DEBUG_OBJECT (aspect_ratio_crop, "set bottom cropping to: %d", bottom);
116   g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "bottom",
117       &value);
118   g_value_set_int (&value, left);
119   GST_DEBUG_OBJECT (aspect_ratio_crop, "set left cropping to: %d", left);
120   g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "left",
121       &value);
122
123   g_value_unset (&value);
124 }
125
126 static gboolean
127 gst_aspect_ratio_crop_set_caps (GstAspectRatioCrop * aspect_ratio_crop,
128     GstCaps * caps)
129 {
130   GstPad *peer_pad;
131   GstStructure *structure;
132   gboolean ret;
133
134   g_mutex_lock (aspect_ratio_crop->crop_lock);
135
136   structure = gst_caps_get_structure (caps, 0);
137   gst_aspect_ratio_transform_structure (aspect_ratio_crop, structure, NULL,
138       TRUE);
139   peer_pad =
140       gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop),
141       "sink");
142   ret = gst_pad_set_caps (peer_pad, caps);
143   gst_object_unref (peer_pad);
144   g_mutex_unlock (aspect_ratio_crop->crop_lock);
145   return ret;
146 }
147
148 static gboolean
149 gst_aspect_ratio_crop_sink_event (GstPad * pad, GstObject * parent,
150     GstEvent * evt)
151 {
152   gboolean ret;
153   GstAspectRatioCrop *aspect_ratio_crop = GST_ASPECT_RATIO_CROP (parent);
154
155   ret =
156       aspect_ratio_crop->sinkpad_old_eventfunc (pad, parent,
157       gst_event_ref (evt));
158
159   switch (GST_EVENT_TYPE (evt)) {
160     case GST_EVENT_CAPS:
161     {
162       GstCaps *caps;
163
164       gst_event_parse_caps (evt, &caps);
165       ret = gst_aspect_ratio_crop_set_caps (aspect_ratio_crop, caps);
166       gst_caps_unref (caps);
167       break;
168     }
169     default:
170       break;
171   }
172   gst_event_unref (evt);
173
174   return ret;
175 }
176
177 static void
178 gst_aspect_ratio_crop_class_init (GstAspectRatioCropClass * klass)
179 {
180   GObjectClass *gobject_class;
181   GstElementClass *element_class;
182
183   gobject_class = (GObjectClass *) klass;
184   element_class = (GstElementClass *) klass;
185
186   gobject_class->set_property = gst_aspect_ratio_crop_set_property;
187   gobject_class->get_property = gst_aspect_ratio_crop_get_property;
188   gobject_class->finalize = gst_aspect_ratio_crop_finalize;
189
190   g_object_class_install_property (gobject_class, ARG_ASPECT_RATIO_CROP,
191       gst_param_spec_fraction ("aspect-ratio", "aspect-ratio",
192           "Target aspect-ratio of video", 0, 1, G_MAXINT, 1, 0, 1,
193           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
194
195   gst_element_class_set_details_simple (element_class, "aspectratiocrop",
196       "Filter/Effect/Video",
197       "Crops video into a user-defined aspect-ratio",
198       "Thijs Vermeir <thijsvermeir@gmail.com>");
199
200   gst_element_class_add_pad_template (element_class,
201       gst_static_pad_template_get (&sink_template));
202   gst_element_class_add_pad_template (element_class,
203       gst_static_pad_template_get (&src_template));
204 }
205
206 static void
207 gst_aspect_ratio_crop_finalize (GObject * object)
208 {
209   GstAspectRatioCrop *aspect_ratio_crop;
210
211   aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object);
212
213   if (aspect_ratio_crop->crop_lock)
214     g_mutex_free (aspect_ratio_crop->crop_lock);
215
216   G_OBJECT_CLASS (parent_class)->finalize (object);
217 }
218
219 static void
220 gst_aspect_ratio_crop_init (GstAspectRatioCrop * aspect_ratio_crop)
221 {
222   GstPad *link_pad;
223   GstPad *src_pad;
224
225   GST_DEBUG_CATEGORY_INIT (aspect_ratio_crop_debug, "aspectratiocrop", 0,
226       "aspectratiocrop");
227
228   aspect_ratio_crop->ar_num = 0;
229   aspect_ratio_crop->ar_denom = 1;
230
231   aspect_ratio_crop->crop_lock = g_mutex_new ();
232
233   /* add the transform element */
234   aspect_ratio_crop->videocrop = gst_element_factory_make ("videocrop", NULL);
235   gst_bin_add (GST_BIN (aspect_ratio_crop), aspect_ratio_crop->videocrop);
236
237   /* create ghost pad src */
238   link_pad =
239       gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop),
240       "src");
241   src_pad = gst_ghost_pad_new ("src", link_pad);
242   gst_pad_set_query_function (src_pad,
243       GST_DEBUG_FUNCPTR (gst_aspect_ratio_crop_src_query));
244   gst_element_add_pad (GST_ELEMENT (aspect_ratio_crop), src_pad);
245   gst_object_unref (link_pad);
246   /* create ghost pad sink */
247   link_pad =
248       gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop),
249       "sink");
250   aspect_ratio_crop->sink = gst_ghost_pad_new ("sink", link_pad);
251   gst_element_add_pad (GST_ELEMENT (aspect_ratio_crop),
252       aspect_ratio_crop->sink);
253   gst_object_unref (link_pad);
254
255   aspect_ratio_crop->sinkpad_old_eventfunc =
256       GST_PAD_EVENTFUNC (aspect_ratio_crop->sink);
257   gst_pad_set_event_function (aspect_ratio_crop->sink,
258       GST_DEBUG_FUNCPTR (gst_aspect_ratio_crop_sink_event));
259 }
260
261 static void
262 gst_aspect_ratio_transform_structure (GstAspectRatioCrop * aspect_ratio_crop,
263     GstStructure * structure, GstStructure ** new_structure,
264     gboolean set_videocrop)
265 {
266   gdouble incoming_ar;
267   gdouble requested_ar;
268   gint width, height;
269   gint cropvalue;
270   gint par_d, par_n;
271
272   /* Check if we need to change the aspect ratio */
273   if (aspect_ratio_crop->ar_num < 1) {
274     GST_DEBUG_OBJECT (aspect_ratio_crop, "No cropping requested");
275     goto beach;
276   }
277
278   /* get the information from the caps */
279   if (!gst_structure_get_int (structure, "width", &width) ||
280       !gst_structure_get_int (structure, "height", &height))
281     goto beach;
282
283   if (!gst_structure_get_fraction (structure, "pixel-aspect-ratio",
284           &par_n, &par_d)) {
285     par_d = par_n = 1;
286   }
287
288   incoming_ar = ((gdouble) (width * par_n)) / (height * par_d);
289   GST_LOG_OBJECT (aspect_ratio_crop,
290       "incoming caps width(%d), height(%d), par (%d/%d) : ar = %f", width,
291       height, par_n, par_d, incoming_ar);
292
293   requested_ar =
294       (gdouble) aspect_ratio_crop->ar_num / aspect_ratio_crop->ar_denom;
295
296   /* check if the original aspect-ratio is the aspect-ratio that we want */
297   if (requested_ar == incoming_ar) {
298     GST_DEBUG_OBJECT (aspect_ratio_crop,
299         "Input video already has the correct aspect ratio (%.3f == %.3f)",
300         incoming_ar, requested_ar);
301     goto beach;
302   } else if (requested_ar > incoming_ar) {
303     /* fix aspect ratio with cropping on top and bottom */
304     cropvalue =
305         ((((double) aspect_ratio_crop->ar_denom /
306                 (double) (aspect_ratio_crop->ar_num)) * ((double) par_n /
307                 (double) par_d) * width) - height) / 2;
308     if (cropvalue < 0) {
309       cropvalue *= -1;
310     }
311     if (cropvalue >= (height / 2))
312       goto crop_failed;
313     if (set_videocrop) {
314       gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, cropvalue, 0,
315           cropvalue, 0);
316     }
317     if (new_structure) {
318       *new_structure = gst_structure_copy (structure);
319       gst_structure_set (*new_structure,
320           "height", G_TYPE_INT, (int) (height - (cropvalue * 2)), NULL);
321     }
322   } else {
323     /* fix aspect ratio with cropping on left and right */
324     cropvalue =
325         ((((double) aspect_ratio_crop->ar_num /
326                 (double) (aspect_ratio_crop->ar_denom)) * ((double) par_d /
327                 (double) par_n) * height) - width) / 2;
328     if (cropvalue < 0) {
329       cropvalue *= -1;
330     }
331     if (cropvalue >= (width / 2))
332       goto crop_failed;
333     if (set_videocrop) {
334       gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, 0, cropvalue,
335           0, cropvalue);
336     }
337     if (new_structure) {
338       *new_structure = gst_structure_copy (structure);
339       gst_structure_set (*new_structure,
340           "width", G_TYPE_INT, (int) (width - (cropvalue * 2)), NULL);
341     }
342   }
343
344   return;
345
346 crop_failed:
347   GST_WARNING_OBJECT (aspect_ratio_crop,
348       "can't crop to aspect ratio requested");
349   goto beach;
350 beach:
351   if (set_videocrop) {
352     gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, 0, 0, 0, 0);
353   }
354
355   if (new_structure) {
356     *new_structure = gst_structure_copy (structure);
357   }
358 }
359
360 static GstCaps *
361 gst_aspect_ratio_crop_transform_caps (GstAspectRatioCrop * aspect_ratio_crop,
362     GstCaps * caps)
363 {
364   GstCaps *transform;
365   gint size, i;
366
367   transform = gst_caps_new_empty ();
368
369   size = gst_caps_get_size (caps);
370
371   for (i = 0; i < size; i++) {
372     GstStructure *s;
373     GstStructure *trans_s;
374
375     s = gst_caps_get_structure (caps, i);
376
377     gst_aspect_ratio_transform_structure (aspect_ratio_crop, s, &trans_s,
378         FALSE);
379     gst_caps_append_structure (transform, trans_s);
380   }
381
382   return transform;
383 }
384
385 static GstCaps *
386 gst_aspect_ratio_crop_get_caps (GstPad * pad, GstCaps * filter)
387 {
388   GstPad *peer;
389   GstAspectRatioCrop *aspect_ratio_crop;
390   GstCaps *return_caps;
391
392   aspect_ratio_crop = GST_ASPECT_RATIO_CROP (gst_pad_get_parent (pad));
393
394   g_mutex_lock (aspect_ratio_crop->crop_lock);
395
396   peer = gst_pad_get_peer (aspect_ratio_crop->sink);
397   if (peer == NULL) {
398     return_caps = gst_static_pad_template_get_caps (&src_template);
399     gst_caps_ref (return_caps);
400   } else {
401     GstCaps *peer_caps;
402
403     peer_caps = gst_pad_query_caps (peer, filter);
404     return_caps =
405         gst_aspect_ratio_crop_transform_caps (aspect_ratio_crop, peer_caps);
406     gst_caps_unref (peer_caps);
407     gst_object_unref (peer);
408   }
409
410   g_mutex_unlock (aspect_ratio_crop->crop_lock);
411   gst_object_unref (aspect_ratio_crop);
412
413   if (return_caps && filter) {
414     GstCaps *tmp =
415         gst_caps_intersect_full (filter, return_caps, GST_CAPS_INTERSECT_FIRST);
416     gst_caps_replace (&return_caps, tmp);
417     gst_caps_unref (tmp);
418   }
419
420   return return_caps;
421 }
422
423 static gboolean
424 gst_aspect_ratio_crop_src_query (GstPad * pad, GstObject * parent,
425     GstQuery * query)
426 {
427   gboolean res = FALSE;
428
429   switch (GST_QUERY_TYPE (query)) {
430     case GST_QUERY_CAPS:
431     {
432       GstCaps *filter, *caps;
433
434       gst_query_parse_caps (query, &filter);
435       caps = gst_aspect_ratio_crop_get_caps (pad, filter);
436       gst_query_set_caps_result (query, caps);
437       gst_caps_unref (caps);
438       res = TRUE;
439       break;
440     }
441     default:
442       res = gst_pad_query_default (pad, parent, query);
443       break;
444   }
445   return res;
446 }
447
448 static void
449 gst_aspect_ratio_crop_set_property (GObject * object, guint prop_id,
450     const GValue * value, GParamSpec * pspec)
451 {
452   GstAspectRatioCrop *aspect_ratio_crop;
453   gboolean recheck = FALSE;
454
455   aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object);
456
457   GST_OBJECT_LOCK (aspect_ratio_crop);
458   switch (prop_id) {
459     case ARG_ASPECT_RATIO_CROP:
460       if (GST_VALUE_HOLDS_FRACTION (value)) {
461         aspect_ratio_crop->ar_num = gst_value_get_fraction_numerator (value);
462         aspect_ratio_crop->ar_denom =
463             gst_value_get_fraction_denominator (value);
464         recheck = gst_pad_has_current_caps (aspect_ratio_crop->sink);
465       }
466       break;
467     default:
468       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469       break;
470   }
471   GST_OBJECT_UNLOCK (aspect_ratio_crop);
472
473   if (recheck) {
474     GstCaps *caps = gst_pad_get_current_caps (aspect_ratio_crop->sink);
475     gst_aspect_ratio_crop_set_caps (aspect_ratio_crop, caps);
476     gst_caps_unref (caps);
477   }
478 }
479
480 static void
481 gst_aspect_ratio_crop_get_property (GObject * object, guint prop_id,
482     GValue * value, GParamSpec * pspec)
483 {
484   GstAspectRatioCrop *aspect_ratio_crop;
485
486   aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object);
487
488   GST_OBJECT_LOCK (aspect_ratio_crop);
489   switch (prop_id) {
490     case ARG_ASPECT_RATIO_CROP:
491       gst_value_set_fraction (value, aspect_ratio_crop->ar_num,
492           aspect_ratio_crop->ar_denom);
493       break;
494     default:
495       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
496       break;
497   }
498   GST_OBJECT_UNLOCK (aspect_ratio_crop);
499 }