#include "gstv4l2tuner.h"
#include "gstv4l2vidorient.h"
-#include "gst/gst-i18n-plugin.h"
+#include <glib/gi18n-lib.h>
GST_DEBUG_CATEGORY (v4l2src_debug);
#define GST_CAT_DEFAULT v4l2src_debug
{
PROP_0,
V4L2_STD_OBJECT_PROPS,
+ PROP_CROP_TOP,
+ PROP_CROP_LEFT,
+ PROP_CROP_BOTTOM,
+ PROP_CROP_RIGHT,
+ PROP_CROP_BOUNDS,
+#ifdef TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID
+ PROP_CAMERA_ID,
+#endif /* TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID */
+#ifdef TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE
+ PROP_AUTO_SCAN_DEVICE,
+#endif /* TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE */
PROP_LAST
};
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));
+
+#ifdef TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID
+ /**
+ * GstV4l2Src:camera-id:
+ *
+ * The value which is set by application will be used as a number of device node.
+ * ex) 1 -> /dev/video1
+ */
+ g_object_class_install_property (gobject_class, PROP_CAMERA_ID,
+ g_param_spec_uint ("camera-id", "Camera ID",
+ "Camera ID for device node", 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif /* TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID */
+#ifdef TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE
+ /**
+ * GstV4l2Src:auto-scan-device:
+ */
+ g_object_class_install_property (gobject_class, PROP_AUTO_SCAN_DEVICE,
+ g_param_spec_boolean ("auto-scan-device", "Scan device automatically",
+ "Scan all device nodes automatically until device open success.",
+ TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif /* TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE */
+#ifdef TIZEN_FEATURE_V4L2_TBM_SUPPORT
+ /**
+ * GstV4l2Src:tbm-output
+ */
+ g_object_class_install_property (gobject_class, PROP_TBM_OUTPUT,
+ g_param_spec_boolean ("tbm-output", "Enable TBM for output buffer",
+ "It works for only DMABUF mode.",
+ FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif /* TIZEN_FEATURE_V4L2_TBM_SUPPORT */
+
+ /**
* GstV4l2Src::prepare-format:
* @v4l2src: the v4l2src instance
* @fd: the file descriptor of the current device
/* Avoid the slow probes */
v4l2src->v4l2object->skip_try_fmt_probes = TRUE;
+#ifdef TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE
+ v4l2src->v4l2object->auto_scan_device = TRUE;
+#endif /* TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE */
gst_base_src_set_format (GST_BASE_SRC (v4l2src), GST_FORMAT_TIME);
gst_base_src_set_live (GST_BASE_SRC (v4l2src), TRUE);
if (!gst_v4l2_object_set_property_helper (v4l2src->v4l2object,
prop_id, value, pspec)) {
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;
+#ifdef TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID
+ case PROP_CAMERA_ID:
+ g_free (v4l2src->v4l2object->videodev);
+
+ v4l2src->camera_id = g_value_get_uint (value);
+ v4l2src->v4l2object->videodev = g_strdup_printf ("/dev/video%u", v4l2src->camera_id);
+
+ GST_INFO_OBJECT (v4l2src, "videodev [%s]", v4l2src->v4l2object->videodev);
+ break;
+#endif /* TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID */
+#ifdef TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE
+ case PROP_AUTO_SCAN_DEVICE:
+ v4l2src->v4l2object->auto_scan_device = g_value_get_boolean (value);
+ GST_INFO_OBJECT (v4l2src, "auto scan device [%d]", v4l2src->v4l2object->auto_scan_device);
+ break;
+#endif /* TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE */
+#ifdef TIZEN_FEATURE_V4L2_TBM_SUPPORT
+ case PROP_TBM_OUTPUT:
+ v4l2src->v4l2object->tbm_output = g_value_get_boolean (value);
+ GST_INFO_OBJECT (v4l2src, "tbm output [%d]", v4l2src->v4l2object->tbm_output);
+ break;
+#endif /* TIZEN_FEATURE_V4L2_TBM_SUPPORT */
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
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
gst_v4l2src_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
if (!gst_v4l2_object_get_property_helper (v4l2src->v4l2object,
prop_id, value, pspec)) {
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;
+#ifdef TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID
+ case PROP_CAMERA_ID:
+ g_value_set_uint (value, v4l2src->camera_id);
+ break;
+#endif /* TIZEN_FEATURE_V4L2SRC_SUPPORT_CAMERA_ID */
+#ifdef TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE
+ case PROP_AUTO_SCAN_DEVICE:
+ GST_INFO_OBJECT (v4l2src, "auto scan device [%d]", v4l2src->v4l2object->auto_scan_device);
+ g_value_set_boolean (value, v4l2src->v4l2object->auto_scan_device);
+ break;
+#endif /* TIZEN_FEATURE_V4L2SRC_AUTO_SCAN_DEVICE_NODE */
+#ifdef TIZEN_FEATURE_V4L2_TBM_SUPPORT
+ case PROP_TBM_OUTPUT:
+ GST_INFO_OBJECT (v4l2src, "tbm output [%d]", v4l2src->v4l2object->tbm_output);
+ g_value_set_boolean (value, v4l2src->v4l2object->tbm_output);
+ break;
+#endif /* TIZEN_FEATURE_V4L2_TBM_SUPPORT */
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
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
gst_v4l2src_set_format (GstV4l2Src * v4l2src, GstCaps * caps,
GstV4l2Error * error)
{
g_signal_emit (v4l2src, gst_v4l2_signals[SIGNAL_PRE_SET_FORMAT], 0,
v4l2src->v4l2object->video_fd, caps);
+ if (!gst_v4l2src_do_source_crop (v4l2src))
+ return FALSE;
+
return gst_v4l2_object_set_format (obj, caps, error);
}
GstV4l2Object *obj = v4l2src->v4l2object;
struct v4l2_dv_timings dv_timings = { 0, };
const struct v4l2_bt_timings *bt = &dv_timings.bt;
+ gboolean not_streaming;
gint tot_width, tot_height;
gint gcd;
/* If are are not streaming (e.g. we received source-change event), lock the
* new timing immediatly so that TRY_FMT can properly work */
- if (!obj->pool || !GST_V4L2_BUFFER_POOL_IS_STREAMING (obj->pool)) {
+ {
+ GstBufferPool *obj_pool = gst_v4l2_object_get_buffer_pool (obj);
+ not_streaming = !obj_pool || !GST_V4L2_BUFFER_POOL_IS_STREAMING (obj_pool);
+ if (obj_pool)
+ gst_object_unref (obj_pool);
+ }
+
+ if (not_streaming) {
gst_v4l2_set_dv_timings (obj, &dv_timings);
/* Setting a new DV timings invalidates the probed caps. */
gst_caps_replace (&obj->probed_caps, NULL);
}
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
gst_v4l2src_negotiate (GstBaseSrc * basesrc)
{
GstV4l2Src *v4l2src = GST_V4L2SRC (basesrc);
* the caps enumeration. */
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 */
thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL);
GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps);
gst_v4l2src_decide_allocation (GstBaseSrc * bsrc, GstQuery * query)
{
GstV4l2Src *src = GST_V4L2SRC (bsrc);
+ GstBufferPool *bpool = gst_v4l2_object_get_buffer_pool (src->v4l2object);
gboolean ret = TRUE;
if (src->pending_set_fmt) {
GstCaps *caps = gst_pad_get_current_caps (GST_BASE_SRC_PAD (bsrc));
GstV4l2Error error = GST_V4L2_ERROR_INIT;
+ /* Setting the format replaces the current pool */
+ gst_clear_object (&bpool);
+
caps = gst_caps_make_writable (caps);
ret = gst_v4l2src_set_format (src, caps, &error);
if (ret) {
- GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (src->v4l2object->pool);
+ GstV4l2BufferPool *pool;
+ bpool = gst_v4l2_object_get_buffer_pool (src->v4l2object);
+ pool = GST_V4L2_BUFFER_POOL (bpool);
gst_v4l2_buffer_pool_enable_resolution_change (pool);
} else {
gst_v4l2_error (src, &error);
gst_caps_unref (caps);
src->pending_set_fmt = FALSE;
- } else if (gst_buffer_pool_is_active (src->v4l2object->pool)) {
+ } else if (gst_buffer_pool_is_active (bpool)) {
/* Trick basesrc into not deactivating the active pool. Renegotiating here
* would otherwise turn off and on the camera. */
GstAllocator *allocator;
gst_object_unref (pool);
if (allocator)
gst_object_unref (allocator);
+ if (bpool)
+ gst_object_unref (bpool);
return GST_BASE_SRC_CLASS (parent_class)->decide_allocation (bsrc, query);
}
}
if (ret) {
- if (!gst_buffer_pool_set_active (src->v4l2object->pool, TRUE))
+ if (!gst_buffer_pool_set_active (bpool, TRUE))
goto activate_failed;
}
+ if (bpool)
+ gst_object_unref (bpool);
return ret;
activate_failed:
GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS,
(_("Failed to allocate required memory.")),
("Buffer pool activation failed"));
+ if (bpool)
+ gst_object_unref (bpool);
return FALSE;
}
}
min_latency /= 2;
/* max latency is total duration of the frame buffer */
- if (obj->pool != NULL)
- num_buffers = GST_V4L2_BUFFER_POOL_CAST (obj->pool)->max_latency;
+ {
+ GstBufferPool *obj_pool = gst_v4l2_object_get_buffer_pool (obj);
+ if (obj_pool != NULL) {
+ num_buffers = GST_V4L2_BUFFER_POOL_CAST (obj_pool)->max_latency;
+ gst_object_unref (obj_pool);
+ }
+ }
if (num_buffers == 0)
max_latency = -1;
return ret;
}
+static gboolean
+gst_v4l2src_handle_resolution_change (GstV4l2Src * v4l2src)
+{
+ GST_INFO_OBJECT (v4l2src, "Resolution change detected.");
+
+ /* It is required to always cycle through streamoff, we also need to
+ * streamoff in order to allow locking a new DV_TIMING which will
+ * influence the output of TRY_FMT */
+ gst_v4l2src_stop (GST_BASE_SRC (v4l2src));
+
+ /* Force renegotiation */
+ v4l2src->renegotiation_adjust = v4l2src->offset + 1;
+ v4l2src->pending_set_fmt = TRUE;
+
+ return gst_base_src_negotiate (GST_BASE_SRC (v4l2src));
+}
+
static GstFlowReturn
gst_v4l2src_create (GstPushSrc * src, GstBuffer ** buf)
{
gboolean half_frame;
do {
- GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL_CAST (obj->pool);
-
ret = GST_BASE_SRC_CLASS (parent_class)->alloc (GST_BASE_SRC (src), 0,
obj->info.size, buf);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
if (ret == GST_V4L2_FLOW_RESOLUTION_CHANGE) {
- GST_INFO_OBJECT (v4l2src, "Resolution change detected.");
-
- /* It is required to always cycle through streamoff, we also need to
- * streamoff in order to allow locking a new DV_TIMING which will
- * influence the output of TRY_FMT */
- gst_v4l2src_stop (GST_BASE_SRC (src));
-
- /* Force renegotiation */
- v4l2src->renegotiation_adjust = v4l2src->offset + 1;
- v4l2src->pending_set_fmt = TRUE;
-
- if (!gst_base_src_negotiate (GST_BASE_SRC (src))) {
+ if (!gst_v4l2src_handle_resolution_change (v4l2src)) {
ret = GST_FLOW_NOT_NEGOTIATED;
goto error;
}
goto alloc_failed;
}
- ret = gst_v4l2_buffer_pool_process (pool, buf, NULL);
+ {
+ GstV4l2BufferPool *obj_pool =
+ GST_V4L2_BUFFER_POOL_CAST (gst_v4l2_object_get_buffer_pool (obj));
+ ret = gst_v4l2_buffer_pool_process (obj_pool, buf, NULL);
+ if (obj_pool)
+ gst_object_unref (obj_pool);
+
+ if (G_UNLIKELY (ret == GST_V4L2_FLOW_RESOLUTION_CHANGE)) {
+ if (!gst_v4l2src_handle_resolution_change (v4l2src)) {
+ ret = GST_FLOW_NOT_NEGOTIATED;
+ goto error;
+ }
+ }
+ }
} while (ret == GST_V4L2_FLOW_CORRUPTED_BUFFER ||
ret == GST_V4L2_FLOW_RESOLUTION_CHANGE);