Merge branch 'master' into 0.11
[platform/upstream/gstreamer.git] / sys / v4l2 / gstv4l2object.c
index 24a4123..7be11b8 100644 (file)
 #include "v4l2_calls.h"
 #include "gstv4l2tuner.h"
 #ifdef HAVE_XVIDEO
-#include "gstv4l2xoverlay.h"
+#include "gstv4l2videooverlay.h"
 #endif
 #include "gstv4l2colorbalance.h"
 
 #include "gst/gst-i18n-plugin.h"
 
+#include <gst/video/video.h>
+
 /* videodev2.h is not versioned and we can't easily check for the presence
  * of enum values at compile time, but the V4L2_CAP_VIDEO_OUTPUT_OVERLAY define
  * was added in the same commit as V4L2_FIELD_INTERLACED_{TB,BT} (b2787845) */
 #endif
 
 GST_DEBUG_CATEGORY_EXTERN (v4l2_debug);
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
 #define GST_CAT_DEFAULT v4l2_debug
 
-
 #define DEFAULT_PROP_DEVICE_NAME       NULL
 #define DEFAULT_PROP_DEVICE_FD          -1
 #define DEFAULT_PROP_FLAGS              0
 #define DEFAULT_PROP_TV_NORM            0
 #define DEFAULT_PROP_CHANNEL            NULL
 #define DEFAULT_PROP_FREQUENCY          0
+#define DEFAULT_PROP_IO_MODE            GST_V4L2_IO_AUTO
 
 enum
 {
@@ -66,21 +69,21 @@ enum
   V4L2_STD_OBJECT_PROPS,
 };
 
+G_LOCK_DEFINE_STATIC (probe_lock);
+
 const GList *
 gst_v4l2_probe_get_properties (GstPropertyProbe * probe)
 {
   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
   static GList *list = NULL;
 
-  /* well, not perfect, but better than no locking at all.
-   * In the worst case we leak a list node, so who cares? */
-  GST_CLASS_LOCK (GST_OBJECT_CLASS (klass));
+  G_LOCK (probe_lock);
 
   if (!list) {
     list = g_list_append (NULL, g_object_class_find_property (klass, "device"));
   }
 
-  GST_CLASS_UNLOCK (GST_OBJECT_CLASS (klass));
+  G_UNLOCK (probe_lock);
 
   return list;
 }
@@ -368,6 +371,26 @@ gst_v4l2_tv_norm_get_type (void)
   return v4l2_tv_norm;
 }
 
+#define GST_TYPE_V4L2_IO_MODE (gst_v4l2_io_mode_get_type ())
+static GType
+gst_v4l2_io_mode_get_type (void)
+{
+  static GType v4l2_io_mode = 0;
+
+  if (!v4l2_io_mode) {
+    static const GEnumValue io_modes[] = {
+      {GST_V4L2_IO_AUTO, "GST_V4L2_IO_AUTO", "auto"},
+      {GST_V4L2_IO_RW, "GST_V4L2_IO_RW", "rw"},
+      {GST_V4L2_IO_MMAP, "GST_V4L2_IO_MMAP", "mmap"},
+      {GST_V4L2_IO_USERPTR, "GST_V4L2_IO_USERPTR", "userptr"},
+
+      {0, NULL, NULL}
+    };
+    v4l2_io_mode = g_enum_register_static ("GstV4l2IOMode", io_modes);
+  }
+  return v4l2_io_mode;
+}
+
 void
 gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class,
     const char *default_device)
@@ -449,6 +472,17 @@ gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class,
           "video standard",
           GST_TYPE_V4L2_TV_NORM, DEFAULT_PROP_TV_NORM,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstV4l2Src:io-mode
+   *
+   * IO Mode
+   */
+  g_object_class_install_property (gobject_class, PROP_IO_MODE,
+      g_param_spec_enum ("io-mode", "IO mode",
+          "I/O mode",
+          GST_TYPE_V4L2_IO_MODE, DEFAULT_PROP_IO_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 GstV4l2Object *
@@ -476,7 +510,7 @@ gst_v4l2_object_new (GstElement * element,
 
   v4l2object->video_fd = -1;
   v4l2object->poll = gst_poll_new (TRUE);
-  v4l2object->buffer = NULL;
+  v4l2object->active = FALSE;
   v4l2object->videodev = g_strdup (default_device);
 
   v4l2object->norms = NULL;
@@ -610,6 +644,9 @@ gst_v4l2_object_set_property_helper (GstV4l2Object * v4l2object,
       }
       break;
 #endif
+    case PROP_IO_MODE:
+      v4l2object->req_mode = g_value_get_enum (value);
+      break;
     default:
       return FALSE;
       break;
@@ -683,6 +720,9 @@ gst_v4l2_object_get_property_helper (GstV4l2Object * v4l2object,
     case PROP_TV_NORM:
       g_value_set_enum (value, v4l2object->tv_norm);
       break;
+    case PROP_IO_MODE:
+      g_value_set_enum (value, v4l2object->req_mode);
+      break;
     default:
       return FALSE;
       break;
@@ -749,7 +789,7 @@ gst_v4l2_set_defaults (GstV4l2Object * v4l2object)
 }
 
 gboolean
-gst_v4l2_object_start (GstV4l2Object * v4l2object)
+gst_v4l2_object_open (GstV4l2Object * v4l2object)
 {
   if (gst_v4l2_open (v4l2object))
     gst_v4l2_set_defaults (v4l2object);
@@ -757,17 +797,17 @@ gst_v4l2_object_start (GstV4l2Object * v4l2object)
     return FALSE;
 
 #ifdef HAVE_XVIDEO
-  gst_v4l2_xoverlay_start (v4l2object);
+  gst_v4l2_video_overlay_start (v4l2object);
 #endif
 
   return TRUE;
 }
 
 gboolean
-gst_v4l2_object_stop (GstV4l2Object * v4l2object)
+gst_v4l2_object_close (GstV4l2Object * v4l2object)
 {
 #ifdef HAVE_XVIDEO
-  gst_v4l2_xoverlay_stop (v4l2object);
+  gst_v4l2_video_overlay_stop (v4l2object);
 #endif
 
   if (!gst_v4l2_close (v4l2object))
@@ -1147,97 +1187,22 @@ gst_v4l2_object_v4l2fourcc_to_structure (guint32 fourcc)
     case V4L2_PIX_FMT_JPEG:    /* JFIF JPEG */
       structure = gst_structure_new ("image/jpeg", NULL);
       break;
+    case V4L2_PIX_FMT_YYUV:    /* 16  YUV 4:2:2     */
+    case V4L2_PIX_FMT_HI240:   /*  8  8-bit color   */
+      /* FIXME: get correct fourccs here */
+      break;
     case V4L2_PIX_FMT_RGB332:
-    case V4L2_PIX_FMT_RGB555:
     case V4L2_PIX_FMT_RGB555X:
-    case V4L2_PIX_FMT_RGB565:
     case V4L2_PIX_FMT_RGB565X:
+      /* FIXME: get correct fourccs here */
+      break;
+    case V4L2_PIX_FMT_GREY:    /*  8  Greyscale     */
+    case V4L2_PIX_FMT_RGB555:
+    case V4L2_PIX_FMT_RGB565:
     case V4L2_PIX_FMT_RGB24:
     case V4L2_PIX_FMT_BGR24:
     case V4L2_PIX_FMT_RGB32:
-    case V4L2_PIX_FMT_BGR32:{
-      guint depth = 0, bpp = 0;
-
-      gint endianness = 0;
-
-      guint32 r_mask = 0, b_mask = 0, g_mask = 0;
-
-      switch (fourcc) {
-        case V4L2_PIX_FMT_RGB332:
-          bpp = depth = 8;
-          endianness = G_BYTE_ORDER;    /* 'like, whatever' */
-          r_mask = 0xe0;
-          g_mask = 0x1c;
-          b_mask = 0x03;
-          break;
-        case V4L2_PIX_FMT_RGB555:
-        case V4L2_PIX_FMT_RGB555X:
-          bpp = 16;
-          depth = 15;
-          endianness =
-              fourcc == V4L2_PIX_FMT_RGB555X ? G_BIG_ENDIAN : G_LITTLE_ENDIAN;
-          r_mask = 0x7c00;
-          g_mask = 0x03e0;
-          b_mask = 0x001f;
-          break;
-        case V4L2_PIX_FMT_RGB565:
-        case V4L2_PIX_FMT_RGB565X:
-          bpp = depth = 16;
-          endianness =
-              fourcc == V4L2_PIX_FMT_RGB565X ? G_BIG_ENDIAN : G_LITTLE_ENDIAN;
-          r_mask = 0xf800;
-          g_mask = 0x07e0;
-          b_mask = 0x001f;
-          break;
-        case V4L2_PIX_FMT_RGB24:
-          bpp = depth = 24;
-          endianness = G_BIG_ENDIAN;
-          r_mask = 0xff0000;
-          g_mask = 0x00ff00;
-          b_mask = 0x0000ff;
-          break;
-        case V4L2_PIX_FMT_BGR24:
-          bpp = depth = 24;
-          endianness = G_BIG_ENDIAN;
-          r_mask = 0x0000ff;
-          g_mask = 0x00ff00;
-          b_mask = 0xff0000;
-          break;
-        case V4L2_PIX_FMT_RGB32:
-          bpp = depth = 32;
-          endianness = G_BIG_ENDIAN;
-          r_mask = 0xff000000;
-          g_mask = 0x00ff0000;
-          b_mask = 0x0000ff00;
-          break;
-        case V4L2_PIX_FMT_BGR32:
-          bpp = depth = 32;
-          endianness = G_BIG_ENDIAN;
-          r_mask = 0x000000ff;
-          g_mask = 0x0000ff00;
-          b_mask = 0x00ff0000;
-          break;
-        default:
-          g_assert_not_reached ();
-          break;
-      }
-      structure = gst_structure_new ("video/x-raw-rgb",
-          "bpp", G_TYPE_INT, bpp,
-          "depth", G_TYPE_INT, depth,
-          "red_mask", G_TYPE_INT, r_mask,
-          "green_mask", G_TYPE_INT, g_mask,
-          "blue_mask", G_TYPE_INT, b_mask,
-          "endianness", G_TYPE_INT, endianness, NULL);
-      break;
-    }
-    case V4L2_PIX_FMT_GREY:    /*  8  Greyscale     */
-      structure = gst_structure_new ("video/x-raw-gray",
-          "bpp", G_TYPE_INT, 8, NULL);
-      break;
-    case V4L2_PIX_FMT_YYUV:    /* 16  YUV 4:2:2     */
-    case V4L2_PIX_FMT_HI240:   /*  8  8-bit color   */
-      /* FIXME: get correct fourccs here */
-      break;
+    case V4L2_PIX_FMT_BGR32:
     case V4L2_PIX_FMT_NV12:    /* 12  Y/CbCr 4:2:0  */
     case V4L2_PIX_FMT_NV21:    /* 12  Y/CrCb 4:2:0  */
     case V4L2_PIX_FMT_YVU410:
@@ -1246,59 +1211,84 @@ gst_v4l2_object_v4l2fourcc_to_structure (guint32 fourcc)
     case V4L2_PIX_FMT_YUYV:
     case V4L2_PIX_FMT_YVU420:
     case V4L2_PIX_FMT_UYVY:
+#if 0
     case V4L2_PIX_FMT_Y41P:
+#endif
     case V4L2_PIX_FMT_YUV422P:
 #ifdef V4L2_PIX_FMT_YVYU
     case V4L2_PIX_FMT_YVYU:
 #endif
     case V4L2_PIX_FMT_YUV411P:{
-      guint32 fcc = 0;
+      GstVideoFormat format;
 
       switch (fourcc) {
+        case V4L2_PIX_FMT_GREY:        /*  8  Greyscale     */
+          format = GST_VIDEO_FORMAT_GRAY8;
+          break;
+        case V4L2_PIX_FMT_RGB555:
+          format = GST_VIDEO_FORMAT_RGB15;
+          break;
+        case V4L2_PIX_FMT_RGB565:
+          format = GST_VIDEO_FORMAT_RGB16;
+          break;
+        case V4L2_PIX_FMT_RGB24:
+          format = GST_VIDEO_FORMAT_RGB;
+          break;
+        case V4L2_PIX_FMT_BGR24:
+          format = GST_VIDEO_FORMAT_BGR;
+          break;
+        case V4L2_PIX_FMT_RGB32:
+          format = GST_VIDEO_FORMAT_RGBx;
+          break;
+        case V4L2_PIX_FMT_BGR32:
+          format = GST_VIDEO_FORMAT_BGRx;
+          break;
         case V4L2_PIX_FMT_NV12:
-          fcc = GST_MAKE_FOURCC ('N', 'V', '1', '2');
+          format = GST_VIDEO_FORMAT_NV12;
           break;
         case V4L2_PIX_FMT_NV21:
-          fcc = GST_MAKE_FOURCC ('N', 'V', '2', '1');
+          format = GST_VIDEO_FORMAT_NV21;
           break;
         case V4L2_PIX_FMT_YVU410:
-          fcc = GST_MAKE_FOURCC ('Y', 'V', 'U', '9');
+          format = GST_VIDEO_FORMAT_YVU9;
           break;
         case V4L2_PIX_FMT_YUV410:
-          fcc = GST_MAKE_FOURCC ('Y', 'U', 'V', '9');
+          format = GST_VIDEO_FORMAT_YUV9;
           break;
         case V4L2_PIX_FMT_YUV420:
-          fcc = GST_MAKE_FOURCC ('I', '4', '2', '0');
+          format = GST_VIDEO_FORMAT_I420;
           break;
         case V4L2_PIX_FMT_YUYV:
-          fcc = GST_MAKE_FOURCC ('Y', 'U', 'Y', '2');
+          format = GST_VIDEO_FORMAT_YUY2;
           break;
         case V4L2_PIX_FMT_YVU420:
-          fcc = GST_MAKE_FOURCC ('Y', 'V', '1', '2');
+          format = GST_VIDEO_FORMAT_YV12;
           break;
         case V4L2_PIX_FMT_UYVY:
-          fcc = GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y');
+          format = GST_VIDEO_FORMAT_UYVY;
           break;
+#if 0
         case V4L2_PIX_FMT_Y41P:
-          fcc = GST_MAKE_FOURCC ('Y', '4', '1', 'P');
+          format = GST_VIDEO_FORMAT_Y41P;
           break;
+#endif
         case V4L2_PIX_FMT_YUV411P:
-          fcc = GST_MAKE_FOURCC ('Y', '4', '1', 'B');
+          format = GST_VIDEO_FORMAT_Y41B;
           break;
         case V4L2_PIX_FMT_YUV422P:
-          fcc = GST_MAKE_FOURCC ('Y', '4', '2', 'B');
+          format = GST_VIDEO_FORMAT_Y42B;
           break;
 #ifdef V4L2_PIX_FMT_YVYU
         case V4L2_PIX_FMT_YVYU:
-          fcc = GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U');
+          format = GST_VIDEO_FORMAT_YVYU;
           break;
 #endif
         default:
           g_assert_not_reached ();
           break;
       }
-      structure = gst_structure_new ("video/x-raw-yuv",
-          "format", GST_TYPE_FOURCC, fcc, NULL);
+      structure = gst_structure_new ("video/x-raw",
+          "format", G_TYPE_STRING, gst_video_format_to_string (format), NULL);
       break;
     }
     case V4L2_PIX_FMT_DV:
@@ -1379,160 +1369,180 @@ gst_v4l2_object_get_all_caps (void)
  * @fps_n/@fps_d: location for framerate
  * @size: location for expected size of the frame or 0 if unknown
  */
-gboolean
+static gboolean
 gst_v4l2_object_get_caps_info (GstV4l2Object * v4l2object, GstCaps * caps,
-    struct v4l2_fmtdesc ** format, gint * w, gint * h,
-    gboolean * interlaced, guint * fps_n, guint * fps_d, guint * size)
+    struct v4l2_fmtdesc **format, GstVideoInfo * info)
 {
   GstStructure *structure;
-  const GValue *framerate;
   guint32 fourcc;
   const gchar *mimetype;
-  guint outsize;
+  struct v4l2_fmtdesc *fmt;
 
   /* default unknown values */
   fourcc = 0;
-  outsize = 0;
 
   structure = gst_caps_get_structure (caps, 0);
 
   mimetype = gst_structure_get_name (structure);
 
-  if (strcmp (mimetype, "video/mpegts") == 0) {
-    fourcc = V4L2_PIX_FMT_MPEG;
-    *fps_n = 0;
-    *fps_d = 1;
-    goto done;
-  }
-
-  if (!gst_structure_get_int (structure, "width", w))
-    return FALSE;
-
-  if (!gst_structure_get_int (structure, "height", h))
-    return FALSE;
-
-  if (!gst_structure_get_boolean (structure, "interlaced", interlaced))
-    *interlaced = FALSE;
+  if (g_str_equal (mimetype, "video/x-raw")) {
+    /* raw caps, parse into video info */
+    if (!gst_video_info_from_caps (info, caps))
+      goto invalid_format;
 
-  framerate = gst_structure_get_value (structure, "framerate");
-  if (!framerate)
-    return FALSE;
-
-  *fps_n = gst_value_get_fraction_numerator (framerate);
-  *fps_d = gst_value_get_fraction_denominator (framerate);
-
-  if (!strcmp (mimetype, "video/x-raw-yuv")) {
-    gst_structure_get_fourcc (structure, "format", &fourcc);
-
-    switch (fourcc) {
-      case GST_MAKE_FOURCC ('I', '4', '2', '0'):
-      case GST_MAKE_FOURCC ('I', 'Y', 'U', 'V'):
+    switch (GST_VIDEO_INFO_FORMAT (info)) {
+      case GST_VIDEO_FORMAT_I420:
         fourcc = V4L2_PIX_FMT_YUV420;
-        outsize = GST_ROUND_UP_4 (*w) * GST_ROUND_UP_2 (*h);
-        outsize += 2 * ((GST_ROUND_UP_8 (*w) / 2) * (GST_ROUND_UP_2 (*h) / 2));
         break;
-      case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
+      case GST_VIDEO_FORMAT_YUY2:
         fourcc = V4L2_PIX_FMT_YUYV;
-        outsize = (GST_ROUND_UP_2 (*w) * 2) * *h;
         break;
-      case GST_MAKE_FOURCC ('Y', '4', '1', 'P'):
+#if 0
+      case GST_VIDEO_FORMAT_Y41P:
         fourcc = V4L2_PIX_FMT_Y41P;
-        outsize = (GST_ROUND_UP_2 (*w) * 2) * *h;
         break;
-      case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
+#endif
+      case GST_VIDEO_FORMAT_UYVY:
         fourcc = V4L2_PIX_FMT_UYVY;
-        outsize = (GST_ROUND_UP_2 (*w) * 2) * *h;
         break;
-      case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):
+      case GST_VIDEO_FORMAT_YV12:
         fourcc = V4L2_PIX_FMT_YVU420;
-        outsize = GST_ROUND_UP_4 (*w) * GST_ROUND_UP_2 (*h);
-        outsize += 2 * ((GST_ROUND_UP_8 (*w) / 2) * (GST_ROUND_UP_2 (*h) / 2));
         break;
-      case GST_MAKE_FOURCC ('Y', '4', '1', 'B'):
+      case GST_VIDEO_FORMAT_Y41B:
         fourcc = V4L2_PIX_FMT_YUV411P;
-        outsize = GST_ROUND_UP_4 (*w) * *h;
-        outsize += 2 * ((GST_ROUND_UP_8 (*w) / 4) * *h);
         break;
-      case GST_MAKE_FOURCC ('Y', '4', '2', 'B'):
+      case GST_VIDEO_FORMAT_Y42B:
         fourcc = V4L2_PIX_FMT_YUV422P;
-        outsize = GST_ROUND_UP_4 (*w) * *h;
-        outsize += 2 * ((GST_ROUND_UP_8 (*w) / 2) * *h);
         break;
-      case GST_MAKE_FOURCC ('N', 'V', '1', '2'):
+      case GST_VIDEO_FORMAT_NV12:
         fourcc = V4L2_PIX_FMT_NV12;
-        outsize = GST_ROUND_UP_4 (*w) * GST_ROUND_UP_2 (*h);
-        outsize += (GST_ROUND_UP_4 (*w) * *h) / 2;
         break;
-      case GST_MAKE_FOURCC ('N', 'V', '2', '1'):
+      case GST_VIDEO_FORMAT_NV21:
         fourcc = V4L2_PIX_FMT_NV21;
-        outsize = GST_ROUND_UP_4 (*w) * GST_ROUND_UP_2 (*h);
-        outsize += (GST_ROUND_UP_4 (*w) * *h) / 2;
         break;
 #ifdef V4L2_PIX_FMT_YVYU
-      case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'):
+      case GST_VIDEO_FORMAT_YVYU:
         fourcc = V4L2_PIX_FMT_YVYU;
-        outsize = (GST_ROUND_UP_2 (*w) * 2) * *h;
         break;
 #endif
-    }
-  } else if (!strcmp (mimetype, "video/x-raw-rgb")) {
-    gint depth, endianness, r_mask;
-
-    gst_structure_get_int (structure, "depth", &depth);
-    gst_structure_get_int (structure, "endianness", &endianness);
-    gst_structure_get_int (structure, "red_mask", &r_mask);
-
-    switch (depth) {
-      case 8:
-        fourcc = V4L2_PIX_FMT_RGB332;
+      case GST_VIDEO_FORMAT_RGB15:
+        fourcc = V4L2_PIX_FMT_RGB555;
+        break;
+      case GST_VIDEO_FORMAT_RGB16:
+        fourcc = V4L2_PIX_FMT_RGB565;
         break;
-      case 15:
-        fourcc = (endianness == G_LITTLE_ENDIAN) ?
-            V4L2_PIX_FMT_RGB555 : V4L2_PIX_FMT_RGB555X;
+      case GST_VIDEO_FORMAT_RGB:
+        fourcc = V4L2_PIX_FMT_RGB24;
         break;
-      case 16:
-        fourcc = (endianness == G_LITTLE_ENDIAN) ?
-            V4L2_PIX_FMT_RGB565 : V4L2_PIX_FMT_RGB565X;
+      case GST_VIDEO_FORMAT_BGR:
+        fourcc = V4L2_PIX_FMT_BGR24;
         break;
-      case 24:
-        fourcc = (r_mask == 0xFF) ? V4L2_PIX_FMT_BGR24 : V4L2_PIX_FMT_RGB24;
+      case GST_VIDEO_FORMAT_RGBx:
+      case GST_VIDEO_FORMAT_RGBA:
+        fourcc = V4L2_PIX_FMT_RGB32;
         break;
-      case 32:
-        fourcc = (r_mask == 0xFF) ? V4L2_PIX_FMT_BGR32 : V4L2_PIX_FMT_RGB32;
+      case GST_VIDEO_FORMAT_BGRx:
+      case GST_VIDEO_FORMAT_BGRA:
+        fourcc = V4L2_PIX_FMT_BGR32;
+        break;
+      case GST_VIDEO_FORMAT_GRAY8:
+        fourcc = V4L2_PIX_FMT_GREY;
+      default:
         break;
     }
-  } else if (strcmp (mimetype, "video/x-dv") == 0) {
-    fourcc = V4L2_PIX_FMT_DV;
-  } else if (strcmp (mimetype, "image/jpeg") == 0) {
-    fourcc = V4L2_PIX_FMT_JPEG;
+  } else {
+    gboolean dimensions = TRUE;
+
+    /* no video caps, construct videoinfo ourselves */
+    gst_video_info_init (info);
+
+    if (g_str_equal (mimetype, "video/mpegts")) {
+      fourcc = V4L2_PIX_FMT_MPEG;
+      dimensions = FALSE;
+    } else if (g_str_equal (mimetype, "video/x-dv")) {
+      fourcc = V4L2_PIX_FMT_DV;
+    } else if (g_str_equal (mimetype, "image/jpeg")) {
+      fourcc = V4L2_PIX_FMT_JPEG;
 #ifdef V4L2_PIX_FMT_SBGGR8
-  } else if (strcmp (mimetype, "video/x-raw-bayer") == 0) {
-    fourcc = V4L2_PIX_FMT_SBGGR8;
+    } else if (g_str_equal (mimetype, "video/x-raw-bayer")) {
+      fourcc = V4L2_PIX_FMT_SBGGR8;
 #endif
 #ifdef V4L2_PIX_FMT_SN9C10X
-  } else if (strcmp (mimetype, "video/x-sonix") == 0) {
-    fourcc = V4L2_PIX_FMT_SN9C10X;
+    } else if (g_str_equal (mimetype, "video/x-sonix")) {
+      fourcc = V4L2_PIX_FMT_SN9C10X;
 #endif
 #ifdef V4L2_PIX_FMT_PWC1
-  } else if (strcmp (mimetype, "video/x-pwc1") == 0) {
-    fourcc = V4L2_PIX_FMT_PWC1;
+    } else if (g_str_equal (mimetype, "video/x-pwc1")) {
+      fourcc = V4L2_PIX_FMT_PWC1;
 #endif
 #ifdef V4L2_PIX_FMT_PWC2
-  } else if (strcmp (mimetype, "video/x-pwc2") == 0) {
-    fourcc = V4L2_PIX_FMT_PWC2;
+    } else if (g_str_equal (mimetype, "video/x-pwc2")) {
+      fourcc = V4L2_PIX_FMT_PWC2;
+    }
 #endif
-  } else if (strcmp (mimetype, "video/x-raw-gray") == 0) {
-    fourcc = V4L2_PIX_FMT_GREY;
+
+    if (dimensions) {
+      gboolean interlaced;
+
+      if (!gst_structure_get_int (structure, "width", &info->width))
+        goto no_width;
+
+      if (!gst_structure_get_int (structure, "height", &info->height))
+        goto no_height;
+
+      if (!gst_structure_get_boolean (structure, "interlaced", &interlaced))
+        interlaced = FALSE;
+      if (interlaced)
+        info->flags |= GST_VIDEO_FLAG_INTERLACED;
+
+      if (!gst_structure_get_fraction (structure, "framerate", &info->fps_n,
+              &info->fps_d))
+        goto no_framerate;
+    }
   }
 
   if (fourcc == 0)
-    return FALSE;
+    goto unhandled_format;
 
-done:
-  *format = gst_v4l2_object_get_format_from_fourcc (v4l2object, fourcc);
-  *size = outsize;
+  fmt = gst_v4l2_object_get_format_from_fourcc (v4l2object, fourcc);
+  if (fmt == NULL)
+    goto unsupported_format;
+
+  *format = fmt;
 
   return TRUE;
+
+  /* ERRORS */
+no_width:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "no width");
+    return FALSE;
+  }
+no_height:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "no height");
+    return FALSE;
+  }
+no_framerate:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "no framerate");
+    return FALSE;
+  }
+invalid_format:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "invalid format");
+    return FALSE;
+  }
+unhandled_format:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "unhandled format");
+    return FALSE;
+  }
+unsupported_format:
+  {
+    GST_DEBUG_OBJECT (v4l2object, "unsupported format");
+    return FALSE;
+  }
 }
 
 
@@ -2092,16 +2102,102 @@ error:
   return FALSE;
 }
 
+static gboolean
+gst_v4l2_object_setup_pool (GstV4l2Object * v4l2object, GstCaps * caps)
+{
+  GstV4l2IOMode mode;
+
+  GST_DEBUG_OBJECT (v4l2object->element, "initializing the capture system");
+
+  GST_V4L2_CHECK_OPEN (v4l2object);
+  GST_V4L2_CHECK_NOT_ACTIVE (v4l2object);
+
+  /* find transport */
+  mode = v4l2object->req_mode;
+
+  if (v4l2object->vcap.capabilities & V4L2_CAP_READWRITE) {
+    if (v4l2object->req_mode == GST_V4L2_IO_AUTO)
+      mode = GST_V4L2_IO_RW;
+  } else if (v4l2object->req_mode == GST_V4L2_IO_RW)
+    goto method_not_supported;
+
+  if (v4l2object->vcap.capabilities & V4L2_CAP_STREAMING) {
+    if (v4l2object->req_mode == GST_V4L2_IO_AUTO)
+      mode = GST_V4L2_IO_MMAP;
+  } else if (v4l2object->req_mode == GST_V4L2_IO_MMAP)
+    goto method_not_supported;
+
+  /* if still no transport selected, error out */
+  if (mode == GST_V4L2_IO_AUTO)
+    goto no_supported_capture_method;
+
+  GST_INFO_OBJECT (v4l2object->element, "accessing buffers via mode %d", mode);
+  v4l2object->mode = mode;
+
+  /* Map the buffers */
+  GST_LOG_OBJECT (v4l2object->element, "initiating buffer pool");
+
+  if (!(v4l2object->pool = gst_v4l2_buffer_pool_new (v4l2object, caps)))
+    goto buffer_pool_new_failed;
+
+  GST_V4L2_SET_ACTIVE (v4l2object);
+
+  return TRUE;
+
+  /* ERRORS */
+buffer_pool_new_failed:
+  {
+    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ,
+        (_("Could not map buffers from device '%s'"),
+            v4l2object->videodev),
+        ("Failed to create buffer pool: %s", g_strerror (errno)));
+    return FALSE;
+  }
+method_not_supported:
+  {
+    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ,
+        (_("The driver of device '%s' does not support the IO method %d"),
+            v4l2object->videodev, mode), (NULL));
+    return FALSE;
+  }
+no_supported_capture_method:
+  {
+    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ,
+        (_("The driver of device '%s' does not support any known IO "
+                "method."), v4l2object->videodev), (NULL));
+    return FALSE;
+  }
+}
+
+
+/* Note about fraction simplification
+ *  * n1/d1 == n2/d2  is also written as  n1 == ( n2 * d1 ) / d2
+ *   */
+#define fractions_are_equal(n1,d1,n2,d2) ((n1) == gst_util_uint64_scale_int((n2), (d1), (d2)))
 
 gboolean
-gst_v4l2_object_set_format (GstV4l2Object * v4l2object, guint32 pixelformat,
-    guint32 width, guint32 height, gboolean interlaced)
+gst_v4l2_object_set_format (GstV4l2Object * v4l2object, GstCaps * caps)
 {
   gint fd = v4l2object->video_fd;
   struct v4l2_format format;
+  struct v4l2_streamparm streamparm;
   enum v4l2_field field;
-
-  if (interlaced) {
+  guint32 pixelformat;
+  struct v4l2_fmtdesc *fmtdesc;
+  GstVideoInfo info;
+  gint width, height, fps_n, fps_d, stride;
+
+  if (!gst_v4l2_object_get_caps_info (v4l2object, caps, &fmtdesc, &info))
+    goto invalid_caps;
+
+  pixelformat = fmtdesc->pixelformat;
+  width = GST_VIDEO_INFO_WIDTH (&info);
+  height = GST_VIDEO_INFO_HEIGHT (&info);
+  fps_n = GST_VIDEO_INFO_FPS_N (&info);
+  fps_d = GST_VIDEO_INFO_FPS_D (&info);
+  stride = GST_VIDEO_INFO_PLANE_STRIDE (&info, 0);
+
+  if (info.flags & GST_VIDEO_FLAG_INTERLACED) {
     GST_DEBUG_OBJECT (v4l2object->element, "interlaced video");
     /* ideally we would differentiate between types of interlaced video
      * but there is not sufficient information in the caps..
@@ -2112,8 +2208,9 @@ gst_v4l2_object_set_format (GstV4l2Object * v4l2object, guint32 pixelformat,
     field = V4L2_FIELD_NONE;
   }
 
-  GST_DEBUG_OBJECT (v4l2object->element, "Setting format to %dx%d, format "
-      "%" GST_FOURCC_FORMAT, width, height, GST_FOURCC_ARGS (pixelformat));
+  GST_DEBUG_OBJECT (v4l2object->element, "Desired format %dx%d, format "
+      "%" GST_FOURCC_FORMAT " stride: %d", width, height,
+      GST_FOURCC_ARGS (pixelformat), stride);
 
   GST_V4L2_CHECK_OPEN (v4l2object);
   GST_V4L2_CHECK_NOT_ACTIVE (v4l2object);
@@ -2121,7 +2218,7 @@ gst_v4l2_object_set_format (GstV4l2Object * v4l2object, guint32 pixelformat,
   /* Only unconditionally accept mpegts for sources */
   if ((v4l2object->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
       (pixelformat == GST_MAKE_FOURCC ('M', 'P', 'E', 'G')))
-    return TRUE;
+    goto done;
 
   memset (&format, 0x00, sizeof (struct v4l2_format));
   format.type = v4l2object->type;
@@ -2129,42 +2226,128 @@ gst_v4l2_object_set_format (GstV4l2Object * v4l2object, guint32 pixelformat,
   if (v4l2_ioctl (fd, VIDIOC_G_FMT, &format) < 0)
     goto get_fmt_failed;
 
-  if (format.type == v4l2object->type &&
-      format.fmt.pix.width == width &&
-      format.fmt.pix.height == height &&
-      format.fmt.pix.pixelformat == pixelformat &&
-      format.fmt.pix.field == field) {
-    /* Nothing to do. We want to succeed immediately
-     * here because setting the same format back
-     * can still fail due to EBUSY. By short-circuiting
-     * here, we allow pausing and re-playing pipelines
-     * with changed caps, as long as the changed caps
-     * do not change the webcam's format. Otherwise,
-     * any caps change would require us to go to NULL
-     * state to close the device and set format.
-     */
-    return TRUE;
+  GST_DEBUG_OBJECT (v4l2object->element, "Got format to %dx%d, format "
+      "%" GST_FOURCC_FORMAT " bytesperline %d, colorspace %d",
+      format.fmt.pix.width, format.fmt.pix.height,
+      GST_FOURCC_ARGS (format.fmt.pix.pixelformat), format.fmt.pix.bytesperline,
+      format.fmt.pix.colorspace);
+
+  if (format.type != v4l2object->type ||
+      format.fmt.pix.width != width ||
+      format.fmt.pix.height != height ||
+      format.fmt.pix.pixelformat != pixelformat ||
+      format.fmt.pix.field != field || format.fmt.pix.bytesperline != stride) {
+    /* something different, set the format */
+    GST_DEBUG_OBJECT (v4l2object->element, "Setting format to %dx%d, format "
+        "%" GST_FOURCC_FORMAT " bytesperline %d", width, height,
+        GST_FOURCC_ARGS (pixelformat), stride);
+
+    format.type = v4l2object->type;
+    format.fmt.pix.width = width;
+    format.fmt.pix.height = height;
+    format.fmt.pix.pixelformat = pixelformat;
+    format.fmt.pix.field = field;
+    /* try to ask our prefered stride */
+    format.fmt.pix.bytesperline = stride;
+
+    if (v4l2_ioctl (fd, VIDIOC_S_FMT, &format) < 0)
+      goto set_fmt_failed;
+
+    GST_DEBUG_OBJECT (v4l2object->element, "Got format to %dx%d, format "
+        "%" GST_FOURCC_FORMAT " stride %d", format.fmt.pix.width,
+        format.fmt.pix.height, GST_FOURCC_ARGS (format.fmt.pix.pixelformat),
+        format.fmt.pix.bytesperline);
+
+    if (format.fmt.pix.width != width || format.fmt.pix.height != height)
+      goto invalid_dimensions;
+
+    if (format.fmt.pix.pixelformat != pixelformat)
+      goto invalid_pixelformat;
   }
 
-  format.type = v4l2object->type;
-  format.fmt.pix.width = width;
-  format.fmt.pix.height = height;
-  format.fmt.pix.pixelformat = pixelformat;
-  format.fmt.pix.field = field;
+  /* figure out the frame layout */
+  v4l2object->bytesperline = format.fmt.pix.bytesperline;
+  v4l2object->sizeimage = format.fmt.pix.sizeimage;
+
+  GST_DEBUG_OBJECT (v4l2object->element, "Got sizeimage %u",
+      v4l2object->sizeimage);
+
+  /* Is there a reason we require the caller to always specify a framerate? */
+  GST_DEBUG_OBJECT (v4l2object->element, "Desired framerate: %u/%u", fps_n,
+      fps_d);
+
+  memset (&streamparm, 0x00, sizeof (struct v4l2_streamparm));
+  streamparm.type = v4l2object->type;
+
+  if (v4l2_ioctl (fd, VIDIOC_G_PARM, &streamparm) < 0)
+    goto get_parm_failed;
+
+  GST_VIDEO_INFO_FPS_N (&info) =
+      streamparm.parm.capture.timeperframe.denominator;
+  GST_VIDEO_INFO_FPS_D (&info) = streamparm.parm.capture.timeperframe.numerator;
+
+  if (v4l2object->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+    GST_DEBUG_OBJECT (v4l2object->element, "Got framerate: %u/%u",
+        streamparm.parm.capture.timeperframe.denominator,
+        streamparm.parm.capture.timeperframe.numerator);
+
+    /* We used to skip frame rate setup if the camera was already setup
+     * with the requested frame rate. This breaks some cameras though,
+     * causing them to not output data (several models of Thinkpad cameras
+     * have this problem at least).
+     * So, don't skip. */
+    GST_LOG_OBJECT (v4l2object->element, "Setting framerate to %u/%u", fps_n,
+        fps_d);
+    /* We want to change the frame rate, so check whether we can. Some cheap USB
+     * cameras don't have the capability */
+    if ((streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) == 0) {
+      GST_DEBUG_OBJECT (v4l2object->element,
+          "Not setting framerate (not supported)");
+      goto done;
+    }
+
+    /* Note: V4L2 wants the frame interval, we have the frame rate */
+    streamparm.parm.capture.timeperframe.numerator = fps_d;
+    streamparm.parm.capture.timeperframe.denominator = fps_n;
 
-  if (v4l2_ioctl (fd, VIDIOC_S_FMT, &format) < 0) {
-    goto set_fmt_failed;
+    /* some cheap USB cam's won't accept any change */
+    if (v4l2_ioctl (fd, VIDIOC_S_PARM, &streamparm) < 0)
+      goto set_parm_failed;
+
+    /* get new values */
+    fps_d = streamparm.parm.capture.timeperframe.numerator;
+    fps_n = streamparm.parm.capture.timeperframe.denominator;
+
+    GST_INFO_OBJECT (v4l2object->element, "Set framerate to %u/%u", fps_n,
+        fps_d);
+
+    GST_VIDEO_INFO_FPS_N (&info) = fps_n;
+    GST_VIDEO_INFO_FPS_D (&info) = fps_d;
   }
 
-  if (format.fmt.pix.width != width || format.fmt.pix.height != height)
-    goto invalid_dimensions;
+done:
+  /* if we have a framerate pre-calculate duration */
+  if (fps_n > 0 && fps_d > 0) {
+    v4l2object->duration = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
+  } else {
+    v4l2object->duration = GST_CLOCK_TIME_NONE;
+  }
+  v4l2object->info = info;
+  v4l2object->fmtdesc = fmtdesc;
 
-  if (format.fmt.pix.pixelformat != pixelformat)
-    goto invalid_pixelformat;
+  /* now configure ther pools */
+  if (!gst_v4l2_object_setup_pool (v4l2object, caps))
+    goto pool_failed;
 
   return TRUE;
 
   /* ERRORS */
+invalid_caps:
+  {
+    GST_DEBUG_OBJECT (v4l2object->element, "can't parse caps %" GST_PTR_FORMAT,
+        caps);
+    return FALSE;
+  }
 get_fmt_failed:
   {
     GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, SETTINGS,
@@ -2202,30 +2385,220 @@ invalid_pixelformat:
             GST_FOURCC_ARGS (format.fmt.pix.pixelformat)));
     return FALSE;
   }
+get_parm_failed:
+  {
+    /* it's possible that this call is not supported */
+    if (errno != EINVAL) {
+      GST_ELEMENT_WARNING (v4l2object->element, RESOURCE, SETTINGS,
+          (_("Could not get parameters on device '%s'"),
+              v4l2object->videodev), GST_ERROR_SYSTEM);
+    }
+    goto done;
+  }
+set_parm_failed:
+  {
+    GST_ELEMENT_WARNING (v4l2object->element, RESOURCE, SETTINGS,
+        (_("Video device did not accept new frame rate setting.")),
+        GST_ERROR_SYSTEM);
+    goto done;
+  }
+pool_failed:
+  {
+    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, SETTINGS,
+        (_("Video device could not create buffer pool.")), GST_ERROR_SYSTEM);
+    return FALSE;
+  }
 }
 
 gboolean
-gst_v4l2_object_start_streaming (GstV4l2Object * v4l2object)
+gst_v4l2_object_unlock (GstV4l2Object * v4l2object)
 {
-  if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_STREAMON,
-          &(v4l2object->type)) < 0) {
-    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, OPEN_READ,
-        (_("Error starting streaming on device '%s'."), v4l2object->videodev),
-        GST_ERROR_SYSTEM);
-    return FALSE;
+  GST_LOG_OBJECT (v4l2object->element, "flush poll");
+  gst_poll_set_flushing (v4l2object->poll, TRUE);
+
+  return TRUE;
+}
+
+gboolean
+gst_v4l2_object_unlock_stop (GstV4l2Object * v4l2object)
+{
+  GST_LOG_OBJECT (v4l2object->element, "flush stop poll");
+  gst_poll_set_flushing (v4l2object->poll, FALSE);
+
+  return TRUE;
+}
+
+gboolean
+gst_v4l2_object_stop (GstV4l2Object * v4l2object)
+{
+  GST_DEBUG_OBJECT (v4l2object->element, "stopping");
+
+  if (!GST_V4L2_IS_OPEN (v4l2object))
+    goto done;
+  if (!GST_V4L2_IS_ACTIVE (v4l2object))
+    goto done;
+
+  if (v4l2object->pool) {
+    GST_DEBUG_OBJECT (v4l2object->element, "deactivating pool");
+    gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (v4l2object->pool), FALSE);
+    gst_object_unref (v4l2object->pool);
+    v4l2object->pool = NULL;
   }
+
+  GST_V4L2_SET_INACTIVE (v4l2object);
+
+done:
   return TRUE;
 }
 
+#if 0
+static GstFlowReturn
+gst_v4l2_object_get_mmap (GstV4l2Object * v4l2object, GstBuffer ** buf)
+{
+  GstFlowReturn res;
+#define NUM_TRIALS 50
+  GstBufferPool *pool;
+  gint32 trials = NUM_TRIALS;
+  GstBuffer *pool_buffer;
+  gboolean need_copy;
+
+  pool = v4l2object->pool;
+  if (!pool)
+    goto no_buffer_pool;
+
+  GST_DEBUG_OBJECT (v4l2object->element, "grab frame");
+
+  for (;;) {
+    if ((res = gst_v4l2_object_poll (v4l2object)) != GST_FLOW_OK)
+      goto poll_error;
+
+    res = gst_buffer_pool_acquire_buffer (pool, &pool_buffer, NULL);
+    if (res != GST_FLOW_OK)
+      goto no_buffer;
+
+    if (v4l2object->size > 0) {
+      gsize size = gst_buffer_get_size (pool_buffer);
+
+      /* if size does not match what we expected, try again */
+      if (size != v4l2object->size) {
+        GST_ELEMENT_WARNING (v4l2object->element, RESOURCE, READ,
+            (_("Got unexpected frame size of %u instead of %u."),
+                size, v4l2object->size), (NULL));
+        gst_buffer_unref (pool_buffer);
+        goto no_buffer;
+      }
+    }
+    /* when we get here all is fine */
+    break;
+
+  no_buffer:
+    GST_WARNING_OBJECT (v4l2object->element, "trials=%d", trials);
+
+    /* if the sync() got interrupted, we can retry */
+    switch (errno) {
+      case EINVAL:
+      case ENOMEM:
+        /* fatal */
+        return GST_FLOW_ERROR;
+
+      case EAGAIN:
+      case EIO:
+      case EINTR:
+      default:
+        /* try again, until too many trials */
+        break;
+    }
+
+    /* check nr. of attempts to capture */
+    if (--trials == -1) {
+      goto too_many_trials;
+    }
+  }
+
+
+  /* if we are handing out the last buffer in the pool, we need to make a
+   * copy and bring the buffer back in the pool. */
+  need_copy = v4l2object->always_copy
+      || !gst_v4l2_buffer_pool_available_buffers (pool);
+
+  if (G_UNLIKELY (need_copy)) {
+    if (!v4l2object->always_copy) {
+      GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, v4l2object->element,
+          "running out of buffers, making a copy to reuse current one");
+    }
+    *buf = gst_buffer_copy (pool_buffer);
+    /* this will requeue */
+    gst_buffer_unref (pool_buffer);
+  } else {
+    *buf = pool_buffer;
+  }
+
+  return GST_FLOW_OK;
+
+  /* ERRORS */
+no_buffer_pool:
+  {
+    GST_DEBUG_OBJECT (v4l2object->element, "no buffer pool");
+    return GST_FLOW_WRONG_STATE;
+  }
+poll_error:
+  {
+    return res;
+  }
+too_many_trials:
+  {
+    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, FAILED,
+        (_("Failed trying to get video frames from device '%s'."),
+            v4l2object->videodev),
+        (_("Failed after %d tries. device %s. system error: %s"),
+            NUM_TRIALS, v4l2object->videodev, g_strerror (errno)));
+    return GST_FLOW_ERROR;
+  }
+}
+#endif
+
 gboolean
-gst_v4l2_object_stop_streaming (GstV4l2Object * v4l2object)
+gst_v4l2_object_copy (GstV4l2Object * v4l2object, GstBuffer * dest,
+    GstBuffer * src)
 {
-  if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_STREAMOFF,
-          &(v4l2object->type)) < 0) {
-    GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, OPEN_READ,
-        (_("Error stopping streaming on device '%s'."), v4l2object->videodev),
-        GST_ERROR_SYSTEM);
-    return FALSE;
+  guint8 *data;
+  gsize size;
+
+  if (v4l2object->info.finfo) {
+    GstVideoFrame src_frame, dest_frame;
+
+    GST_DEBUG_OBJECT (v4l2object->element, "copy video frame");
+
+    /* we have raw video, use videoframe copy to get strides right */
+    if (!gst_video_frame_map (&src_frame, &v4l2object->info, src, GST_MAP_READ))
+      goto invalid_buffer;
+
+    if (!gst_video_frame_map (&dest_frame, &v4l2object->info, dest,
+            GST_MAP_WRITE)) {
+      gst_video_frame_unmap (&src_frame);
+      goto invalid_buffer;
+    }
+
+    gst_video_frame_copy (&dest_frame, &src_frame);
+
+    gst_video_frame_unmap (&src_frame);
+    gst_video_frame_unmap (&dest_frame);
+  } else {
+    GST_DEBUG_OBJECT (v4l2object->element, "copy raw bytes");
+    data = gst_buffer_map (src, &size, NULL, GST_MAP_READ);
+    gst_buffer_fill (dest, 0, data, size);
+    gst_buffer_unmap (src, data, size);
   }
+  GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, v4l2object->element,
+      "slow copy into buffer %p", dest);
+
   return TRUE;
+
+  /* ERRORS */
+invalid_buffer:
+  {
+    /* No Window available to put our image into */
+    GST_WARNING_OBJECT (v4l2object->element, "could not map image");
+    return FALSE;
+  }
 }