From 7e69aee2235e401680ca441a449e6b741e8c5837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 2 Sep 2006 15:30:45 +0000 Subject: [PATCH] Port/rewrite videocrop from scratch for GStreamer-0.10, and make it support all formats videoscale supports (#345653). 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 | 17 + configure.ac | 4 +- gst/videocrop/Makefile.am | 9 +- gst/videocrop/gstvideocrop.c | 930 +++++++++++++++++++---------------- 4 files changed, 542 insertions(+), 418 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8cec581c1f..5bc084cbd9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2006-09-02 Tim-Philipp Müller + + * 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 * sys/v4l2/gstv4l2.c: diff --git a/configure.ac b/configure.ac index b10be5c30c..29bb7faba5 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/gst/videocrop/Makefile.am b/gst/videocrop/Makefile.am index 81c4199031..6cf889aab5 100644 --- a/gst/videocrop/Makefile.am +++ b/gst/videocrop/Makefile.am @@ -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 = diff --git a/gst/videocrop/gstvideocrop.c b/gst/videocrop/gstvideocrop.c index 12db035291..72911d5098 100644 --- a/gst/videocrop/gstvideocrop.c +++ b/gst/videocrop/gstvideocrop.c @@ -1,5 +1,5 @@ -/* GStreamer - * Copyright (C) <1999> Erik Walthinsen +/* GStreamer video frame cropping + * Copyright (C) 2006 Tim-Philipp Müller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,14 +17,54 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:element-videocrop + * @see_also: GstVideoBox + * + * + * + * 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. + * + * + * 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. + * + * + * If there is nothing to crop, the element will operate in pass-through mode. + * + * Example launch line + * + * + * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink + * + * + * + */ + +/* 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 #include - +#include +#include #include +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) \ @@ -33,40 +73,56 @@ (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 "); + "Crops video into a user-defined region", + "Tim-Philipp Müller "); - -/* VideoCrop args */ enum { ARG_0, @@ -74,150 +130,485 @@ 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; - - parent_class = g_type_class_peek_parent (klass); - - g_object_class_install_property (G_OBJECT_CLASS (klass), 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_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_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_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom", - 0, G_MAXINT, 0, G_PARAM_READWRITE)); + basetransform_class = (GstBaseTransformClass *) klass; gobject_class->set_property = gst_video_crop_set_property; gobject_class->get_property = gst_video_crop_get_property; - gstelement_class->change_state = gst_video_crop_change_state; + 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 (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 (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 (gobject_class, ARG_BOTTOM, + g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom", + 0, G_MAXINT, 0, G_PARAM_READWRITE)); + + 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; + + 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; +} + +static gboolean +gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop, + GstVideoCropImageDetails * details, GstCaps * caps) +{ + GstStructure *structure; + gint width, height; + + 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; + } + + 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; + } + + 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_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + guint8 *in_data, *out_data; + guint i, dx; + + in_data = GST_BUFFER_DATA (inbuf); + out_data = GST_BUFFER_DATA (outbuf); + + in_data += vcrop->crop_top * vcrop->in.stride; + in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel; + + dx = vcrop->out.width * vcrop->out.bytes_per_pixel; + + 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_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + guint8 *y_out, *u_out, *v_out; + guint8 *y_in, *u_in, *v_in; + guint i, dx; + + /* Y plane */ + y_in = GST_BUFFER_DATA (inbuf); + y_out = GST_BUFFER_DATA (outbuf); + + y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left; + dx = vcrop->out.width * 1; + + 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; + } + + /* U + V planes */ + u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off; + u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off; + + u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride; + u_in += vcrop->crop_left / 2; + + v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off; + v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off; + + v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride; + v_in += vcrop->crop_left / 2; + + 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 GstFlowReturn +gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstVideoCrop *vcrop = GST_VIDEO_CROP (trans); + + /* we should be operating in passthrough mode if there's nothing to do */ + g_assert (vcrop->noop == FALSE); + + GST_OBJECT_LOCK (vcrop); + + 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; + } + + if (vcrop->in.packed) { + gst_video_crop_transform_packed (vcrop, inbuf, outbuf); + } else { + gst_video_crop_transform_planar (vcrop, inbuf, outbuf); + } + + GST_OBJECT_UNLOCK (vcrop); + + 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 gint +gst_video_crop_transform_dimension (gint val, gint delta) +{ + gint64 new_val = (gint64) val + (gint64) delta; + + new_val = CLAMP (new_val, 1, G_MAXINT); + + return (gint) new_val; +} + +static gboolean +gst_video_crop_transform_dimension_value (const GValue * src_val, + gint delta, GValue * dest_val) +{ + gboolean ret = TRUE; + + g_value_init (dest_val, G_VALUE_TYPE (src_val)); + + if (G_VALUE_HOLDS_INT (src_val)) { + gint ival = g_value_get_int (src_val); + + 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); + + 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; + + for (i = 0; i < gst_value_list_get_size (src_val); ++i) { + const GValue *list_val; + GValue newval = { 0, }; + + 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); + } + + if (gst_value_list_get_size (dest_val) == 0) { + g_value_unset (dest_val); + ret = FALSE; + } + } else { + g_value_unset (dest_val); + ret = FALSE; + } + + return ret; +} + +static GstCaps * +gst_video_crop_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps) +{ + GstVideoCrop *vcrop; + GstCaps *other_caps; + gint dy, dx, i; + + vcrop = GST_VIDEO_CROP (trans); + + 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); + + GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps); + + other_caps = gst_caps_new_empty (); + + for (i = 0; i < gst_caps_get_size (caps); ++i) { + const GValue *v; + GstStructure *structure, *new_structure; + GValue w_val = { 0, }, h_val = { + 0,}; + + 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; + } + + 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; + } + + 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); + } + + if (gst_caps_is_empty (other_caps)) { + gst_caps_unref (other_caps); + other_caps = NULL; + } + + return other_caps; +} + +static gboolean +gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstVideoCrop *crop = GST_VIDEO_CROP (trans); + + 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; + } + + 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; + } + + return TRUE; } -/* do we need this function? */ static void gst_video_crop_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstVideoCrop *video_crop; - g_return_if_fail (GST_IS_VIDEO_CROP (object)); - video_crop = GST_VIDEO_CROP (object); + GST_OBJECT_LOCK (video_crop); switch (prop_id) { case ARG_LEFT: video_crop->crop_left = g_value_get_int (value); @@ -235,17 +626,22 @@ gst_video_crop_set_property (GObject * object, guint prop_id, G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + + 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; - g_return_if_fail (GST_IS_VIDEO_CROP (object)); - video_crop = GST_VIDEO_CROP (object); + GST_OBJECT_LOCK (video_crop); switch (prop_id) { case ARG_LEFT: g_value_set_int (value, video_crop->crop_left); @@ -263,308 +659,14 @@ gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value, G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } - - if (gst_pad_is_negotiated (video_crop->srcpad)) - video_crop->renegotiate_src_caps = TRUE; -} - -static void -gst_video_crop_add_to_struct_val (GstStructure * s, const gchar * field_name, - gint addval) -{ - const GValue *val; - - val = gst_structure_get_value (s, field_name); - - if (G_VALUE_HOLDS_INT (val)) { - gint ival = g_value_get_int (val); - - gst_structure_set (s, field_name, G_TYPE_INT, ival + addval, NULL); - return; - } - - 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_structure_set (s, field_name, GST_TYPE_INT_RANGE, min + addval, - max + addval, NULL); - return; - } - - if (GST_VALUE_HOLDS_LIST (val)) { - GValue newlist = { 0, }; - gint i; - - 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; - } - - g_return_if_reached (); -} - -static GstCaps * -gst_video_crop_getcaps (GstPad * pad) -{ - GstVideoCrop *vc; - GstCaps *othercaps, *caps; - GstPad *otherpad; - gint i, delta_w, delta_h; - - vc = GST_VIDEO_CROP (gst_pad_get_parent (pad)); - otherpad = (pad == vc->srcpad) ? vc->sinkpad : vc->srcpad; - othercaps = gst_pad_get_allowed_caps (otherpad); - - GST_DEBUG_OBJECT (pad, "othercaps of otherpad %s:%s are: %" GST_PTR_FORMAT, - GST_DEBUG_PAD_NAME (otherpad), othercaps); - - 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; - } - - 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); - } - - caps = gst_caps_intersect (othercaps, gst_pad_get_pad_template_caps (pad)); - gst_caps_free (othercaps); - - GST_DEBUG_OBJECT (pad, "returning caps: %" GST_PTR_FORMAT, caps); - return caps; -} - -static GstPadLinkReturn -gst_video_crop_link (GstPad * pad, const GstCaps * caps) -{ - GstPadLinkReturn ret; - GstStructure *structure; - GstVideoCrop *vc; - GstCaps *newcaps; - GstPad *otherpad; - gint w, h, other_w, other_h; - - vc = GST_VIDEO_CROP (gst_pad_get_parent (pad)); - - 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); - - ret = gst_pad_try_set_caps (otherpad, newcaps); - gst_caps_free (newcaps); - - if (ret == GST_PAD_LINK_REFUSED) - return GST_PAD_LINK_REFUSED; - - return GST_PAD_LINK_OK; -} - -/* 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) - -#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) - -#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)) - -#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)) - -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); - } - - 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); - } -} - -static void -gst_video_crop_chain (GstPad * pad, GstData * _data) -{ - GstBuffer *buffer = GST_BUFFER (_data); - GstVideoCrop *video_crop; - GstBuffer *outbuf; - gint new_width, new_height; - - video_crop = GST_VIDEO_CROP (gst_pad_get_parent (pad)); - - 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 (video_crop->renegotiate_src_caps || !GST_PAD_CAPS (video_crop->srcpad)) { - GstCaps *newcaps; - - newcaps = gst_caps_copy (gst_pad_get_negotiated_caps (video_crop->sinkpad)); - - gst_caps_set_simple (newcaps, - "width", G_TYPE_INT, new_width, "height", G_TYPE_INT, new_height, NULL); - - 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; - } - - gst_caps_free (newcaps); - - video_crop->renegotiate_src_caps = FALSE; - } - - /* 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; - } - - g_return_if_fail (GST_BUFFER_SIZE (buffer) >= - GST_VIDEO_I420_SIZE (video_crop->width, video_crop->height)); - - outbuf = - gst_pad_alloc_buffer_and_set_caps (video_crop->srcpad, - GST_BUFFER_OFFSET (buffer), GST_VIDEO_I420_SIZE (new_width, new_height)); - - gst_buffer_stamp (outbuf, buffer); - - gst_video_crop_i420 (video_crop, buffer, outbuf); - gst_buffer_unref (buffer); - - gst_pad_push (video_crop->srcpad, GST_DATA (outbuf)); -} - -static GstStateChangeReturn -gst_video_crop_change_state (GstElement * element, GstStateChange transition) -{ - GstVideoCrop *video_crop; - - video_crop = GST_VIDEO_CROP (element); - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - video_crop->renegotiate_src_caps = TRUE; - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - } - - if (parent_class->change_state != NULL) - return parent_class->change_state (element, transition); - - return GST_STATE_CHANGE_SUCCESS; + 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)