gstcolorspace.c 16.8 KB
Newer Older
1 2
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 4 5
 * This file:
 * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
 * Copyright (C) 2010 David Schleef <ds@schleef.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

23 24 25 26 27 28 29 30 31 32 33 34 35
/**
 * SECTION:element-colorspace
 *
 * Convert video frames between a great variety of colorspace formats.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch -v videotestsrc ! video/x-raw-yuv,format=\(fourcc\)YUY2 ! colorspace ! ximagesink
 * ]|
 * </refsect2>
 */

36
#ifdef HAVE_CONFIG_H
37
#  include "config.h"
38
#endif
39

40
#include "gstcolorspace.h"
41 42
#include <gst/video/video.h>

43 44
#include <string.h>

45 46 47 48
GST_DEBUG_CATEGORY (colorspace_debug);
#define GST_CAT_DEFAULT colorspace_debug
GST_DEBUG_CATEGORY (colorspace_performance);

49 50 51 52 53 54
enum
{
  PROP_0,
  PROP_DITHER
};

55 56 57
#define CSP_VIDEO_CAPS						\
  "video/x-raw-yuv, width = "GST_VIDEO_SIZE_RANGE" , "			\
  "height="GST_VIDEO_SIZE_RANGE",framerate="GST_VIDEO_FPS_RANGE","	\
58
  "format= (fourcc) { I420 , NV12 , NV21 , YV12 , YUY2 , Y42B , Y444 , YUV9 , YVU9 , Y41B , Y800 , Y8 , GREY , Y16 , UYVY , YVYU , IYU1 , v308 , AYUV, v210, v216, A420, AY64 } ;" \
59 60 61 62 63 64 65 66 67 68 69
  GST_VIDEO_CAPS_RGB";"							\
  GST_VIDEO_CAPS_BGR";"							\
  GST_VIDEO_CAPS_RGBx";"						\
  GST_VIDEO_CAPS_xRGB";"						\
  GST_VIDEO_CAPS_BGRx";"						\
  GST_VIDEO_CAPS_xBGR";"						\
  GST_VIDEO_CAPS_RGBA";"						\
  GST_VIDEO_CAPS_ARGB";"						\
  GST_VIDEO_CAPS_BGRA";"						\
  GST_VIDEO_CAPS_ABGR";"						\
  GST_VIDEO_CAPS_RGB_16";"						\
70
  GST_VIDEO_CAPS_BGR_16";"						\
71
  GST_VIDEO_CAPS_RGB_15";"						\
72
  GST_VIDEO_CAPS_BGR_15";"						\
73
  GST_VIDEO_CAPS_RGB8_PALETTED "; "                                     \
74 75
  GST_VIDEO_CAPS_GRAY8";"						\
  GST_VIDEO_CAPS_GRAY16("BIG_ENDIAN")";"				\
76
  GST_VIDEO_CAPS_GRAY16("LITTLE_ENDIAN")";"                             \
77
  GST_VIDEO_CAPS_r210";"                                                \
78
  GST_VIDEO_CAPS_ARGB_64
79 80 81 82 83 84 85

static GstStaticPadTemplate gst_csp_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (CSP_VIDEO_CAPS)
    );
86

87
static GstStaticPadTemplate gst_csp_sink_template =
88
GST_STATIC_PAD_TEMPLATE ("sink",
89 90
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
91
    GST_STATIC_CAPS (CSP_VIDEO_CAPS)
92
    );
93

94
GType gst_csp_get_type (void);
95

96 97 98 99 100 101
static void gst_csp_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec);
static void gst_csp_get_property (GObject * object,
    guint property_id, GValue * value, GParamSpec * pspec);
static void gst_csp_dispose (GObject * object);

102 103 104 105 106 107
static gboolean gst_csp_set_caps (GstBaseTransform * btrans,
    GstCaps * incaps, GstCaps * outcaps);
static gboolean gst_csp_get_unit_size (GstBaseTransform * btrans,
    GstCaps * caps, guint * size);
static GstFlowReturn gst_csp_transform (GstBaseTransform * btrans,
    GstBuffer * inbuf, GstBuffer * outbuf);
108

109 110 111 112
static GQuark _QRAWRGB;         /* "video/x-raw-rgb" */
static GQuark _QRAWYUV;         /* "video/x-raw-yuv" */
static GQuark _QALPHAMASK;      /* "alpha_mask" */

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

static GType
dither_method_get_type (void)
{
  static GType gtype = 0;

  if (gtype == 0) {
    static const GEnumValue values[] = {
      {DITHER_NONE, "No dithering (default)", "none"},
      {DITHER_VERTERR, "Vertical error propogation", "verterr"},
      {DITHER_HALFTONE, "Half-tone", "halftone"},
      {0, NULL, NULL}
    };

    gtype = g_enum_register_static ("GstColorspaceDitherMethod", values);
  }
  return gtype;
}

132 133 134
/* copies the given caps */
static GstCaps *
gst_csp_caps_remove_format_info (GstCaps * caps)
135
{
136
  GstStructure *yuvst, *rgbst, *grayst;
137

138 139
  /* We know there's only one structure since we're given simple caps */
  caps = gst_caps_copy (caps);
140

141
  yuvst = gst_caps_get_structure (caps, 0);
142

143 144 145
  gst_structure_set_name (yuvst, "video/x-raw-yuv");
  gst_structure_remove_fields (yuvst, "format", "endianness", "depth",
      "bpp", "red_mask", "green_mask", "blue_mask", "alpha_mask",
146
      "palette_data", "color-matrix", NULL);
147

148 149 150
  rgbst = gst_structure_copy (yuvst);
  gst_structure_set_name (rgbst, "video/x-raw-rgb");
  gst_structure_remove_fields (rgbst, "color-matrix", "chroma-site", NULL);
151

152 153
  grayst = gst_structure_copy (rgbst);
  gst_structure_set_name (grayst, "video/x-raw-gray");
154

155 156
  gst_caps_append_structure (caps, rgbst);
  gst_caps_append_structure (caps, grayst);
157

158
  return caps;
159 160
}

161 162 163

static gboolean
gst_csp_structure_is_alpha (GstStructure * s)
164
{
165 166 167 168 169 170 171 172 173 174 175 176 177
  GQuark name;

  name = gst_structure_get_name_id (s);

  if (name == _QRAWRGB) {
    return gst_structure_id_has_field (s, _QALPHAMASK);
  } else if (name == _QRAWYUV) {
    guint32 fourcc;

    if (!gst_structure_get_fourcc (s, "format", &fourcc))
      return FALSE;

    return (fourcc == GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'));
178 179
  }

180
  return FALSE;
181 182
}

183 184 185
/* The caps can be transformed into any other caps with format info removed.
 * However, we should prefer passthrough, so if passthrough is possible,
 * put it first in the list. */
186
static GstCaps *
187 188
gst_csp_transform_caps (GstBaseTransform * btrans,
    GstPadDirection direction, GstCaps * caps)
189
{
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
  GstCaps *template;
  GstCaps *tmp, *tmp2;
  GstCaps *result;
  GstStructure *s;
  GstCaps *alpha, *non_alpha;

  template = gst_static_pad_template_get_caps (&gst_csp_src_template);
  result = gst_caps_copy (caps);

  /* Get all possible caps that we can transform to */
  tmp = gst_csp_caps_remove_format_info (caps);
  tmp2 = gst_caps_intersect (tmp, template);
  gst_caps_unref (tmp);
  tmp = tmp2;

  /* Now move alpha formats to the beginning if caps is an alpha format
   * or at the end if caps is no alpha format */
  alpha = gst_caps_new_empty ();
  non_alpha = gst_caps_new_empty ();

  while ((s = gst_caps_steal_structure (tmp, 0))) {
    if (gst_csp_structure_is_alpha (s))
      gst_caps_append_structure (alpha, s);
    else
      gst_caps_append_structure (non_alpha, s);
  }
216

217 218
  s = gst_caps_get_structure (caps, 0);
  gst_caps_unref (tmp);
219

220 221 222 223 224 225 226
  if (gst_csp_structure_is_alpha (s)) {
    gst_caps_append (alpha, non_alpha);
    tmp = alpha;
  } else {
    gst_caps_append (non_alpha, alpha);
    tmp = non_alpha;
  }
227

228
  gst_caps_append (result, tmp);
229

230 231
  GST_DEBUG_OBJECT (btrans, "transformed %" GST_PTR_FORMAT " into %"
      GST_PTR_FORMAT, caps, result);
232

233
  return result;
234 235
}

236 237 238
static gboolean
gst_csp_set_caps (GstBaseTransform * btrans, GstCaps * incaps,
    GstCaps * outcaps)
239
{
240 241 242 243 244 245 246 247 248 249 250
  GstCsp *space;
  GstVideoFormat in_format;
  GstVideoFormat out_format;
  gint in_height, in_width;
  gint out_height, out_width;
  gint in_fps_n, in_fps_d, in_par_n, in_par_d;
  gint out_fps_n, out_fps_d, out_par_n, out_par_d;
  gboolean have_in_par, have_out_par;
  gboolean have_in_interlaced, have_out_interlaced;
  gboolean in_interlaced, out_interlaced;
  gboolean ret;
251
  ColorSpaceColorSpec in_spec, out_spec;
252

253
  space = GST_CSP (btrans);
254

255 256 257 258
  if (space->convert) {
    colorspace_convert_free (space->convert);
  }

259
  /* input caps */
260

261 262 263
  ret = gst_video_format_parse_caps (incaps, &in_format, &in_width, &in_height);
  if (!ret)
    goto no_width_height;
264

265 266 267
  ret = gst_video_parse_caps_framerate (incaps, &in_fps_n, &in_fps_d);
  if (!ret)
    goto no_framerate;
268

269 270 271 272
  have_in_par = gst_video_parse_caps_pixel_aspect_ratio (incaps,
      &in_par_n, &in_par_d);
  have_in_interlaced = gst_video_format_parse_caps_interlaced (incaps,
      &in_interlaced);
273

274 275 276 277 278 279 280 281 282 283 284 285
  if (gst_video_format_is_rgb (in_format)) {
    in_spec = COLOR_SPEC_RGB;
  } else if (gst_video_format_is_yuv (in_format)) {
    const gchar *matrix = gst_video_parse_caps_color_matrix (incaps);

    if (matrix && g_str_equal (matrix, "hdtv"))
      in_spec = COLOR_SPEC_YUV_BT709;
    else
      in_spec = COLOR_SPEC_YUV_BT470_6;
  } else {
    in_spec = COLOR_SPEC_GRAY;
  }
286

287
  /* output caps */
288

289 290 291 292 293
  ret =
      gst_video_format_parse_caps (outcaps, &out_format, &out_width,
      &out_height);
  if (!ret)
    goto no_width_height;
294

295 296 297
  ret = gst_video_parse_caps_framerate (outcaps, &out_fps_n, &out_fps_d);
  if (!ret)
    goto no_framerate;
298

299 300 301 302
  have_out_par = gst_video_parse_caps_pixel_aspect_ratio (outcaps,
      &out_par_n, &out_par_d);
  have_out_interlaced = gst_video_format_parse_caps_interlaced (incaps,
      &out_interlaced);
303

304 305 306 307 308 309 310 311 312 313 314 315
  if (gst_video_format_is_rgb (out_format)) {
    out_spec = COLOR_SPEC_RGB;
  } else if (gst_video_format_is_yuv (out_format)) {
    const gchar *matrix = gst_video_parse_caps_color_matrix (outcaps);

    if (matrix && g_str_equal (matrix, "hdtv"))
      out_spec = COLOR_SPEC_YUV_BT709;
    else
      out_spec = COLOR_SPEC_YUV_BT470_6;
  } else {
    out_spec = COLOR_SPEC_GRAY;
  }
316

317 318 319 320
  /* these must match */
  if (in_width != out_width || in_height != out_height ||
      in_fps_n != out_fps_n || in_fps_d != out_fps_d)
    goto format_mismatch;
321

322 323 324 325
  /* if present, these must match too */
  if (have_in_par && have_out_par &&
      (in_par_n != out_par_n || in_par_d != out_par_d))
    goto format_mismatch;
326

327 328 329 330
  /* if present, these must match too */
  if (have_in_interlaced && have_out_interlaced &&
      in_interlaced != out_interlaced)
    goto format_mismatch;
331

332
  space->from_format = in_format;
333
  space->from_spec = in_spec;
334
  space->to_format = out_format;
335
  space->to_spec = out_spec;
336 337 338
  space->width = in_width;
  space->height = in_height;
  space->interlaced = in_interlaced;
339

340 341
  space->convert = colorspace_convert_new (out_format, out_spec, in_format,
      in_spec, in_width, in_height);
342 343 344
  if (space->convert) {
    colorspace_convert_set_interlaced (space->convert, in_interlaced);
  }
345
  /* palette, only for from data */
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
  if (space->from_format == GST_VIDEO_FORMAT_RGB8_PALETTED &&
      space->to_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
    goto format_mismatch;
  } else if (space->from_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
    GstBuffer *palette;

    palette = gst_video_parse_caps_palette (incaps);

    if (!palette || GST_BUFFER_SIZE (palette) < 256 * 4) {
      if (palette)
        gst_buffer_unref (palette);
      goto invalid_palette;
    }
    colorspace_convert_set_palette (space->convert,
        (const guint32 *) GST_BUFFER_DATA (palette));
    gst_buffer_unref (palette);
  } else if (space->to_format == GST_VIDEO_FORMAT_RGB8_PALETTED) {
    const guint32 *palette;
    GstBuffer *p_buf;

    palette = colorspace_convert_get_palette (space->convert);
    p_buf = gst_buffer_new_and_alloc (256 * 4);
    memcpy (GST_BUFFER_DATA (p_buf), palette, 256 * 4);
    gst_caps_set_simple (outcaps, "palette_data", GST_TYPE_BUFFER, p_buf, NULL);
    gst_buffer_unref (p_buf);
  }
372

373
  GST_DEBUG ("reconfigured %d %d", space->from_format, space->to_format);
374

375
  return TRUE;
376

377 378 379
  /* ERRORS */
no_width_height:
  {
380
    GST_ERROR_OBJECT (space, "did not specify width or height");
381 382 383
    space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
    space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
    return FALSE;
384
  }
385 386
no_framerate:
  {
387
    GST_ERROR_OBJECT (space, "did not specify framerate");
388 389 390
    space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
    space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
    return FALSE;
391
  }
392 393
format_mismatch:
  {
394
    GST_ERROR_OBJECT (space, "input and output formats do not match");
395 396 397
    space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
    space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
    return FALSE;
398
  }
399 400 401 402 403 404 405
invalid_palette:
  {
    GST_ERROR_OBJECT (space, "invalid palette");
    space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
    space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
    return FALSE;
  }
406 407
}

408
GST_BOILERPLATE (GstCsp, gst_csp, GstVideoFilter, GST_TYPE_VIDEO_FILTER);
409 410

static void
411
gst_csp_base_init (gpointer klass)
412
{
413
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
414

415
  gst_element_class_add_pad_template (element_class,
416
      gst_static_pad_template_get (&gst_csp_src_template));
417
  gst_element_class_add_pad_template (element_class,
418 419 420 421 422 423
      gst_static_pad_template_get (&gst_csp_sink_template));

  gst_element_class_set_details_simple (element_class,
      " Colorspace converter", "Filter/Converter/Video",
      "Converts video from one colorspace to another",
      "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
424

425 426 427
  _QRAWRGB = g_quark_from_string ("video/x-raw-rgb");
  _QRAWYUV = g_quark_from_string ("video/x-raw-yuv");
  _QALPHAMASK = g_quark_from_string ("alpha_mask");
428
}
429

430 431 432 433 434 435
void
gst_csp_dispose (GObject * object)
{
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

436
static void
437
gst_csp_finalize (GObject * obj)
438
{
439 440 441 442 443 444
  GstCsp *space = GST_CSP (obj);

  if (space->convert) {
    colorspace_convert_free (space->convert);
  }

445
  G_OBJECT_CLASS (parent_class)->finalize (obj);
446

447 448 449 450 451 452 453 454
}

static void
gst_csp_class_init (GstCspClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstBaseTransformClass *gstbasetransform_class =
      (GstBaseTransformClass *) klass;
455

456 457 458
  gobject_class->set_property = gst_csp_set_property;
  gobject_class->get_property = gst_csp_get_property;
  gobject_class->dispose = gst_csp_dispose;
459
  gobject_class->finalize = gst_csp_finalize;
460

461 462 463 464 465 466
  gstbasetransform_class->transform_caps =
      GST_DEBUG_FUNCPTR (gst_csp_transform_caps);
  gstbasetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_csp_set_caps);
  gstbasetransform_class->get_unit_size =
      GST_DEBUG_FUNCPTR (gst_csp_get_unit_size);
  gstbasetransform_class->transform = GST_DEBUG_FUNCPTR (gst_csp_transform);
467

468
  gstbasetransform_class->passthrough_on_same_caps = TRUE;
469 470 471 472 473 474

  g_object_class_install_property (gobject_class, PROP_DITHER,
      g_param_spec_enum ("dither", "Dither", "Apply dithering while converting",
          dither_method_get_type (), DITHER_NONE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

475 476 477
}

static void
478
gst_csp_init (GstCsp * space, GstCspClass * klass)
479
{
480 481
  space->from_format = GST_VIDEO_FORMAT_UNKNOWN;
  space->to_format = GST_VIDEO_FORMAT_UNKNOWN;
482 483
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
void
gst_csp_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GstCsp *csp;

  g_return_if_fail (GST_IS_CSP (object));
  csp = GST_CSP (object);

  switch (property_id) {
    case PROP_DITHER:
      csp->dither = g_value_get_enum (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

void
gst_csp_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GstCsp *csp;

  g_return_if_fail (GST_IS_CSP (object));
  csp = GST_CSP (object);

  switch (property_id) {
    case PROP_DITHER:
      g_value_set_enum (value, csp->dither);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

522 523
static gboolean
gst_csp_get_unit_size (GstBaseTransform * btrans, GstCaps * caps, guint * size)
524
{
525 526 527
  gboolean ret = TRUE;
  GstVideoFormat format;
  gint width, height;
528

529
  g_assert (size);
530

531 532 533
  ret = gst_video_format_parse_caps (caps, &format, &width, &height);
  if (ret) {
    *size = gst_video_format_get_size (format, width, height);
534 535
  }

536
  return ret;
537 538
}

539 540 541
static GstFlowReturn
gst_csp_transform (GstBaseTransform * btrans, GstBuffer * inbuf,
    GstBuffer * outbuf)
542
{
543
  GstCsp *space;
544

545
  space = GST_CSP (btrans);
546

547
  GST_DEBUG ("from %d -> to %d", space->from_format, space->to_format);
548

549 550 551
  if (G_UNLIKELY (space->from_format == GST_VIDEO_FORMAT_UNKNOWN ||
          space->to_format == GST_VIDEO_FORMAT_UNKNOWN))
    goto unknown_format;
552

553
  colorspace_convert_set_dither (space->convert, space->dither);
554

555 556
  colorspace_convert_convert (space->convert, GST_BUFFER_DATA (outbuf),
      GST_BUFFER_DATA (inbuf));
557

558 559
  /* baseclass copies timestamps */
  GST_DEBUG ("from %d -> to %d done", space->from_format, space->to_format);
560

561
  return GST_FLOW_OK;
562

563 564 565 566 567 568
  /* ERRORS */
unknown_format:
  {
    GST_ELEMENT_ERROR (space, CORE, NOT_IMPLEMENTED, (NULL),
        ("attempting to convert colorspaces between unknown formats"));
    return GST_FLOW_NOT_NEGOTIATED;
569
  }
570 571 572 573 574 575 576 577
#if 0
not_supported:
  {
    GST_ELEMENT_ERROR (space, CORE, NOT_IMPLEMENTED, (NULL),
        ("cannot convert between formats"));
    return GST_FLOW_NOT_SUPPORTED;
  }
#endif
578 579 580
}

static gboolean
581
plugin_init (GstPlugin * plugin)
582
{
583 584 585
  GST_DEBUG_CATEGORY_INIT (colorspace_debug, "colorspace", 0,
      "Colorspace Converter");
  GST_DEBUG_CATEGORY_GET (colorspace_performance, "GST_PERFORMANCE");
586

587 588
  return gst_element_register (plugin, "colorspace",
      GST_RANK_NONE, GST_TYPE_CSP);
589 590
}

591 592
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
593
    "colorspace", "Colorspace conversion", plugin_init, VERSION, "LGPL", "", "")