Port/rewrite videocrop from scratch for GStreamer-0.10, and make it support all forma...
authorTim-Philipp Müller <tim@centricular.net>
Sat, 2 Sep 2006 15:30:45 +0000 (15:30 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Sat, 2 Sep 2006 15:30:45 +0000 (15:30 +0000)
Original commit message from CVS:
* configure.ac:
* gst/videocrop/Makefile.am:
* gst/videocrop/gstvideocrop.c: (gst_video_crop_base_init),
(gst_video_crop_class_init), (gst_video_crop_init),
(gst_video_crop_get_image_details_from_caps),
(gst_video_crop_get_unit_size), (gst_video_crop_transform_packed),
(gst_video_crop_transform_planar), (gst_video_crop_transform),
(gst_video_crop_transform_dimension),
(gst_video_crop_transform_dimension_value),
(gst_video_crop_transform_caps), (gst_video_crop_set_caps),
(gst_video_crop_set_property), (gst_video_crop_get_property),
(plugin_init):
Port/rewrite videocrop from scratch for GStreamer-0.10, and make
it support all formats videoscale supports (#345653).

ChangeLog
configure.ac
gst/videocrop/Makefile.am
gst/videocrop/gstvideocrop.c

index 8cec581c1fb7bfadb78b81287e8cd3926785ad27..5bc084cbd9453b68aa973113427b21cf5d6a9602 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2006-09-02  Tim-Philipp Müller  <tim at centricular dot net>
+
+       * configure.ac:
+       * gst/videocrop/Makefile.am:
+       * gst/videocrop/gstvideocrop.c: (gst_video_crop_base_init),
+       (gst_video_crop_class_init), (gst_video_crop_init),
+       (gst_video_crop_get_image_details_from_caps),
+       (gst_video_crop_get_unit_size), (gst_video_crop_transform_packed),
+       (gst_video_crop_transform_planar), (gst_video_crop_transform),
+       (gst_video_crop_transform_dimension),
+       (gst_video_crop_transform_dimension_value),
+       (gst_video_crop_transform_caps), (gst_video_crop_set_caps),
+       (gst_video_crop_set_property), (gst_video_crop_get_property),
+       (plugin_init):
+         Port/rewrite videocrop from scratch for GStreamer-0.10, and make
+         it support all formats videoscale supports (#345653).
+
 2006-09-02  Stefan Kost  <ensonic@users.sf.net>
 
        * sys/v4l2/gstv4l2.c:
index b10be5c30c058e80c273b3d57c28b2fc6bd16749..29bb7faba57b887559193e2f6cc8f45d5e55eb62 100644 (file)
@@ -85,8 +85,9 @@ GST_PLUGINS_ALL="\
   spectrum \
   speed \
   qtdemux \
-  xingheader \
   tta \
+  videocrop \
+  xingheader \
   "
 
 AC_SUBST(GST_PLUGINS_ALL)
@@ -848,6 +849,7 @@ gst/spectrum/Makefile
 gst/speed/Makefile
 gst/qtdemux/Makefile
 gst/tta/Makefile
+gst/videocrop/Makefile
 gst/xingheader/Makefile
 gst-libs/Makefile
 gst-libs/gst/Makefile
index 81c4199031cac0b8184d9542b650c09297f99664..6cf889aab585725331bcc3b3af3025d69fd0a31b 100644 (file)
@@ -1,9 +1,12 @@
-
 plugin_LTLIBRARIES = libgstvideocrop.la
 
+# Note: we only use defines from gst/video/video.h, but none
+# of the functions, so we don't need to link to libgstvideo
+
 libgstvideocrop_la_SOURCES = gstvideocrop.c
-libgstvideocrop_la_CFLAGS = $(GST_CFLAGS)
-libgstvideocrop_la_LIBADD =
+libgstvideocrop_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+       $(GST_PLUGINS_BASE_CFLAGS) $(LIBOIL_CFLAGS)
+libgstvideocrop_la_LIBADD = $(GST_BASE_LIBS) $(LIBOIL_LIBS)
 libgstvideocrop_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
 noinst_HEADERS = 
index 12db0352916b08336bdecd6d0ac2d06fc917514e..72911d509878d8f58023cd21db419741011441e3 100644 (file)
@@ -1,5 +1,5 @@
-/* GStreamer
- * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+/* GStreamer video frame cropping
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  * Boston, MA 02111-1307, USA.
  */
 
+/**
+ * SECTION:element-videocrop
+ * @see_also: GstVideoBox
+ *
+ * <refsect2>
+ * <para>
+ * This element crops video frames, meaning it can remove parts of the
+ * picture on the left, right, top or bottom of the picture and output
+ * a smaller picture than the input picture, with the unwanted parts at the
+ * border removed.
+ * </para>
+ * <para>
+ * The videocrop element is similar to the videobox element, but its main
+ * goal is to support a multitude of formats as efficiently as possible.
+ * Unlike videbox, it cannot add borders to the picture and unlike videbox
+ * it will always output images in exactly the same format as the input image.
+ * </para>
+ * <para>
+ * If there is nothing to crop, the element will operate in pass-through mode.
+ * </para>
+ * <title>Example launch line</title>
+ * <para>
+ * <programlisting>
+ * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
+ * </programlisting>
+ * </para>
+ * </refsect2>
+ */
+
+/* TODO:
+ *  - for packed formats, we could avoid memcpy() in case crop_left
+ *    and crop_right are 0 and just create a sub-buffer of the input
+ *    buffer
+ */
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
 #include <gst/gst.h>
 #include <gst/video/video.h>
-
+#include <gst/base/gstbasetransform.h>
+#include <liboil/liboil.h>
 #include <string.h>
 
+GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
+#define GST_CAT_DEFAULT videocrop_debug
+
 #define GST_TYPE_VIDEO_CROP \
   (gst_video_crop_get_type())
 #define GST_VIDEO_CROP(obj) \
   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_CROP,GstVideoCropClass))
 #define GST_IS_VIDEO_CROP(obj) \
   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_CROP))
-#define GST_IS_VIDEO_CROP_CLASS(obj) \
+#define GST_IS_VIDEO_CROP_CLASS(klass) \
   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_CROP))
 
+typedef struct _GstVideoCropImageDetails GstVideoCropImageDetails;
+struct _GstVideoCropImageDetails
+{
+  gboolean packed;              /* TRUE if packed, FALSE if planar */
+
+  guint width;
+  guint height;
+  guint size;
+
+  /* for packed RGB and YUV */
+  guint stride;
+  guint bytes_per_pixel;
+
+  /* for planar YUV */
+  guint y_stride, y_off;
+  guint u_stride, u_off;
+  guint v_stride, v_off;
+};
+
 typedef struct _GstVideoCrop GstVideoCrop;
 typedef struct _GstVideoCropClass GstVideoCropClass;
 
 struct _GstVideoCrop
 {
-  GstElement element;
+  GstBaseTransform basetransform;
 
-  /* pads */
-  GstPad *sinkpad;
-  GstPad *srcpad;
+  gboolean noop;                /* TRUE if crop_left,_right,_top and _bottom are all 0 */
 
-  /* caps */
-  gint width, height;
-  gint crop_left, crop_right, crop_top, crop_bottom;
-  gboolean renegotiate_src_caps;
+  gint crop_left;
+  gint crop_right;
+  gint crop_top;
+  gint crop_bottom;
+
+  GstVideoCropImageDetails in;  /* details of input image */
+  GstVideoCropImageDetails out; /* details of output image */
 };
 
 struct _GstVideoCropClass
 {
-  GstElementClass parent_class;
+  GstBaseTransformClass basetransform_class;
 };
 
-/* elementfactory information */
-static const GstElementDetails gst_video_crop_details =
-GST_ELEMENT_DETAILS ("Crop",
+static const GstElementDetails video_crop_details = GST_ELEMENT_DETAILS ("Crop",
     "Filter/Effect/Video",
-    "Crops video into a user defined region",
-    "Wim Taymans <wim.taymans@chello.be>");
-
+    "Crops video into a user-defined region",
+    "Tim-Philipp Müller <tim centricular net>");
 
-/* VideoCrop args */
 enum
 {
   ARG_0,
@@ -74,497 +130,543 @@ enum
   ARG_RIGHT,
   ARG_TOP,
   ARG_BOTTOM
-      /* FILL ME */
 };
 
-static GstStaticPadTemplate gst_video_crop_src_template =
-GST_STATIC_PAD_TEMPLATE ("src",
+/* the formats we support */
+#define VIDEO_CROP_CAPS                          \
+  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 ";"                         \
+  GST_VIDEO_CAPS_BGR ";"                         \
+  GST_VIDEO_CAPS_YUV ("AYUV") ";"                \
+  GST_VIDEO_CAPS_YUV ("YUY2") ";"                \
+  GST_VIDEO_CAPS_YUV ("YVYU") ";"                \
+  GST_VIDEO_CAPS_YUV ("UYVY") ";"                \
+  GST_VIDEO_CAPS_YUV ("Y800") ";"                \
+  GST_VIDEO_CAPS_YUV ("I420") ";"                \
+  GST_VIDEO_CAPS_YUV ("YV12") ";"                \
+  GST_VIDEO_CAPS_RGB_16 ";"                      \
+  GST_VIDEO_CAPS_RGB_15
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
+    GST_STATIC_CAPS (VIDEO_CROP_CAPS)
     );
 
-static GstStaticPadTemplate gst_video_crop_sink_template =
-GST_STATIC_PAD_TEMPLATE ("sink",
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
+    GST_STATIC_CAPS (VIDEO_CROP_CAPS)
     );
 
-
-static void gst_video_crop_base_init (gpointer g_class);
-static void gst_video_crop_class_init (GstVideoCropClass * klass);
-static void gst_video_crop_init (GstVideoCrop * video_crop);
+GST_BOILERPLATE (GstVideoCrop, gst_video_crop, GstBaseTransform,
+    GST_TYPE_BASE_TRANSFORM);
 
 static void gst_video_crop_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 static void gst_video_crop_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
 
-static GstCaps *gst_video_crop_getcaps (GstPad * pad);
-
-static GstPadLinkReturn
-gst_video_crop_link (GstPad * pad, const GstCaps * caps);
-static void gst_video_crop_chain (GstPad * pad, GstData * _data);
-
-static GstStateChangeReturn gst_video_crop_change_state (GstElement * element,
-    GstStateChange transition);
-
-
-static GstElementClass *parent_class = NULL;
-
-/* static guint gst_video_crop_signals[LAST_SIGNAL] = { 0 }; */
-
-GType
-gst_video_crop_get_type (void)
-{
-  static GType video_crop_type = 0;
-
-  if (!video_crop_type) {
-    static const GTypeInfo video_crop_info = {
-      sizeof (GstVideoCropClass),
-      gst_video_crop_base_init,
-      NULL,
-      (GClassInitFunc) gst_video_crop_class_init,
-      NULL,
-      NULL,
-      sizeof (GstVideoCrop),
-      0,
-      (GInstanceInitFunc) gst_video_crop_init,
-    };
-
-    video_crop_type =
-        g_type_register_static (GST_TYPE_ELEMENT, "GstVideoCrop",
-        &video_crop_info, 0);
-  }
-  return video_crop_type;
-}
+static GstCaps *gst_video_crop_transform_caps (GstBaseTransform * trans,
+    GstPadDirection direction, GstCaps * caps);
+static GstFlowReturn gst_video_crop_transform (GstBaseTransform * trans,
+    GstBuffer * inbuf, GstBuffer * outbuf);
+static gboolean gst_video_crop_get_unit_size (GstBaseTransform * trans,
+    GstCaps * caps, guint * size);
+static gboolean gst_video_crop_set_caps (GstBaseTransform * trans,
+    GstCaps * in_caps, GstCaps * outcaps);
 
 static void
 gst_video_crop_base_init (gpointer g_class)
 {
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
 
-  gst_element_class_set_details (element_class, &gst_video_crop_details);
+  gst_element_class_set_details (element_class, &video_crop_details);
 
   gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&gst_video_crop_sink_template));
+      gst_static_pad_template_get (&sink_template));
   gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&gst_video_crop_src_template));
+      gst_static_pad_template_get (&src_template));
 }
+
 static void
 gst_video_crop_class_init (GstVideoCropClass * klass)
 {
   GObjectClass *gobject_class;
-  GstElementClass *gstelement_class;
+  GstBaseTransformClass *basetransform_class;
 
   gobject_class = (GObjectClass *) klass;
-  gstelement_class = (GstElementClass *) klass;
+  basetransform_class = (GstBaseTransformClass *) klass;
 
-  parent_class = g_type_class_peek_parent (klass);
+  gobject_class->set_property = gst_video_crop_set_property;
+  gobject_class->get_property = gst_video_crop_get_property;
 
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LEFT,
+  g_object_class_install_property (gobject_class, ARG_LEFT,
       g_param_spec_int ("left", "Left", "Pixels to crop at left",
           0, G_MAXINT, 0, G_PARAM_READWRITE));
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RIGHT,
+  g_object_class_install_property (gobject_class, ARG_RIGHT,
       g_param_spec_int ("right", "Right", "Pixels to crop at right",
           0, G_MAXINT, 0, G_PARAM_READWRITE));
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOP,
+  g_object_class_install_property (gobject_class, ARG_TOP,
       g_param_spec_int ("top", "Top", "Pixels to crop at top",
           0, G_MAXINT, 0, G_PARAM_READWRITE));
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BOTTOM,
+  g_object_class_install_property (gobject_class, ARG_BOTTOM,
       g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
           0, G_MAXINT, 0, G_PARAM_READWRITE));
 
-  gobject_class->set_property = gst_video_crop_set_property;
-  gobject_class->get_property = gst_video_crop_get_property;
+  basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_video_crop_transform);
+  basetransform_class->transform_caps =
+      GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
+  basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_crop_set_caps);
+  basetransform_class->get_unit_size =
+      GST_DEBUG_FUNCPTR (gst_video_crop_get_unit_size);
+
+  basetransform_class->passthrough_on_same_caps = TRUE;
 
-  gstelement_class->change_state = gst_video_crop_change_state;
+  oil_init ();
 }
 
 static void
-gst_video_crop_init (GstVideoCrop * video_crop)
+gst_video_crop_init (GstVideoCrop * vcrop, GstVideoCropClass * klass)
 {
-  /* create the sink and src pads */
-  video_crop->sinkpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&gst_video_crop_sink_template), "sink");
-  gst_element_add_pad (GST_ELEMENT (video_crop), video_crop->sinkpad);
-  gst_pad_set_chain_function (video_crop->sinkpad, gst_video_crop_chain);
-  gst_pad_set_getcaps_function (video_crop->sinkpad, gst_video_crop_getcaps);
-  gst_pad_set_link_function (video_crop->sinkpad, gst_video_crop_link);
-
-  video_crop->srcpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&gst_video_crop_src_template), "src");
-  gst_element_add_pad (GST_ELEMENT (video_crop), video_crop->srcpad);
-  gst_pad_set_getcaps_function (video_crop->srcpad, gst_video_crop_getcaps);
-  gst_pad_set_link_function (video_crop->srcpad, gst_video_crop_link);
-
-  video_crop->crop_right = 0;
-  video_crop->crop_left = 0;
-  video_crop->crop_top = 0;
-  video_crop->crop_bottom = 0;
+  vcrop->crop_right = 0;
+  vcrop->crop_left = 0;
+  vcrop->crop_top = 0;
+  vcrop->crop_bottom = 0;
+  vcrop->noop = TRUE;
 }
 
-/* do we need this function? */
-static void
-gst_video_crop_set_property (GObject * object, guint prop_id,
-    const GValue * value, GParamSpec * pspec)
+static gboolean
+gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
+    GstVideoCropImageDetails * details, GstCaps * caps)
 {
-  GstVideoCrop *video_crop;
+  GstStructure *structure;
+  gint width, height;
 
-  g_return_if_fail (GST_IS_VIDEO_CROP (object));
+  structure = gst_caps_get_structure (caps, 0);
+  if (!gst_structure_get_int (structure, "width", &width) ||
+      !gst_structure_get_int (structure, "height", &height)) {
+    goto incomplete_format;
+  }
 
-  video_crop = GST_VIDEO_CROP (object);
+  details->width = width;
+  details->height = height;
+
+  if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
+    gint bpp = 0;
+
+    if (!gst_structure_get_int (structure, "bpp", &bpp) || (bpp & 0x07) != 0)
+      goto incomplete_format;
+
+    details->packed = TRUE;
+    details->bytes_per_pixel = bpp / 8;
+    details->stride = GST_ROUND_UP_4 (width * details->bytes_per_pixel);
+    details->size = details->stride * height;
+  } else if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
+    guint32 format = 0;
+
+    if (!gst_structure_get_fourcc (structure, "format", &format))
+      goto incomplete_format;
+
+    switch (format) {
+      case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'):
+        details->packed = TRUE;
+        details->bytes_per_pixel = 4;
+        details->stride = GST_ROUND_UP_4 (width * 4);
+        details->size = details->stride * height;
+        break;
+      case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'):
+      case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
+      case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
+        details->packed = TRUE;
+        details->bytes_per_pixel = 2;
+        details->stride = GST_ROUND_UP_4 (width * 2);
+        details->size = details->stride * height;
+        break;
+      case GST_MAKE_FOURCC ('Y', '8', '0', '0'):
+        details->packed = TRUE;
+        details->bytes_per_pixel = 1;
+        details->stride = GST_ROUND_UP_4 (width);
+        details->size = details->stride * height;
+        break;
+      case GST_MAKE_FOURCC ('I', '4', '2', '0'):
+      case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):{
+        details->packed = FALSE;
+
+        details->y_stride = GST_ROUND_UP_4 (width);
+        details->u_stride = GST_ROUND_UP_8 (width) / 2;
+        details->v_stride = GST_ROUND_UP_8 (width) / 2;
+
+        /* I420 and YV12 have U/V planes swapped, but doesn't matter for us */
+        details->y_off = 0;
+        details->u_off = 0 + details->y_stride * GST_ROUND_UP_2 (height);
+        details->v_off = details->u_off +
+            details->u_stride * (GST_ROUND_UP_2 (height) / 2);
+        details->size = details->v_off +
+            details->v_stride * (GST_ROUND_UP_2 (height) / 2);
+        break;
+      }
+      default:
+        goto unknown_format;
+    }
+  } else {
+    goto unknown_format;
+  }
 
-  switch (prop_id) {
-    case ARG_LEFT:
-      video_crop->crop_left = g_value_get_int (value);
-      break;
-    case ARG_RIGHT:
-      video_crop->crop_right = g_value_get_int (value);
-      break;
-    case ARG_TOP:
-      video_crop->crop_top = g_value_get_int (value);
-      break;
-    case ARG_BOTTOM:
-      video_crop->crop_bottom = g_value_get_int (value);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
+  return TRUE;
+
+  /* ERRORS */
+unknown_format:
+  {
+    GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
+        ("Unsupported format"));
+    return FALSE;
   }
+
+incomplete_format:
+  {
+    GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
+        ("Incomplete caps, some required field is missing"));
+    return FALSE;
+  }
+}
+
+static gboolean
+gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
+    guint * size)
+{
+  GstVideoCropImageDetails img_details = { 0, };
+  GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
+
+  if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
+    return FALSE;
+
+  *size = img_details.size;
+  return TRUE;
 }
+
 static void
-gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
-    GParamSpec * pspec)
+gst_video_crop_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf,
+    GstBuffer * outbuf)
 {
-  GstVideoCrop *video_crop;
+  guint8 *in_data, *out_data;
+  guint i, dx;
 
-  g_return_if_fail (GST_IS_VIDEO_CROP (object));
+  in_data = GST_BUFFER_DATA (inbuf);
+  out_data = GST_BUFFER_DATA (outbuf);
 
-  video_crop = GST_VIDEO_CROP (object);
+  in_data += vcrop->crop_top * vcrop->in.stride;
+  in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel;
 
-  switch (prop_id) {
-    case ARG_LEFT:
-      g_value_set_int (value, video_crop->crop_left);
-      break;
-    case ARG_RIGHT:
-      g_value_set_int (value, video_crop->crop_right);
-      break;
-    case ARG_TOP:
-      g_value_set_int (value, video_crop->crop_top);
-      break;
-    case ARG_BOTTOM:
-      g_value_set_int (value, video_crop->crop_bottom);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-  }
+  dx = vcrop->out.width * vcrop->out.bytes_per_pixel;
 
-  if (gst_pad_is_negotiated (video_crop->srcpad))
-    video_crop->renegotiate_src_caps = TRUE;
+  for (i = 0; i < vcrop->out.height; ++i) {
+    oil_memcpy (out_data, in_data, dx);
+    in_data += vcrop->in.stride;
+    out_data += vcrop->out.stride;
+  }
 }
 
 static void
-gst_video_crop_add_to_struct_val (GstStructure * s, const gchar * field_name,
-    gint addval)
+gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
+    GstBuffer * outbuf)
 {
-  const GValue *val;
+  guint8 *y_out, *u_out, *v_out;
+  guint8 *y_in, *u_in, *v_in;
+  guint i, dx;
 
-  val = gst_structure_get_value (s, field_name);
+  /* Y plane */
+  y_in = GST_BUFFER_DATA (inbuf);
+  y_out = GST_BUFFER_DATA (outbuf);
 
-  if (G_VALUE_HOLDS_INT (val)) {
-    gint ival = g_value_get_int (val);
+  y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left;
+  dx = vcrop->out.width * 1;
 
-    gst_structure_set (s, field_name, G_TYPE_INT, ival + addval, NULL);
-    return;
+  for (i = 0; i < vcrop->out.height; ++i) {
+    oil_memcpy (y_out, y_in, dx);
+    y_in += vcrop->in.y_stride;
+    y_out += vcrop->out.y_stride;
   }
 
-  if (GST_VALUE_HOLDS_INT_RANGE (val)) {
-    gint min = gst_value_get_int_range_min (val);
-    gint max = gst_value_get_int_range_max (val);
+  /* U + V planes */
+  u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off;
+  u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off;
 
-    gst_structure_set (s, field_name, GST_TYPE_INT_RANGE, min + addval,
-        max + addval, NULL);
-    return;
-  }
+  u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride;
+  u_in += vcrop->crop_left / 2;
 
-  if (GST_VALUE_HOLDS_LIST (val)) {
-    GValue newlist = { 0, };
-    gint i;
+  v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off;
+  v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off;
 
-    g_value_init (&newlist, GST_TYPE_LIST);
-    for (i = 0; i < gst_value_list_get_size (val); ++i) {
-      GValue newval = { 0, };
-      g_value_init (&newval, G_VALUE_TYPE (val));
-      g_value_copy (val, &newval);
-      if (G_VALUE_HOLDS_INT (val)) {
-        gint ival = g_value_get_int (val);
-
-        g_value_set_int (&newval, ival + addval);
-      } else if (GST_VALUE_HOLDS_INT_RANGE (val)) {
-        gint min = gst_value_get_int_range_min (val);
-        gint max = gst_value_get_int_range_max (val);
-
-        gst_value_set_int_range (&newval, min + addval, max + addval);
-      } else {
-        g_return_if_reached ();
-      }
-      gst_value_list_append_value (&newlist, &newval);
-      g_value_unset (&newval);
-    }
-    gst_structure_set_value (s, field_name, &newlist);
-    g_value_unset (&newlist);
-    return;
-  }
+  v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride;
+  v_in += vcrop->crop_left / 2;
 
-  g_return_if_reached ();
+  dx = GST_ROUND_UP_2 (vcrop->out.width) / 2;
+
+  for (i = 0; i < GST_ROUND_UP_2 (vcrop->out.height) / 2; ++i) {
+    oil_memcpy (u_out, u_in, dx);
+    oil_memcpy (v_out, v_in, dx);
+    u_in += vcrop->in.u_stride;
+    u_out += vcrop->out.u_stride;
+    v_in += vcrop->in.v_stride;
+    v_out += vcrop->out.v_stride;
+  }
 }
 
-static GstCaps *
-gst_video_crop_getcaps (GstPad * pad)
+static GstFlowReturn
+gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
+    GstBuffer * outbuf)
 {
-  GstVideoCrop *vc;
-  GstCaps *othercaps, *caps;
-  GstPad *otherpad;
-  gint i, delta_w, delta_h;
+  GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
 
-  vc = GST_VIDEO_CROP (gst_pad_get_parent (pad));
-  otherpad = (pad == vc->srcpad) ? vc->sinkpad : vc->srcpad;
-  othercaps = gst_pad_get_allowed_caps (otherpad);
+  /* we should be operating in passthrough mode if there's nothing to do */
+  g_assert (vcrop->noop == FALSE);
 
-  GST_DEBUG_OBJECT (pad, "othercaps of otherpad %s:%s are: %" GST_PTR_FORMAT,
-      GST_DEBUG_PAD_NAME (otherpad), othercaps);
+  GST_OBJECT_LOCK (vcrop);
 
-  if (pad == vc->srcpad) {
-    delta_w = 0 - vc->crop_left - vc->crop_right;
-    delta_h = 0 - vc->crop_top - vc->crop_bottom;
-  } else {
-    delta_w = vc->crop_left + vc->crop_right;
-    delta_h = vc->crop_top + vc->crop_bottom;
+  if (G_UNLIKELY ((vcrop->crop_left + vcrop->crop_right) >= vcrop->in.width ||
+          (vcrop->crop_top + vcrop->crop_bottom) >= vcrop->in.height)) {
+    GST_OBJECT_UNLOCK (vcrop);
+    goto cropping_too_much;
   }
 
-  for (i = 0; i < gst_caps_get_size (othercaps); i++) {
-    GstStructure *s = gst_caps_get_structure (othercaps, i);
-
-    gst_video_crop_add_to_struct_val (s, "width", delta_w);
-    gst_video_crop_add_to_struct_val (s, "height", delta_h);
+  if (vcrop->in.packed) {
+    gst_video_crop_transform_packed (vcrop, inbuf, outbuf);
+  } else {
+    gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
   }
 
-  caps = gst_caps_intersect (othercaps, gst_pad_get_pad_template_caps (pad));
-  gst_caps_free (othercaps);
+  GST_OBJECT_UNLOCK (vcrop);
 
-  GST_DEBUG_OBJECT (pad, "returning caps: %" GST_PTR_FORMAT, caps);
-  return caps;
+  return GST_FLOW_OK;
+
+cropping_too_much:
+  {
+    /* is there a better error code for this? */
+    GST_ELEMENT_ERROR (vcrop, LIBRARY, SETTINGS, (NULL),
+        ("Can't crop more pixels than there are"));
+    return GST_FLOW_ERROR;
+  }
 }
 
-static GstPadLinkReturn
-gst_video_crop_link (GstPad * pad, const GstCaps * caps)
+static gint
+gst_video_crop_transform_dimension (gint val, gint delta)
 {
-  GstPadLinkReturn ret;
-  GstStructure *structure;
-  GstVideoCrop *vc;
-  GstCaps *newcaps;
-  GstPad *otherpad;
-  gint w, h, other_w, other_h;
+  gint64 new_val = (gint64) val + (gint64) delta;
 
-  vc = GST_VIDEO_CROP (gst_pad_get_parent (pad));
+  new_val = CLAMP (new_val, 1, G_MAXINT);
 
-  structure = gst_caps_get_structure (caps, 0);
-  if (!gst_structure_get_int (structure, "width", &w)
-      || !gst_structure_get_int (structure, "height", &h))
-    return GST_PAD_LINK_DELAYED;
-
-  if (pad == vc->srcpad) {
-    other_w = w + vc->crop_left + vc->crop_right;
-    other_h = h + vc->crop_top + vc->crop_bottom;
-    otherpad = vc->sinkpad;
-    vc->width = other_w;
-    vc->height = other_h;
-  } else {
-    other_w = w - vc->crop_left - vc->crop_right;
-    other_h = h - vc->crop_top - vc->crop_bottom;
-    vc->width = w;
-    vc->height = h;
-    otherpad = vc->srcpad;
-  }
-
-  newcaps = gst_caps_copy (caps);
-
-  gst_caps_set_simple (newcaps,
-      "width", G_TYPE_INT, other_w, "height", G_TYPE_INT, other_h, NULL);
+  return (gint) new_val;
+}
 
-  ret = gst_pad_try_set_caps (otherpad, newcaps);
-  gst_caps_free (newcaps);
+static gboolean
+gst_video_crop_transform_dimension_value (const GValue * src_val,
+    gint delta, GValue * dest_val)
+{
+  gboolean ret = TRUE;
 
-  if (ret == GST_PAD_LINK_REFUSED)
-    return GST_PAD_LINK_REFUSED;
+  g_value_init (dest_val, G_VALUE_TYPE (src_val));
 
-  return GST_PAD_LINK_OK;
-}
+  if (G_VALUE_HOLDS_INT (src_val)) {
+    gint ival = g_value_get_int (src_val);
 
-/* these macros are adapted from videotestsrc.c, paint_setup_I420() */
-#define ROUND_UP_2(x)  (((x)+1)&~1)
-#define ROUND_UP_4(x)  (((x)+3)&~3)
-#define ROUND_UP_8(x)  (((x)+7)&~7)
+    ival = gst_video_crop_transform_dimension (ival, delta);
+    g_value_set_int (dest_val, ival);
+  } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
+    gint min = gst_value_get_int_range_min (src_val);
+    gint max = gst_value_get_int_range_max (src_val);
 
-#define GST_VIDEO_I420_Y_ROWSTRIDE(width) (ROUND_UP_4(width))
-#define GST_VIDEO_I420_U_ROWSTRIDE(width) (ROUND_UP_8(width)/2)
-#define GST_VIDEO_I420_V_ROWSTRIDE(width) ((ROUND_UP_8(GST_VIDEO_I420_Y_ROWSTRIDE(width)))/2)
+    min = gst_video_crop_transform_dimension (min, delta);
+    max = gst_video_crop_transform_dimension (max, delta);
+    gst_value_set_int_range (dest_val, min, max);
+  } else if (GST_VALUE_HOLDS_LIST (src_val)) {
+    gint i;
 
-#define GST_VIDEO_I420_Y_OFFSET(w,h) (0)
-#define GST_VIDEO_I420_U_OFFSET(w,h) (GST_VIDEO_I420_Y_OFFSET(w,h)+(GST_VIDEO_I420_Y_ROWSTRIDE(w)*ROUND_UP_2(h)))
-#define GST_VIDEO_I420_V_OFFSET(w,h) (GST_VIDEO_I420_U_OFFSET(w,h)+(GST_VIDEO_I420_U_ROWSTRIDE(w)*ROUND_UP_2(h)/2))
+    for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
+      const GValue *list_val;
+      GValue newval = { 0, };
 
-#define GST_VIDEO_I420_SIZE(w,h) (GST_VIDEO_I420_V_OFFSET(w,h)+(GST_VIDEO_I420_V_ROWSTRIDE(w)*ROUND_UP_2(h)/2))
+      list_val = gst_value_list_get_value (src_val, i);
+      if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
+        gst_value_list_append_value (dest_val, &newval);
+      g_value_unset (&newval);
+    }
 
-static void
-gst_video_crop_i420 (GstVideoCrop * video_crop, GstBuffer * src_buffer,
-    GstBuffer * dest_buffer)
-{
-  guint8 *src;
-  guint8 *dest;
-  guint8 *srcY, *srcU, *srcV;
-  guint8 *destY, *destU, *destV;
-  gint out_width = video_crop->width -
-      (video_crop->crop_left + video_crop->crop_right);
-  gint out_height = video_crop->height -
-      (video_crop->crop_top + video_crop->crop_bottom);
-  gint j;
-
-  src = GST_BUFFER_DATA (src_buffer);
-  dest = GST_BUFFER_DATA (dest_buffer);
-
-  srcY = src + GST_VIDEO_I420_Y_OFFSET (video_crop->width, video_crop->height);
-  destY = dest + GST_VIDEO_I420_Y_OFFSET (out_width, out_height);
-
-  /* copy Y plane first */
-  srcY +=
-      (GST_VIDEO_I420_Y_ROWSTRIDE (video_crop->width) * video_crop->crop_top) +
-      video_crop->crop_left;
-  for (j = 0; j < out_height; j++) {
-    memcpy (destY, srcY, out_width);
-    srcY += GST_VIDEO_I420_Y_ROWSTRIDE (video_crop->width);
-    destY += GST_VIDEO_I420_Y_ROWSTRIDE (out_width);
+    if (gst_value_list_get_size (dest_val) == 0) {
+      g_value_unset (dest_val);
+      ret = FALSE;
+    }
+  } else {
+    g_value_unset (dest_val);
+    ret = FALSE;
   }
 
-  destU = dest + GST_VIDEO_I420_U_OFFSET (out_width, out_height);
-  destV = dest + GST_VIDEO_I420_V_OFFSET (out_width, out_height);
-
-  srcU = src + GST_VIDEO_I420_U_OFFSET (video_crop->width, video_crop->height);
-  srcV = src + GST_VIDEO_I420_V_OFFSET (video_crop->width, video_crop->height);
-
-  srcU +=
-      (GST_VIDEO_I420_U_ROWSTRIDE (video_crop->width) * (video_crop->crop_top /
-          2)) + (video_crop->crop_left / 2);
-  srcV +=
-      (GST_VIDEO_I420_V_ROWSTRIDE (video_crop->width) * (video_crop->crop_top /
-          2)) + (video_crop->crop_left / 2);
-
-  for (j = 0; j < out_height / 2; j++) {
-    /* copy U plane */
-    memcpy (destU, srcU, out_width / 2);
-    srcU += GST_VIDEO_I420_U_ROWSTRIDE (video_crop->width);
-    destU += GST_VIDEO_I420_U_ROWSTRIDE (out_width);
-
-    /* copy V plane */
-    memcpy (destV, srcV, out_width / 2);
-    srcV += GST_VIDEO_I420_V_ROWSTRIDE (video_crop->width);
-    destV += GST_VIDEO_I420_V_ROWSTRIDE (out_width);
-  }
+  return ret;
 }
 
-static void
-gst_video_crop_chain (GstPad * pad, GstData * _data)
+static GstCaps *
+gst_video_crop_transform_caps (GstBaseTransform * trans,
+    GstPadDirection direction, GstCaps * caps)
 {
-  GstBuffer *buffer = GST_BUFFER (_data);
-  GstVideoCrop *video_crop;
-  GstBuffer *outbuf;
-  gint new_width, new_height;
+  GstVideoCrop *vcrop;
+  GstCaps *other_caps;
+  gint dy, dx, i;
 
-  video_crop = GST_VIDEO_CROP (gst_pad_get_parent (pad));
+  vcrop = GST_VIDEO_CROP (trans);
 
-  new_width = video_crop->width -
-      (video_crop->crop_left + video_crop->crop_right);
-  new_height = video_crop->height -
-      (video_crop->crop_top + video_crop->crop_bottom);
+  if (vcrop->noop)
+    return gst_caps_ref (caps);
+
+  GST_OBJECT_LOCK (vcrop);
+  if (direction == GST_PAD_SRC) {
+    dx = vcrop->crop_left + vcrop->crop_right;
+    dy = vcrop->crop_top + vcrop->crop_bottom;
+  } else {
+    dx = 0 - (vcrop->crop_left + vcrop->crop_right);
+    dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
+  }
+  GST_OBJECT_UNLOCK (vcrop);
 
-  if (video_crop->renegotiate_src_caps || !GST_PAD_CAPS (video_crop->srcpad)) {
-    GstCaps *newcaps;
+  GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
 
-    newcaps = gst_caps_copy (gst_pad_get_negotiated_caps (video_crop->sinkpad));
+  other_caps = gst_caps_new_empty ();
 
-    gst_caps_set_simple (newcaps,
-        "width", G_TYPE_INT, new_width, "height", G_TYPE_INT, new_height, NULL);
+  for (i = 0; i < gst_caps_get_size (caps); ++i) {
+    const GValue *v;
+    GstStructure *structure, *new_structure;
+    GValue w_val = { 0, }, h_val = {
+    0,};
 
-    if (GST_PAD_LINK_FAILED (gst_pad_try_set_caps (video_crop->srcpad,
-                newcaps))) {
-      GST_ELEMENT_ERROR (video_crop, CORE, NEGOTIATION, (NULL), (NULL));
-      gst_caps_free (newcaps);
-      return;
+    structure = gst_caps_get_structure (caps, i);
+
+    v = gst_structure_get_value (structure, "width");
+    if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
+      GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
+          ", caps structure=%" GST_PTR_FORMAT, dx, structure);
+      continue;
     }
 
-    gst_caps_free (newcaps);
+    v = gst_structure_get_value (structure, "height");
+    if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
+      g_value_unset (&w_val);
+      GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
+          ", caps structure=%" GST_PTR_FORMAT, dy, structure);
+      continue;
+    }
 
-    video_crop->renegotiate_src_caps = FALSE;
+    new_structure = gst_structure_copy (structure);
+    gst_structure_set_value (new_structure, "width", &w_val);
+    gst_structure_set_value (new_structure, "height", &h_val);
+    g_value_unset (&w_val);
+    g_value_unset (&h_val);
+    GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
+        " => %" GST_PTR_FORMAT, i, structure, new_structure);
+    gst_caps_append_structure (other_caps, new_structure);
   }
 
-  /* passthrough if nothing to do */
-  if (new_width == video_crop->width && new_height == video_crop->height) {
-    gst_pad_push (video_crop->srcpad, GST_DATA (buffer));
-    return;
+  if (gst_caps_is_empty (other_caps)) {
+    gst_caps_unref (other_caps);
+    other_caps = NULL;
   }
 
-  g_return_if_fail (GST_BUFFER_SIZE (buffer) >=
-      GST_VIDEO_I420_SIZE (video_crop->width, video_crop->height));
+  return other_caps;
+}
 
-  outbuf =
-      gst_pad_alloc_buffer_and_set_caps (video_crop->srcpad,
-      GST_BUFFER_OFFSET (buffer), GST_VIDEO_I420_SIZE (new_width, new_height));
+static gboolean
+gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
+    GstCaps * outcaps)
+{
+  GstVideoCrop *crop = GST_VIDEO_CROP (trans);
 
-  gst_buffer_stamp (outbuf, buffer);
+  if (!gst_video_crop_get_image_details_from_caps (crop, &crop->in, incaps)) {
+    GST_DEBUG_OBJECT (crop, "failed to parse input caps %" GST_PTR_FORMAT,
+        incaps);
+    return FALSE;
+  }
 
-  gst_video_crop_i420 (video_crop, buffer, outbuf);
-  gst_buffer_unref (buffer);
+  if (!gst_video_crop_get_image_details_from_caps (crop, &crop->out, outcaps)) {
+    GST_DEBUG_OBJECT (crop, "failed to parse output caps %" GST_PTR_FORMAT,
+        outcaps);
+    return FALSE;
+  }
 
-  gst_pad_push (video_crop->srcpad, GST_DATA (outbuf));
+  return TRUE;
 }
 
-static GstStateChangeReturn
-gst_video_crop_change_state (GstElement * element, GstStateChange transition)
+static void
+gst_video_crop_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
 {
   GstVideoCrop *video_crop;
 
-  video_crop = GST_VIDEO_CROP (element);
+  video_crop = GST_VIDEO_CROP (object);
 
-  switch (transition) {
-    case GST_STATE_CHANGE_NULL_TO_READY:
-      video_crop->renegotiate_src_caps = TRUE;
-      break;
-    case GST_STATE_CHANGE_READY_TO_PAUSED:
+  GST_OBJECT_LOCK (video_crop);
+  switch (prop_id) {
+    case ARG_LEFT:
+      video_crop->crop_left = g_value_get_int (value);
       break;
-    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+    case ARG_RIGHT:
+      video_crop->crop_right = g_value_get_int (value);
       break;
-    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+    case ARG_TOP:
+      video_crop->crop_top = g_value_get_int (value);
       break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case ARG_BOTTOM:
+      video_crop->crop_bottom = g_value_get_int (value);
       break;
-    case GST_STATE_CHANGE_READY_TO_NULL:
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
 
-  if (parent_class->change_state != NULL)
-    return parent_class->change_state (element, transition);
+  video_crop->noop = ((video_crop->crop_left | video_crop->crop_right |
+          video_crop->crop_top | video_crop->crop_bottom) == 0);
+
+  GST_OBJECT_UNLOCK (video_crop);
+}
+
+static void
+gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstVideoCrop *video_crop;
+
+  video_crop = GST_VIDEO_CROP (object);
 
-  return GST_STATE_CHANGE_SUCCESS;
+  GST_OBJECT_LOCK (video_crop);
+  switch (prop_id) {
+    case ARG_LEFT:
+      g_value_set_int (value, video_crop->crop_left);
+      break;
+    case ARG_RIGHT:
+      g_value_set_int (value, video_crop->crop_right);
+      break;
+    case ARG_TOP:
+      g_value_set_int (value, video_crop->crop_top);
+      break;
+    case ARG_BOTTOM:
+      g_value_set_int (value, video_crop->crop_bottom);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (video_crop);
 }
 
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
+  GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
+
   return gst_element_register (plugin, "videocrop", GST_RANK_NONE,
       GST_TYPE_VIDEO_CROP);
 }
@@ -572,5 +674,5 @@ plugin_init (GstPlugin * plugin)
 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
     GST_VERSION_MINOR,
     "videocrop",
-    "Crops video into a user defined region",
+    "Crops video into a user-defined region",
     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)