summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorNicolas Dufresne <nicolas.dufresne@collabora.com>2014-03-04 18:31:27 -0500
committerNicolas Dufresne <nicolas.dufresne@collabora.com>2014-05-08 15:56:35 -0400
commitb78115662505a826405734d71626ffe9db5c3586 (patch)
treeb61d05a624bad1e9f5cf2e4caa899765c2a53ac1 /sys
parent0028de808bb7b00fb55d8dcc5ba29faffcfae7c3 (diff)
Implement GstV4l2Transform
Implement a v4l2 element that wraps HW video converters.
Diffstat (limited to 'sys')
-rw-r--r--sys/v4l2/Makefile.am2
-rw-r--r--sys/v4l2/gstv4l2.c4
-rw-r--r--sys/v4l2/gstv4l2object.c4
-rw-r--r--sys/v4l2/gstv4l2transform.c677
-rw-r--r--sys/v4l2/gstv4l2transform.h80
5 files changed, 767 insertions, 0 deletions
diff --git a/sys/v4l2/Makefile.am b/sys/v4l2/Makefile.am
index 248605fb7..1afed3219 100644
--- a/sys/v4l2/Makefile.am
+++ b/sys/v4l2/Makefile.am
@@ -11,6 +11,7 @@ libgstvideo4linux2_la_SOURCES = gstv4l2.c \
gstv4l2src.c \
gstv4l2radio.c \
gstv4l2tuner.c \
+ gstv4l2transform.c \
gstv4l2videodec.c \
gstv4l2vidorient.c \
v4l2_calls.c \
@@ -50,6 +51,7 @@ noinst_HEADERS = \
gstv4l2src.h \
gstv4l2radio.h \
gstv4l2tuner.h \
+ gstv4l2transform.h \
gstv4l2videodec.h \
gstv4l2vidorient.h \
v4l2_calls.h \
diff --git a/sys/v4l2/gstv4l2.c b/sys/v4l2/gstv4l2.c
index fc36d185c..db1052dd5 100644
--- a/sys/v4l2/gstv4l2.c
+++ b/sys/v4l2/gstv4l2.c
@@ -44,6 +44,7 @@
#include "gstv4l2radio.h"
#include "gstv4l2videodec.h"
#include "gstv4l2devicemonitor.h"
+#include "gstv4l2transform.h"
/* used in v4l2_calls.c and v4l2src_calls.c */
GST_DEBUG_CATEGORY (v4l2_debug);
@@ -148,6 +149,9 @@ gst_v4l2_probe_and_register (GstPlugin * plugin)
if (gst_v4l2_is_video_dec (sink_caps, src_caps))
ret = gst_v4l2_video_dec_register (plugin, basename, it->device_path,
sink_caps, src_caps);
+ else if (gst_v4l2_is_transform (sink_caps, src_caps))
+ ret = gst_v4l2_transform_register (plugin, basename, it->device_path,
+ sink_caps, src_caps);
/* else if ( ... etc. */
gst_caps_unref (sink_caps);
diff --git a/sys/v4l2/gstv4l2object.c b/sys/v4l2/gstv4l2object.c
index ee759030c..36a2968a2 100644
--- a/sys/v4l2/gstv4l2object.c
+++ b/sys/v4l2/gstv4l2object.c
@@ -1539,6 +1539,10 @@ gst_v4l2_object_add_aspect_ratio (GstV4l2Object * v4l2object, GstStructure * s)
num = cropcap.pixelaspect.numerator;
den = cropcap.pixelaspect.denominator;
+ /* Ignore PAR that are 0/0 */
+ if (den == 0)
+ return;
+
done:
gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION, num, den,
NULL);
diff --git a/sys/v4l2/gstv4l2transform.c b/sys/v4l2/gstv4l2transform.c
new file mode 100644
index 000000000..cf0c84e7a
--- /dev/null
+++ b/sys/v4l2/gstv4l2transform.c
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2014 Collabora Ltd.
+ * Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gstv4l2transform.h"
+#include "v4l2_calls.h"
+
+#include <string.h>
+#include <gst/gst-i18n-plugin.h>
+
+#define DEFAULT_PROP_DEVICE "/dev/video10"
+
+#define V4L2_TRANSFORM_QUARK \
+ g_quark_from_static_string("gst-v4l2-transform-info")
+
+GST_DEBUG_CATEGORY_STATIC (gst_v4l2_transform_debug);
+#define GST_CAT_DEFAULT gst_v4l2_transform_debug
+
+
+enum
+{
+ PROP_0,
+ V4L2_STD_OBJECT_PROPS,
+ PROP_CAPTURE_IO_MODE,
+};
+
+typedef struct
+{
+ gchar *device;
+ GstCaps *sink_caps;
+ GstCaps *src_caps;
+} GstV4l2TransformCData;
+
+#define gst_v4l2_transform_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE (GstV4l2Transform, gst_v4l2_transform,
+ GST_TYPE_BASE_TRANSFORM);
+
+static void
+gst_v4l2_transform_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (object);
+
+ switch (prop_id) {
+ /* Split IO mode so output is configure through 'io-mode' and capture
+ * through 'capture-io-mode' */
+ case PROP_IO_MODE:
+ gst_v4l2_object_set_property_helper (self->v4l2output, prop_id, value,
+ pspec);
+ break;
+ case PROP_CAPTURE_IO_MODE:
+ gst_v4l2_object_set_property_helper (self->v4l2capture, prop_id, value,
+ pspec);
+ break;
+
+ case PROP_DEVICE:
+ gst_v4l2_object_set_property_helper (self->v4l2output, prop_id, value,
+ pspec);
+ gst_v4l2_object_set_property_helper (self->v4l2capture, prop_id, value,
+ pspec);
+ break;
+
+ /* By default, only set on output */
+ default:
+ if (!gst_v4l2_object_set_property_helper (self->v4l2output,
+ prop_id, value, pspec)) {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+gst_v4l2_transform_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (object);
+
+ switch (prop_id) {
+ case PROP_IO_MODE:
+ gst_v4l2_object_get_property_helper (self->v4l2output, prop_id, value,
+ pspec);
+ break;
+ case PROP_CAPTURE_IO_MODE:
+ gst_v4l2_object_get_property_helper (self->v4l2output, PROP_IO_MODE,
+ value, pspec);
+ break;
+
+ /* By default read from output */
+ default:
+ if (!gst_v4l2_object_get_property_helper (self->v4l2output,
+ prop_id, value, pspec)) {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static gboolean
+gst_v4l2_transform_start (GstBaseTransform * trans)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+
+ GST_DEBUG_OBJECT (self, "Opening");
+
+ if (!gst_v4l2_object_open (self->v4l2output))
+ goto failure;
+
+ if (!gst_v4l2_object_open_shared (self->v4l2capture, self->v4l2output))
+ goto failure;
+
+ self->probed_sinkcaps = gst_v4l2_object_get_caps (self->v4l2output,
+ gst_v4l2_object_get_raw_caps ());
+
+ if (gst_caps_is_empty (self->probed_sinkcaps))
+ goto no_input_format;
+
+ self->probed_srccaps = gst_v4l2_object_get_caps (self->v4l2capture,
+ gst_v4l2_object_get_raw_caps ());
+
+ if (gst_caps_is_empty (self->probed_srccaps))
+ goto no_output_format;
+
+ return TRUE;
+
+no_input_format:
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ (_("Converter on device %s has no supported input format"),
+ self->v4l2output->videodev), (NULL));
+ goto failure;
+
+
+no_output_format:
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ (_("Converter on device %s has no supported output format"),
+ self->v4l2output->videodev), (NULL));
+ goto failure;
+
+failure:
+ if (GST_V4L2_IS_OPEN (self->v4l2output))
+ gst_v4l2_object_close (self->v4l2output);
+
+ if (GST_V4L2_IS_OPEN (self->v4l2capture))
+ gst_v4l2_object_close (self->v4l2capture);
+
+ gst_caps_replace (&self->probed_srccaps, NULL);
+ gst_caps_replace (&self->probed_sinkcaps, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+gst_v4l2_transform_stop (GstBaseTransform * trans)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+
+ GST_DEBUG_OBJECT (self, "Closing");
+
+ gst_v4l2_object_close (self->v4l2output);
+ gst_v4l2_object_close (self->v4l2capture);
+ gst_caps_replace (&self->probed_srccaps, NULL);
+ gst_caps_replace (&self->probed_sinkcaps, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+gst_v4l2_transform_set_caps (GstBaseTransform * trans, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+
+ /* TODO Add renegotiation support */
+ g_return_val_if_fail (!GST_V4L2_IS_ACTIVE (self->v4l2output), FALSE);
+ g_return_val_if_fail (!GST_V4L2_IS_ACTIVE (self->v4l2capture), FALSE);
+
+ if (!gst_v4l2_object_set_format (self->v4l2output, incaps))
+ goto incaps_failed;
+
+ if (!gst_v4l2_object_set_format (self->v4l2capture, outcaps))
+ goto outcaps_failed;
+
+ return TRUE;
+
+
+incaps_failed:
+ {
+ GST_ERROR_OBJECT (self, "failed to set input caps: %" GST_PTR_FORMAT,
+ incaps);
+ return FALSE;
+ }
+
+outcaps_failed:
+ {
+ gst_v4l2_object_stop (self->v4l2output);
+ GST_ERROR_OBJECT (self, "failed to set output caps: %" GST_PTR_FORMAT,
+ outcaps);
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_v4l2_transform_query (GstBaseTransform * trans, GstPadDirection direction,
+ GstQuery * query)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+ gboolean ret = TRUE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:{
+ GstCaps *filter, *caps = NULL, *result = NULL;
+ GstPad *pad, *otherpad;
+
+ gst_query_parse_caps (query, &filter);
+
+ if (direction == GST_PAD_SRC) {
+ pad = GST_BASE_TRANSFORM_SRC_PAD (trans);
+ otherpad = GST_BASE_TRANSFORM_SINK_PAD (trans);
+ if (self->probed_srccaps)
+ caps = gst_caps_ref (self->probed_srccaps);
+ } else {
+ pad = GST_BASE_TRANSFORM_SINK_PAD (trans);
+ otherpad = GST_BASE_TRANSFORM_SRC_PAD (trans);
+ if (self->probed_srccaps)
+ caps = gst_caps_ref (self->probed_srccaps);
+ }
+
+ if (!caps)
+ caps = gst_pad_get_pad_template_caps (pad);
+
+ if (filter) {
+ GstCaps *tmp = caps;
+ caps = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ }
+
+ result = gst_pad_peer_query_caps (otherpad, caps);
+ result = gst_caps_make_writable (result);
+ gst_caps_append (result, caps);
+
+ GST_DEBUG_OBJECT (self, "Returning %s caps %" GST_PTR_FORMAT,
+ GST_PAD_NAME (pad), result);
+
+ gst_query_set_caps_result (query, result);
+ gst_caps_unref (result);
+ break;
+ }
+
+ default:
+ ret = GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
+ query);
+ break;
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_v4l2_transform_decide_allocation (GstBaseTransform * trans,
+ GstQuery * query)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+ gboolean ret = FALSE;
+
+ if (gst_v4l2_object_decide_allocation (self->v4l2capture, query))
+ ret = GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
+ query);
+
+ return ret;
+}
+
+/* TODO */
+#if 0
+static gboolean
+gst_v4l2_transform_propose_allocation (GstBaseTransform * trans,
+ GstQuery * decide_query, GstQuery * query)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+ gboolean ret = FALSE;
+
+ /* FIXME propose alloc this need to be moved from src to v4l2object */
+ if (gst_v4l2_object_propose_allocation (self->v4l2output, query))
+ ret = GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans,
+ query);
+
+ return ret;
+}
+#endif
+
+/* copies the given caps */
+static GstCaps *
+gst_v4l2_transform_caps_remove_format_info (GstCaps * caps)
+{
+ GstStructure *st;
+ GstCapsFeatures *f;
+ gint i, n;
+ GstCaps *res;
+
+ res = gst_caps_new_empty ();
+
+ n = gst_caps_get_size (caps);
+ for (i = 0; i < n; i++) {
+ st = gst_caps_get_structure (caps, i);
+ f = gst_caps_get_features (caps, i);
+
+ /* If this is already expressed by the existing caps
+ * skip this structure */
+ if (i > 0 && gst_caps_is_subset_structure_full (res, st, f))
+ continue;
+
+ st = gst_structure_copy (st);
+ /* Only remove format info for the cases when we can actually convert */
+ if (!gst_caps_features_is_any (f)
+ && gst_caps_features_is_equal (f,
+ GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))
+ gst_structure_remove_fields (st, "format", "colorimetry", "chroma-site",
+ NULL);
+
+ gst_caps_append_structure_full (res, st, gst_caps_features_copy (f));
+ }
+
+ return res;
+}
+
+/* The caps can be transformed into any other caps with format info removed.
+ * However, we should prefer passthrough, so if passthrough is possible,
+ * put it first in the list. */
+static GstCaps *
+gst_v4l2_transform_transform_caps (GstBaseTransform * btrans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+{
+ GstCaps *tmp, *tmp2;
+ GstCaps *result;
+
+ /* Get all possible caps that we can transform to */
+ tmp = gst_v4l2_transform_caps_remove_format_info (caps);
+
+ if (filter) {
+ tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ tmp = tmp2;
+ }
+
+ result = tmp;
+
+ GST_DEBUG_OBJECT (btrans, "transformed %" GST_PTR_FORMAT " into %"
+ GST_PTR_FORMAT, caps, result);
+
+ return result;
+}
+
+static GstCaps *
+gst_v4l2_transform_fixate_caps (GstBaseTransform * trans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
+{
+ GstCaps *result;
+
+ GST_DEBUG_OBJECT (trans, "trying to fixate othercaps %" GST_PTR_FORMAT
+ " based on caps %" GST_PTR_FORMAT, othercaps, caps);
+
+ result = gst_caps_intersect (othercaps, caps);
+ if (gst_caps_is_empty (result)) {
+ gst_caps_unref (result);
+ result = othercaps;
+ } else {
+ gst_caps_unref (othercaps);
+ }
+
+ GST_DEBUG_OBJECT (trans, "now fixating %" GST_PTR_FORMAT, result);
+
+ result = gst_caps_fixate (result);
+
+ return result;
+}
+
+static GstFlowReturn
+gst_v4l2_transform_prepare_output_buffer (GstBaseTransform * trans,
+ GstBuffer * inbuf, GstBuffer ** outbuf)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+ GstBufferPool *pool = GST_BUFFER_POOL (self->v4l2output->pool);
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ if (gst_base_transform_is_passthrough (trans)) {
+ GST_DEBUG_OBJECT (self, "Passthrough, no need to do anything");
+ *outbuf = inbuf;
+ goto beach;
+ }
+
+ GST_DEBUG_OBJECT (self, "Queue input buffer");
+ ret = gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (pool), inbuf);
+ if (ret != GST_FLOW_OK)
+ goto beach;
+
+ pool = gst_base_transform_get_buffer_pool (trans);
+
+ if (!gst_buffer_pool_is_active (pool)) {
+ if (!gst_buffer_pool_set_active (pool, TRUE))
+ goto activate_failed;
+ }
+
+ GST_DEBUG_OBJECT (self, "Dequeue output buffer");
+ ret = gst_buffer_pool_acquire_buffer (pool, outbuf, NULL);
+ g_object_unref (pool);
+
+ if (ret != GST_FLOW_OK)
+ goto alloc_failed;
+
+ pool = self->v4l2capture->pool;
+ ret = gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (pool), *outbuf);
+
+ if (ret != GST_FLOW_OK) {
+ gst_buffer_unref (*outbuf);
+ *outbuf = NULL;
+ }
+
+beach:
+ return ret;
+
+activate_failed:
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ ("failed to activate bufferpool"), ("failed to activate bufferpool"));
+ return GST_FLOW_ERROR;
+
+alloc_failed:
+ GST_DEBUG_OBJECT (self, "could not allocate buffer from pool");
+ return ret;
+}
+
+static GstFlowReturn
+gst_v4l2_transform_transform (GstBaseTransform * trans, GstBuffer * inbuf,
+ GstBuffer * outbuf)
+{
+ /* Nothing to do */
+ return GST_FLOW_OK;
+}
+
+static gboolean
+gst_v4l2_transform_sink_event (GstBaseTransform * trans, GstEvent * event)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_START:
+ gst_v4l2_object_unlock (self->v4l2output);
+ gst_v4l2_object_unlock (self->v4l2capture);
+ default:
+ break;
+ }
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
+}
+
+static GstStateChangeReturn
+gst_v4l2_transform_change_state (GstElement * element,
+ GstStateChange transition)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (element);
+
+ if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
+ gst_v4l2_object_unlock (self->v4l2output);
+ gst_v4l2_object_unlock (self->v4l2capture);
+ }
+
+ return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+}
+
+static void
+gst_v4l2_transform_dispose (GObject * object)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (object);
+
+ gst_caps_replace (&self->probed_sinkcaps, NULL);
+ gst_caps_replace (&self->probed_srccaps, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_v4l2_transform_finalize (GObject * object)
+{
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (object);
+
+ gst_v4l2_object_destroy (self->v4l2capture);
+ gst_v4l2_object_destroy (self->v4l2output);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_v4l2_transform_init (GstV4l2Transform * self)
+{
+ /* V4L2 object are created in subinstance_init */
+}
+
+static void
+gst_v4l2_transform_class_init (GstV4l2TransformClass * klass)
+{
+ GstElementClass *element_class;
+ GObjectClass *gobject_class;
+ GstBaseTransformClass *base_transform_class;
+
+ element_class = (GstElementClass *) klass;
+ gobject_class = (GObjectClass *) klass;
+ base_transform_class = (GstBaseTransformClass *) klass;
+
+ gst_element_class_set_static_metadata (element_class,
+ "V4L2 Video Converter",
+ "Filter/Converter/Video",
+ "Transform streams via V4L2 API",
+ "Nicolas Dufresne <nicolas.dufresne@collabora.com>");
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_v4l2_transform_dispose);
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_v4l2_transform_finalize);
+ gobject_class->set_property =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_set_property);
+ gobject_class->get_property =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_get_property);
+
+ base_transform_class->start = GST_DEBUG_FUNCPTR (gst_v4l2_transform_start);
+ base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_transform_stop);
+ base_transform_class->set_caps =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_set_caps);
+ base_transform_class->query = GST_DEBUG_FUNCPTR (gst_v4l2_transform_query);
+ base_transform_class->sink_event =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_sink_event);
+ base_transform_class->decide_allocation =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_decide_allocation);
+ /* TODO */
+#if 0
+ base_transform_class->propose_allocation =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_propose_allocation);
+#endif
+ base_transform_class->transform_caps =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_transform_caps);
+ base_transform_class->fixate_caps =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_fixate_caps);
+ base_transform_class->prepare_output_buffer =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_prepare_output_buffer);
+ base_transform_class->transform =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_transform);
+
+ base_transform_class->passthrough_on_same_caps = TRUE;
+
+ /* FIXME need this ? */
+ element_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_v4l2_transform_change_state);
+
+ gst_v4l2_object_install_properties_helper (gobject_class,
+ DEFAULT_PROP_DEVICE);
+
+ /**
+ * GstV4l2Transform:capture-io-mode
+ *
+ * Capture IO Mode
+ */
+ g_object_class_install_property (gobject_class, PROP_IO_MODE,
+ g_param_spec_enum ("capture-io-mode", "Capture IO mode",
+ "Capture I/O mode",
+ GST_TYPE_V4L2_IO_MODE, GST_V4L2_IO_AUTO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_v4l2_transform_subclass_init (gpointer g_class, gpointer data)
+{
+ GstV4l2TransformClass *klass = GST_V4L2_TRANSFORM_CLASS (g_class);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+ GstV4l2TransformCData *cdata = data;
+
+ klass->default_device = cdata->device;
+
+ /* Note: gst_pad_template_new() take the floating ref from the caps */
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ cdata->sink_caps));
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ cdata->src_caps));
+
+ g_free (cdata);
+}
+
+static void
+gst_v4l2_transform_subinstance_init (GTypeInstance * instance, gpointer g_class)
+{
+ GstV4l2TransformClass *klass = GST_V4L2_TRANSFORM_CLASS (g_class);
+ GstV4l2Transform *self = GST_V4L2_TRANSFORM (instance);
+
+ self->v4l2output = gst_v4l2_object_new (GST_ELEMENT (self),
+ V4L2_BUF_TYPE_VIDEO_OUTPUT, klass->default_device,
+ gst_v4l2_get_output, gst_v4l2_set_output, NULL);
+ self->v4l2output->no_initial_format = TRUE;
+ self->v4l2output->keep_aspect = FALSE;
+
+ self->v4l2capture = gst_v4l2_object_new (GST_ELEMENT (self),
+ V4L2_BUF_TYPE_VIDEO_CAPTURE, klass->default_device,
+ gst_v4l2_get_input, gst_v4l2_set_input, NULL);
+ self->v4l2capture->no_initial_format = TRUE;
+ self->v4l2output->keep_aspect = FALSE;
+
+ g_object_set (self, "device", klass->default_device, NULL);
+}
+
+/* Probing functions */
+gboolean
+gst_v4l2_is_transform (GstCaps * sink_caps, GstCaps * src_caps)
+{
+ gboolean ret = FALSE;
+
+ if (gst_caps_is_subset (sink_caps, gst_v4l2_object_get_raw_caps ())
+ && gst_caps_is_subset (src_caps, gst_v4l2_object_get_raw_caps ()))
+ ret = TRUE;
+
+ return ret;
+}
+
+gboolean
+gst_v4l2_transform_register (GstPlugin * plugin, const gchar * basename,
+ const gchar * device_path, GstCaps * sink_caps, GstCaps * src_caps)
+{
+ GTypeQuery type_query;
+ GTypeInfo type_info = { 0, };
+ GType type, subtype;
+ gchar *type_name;
+ GstV4l2TransformCData *cdata;
+
+ cdata = g_new0 (GstV4l2TransformCData, 1);
+ cdata->device = g_strdup (device_path);
+ cdata->sink_caps = gst_caps_ref (sink_caps);
+ cdata->src_caps = gst_caps_ref (src_caps);
+
+ type = gst_v4l2_transform_get_type ();
+ g_type_query (type, &type_query);
+ memset (&type_info, 0, sizeof (type_info));
+ type_info.class_size = type_query.class_size;
+ type_info.instance_size = type_query.instance_size;
+ type_info.class_init = gst_v4l2_transform_subclass_init;
+ type_info.class_data = cdata;
+ type_info.instance_init = gst_v4l2_transform_subinstance_init;
+
+ type_name = g_strdup_printf ("v4l2%sconvert", basename);
+ subtype = g_type_register_static (type, type_name, &type_info, 0);
+
+ gst_element_register (plugin, type_name, GST_RANK_PRIMARY + 1, subtype);
+
+ g_free (type_name);
+
+ return TRUE;
+}
diff --git a/sys/v4l2/gstv4l2transform.h b/sys/v4l2/gstv4l2transform.h
new file mode 100644
index 000000000..6c26a9c1d
--- /dev/null
+++ b/sys/v4l2/gstv4l2transform.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 Collabora Ltd.
+ * Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GST_V4L2_TRANSFORM_H__
+#define __GST_V4L2_TRANSFORM_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+
+#include <gstv4l2object.h>
+#include <gstv4l2bufferpool.h>
+
+GST_DEBUG_CATEGORY_EXTERN (v4l2transform_debug);
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_V4L2_TRANSFORM \
+ (gst_v4l2_transform_get_type())
+#define GST_V4L2_TRANSFORM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_V4L2_TRANSFORM,GstV4l2Transform))
+#define GST_V4L2_TRANSFORM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_V4L2_TRANSFORM,GstV4l2TransformClass))
+#define GST_IS_V4L2_TRANSFORM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4L2_TRANSFORM))
+#define GST_IS_V4L2_TRANSFORM_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4L2_TRANSFORM))
+#define GST_V4L2_TRANSFORM_GET_CLASS(inst) \
+ (G_TYPE_INSTANCE_GET_CLASS ((inst),GST_TYPE_V4L2_TRANSFORM,GstV4l2TransformClass))
+
+typedef struct _GstV4l2Transform GstV4l2Transform;
+typedef struct _GstV4l2TransformClass GstV4l2TransformClass;
+
+struct _GstV4l2Transform
+{
+ GstBaseTransform parent;
+
+ /* < private > */
+ GstV4l2Object * v4l2output;
+ GstV4l2Object * v4l2capture;
+
+ /* pads */
+ GstCaps *probed_srccaps;
+ GstCaps *probed_sinkcaps;
+};
+
+struct _GstV4l2TransformClass
+{
+ GstBaseTransformClass parent_class;
+ gchar *default_device;
+};
+
+GType gst_v4l2_transform_get_type (void);
+
+gboolean gst_v4l2_is_transform (GstCaps * sink_caps, GstCaps * src_caps);
+gboolean gst_v4l2_transform_register (GstPlugin * plugin,
+ const gchar *basename,
+ const gchar *device_path,
+ GstCaps * sink_caps, GstCaps * src_caps);
+
+G_END_DECLS
+
+#endif /* __GST_V4L2_TRANSFORM_H__ */