jpegdec: add support for RGB and grayscale color space
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Tue, 27 Apr 2010 13:44:39 +0000 (15:44 +0200)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Fri, 30 Apr 2010 15:49:11 +0000 (17:49 +0200)
Also refactor src caps negotiation and setting.

ext/jpeg/gstjpegdec.c
ext/jpeg/gstjpegdec.h

index a3fa13a..b4b2979 100644 (file)
@@ -59,12 +59,17 @@ enum
   PROP_IDCT_METHOD
 };
 
+/* *INDENT-OFF* */
 static GstStaticPadTemplate gst_jpeg_dec_src_pad_template =
 GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420") "; "
+        GST_VIDEO_CAPS_RGB "; " GST_VIDEO_CAPS_BGR "; "
+        GST_VIDEO_CAPS_RGBx "; " GST_VIDEO_CAPS_xRGB "; "
+        GST_VIDEO_CAPS_BGRx "; " GST_VIDEO_CAPS_xBGR)
     );
+/* *INDENT-ON* */
 
 static GstStaticPadTemplate gst_jpeg_dec_sink_pad_template =
 GST_STATIC_PAD_TEMPLATE ("sink",
@@ -715,8 +720,50 @@ gst_jpeg_dec_ensure_buffers (GstJpegDec * dec, guint maxrowbytes)
 }
 
 static void
+gst_jpeg_dec_decode_rgb (GstJpegDec * dec, guchar * base[3],
+    guint width, guint height, guint pstride, guint rstride)
+{
+  guchar *r_rows[16], *g_rows[16], *b_rows[16];
+  guchar **scanarray[3] = { r_rows, g_rows, b_rows };
+  gint i, j, k;
+  gint lines;
+
+  GST_DEBUG_OBJECT (dec, "indirect decoding of RGB");
+
+  if (G_UNLIKELY (!gst_jpeg_dec_ensure_buffers (dec, GST_ROUND_UP_32 (width))))
+    return;
+
+  memcpy (r_rows, dec->idr_y, 16 * sizeof (gpointer));
+  memcpy (g_rows, dec->idr_u, 16 * sizeof (gpointer));
+  memcpy (b_rows, dec->idr_v, 16 * sizeof (gpointer));
+
+  i = 0;
+  while (i < height) {
+    lines = jpeg_read_raw_data (&dec->cinfo, scanarray, DCTSIZE);
+    if (G_LIKELY (lines > 0)) {
+      for (j = 0; (j < DCTSIZE) && (i < height); j++, i++) {
+        gint p;
+
+        p = 0;
+        for (k = 0; k < width; k++) {
+          base[0][p] = r_rows[j][k];
+          base[1][p] = g_rows[j][k];
+          base[2][p] = b_rows[j][k];
+          p += pstride;
+        }
+        base[0] += rstride;
+        base[1] += rstride;
+        base[2] += rstride;
+      }
+    } else {
+      GST_INFO_OBJECT (dec, "jpeg_read_raw_data() returned 0");
+    }
+  }
+}
+
+static void
 gst_jpeg_dec_decode_indirect (GstJpegDec * dec, guchar * base[3],
-    guchar * last[3], guint width, guint height, gint r_v, gint r_h)
+    guchar * last[3], guint width, guint height, gint r_v, gint r_h, gint comp)
 {
   guchar *y_rows[16], *u_rows[16], *v_rows[16];
   guchar **scanarray[3] = { y_rows, u_rows, v_rows };
@@ -733,6 +780,15 @@ gst_jpeg_dec_decode_indirect (GstJpegDec * dec, guchar * base[3],
   memcpy (u_rows, dec->idr_u, 16 * sizeof (gpointer));
   memcpy (v_rows, dec->idr_v, 16 * sizeof (gpointer));
 
+  /* fill chroma components for grayscale */
+  if (comp == 1) {
+    GST_DEBUG_OBJECT (dec, "grayscale, filling chroma");
+    for (i = 0; i < 16; i++) {
+      memset (u_rows[i], GST_ROUND_UP_32 (width), 0x80);
+      memset (v_rows[i], GST_ROUND_UP_32 (width), 0x80);
+    }
+  }
+
   for (i = 0; i < height; i += r_v * DCTSIZE) {
     lines = jpeg_read_raw_data (&dec->cinfo, scanarray, r_v * DCTSIZE);
     if (G_LIKELY (lines > 0)) {
@@ -927,6 +983,110 @@ gst_jpeg_dec_do_qos (GstJpegDec * dec, GstClockTime timestamp)
   return TRUE;
 }
 
+static void
+gst_jpeg_dec_negotiate (GstJpegDec * dec, gint width, gint height, gint clrspc)
+{
+  GstCaps *caps;
+  GstVideoFormat format;
+
+  if (G_UNLIKELY (width == dec->caps_width && height == dec->caps_height &&
+          dec->framerate_numerator == dec->caps_framerate_numerator &&
+          dec->framerate_denominator == dec->caps_framerate_denominator &&
+          clrspc == dec->clrspc))
+    return;
+
+  /* framerate == 0/1 is a still frame */
+  if (dec->framerate_denominator == 0) {
+    dec->framerate_numerator = 0;
+    dec->framerate_denominator = 1;
+  }
+
+  /* calculate or assume an average frame duration for QoS purposes */
+  GST_OBJECT_LOCK (dec);
+  if (dec->framerate_numerator != 0) {
+    dec->qos_duration = gst_util_uint64_scale (GST_SECOND,
+        dec->framerate_denominator, dec->framerate_numerator);
+  } else {
+    /* if not set just use 25fps */
+    dec->qos_duration = gst_util_uint64_scale (GST_SECOND, 1, 25);
+  }
+  GST_OBJECT_UNLOCK (dec);
+
+  if (dec->cinfo.jpeg_color_space == JCS_RGB) {
+    gint i;
+    GstCaps *allowed_caps;
+
+    GST_DEBUG_OBJECT (dec, "selecting RGB format");
+    /* retrieve allowed caps, and find the first one that reasonably maps
+     * to the parameters of the colourspace */
+    caps = gst_pad_get_allowed_caps (dec->srcpad);
+    if (!caps) {
+      GST_DEBUG_OBJECT (dec, "... but no peer, using template caps");
+      /* need to copy because get_allowed_caps returns a ref,
+       * and get_pad_template_caps doesn't */
+      caps = gst_caps_copy (gst_pad_get_pad_template_caps (dec->srcpad));
+    }
+    /* avoid lists of fourcc, etc */
+    allowed_caps = gst_caps_normalize (caps);
+    gst_caps_unref (caps);
+    caps = NULL;
+    GST_LOG_OBJECT (dec, "allowed source caps %" GST_PTR_FORMAT, allowed_caps);
+
+    for (i = 0; i < gst_caps_get_size (allowed_caps); i++) {
+      if (caps)
+        gst_caps_unref (caps);
+      caps = gst_caps_copy_nth (allowed_caps, i);
+      /* sigh, ds and _parse_caps need fixed caps for parsing, fixate */
+      gst_pad_fixate_caps (dec->srcpad, caps);
+      GST_LOG_OBJECT (dec, "checking caps %" GST_PTR_FORMAT, caps);
+      if (!gst_video_format_parse_caps (caps, &format, NULL, NULL))
+        continue;
+      /* we'll settle for the first (preferred) downstream rgb format */
+      if (gst_video_format_is_rgb (format))
+        break;
+      /* default fall-back */
+      format = GST_VIDEO_FORMAT_RGB;
+    }
+    if (caps)
+      gst_caps_unref (caps);
+    gst_caps_unref (allowed_caps);
+    caps = gst_video_format_new_caps (format, width, height,
+        dec->framerate_numerator, dec->framerate_denominator, 1, 1);
+    dec->outsize = gst_video_format_get_size (format, width, height);
+    /* some format info */
+    dec->offset[0] =
+        gst_video_format_get_component_offset (format, 0, width, height);
+    dec->offset[1] =
+        gst_video_format_get_component_offset (format, 1, width, height);
+    dec->offset[2] =
+        gst_video_format_get_component_offset (format, 2, width, height);
+    /* equal for all components */
+    dec->stride = gst_video_format_get_row_stride (format, 0, width);
+    dec->inc = gst_video_format_get_pixel_stride (format, 0);
+  } else {
+    /* go for plain and simple I420 */
+    /* TODO other YUV cases ? */
+    caps = gst_caps_new_simple ("video/x-raw-yuv",
+        "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),
+        "width", G_TYPE_INT, width, "height", G_TYPE_INT, height,
+        "framerate", GST_TYPE_FRACTION, dec->framerate_numerator,
+        dec->framerate_denominator, NULL);
+    dec->outsize = I420_SIZE (width, height);
+  }
+
+  GST_DEBUG_OBJECT (dec, "setting caps %" GST_PTR_FORMAT, caps);
+  GST_DEBUG_OBJECT (dec, "max_v_samp_factor=%d", dec->cinfo.max_v_samp_factor);
+  GST_DEBUG_OBJECT (dec, "max_h_samp_factor=%d", dec->cinfo.max_h_samp_factor);
+
+  gst_pad_set_caps (dec->srcpad, caps);
+  gst_caps_unref (caps);
+
+  dec->caps_width = width;
+  dec->caps_height = height;
+  dec->caps_framerate_numerator = dec->framerate_numerator;
+  dec->caps_framerate_denominator = dec->framerate_denominator;
+}
+
 static GstFlowReturn
 gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
 {
@@ -1035,7 +1195,9 @@ gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
     goto components_not_supported;
 
   /* verify color space expectation to avoid going *boom* or bogus output */
-  if (dec->cinfo.jpeg_color_space != JCS_YCbCr)
+  if (dec->cinfo.jpeg_color_space != JCS_YCbCr &&
+      dec->cinfo.jpeg_color_space != JCS_GRAYSCALE &&
+      dec->cinfo.jpeg_color_space != JCS_RGB)
     goto unsupported_colorspace;
 
 #ifndef GST_DISABLE_GST_DEBUG
@@ -1054,7 +1216,7 @@ gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
   /* prepare for raw output */
   dec->cinfo.do_fancy_upsampling = FALSE;
   dec->cinfo.do_block_smoothing = FALSE;
-  dec->cinfo.out_color_space = JCS_YCbCr;
+  dec->cinfo.out_color_space = dec->cinfo.jpeg_color_space;
   dec->cinfo.dct_method = dec->idct_method;
   dec->cinfo.raw_data_out = TRUE;
 
@@ -1064,11 +1226,27 @@ gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
     GST_WARNING_OBJECT (dec, "failed to start decompression cycle");
   }
 
-  /* YUV sanity checks to get safe and reasonable I420 output */
-  g_assert (dec->cinfo.num_components == 3);
-  if (r_v > 2 || r_v < dec->cinfo.comp_info[0].v_samp_factor ||
-      r_h < dec->cinfo.comp_info[0].h_samp_factor)
-    goto invalid_yuv;
+  /* sanity checks to get safe and reasonable output */
+  switch (dec->cinfo.jpeg_color_space) {
+    case JCS_GRAYSCALE:
+      break;
+    case JCS_RGB:
+      if (dec->cinfo.num_components != 3 || dec->cinfo.max_v_samp_factor > 1 ||
+          dec->cinfo.max_h_samp_factor > 1)
+        goto invalid_yuvrgb;
+      break;
+    case JCS_YCbCr:
+      if (dec->cinfo.num_components != 3 ||
+          r_v > 2 || r_v < dec->cinfo.comp_info[0].v_samp_factor ||
+          r_v < dec->cinfo.comp_info[1].v_samp_factor ||
+          r_h < dec->cinfo.comp_info[0].h_samp_factor ||
+          r_h < dec->cinfo.comp_info[1].h_samp_factor)
+        goto invalid_yuvrgb;
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
 
   width = dec->cinfo.output_width;
   height = dec->cinfo.output_height;
@@ -1077,50 +1255,7 @@ gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
           height < MIN_HEIGHT || height > MAX_HEIGHT))
     goto wrong_size;
 
-  if (G_UNLIKELY (width != dec->caps_width || height != dec->caps_height ||
-          dec->framerate_numerator != dec->caps_framerate_numerator ||
-          dec->framerate_denominator != dec->caps_framerate_denominator)) {
-    GstCaps *caps;
-
-    /* framerate == 0/1 is a still frame */
-    if (dec->framerate_denominator == 0) {
-      dec->framerate_numerator = 0;
-      dec->framerate_denominator = 1;
-    }
-
-    /* calculate or assume an average frame duration for QoS purposes */
-    GST_OBJECT_LOCK (dec);
-    if (dec->framerate_numerator != 0) {
-      dec->qos_duration = gst_util_uint64_scale (GST_SECOND,
-          dec->framerate_denominator, dec->framerate_numerator);
-    } else {
-      /* if not set just use 25fps */
-      dec->qos_duration = gst_util_uint64_scale (GST_SECOND, 1, 25);
-    }
-    GST_OBJECT_UNLOCK (dec);
-
-    caps = gst_caps_new_simple ("video/x-raw-yuv",
-        "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),
-        "width", G_TYPE_INT, width,
-        "height", G_TYPE_INT, height,
-        "framerate", GST_TYPE_FRACTION, dec->framerate_numerator,
-        dec->framerate_denominator, NULL);
-
-    GST_DEBUG_OBJECT (dec, "setting caps %" GST_PTR_FORMAT, caps);
-    GST_DEBUG_OBJECT (dec, "max_v_samp_factor=%d",
-        dec->cinfo.max_v_samp_factor);
-    GST_DEBUG_OBJECT (dec, "max_h_samp_factor=%d",
-        dec->cinfo.max_h_samp_factor);
-
-    gst_pad_set_caps (dec->srcpad, caps);
-    gst_caps_unref (caps);
-
-    dec->caps_width = width;
-    dec->caps_height = height;
-    dec->caps_framerate_numerator = dec->framerate_numerator;
-    dec->caps_framerate_denominator = dec->framerate_denominator;
-    dec->outsize = I420_SIZE (width, height);
-  }
+  gst_jpeg_dec_negotiate (dec, width, height, dec->cinfo.jpeg_color_space);
 
   ret = gst_pad_alloc_buffer_and_set_caps (dec->srcpad, GST_BUFFER_OFFSET_NONE,
       dec->outsize, GST_PAD_CAPS (dec->srcpad), &outbuf);
@@ -1153,43 +1288,51 @@ gst_jpeg_dec_chain (GstPad * pad, GstBuffer * buf)
   }
   GST_BUFFER_DURATION (outbuf) = duration;
 
-  /* mind the swap, jpeglib outputs blue chroma first
-   * ensonic: I see no swap?
-   */
-  base[0] = outdata + I420_Y_OFFSET (width, height);
-  base[1] = outdata + I420_U_OFFSET (width, height);
-  base[2] = outdata + I420_V_OFFSET (width, height);
-
-  /* make sure we don't make jpeglib write beyond our buffer,
-   * which might happen if (height % (r_v*DCTSIZE)) != 0 */
-  last[0] = base[0] + (I420_Y_ROWSTRIDE (width) * (height - 1));
-  last[1] =
-      base[1] + (I420_U_ROWSTRIDE (width) * ((GST_ROUND_UP_2 (height) / 2) -
-          1));
-  last[2] =
-      base[2] + (I420_V_ROWSTRIDE (width) * ((GST_ROUND_UP_2 (height) / 2) -
-          1));
-
-  GST_LOG_OBJECT (dec, "decompressing (reqired scanline buffer height = %u)",
-      dec->cinfo.rec_outbuf_height);
-
-  /* For some widths jpeglib requires more horizontal padding than I420 
-   * provides. In those cases we need to decode into separate buffers and then
-   * copy over the data into our final picture buffer, otherwise jpeglib might
-   * write over the end of a line into the beginning of the next line,
-   * resulting in blocky artifacts on the left side of the picture. */
-  if (G_UNLIKELY (width % (dec->cinfo.max_h_samp_factor * DCTSIZE) != 0
-          || dec->cinfo.comp_info[0].h_samp_factor != 2
-          || dec->cinfo.comp_info[1].h_samp_factor != 1
-          || dec->cinfo.comp_info[2].h_samp_factor != 1)) {
-    GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, dec,
-        "indirect decoding using extra buffer copy");
-    gst_jpeg_dec_decode_indirect (dec, base, last, width, height, r_v, r_h);
+  if (dec->cinfo.jpeg_color_space == JCS_RGB) {
+    base[0] = outdata + dec->offset[0];
+    base[1] = outdata + dec->offset[1];
+    base[2] = outdata + dec->offset[2];
+    gst_jpeg_dec_decode_rgb (dec, base, width, height, dec->inc, dec->stride);
   } else {
-    ret = gst_jpeg_dec_decode_direct (dec, base, last, width, height);
+    /* mind the swap, jpeglib outputs blue chroma first
+     * ensonic: I see no swap?
+     */
+    base[0] = outdata + I420_Y_OFFSET (width, height);
+    base[1] = outdata + I420_U_OFFSET (width, height);
+    base[2] = outdata + I420_V_OFFSET (width, height);
+
+    /* make sure we don't make jpeglib write beyond our buffer,
+     * which might happen if (height % (r_v*DCTSIZE)) != 0 */
+    last[0] = base[0] + (I420_Y_ROWSTRIDE (width) * (height - 1));
+    last[1] =
+        base[1] + (I420_U_ROWSTRIDE (width) * ((GST_ROUND_UP_2 (height) / 2) -
+            1));
+    last[2] =
+        base[2] + (I420_V_ROWSTRIDE (width) * ((GST_ROUND_UP_2 (height) / 2) -
+            1));
+
+    GST_LOG_OBJECT (dec, "decompressing (reqired scanline buffer height = %u)",
+        dec->cinfo.rec_outbuf_height);
+
+    /* For some widths jpeglib requires more horizontal padding than I420 
+     * provides. In those cases we need to decode into separate buffers and then
+     * copy over the data into our final picture buffer, otherwise jpeglib might
+     * write over the end of a line into the beginning of the next line,
+     * resulting in blocky artifacts on the left side of the picture. */
+    if (G_UNLIKELY (width % (dec->cinfo.max_h_samp_factor * DCTSIZE) != 0
+            || dec->cinfo.comp_info[0].h_samp_factor != 2
+            || dec->cinfo.comp_info[1].h_samp_factor != 1
+            || dec->cinfo.comp_info[2].h_samp_factor != 1)) {
+      GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, dec,
+          "indirect decoding using extra buffer copy");
+      gst_jpeg_dec_decode_indirect (dec, base, last, width, height, r_v, r_h,
+          dec->cinfo.num_components);
+    } else {
+      ret = gst_jpeg_dec_decode_direct (dec, base, last, width, height);
 
-    if (G_UNLIKELY (ret != GST_FLOW_OK))
-      goto decode_direct_failed;
+      if (G_UNLIKELY (ret != GST_FLOW_OK))
+        goto decode_direct_failed;
+    }
   }
 
   GST_LOG_OBJECT (dec, "decompressing finished");
@@ -1323,10 +1466,10 @@ unsupported_colorspace:
     ret = GST_FLOW_ERROR;
     goto done;
   }
-invalid_yuv:
+invalid_yuvrgb:
   {
     GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL),
-        ("Picture is corrupt or unhandled YUV layout"));
+        ("Picture is corrupt or unhandled YUV/RGB layout"));
     ret = GST_FLOW_ERROR;
     goto done;
   }
@@ -1456,6 +1599,7 @@ gst_jpeg_dec_change_state (GstElement * element, GstStateChange transition)
       dec->caps_framerate_numerator = dec->caps_framerate_denominator = 0;
       dec->caps_width = -1;
       dec->caps_height = -1;
+      dec->clrspc = -1;
       dec->packetized = FALSE;
       dec->next_ts = 0;
       dec->discont = TRUE;
index cdf77db..c1e4632 100644 (file)
@@ -24,6 +24,7 @@
 
 #include <setjmp.h>
 #include <gst/gst.h>
+#include <gst/video/video.h>
 
 /* this is a hack hack hack to get around jpeglib header bugs... */
 #ifdef HAVE_STDLIB_H
@@ -96,6 +97,11 @@ struct _GstJpegDec {
   gint     caps_width;
   gint     caps_height;
   gint     outsize;
+  gint     clrspc;
+
+  gint     offset[3];
+  gint     stride;
+  gint     inc;
 
   /* properties */
   gint     idct_method;