-/* 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,
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);
}
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)