-base: port elements to new video caps
[platform/upstream/gstreamer.git] / gst / videoconvert / gstvideoconvert.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * This file:
4  * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
5  * Copyright (C) 2010 David Schleef <ds@schleef.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 /**
24  * SECTION:element-videoconvert
25  *
26  * Convert video frames between a great variety of video formats.
27  *
28  * <refsect2>
29  * <title>Example launch line</title>
30  * |[
31  * gst-launch -v videotestsrc ! video/x-raw,format=\(fourcc\)YUY2 ! videoconvert ! ximagesink
32  * ]|
33  * </refsect2>
34  */
35
36 #ifdef HAVE_CONFIG_H
37 #  include "config.h"
38 #endif
39
40 #include "gstvideoconvert.h"
41 #include <gst/video/video.h>
42
43 #include <string.h>
44
45 GST_DEBUG_CATEGORY (videoconvert_debug);
46 #define GST_CAT_DEFAULT videoconvert_debug
47 GST_DEBUG_CATEGORY (videoconvert_performance);
48
49 enum
50 {
51   PROP_0,
52   PROP_DITHER
53 };
54
55 #define CSP_VIDEO_CAPS GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)
56
57 static GstStaticPadTemplate gst_video_convert_src_template =
58 GST_STATIC_PAD_TEMPLATE ("src",
59     GST_PAD_SRC,
60     GST_PAD_ALWAYS,
61     GST_STATIC_CAPS (CSP_VIDEO_CAPS)
62     );
63
64 static GstStaticPadTemplate gst_video_convert_sink_template =
65 GST_STATIC_PAD_TEMPLATE ("sink",
66     GST_PAD_SINK,
67     GST_PAD_ALWAYS,
68     GST_STATIC_CAPS (CSP_VIDEO_CAPS)
69     );
70
71 GType gst_video_convert_get_type (void);
72
73 static void gst_video_convert_set_property (GObject * object,
74     guint property_id, const GValue * value, GParamSpec * pspec);
75 static void gst_video_convert_get_property (GObject * object,
76     guint property_id, GValue * value, GParamSpec * pspec);
77
78 static gboolean gst_video_convert_set_caps (GstBaseTransform * btrans,
79     GstCaps * incaps, GstCaps * outcaps);
80 static gboolean gst_video_convert_get_unit_size (GstBaseTransform * btrans,
81     GstCaps * caps, gsize * size);
82 static GstFlowReturn gst_video_convert_transform (GstBaseTransform * btrans,
83     GstBuffer * inbuf, GstBuffer * outbuf);
84
85 static GType
86 dither_method_get_type (void)
87 {
88   static GType gtype = 0;
89
90   if (gtype == 0) {
91     static const GEnumValue values[] = {
92       {DITHER_NONE, "No dithering (default)", "none"},
93       {DITHER_VERTERR, "Vertical error propogation", "verterr"},
94       {DITHER_HALFTONE, "Half-tone", "halftone"},
95       {0, NULL, NULL}
96     };
97
98     gtype = g_enum_register_static ("GstColorspaceDitherMethod", values);
99   }
100   return gtype;
101 }
102
103 /* copies the given caps */
104 static GstCaps *
105 gst_video_convert_caps_remove_format_info (GstCaps * caps)
106 {
107   GstStructure *st;
108   gint i, n;
109   GstCaps *res;
110
111   res = gst_caps_new_empty ();
112
113   n = gst_caps_get_size (caps);
114   for (i = 0; i < n; i++) {
115     st = gst_caps_get_structure (caps, i);
116
117     /* If this is already expressed by the existing caps
118      * skip this structure */
119     if (i > 0 && gst_caps_is_subset_structure (res, st))
120       continue;
121
122     st = gst_structure_copy (st);
123     gst_structure_remove_fields (st, "format", "palette_data",
124         "color-matrix", "chroma-site", NULL);
125
126     gst_caps_append_structure (res, st);
127   }
128
129   return res;
130 }
131
132 /* The caps can be transformed into any other caps with format info removed.
133  * However, we should prefer passthrough, so if passthrough is possible,
134  * put it first in the list. */
135 static GstCaps *
136 gst_video_convert_transform_caps (GstBaseTransform * btrans,
137     GstPadDirection direction, GstCaps * caps, GstCaps * filter)
138 {
139   GstCaps *template;
140   GstCaps *tmp, *tmp2;
141   GstCaps *result;
142
143   template = gst_static_pad_template_get_caps (&gst_video_convert_src_template);
144   result = gst_caps_copy (caps);
145
146   /* Get all possible caps that we can transform to */
147   tmp = gst_video_convert_caps_remove_format_info (caps);
148
149   if (filter) {
150     tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
151     gst_caps_unref (tmp);
152     tmp = tmp2;
153   }
154
155   result = tmp;
156
157   GST_DEBUG_OBJECT (btrans, "transformed %" GST_PTR_FORMAT " into %"
158       GST_PTR_FORMAT, caps, result);
159
160   return result;
161 }
162
163 static gboolean
164 gst_video_convert_set_caps (GstBaseTransform * btrans, GstCaps * incaps,
165     GstCaps * outcaps)
166 {
167   GstVideoConvert *space;
168   GstVideoFormat in_format;
169   GstVideoFormat out_format;
170   gint in_height, in_width;
171   gint out_height, out_width;
172   gint in_fps_n, in_fps_d, in_par_n, in_par_d;
173   gint out_fps_n, out_fps_d, out_par_n, out_par_d;
174   gboolean have_in_par, have_out_par;
175   gboolean have_in_interlaced, have_out_interlaced;
176   gboolean in_interlaced, out_interlaced;
177   gboolean ret;
178   ColorSpaceColorSpec in_spec, out_spec;
179
180   space = GST_VIDEO_CONVERT_CAST (btrans);
181
182   if (space->convert) {
183     videoconvert_convert_free (space->convert);
184   }
185
186   /* input caps */
187
188   ret = gst_video_format_parse_caps (incaps, &in_format, &in_width, &in_height);
189   if (!ret)
190     goto no_width_height;
191
192   ret = gst_video_parse_caps_framerate (incaps, &in_fps_n, &in_fps_d);
193   if (!ret)
194     goto no_framerate;
195
196   have_in_par = gst_video_parse_caps_pixel_aspect_ratio (incaps,
197       &in_par_n, &in_par_d);
198   have_in_interlaced = gst_video_format_parse_caps_interlaced (incaps,
199       &in_interlaced);
200
201   if (gst_video_format_is_rgb (in_format)) {
202     in_spec = COLOR_SPEC_RGB;
203   } else if (gst_video_format_is_yuv (in_format)) {
204     const gchar *matrix = gst_video_parse_caps_color_matrix (incaps);
205
206     if (matrix && g_str_equal (matrix, "hdtv"))
207       in_spec = COLOR_SPEC_YUV_BT709;
208     else
209       in_spec = COLOR_SPEC_YUV_BT470_6;
210   } else {
211     in_spec = COLOR_SPEC_GRAY;
212   }
213
214   /* output caps */
215
216   ret =
217       gst_video_format_parse_caps (outcaps, &out_format, &out_width,
218       &out_height);
219   if (!ret)
220     goto no_width_height;
221
222   ret = gst_video_parse_caps_framerate (outcaps, &out_fps_n, &out_fps_d);
223   if (!ret)
224     goto no_framerate;
225
226   have_out_par = gst_video_parse_caps_pixel_aspect_ratio (outcaps,
227       &out_par_n, &out_par_d);
228   have_out_interlaced = gst_video_format_parse_caps_interlaced (incaps,
229       &out_interlaced);
230
231   if (gst_video_format_is_rgb (out_format)) {
232     out_spec = COLOR_SPEC_RGB;
233   } else if (gst_video_format_is_yuv (out_format)) {
234     const gchar *matrix = gst_video_parse_caps_color_matrix (outcaps);
235
236     if (matrix && g_str_equal (matrix, "hdtv"))
237       out_spec = COLOR_SPEC_YUV_BT709;
238     else
239       out_spec = COLOR_SPEC_YUV_BT470_6;
240   } else {
241     out_spec = COLOR_SPEC_GRAY;
242   }
243
244   /* these must match */
245   if (in_width != out_width || in_height != out_height ||
246       in_fps_n != out_fps_n || in_fps_d != out_fps_d)
247     goto format_mismatch;
248
249   /* if present, these must match too */
250   if (have_in_par && have_out_par &&
251       (in_par_n != out_par_n || in_par_d != out_par_d))
252     goto format_mismatch;
253
254   /* if present, these must match too */
255   if (have_in_interlaced && have_out_interlaced &&
256       in_interlaced != out_interlaced)
257     goto format_mismatch;
258
259   space->from_format = in_format;
260   space->from_spec = in_spec;
261   space->to_format = out_format;
262   space->to_spec = out_spec;
263   space->width = in_width;
264   space->height = in_height;
265   space->interlaced = in_interlaced;
266
267   space->convert = videoconvert_convert_new (out_format, out_spec, in_format,
268       in_spec, in_width, in_height);
269   if (space->convert) {
270     videoconvert_convert_set_interlaced (space->convert, in_interlaced);
271   }
272   /* palette, only for from data */
273   if (space->from_format == GST_VIDEO_FORMAT_RGB8_PALETTED &&
274       space->to_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
275     goto format_mismatch;
276   } else if (space->from_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
277     GstBuffer *palette;
278     guint32 *data;
279
280     palette = gst_video_parse_caps_palette (incaps);
281
282     if (!palette || gst_buffer_get_size (palette) < 256 * 4) {
283       if (palette)
284         gst_buffer_unref (palette);
285       goto invalid_palette;
286     }
287
288     data = gst_buffer_map (palette, NULL, NULL, GST_MAP_READ);
289     videoconvert_convert_set_palette (space->convert, data);
290     gst_buffer_unmap (palette, data, -1);
291
292     gst_buffer_unref (palette);
293   } else if (space->to_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
294     const guint32 *palette;
295     GstBuffer *p_buf;
296
297     palette = videoconvert_convert_get_palette (space->convert);
298
299     p_buf = gst_buffer_new_and_alloc (256 * 4);
300     gst_buffer_fill (p_buf, 0, palette, 256 * 4);
301     gst_caps_set_simple (outcaps, "palette_data", GST_TYPE_BUFFER, p_buf, NULL);
302     gst_buffer_unref (p_buf);
303   }
304
305   GST_DEBUG ("reconfigured %d %d", space->from_format, space->to_format);
306
307   return TRUE;
308
309   /* ERRORS */
310 no_width_height:
311   {
312     GST_ERROR_OBJECT (space, "did not specify width or height");
313     space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
314     space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
315     return FALSE;
316   }
317 no_framerate:
318   {
319     GST_ERROR_OBJECT (space, "did not specify framerate");
320     space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
321     space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
322     return FALSE;
323   }
324 format_mismatch:
325   {
326     GST_ERROR_OBJECT (space, "input and output formats do not match");
327     space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
328     space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
329     return FALSE;
330   }
331 invalid_palette:
332   {
333     GST_ERROR_OBJECT (space, "invalid palette");
334     space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
335     space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
336     return FALSE;
337   }
338 }
339
340 #define gst_video_convert_parent_class parent_class
341 G_DEFINE_TYPE (GstVideoConvert, gst_video_convert, GST_TYPE_VIDEO_FILTER);
342
343 static void
344 gst_video_convert_finalize (GObject * obj)
345 {
346   GstVideoConvert *space = GST_VIDEO_CONVERT (obj);
347
348   if (space->convert) {
349     videoconvert_convert_free (space->convert);
350   }
351
352   G_OBJECT_CLASS (parent_class)->finalize (obj);
353 }
354
355 static void
356 gst_video_convert_class_init (GstVideoConvertClass * klass)
357 {
358   GObjectClass *gobject_class = (GObjectClass *) klass;
359   GstElementClass *gstelement_class = (GstElementClass *) klass;
360   GstBaseTransformClass *gstbasetransform_class =
361       (GstBaseTransformClass *) klass;
362
363   gobject_class->set_property = gst_video_convert_set_property;
364   gobject_class->get_property = gst_video_convert_get_property;
365   gobject_class->finalize = gst_video_convert_finalize;
366
367   gst_element_class_add_pad_template (gstelement_class,
368       gst_static_pad_template_get (&gst_video_convert_src_template));
369   gst_element_class_add_pad_template (gstelement_class,
370       gst_static_pad_template_get (&gst_video_convert_sink_template));
371
372   gst_element_class_set_details_simple (gstelement_class,
373       " Colorspace converter", "Filter/Converter/Video",
374       "Converts video from one colorspace to another",
375       "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
376
377   gstbasetransform_class->transform_caps =
378       GST_DEBUG_FUNCPTR (gst_video_convert_transform_caps);
379   gstbasetransform_class->set_caps =
380       GST_DEBUG_FUNCPTR (gst_video_convert_set_caps);
381   gstbasetransform_class->get_unit_size =
382       GST_DEBUG_FUNCPTR (gst_video_convert_get_unit_size);
383   gstbasetransform_class->transform =
384       GST_DEBUG_FUNCPTR (gst_video_convert_transform);
385
386   gstbasetransform_class->passthrough_on_same_caps = TRUE;
387
388   g_object_class_install_property (gobject_class, PROP_DITHER,
389       g_param_spec_enum ("dither", "Dither", "Apply dithering while converting",
390           dither_method_get_type (), DITHER_NONE,
391           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
392 }
393
394 static void
395 gst_video_convert_init (GstVideoConvert * space)
396 {
397   space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
398   space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
399 }
400
401 void
402 gst_video_convert_set_property (GObject * object, guint property_id,
403     const GValue * value, GParamSpec * pspec)
404 {
405   GstVideoConvert *csp;
406
407   csp = GST_VIDEO_CONVERT (object);
408
409   switch (property_id) {
410     case PROP_DITHER:
411       csp->dither = g_value_get_enum (value);
412       break;
413     default:
414       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
415       break;
416   }
417 }
418
419 void
420 gst_video_convert_get_property (GObject * object, guint property_id,
421     GValue * value, GParamSpec * pspec)
422 {
423   GstVideoConvert *csp;
424
425   csp = GST_VIDEO_CONVERT (object);
426
427   switch (property_id) {
428     case PROP_DITHER:
429       g_value_set_enum (value, csp->dither);
430       break;
431     default:
432       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
433       break;
434   }
435 }
436
437 static gboolean
438 gst_video_convert_get_unit_size (GstBaseTransform * btrans, GstCaps * caps,
439     gsize * size)
440 {
441   gboolean ret = TRUE;
442   GstVideoFormat format;
443   gint width, height;
444
445   g_assert (size);
446
447   ret = gst_video_format_parse_caps (caps, &format, &width, &height);
448   if (ret) {
449     *size = gst_video_format_get_size (format, width, height);
450   }
451
452   return ret;
453 }
454
455 static GstFlowReturn
456 gst_video_convert_transform (GstBaseTransform * btrans, GstBuffer * inbuf,
457     GstBuffer * outbuf)
458 {
459   GstVideoConvert *space;
460   guint8 *indata, *outdata;
461   gsize insize, outsize;
462
463   space = GST_VIDEO_CONVERT_CAST (btrans);
464
465   GST_DEBUG ("from %d -> to %d", space->from_format, space->to_format);
466
467   if (G_UNLIKELY (space->from_format == GST_VIDEO_FORMAT_UNKNOWN ||
468           space->to_format == GST_VIDEO_FORMAT_UNKNOWN))
469     goto unknown_format;
470
471   videoconvert_convert_set_dither (space->convert, space->dither);
472
473   indata = gst_buffer_map (inbuf, &insize, NULL, GST_MAP_READ);
474   outdata = gst_buffer_map (outbuf, &outsize, NULL, GST_MAP_WRITE);
475
476   videoconvert_convert_convert (space->convert, outdata, indata);
477
478   gst_buffer_unmap (outbuf, outdata, outsize);
479   gst_buffer_unmap (inbuf, indata, insize);
480
481   /* baseclass copies timestamps */
482   GST_DEBUG ("from %d -> to %d done", space->from_format, space->to_format);
483
484   return GST_FLOW_OK;
485
486   /* ERRORS */
487 unknown_format:
488   {
489     GST_ELEMENT_ERROR (space, CORE, NOT_IMPLEMENTED, (NULL),
490         ("attempting to convert colorspaces between unknown formats"));
491     return GST_FLOW_NOT_NEGOTIATED;
492   }
493 #if 0
494 not_supported:
495   {
496     GST_ELEMENT_ERROR (space, CORE, NOT_IMPLEMENTED, (NULL),
497         ("cannot convert between formats"));
498     return GST_FLOW_NOT_SUPPORTED;
499   }
500 #endif
501 }
502
503 static gboolean
504 plugin_init (GstPlugin * plugin)
505 {
506   GST_DEBUG_CATEGORY_INIT (videoconvert_debug, "videoconvert", 0,
507       "Colorspace Converter");
508   GST_DEBUG_CATEGORY_GET (videoconvert_performance, "GST_PERFORMANCE");
509
510   return gst_element_register (plugin, "videoconvert",
511       GST_RANK_NONE, GST_TYPE_VIDEO_CONVERT);
512 }
513
514 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
515     GST_VERSION_MINOR,
516     "videoconvert", "Colorspace conversion", plugin_init, VERSION, "LGPL", "",
517     "")