v4l2src: Add support for cropping at capture source input

Add properties to control input cropping in the V4L2 device.
The input cropping is applied before composing the result to the
capture buffer.  By default the capture size will be set to the same
size as the crop region, but it can be scaled to a different output
frame size if supported by the V4L2 device.
If scaling is not supported, the cropped image will
be composed as is into the top-left corner of the capture buffer.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1089>
This commit is contained in:
Damian Hobson-Garcia 2021-10-14 17:22:19 +09:00 committed by GStreamer Marge Bot
parent ceff3e8ff7
commit 70086fda22
2 changed files with 209 additions and 0 deletions

View file

@ -72,6 +72,11 @@ enum
{ {
PROP_0, PROP_0,
V4L2_STD_OBJECT_PROPS, V4L2_STD_OBJECT_PROPS,
PROP_CROP_TOP,
PROP_CROP_LEFT,
PROP_CROP_BOTTOM,
PROP_CROP_RIGHT,
PROP_CROP_BOUNDS,
PROP_LAST PROP_LAST
}; };
@ -157,6 +162,81 @@ gst_v4l2src_class_init (GstV4l2SrcClass * klass)
gst_v4l2_object_install_properties_helper (gobject_class, gst_v4l2_object_install_properties_helper (gobject_class,
DEFAULT_PROP_DEVICE); DEFAULT_PROP_DEVICE);
/**
* GstV4l2Src:crop-top:
*
* Number of pixels to crop from the top edge of captured video
* stream
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_CROP_TOP,
g_param_spec_uint ("crop-top", "Crop top",
"Pixels to crop at top of video capture input",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstV4l2Src:crop-left:
*
* Number of pixels to crop from the left edge of captured video
* stream
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_CROP_LEFT,
g_param_spec_uint ("crop-left", "Crop left",
"Pixels to crop at left of video capture input",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstV4l2Src:crop-bottom:
*
* Number of pixels to crop from the bottom edge of captured video
* stream
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_CROP_BOTTOM,
g_param_spec_uint ("crop-bottom", "Crop bottom",
"Pixels to crop at bottom of video capture input",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstV4l2Src:crop-right:
*
* Number of pixels to crop from the right edge of captured video
* stream
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_CROP_RIGHT,
g_param_spec_uint ("crop-right", "Crop right",
"Pixels to crop at right of video capture input",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstV4l2Src:crop-bounds:
*
* Crop bounding region. All crop regions must lie within this region.
* The bounds are represented as a four element array, that descibes the
* [x, y, width, height] of the area.
*
* The size and position of the crop
* bounds will only be known, once the v4l2 device is opened and the
* input source selected. Applications can connect to the
* "notify::crop-bounds" signal to be notified when the bounding region is
* updated, and set an appropriate crop region.
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_CROP_BOUNDS,
gst_param_spec_array ("crop-bounds", "Crop bounds",
"The bounding region for crop rectangles ('<x, y, width, height>').",
g_param_spec_int ("rect-value", "Rectangle Value",
"One of x, y, width or height value.", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS),
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/** /**
* GstV4l2Src::prepare-format: * GstV4l2Src::prepare-format:
* @v4l2src: the v4l2src instance * @v4l2src: the v4l2src instance
@ -236,6 +316,18 @@ gst_v4l2src_set_property (GObject * object,
if (!gst_v4l2_object_set_property_helper (v4l2src->v4l2object, if (!gst_v4l2_object_set_property_helper (v4l2src->v4l2object,
prop_id, value, pspec)) { prop_id, value, pspec)) {
switch (prop_id) { switch (prop_id) {
case PROP_CROP_TOP:
v4l2src->crop_top = g_value_get_uint (value);
break;
case PROP_CROP_LEFT:
v4l2src->crop_left = g_value_get_uint (value);
break;
case PROP_CROP_BOTTOM:
v4l2src->crop_bottom = g_value_get_uint (value);
break;
case PROP_CROP_RIGHT:
v4l2src->crop_right = g_value_get_uint (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -243,6 +335,29 @@ gst_v4l2src_set_property (GObject * object,
} }
} }
static void
gst_v4l2src_set_rect_value (GValue * value, struct v4l2_rect *rect)
{
GValue val = { 0 };
g_value_init (&val, G_TYPE_INT);
g_value_reset (value);
g_value_set_int (&val, rect->left);
gst_value_array_append_value (value, &val);
g_value_set_int (&val, rect->top);
gst_value_array_append_value (value, &val);
g_value_set_int (&val, rect->width);
gst_value_array_append_value (value, &val);
g_value_set_int (&val, rect->height);
gst_value_array_append_value (value, &val);
g_value_unset (&val);
}
static void static void
gst_v4l2src_get_property (GObject * object, gst_v4l2src_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec) guint prop_id, GValue * value, GParamSpec * pspec)
@ -252,6 +367,21 @@ gst_v4l2src_get_property (GObject * object,
if (!gst_v4l2_object_get_property_helper (v4l2src->v4l2object, if (!gst_v4l2_object_get_property_helper (v4l2src->v4l2object,
prop_id, value, pspec)) { prop_id, value, pspec)) {
switch (prop_id) { switch (prop_id) {
case PROP_CROP_TOP:
g_value_set_uint (value, v4l2src->crop_top);
break;
case PROP_CROP_LEFT:
g_value_set_uint (value, v4l2src->crop_left);
break;
case PROP_CROP_BOTTOM:
g_value_set_uint (value, v4l2src->crop_bottom);
break;
case PROP_CROP_RIGHT:
g_value_set_uint (value, v4l2src->crop_right);
break;
case PROP_CROP_BOUNDS:
gst_v4l2src_set_rect_value (value, &v4l2src->crop_bounds);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -373,6 +503,23 @@ done:
return ret; return ret;
} }
static gboolean
gst_v4l2src_do_source_crop (GstV4l2Src * v4l2src)
{
struct v4l2_rect def_crop;
if (v4l2src->apply_crop_settings)
return gst_v4l2_object_set_crop (v4l2src->v4l2object, &v4l2src->crop_rect);
/* If no crop setting is given, reset to the default. Resetting the default
* crop may fail if the device does not support cropping. This should not
* be considered an error. */
if (gst_v4l2_object_get_crop_default (v4l2src->v4l2object, &def_crop))
gst_v4l2_object_set_crop (v4l2src->v4l2object, &def_crop);
return TRUE;
}
static gboolean static gboolean
gst_v4l2src_set_format (GstV4l2Src * v4l2src, GstCaps * caps, gst_v4l2src_set_format (GstV4l2Src * v4l2src, GstCaps * caps,
GstV4l2Error * error) GstV4l2Error * error)
@ -388,6 +535,9 @@ gst_v4l2src_set_format (GstV4l2Src * v4l2src, GstCaps * caps,
g_signal_emit (v4l2src, gst_v4l2_signals[SIGNAL_PRE_SET_FORMAT], 0, g_signal_emit (v4l2src, gst_v4l2_signals[SIGNAL_PRE_SET_FORMAT], 0,
v4l2src->v4l2object->video_fd, caps); v4l2src->v4l2object->video_fd, caps);
if (!gst_v4l2src_do_source_crop (v4l2src))
return FALSE;
return gst_v4l2_object_set_format (obj, caps, error); return gst_v4l2_object_set_format (obj, caps, error);
} }
@ -576,6 +726,53 @@ gst_v4l2src_query_preferred_size (GstV4l2Src * v4l2src,
return FALSE; return FALSE;
} }
static gboolean
gst_v4l2src_setup_source_crop (GstV4l2Src * v4l2src,
struct PreferredCapsInfo *pref)
{
gint cropped_width, cropped_height;
struct v4l2_rect *crop_bounds = &v4l2src->crop_bounds;
v4l2src->apply_crop_settings = FALSE;
if (!gst_v4l2_object_get_crop_bounds (v4l2src->v4l2object, crop_bounds))
return FALSE;
g_object_notify (G_OBJECT (v4l2src), "crop-bounds");
cropped_width = crop_bounds->width - v4l2src->crop_left - v4l2src->crop_right;
cropped_height =
crop_bounds->height - v4l2src->crop_top - v4l2src->crop_bottom;
if (v4l2src->crop_left < crop_bounds->left
|| v4l2src->crop_top < crop_bounds->top
|| cropped_width <= 0 || cropped_height <= 0) {
GST_WARNING_OBJECT (v4l2src, "Ignoring out of bounds crop region");
return FALSE;
}
if (cropped_width == crop_bounds->width
&& cropped_height == crop_bounds->height) {
GST_DEBUG_OBJECT (v4l2src,
"No cropping requested, keep current preferred size");
return FALSE;
}
v4l2src->crop_rect.left = v4l2src->crop_left;
v4l2src->crop_rect.top = v4l2src->crop_top;
v4l2src->crop_rect.width = cropped_width;
v4l2src->crop_rect.height = cropped_height;
v4l2src->apply_crop_settings = TRUE;
pref->width = cropped_width;
pref->height = cropped_height;
GST_INFO_OBJECT (v4l2src, "Updated preferred capture size to %i x %i",
pref->width, pref->height);
return TRUE;
}
static gboolean static gboolean
gst_v4l2src_negotiate (GstBaseSrc * basesrc) gst_v4l2src_negotiate (GstBaseSrc * basesrc)
{ {
@ -596,6 +793,8 @@ gst_v4l2src_negotiate (GstBaseSrc * basesrc)
* the caps enumeration. */ * the caps enumeration. */
have_pref = gst_v4l2src_query_preferred_size (v4l2src, &pref); have_pref = gst_v4l2src_query_preferred_size (v4l2src, &pref);
have_pref |= gst_v4l2src_setup_source_crop (v4l2src, &pref);
/* first see what is possible on our source pad */ /* first see what is possible on our source pad */
thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL);
GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps); GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps);

View file

@ -65,6 +65,16 @@ struct _GstV4l2Src
gboolean pending_set_fmt; gboolean pending_set_fmt;
guint crop_top;
guint crop_left;
guint crop_bottom;
guint crop_right;
struct v4l2_rect crop_bounds;
gboolean apply_crop_settings;
struct v4l2_rect crop_rect;
/* Timestamp sanity check */ /* Timestamp sanity check */
GstClockTime last_timestamp; GstClockTime last_timestamp;
gboolean has_bad_timestamp; gboolean has_bad_timestamp;