Subject: This is a copy of the camerabin2,
 basecamerabin and photography plugins from gst-plugins-bad. They're needed
 for applications in Ubuntu main, so we move them here.

Forwarded: not-needed
Index: b/gst-libs/gst/basecamerabinsrc/basecamerabinsrc-prelude.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/basecamerabinsrc-prelude.h
@@ -0,0 +1,35 @@
+/* GStreamer BaseCameraBinSrc Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * basecamerasrc-prelude.h: prelude include header for gst-basecamerasrc library
+ *
+ * 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_BASE_CAMERA_BIN_SRC_PRELUDE_H__
+#define __GST_BASE_CAMERA_BIN_SRC_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_BASE_CAMERA_BIN_SRC_API
+# ifdef BUILDING_GST_BASE_CAMERA_BIN_SRC
+#  define GST_BASE_CAMERA_BIN_SRC_API GST_API_EXPORT         /* from config.h */
+# else
+#  define GST_BASE_CAMERA_BIN_SRC_API GST_API_IMPORT
+# endif
+#endif
+
+#endif /* __GST_BASE_CAMERA_BIN_SRC_PRELUDE_H__ */
Index: b/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.c
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.c
@@ -0,0 +1,587 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Texas Instruments, Inc
+ *
+ * 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.
+ */
+
+
+
+/**
+ * SECTION:element-basecamerasrc
+ *
+ * Base class for the camera source bin used by camerabin for capture.
+ * Sophisticated camera hardware can derive from this baseclass and map the
+ * features to this interface.
+ *
+ * The design mandates that the subclasses implement the following features and
+ * behaviour:
+ *
+ * * 3 pads: viewfinder, image capture, video capture
+ *
+ * During `construct_pipeline()` vmethod a subclass can add several elements into
+ * the bin and expose 3 srcs pads as ghostpads implementing the 3 pad templates.
+ *
+ * However the subclass is responsible for adding the pad templates for the
+ * source pads and they must be named "vidsrc", "imgsrc" and "vfsrc". The pad
+ * templates should be installed in the subclass' class_init function, like so:
+ * |[
+ * static void
+ * my_element_class_init (GstMyElementClass *klass)
+ * {
+ *   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+ *   // pad templates should be a #GstStaticPadTemplate with direction
+ *   // #GST_PAD_SRC and name "vidsrc", "imgsrc" and "vfsrc"
+ *   gst_element_class_add_static_pad_template (gstelement_class,
+ *       &amp;vidsrc_template);
+ *   gst_element_class_add_static_pad_template (gstelement_class,
+ *       &amp;imgsrc_template);
+ *   gst_element_class_add_static_pad_template (gstelement_class,
+ *       &amp;vfsrc_template);
+ *   // see #GstElementDetails
+ *   gst_element_class_set_details (gstelement_class, &amp;details);
+ * }
+ * ]|
+ *
+ * It is also possible to add regular pads from the subclass and implement the
+ * dataflow methods on these pads. This way all functionality can be implemented
+ * directly in the subclass without extra elements.
+ *
+ * The src will receive the capture mode from `GstCameraBin2` on the
+ * #GstBaseCameraSrc:mode property. Possible capture modes are defined in
+ * #GstCameraBinMode.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <gst/glib-compat-private.h>
+#include "gstbasecamerasrc.h"
+
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_CAPTURE_WIDTH 800
+#define DEFAULT_CAPTURE_HEIGHT 600
+#define DEFAULT_FPS_N 0         /* makes it use the default */
+#define DEFAULT_FPS_D 1
+#define DEFAULT_ZOOM MIN_ZOOM
+
+enum
+{
+  PROP_0,
+  PROP_MODE,
+  PROP_ZOOM,
+  PROP_MAX_ZOOM,
+  PROP_READY_FOR_CAPTURE,
+  PROP_POST_PREVIEW,
+  PROP_PREVIEW_CAPS,
+  PROP_PREVIEW_FILTER,
+  PROP_AUTO_START
+};
+
+enum
+{
+  /* action signals */
+  START_CAPTURE_SIGNAL,
+  STOP_CAPTURE_SIGNAL,
+  /* emit signals */
+  LAST_SIGNAL
+};
+
+#define DEFAULT_POST_PREVIEW TRUE
+#define DEFAULT_AUTO_START FALSE
+
+static guint basecamerasrc_signals[LAST_SIGNAL];
+
+GST_DEBUG_CATEGORY (base_camera_src_debug);
+#define GST_CAT_DEFAULT base_camera_src_debug
+
+#define parent_class gst_base_camera_src_parent_class
+G_DEFINE_TYPE (GstBaseCameraSrc, gst_base_camera_src, GST_TYPE_BIN);
+
+
+/* NOTE: we could provide a vmethod for derived class to overload to provide
+ * it's own implementation of interface..  but in all cases I can think of at
+ * moment, either the camerasrc itself, or some element within the bin, will
+ * be implementing the interface..
+ */
+
+/**
+ * gst_base_camera_src_set_mode:
+ * @self: the camerasrc bin
+ * @mode: the mode
+ *
+ * Set the chosen #GstCameraBinMode capture mode.
+ */
+gboolean
+gst_base_camera_src_set_mode (GstBaseCameraSrc * self, GstCameraBinMode mode)
+{
+  GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self);
+
+  g_return_val_if_fail (bclass->set_mode, FALSE);
+
+  if (bclass->set_mode (self, mode)) {
+    self->mode = mode;
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * gst_base_camera_src_setup_zoom:
+ * @self: camerasrc object
+ *
+ * Apply zoom configured to camerabin to capture.
+ */
+void
+gst_base_camera_src_setup_zoom (GstBaseCameraSrc * self)
+{
+  GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self);
+
+  g_return_if_fail (self->zoom);
+  g_return_if_fail (bclass->set_zoom);
+
+  bclass->set_zoom (self, self->zoom);
+}
+
+/**
+ * gst_base_camera_src_setup_preview:
+ * @self: camerasrc bin
+ * @preview_caps: preview caps to set
+ *
+ * Apply preview caps to preview pipeline and to video source.
+ */
+void
+gst_base_camera_src_setup_preview (GstBaseCameraSrc * self,
+    GstCaps * preview_caps)
+{
+  GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self);
+
+  if (self->preview_pipeline) {
+    GST_DEBUG_OBJECT (self,
+        "Setting preview pipeline caps %" GST_PTR_FORMAT, self->preview_caps);
+    gst_camerabin_preview_set_caps (self->preview_pipeline, preview_caps);
+  }
+
+  if (bclass->set_preview)
+    bclass->set_preview (self, preview_caps);
+}
+
+static void
+gst_base_camera_src_start_capture (GstBaseCameraSrc * src)
+{
+  GstBaseCameraSrcClass *klass = GST_BASE_CAMERA_SRC_GET_CLASS (src);
+
+  g_return_if_fail (klass->start_capture != NULL);
+
+  GST_DEBUG_OBJECT (src, "Starting capture");
+
+  g_mutex_lock (&src->capturing_mutex);
+  if (src->capturing) {
+    GST_WARNING_OBJECT (src, "Capturing already ongoing");
+    g_mutex_unlock (&src->capturing_mutex);
+
+    /* post a warning to notify camerabin2 that the capture failed */
+    GST_ELEMENT_WARNING (src, RESOURCE, BUSY, (NULL), (NULL));
+    return;
+  }
+
+  src->capturing = TRUE;
+  g_object_notify (G_OBJECT (src), "ready-for-capture");
+  if (klass->start_capture (src)) {
+    GST_DEBUG_OBJECT (src, "Capture started");
+  } else {
+    src->capturing = FALSE;
+    g_object_notify (G_OBJECT (src), "ready-for-capture");
+    GST_WARNING_OBJECT (src, "Failed to start capture");
+  }
+  g_mutex_unlock (&src->capturing_mutex);
+}
+
+static void
+gst_base_camera_src_stop_capture (GstBaseCameraSrc * src)
+{
+  GstBaseCameraSrcClass *klass = GST_BASE_CAMERA_SRC_GET_CLASS (src);
+
+  g_return_if_fail (klass->stop_capture != NULL);
+
+  g_mutex_lock (&src->capturing_mutex);
+  if (!src->capturing) {
+    GST_DEBUG_OBJECT (src, "No ongoing capture");
+    g_mutex_unlock (&src->capturing_mutex);
+    return;
+  }
+  klass->stop_capture (src);
+  g_mutex_unlock (&src->capturing_mutex);
+}
+
+void
+gst_base_camera_src_finish_capture (GstBaseCameraSrc * self)
+{
+  GST_DEBUG_OBJECT (self, "Finishing capture");
+  g_return_if_fail (self->capturing);
+  self->capturing = FALSE;
+  g_object_notify (G_OBJECT (self), "ready-for-capture");
+}
+
+static void
+gst_base_camera_src_dispose (GObject * object)
+{
+  GstBaseCameraSrc *src = GST_BASE_CAMERA_SRC_CAST (object);
+
+  g_mutex_clear (&src->capturing_mutex);
+
+  if (src->preview_pipeline) {
+    gst_camerabin_destroy_preview_pipeline (src->preview_pipeline);
+    src->preview_pipeline = NULL;
+  }
+
+  if (src->preview_caps)
+    gst_caps_replace (&src->preview_caps, NULL);
+
+  if (src->preview_filter) {
+    gst_object_unref (src->preview_filter);
+    src->preview_filter = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_base_camera_src_finalize (GstBaseCameraSrc * self)
+{
+  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (self));
+}
+
+static void
+gst_base_camera_src_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (object);
+
+  switch (prop_id) {
+    case PROP_MODE:
+      gst_base_camera_src_set_mode (GST_BASE_CAMERA_SRC (self),
+          g_value_get_enum (value));
+      break;
+    case PROP_ZOOM:{
+      self->zoom = g_value_get_float (value);
+      /* limit to max-zoom */
+      if (self->zoom > self->max_zoom) {
+        GST_DEBUG_OBJECT (self, "Clipping zoom %f to max-zoom %f", self->zoom,
+            self->max_zoom);
+        self->zoom = self->max_zoom;
+      }
+      /* does not set it if in NULL, the src is not created yet */
+      if (GST_STATE (self) != GST_STATE_NULL)
+        gst_base_camera_src_setup_zoom (self);
+      break;
+    }
+    case PROP_POST_PREVIEW:
+      self->post_preview = g_value_get_boolean (value);
+      break;
+    case PROP_PREVIEW_CAPS:{
+      GstCaps *new_caps;
+
+      new_caps = (GstCaps *) gst_value_get_caps (value);
+      if (new_caps == NULL) {
+        new_caps = gst_caps_new_any ();
+      } else {
+        new_caps = gst_caps_ref (new_caps);
+      }
+
+      if (!gst_caps_is_equal (self->preview_caps, new_caps)) {
+        gst_caps_replace (&self->preview_caps, new_caps);
+        gst_base_camera_src_setup_preview (self, new_caps);
+      } else {
+        GST_DEBUG_OBJECT (self, "New preview caps equal current preview caps");
+      }
+      gst_caps_unref (new_caps);
+    }
+      break;
+    case PROP_PREVIEW_FILTER:
+      if (self->preview_filter)
+        gst_object_unref (self->preview_filter);
+      self->preview_filter = g_value_dup_object (value);
+      if (!gst_camerabin_preview_set_filter (self->preview_pipeline,
+              self->preview_filter)) {
+        GST_WARNING_OBJECT (self,
+            "Cannot change preview filter, is element in NULL state?");
+      }
+      break;
+    case PROP_AUTO_START:
+      self->auto_start = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_base_camera_src_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (object);
+
+  switch (prop_id) {
+    case PROP_MODE:
+      g_value_set_enum (value, self->mode);
+      break;
+    case PROP_READY_FOR_CAPTURE:
+      g_value_set_boolean (value, !self->capturing);
+      break;
+    case PROP_ZOOM:
+      g_value_set_float (value, self->zoom);
+      break;
+    case PROP_MAX_ZOOM:
+      g_value_set_float (value, self->max_zoom);
+      break;
+    case PROP_POST_PREVIEW:
+      g_value_set_boolean (value, self->post_preview);
+      break;
+    case PROP_PREVIEW_CAPS:
+      if (self->preview_caps)
+        gst_value_set_caps (value, self->preview_caps);
+      break;
+    case PROP_PREVIEW_FILTER:
+      if (self->preview_filter)
+        g_value_set_object (value, self->preview_filter);
+      break;
+    case PROP_AUTO_START:
+      g_value_set_boolean (value, self->auto_start);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+construct_pipeline (GstBaseCameraSrc * self)
+{
+  GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self);
+
+  if (bclass->construct_pipeline) {
+    if (!bclass->construct_pipeline (self)) {
+      GST_ERROR_OBJECT (self, "pipeline construction failed");
+      return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+setup_pipeline (GstBaseCameraSrc * self)
+{
+  GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self);
+  if (bclass->setup_pipeline)
+    return bclass->setup_pipeline (self);
+  return TRUE;
+}
+
+static GstStateChangeReturn
+gst_base_camera_src_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (element);
+
+  GST_DEBUG_OBJECT (self, "%d -> %d",
+      GST_STATE_TRANSITION_CURRENT (transition),
+      GST_STATE_TRANSITION_NEXT (transition));
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!construct_pipeline (self))
+        return GST_STATE_CHANGE_FAILURE;
+
+      if (self->preview_pipeline == NULL) {
+        /* failed to create preview pipeline, fail state change */
+        return GST_STATE_CHANGE_FAILURE;
+      }
+
+      if (self->preview_caps) {
+        GST_DEBUG_OBJECT (self,
+            "Setting preview pipeline caps %" GST_PTR_FORMAT,
+            self->preview_caps);
+        gst_camerabin_preview_set_caps (self->preview_pipeline,
+            self->preview_caps);
+      }
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      if (!setup_pipeline (self))
+        return GST_STATE_CHANGE_FAILURE;
+      /* without this the preview pipeline will not post buffer
+       * messages on the pipeline */
+      gst_element_set_state (self->preview_pipeline->pipeline,
+          GST_STATE_PLAYING);
+      if (self->auto_start)
+        g_signal_emit_by_name (G_OBJECT (self), "start-capture", NULL);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_element_set_state (self->preview_pipeline->pipeline, GST_STATE_READY);
+      if (self->auto_start)
+        g_signal_emit_by_name (G_OBJECT (self), "stop-capture", NULL);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gst_element_set_state (self->preview_pipeline->pipeline, GST_STATE_NULL);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_base_camera_src_class_init (GstBaseCameraSrcClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  GST_DEBUG_CATEGORY_INIT (base_camera_src_debug, "base_camera_src", 0,
+      "Base camera src");
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  gobject_class->dispose = gst_base_camera_src_dispose;
+  gobject_class->finalize = (GObjectFinalizeFunc) gst_base_camera_src_finalize;
+  gobject_class->set_property = gst_base_camera_src_set_property;
+  gobject_class->get_property = gst_base_camera_src_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_MODE,
+      g_param_spec_enum ("mode", "Mode",
+          "The capture mode (still image capture or video recording)",
+          GST_TYPE_CAMERABIN_MODE, MODE_IMAGE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_ZOOM,
+      g_param_spec_float ("zoom", "Zoom",
+          "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT,
+          DEFAULT_ZOOM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_MAX_ZOOM,
+      g_param_spec_float ("max-zoom", "Maximum zoom level (note: may change "
+          "depending on resolution/implementation)",
+          "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT,
+          MAX_ZOOM, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstBaseCameraSrc:post-previews:
+   *
+   * When %TRUE, preview images should be posted to the bus when
+   * captures are made
+   */
+  g_object_class_install_property (gobject_class, PROP_POST_PREVIEW,
+      g_param_spec_boolean ("post-previews", "Post Previews",
+          "If capture preview images should be posted to the bus",
+          DEFAULT_POST_PREVIEW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_PREVIEW_CAPS,
+      g_param_spec_boxed ("preview-caps", "Preview caps",
+          "The caps of the preview image to be posted (NULL means ANY)",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_PREVIEW_FILTER,
+      g_param_spec_object ("preview-filter", "Preview filter",
+          "A custom preview filter to process preview image data",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_AUTO_START,
+      g_param_spec_boolean ("auto-start", "Auto start capture",
+          "Automatically starts capture when going to the PAUSED state",
+          DEFAULT_AUTO_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstBaseCameraSrc:ready-for-capture:
+   *
+   * When TRUE new capture can be prepared. If FALSE capturing is ongoing
+   * and starting a new capture immediately is not possible.
+   *
+   * Note that calling start-capture from the notify callback of this property
+   * will cause a deadlock. If you need to react like this on the notify
+   * function, please schedule a new thread to do it. If you're using glib's
+   * mainloop you can use g_idle_add() for example.
+   */
+  g_object_class_install_property (gobject_class, PROP_READY_FOR_CAPTURE,
+      g_param_spec_boolean ("ready-for-capture", "Ready for capture",
+          "Informs this element is ready for starting another capture",
+          TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+
+  /* Signals */
+  basecamerasrc_signals[START_CAPTURE_SIGNAL] =
+      g_signal_new_class_handler ("start-capture",
+      G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_base_camera_src_start_capture),
+      NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  basecamerasrc_signals[STOP_CAPTURE_SIGNAL] =
+      g_signal_new_class_handler ("stop-capture",
+      G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_base_camera_src_stop_capture),
+      NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  gstelement_class->change_state = gst_base_camera_src_change_state;
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Base class for camerabin src bin", "Source/Video",
+      "Abstracts capture device for camerabin2", "Rob Clark <rob@ti.com>");
+}
+
+static void
+gst_base_camera_src_init (GstBaseCameraSrc * self)
+{
+  self->width = DEFAULT_WIDTH;
+  self->height = DEFAULT_HEIGHT;
+  self->zoom = DEFAULT_ZOOM;
+  self->max_zoom = MAX_ZOOM;
+  self->mode = MODE_IMAGE;
+
+  self->auto_start = DEFAULT_AUTO_START;
+  self->capturing = FALSE;
+  g_mutex_init (&self->capturing_mutex);
+
+  self->post_preview = DEFAULT_POST_PREVIEW;
+  self->preview_caps = gst_caps_new_any ();
+
+  self->preview_pipeline =
+      gst_camerabin_create_preview_pipeline (GST_ELEMENT_CAST (self), NULL);
+}
+
+void
+gst_base_camera_src_post_preview (GstBaseCameraSrc * self, GstSample * sample)
+{
+  if (self->post_preview) {
+    gst_camerabin_preview_pipeline_post (self->preview_pipeline, sample);
+  } else {
+    GST_DEBUG_OBJECT (self, "Previews not enabled, not posting");
+  }
+}
Index: b/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.h
@@ -0,0 +1,159 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Texas Instruments, Inc
+ * Copyright (C) 2011 Thiago Santos <thiago.sousa.santos@collabora.com>
+ *
+ * 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_BASE_CAMERA_SRC_H__
+#define __GST_BASE_CAMERA_SRC_H__
+
+#ifndef GST_USE_UNSTABLE_API
+#warning "GstBaseCameraSrc is unstable API and may change in future."
+#warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+#endif
+
+#include <gst/gst.h>
+#include <gst/gstbin.h>
+#include "basecamerabinsrc-prelude.h"
+#include "gstcamerabin-enum.h"
+#include "gstcamerabinpreview.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_BASE_CAMERA_SRC \
+  (gst_base_camera_src_get_type())
+#define GST_BASE_CAMERA_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_CAMERA_SRC,GstBaseCameraSrc))
+#define GST_BASE_CAMERA_SRC_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BASE_CAMERA_SRC, GstBaseCameraSrcClass))
+#define GST_BASE_CAMERA_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_CAMERA_SRC,GstBaseCameraSrcClass))
+#define GST_IS_BASE_CAMERA_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_CAMERA_SRC))
+#define GST_IS_BASE_CAMERA_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_CAMERA_SRC))
+#define GST_BASE_CAMERA_SRC_CAST(obj) \
+  ((GstBaseCameraSrc *) (obj))
+GST_BASE_CAMERA_BIN_SRC_API
+GType gst_base_camera_src_get_type (void);
+
+typedef struct _GstBaseCameraSrc GstBaseCameraSrc;
+typedef struct _GstBaseCameraSrcClass GstBaseCameraSrcClass;
+
+#define GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME "vfsrc"
+#define GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME "imgsrc"
+#define GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME "vidsrc"
+
+#define GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME "preview-image"
+
+/**
+ * GstBaseCameraSrc:
+ */
+struct _GstBaseCameraSrc
+{
+  GstBin parent;
+
+  GstCameraBinMode mode;
+
+  gboolean auto_start;
+  gboolean capturing;
+  GMutex capturing_mutex;
+
+  /* Preview convert pipeline */
+  GstCaps *preview_caps;
+  gboolean post_preview;
+  GstElement *preview_filter;
+  GstCameraBinPreviewPipelineData *preview_pipeline;
+
+  /* Resolution of the buffers configured to camerabin */
+  gint width;
+  gint height;
+
+  gfloat zoom;
+  gfloat max_zoom;
+
+  gpointer _gst_reserved[GST_PADDING_LARGE];
+};
+
+
+/**
+ * GstBaseCameraSrcClass:
+ * @construct_pipeline: construct pipeline
+ * @setup_pipeline: configure pipeline for the chosen settings
+ * @set_zoom: set the zoom
+ * @set_mode: set the mode
+ */
+struct _GstBaseCameraSrcClass
+{
+  GstBinClass parent;
+
+  /* Construct pipeline. (called in GST_STATE_CHANGE_NULL_TO_READY) Optional. */
+  gboolean    (*construct_pipeline)  (GstBaseCameraSrc *self);
+
+  /* (called in GST_STATE_CHANGE_READY_TO_PAUSED). Optional. */
+  gboolean    (*setup_pipeline)      (GstBaseCameraSrc *self);
+
+  /* Set the zoom. If set, called when changing 'zoom' property. Optional. */
+  void        (*set_zoom)            (GstBaseCameraSrc *self, gfloat zoom);
+
+  /* Set the mode. If set, called when changing 'mode' property. Optional. */
+  gboolean    (*set_mode)            (GstBaseCameraSrc *self,
+                                      GstCameraBinMode mode);
+
+  /* Set preview caps. If set, called called when setting new 'preview-caps'. Optional. */
+  gboolean    (*set_preview)         (GstBaseCameraSrc *self,
+                                      GstCaps *preview_caps);
+
+  /* Called by the handler for 'start-capture'. Mandatory. */
+  gboolean (*start_capture) (GstBaseCameraSrc * src);
+
+  /* Called by the handler for 'stop-capture'. Mandatory. */
+  void (*stop_capture) (GstBaseCameraSrc * src);
+
+  gpointer _gst_reserved[GST_PADDING_LARGE];
+};
+
+
+/* FIXME: these should be properly namespaced if they're meant as exposed API */
+#ifndef __GI_SCANNER__
+#define MIN_ZOOM 1.0f
+#define MAX_ZOOM 10.0f
+#define ZOOM_1X MIN_ZOOM
+#endif /* !__GI_SCANNER__ */
+
+GST_BASE_CAMERA_BIN_SRC_API
+gboolean gst_base_camera_src_set_mode (GstBaseCameraSrc *self, GstCameraBinMode mode);
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_base_camera_src_setup_zoom (GstBaseCameraSrc * self);
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_base_camera_src_setup_preview (GstBaseCameraSrc * self, GstCaps * preview_caps);
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_base_camera_src_finish_capture (GstBaseCameraSrc *self);
+
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_base_camera_src_post_preview (GstBaseCameraSrc *self, GstSample * sample);
+// XXX add methods to get/set img capture and vid capture caps..
+
+G_END_DECLS
+
+#endif /* __GST_BASE_CAMERA_SRC_H__ */
Index: b/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.c
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.c
@@ -0,0 +1,45 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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 "gstcamerabin-enum.h"
+
+/**
+ * GstCameraBin2Mode:
+ */
+GType
+gst_camerabin_mode_get_type (void)
+{
+  static GType gtype = 0;
+
+  if (gtype == 0) {
+    static const GEnumValue values[] = {
+      /* {MODE_PREVIEW, "Preview mode (should be default?)", "mode-preview"}, */
+      {MODE_IMAGE, "Still image capture (default)", "mode-image"},
+      {MODE_VIDEO, "Video recording", "mode-video"},
+      {0, NULL, NULL}
+    };
+
+    gtype = g_enum_register_static ("GstCameraBin2Mode", values);
+  }
+  return gtype;
+}
Index: b/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.h
@@ -0,0 +1,68 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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_CAMERABIN_ENUM_H__
+#define __GST_CAMERABIN_ENUM_H__
+
+#ifndef GST_USE_UNSTABLE_API
+#warning "camerabin enums are unstable API and may change in future."
+#warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+#endif
+
+#include <gst/gst.h>
+#include "basecamerabinsrc-prelude.h"
+
+G_BEGIN_DECLS
+
+/* FIXME: these should be properly namespaced if they're meant as exposed API */
+#ifndef __GI_SCANNER__
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_CAPTURE_WIDTH 800
+#define DEFAULT_CAPTURE_HEIGHT 600
+#define DEFAULT_FPS_N 0         /* makes it use the default */
+#define DEFAULT_FPS_D 1
+#define DEFAULT_ZOOM MIN_ZOOM
+#endif /* !__GI_SCANNER__ */
+
+
+/* FIXME: properly namespace these enums */
+/**
+ * GstCameraBinMode:
+ * @MODE_IMAGE: image capture
+ * @MODE_VIDEO: video capture
+ *
+ * Capture mode to use.
+ */
+typedef enum
+{
+  /* MODE_PREVIEW = 0, No use for this */
+  MODE_IMAGE = 1,
+  MODE_VIDEO = 2,
+} GstCameraBinMode;
+
+/* FIXME: should be CAMERA_BIN_MODE and camera_bin_mode */
+#define GST_TYPE_CAMERABIN_MODE (gst_camerabin_mode_get_type ())
+GST_BASE_CAMERA_BIN_SRC_API
+GType gst_camerabin_mode_get_type (void);
+
+G_END_DECLS
+
+#endif                          /* #ifndef __GST_CAMERABIN_ENUM_H__ */
Index: b/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.c
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.c
@@ -0,0 +1,409 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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.
+ */
+
+/**
+ * SECTION:camerabingeneral
+ * @short_description: helper functions for #GstCameraBin and it's modules
+ *
+ * Common helper functions for #GstCameraBin.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+#include <gst/glib-compat-private.h>
+#include "gstcamerabinpreview.h"
+#include "gstbasecamerasrc.h"
+
+GST_DEBUG_CATEGORY_EXTERN (base_camera_src_debug);
+#define GST_CAT_DEFAULT base_camera_src_debug
+
+static void _gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData *
+    preview, GstCaps * caps);
+
+static gboolean
+bus_callback (GstBus * bus, GstMessage * message, gpointer user_data)
+{
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err;
+      GstCameraBinPreviewPipelineData *data;
+
+      data = user_data;
+
+      gst_message_parse_error (message, &err, NULL);
+      GST_WARNING ("Error from preview pipeline: %s", err->message);
+      g_error_free (err);
+
+      /* TODO Not sure if we should post an Error or Warning here */
+      GST_ELEMENT_ERROR (data->element, CORE, FAILED,
+          ("fatal error in preview pipeline, disposing the pipeline"), (NULL));
+
+      /* Possible error situations:
+       * 1) cond_wait pending. prevent deadlock by signalling the cond
+       * 2) preview_pipeline_post called with new buffer to handle. returns
+       *    because data->pipeline is set to null
+       * 3) new preview caps incoming. returns because data->pipeline is null
+       */
+
+      if (data->pipeline) {
+        gst_element_set_state (data->pipeline, GST_STATE_NULL);
+        gst_object_unref (data->pipeline);
+        data->pipeline = NULL;
+      }
+
+      g_cond_signal (&data->processing_cond);
+
+      break;
+    }
+    default:
+      break;
+  }
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_camerabin_preview_pipeline_new_sample (GstAppSink * appsink,
+    gpointer user_data)
+{
+  GstSample *sample;
+  GstStructure *s;
+  GstMessage *msg;
+  GstCameraBinPreviewPipelineData *data;
+
+  data = user_data;
+
+  sample = gst_app_sink_pull_sample (appsink);
+  s = gst_structure_new (GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME,
+      "sample", GST_TYPE_SAMPLE, sample, NULL);
+  gst_sample_unref (sample);
+  msg = gst_message_new_element (GST_OBJECT (data->element), s);
+
+  GST_DEBUG_OBJECT (data->element, "sending message with preview image");
+  if (gst_element_post_message (data->element, msg) == FALSE) {
+    GST_WARNING_OBJECT (data->element,
+        "This element has no bus, therefore no message sent!");
+  }
+
+  g_mutex_lock (&data->processing_lock);
+
+  data->processing--;
+  if (data->processing == 0)
+    g_cond_signal (&data->processing_cond);
+
+  g_mutex_unlock (&data->processing_lock);
+
+  return GST_FLOW_OK;
+}
+
+/**
+ * gst_camerabin_create_preview_pipeline:
+ * @element: Owner of this pipeline
+ * @filter: Custom filter to process preview data (an extra ref is taken)
+ *
+ * Creates a new previewing pipeline that can receive buffers
+ * to be posted as camerabin preview messages for @element
+ *
+ * Returns: The newly created #GstCameraBinPreviewPipelineData
+ */
+GstCameraBinPreviewPipelineData *
+gst_camerabin_create_preview_pipeline (GstElement * element,
+    GstElement * filter)
+{
+  GstCameraBinPreviewPipelineData *data;
+  GstElement *csp;
+  GstElement *vscale;
+  gboolean added = FALSE;
+  gboolean linkfail = FALSE;
+  GstBus *bus;
+  GstAppSinkCallbacks callbacks = { 0, };
+
+  data = g_new0 (GstCameraBinPreviewPipelineData, 1);
+
+  data->pipeline = gst_pipeline_new ("preview-pipeline");
+  data->appsrc = gst_element_factory_make ("appsrc", "preview-appsrc");
+  data->appsink = gst_element_factory_make ("appsink", "preview-appsink");
+  csp = gst_element_factory_make ("videoconvert", "preview-vconv");
+  vscale = gst_element_factory_make ("videoscale", "preview-vscale");
+
+  if (!data->appsrc || !data->appsink || !csp || !vscale) {
+    goto error;
+  }
+
+  g_object_set (data->appsrc, "emit-signals", FALSE, NULL);
+  g_object_set (data->appsink, "sync", FALSE, "enable-last-sample",
+      FALSE, NULL);
+
+  gst_bin_add_many (GST_BIN (data->pipeline), data->appsrc,
+      data->appsink, csp, vscale, NULL);
+  if (filter)
+    gst_bin_add (GST_BIN (data->pipeline), gst_object_ref (filter));
+  added = TRUE;
+
+  if (filter) {
+    linkfail |=
+        GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src",
+            filter, NULL, GST_PAD_LINK_CHECK_NOTHING));
+    linkfail |=
+        GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL,
+            vscale, "sink", GST_PAD_LINK_CHECK_CAPS));
+  } else {
+    linkfail |=
+        GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src",
+            vscale, "sink", GST_PAD_LINK_CHECK_NOTHING));
+  }
+
+  linkfail |=
+      GST_PAD_LINK_FAILED (gst_element_link_pads_full (vscale, "src", csp,
+          "sink", GST_PAD_LINK_CHECK_NOTHING));
+  linkfail |=
+      GST_PAD_LINK_FAILED (gst_element_link_pads_full (csp, "src",
+          data->appsink, "sink", GST_PAD_LINK_CHECK_NOTHING));
+
+  if (linkfail) {
+    GST_WARNING ("Failed to link preview pipeline elements");
+    goto error;
+  }
+
+  callbacks.new_sample = gst_camerabin_preview_pipeline_new_sample;
+  gst_app_sink_set_callbacks ((GstAppSink *) data->appsink, &callbacks, data,
+      NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
+  gst_bus_add_watch (bus, bus_callback, data);
+  gst_object_unref (bus);
+
+  data->element = element;
+  data->filter = filter;
+  data->vscale = vscale;
+
+  g_mutex_init (&data->processing_lock);
+  g_cond_init (&data->processing_cond);
+
+  data->pending_preview_caps = NULL;
+  data->processing = 0;
+
+  return data;
+error:
+  GST_WARNING ("Failed to create camerabin's preview pipeline");
+  if (!added) {
+    if (csp)
+      gst_object_unref (csp);
+    if (vscale)
+      gst_object_unref (vscale);
+    if (data->appsrc)
+      gst_object_unref (data->appsrc);
+    if (data->appsink)
+      gst_object_unref (data->appsink);
+  }
+  gst_camerabin_destroy_preview_pipeline (data);
+  return NULL;
+}
+
+/**
+ * gst_camerabin_destroy_preview_pipeline:
+ * @preview: the #GstCameraBinPreviewPipelineData
+ *
+ * Frees a #GstCameraBinPreviewPipelineData
+ */
+void
+gst_camerabin_destroy_preview_pipeline (GstCameraBinPreviewPipelineData *
+    preview)
+{
+  g_return_if_fail (preview != NULL);
+
+  g_mutex_clear (&preview->processing_lock);
+  g_cond_clear (&preview->processing_cond);
+
+  if (preview->pipeline) {
+    GstBus *bus;
+
+    gst_element_set_state (preview->pipeline, GST_STATE_NULL);
+
+    bus = gst_pipeline_get_bus (GST_PIPELINE (preview->pipeline));
+    gst_bus_remove_watch (bus);
+    gst_object_unref (bus);
+
+    gst_object_unref (preview->pipeline);
+  }
+  g_free (preview);
+}
+
+/**
+ * gst_camerabin_preview_pipeline_post:
+ * @preview: the #GstCameraBinPreviewPipelineData
+ * @sample: the sample to be posted as a preview
+ *
+ * Converts the @sample to the desired format and posts the preview
+ * message to the bus.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gst_camerabin_preview_pipeline_post (GstCameraBinPreviewPipelineData * preview,
+    GstSample * sample)
+{
+  g_return_val_if_fail (preview != NULL, FALSE);
+  g_return_val_if_fail (preview->pipeline != NULL, FALSE);
+  g_return_val_if_fail (sample, FALSE);
+
+  g_mutex_lock (&preview->processing_lock);
+  g_return_val_if_fail (preview->pipeline != NULL, FALSE);
+
+  if (preview->pending_preview_caps) {
+    if (preview->processing > 0) {
+      g_cond_wait (&preview->processing_cond, &preview->processing_lock);
+    }
+    _gst_camerabin_preview_set_caps (preview, preview->pending_preview_caps);
+    gst_caps_replace (&preview->pending_preview_caps, NULL);
+  }
+
+  preview->processing++;
+
+  g_object_set (preview->appsrc, "caps", gst_sample_get_caps (sample), NULL);
+  gst_app_src_push_buffer ((GstAppSrc *) preview->appsrc,
+      gst_buffer_ref (gst_sample_get_buffer (sample)));
+
+  g_mutex_unlock (&preview->processing_lock);
+
+  return TRUE;
+}
+
+static void
+_gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview,
+    GstCaps * caps)
+{
+  GstState state, pending;
+  GstStateChangeReturn ret;
+
+  g_return_if_fail (preview != NULL);
+  g_return_if_fail (preview->pipeline != NULL);
+
+  ret = gst_element_get_state (preview->pipeline, &state, &pending, 0);
+  if (ret == GST_STATE_CHANGE_FAILURE) {
+    /* make it try again */
+    state = GST_STATE_PLAYING;
+    pending = GST_STATE_VOID_PENDING;
+  }
+  gst_element_set_state (preview->pipeline, GST_STATE_NULL);
+  g_object_set (preview->appsink, "caps", caps, NULL);
+  if (pending != GST_STATE_VOID_PENDING)
+    state = pending;
+  gst_element_set_state (preview->pipeline, state);
+}
+
+/**
+ * gst_camerabin_preview_set_caps:
+ * @preview: the #GstCameraBinPreviewPipelineData
+ * @caps: the #GstCaps to be set (a new ref will be taken)
+ *
+ * The caps that preview buffers should have when posted
+ * on the bus
+ */
+void
+gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview,
+    GstCaps * caps)
+{
+  g_return_if_fail (preview != NULL);
+
+  g_mutex_lock (&preview->processing_lock);
+
+  if (preview->processing == 0) {
+    _gst_camerabin_preview_set_caps (preview, caps);
+  } else {
+    GST_DEBUG ("Preview pipeline busy, storing new caps as pending");
+    gst_caps_replace (&preview->pending_preview_caps, caps);
+  }
+  g_mutex_unlock (&preview->processing_lock);
+}
+
+/**
+ * gst_camerabin_preview_set_filter:
+ * @preview: the #GstCameraBinPreviewPipelineData
+ * @filter: Custom filter to process preview data (an extra ref is taken)
+ *
+ * Set the filter element into preview pipeline.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gst_camerabin_preview_set_filter (GstCameraBinPreviewPipelineData * preview,
+    GstElement * filter)
+{
+  gboolean ret = TRUE;
+  GstState current;
+
+  g_return_val_if_fail (preview != NULL, FALSE);
+
+  GST_DEBUG ("Preview pipeline setting new filter %p", filter);
+
+  g_mutex_lock (&preview->processing_lock);
+
+  gst_element_get_state (preview->pipeline, &current, NULL, 0);
+
+  if (preview->processing == 0 && current == GST_STATE_NULL) {
+    gboolean linkfail = FALSE;
+
+    if (preview->filter) {
+      /* Unlink and remove old filter */
+      gst_element_unlink (preview->appsrc, preview->filter);
+      gst_element_unlink (preview->filter, preview->vscale);
+      gst_bin_remove (GST_BIN (preview->pipeline), preview->filter);
+    } else {
+      /* Make room for filter by breaking the link between appsrc and vcale */
+      gst_element_unlink (preview->appsrc, preview->vscale);
+    }
+
+    if (filter) {
+      /* Add and link the new filter between appsrc and vscale */
+      gst_bin_add (GST_BIN (preview->pipeline), gst_object_ref (filter));
+
+      linkfail |=
+          GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc,
+              "src", filter, NULL, GST_PAD_LINK_CHECK_NOTHING));
+
+      linkfail |=
+          GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL,
+              preview->vscale, "sink", GST_PAD_LINK_CHECK_CAPS));
+    } else {
+      /* No filter was given. Just link the appsrc to vscale directly */
+      linkfail |=
+          GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc,
+              "src", preview->vscale, "sink", GST_PAD_LINK_CHECK_NOTHING));
+    }
+
+    if (linkfail) {
+      GST_WARNING ("Linking the filter to pipeline failed");
+      ret = FALSE;
+    } else {
+      GST_DEBUG ("Linking the filter to pipeline successful");
+      preview->filter = filter;
+    }
+  } else {
+    GST_WARNING ("Cannot change filter when pipeline is running");
+    ret = FALSE;
+  }
+  g_mutex_unlock (&preview->processing_lock);
+
+  return ret;
+}
Index: b/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.h
@@ -0,0 +1,69 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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 __CAMERABIN_PREVIEW_H_
+#define __CAMERABIN_PREVIEW_H_
+
+#ifndef GST_USE_UNSTABLE_API
+#warning "camera bin preview is unstable API and may change in future."
+#warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+#endif
+
+#include <gst/gst.h>
+#include "basecamerabinsrc-prelude.h"
+
+/**
+ * GstCameraBinPreviewPipelineData: (skip)
+ */
+typedef struct
+{
+  GstElement *pipeline;
+
+  GstElement *appsrc;
+  GstElement *filter;
+  GstElement *appsink;
+  GstElement *vscale;
+
+  GstElement *element;
+
+  GstCaps *pending_preview_caps;
+  guint processing;
+  GMutex processing_lock;
+  GCond processing_cond;
+
+} GstCameraBinPreviewPipelineData;
+
+GST_BASE_CAMERA_BIN_SRC_API
+GstCameraBinPreviewPipelineData *gst_camerabin_create_preview_pipeline (GstElement * element, GstElement * filter);
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_camerabin_destroy_preview_pipeline (GstCameraBinPreviewPipelineData * preview);
+
+GST_BASE_CAMERA_BIN_SRC_API
+gboolean gst_camerabin_preview_pipeline_post (GstCameraBinPreviewPipelineData * preview, GstSample * sample);
+
+GST_BASE_CAMERA_BIN_SRC_API
+void gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview, GstCaps * caps);
+
+GST_BASE_CAMERA_BIN_SRC_API
+gboolean gst_camerabin_preview_set_filter (GstCameraBinPreviewPipelineData * preview, GstElement * filter);
+
+#endif /* #ifndef __CAMERABIN_PREVIEW_H_ */
Index: b/gst-libs/gst/basecamerabinsrc/meson.build
===================================================================
--- /dev/null
+++ b/gst-libs/gst/basecamerabinsrc/meson.build
@@ -0,0 +1,45 @@
+camerabin_sources = [
+  'gstcamerabin-enum.c',
+  'gstcamerabinpreview.c',
+  'gstbasecamerasrc.c',
+]
+camerabin_headers = [
+  'basecamerabinsrc-prelude.h',
+  'gstcamerabin-enum.h',
+  'gstcamerabinpreview.h',
+  'gstbasecamerasrc.h',
+]
+install_headers(camerabin_headers, subdir : 'gstreamer-1.0/gst/basecamerabinsrc')
+
+gstbasecamerabin = library('gstbasecamerabinsrc-' + api_version,
+  camerabin_sources,
+  c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API', '-DBUILDING_GST_BASE_CAMERA_BIN_SRC'],
+  include_directories : [configinc, libsinc],
+  version : libversion,
+  soversion : soversion,
+  darwin_versions : osxversion,
+  install : true,
+  dependencies : [gstapp_dep],
+)
+
+_sources = []
+if build_gir
+  basecamerabin_gir = gnome.generate_gir(gstbasecamerabin,
+    sources : camerabin_sources + camerabin_headers,
+    namespace : 'GstBadBaseCameraBin',
+    nsversion : api_version,
+    identifier_prefix : 'Gst',
+    symbol_prefix : 'gst',
+    export_packages : 'gstreamer-bad-base-camerabinsrc-1.0',
+    includes : ['Gst-1.0', 'GstApp-1.0'],
+    install : false, # Only for the documentation
+    extra_args : gir_init_section + ['-DGST_USE_UNSTABLE_API'],
+    dependencies : [gstapp_dep],
+    build_by_default : true,
+  )
+  _sources += [basecamerabin_gir]
+endif
+
+gstbasecamerabin_dep = declare_dependency(link_with : gstbasecamerabin,
+  include_directories : [libsinc],
+  dependencies : [gstapp_dep])
Index: b/gst/camerabin2/PORTING
===================================================================
--- /dev/null
+++ b/gst/camerabin2/PORTING
@@ -0,0 +1,39 @@
+This document lists the differenced between camerabin and camerabin2 from
+the API point of view and should be used to help on porting applications
+from camerabin to camerabin2.
+
+* Setting the location for the captures:
+camerabin requires that the path of the file to save the captures is set before
+each capture. Camerabin2 allows the application to use a multifilesink-like
+approach, the application can set a file with a '%d' marker, this marker
+will be automatically replaced by a number and be autoincremented after each
+capture.
+The property is now called 'location' instead of 'filename'
+
+* Capture signals
+The signals were renamed from capture-start/stop to start/stop-capture as
+this is the usual naming on actions.
+Additionally, stop-capture is now async, the user should check 'idle' property
+to be sure that it can shut camerabin2.
+
+* image-done
+In camerabin, image-done is a signal, in camerabin2, it is a bus message
+
+* video recording encoder/muxer
+In camerabin, video/audio encoder/muxer are selected by passing GstElements to
+camerabin properties. In camerabin2, a GstEncodingProfile is passed as a 
+property and encodebin manages to instantiate the elements for the format.
+
+* Previews
+new "post-previews" property for enabling/disabling preview image posting
+set location=NULL to skip writing image to file but to receive the preview,
+useful for scenarios that wants the image in memory.
+
+* Configuring resolution and framerate
+Camerabin2 has new GstCaps type properties for configuring capture and
+viewfinder formats:
+  video-capture-caps
+  image-capture-caps
+  audio-capture-caps
+  viewfinder-caps
+
Index: b/gst/camerabin2/camerabin2-src.txt
===================================================================
--- /dev/null
+++ b/gst/camerabin2/camerabin2-src.txt
@@ -0,0 +1,73 @@
+=== Camerabin2 Source Requirements (draft) ===
+
+This small document contains a collection of notes on different requirements
+of a camerabin2 source element.
+
+
+-- General --
+It is recommended that camerabin2 source elements inherit from basecamerasrc
+from gst-plugins-bad.
+
+
+-- Pads --
+Camerabin2 sources must have 3 static pads named 'vfsrc', 'imgsrc' and
+'vidsrc'.
+
+From an external point of view, all 3 pads work independently and camerabin2
+makes no assumptions about relations about them (caps they can produce, or if
+the same buffer is pushed to 2 different pads).
+
+'vfsrc' is the pad where the viewfinder buffers should be pushed, it will
+be feeding a video sink. This is the same scenario as a 'regular' source
+feeding a video sink. Buffers should be continuously pushed on this pad.
+
+'imgsrc' is the pad where image capture buffers are pushed. Timestamps aren't
+really important here as the images are going to be encoded and saved
+separately from each other. For each capture in image mode, one buffer should
+be pushed on this pad.
+
+'vidsrc' is the pad where video capture buffers are pushed. Once capture is
+started, buffers should start being pushed on this pad until the capture is
+stopped.
+-> TODO - define how segments/timestamps should work here
+-> TODO - How to make audio and video sync properly
+
+
+-- Capture --
+The sources should have a 'mode' property that informs the source of the
+current capturing mode. The available options are image or video.
+
+There are 2 signals that should be implemented, start-capture and
+stop-capture, they take no arguments.
+
+On image mode, start-capture tells the source to push an image capture
+buffer on its imgsrc pad. For video mode, start-capture tells the source
+to start pushing buffers on the vidsrc pad, it should only stop
+pushing when a stop-capture signal is received. In either case, it is
+recommended that the viewfinder pad keeps pushing buffers so the user
+has a smooth experience.
+
+Note that basecamerasrc already has the mode property and start/stop-capture
+signals. It has functions that should be overridden by its child classes to
+implement the handling of these actions.
+
+
+-- Previews --
+Camerabin2 sources must have a post-previews boolean property that the user
+can select if we wants or not preview images.
+
+Previews are posted on the bus as custom 'preview-image' messages. This message
+must have a 'buffer' field that contains a GstBuffer, the preview.
+
+Additionally, there should be a preview-caps property that is used to inform the
+camera source what is the expected format of the preview image.
+
+A preview image should be posted for each capture.
+
+
+-- Negotiation --
+Capture caps selection on camerabin2 works just like gstreamer's default
+caps negotiation. Camerabin2 puts capsfilters downstream from each of the
+camera source pads. The camera source can simply get_caps on the peer of
+each of its pads to know what are the allowed caps for that pad.
+
Index: b/gst/camerabin2/camerabingeneral.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/camerabingeneral.c
@@ -0,0 +1,260 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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.
+ */
+
+/**
+ * SECTION:camerabingeneral
+ * @title: GstCameraBin2
+ * @short_description: helper functions for #GstCameraBin2 and it's modules
+ *
+ * Common helper functions for #GstCameraBin2, #GstCameraBin2Image and
+ * #GstCameraBin2Video.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+#include <gst/basecamerabinsrc/gstbasecamerasrc.h>
+#include <gst/gst-i18n-plugin.h>
+
+#include "camerabingeneral.h"
+
+/**
+ * gst_camerabin_add_element:
+ * @bin: add an element to this bin
+ * @new_elem: new element to be added
+ *
+ * Adds given element to given @bin. Looks for an unconnected src pad
+ * from the @bin and links the element to it.  Raises an error if adding
+ * or linking failed. Unrefs the element in the case of an error.
+ *
+ * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise.
+ */
+gboolean
+gst_camerabin_add_element (GstBin * bin, GstElement * new_elem)
+{
+  return gst_camerabin_add_element_full (bin, NULL, new_elem, NULL);
+}
+
+/**
+ * gst_camerabin_add_element_full:
+ * @bin: add an element to this bin
+ * @srcpad:  src pad name, or NULL for any
+ * @new_elem: new element to be added
+ * @dstpad:  dst pad name, or NULL for any
+ *
+ * Adds given element to given @bin. Looks for an unconnected src pad
+ * (with name @srcpad, if specified) from the @bin and links the element
+ * to it.  Raises an error if adding or linking failed. Unrefs the element
+ * in the case of an error.
+ *
+ * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise.
+ */
+gboolean
+gst_camerabin_add_element_full (GstBin * bin, const gchar * srcpad,
+    GstElement * new_elem, const gchar * dstpad)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (bin, FALSE);
+  g_return_val_if_fail (new_elem, FALSE);
+
+  ret = gst_camerabin_try_add_element (bin, srcpad, new_elem, dstpad);
+
+  if (!ret) {
+    gchar *elem_name = gst_element_get_name (new_elem);
+    GST_ELEMENT_ERROR (bin, CORE, NEGOTIATION, (NULL),
+        ("linking %s failed", elem_name));
+    g_free (elem_name);
+    gst_object_unref (new_elem);
+  }
+
+  return ret;
+}
+
+/**
+ * gst_camerabin_try_add_element:
+ * @bin: tries adding an element to this bin
+ * @srcpad:  src pad name, or NULL for any
+ * @new_elem: new element to be added
+ * @dstpad:  dst pad name, or NULL for any
+ *
+ * Adds given element to given @bin. Looks for an unconnected src pad
+ * (with name @srcpad, if specified) from the @bin and links the element to
+ * it.
+ *
+ * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise.
+ */
+gboolean
+gst_camerabin_try_add_element (GstBin * bin, const gchar * srcpad,
+    GstElement * new_elem, const gchar * dstpad)
+{
+  GstPad *bin_pad;
+  GstElement *bin_elem;
+  gboolean ret = TRUE;
+
+  g_return_val_if_fail (bin, FALSE);
+  g_return_val_if_fail (new_elem, FALSE);
+
+  /* Get pads for linking */
+  bin_pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC);
+  /* Add to bin */
+  gst_bin_add (GST_BIN (bin), new_elem);
+  /* Link, if unconnected pad was found, otherwise just add it to bin */
+  if (bin_pad) {
+    GST_DEBUG_OBJECT (bin, "linking %s to %s:%s", GST_OBJECT_NAME (new_elem),
+        GST_DEBUG_PAD_NAME (bin_pad));
+    bin_elem = gst_pad_get_parent_element (bin_pad);
+    gst_object_unref (bin_pad);
+    if (!gst_element_link_pads_full (bin_elem, srcpad, new_elem, dstpad,
+            GST_PAD_LINK_CHECK_CAPS)) {
+      gst_object_ref (new_elem);
+      gst_bin_remove (bin, new_elem);
+      ret = FALSE;
+    }
+    gst_object_unref (bin_elem);
+  } else {
+    GST_INFO_OBJECT (bin, "no unlinked source pad in bin");
+  }
+
+  return ret;
+}
+
+/**
+ * gst_camerabin_create_and_add_element:
+ * @bin: tries adding an element to this bin
+ * @elem_name: name of the element to be created
+ * @instance_name: name of the instance of the element to be created
+ *
+ * Creates an element according to given name and
+ * adds it to given @bin. Looks for an unconnected src pad
+ * from the @bin and links the element to it.
+ *
+ * Returns: pointer to the new element if successful, NULL otherwise.
+ */
+GstElement *
+gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name,
+    const gchar * instance_name)
+{
+  GstElement *new_elem;
+
+  g_return_val_if_fail (bin, FALSE);
+  g_return_val_if_fail (elem_name, FALSE);
+
+  new_elem = gst_element_factory_make (elem_name, instance_name);
+  if (!new_elem) {
+    GST_ELEMENT_ERROR (bin, CORE, MISSING_PLUGIN,
+        (_("Missing element '%s' - check your GStreamer installation."),
+            elem_name), (NULL));
+  } else if (!gst_camerabin_add_element (bin, new_elem)) {
+    new_elem = NULL;
+  }
+
+  return new_elem;
+}
+
+/* try to change the state of an element. This function returns the element
+ * when the state change could be performed. When this function returns NULL
+ * an error occurred and the element is unreffed. */
+static GstElement *
+try_element (GstElement * bin, GstElement * element)
+{
+  GstStateChangeReturn ret;
+
+  if (element) {
+    ret = gst_element_set_state (element, GST_STATE_READY);
+    if (ret == GST_STATE_CHANGE_FAILURE) {
+      GST_DEBUG_OBJECT (bin, "failed state change..");
+      gst_element_set_state (element, GST_STATE_NULL);
+      gst_object_unref (element);
+      element = NULL;
+    }
+  }
+  return element;
+}
+
+GstElement *
+gst_camerabin_setup_default_element (GstBin * bin, GstElement * user_elem,
+    const gchar * auto_elem_name, const gchar * default_elem_name,
+    const gchar * instance_name)
+{
+  GstElement *elem;
+
+  if (user_elem) {
+    GST_DEBUG_OBJECT (bin, "trying configured element");
+    elem = try_element (GST_ELEMENT_CAST (bin), gst_object_ref (user_elem));
+  } else {
+    /* only try fallback if no specific sink was chosen */
+    GST_DEBUG_OBJECT (bin, "trying %s", auto_elem_name);
+    elem = gst_element_factory_make (auto_elem_name, instance_name);
+    elem = try_element (GST_ELEMENT_CAST (bin), elem);
+    if (elem == NULL) {
+      /* if default sink from config.h is different then try it too */
+      if (strcmp (default_elem_name, auto_elem_name)) {
+        GST_DEBUG_OBJECT (bin, "trying %s", default_elem_name);
+        elem = gst_element_factory_make (default_elem_name, instance_name);
+        elem = try_element (GST_ELEMENT_CAST (bin), elem);
+      }
+    }
+  }
+  return elem;
+}
+
+/**
+ * gst_camerabin_remove_elements_from_bin:
+ * @bin: removes all elements from this bin
+ *
+ * Removes all elements from this @bin.
+ */
+void
+gst_camerabin_remove_elements_from_bin (GstBin * bin)
+{
+  GstIterator *iter = NULL;
+  GValue value = { 0 };
+  GstElement *elem = NULL;
+  gboolean done = FALSE;
+
+  iter = gst_bin_iterate_elements (bin);
+  while (!done) {
+    switch (gst_iterator_next (iter, &value)) {
+      case GST_ITERATOR_OK:
+        elem = (GstElement *) g_value_get_object (&value);
+        gst_bin_remove (bin, elem);
+        gst_element_set_state (GST_ELEMENT (elem), GST_STATE_NULL);
+        /* Iterator increased the element refcount, so unref */
+        g_value_unset (&value);
+        break;
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (iter);
+        break;
+      case GST_ITERATOR_ERROR:
+        GST_WARNING_OBJECT (bin, "error in iterating elements");
+        done = TRUE;
+        break;
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+    }
+  }
+  gst_iterator_free (iter);
+}
Index: b/gst/camerabin2/camerabingeneral.h
===================================================================
--- /dev/null
+++ b/gst/camerabin2/camerabingeneral.h
@@ -0,0 +1,37 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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 __CAMERABIN_GENERAL_H_
+#define __CAMERABIN_GENERAL_H_
+
+#include <gst/gst.h>
+
+gboolean gst_camerabin_try_add_element (GstBin * bin, const gchar * srcpad, GstElement * new_elem, const gchar * dstpad);
+gboolean gst_camerabin_add_element (GstBin * bin, GstElement * new_elem);
+gboolean gst_camerabin_add_element_full (GstBin * bin, const gchar * srcpad, GstElement * new_elem, const gchar * dstpad);
+
+GstElement *gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name, const gchar * instance_name);
+
+GstElement * gst_camerabin_setup_default_element (GstBin * bin, GstElement *user_elem, const gchar *auto_elem_name, const gchar *default_elem_name,
+    const gchar * instance_elem_name);
+
+void gst_camerabin_remove_elements_from_bin (GstBin * bin);
+
+#endif /* #ifndef __CAMERABIN_GENERAL_H_ */
Index: b/gst/camerabin2/gstcamerabin2.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstcamerabin2.c
@@ -0,0 +1,2401 @@
+/* GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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.
+ */
+/**
+ * SECTION:element-camerabin
+ * @title: camerabin
+ *
+ * CameraBin is a high-level camera object that encapsulates gstreamer
+ * elements, providing an API for controlling a digital camera.
+ *
+ * > Note that camerabin is still UNSTABLE and under development.
+ *
+ * CameraBin has the following main features:
+ *
+ * * Record videos
+ * * Capture pictures
+ * * Display a viewfinder
+ * * Post preview images for each capture (video and image)
+ *
+ * ## Usage
+ *
+ * Camerabin can be created using gst_element_factory_make() just like
+ * any other element. Video or image capture mode can be selected using
+ * the #GstCameraBin:mode property and the file to save the capture is
+ * selected using #GstCameraBin:location property.
+ *
+ * After creating camerabin, applications might want to do some
+ * customization (there's a section about this below), then select
+ * the desired mode and start capturing.
+ *
+ * In image capture mode, just send a #GstCameraBin::start-capture and a
+ * picture will be captured. When the picture is stored on the selected
+ * location, a %GST_MESSAGE_ELEMENT named 'image-done' will be posted on
+ * the #GstBus.
+ *
+ * In video capture mode, send a #GstCameraBin::start-capture to start
+ * recording, then send a #GstCameraBin::stop-capture to stop recording.
+ * Note that both signals are asynchronous, so, calling
+ * #GstCameraBin::stop-capture doesn't guarantee that the video has been
+ * properly finished yet. Applications should wait for the 'video-done'
+ * message to be posted on the bus.
+ *
+ * In both modes, if #GstCameraBin:post-previews is %TRUE, a #GstBuffer
+ * will be post to the #GstBus in a field named 'buffer', in a
+ * 'preview-image' message of type %GST_MESSAGE_ELEMENT.
+ *
+
+ *
+ * ## Customization
+ *
+ * Camerabin provides various customization properties, allowing the user
+ * to set custom filters, selecting the viewfinder sink and formats to
+ * use to encode the captured images/videos.
+ *
+ * #GstEncodingProfile<!-- -->s are used to tell camerabin which formats it
+ * should encode the captures to, those should be set to
+ * #GstCameraBin:image-profile and #GstCameraBin:video-profile. Default is
+ * jpeg for images, and ogg (theora and vorbis) for video. If a profile without
+ * an audio stream is set for video, audio will be disabled on recordings.
+ *
+ * #GstCameraBin:preview-caps can be used to select which format preview
+ * images should be posted on the #GstBus. It has to be a raw video format.
+ *
+ * Camerabin has a #GstCameraBin:camera-source property so applications can
+ * set their source that will provide buffers for the viewfinder and for
+ * captures. This camera source is a special type of source that has 3 pads.
+ * To use a 'regular' source with a single pad you should use
+ * #GstWrapperCameraBinSrc, it will adapt your source and provide 3 pads.
+ *
+ * Applications can also select the desired viewfinder sink using
+ * #GstCameraBin:viewfinder-sink, it is also possible to select the audio
+ * source using #GstCameraBin:audio-source.
+ *
+ * The viewfinder resolution can be configured using
+ * #GstCameraBin:viewfinder-caps, these #GstCaps should be a subset of
+ * #GstCameraBin:viewfinder-supported-caps.
+ *
+ * To select the desired resolution for captures, camerabin provides
+ * #GstCameraBin:image-capture-caps and #GstCameraBin:video-capture-caps,
+ * these caps must be a subset of what the source can produce. The allowed
+ * caps can be probed using #GstCameraBin:image-capture-supported-caps and
+ * #GstCameraBin:video-capture-supported-caps. In an analogous way, there
+ * are #GstCameraBin:audio-capture-caps and
+ * #GstCameraBin:audio-capture-supported-caps.
+ *
+ * Camerabin also allows applications to insert custom #GstElements on any
+ * of its branches: video capture, image capture, viewfinder and preview.
+ * Check #GstCameraBin:video-filter, #GstCameraBin:image-filter,
+ * #GstCameraBin:viewfinder-filter and #GstCameraBin:preview-filter.
+ *
+ * ## Example launch line
+ *
+ * Unfortunately, camerabin can't be really used from gst-launch-1.0, as you
+ * need to send signals to control it. The following pipeline might be able
+ * to show the viewfinder using all the default elements.
+ * |[
+ * gst-launch-1.0 -v -m camerabin
+ * ]|
+ *
+
+ */
+
+/*
+ * Detail Topics:
+ *
+ * videorecordingbin state management (for now on called 'videobin')
+ * - The problem: keeping videobin state in sync with camerabin will make it
+ *                go to playing when it might not be used, causing its internal
+ *                filesink to open a file that might be left blank.
+ * - The solution: videobin state is set to locked upon its creation and camerabin
+ *                 registers itself on the notify::ready-for-capture of the src.
+ *                 Whenever the src readyness goes to FALSE it means a new
+ *                 capture is starting. If we are on video mode, the videobin's
+ *                 state is set to NULL and then PLAYING (in between this we
+ *                 have room to set the destination filename).
+ *                 There is no problem to leave it on playing after an EOS, so
+ *                 no action is taken on stop-capture.
+ *
+ * - TODO: What happens when an error pops?
+ * - TODO: Should we split properties in image/video variants? We already do so
+ *         for some of them
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/basecamerabinsrc/gstbasecamerasrc.h>
+#include "gstcamerabin2.h"
+#include <gst/gst-i18n-plugin.h>
+#include <gst/pbutils/pbutils.h>
+#include <gst/glib-compat-private.h>
+
+#define GST_CAMERA_BIN2_PROCESSING_INC(c)                                \
+{                                                                       \
+  gint bef = g_atomic_int_add (&c->processing_counter, 1); \
+  if (bef == 0)                                                         \
+    g_object_notify (G_OBJECT (c), "idle");                             \
+  GST_DEBUG_OBJECT ((c), "Processing counter incremented to: %d",       \
+      bef + 1);                                                         \
+}
+
+#define GST_CAMERA_BIN2_PROCESSING_DEC(c)                                \
+{                                                                       \
+  if (g_atomic_int_dec_and_test (&c->processing_counter)) {             \
+    g_object_notify (G_OBJECT (c), "idle");                             \
+    GST_DEBUG_OBJECT ((c), "Camerabin now idle");			\
+  }									\
+  GST_DEBUG_OBJECT ((c), "Processing counter decremented");             \
+}
+
+#define GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER(c)                      \
+{                                                                       \
+  g_atomic_int_set (&c->processing_counter, 0);                         \
+  GST_DEBUG_OBJECT ((c), "Processing counter reset");                   \
+}
+
+GST_DEBUG_CATEGORY_STATIC (gst_camera_bin_debug);
+#define GST_CAT_DEFAULT gst_camera_bin_debug
+
+/* prototypes */
+
+enum
+{
+  PROP_0,
+  PROP_MODE,
+  PROP_LOCATION,
+  PROP_CAMERA_SRC,
+  PROP_IMAGE_CAPTURE_SUPPORTED_CAPS,
+  PROP_VIDEO_CAPTURE_SUPPORTED_CAPS,
+  PROP_IMAGE_CAPTURE_CAPS,
+  PROP_VIDEO_CAPTURE_CAPS,
+  PROP_POST_PREVIEWS,
+  PROP_PREVIEW_CAPS,
+  PROP_VIDEO_ENCODING_PROFILE,
+  PROP_IMAGE_FILTER,
+  PROP_VIDEO_FILTER,
+  PROP_VIEWFINDER_FILTER,
+  PROP_PREVIEW_FILTER,
+  PROP_VIEWFINDER_SINK,
+  PROP_VIEWFINDER_SUPPORTED_CAPS,
+  PROP_VIEWFINDER_CAPS,
+  PROP_AUDIO_SRC,
+  PROP_MUTE_AUDIO,
+  PROP_AUDIO_CAPTURE_SUPPORTED_CAPS,
+  PROP_AUDIO_CAPTURE_CAPS,
+  PROP_ZOOM,
+  PROP_MAX_ZOOM,
+  PROP_IMAGE_ENCODING_PROFILE,
+  PROP_IDLE,
+  PROP_FLAGS,
+  PROP_AUDIO_FILTER
+};
+
+enum
+{
+  /* action signals */
+  START_CAPTURE_SIGNAL,
+  STOP_CAPTURE_SIGNAL,
+  /* emit signals */
+  LAST_SIGNAL
+};
+static guint camerabin_signals[LAST_SIGNAL];
+
+#define DEFAULT_MODE MODE_IMAGE
+#define DEFAULT_LOCATION "cap_%d"
+#define DEFAULT_POST_PREVIEWS FALSE
+#define DEFAULT_MUTE_AUDIO FALSE
+#define DEFAULT_IDLE TRUE
+#define DEFAULT_FLAGS 0
+
+#define DEFAULT_AUDIO_SRC "autoaudiosrc"
+
+/********************************
+ * Standard GObject boilerplate *
+ * and GObject types            *
+ ********************************/
+
+static GstPipelineClass *parent_class;
+static void gst_camera_bin_class_init (GstCameraBin2Class * klass);
+static void gst_camera_bin_base_init (gpointer klass);
+static void gst_camera_bin_init (GstCameraBin2 * camera);
+static void gst_camera_bin_dispose (GObject * object);
+static void gst_camera_bin_finalize (GObject * object);
+
+static void gst_camera_bin_handle_message (GstBin * bin, GstMessage * message);
+static gboolean gst_camera_bin_send_event (GstElement * element,
+    GstEvent * event);
+
+#define C_FLAGS(v) ((guint) v)
+#define GST_TYPE_CAM_FLAGS (gst_cam_flags_get_type())
+static GType
+gst_cam_flags_get_type (void)
+{
+  static const GFlagsValue values[] = {
+    {C_FLAGS (GST_CAM_FLAG_NO_AUDIO_CONVERSION), "Do not use audio conversion "
+          "elements", "no-audio-conversion"},
+    {C_FLAGS (GST_CAM_FLAG_NO_VIDEO_CONVERSION), "Do not use video conversion "
+          "elements", "no-video-conversion"},
+    {C_FLAGS (GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION),
+          "Do not use viewfinder conversion " "elements",
+        "no-viewfinder-conversion"},
+    {C_FLAGS (GST_CAM_FLAG_NO_IMAGE_CONVERSION), "Do not use image conversion "
+          "elements", "no-image-conversion"},
+    {0, NULL, NULL}
+  };
+  static volatile GType id = 0;
+
+  if (g_once_init_enter ((gsize *) & id)) {
+    GType _id;
+
+    _id = g_flags_register_static ("GstCamFlags", values);
+
+    g_once_init_leave ((gsize *) & id, _id);
+  }
+
+  return id;
+}
+
+GType
+gst_camera_bin2_get_type (void)
+{
+  static GType gst_camera_bin_type = 0;
+  static const GInterfaceInfo camerabin_tagsetter_info = {
+    NULL,
+    NULL,
+    NULL,
+  };
+
+  if (!gst_camera_bin_type) {
+    static const GTypeInfo gst_camera_bin_info = {
+      sizeof (GstCameraBin2Class),
+      (GBaseInitFunc) gst_camera_bin_base_init,
+      NULL,
+      (GClassInitFunc) gst_camera_bin_class_init,
+      NULL,
+      NULL,
+      sizeof (GstCameraBin2),
+      0,
+      (GInstanceInitFunc) gst_camera_bin_init,
+      NULL
+    };
+
+    gst_camera_bin_type =
+        g_type_register_static (GST_TYPE_PIPELINE, "GstCameraBin",
+        &gst_camera_bin_info, 0);
+
+    g_type_add_interface_static (gst_camera_bin_type, GST_TYPE_TAG_SETTER,
+        &camerabin_tagsetter_info);
+  }
+
+  return gst_camera_bin_type;
+}
+
+/* GObject class functions */
+static void gst_camera_bin_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_camera_bin_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+/* Element class functions */
+static GstStateChangeReturn
+gst_camera_bin_change_state (GstElement * element, GstStateChange trans);
+
+
+/* Camerabin functions */
+
+static GstEvent *
+gst_camera_bin_new_event_file_location (const gchar * location)
+{
+  return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+      gst_structure_new ("new-location", "location", G_TYPE_STRING, location,
+          NULL));
+}
+
+static void
+gst_camera_bin_start_capture (GstCameraBin2 * camerabin)
+{
+  const GstTagList *taglist;
+  gint capture_index = camerabin->capture_index;
+  gchar *location = NULL;
+  GST_DEBUG_OBJECT (camerabin, "Received start-capture");
+
+  /* check that we have a valid location */
+  if (camerabin->mode == MODE_VIDEO) {
+    if (camerabin->location == NULL) {
+      GST_ELEMENT_ERROR (camerabin, RESOURCE, OPEN_WRITE,
+          (_("File location is set to NULL, please set it to a valid filename")), (NULL));
+      return;
+    }
+
+    g_mutex_lock (&camerabin->video_capture_mutex);
+    while (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING) {
+      g_cond_wait (&camerabin->video_state_cond,
+          &camerabin->video_capture_mutex);
+    }
+    if (camerabin->video_state != GST_CAMERA_BIN_VIDEO_IDLE) {
+      GST_WARNING_OBJECT (camerabin, "Another video recording is ongoing"
+          " (state %d), cannot start a new one", camerabin->video_state);
+      g_mutex_unlock (&camerabin->video_capture_mutex);
+      return;
+    }
+    camerabin->video_state = GST_CAMERA_BIN_VIDEO_STARTING;
+  }
+
+  GST_CAMERA_BIN2_PROCESSING_INC (camerabin);
+
+  if (camerabin->location)
+    location = g_strdup_printf (camerabin->location, capture_index);
+
+  if (camerabin->mode == MODE_IMAGE) {
+    /* store the next capture buffer filename */
+    g_mutex_lock (&camerabin->image_capture_mutex);
+    camerabin->image_location_list =
+        g_slist_append (camerabin->image_location_list, g_strdup (location));
+    g_mutex_unlock (&camerabin->image_capture_mutex);
+  }
+
+  if (camerabin->post_previews) {
+    /* Count processing of preview images too */
+    GST_CAMERA_BIN2_PROCESSING_INC (camerabin);
+    /* store the next preview filename */
+    g_mutex_lock (&camerabin->preview_list_mutex);
+    camerabin->preview_location_list =
+        g_slist_append (camerabin->preview_location_list, location);
+    g_mutex_unlock (&camerabin->preview_list_mutex);
+  } else {
+    g_free (location);
+  }
+
+  g_signal_emit_by_name (camerabin->src, "start-capture", NULL);
+  if (camerabin->mode == MODE_VIDEO) {
+    camerabin->audio_send_newseg = TRUE;
+    if (camerabin->audio_src)
+      gst_element_set_state (camerabin->audio_src, GST_STATE_PLAYING);
+
+    camerabin->video_state = GST_CAMERA_BIN_VIDEO_RECORDING;
+    g_mutex_unlock (&camerabin->video_capture_mutex);
+  }
+
+  /*
+   * We have to push tags after start capture because the video elements
+   * might be flushing from the previous capture and are reset only on the
+   * notify from ready for capture going to FALSE
+   */
+  taglist = gst_tag_setter_get_tag_list (GST_TAG_SETTER (camerabin));
+  GST_DEBUG_OBJECT (camerabin, "Have tags from application: %"
+      GST_PTR_FORMAT, taglist);
+
+  if (camerabin->mode == MODE_IMAGE) {
+    /* Store image tags in a list and push them later, this prevents
+       start_capture() from blocking in pad_push_event call */
+    g_mutex_lock (&camerabin->image_capture_mutex);
+    camerabin->image_tags_list =
+        g_slist_append (camerabin->image_tags_list,
+        taglist ? gst_tag_list_copy (taglist) : NULL);
+    g_mutex_unlock (&camerabin->image_capture_mutex);
+  } else if (taglist) {
+    GstPad *active_pad;
+
+    active_pad = gst_element_get_static_pad (camerabin->src,
+        GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME);
+    gst_pad_push_event (active_pad,
+        gst_event_new_tag (gst_tag_list_copy (taglist)));
+
+    gst_object_unref (active_pad);
+  }
+
+  GST_DEBUG_OBJECT (camerabin, "Start-capture end");
+}
+
+static void
+gst_camera_bin_stop_capture (GstCameraBin2 * camerabin)
+{
+  GST_DEBUG_OBJECT (camerabin, "Received stop-capture");
+  if (camerabin->mode == MODE_VIDEO) {
+    g_mutex_lock (&camerabin->video_capture_mutex);
+    if (camerabin->video_state == GST_CAMERA_BIN_VIDEO_RECORDING) {
+      if (camerabin->src)
+        g_signal_emit_by_name (camerabin->src, "stop-capture", NULL);
+
+      camerabin->video_state = GST_CAMERA_BIN_VIDEO_FINISHING;
+      if (camerabin->audio_src) {
+        camerabin->audio_drop_eos = FALSE;
+        gst_element_send_event (camerabin->audio_src, gst_event_new_eos ());
+      }
+    }
+    g_mutex_unlock (&camerabin->video_capture_mutex);
+  }
+}
+
+static void
+gst_camera_bin_change_mode (GstCameraBin2 * camerabin, gint mode)
+{
+  if (mode == camerabin->mode)
+    return;
+
+  GST_DEBUG_OBJECT (camerabin, "Changing mode to %d", mode);
+
+  /* stop any ongoing capture */
+  gst_camera_bin_stop_capture (camerabin);
+  camerabin->mode = mode;
+  if (camerabin->src)
+    g_object_set (camerabin->src, "mode", mode, NULL);
+}
+
+static void
+gst_camera_bin_src_notify_readyforcapture (GObject * obj, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (user_data);
+  gboolean ready;
+
+  g_object_get (camera->src, "ready-for-capture", &ready, NULL);
+  if (!ready) {
+    gchar *location = NULL;
+
+    if (camera->mode == MODE_VIDEO) {
+      /* a video recording is about to start, change the filesink location */
+      gst_element_set_state (camera->videosink, GST_STATE_NULL);
+      location = g_strdup_printf (camera->location, camera->capture_index);
+      GST_DEBUG_OBJECT (camera, "Switching videobin location to %s", location);
+      g_object_set (camera->videosink, "location", location, NULL);
+      g_free (location);
+      if (gst_element_set_state (camera->videosink, GST_STATE_PLAYING) ==
+          GST_STATE_CHANGE_FAILURE) {
+        /* Resets the latest state change return, that would be a failure
+         * and could cause problems in a camerabin2 state change */
+        gst_element_set_state (camera->videosink, GST_STATE_NULL);
+      }
+    }
+
+    camera->capture_index++;
+  }
+}
+
+static void
+gst_camera_bin_dispose (GObject * object)
+{
+  GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (object);
+
+  g_free (camerabin->location);
+  g_mutex_clear (&camerabin->preview_list_mutex);
+  g_mutex_clear (&camerabin->image_capture_mutex);
+  g_mutex_clear (&camerabin->video_capture_mutex);
+  g_cond_clear (&camerabin->video_state_cond);
+
+  if (camerabin->src_capture_notify_id)
+    g_signal_handler_disconnect (camerabin->src,
+        camerabin->src_capture_notify_id);
+  if (camerabin->src)
+    gst_object_unref (camerabin->src);
+  if (camerabin->user_src)
+    gst_object_unref (camerabin->user_src);
+
+  if (camerabin->audio_src)
+    gst_object_unref (camerabin->audio_src);
+  if (camerabin->user_audio_src)
+    gst_object_unref (camerabin->user_audio_src);
+
+  if (camerabin->audio_capsfilter)
+    gst_object_unref (camerabin->audio_capsfilter);
+  if (camerabin->audio_volume)
+    gst_object_unref (camerabin->audio_volume);
+
+  if (camerabin->viewfinderbin)
+    gst_object_unref (camerabin->viewfinderbin);
+  if (camerabin->viewfinderbin_queue)
+    gst_object_unref (camerabin->viewfinderbin_queue);
+  if (camerabin->viewfinderbin_capsfilter)
+    gst_object_unref (camerabin->viewfinderbin_capsfilter);
+
+  if (camerabin->video_encodebin_signal_id)
+    g_signal_handler_disconnect (camerabin->video_encodebin,
+        camerabin->video_encodebin_signal_id);
+
+  if (camerabin->videosink)
+    gst_object_unref (camerabin->videosink);
+  if (camerabin->video_encodebin)
+    gst_object_unref (camerabin->video_encodebin);
+  if (camerabin->videobin_capsfilter)
+    gst_object_unref (camerabin->videobin_capsfilter);
+
+  if (camerabin->image_encodebin_signal_id)
+    g_signal_handler_disconnect (camerabin->image_encodebin,
+        camerabin->image_encodebin_signal_id);
+  if (camerabin->imagesink)
+    gst_object_unref (camerabin->imagesink);
+  if (camerabin->image_encodebin)
+    gst_object_unref (camerabin->image_encodebin);
+  if (camerabin->imagebin_capsfilter)
+    gst_object_unref (camerabin->imagebin_capsfilter);
+
+  if (camerabin->video_filter)
+    gst_object_unref (camerabin->video_filter);
+  if (camerabin->image_filter)
+    gst_object_unref (camerabin->image_filter);
+  if (camerabin->viewfinder_filter)
+    gst_object_unref (camerabin->viewfinder_filter);
+  if (camerabin->audio_filter)
+    gst_object_unref (camerabin->audio_filter);
+
+  if (camerabin->user_video_filter)
+    gst_object_unref (camerabin->user_video_filter);
+  if (camerabin->user_audio_filter)
+    gst_object_unref (camerabin->user_audio_filter);
+  if (camerabin->user_image_filter)
+    gst_object_unref (camerabin->user_image_filter);
+  if (camerabin->user_viewfinder_filter)
+    gst_object_unref (camerabin->user_viewfinder_filter);
+
+  if (camerabin->video_profile)
+    gst_encoding_profile_unref (camerabin->video_profile);
+  if (camerabin->image_profile)
+    gst_encoding_profile_unref (camerabin->image_profile);
+
+  if (camerabin->preview_caps)
+    gst_caps_replace (&camerabin->preview_caps, NULL);
+  if (camerabin->preview_filter) {
+    gst_object_unref (camerabin->preview_filter);
+    camerabin->preview_filter = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_camera_bin_finalize (GObject * object)
+{
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_camera_bin_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_set_static_metadata (element_class, "Camera Bin",
+      "Generic/Bin/Camera",
+      "Take image snapshots and record movies from camera",
+      "Thiago Santos <thiago.sousa.santos@collabora.co.uk>");
+}
+
+static void
+gst_camera_bin_class_init (GstCameraBin2Class * klass)
+{
+  GObjectClass *object_class;
+  GstElementClass *element_class;
+  GstBinClass *bin_class;
+
+  parent_class = g_type_class_peek_parent (klass);
+  object_class = G_OBJECT_CLASS (klass);
+  element_class = GST_ELEMENT_CLASS (klass);
+  bin_class = GST_BIN_CLASS (klass);
+
+  object_class->dispose = gst_camera_bin_dispose;
+  object_class->finalize = gst_camera_bin_finalize;
+  object_class->set_property = gst_camera_bin_set_property;
+  object_class->get_property = gst_camera_bin_get_property;
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_camera_bin_change_state);
+  element_class->send_event = GST_DEBUG_FUNCPTR (gst_camera_bin_send_event);
+
+  bin_class->handle_message = gst_camera_bin_handle_message;
+
+  klass->start_capture = gst_camera_bin_start_capture;
+  klass->stop_capture = gst_camera_bin_stop_capture;
+
+  /**
+   * GstCameraBin2:mode:
+   *
+   * Set the mode of operation: still image capturing or video recording.
+   */
+  g_object_class_install_property (object_class, PROP_MODE,
+      g_param_spec_enum ("mode", "Mode",
+          "The capture mode (still image capture or video recording)",
+          GST_TYPE_CAMERABIN_MODE, DEFAULT_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_LOCATION,
+      g_param_spec_string ("location", "Location",
+          "Location to save the captured files. A %d might be used on the"
+          "filename as a placeholder for a numeric index of the capture."
+          "Default is cap_%d",
+          DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_CAMERA_SRC,
+      g_param_spec_object ("camera-source", "Camera source",
+          "The camera source element to be used. It is only taken into use on"
+          " the next null to ready transition",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_AUDIO_SRC,
+      g_param_spec_object ("audio-source", "Audio source",
+          "The audio source element to be used on video recordings. It is only"
+          " taken into use on the next null to ready transition",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_MUTE_AUDIO,
+      g_param_spec_boolean ("mute", "Mute",
+          "If the audio recording should be muted. Note that this still "
+          "saves audio data to the resulting file, but they are silent. Use "
+          "a video-profile without audio to disable audio completely",
+          DEFAULT_MUTE_AUDIO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_AUDIO_CAPTURE_SUPPORTED_CAPS,
+      g_param_spec_boxed ("audio-capture-supported-caps",
+          "Audio capture supported caps",
+          "Formats supported for capturing audio represented as GstCaps",
+          GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_AUDIO_CAPTURE_CAPS,
+      g_param_spec_boxed ("audio-capture-caps",
+          "Audio capture caps",
+          "Format to capture audio for video recording represented as GstCaps",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_IMAGE_CAPTURE_SUPPORTED_CAPS,
+      g_param_spec_boxed ("image-capture-supported-caps",
+          "Image capture supported caps",
+          "Formats supported for capturing images represented as GstCaps",
+          GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_VIDEO_CAPTURE_SUPPORTED_CAPS,
+      g_param_spec_boxed ("video-capture-supported-caps",
+          "Video capture supported caps",
+          "Formats supported for capturing videos represented as GstCaps",
+          GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_IMAGE_CAPTURE_CAPS,
+      g_param_spec_boxed ("image-capture-caps",
+          "Image capture caps",
+          "Caps for image capture",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_VIDEO_CAPTURE_CAPS,
+      g_param_spec_boxed ("video-capture-caps",
+          "Video capture caps",
+          "Caps for video capture",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_POST_PREVIEWS,
+      g_param_spec_boolean ("post-previews", "Post Previews",
+          "If capture preview images should be posted to the bus",
+          DEFAULT_POST_PREVIEWS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_PREVIEW_CAPS,
+      g_param_spec_boxed ("preview-caps", "Preview caps",
+          "The caps of the preview image to be posted",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_VIDEO_ENCODING_PROFILE,
+      g_param_spec_object ("video-profile", "Video Profile",
+          "The GstEncodingProfile to use for video recording. Audio is enabled "
+          "when this profile supports audio.", GST_TYPE_ENCODING_PROFILE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_IMAGE_FILTER,
+      g_param_spec_object ("image-filter", "Image filter",
+          "The element that will process captured image frames. (Should be"
+          " set on NULL state)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_VIDEO_FILTER,
+      g_param_spec_object ("video-filter", "Video filter",
+          "The element that will process captured video frames. (Should be"
+          " set on NULL state)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_VIEWFINDER_FILTER,
+      g_param_spec_object ("viewfinder-filter", "Viewfinder filter",
+          "The element that will process frames going to the viewfinder."
+          " (Should be set on NULL state)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_AUDIO_FILTER,
+      g_param_spec_object ("audio-filter", "Audio filter",
+          "The element that will process captured audio buffers when recording"
+          ". (Should be set on NULL state)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_PREVIEW_FILTER,
+      g_param_spec_object ("preview-filter", "Preview filter",
+          "The element that will process preview buffers."
+          " (Should be set on NULL state)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_VIEWFINDER_SINK,
+      g_param_spec_object ("viewfinder-sink", "Viewfinder sink",
+          "The video sink of the viewfinder. It is only taken into use"
+          " on the next null to ready transition",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_VIEWFINDER_CAPS,
+      g_param_spec_boxed ("viewfinder-caps",
+          "Viewfinder caps",
+          "Restricts the caps that can be used on the viewfinder",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_ZOOM,
+      g_param_spec_float ("zoom", "Zoom",
+          "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, MAX_ZOOM,
+          DEFAULT_ZOOM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_MAX_ZOOM,
+      g_param_spec_float ("max-zoom", "Maximum zoom level (note: may change "
+          "depending on resolution/implementation)",
+          "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT,
+          MAX_ZOOM, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /* TODO
+   * Review before stable
+   * - One problem with using encodebin for images here is how jifmux
+   *   autoplugging works. We need to give it a higher rank and fix its
+   *   caps (it has image/jpeg on sink and src pads). Preliminary tests
+   *   show that jifmux is picked if image/jpeg is the caps of a container
+   *   profile. So this could work.
+   * - There seems to be a problem with encodebin for images currently as
+   *   it autoplugs a videorate that only starts outputting buffers after
+   *   getting the 2nd buffer.
+   */
+  g_object_class_install_property (object_class, PROP_IMAGE_ENCODING_PROFILE,
+      g_param_spec_object ("image-profile", "Image Profile",
+          "The GstEncodingProfile to use for image captures.",
+          GST_TYPE_ENCODING_PROFILE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+
+  g_object_class_install_property (object_class, PROP_IDLE,
+      g_param_spec_boolean ("idle", "Idle",
+          "If camerabin2 is idle (not doing captures).", DEFAULT_IDLE,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /* TODO review before going stable
+   * We have viewfinder-supported-caps that returns the caps that the
+   * camerasrc can produce on its viewfinder pad, this could easily be
+   * confused with what the viewfinder-sink accepts.
+   *
+   * Do we want to add a 'viewfinder-sink-supported-caps' or maybe change
+   * the name of this property?
+   */
+  g_object_class_install_property (object_class,
+      PROP_VIEWFINDER_SUPPORTED_CAPS,
+      g_param_spec_boxed ("viewfinder-supported-caps",
+          "Camera source Viewfinder pad supported caps",
+          "The caps that the camera source can produce on the viewfinder pad",
+          GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+   /**
+    * GstCameraBin:flags
+    *
+    * Control the behaviour of encodebin.
+    */
+  g_object_class_install_property (object_class, PROP_FLAGS,
+      g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
+          GST_TYPE_CAM_FLAGS, DEFAULT_FLAGS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstCameraBin2::capture-start:
+   * @camera: the camera bin element
+   *
+   * Starts image capture or video recording depending on the Mode.
+   */
+  camerabin_signals[START_CAPTURE_SIGNAL] =
+      g_signal_new ("start-capture",
+      G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_STRUCT_OFFSET (GstCameraBin2Class, start_capture),
+      NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * GstCameraBin2::capture-stop:
+   * @camera: the camera bin element
+   */
+  camerabin_signals[STOP_CAPTURE_SIGNAL] =
+      g_signal_new ("stop-capture",
+      G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_STRUCT_OFFSET (GstCameraBin2Class, stop_capture),
+      NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  gst_type_mark_as_plugin_api (GST_TYPE_CAM_FLAGS, 0);
+}
+
+static void
+gst_camera_bin_init (GstCameraBin2 * camera)
+{
+  camera->post_previews = DEFAULT_POST_PREVIEWS;
+  camera->mode = DEFAULT_MODE;
+  camera->location = g_strdup (DEFAULT_LOCATION);
+  camera->viewfinderbin = gst_element_factory_make ("viewfinderbin", "vf-bin");
+  camera->zoom = DEFAULT_ZOOM;
+  camera->max_zoom = MAX_ZOOM;
+  camera->flags = DEFAULT_FLAGS;
+  g_mutex_init (&camera->preview_list_mutex);
+  g_mutex_init (&camera->image_capture_mutex);
+  g_mutex_init (&camera->video_capture_mutex);
+  g_cond_init (&camera->video_state_cond);
+
+  /* capsfilters are created here as we proxy their caps properties and
+   * this way we avoid having to store the caps while on NULL state to
+   * set them later */
+  camera->videobin_capsfilter = gst_element_factory_make ("capsfilter",
+      "videobin-capsfilter");
+  camera->imagebin_capsfilter = gst_element_factory_make ("capsfilter",
+      "imagebin-capsfilter");
+  camera->viewfinderbin_capsfilter = gst_element_factory_make ("capsfilter",
+      "viewfinderbin-capsfilter");
+
+  gst_bin_add_many (GST_BIN (camera),
+      gst_object_ref (camera->viewfinderbin),
+      gst_object_ref (camera->videobin_capsfilter),
+      gst_object_ref (camera->imagebin_capsfilter),
+      gst_object_ref (camera->viewfinderbin_capsfilter), NULL);
+
+  /* these elements are only added if they are going to be used */
+  camera->audio_capsfilter = gst_element_factory_make ("capsfilter",
+      "audio-capsfilter");
+  camera->audio_volume = gst_element_factory_make ("volume", "audio-volume");
+}
+
+static void
+gst_image_capture_bin_post_image_done (GstCameraBin2 * camera,
+    const gchar * filename)
+{
+  GstMessage *msg;
+
+  g_return_if_fail (filename != NULL);
+
+  msg = gst_message_new_element (GST_OBJECT_CAST (camera),
+      gst_structure_new ("image-done", "filename", G_TYPE_STRING,
+          filename, NULL));
+
+  if (!gst_element_post_message (GST_ELEMENT_CAST (camera), msg))
+    GST_WARNING_OBJECT (camera, "Failed to post image-done message");
+}
+
+static void
+gst_video_capture_bin_post_video_done (GstCameraBin2 * camera)
+{
+  GstMessage *msg;
+
+  msg = gst_message_new_element (GST_OBJECT_CAST (camera),
+      gst_structure_new_empty ("video-done"));
+
+  if (!gst_element_post_message (GST_ELEMENT_CAST (camera), msg))
+    GST_WARNING_OBJECT (camera, "Failed to post video-done message");
+}
+
+static void
+gst_camera_bin_skip_next_preview (GstCameraBin2 * camerabin)
+{
+  gchar *location;
+
+  g_mutex_lock (&camerabin->preview_list_mutex);
+  if (camerabin->preview_location_list) {
+    location = camerabin->preview_location_list->data;
+    GST_DEBUG_OBJECT (camerabin, "Skipping preview for %s", location);
+    g_free (location);
+    camerabin->preview_location_list =
+        g_slist_delete_link (camerabin->preview_location_list,
+        camerabin->preview_location_list);
+    GST_CAMERA_BIN2_PROCESSING_DEC (camerabin);
+  } else {
+    GST_WARNING_OBJECT (camerabin, "No previews to skip");
+  }
+  g_mutex_unlock (&camerabin->preview_list_mutex);
+}
+
+static void
+gst_camera_bin_finish_video_file (GstCameraBin2 * camerabin)
+{
+  /* make sure the file is closed */
+  gst_element_set_state (camerabin->videosink, GST_STATE_NULL);
+
+  gst_video_capture_bin_post_video_done (camerabin);
+  GST_CAMERA_BIN2_PROCESSING_DEC (camerabin);
+}
+
+static gpointer
+gst_camera_bin_video_reset_elements (gpointer u_data)
+{
+  GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (u_data);
+
+  GST_DEBUG_OBJECT (camerabin, "Resetting video elements state");
+  g_mutex_lock (&camerabin->video_capture_mutex);
+
+  gst_camera_bin_finish_video_file (camerabin);
+
+  /* reset element states to clear eos/flushing pads */
+  gst_element_set_state (camerabin->video_encodebin, GST_STATE_READY);
+  gst_element_set_state (camerabin->videobin_capsfilter, GST_STATE_READY);
+  if (camerabin->video_filter) {
+    gst_element_set_state (camerabin->video_filter, GST_STATE_READY);
+    gst_element_sync_state_with_parent (camerabin->video_filter);
+  }
+  gst_element_sync_state_with_parent (camerabin->videobin_capsfilter);
+  gst_element_sync_state_with_parent (camerabin->video_encodebin);
+
+  if (camerabin->audio_src) {
+    gst_element_set_state (camerabin->audio_capsfilter, GST_STATE_READY);
+    gst_element_set_state (camerabin->audio_volume, GST_STATE_READY);
+    gst_element_set_state (camerabin->audio_src, GST_STATE_READY);
+
+    if (camerabin->audio_filter) {
+      gst_element_set_state (camerabin->audio_filter, GST_STATE_READY);
+      gst_element_sync_state_with_parent (camerabin->audio_filter);
+    }
+
+    gst_element_sync_state_with_parent (camerabin->audio_capsfilter);
+    gst_element_sync_state_with_parent (camerabin->audio_volume);
+
+  }
+
+  GST_DEBUG_OBJECT (camerabin, "Setting video state to idle");
+  camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
+  g_cond_signal (&camerabin->video_state_cond);
+  g_mutex_unlock (&camerabin->video_capture_mutex);
+
+  gst_object_unref (camerabin);
+  return NULL;
+}
+
+static void
+gst_camera_bin_handle_message (GstBin * bin, GstMessage * message)
+{
+  GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (bin);
+  gboolean dec_counter = FALSE;
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ELEMENT:{
+      const GstStructure *structure = gst_message_get_structure (message);
+      const gchar *filename;
+
+      if (gst_structure_has_name (structure, "GstMultiFileSink")) {
+        filename = gst_structure_get_string (structure, "filename");
+        GST_DEBUG_OBJECT (bin, "Got file save message from multifilesink, "
+            "image %s has been saved", filename);
+        if (filename) {
+          gst_image_capture_bin_post_image_done (GST_CAMERA_BIN2_CAST (bin),
+              filename);
+        }
+        dec_counter = TRUE;
+      } else if (gst_structure_has_name (structure, "preview-image")) {
+        gchar *location = NULL;
+
+        g_mutex_lock (&camerabin->preview_list_mutex);
+        if (camerabin->preview_location_list) {
+          location = camerabin->preview_location_list->data;
+          camerabin->preview_location_list =
+              g_slist_delete_link (camerabin->preview_location_list,
+              camerabin->preview_location_list);
+          GST_DEBUG_OBJECT (camerabin, "Adding preview location to preview "
+              "message '%s'", location);
+        } else {
+          GST_WARNING_OBJECT (camerabin, "Unexpected preview message received, "
+              "won't be able to put location field into the message. This can "
+              "happen if the source is posting previews while camerabin2 is "
+              "shutting down");
+        }
+        g_mutex_unlock (&camerabin->preview_list_mutex);
+
+        if (location) {
+          GstStructure *new_structure;
+          GValue value = { 0 };
+
+          g_value_init (&value, G_TYPE_STRING);
+          g_value_take_string (&value, location);
+
+          /* need to do a copy because the structure isn't mutable */
+          new_structure = gst_structure_copy (structure);
+          gst_structure_take_value (new_structure, "location", &value);
+
+          gst_message_unref (message);
+          message =
+              gst_message_new_element (GST_OBJECT_CAST (camerabin),
+              new_structure);
+        }
+
+        GST_LOG_OBJECT (bin, "received preview-image message");
+        dec_counter = TRUE;
+      }
+    }
+      break;
+    case GST_MESSAGE_WARNING:{
+      GError *err = NULL;
+      gchar *debug = NULL;
+
+      gst_message_parse_warning (message, &err, &debug);
+      if (err->domain == GST_RESOURCE_ERROR) {
+        /* some capturing failed */
+        GST_WARNING_OBJECT (bin, "Capture failed, reason: %s - %s",
+            err->message, debug);
+        if (camerabin->post_previews) {
+          gst_camera_bin_skip_next_preview (camerabin);
+        }
+        dec_counter = TRUE;
+      }
+      g_error_free (err);
+      g_free (debug);
+    }
+      break;
+    case GST_MESSAGE_EOS:{
+      GstElement *src = GST_ELEMENT (GST_MESSAGE_SRC (message));
+      if (src == GST_CAMERA_BIN2_CAST (bin)->videosink) {
+
+        g_mutex_lock (&camerabin->video_capture_mutex);
+        GST_DEBUG_OBJECT (bin, "EOS from video branch");
+        if (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING) {
+          if (!g_thread_try_new ("reset-element-thread",
+                  gst_camera_bin_video_reset_elements,
+                  gst_object_ref (camerabin), NULL)) {
+            GST_WARNING_OBJECT (camerabin,
+                "Failed to create thread to "
+                "reset video elements' state, video recordings may not work "
+                "anymore");
+            gst_object_unref (camerabin);
+            camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
+          }
+        } else if (camerabin->video_state == GST_CAMERA_BIN_VIDEO_IDLE) {
+          GST_DEBUG_OBJECT (camerabin, "Received EOS from video branch but "
+              "video recording is idle, ignoring");
+        } else {
+          GST_WARNING_OBJECT (camerabin, "Received EOS from video branch but "
+              "video is recording and stop-capture wasn't requested");
+          g_assert_not_reached ();
+        }
+
+        g_mutex_unlock (&camerabin->video_capture_mutex);
+      }
+    }
+      break;
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+
+  if (dec_counter)
+    GST_CAMERA_BIN2_PROCESSING_DEC (camerabin);
+}
+
+/*
+ * Transforms:
+ * ... ! previous_element [ ! current_filter ] ! next_element ! ...
+ *
+ * into:
+ * ... ! previous_element [ ! new_filter ] ! next_element ! ...
+ *
+ * Where current_filter and new_filter might or might not be NULL
+ */
+static void
+gst_camera_bin_check_and_replace_filter (GstCameraBin2 * camera,
+    GstElement ** current_filter, GstElement * new_filter,
+    GstElement * previous_element, GstElement * next_element,
+    const gchar * prev_elem_pad)
+{
+  if (*current_filter == new_filter) {
+    GST_DEBUG_OBJECT (camera, "Current filter is the same as the previous, "
+        "no switch needed.");
+    return;
+  }
+
+  GST_DEBUG_OBJECT (camera, "Replacing current filter (%s) with new filter "
+      "(%s)", *current_filter ? GST_ELEMENT_NAME (*current_filter) : "null",
+      new_filter ? GST_ELEMENT_NAME (new_filter) : "null");
+
+  if (*current_filter) {
+    gst_bin_remove (GST_BIN_CAST (camera), *current_filter);
+    gst_object_unref (*current_filter);
+    *current_filter = NULL;
+  } else {
+    /* unlink the pads */
+    gst_element_unlink (previous_element, next_element);
+  }
+
+  if (new_filter) {
+    *current_filter = gst_object_ref (new_filter);
+    gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (new_filter));
+  }
+
+  if (prev_elem_pad) {
+    if (new_filter) {
+      gst_element_link_pads (previous_element, prev_elem_pad, new_filter, NULL);
+      gst_element_link (new_filter, next_element);
+    } else {
+      gst_element_link_pads (previous_element, prev_elem_pad, next_element,
+          NULL);
+    }
+  } else {
+    if (new_filter)
+      gst_element_link_many (previous_element, new_filter, next_element, NULL);
+    else
+      gst_element_link (previous_element, next_element);
+  }
+}
+
+static void
+encodebin_element_added (GstElement * encodebin, GstElement * new_element,
+    GstCameraBin2 * camera)
+{
+  GstElementFactory *factory = gst_element_get_factory (new_element);
+
+  if (factory != NULL) {
+    if (strcmp (GST_OBJECT_NAME (factory), "audiorate") == 0 ||
+        strcmp (GST_OBJECT_NAME (factory), "videorate") == 0) {
+      g_object_set (new_element, "skip-to-first", TRUE, NULL);
+    }
+  }
+
+  if (GST_IS_TAG_SETTER (new_element)) {
+    GstTagSetter *tagsetter = GST_TAG_SETTER (new_element);
+
+    gst_tag_setter_set_tag_merge_mode (tagsetter, GST_TAG_MERGE_REPLACE);
+  }
+}
+
+#define VIDEO_PAD 1
+#define AUDIO_PAD 2
+static GstPad *
+encodebin_find_pad (GstCameraBin2 * camera, GstElement * encodebin,
+    gint pad_type)
+{
+  GValue value = { 0 };
+  GstPad *pad = NULL;
+  GstIterator *iter;
+  gboolean done;
+
+  GST_DEBUG_OBJECT (camera, "Looking at encodebin pads, searching for %s pad",
+      pad_type == VIDEO_PAD ? "video" : "audio");
+
+  iter = gst_element_iterate_sink_pads (encodebin);
+  done = FALSE;
+  while (!done) {
+    switch (gst_iterator_next (iter, &value)) {
+      case GST_ITERATOR_OK:
+        pad = g_value_dup_object (&value);
+        g_value_unset (&value);
+        if (pad_type == VIDEO_PAD) {
+          if (strstr (GST_PAD_NAME (pad), "video") != NULL) {
+            GST_DEBUG_OBJECT (camera, "Found video pad %s", GST_PAD_NAME (pad));
+            done = TRUE;
+            break;
+          }
+        } else if (pad_type == AUDIO_PAD) {
+          if (strstr (GST_PAD_NAME (pad), "audio") != NULL) {
+            GST_DEBUG_OBJECT (camera, "Found audio pad %s", GST_PAD_NAME (pad));
+            done = TRUE;
+            break;
+          }
+        }
+        gst_object_unref (pad);
+        pad = NULL;
+        break;
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (iter);
+        break;
+      case GST_ITERATOR_ERROR:
+        pad = NULL;
+        done = TRUE;
+        break;
+      case GST_ITERATOR_DONE:
+        pad = NULL;
+        done = TRUE;
+        break;
+    }
+  }
+  gst_iterator_free (iter);
+
+  /* no static pad, try requesting one */
+  if (pad == NULL) {
+    GstElementClass *klass;
+    GstPadTemplate *tmpl;
+
+    GST_DEBUG_OBJECT (camera, "No pads found, trying to request one");
+
+    klass = GST_ELEMENT_GET_CLASS (encodebin);
+    tmpl = gst_element_class_get_pad_template (klass, pad_type == VIDEO_PAD ?
+        "video_%u" : "audio_%u");
+
+    if (!tmpl) {
+      GST_DEBUG_OBJECT (camera, "No templates found, can't request pad");
+      return NULL;
+    }
+
+    pad = gst_element_request_pad (encodebin, tmpl, NULL, NULL);
+    GST_DEBUG_OBJECT (camera, "Got pad: %s", pad ? GST_PAD_NAME (pad) : "null");
+  }
+
+  return pad;
+}
+
+static gboolean
+gst_camera_bin_video_profile_has_audio (GstCameraBin2 * camera)
+{
+  const GList *list;
+
+  g_return_val_if_fail (camera->video_profile != NULL, FALSE);
+
+  if (GST_IS_ENCODING_VIDEO_PROFILE (camera->video_profile))
+    return FALSE;
+
+  for (list =
+      gst_encoding_container_profile_get_profiles ((GstEncodingContainerProfile
+              *) camera->video_profile); list; list = g_list_next (list)) {
+    GstEncodingProfile *profile = (GstEncodingProfile *) list->data;
+
+    if (GST_IS_ENCODING_AUDIO_PROFILE (profile))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static GstPadLinkReturn
+gst_camera_bin_link_encodebin (GstCameraBin2 * camera, GstElement * encodebin,
+    GstElement * element, gint padtype)
+{
+  GstPadLinkReturn ret;
+  GstPad *srcpad;
+  GstPad *sinkpad = NULL;
+
+  srcpad = gst_element_get_static_pad (element, "src");
+  g_assert (srcpad != NULL);
+
+  sinkpad = encodebin_find_pad (camera, encodebin, padtype);
+
+  /* there may be no available sink pad for encodebin in some situations:
+   * e.g. missing elements or incompatible padtype */
+  if (sinkpad == NULL) {
+    gst_object_unref (srcpad);
+    return GST_PAD_LINK_REFUSED;
+  }
+
+  ret = gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_CAPS);
+  gst_object_unref (sinkpad);
+  gst_object_unref (srcpad);
+
+  return ret;
+}
+
+static void
+gst_camera_bin_src_notify_max_zoom_cb (GObject * self, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GParamSpecFloat *zoom_pspec;
+  GstCameraBin2 *camera = (GstCameraBin2 *) user_data;
+
+  g_object_get (self, "max-zoom", &camera->max_zoom, NULL);
+  GST_DEBUG_OBJECT (camera, "Max zoom updated to %f", camera->max_zoom);
+
+  /* update zoom pspec */
+  zoom_pspec =
+      G_PARAM_SPEC_FLOAT (g_object_class_find_property (G_OBJECT_GET_CLASS
+          (G_OBJECT (camera)), "zoom"));
+  zoom_pspec->maximum = camera->max_zoom;
+
+  g_object_notify (G_OBJECT (camera), "max-zoom");
+}
+
+static void
+gst_camera_bin_src_notify_zoom_cb (GObject * self, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GstCameraBin2 *camera = (GstCameraBin2 *) user_data;
+
+  g_object_get (self, "zoom", &camera->zoom, NULL);
+  GST_DEBUG_OBJECT (camera, "Zoom updated to %f", camera->zoom);
+  g_object_notify (G_OBJECT (camera), "zoom");
+}
+
+static GstPadProbeReturn
+gst_camera_bin_image_src_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer data)
+{
+  GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+  GstCameraBin2 *camerabin = data;
+  GstEvent *evt;
+  gchar *location = NULL;
+  GstPad *peer;
+  GstTagList *tags;
+
+  g_mutex_lock (&camerabin->image_capture_mutex);
+
+  /* Push pending image tags */
+  if (camerabin->image_tags_list) {
+    tags = camerabin->image_tags_list->data;
+    camerabin->image_tags_list =
+        g_slist_delete_link (camerabin->image_tags_list,
+        camerabin->image_tags_list);
+    GST_DEBUG_OBJECT (camerabin, "Pushing tags from application: %"
+        GST_PTR_FORMAT, tags);
+    if (tags) {
+      peer = gst_pad_get_peer (pad);
+      gst_pad_send_event (peer, gst_event_new_tag (tags));
+      gst_object_unref (peer);
+    }
+  } else {
+    GST_DEBUG_OBJECT (camerabin, "No tags from application to send");
+  }
+
+  /* Push image location event */
+  if (camerabin->image_location_list) {
+    location = camerabin->image_location_list->data;
+    camerabin->image_location_list =
+        g_slist_delete_link (camerabin->image_location_list,
+        camerabin->image_location_list);
+    GST_DEBUG_OBJECT (camerabin, "Sending image location change to '%s'",
+        location);
+  } else {
+    GST_DEBUG_OBJECT (camerabin, "No filename location change to send");
+    g_mutex_unlock (&camerabin->image_capture_mutex);
+    return ret;
+  }
+  g_mutex_unlock (&camerabin->image_capture_mutex);
+
+  if (location) {
+    evt = gst_camera_bin_new_event_file_location (location);
+    peer = gst_pad_get_peer (pad);
+    gst_pad_send_event (peer, evt);
+    gst_object_unref (peer);
+    g_free (location);
+  } else {
+    /* This means we don't have to encode the capture, it is used for
+     * signaling the application just wants the preview */
+    ret = GST_PAD_PROBE_DROP;
+    GST_CAMERA_BIN2_PROCESSING_DEC (camerabin);
+  }
+
+  return ret;
+}
+
+
+static GstPadProbeReturn
+gst_camera_bin_image_sink_event_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer data)
+{
+  GstCameraBin2 *camerabin = data;
+  GstEvent *event = GST_EVENT (info->data);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CUSTOM_DOWNSTREAM:{
+      if (gst_event_has_name (event, "new-location")) {
+        const GstStructure *structure = gst_event_get_structure (event);
+        const gchar *filename = gst_structure_get_string (structure,
+            "location");
+
+        gst_element_set_state (camerabin->imagesink, GST_STATE_NULL);
+        GST_DEBUG_OBJECT (camerabin, "Setting filename to imagesink: %s",
+            filename);
+        g_object_set (camerabin->imagesink, "location", filename, NULL);
+        if (gst_element_set_state (camerabin->imagesink, GST_STATE_PLAYING) ==
+            GST_STATE_CHANGE_FAILURE) {
+          /* Resets the latest state change return, that would be a failure
+           * and could cause problems in a camerabin2 state change */
+          gst_element_set_state (camerabin->imagesink, GST_STATE_NULL);
+        }
+      }
+    }
+      break;
+    default:
+      break;
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+gst_camera_bin_audio_src_data_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer data)
+{
+  GstCameraBin2 *camera = data;
+  gboolean ret = GST_PAD_PROBE_OK;
+
+  if (GST_IS_BUFFER (info->data)) {
+    if (G_UNLIKELY (camera->audio_send_newseg)) {
+      GstBuffer *buf = GST_BUFFER_CAST (info->data);
+      GstClockTime ts = GST_BUFFER_TIMESTAMP (buf);
+      GstPad *peer;
+      GstSegment segment;
+
+      if (!GST_CLOCK_TIME_IS_VALID (ts)) {
+        ts = 0;
+      }
+
+      peer = gst_pad_get_peer (pad);
+      g_return_val_if_fail (peer != NULL, TRUE);
+
+      gst_segment_init (&segment, GST_FORMAT_TIME);
+      segment.start = ts;
+      gst_pad_send_event (peer, gst_event_new_segment (&segment));
+
+      gst_object_unref (peer);
+
+      camera->audio_send_newseg = FALSE;
+    }
+  } else {
+    GstEvent *event = GST_EVENT_CAST (data);
+    if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+      /* we only let an EOS pass when the user is stopping a capture */
+      if (camera->audio_drop_eos) {
+        ret = GST_PAD_PROBE_DROP;
+      } else {
+        camera->audio_drop_eos = TRUE;
+        /* should already be false, but reinforce in case no buffers get
+         * pushed */
+        camera->audio_send_newseg = FALSE;
+      }
+    } else if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
+      ret = GST_PAD_PROBE_DROP;
+    }
+  }
+
+  return ret;
+}
+
+/**
+ * gst_camera_bin_create_elements:
+ * @param camera: the #GstCameraBin2
+ *
+ * Creates all elements inside #GstCameraBin2
+ *
+ * Each of the pads on the camera source is linked as follows:
+ * .pad ! queue ! capsfilter ! correspondingbin
+ *
+ * Where 'correspondingbin' is the bin appropriate for
+ * the camera source pad.
+ */
+static gboolean
+gst_camera_bin_create_elements (GstCameraBin2 * camera)
+{
+  gboolean new_src = FALSE;
+  gboolean new_audio_src = FALSE;
+  gboolean has_audio;
+  gboolean profile_switched = FALSE;
+  const gchar *missing_element_name;
+  gint encbin_flags = 0;
+
+  if (!camera->elements_created) {
+    /* Check that elements created in _init were really created */
+    if (!(camera->audio_capsfilter && camera->videobin_capsfilter &&
+            camera->imagebin_capsfilter && camera->viewfinderbin_capsfilter)) {
+      missing_element_name = "capsfilter";
+      goto missing_element;
+    }
+
+    camera->video_encodebin =
+        gst_element_factory_make ("encodebin", "video-encodebin");
+    if (!camera->video_encodebin) {
+      missing_element_name = "encodebin";
+      goto missing_element;
+    }
+    camera->video_encodebin_signal_id =
+        g_signal_connect (camera->video_encodebin, "element-added",
+        (GCallback) encodebin_element_added, camera);
+
+    camera->videosink =
+        gst_element_factory_make ("filesink", "videobin-filesink");
+    if (!camera->videosink) {
+      missing_element_name = "filesink";
+      goto missing_element;
+    }
+    g_object_set (camera->videosink, "async", FALSE, NULL);
+
+    /* audio elements */
+    if (!camera->audio_volume) {
+      missing_element_name = "volume";
+      goto missing_element;
+    }
+
+    if (camera->video_profile == NULL) {
+      GstEncodingContainerProfile *prof;
+      GstCaps *caps;
+
+      caps = gst_caps_new_empty_simple ("application/ogg");
+      prof = gst_encoding_container_profile_new ("ogg", "theora+vorbis+ogg",
+          caps, NULL);
+      gst_caps_unref (caps);
+
+      caps = gst_caps_new_empty_simple ("video/x-theora");
+      if (!gst_encoding_container_profile_add_profile (prof,
+              (GstEncodingProfile *) gst_encoding_video_profile_new (caps,
+                  NULL, NULL, 1))) {
+        GST_WARNING_OBJECT (camera, "Failed to create encoding profiles");
+      }
+      gst_caps_unref (caps);
+
+      caps = gst_caps_new_empty_simple ("audio/x-vorbis");
+      if (!gst_encoding_container_profile_add_profile (prof,
+              (GstEncodingProfile *) gst_encoding_audio_profile_new (caps,
+                  NULL, NULL, 1))) {
+        GST_WARNING_OBJECT (camera, "Failed to create encoding profiles");
+      }
+      gst_caps_unref (caps);
+
+      camera->video_profile = (GstEncodingProfile *) prof;
+      camera->video_profile_switch = TRUE;
+    }
+
+    camera->image_encodebin =
+        gst_element_factory_make ("encodebin", "image-encodebin");
+    if (!camera->image_encodebin) {
+      missing_element_name = "encodebin";
+      goto missing_element;
+    }
+    /* durations have no meaning for image captures */
+    g_object_set (camera->image_encodebin, "queue-time-max", (guint64) 0, NULL);
+
+    camera->image_encodebin_signal_id =
+        g_signal_connect (camera->image_encodebin, "element-added",
+        (GCallback) encodebin_element_added, camera);
+
+    camera->imagesink =
+        gst_element_factory_make ("multifilesink", "imagebin-filesink");
+    if (!camera->imagesink) {
+      missing_element_name = "multifilesink";
+      goto missing_element;
+    }
+    g_object_set (camera->imagesink, "async", FALSE, "post-messages", TRUE,
+        NULL);
+
+    if (camera->image_profile == NULL) {
+      GstEncodingVideoProfile *vprof;
+      GstCaps *caps;
+
+      caps = gst_caps_new_empty_simple ("image/jpeg");
+      vprof = gst_encoding_video_profile_new (caps, NULL, NULL, 1);
+      gst_encoding_video_profile_set_variableframerate (vprof, TRUE);
+
+      gst_caps_unref (caps);
+      camera->image_profile = (GstEncodingProfile *) vprof;
+      camera->image_profile_switch = TRUE;
+    }
+
+    camera->viewfinderbin_queue =
+        gst_element_factory_make ("queue", "viewfinderbin-queue");
+    if (!camera->viewfinderbin_queue) {
+      missing_element_name = "queue";
+      goto missing_element;
+    }
+
+    g_object_set (camera->viewfinderbin_queue, "leaky", 2, "silent", TRUE,
+        "max-size-time", (guint64) 0, "max-size-bytes", (guint) 0,
+        "max-size-buffers", (guint) 1, NULL);
+
+    gst_bin_add_many (GST_BIN_CAST (camera),
+        gst_object_ref (camera->video_encodebin),
+        gst_object_ref (camera->videosink),
+        gst_object_ref (camera->image_encodebin),
+        gst_object_ref (camera->imagesink),
+        gst_object_ref (camera->viewfinderbin_queue), NULL);
+
+    gst_element_link_pads_full (camera->video_encodebin, "src",
+        camera->videosink, "sink", GST_PAD_LINK_CHECK_NOTHING);
+    gst_element_link_pads_full (camera->image_encodebin, "src",
+        camera->imagesink, "sink", GST_PAD_LINK_CHECK_NOTHING);
+    gst_element_link_pads_full (camera->viewfinderbin_queue, "src",
+        camera->viewfinderbin_capsfilter, "sink", GST_PAD_LINK_CHECK_CAPS);
+    gst_element_link_pads_full (camera->viewfinderbin_capsfilter, "src",
+        camera->viewfinderbin, "sink", GST_PAD_LINK_CHECK_CAPS);
+
+    {
+      /* set an event probe to watch for custom location changes */
+      GstPad *srcpad;
+
+      srcpad = gst_element_get_static_pad (camera->image_encodebin, "src");
+
+      gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+          gst_camera_bin_image_sink_event_probe, camera, NULL);
+
+      gst_object_unref (srcpad);
+    }
+
+    /*
+     * Video can't get into playing as its internal filesink will open
+     * a file for writing and leave it empty if unused.
+     *
+     * Its state is managed using the current mode and the source's
+     * ready-for-capture notify callback. When we are at video mode and
+     * the source's ready-for-capture goes to FALSE it means it is
+     * starting recording, so we should prepare the video bin.
+     */
+    gst_element_set_locked_state (camera->videosink, TRUE);
+    gst_element_set_locked_state (camera->imagesink, TRUE);
+
+    g_object_set (camera->videosink, "location", camera->location, NULL);
+    g_object_set (camera->imagesink, "location", camera->location, NULL);
+  }
+
+  /* propagate the flags property by translating appropriate values
+   * to GstEncFlags values */
+  if (camera->flags & GST_CAM_FLAG_NO_AUDIO_CONVERSION)
+    encbin_flags |= (1 << 0);
+  if (camera->flags & GST_CAM_FLAG_NO_VIDEO_CONVERSION)
+    encbin_flags |= (1 << 1);
+  g_object_set (camera->video_encodebin, "flags", encbin_flags, NULL);
+
+  /* image encodebin has only video branch so disable its conversion elements
+   * appropriately */
+  if (camera->flags & GST_CAM_FLAG_NO_IMAGE_CONVERSION)
+    g_object_set (camera->image_encodebin, "flags", (1 << 1), NULL);
+
+  g_object_set (camera->viewfinderbin, "disable-converters",
+      camera->flags & GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION ? TRUE : FALSE,
+      NULL);
+
+  if (camera->video_profile_switch) {
+    GST_DEBUG_OBJECT (camera, "Switching video-encodebin's profile");
+    g_object_set (camera->video_encodebin, "profile", camera->video_profile,
+        NULL);
+    if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera,
+                camera->video_encodebin, camera->videobin_capsfilter,
+                VIDEO_PAD))) {
+      goto fail;
+    }
+    camera->video_profile_switch = FALSE;
+
+    /* used to trigger relinking further down */
+    profile_switched = TRUE;
+  }
+
+  if (camera->image_profile_switch) {
+    GST_DEBUG_OBJECT (camera, "Switching image-encodebin's profile");
+    g_object_set (camera->image_encodebin, "profile", camera->image_profile,
+        NULL);
+    if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera,
+                camera->image_encodebin, camera->imagebin_capsfilter,
+                VIDEO_PAD))) {
+      goto fail;
+    }
+    camera->image_profile_switch = FALSE;
+  }
+
+  /* check if we need to replace the camera src */
+  if (camera->src) {
+    if (camera->user_src && camera->user_src != camera->src) {
+
+      if (camera->src_capture_notify_id)
+        g_signal_handler_disconnect (camera->src,
+            camera->src_capture_notify_id);
+
+      gst_bin_remove (GST_BIN_CAST (camera), camera->src);
+      gst_object_unref (camera->src);
+      camera->src = NULL;
+    }
+  }
+
+  if (!camera->src) {
+    if (camera->user_src) {
+      camera->src = gst_object_ref (camera->user_src);
+    } else {
+      camera->src =
+          gst_element_factory_make ("wrappercamerabinsrc", "camerasrc");
+    }
+
+    new_src = TRUE;
+  }
+
+  g_assert (camera->src != NULL);
+  g_object_set (camera->src, "mode", camera->mode, NULL);
+  if (camera->src) {
+    if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src),
+            "preview-caps")) {
+      g_object_set (camera->src, "post-previews", camera->post_previews,
+          "preview-caps", camera->preview_caps, "preview-filter",
+          camera->preview_filter, NULL);
+    }
+    g_signal_connect (G_OBJECT (camera->src), "notify::zoom",
+        (GCallback) gst_camera_bin_src_notify_zoom_cb, camera);
+    g_object_set (camera->src, "zoom", camera->zoom, NULL);
+    g_signal_connect (G_OBJECT (camera->src), "notify::max-zoom",
+        (GCallback) gst_camera_bin_src_notify_max_zoom_cb, camera);
+  }
+  if (new_src) {
+    GstPad *imgsrc = gst_element_get_static_pad (camera->src, "imgsrc");
+
+    gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->src));
+    camera->src_capture_notify_id = g_signal_connect (G_OBJECT (camera->src),
+        "notify::ready-for-capture",
+        G_CALLBACK (gst_camera_bin_src_notify_readyforcapture), camera);
+
+    if (!gst_element_link_pads (camera->src, "vfsrc",
+            camera->viewfinderbin_queue, "sink")) {
+      GST_ERROR_OBJECT (camera,
+          "Failed to link camera source's vfsrc pad to viewfinder queue");
+      goto fail;
+    }
+
+    if (!gst_element_link_pads (camera->src, "imgsrc",
+            camera->imagebin_capsfilter, "sink")) {
+      GST_ERROR_OBJECT (camera,
+          "Failed to link camera source's imgsrc pad to image bin capsfilter");
+      goto fail;
+    }
+    if (!gst_element_link_pads (camera->src, "vidsrc",
+            camera->videobin_capsfilter, "sink")) {
+      GST_ERROR_OBJECT (camera,
+          "Failed to link camera source's vidsrc pad to video bin capsfilter");
+      goto fail;
+    }
+
+    gst_pad_add_probe (imgsrc, GST_PAD_PROBE_TYPE_BUFFER,
+        gst_camera_bin_image_src_buffer_probe, camera, NULL);
+    gst_object_unref (imgsrc);
+  }
+
+  gst_camera_bin_check_and_replace_filter (camera, &camera->image_filter,
+      camera->user_image_filter, camera->src, camera->imagebin_capsfilter,
+      "imgsrc");
+  gst_camera_bin_check_and_replace_filter (camera, &camera->video_filter,
+      camera->user_video_filter, camera->src, camera->videobin_capsfilter,
+      "vidsrc");
+  gst_camera_bin_check_and_replace_filter (camera, &camera->viewfinder_filter,
+      camera->user_viewfinder_filter, camera->viewfinderbin_queue,
+      camera->viewfinderbin_capsfilter, NULL);
+
+  /* check if we need to replace the camera audio src */
+  has_audio = gst_camera_bin_video_profile_has_audio (camera);
+  if (camera->audio_src) {
+    if ((camera->user_audio_src && camera->user_audio_src != camera->audio_src)
+        || !has_audio) {
+      gst_bin_remove (GST_BIN_CAST (camera), camera->audio_src);
+      gst_bin_remove (GST_BIN_CAST (camera), camera->audio_volume);
+      gst_bin_remove (GST_BIN_CAST (camera), camera->audio_capsfilter);
+      gst_object_unref (camera->audio_src);
+      camera->audio_src = NULL;
+    }
+  }
+
+  if (!camera->audio_src && has_audio) {
+    if (camera->user_audio_src) {
+      camera->audio_src = gst_object_ref (camera->user_audio_src);
+    } else {
+      camera->audio_src =
+          gst_element_factory_make (DEFAULT_AUDIO_SRC, "audiosrc");
+      if (!camera->audio_src) {
+        missing_element_name = DEFAULT_AUDIO_SRC;
+        goto missing_element;
+      }
+    }
+
+    gst_element_set_locked_state (camera->audio_src, TRUE);
+    new_audio_src = TRUE;
+  }
+
+  if (new_audio_src) {
+    GstPad *srcpad;
+
+    if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->audio_src),
+            "provide-clock")) {
+      g_object_set (camera->audio_src, "provide-clock", FALSE, NULL);
+    }
+    gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->audio_src));
+    gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->audio_volume));
+    gst_bin_add (GST_BIN_CAST (camera),
+        gst_object_ref (camera->audio_capsfilter));
+
+    gst_element_link_pads_full (camera->audio_src, "src",
+        camera->audio_volume, "sink", GST_PAD_LINK_CHECK_CAPS);
+    gst_element_link_pads_full (camera->audio_volume, "src",
+        camera->audio_capsfilter, "sink", GST_PAD_LINK_CHECK_CAPS);
+
+    srcpad = gst_element_get_static_pad (camera->audio_src, "src");
+
+    /* drop EOS for audiosrc elements that push them on state_changes
+     * (basesrc does this) */
+    gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+        gst_camera_bin_audio_src_data_probe, camera, NULL);
+
+    gst_object_unref (srcpad);
+  }
+  if (has_audio) {
+    gst_camera_bin_check_and_replace_filter (camera, &camera->audio_filter,
+        camera->user_audio_filter, camera->audio_src, camera->audio_volume,
+        "src");
+  }
+
+  if ((profile_switched && has_audio) || new_audio_src) {
+    if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera,
+                camera->video_encodebin, camera->audio_capsfilter,
+                AUDIO_PAD))) {
+      goto fail;
+    }
+  }
+
+  camera->elements_created = TRUE;
+  return TRUE;
+
+missing_element:
+  gst_element_post_message (GST_ELEMENT_CAST (camera),
+      gst_missing_element_message_new (GST_ELEMENT_CAST (camera),
+          missing_element_name));
+  GST_ELEMENT_ERROR (camera, CORE, MISSING_PLUGIN,
+      (_("Missing element '%s' - check your GStreamer installation."),
+          missing_element_name), (NULL));
+  goto fail;
+
+fail:
+  /* FIXME properly clean up */
+  return FALSE;
+}
+
+static void
+_gst_tag_list_unref_maybe (GstTagList * taglist)
+{
+  if (taglist)
+    gst_tag_list_unref (taglist);
+}
+
+static GstStateChangeReturn
+gst_camera_bin_change_state (GstElement * element, GstStateChange trans)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (element);
+
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_camera_bin_create_elements (camera)) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER (camera);
+      camera->audio_drop_eos = TRUE;
+      camera->audio_send_newseg = FALSE;
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      if (GST_STATE (camera->videosink) >= GST_STATE_PAUSED)
+        gst_element_set_state (camera->videosink, GST_STATE_READY);
+      if (GST_STATE (camera->imagesink) >= GST_STATE_PAUSED)
+        gst_element_set_state (camera->imagesink, GST_STATE_READY);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gst_element_set_state (camera->videosink, GST_STATE_NULL);
+      gst_element_set_state (camera->imagesink, GST_STATE_NULL);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      if (camera->audio_src && GST_STATE (camera->audio_src) >= GST_STATE_READY)
+        gst_element_set_state (camera->audio_src, GST_STATE_READY);
+
+      gst_tag_setter_reset_tags (GST_TAG_SETTER (camera));
+      GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER (camera);
+      camera->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
+
+      g_mutex_lock (&camera->image_capture_mutex);
+      g_slist_foreach (camera->image_location_list, (GFunc) g_free, NULL);
+      g_slist_free (camera->image_location_list);
+      camera->image_location_list = NULL;
+
+      g_slist_foreach (camera->image_tags_list,
+          (GFunc) _gst_tag_list_unref_maybe, NULL);
+      g_slist_free (camera->image_tags_list);
+      camera->image_tags_list = NULL;
+      g_mutex_unlock (&camera->image_capture_mutex);
+
+      g_mutex_lock (&camera->preview_list_mutex);
+      g_slist_foreach (camera->preview_location_list, (GFunc) g_free, NULL);
+      g_slist_free (camera->preview_location_list);
+      camera->preview_location_list = NULL;
+      g_mutex_unlock (&camera->preview_list_mutex);
+
+      /* explicitly set to READY as they might be outside of the bin */
+      gst_element_set_state (camera->audio_volume, GST_STATE_READY);
+      gst_element_set_state (camera->audio_capsfilter, GST_STATE_READY);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      if (camera->audio_src)
+        gst_element_set_state (camera->audio_src, GST_STATE_NULL);
+
+      /* explicitly set to NULL as they might be outside of the bin */
+      gst_element_set_state (camera->audio_volume, GST_STATE_NULL);
+      gst_element_set_state (camera->audio_capsfilter, GST_STATE_NULL);
+
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_camera_bin_send_event (GstElement * element, GstEvent * event)
+{
+  GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (element);
+  gboolean res;
+
+  /* avoid losing our ref to send_event */
+  gst_event_ref (event);
+
+  res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+    {
+      GstState current;
+
+      if (camera->videosink) {
+        gst_element_get_state (camera->videosink, &current, NULL, 0);
+        if (current <= GST_STATE_READY)
+          gst_element_post_message (camera->videosink,
+              gst_message_new_eos (GST_OBJECT (camera->videosink)));
+      }
+      if (camera->imagesink) {
+        gst_element_get_state (camera->imagesink, &current, NULL, 0);
+        if (current <= GST_STATE_READY)
+          gst_element_post_message (camera->imagesink,
+              gst_message_new_eos (GST_OBJECT (camera->imagesink)));
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  gst_event_unref (event);
+  return res;
+}
+
+static void
+gst_camera_bin_set_location (GstCameraBin2 * camera, const gchar * location)
+{
+  GST_DEBUG_OBJECT (camera, "Setting mode %d location to %s", camera->mode,
+      location);
+  g_free (camera->location);
+  camera->location = g_strdup (location);
+}
+
+static void
+gst_camera_bin_set_audio_src (GstCameraBin2 * camera, GstElement * src)
+{
+  GST_DEBUG_OBJECT (GST_OBJECT (camera),
+      "Setting audio source %" GST_PTR_FORMAT, src);
+
+  if (camera->user_audio_src)
+    g_object_unref (camera->user_audio_src);
+
+  if (src)
+    gst_object_ref (src);
+  camera->user_audio_src = src;
+}
+
+static void
+gst_camera_bin_set_camera_src (GstCameraBin2 * camera, GstElement * src)
+{
+  GST_DEBUG_OBJECT (GST_OBJECT (camera),
+      "Setting camera source %" GST_PTR_FORMAT, src);
+
+  if (camera->user_src)
+    g_object_unref (camera->user_src);
+
+  if (src)
+    gst_object_ref (src);
+  camera->user_src = src;
+}
+
+static void
+gst_camera_bin_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (object);
+
+  switch (prop_id) {
+    case PROP_MODE:
+      gst_camera_bin_change_mode (camera, g_value_get_enum (value));
+      break;
+    case PROP_LOCATION:
+      gst_camera_bin_set_location (camera, g_value_get_string (value));
+      break;
+    case PROP_CAMERA_SRC:
+      gst_camera_bin_set_camera_src (camera, g_value_get_object (value));
+      break;
+    case PROP_AUDIO_SRC:
+      gst_camera_bin_set_audio_src (camera, g_value_get_object (value));
+      break;
+    case PROP_MUTE_AUDIO:
+      g_object_set (camera->audio_volume, "mute", g_value_get_boolean (value),
+          NULL);
+      break;
+    case PROP_AUDIO_CAPTURE_CAPS:{
+      GST_DEBUG_OBJECT (camera,
+          "Setting audio capture caps to %" GST_PTR_FORMAT,
+          gst_value_get_caps (value));
+
+      if (G_LIKELY (camera->audio_capsfilter)) {
+        g_object_set (camera->audio_capsfilter, "caps",
+            gst_value_get_caps (value), NULL);
+      } else {
+        GST_WARNING_OBJECT (camera, "Audio capsfilter missing");
+      }
+    }
+      break;
+    case PROP_IMAGE_CAPTURE_CAPS:{
+
+      GST_DEBUG_OBJECT (camera,
+          "Setting image capture caps to %" GST_PTR_FORMAT,
+          gst_value_get_caps (value));
+
+      if (G_LIKELY (camera->imagebin_capsfilter)) {
+        g_object_set (camera->imagebin_capsfilter, "caps",
+            gst_value_get_caps (value), NULL);
+      } else {
+        GST_WARNING_OBJECT (camera, "Image capsfilter missing");
+      }
+    }
+      break;
+    case PROP_VIDEO_CAPTURE_CAPS:{
+      GST_DEBUG_OBJECT (camera,
+          "Setting video capture caps to %" GST_PTR_FORMAT,
+          gst_value_get_caps (value));
+
+      if (G_LIKELY (camera->videobin_capsfilter)) {
+        g_object_set (camera->videobin_capsfilter, "caps",
+            gst_value_get_caps (value), NULL);
+      } else {
+        GST_WARNING_OBJECT (camera, "Video capsfilter missing");
+      }
+
+    }
+      break;
+    case PROP_VIEWFINDER_CAPS:{
+      GST_DEBUG_OBJECT (camera,
+          "Setting viewfinder capture caps to %" GST_PTR_FORMAT,
+          gst_value_get_caps (value));
+
+      if (G_LIKELY (camera->viewfinderbin_capsfilter)) {
+        g_object_set (camera->viewfinderbin_capsfilter, "caps",
+            gst_value_get_caps (value), NULL);
+      } else {
+        GST_WARNING_OBJECT (camera, "Viewfinder capsfilter missing");
+      }
+    }
+      break;
+    case PROP_POST_PREVIEWS:
+      camera->post_previews = g_value_get_boolean (value);
+      if (camera->src
+          && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src),
+              "post-previews"))
+        g_object_set (camera->src, "post-previews", camera->post_previews,
+            NULL);
+      break;
+    case PROP_PREVIEW_CAPS:
+      gst_caps_replace (&camera->preview_caps,
+          (GstCaps *) gst_value_get_caps (value));
+      if (camera->src
+          && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src),
+              "preview-caps"))
+        g_object_set (camera->src, "preview-caps", camera->preview_caps, NULL);
+      break;
+    case PROP_VIDEO_ENCODING_PROFILE:
+      if (camera->video_profile)
+        gst_encoding_profile_unref (camera->video_profile);
+      camera->video_profile = (GstEncodingProfile *) g_value_dup_object (value);
+      camera->video_profile_switch = TRUE;
+      break;
+    case PROP_IMAGE_FILTER:
+      if (camera->user_image_filter)
+        g_object_unref (camera->user_image_filter);
+
+      camera->user_image_filter = g_value_dup_object (value);
+      break;
+    case PROP_VIDEO_FILTER:
+      if (camera->user_video_filter)
+        g_object_unref (camera->user_video_filter);
+
+      camera->user_video_filter = g_value_dup_object (value);
+      break;
+    case PROP_VIEWFINDER_FILTER:
+      if (camera->user_viewfinder_filter)
+        g_object_unref (camera->user_viewfinder_filter);
+
+      camera->user_viewfinder_filter = g_value_dup_object (value);
+      break;
+    case PROP_PREVIEW_FILTER:
+      if (camera->preview_filter)
+        g_object_unref (camera->preview_filter);
+
+      camera->preview_filter = g_value_dup_object (value);
+      if (camera->src
+          && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src),
+              "preview-filter"))
+        g_object_set (camera->src, "preview-filter", camera->preview_filter,
+            NULL);
+      break;
+    case PROP_AUDIO_FILTER:
+      if (camera->user_audio_filter)
+        g_object_unref (camera->user_audio_filter);
+
+      camera->user_audio_filter = g_value_dup_object (value);
+      break;
+    case PROP_VIEWFINDER_SINK:
+      g_object_set (camera->viewfinderbin, "video-sink",
+          g_value_get_object (value), NULL);
+      break;
+    case PROP_ZOOM:
+      camera->zoom = g_value_get_float (value);
+      /* limit to max-zoom */
+      if (camera->zoom > camera->max_zoom) {
+        GST_DEBUG_OBJECT (camera, "Clipping zoom %f to max-zoom %f",
+            camera->zoom, camera->max_zoom);
+        camera->zoom = camera->max_zoom;
+      }
+      if (camera->src)
+        g_object_set (camera->src, "zoom", camera->zoom, NULL);
+      break;
+    case PROP_IMAGE_ENCODING_PROFILE:
+      if (camera->image_profile)
+        gst_encoding_profile_unref (camera->image_profile);
+      camera->image_profile = (GstEncodingProfile *) g_value_dup_object (value);
+
+      /* make sure we set variable framerate here to prevent videorate from
+       * being used in encodebin. It will always keep a buffer stored
+       * internally and push it when a second one arrives. This breaks
+       * the image capture */
+      if (GST_IS_ENCODING_VIDEO_PROFILE (camera->image_profile))
+        gst_encoding_video_profile_set_variableframerate (
+            (GstEncodingVideoProfile *) camera->image_profile, TRUE);
+      else if (GST_IS_ENCODING_CONTAINER_PROFILE (camera->image_profile)) {
+        const GList *profs =
+            gst_encoding_container_profile_get_profiles (
+            (GstEncodingContainerProfile *) camera->image_profile);
+        for (; profs; profs = g_list_next (profs)) {
+          if (GST_IS_ENCODING_VIDEO_PROFILE (profs->data)) {
+            gst_encoding_video_profile_set_variableframerate (profs->data,
+                TRUE);
+          }
+        }
+      }
+      camera->image_profile_switch = TRUE;
+      break;
+    case PROP_FLAGS:
+      camera->flags = g_value_get_flags (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_camera_bin_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (object);
+
+  switch (prop_id) {
+    case PROP_MODE:
+      g_value_set_enum (value, camera->mode);
+      break;
+    case PROP_LOCATION:
+      g_value_set_string (value, camera->location);
+      break;
+    case PROP_CAMERA_SRC:
+      g_value_set_object (value, camera->user_src);
+      break;
+    case PROP_AUDIO_SRC:
+      g_value_set_object (value, camera->user_audio_src);
+      break;
+    case PROP_MUTE_AUDIO:{
+      gboolean mute;
+
+      g_object_get (camera->audio_volume, "mute", &mute, NULL);
+      g_value_set_boolean (value, mute);
+      break;
+    }
+    case PROP_AUDIO_CAPTURE_SUPPORTED_CAPS:
+    case PROP_VIDEO_CAPTURE_SUPPORTED_CAPS:
+    case PROP_VIEWFINDER_SUPPORTED_CAPS:
+    case PROP_IMAGE_CAPTURE_SUPPORTED_CAPS:{
+      GstPad *pad;
+      GstElement *element;
+      GstCaps *caps;
+      const gchar *padname;
+
+      if (prop_id == PROP_VIDEO_CAPTURE_SUPPORTED_CAPS) {
+        element = camera->src;
+        padname = GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME;
+      } else if (prop_id == PROP_IMAGE_CAPTURE_SUPPORTED_CAPS) {
+        element = camera->src;
+        padname = GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME;
+      } else if (prop_id == PROP_VIEWFINDER_SUPPORTED_CAPS) {
+        element = camera->src;
+        padname = GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME;
+      } else {
+        element = camera->audio_src;
+        padname = "src";
+      }
+
+      if (element) {
+        pad = gst_element_get_static_pad (element, padname);
+
+        g_assert (pad != NULL);
+
+        /* TODO not sure if we want get_caps or get_allowed_caps to already
+         * consider the full pipeline scenario and avoid picking a caps that
+         * won't negotiate. Need to take care on the special case of the
+         * pad being unlinked.
+         */
+        caps = gst_pad_query_caps (pad, NULL);
+        if (caps) {
+          gst_value_set_caps (value, caps);
+          gst_caps_unref (caps);
+        }
+
+        gst_object_unref (pad);
+      } else {
+        GST_DEBUG_OBJECT (camera, "Source not created, can't get "
+            "supported caps");
+      }
+    }
+      break;
+    case PROP_AUDIO_CAPTURE_CAPS:{
+      GstCaps *caps = NULL;
+      if (G_LIKELY (camera->audio_capsfilter)) {
+        g_object_get (camera->audio_capsfilter, "caps", &caps, NULL);
+      } else {
+        GST_WARNING ("Missing audio capsfilter");
+      }
+      gst_value_set_caps (value, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    case PROP_IMAGE_CAPTURE_CAPS:{
+      GstCaps *caps = NULL;
+      if (G_LIKELY (camera->imagebin_capsfilter)) {
+        g_object_get (camera->imagebin_capsfilter, "caps", &caps, NULL);
+      } else {
+        GST_WARNING ("Missing imagebin capsfilter");
+      }
+      gst_value_set_caps (value, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    case PROP_VIDEO_CAPTURE_CAPS:{
+      GstCaps *caps = NULL;
+      if (G_LIKELY (camera->videobin_capsfilter)) {
+        g_object_get (camera->videobin_capsfilter, "caps", &caps, NULL);
+      } else {
+        GST_WARNING ("Missing imagebin capsfilter");
+      }
+      gst_value_set_caps (value, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    case PROP_VIEWFINDER_CAPS:{
+      GstCaps *caps = NULL;
+      if (G_LIKELY (camera->viewfinderbin_capsfilter)) {
+        g_object_get (camera->viewfinderbin_capsfilter, "caps", &caps, NULL);
+      } else {
+        GST_WARNING ("Missing imagebin capsfilter");
+      }
+      gst_value_set_caps (value, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    case PROP_POST_PREVIEWS:
+      g_value_set_boolean (value, camera->post_previews);
+      break;
+    case PROP_PREVIEW_CAPS:
+      if (camera->preview_caps)
+        gst_value_set_caps (value, camera->preview_caps);
+      break;
+    case PROP_VIDEO_ENCODING_PROFILE:
+      if (camera->video_profile) {
+        g_value_set_object (value, camera->video_profile);
+      }
+      break;
+    case PROP_VIDEO_FILTER:
+      if (camera->user_video_filter)
+        g_value_set_object (value, camera->user_video_filter);
+      break;
+    case PROP_IMAGE_FILTER:
+      if (camera->user_image_filter)
+        g_value_set_object (value, camera->user_image_filter);
+      break;
+    case PROP_VIEWFINDER_FILTER:
+      if (camera->user_viewfinder_filter)
+        g_value_set_object (value, camera->user_viewfinder_filter);
+      break;
+    case PROP_AUDIO_FILTER:
+      if (camera->user_audio_filter)
+        g_value_set_object (value, camera->user_audio_filter);
+      break;
+    case PROP_PREVIEW_FILTER:
+      if (camera->preview_filter)
+        g_value_set_object (value, camera->preview_filter);
+      break;
+    case PROP_VIEWFINDER_SINK:{
+      GstElement *sink;
+
+      g_object_get (camera->viewfinderbin, "video-sink", &sink, NULL);
+      g_value_take_object (value, sink);
+      break;
+    }
+    case PROP_ZOOM:
+      g_value_set_float (value, camera->zoom);
+      break;
+    case PROP_MAX_ZOOM:
+      g_value_set_float (value, camera->max_zoom);
+      break;
+    case PROP_IMAGE_ENCODING_PROFILE:
+      if (camera->image_profile) {
+        g_value_set_object (value, camera->image_profile);
+      }
+      break;
+    case PROP_IDLE:
+      g_value_set_boolean (value,
+          g_atomic_int_get (&camera->processing_counter) == 0);
+      break;
+    case PROP_FLAGS:
+      g_value_set_flags (value, camera->flags);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+gboolean
+gst_camera_bin2_plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_camera_bin_debug, "camerabin", 0, "CameraBin");
+
+  return gst_element_register (plugin, "camerabin", GST_RANK_NONE,
+      gst_camera_bin2_get_type ());
+}
Index: b/gst/camerabin2/gstcamerabin2.h
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstcamerabin2.h
@@ -0,0 +1,168 @@
+/* GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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_CAMERA_BIN2_H_
+#define _GST_CAMERA_BIN2_H_
+
+#include <gst/gst.h>
+#include <gst/pbutils/encoding-profile.h>
+
+G_BEGIN_DECLS
+
+/* to keep code mergeable */
+#define GstCameraBin2 GstCameraBin
+#define GstCameraBin2Class GstCameraBinClass
+
+#define GST_TYPE_CAMERA_BIN2   (gst_camera_bin2_get_type())
+#define GST_CAMERA_BIN2(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERA_BIN2,GstCameraBin2))
+#define GST_CAMERA_BIN2_CAST(obj)   ((GstCameraBin2 *) obj)
+#define GST_CAMERA_BIN2_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERA_BIN2,GstCameraBin2Class))
+#define GST_IS_CAMERA_BIN2(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERA_BIN2))
+#define GST_IS_CAMERA_BIN2_CLASS(obj)   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERA_BIN2))
+
+typedef enum
+{
+  /* matches GstEncFlags GST_ENC_FLAG_NO_AUDIO_CONVERSION in encodebin */
+  GST_CAM_FLAG_NO_AUDIO_CONVERSION = (1 << 0),
+  /* matches GstEncFlags GST_ENC_FLAG_NO_VIDEO_CONVERSION in encodebin */
+  GST_CAM_FLAG_NO_VIDEO_CONVERSION = (1 << 1),
+  /* maps to 'disable-converters' property in viewfinderbin */
+  GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION = (1 << 2),
+  /* maps to GstEncFlags GST_ENC_FLAG_NO_VIDEO_CONVERSION in the image bin's
+   * encodebin */
+  GST_CAM_FLAG_NO_IMAGE_CONVERSION = (1 << 3)
+} GstCamFlags;
+
+
+typedef enum _GstCameraBinVideoState
+{
+  GST_CAMERA_BIN_VIDEO_IDLE=0,
+  GST_CAMERA_BIN_VIDEO_STARTING=1,
+  GST_CAMERA_BIN_VIDEO_RECORDING=2,
+  GST_CAMERA_BIN_VIDEO_FINISHING=3
+} GstCameraBinVideoState;
+
+typedef struct _GstCameraBin2 GstCameraBin2;
+typedef struct _GstCameraBin2Class GstCameraBin2Class;
+
+struct _GstCameraBin2
+{
+  GstPipeline pipeline;
+
+  GstElement *src;
+  GstElement *user_src;
+  gulong src_capture_notify_id;
+
+  GstElement *video_encodebin;
+  gulong video_encodebin_signal_id;
+  GstElement *videosink;
+  GstElement *videobin_capsfilter;
+
+  GstElement *viewfinderbin;
+  GstElement *viewfinderbin_queue;
+  GstElement *viewfinderbin_capsfilter;
+
+  GstElement *image_encodebin;
+  gulong image_encodebin_signal_id;
+  GstElement *imagesink;
+  GstElement *imagebin_capsfilter;
+
+  GstElement *video_filter;
+  GstElement *image_filter;
+  GstElement *viewfinder_filter;
+  GstElement *audio_filter;
+  GstElement *user_video_filter;
+  GstElement *user_image_filter;
+  GstElement *user_viewfinder_filter;
+  GstElement *user_audio_filter;
+
+  GstElement *audio_src;
+  GstElement *user_audio_src;
+  GstElement *audio_volume;
+  GstElement *audio_capsfilter;
+
+  gint processing_counter; /* atomic int */
+
+  /* Index of the auto incrementing file index for captures */
+  gint capture_index;
+
+  GMutex image_capture_mutex;
+  /* stores list of image locations to be pushed to the image sink
+   * as file location change notifications, they are pushed before
+   * each buffer capture */
+  GSList *image_location_list;
+  /* Store also tags and push them before each captured image */
+  GSList *image_tags_list;
+
+  /*
+   * Similar to above, but used for giving names to previews
+   *
+   * Need to protect with a mutex as this list is used when the
+   * camera-source posts a preview image. As we have no control
+   * on how the camera-source will behave (we can only tell how
+   * it should), the preview location list might be used in an
+   * inconsistent way.
+   * One example is the camera-source posting a preview image after
+   * camerabin2 was put to ready, when this preview list will be
+   * freed and set to NULL. Concurrent access might lead to crashes in
+   * this situation. (Concurrency from the state-change freeing the
+   * list and the message handling function looking at preview names)
+   */
+  GSList *preview_location_list;
+  GMutex preview_list_mutex;
+
+  gboolean video_profile_switch;
+  gboolean image_profile_switch;
+
+  gboolean audio_drop_eos;
+  gboolean audio_send_newseg;
+
+  GMutex video_capture_mutex;
+  GCond video_state_cond;
+  GstCameraBinVideoState video_state;
+
+  /* properties */
+  gint mode;
+  gchar *location;
+  gboolean post_previews;
+  GstCaps *preview_caps;
+  GstElement *preview_filter;
+  GstEncodingProfile *video_profile;
+  GstEncodingProfile *image_profile;
+  gfloat zoom;
+  gfloat max_zoom;
+  GstCamFlags flags;
+
+  gboolean elements_created;
+};
+
+struct _GstCameraBin2Class
+{
+  GstPipelineClass pipeline_class;
+
+  /* Action signals */
+  void (*start_capture) (GstCameraBin2 * camera);
+  void (*stop_capture) (GstCameraBin2 * camera);
+};
+
+GType gst_camera_bin2_get_type (void);
+gboolean gst_camera_bin2_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif
Index: b/gst/camerabin2/gstdigitalzoom.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstdigitalzoom.c
@@ -0,0 +1,386 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Thiago Santos <thiagoss@osg.samsung.com>
+ *
+ * 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.
+ */
+
+
+/**
+ * SECTION:element-digitalzoom
+ * @title: digitalzoom
+ *
+ * Does digital zooming by cropping and scaling an image.
+ *
+ * It is a bin that contains the internal pipeline:
+ * videocrop ! videoscale ! capsfilter
+ *
+ * It keeps monitoring the input caps and when it is set/updated
+ * the capsfilter gets set the same caps to guarantee that the same
+ * input resolution is provided as output.
+ *
+ * Exposes the 'zoom' property as a float to allow setting the amount
+ * of zoom desired. Zooming is done in the center.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <gst/gst-i18n-plugin.h>
+#include "gstdigitalzoom.h"
+
+enum
+{
+  PROP_0,
+  PROP_ZOOM
+};
+
+GST_DEBUG_CATEGORY (digital_zoom_debug);
+#define GST_CAT_DEFAULT digital_zoom_debug
+
+#define gst_digital_zoom_parent_class parent_class
+G_DEFINE_TYPE (GstDigitalZoom, gst_digital_zoom, GST_TYPE_BIN);
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static void
+gst_digital_zoom_update_crop (GstDigitalZoom * self, GstCaps * caps)
+{
+  gint w2_crop = 0, h2_crop = 0;
+  gint left = 0;
+  gint right = 0;
+  gint top = 0;
+  gint bottom = 0;
+  gint width, height;
+  gfloat zoom;
+  GstStructure *structure;
+
+  if (caps == NULL || gst_caps_is_any (caps)) {
+    g_object_set (self->capsfilter, "caps", NULL, NULL);
+    return;
+  }
+
+  structure = gst_caps_get_structure (caps, 0);
+  gst_structure_get (structure, "width", G_TYPE_INT, &width, "height",
+      G_TYPE_INT, &height, NULL);
+
+  zoom = self->zoom;
+
+  if (self->videocrop) {
+    /* Update capsfilters to apply the zoom */
+    GST_INFO_OBJECT (self, "zoom: %f, orig size: %dx%d", zoom, width, height);
+
+    if (zoom != 1.0) {
+      w2_crop = (width - (gint) (width * 1.0 / zoom)) / 2;
+      h2_crop = (height - (gint) (height * 1.0 / zoom)) / 2;
+
+      left += w2_crop;
+      right += w2_crop;
+      top += h2_crop;
+      bottom += h2_crop;
+
+      /* force number of pixels cropped from left to be even, to avoid slow code
+       * path on videoscale */
+      left &= 0xFFFE;
+    }
+
+    GST_INFO_OBJECT (self,
+        "sw cropping: left:%d, right:%d, top:%d, bottom:%d", left, right, top,
+        bottom);
+
+    g_object_set (self->videocrop, "left", left, "right", right, "top",
+        top, "bottom", bottom, NULL);
+  }
+}
+
+static void
+gst_digital_zoom_update_zoom (GstDigitalZoom * self)
+{
+  GstCaps *caps = NULL;
+
+  if (!self->elements_created)
+    return;
+
+  g_object_get (self->capsfilter, "caps", &caps, NULL);
+  if (caps) {
+    gst_digital_zoom_update_crop (self, caps);
+    gst_caps_unref (caps);
+  }
+}
+
+static void
+gst_digital_zoom_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
+
+  switch (prop_id) {
+    case PROP_ZOOM:
+      self->zoom = g_value_get_float (value);
+      GST_DEBUG_OBJECT (self, "Setting zoom: %f", self->zoom);
+      gst_digital_zoom_update_zoom (self);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_digital_zoom_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
+
+  switch (prop_id) {
+    case PROP_ZOOM:
+      g_value_set_float (value, self->zoom);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+gst_digital_zoom_sink_query (GstPad * sink, GstObject * parent,
+    GstQuery * query)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
+  switch (GST_QUERY_TYPE (query)) {
+      /* for caps related queries we want to skip videocrop ! videoscale
+       * as the digital zoom preserves input dimensions */
+    case GST_QUERY_CAPS:
+    case GST_QUERY_ACCEPT_CAPS:
+      if (self->elements_created)
+        return gst_pad_peer_query (self->srcpad, query);
+      /* fall through */
+    default:
+      return gst_pad_query_default (sink, parent, query);
+  }
+}
+
+static gboolean
+gst_digital_zoom_src_query (GstPad * sink, GstObject * parent, GstQuery * query)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
+  switch (GST_QUERY_TYPE (query)) {
+      /* for caps related queries we want to skip videocrop ! videoscale
+       * as the digital zoom preserves input dimensions */
+    case GST_QUERY_CAPS:
+    case GST_QUERY_ACCEPT_CAPS:
+      if (self->elements_created)
+        return gst_pad_peer_query (self->sinkpad, query);
+      /* fall through */
+    default:
+      return gst_pad_query_default (sink, parent, query);
+  }
+}
+
+static gboolean
+gst_digital_zoom_sink_event (GstPad * sink, GstObject * parent,
+    GstEvent * event)
+{
+  gboolean ret;
+  gboolean is_caps;
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
+  GstCaps *old_caps = NULL;
+  GstCaps *caps = NULL;
+
+  is_caps = GST_EVENT_TYPE (event) == GST_EVENT_CAPS;
+
+  if (is_caps) {
+    gst_event_parse_caps (event, &caps);
+    g_object_get (self->capsfilter, "caps", &old_caps, NULL);
+    g_object_set (self->capsfilter, "caps", caps, NULL);
+    gst_digital_zoom_update_crop (self, caps);
+  }
+
+  ret = gst_pad_event_default (sink, parent, event);
+
+  if (is_caps) {
+    if (!ret) {
+      gst_digital_zoom_update_crop (self, old_caps);
+      g_object_set (self->capsfilter, "caps", old_caps, NULL);
+    }
+
+    if (old_caps)
+      gst_caps_unref (old_caps);
+  }
+
+  return ret;
+}
+
+static void
+gst_digital_zoom_dispose (GObject * object)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
+
+  if (self->capsfilter_sinkpad) {
+    gst_object_unref (self->capsfilter_sinkpad);
+    self->capsfilter_sinkpad = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_digital_zoom_init (GstDigitalZoom * self)
+{
+  GstPadTemplate *tmpl;
+
+  tmpl = gst_static_pad_template_get (&src_template);
+  self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl);
+  gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
+  gst_object_unref (tmpl);
+
+  tmpl = gst_static_pad_template_get (&sink_template);
+  self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", tmpl);
+  gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
+  gst_object_unref (tmpl);
+
+  gst_pad_set_event_function (self->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_event));
+  gst_pad_set_query_function (self->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_query));
+
+  gst_pad_set_query_function (self->srcpad,
+      GST_DEBUG_FUNCPTR (gst_digital_zoom_src_query));
+
+  self->zoom = 1;
+}
+
+static GstElement *
+zoom_create_element (GstDigitalZoom * self, const gchar * element_name,
+    const gchar * name)
+{
+  GstElement *element;
+  element = gst_element_factory_make (element_name, name);
+  if (element == NULL) {
+    GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
+        (_("Missing element '%s' - check your GStreamer installation."),
+            element_name), (NULL));
+  }
+  return element;
+}
+
+static gboolean
+gst_digital_zoom_create_elements (GstDigitalZoom * self)
+{
+  GstPad *pad;
+
+  if (self->elements_created)
+    return TRUE;
+
+  self->videocrop = zoom_create_element (self, "videocrop", "zoom-videocrop");
+  if (self->videocrop == NULL)
+    return FALSE;
+  if (!gst_bin_add (GST_BIN_CAST (self), self->videocrop))
+    return FALSE;
+
+  self->videoscale =
+      zoom_create_element (self, "videoscale", "zoom-videoscale");
+  if (self->videoscale == NULL)
+    return FALSE;
+  if (!gst_bin_add (GST_BIN_CAST (self), self->videoscale))
+    return FALSE;
+
+  self->capsfilter =
+      zoom_create_element (self, "capsfilter", "zoom-capsfilter");
+  if (self->capsfilter == NULL)
+    return FALSE;
+  if (!gst_bin_add (GST_BIN_CAST (self), self->capsfilter))
+    return FALSE;
+
+  if (!gst_element_link_pads_full (self->videocrop, "src", self->videoscale,
+          "sink", GST_PAD_LINK_CHECK_CAPS))
+    return FALSE;
+  if (!gst_element_link_pads_full (self->videoscale, "src", self->capsfilter,
+          "sink", GST_PAD_LINK_CHECK_CAPS))
+    return FALSE;
+
+  pad = gst_element_get_static_pad (self->videocrop, "sink");
+  gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), pad);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (self->capsfilter, "src");
+  gst_ghost_pad_set_target (GST_GHOST_PAD (self->srcpad), pad);
+  gst_object_unref (pad);
+
+  self->capsfilter_sinkpad =
+      gst_element_get_static_pad (self->capsfilter, "sink");
+
+  self->elements_created = TRUE;
+  return TRUE;
+}
+
+static GstStateChangeReturn
+gst_digital_zoom_change_state (GstElement * element, GstStateChange trans)
+{
+  GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (element);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_digital_zoom_create_elements (self)) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  return GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+}
+
+static void
+gst_digital_zoom_class_init (GstDigitalZoomClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  gobject_class->dispose = gst_digital_zoom_dispose;
+  gobject_class->set_property = gst_digital_zoom_set_property;
+  gobject_class->get_property = gst_digital_zoom_get_property;
+
+  /* g_object_class_install_property .... */
+  g_object_class_install_property (gobject_class, PROP_ZOOM,
+      g_param_spec_float ("zoom", "Zoom",
+          "Digital zoom level to be used", 1.0, G_MAXFLOAT, 1.0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  gstelement_class->change_state = gst_digital_zoom_change_state;
+
+  GST_DEBUG_CATEGORY_INIT (digital_zoom_debug, "digitalzoom",
+      0, "digital zoom");
+
+  gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
+  gst_element_class_add_static_pad_template (gstelement_class, &src_template);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Digital zoom bin", "Generic/Video",
+      "Digital zoom bin", "Thiago Santos <thiagoss@osg.samsung.com>");
+}
Index: b/gst/camerabin2/gstdigitalzoom.h
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstdigitalzoom.h
@@ -0,0 +1,79 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Thiago Santos <thiagoss@osg.samsung.com>
+ *
+ * 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_DIGITAL_ZOOM_H__
+#define __GST_DIGITAL_ZOOM_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_DIGITAL_ZOOM \
+  (gst_digital_zoom_get_type())
+#define GST_DIGITAL_ZOOM(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DIGITAL_ZOOM,GstDigitalZoom))
+#define GST_DIGITAL_ZOOM_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DIGITAL_ZOOM,GstDigitalZoomClass))
+#define GST_IS_DIGITAL_ZOOM(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DIGITAL_ZOOM))
+#define GST_IS_DIGITAL_ZOOM_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DIGITAL_ZOOM))
+#define GST_DIGITAL_ZOOM_CAST(d) ((GstDigitalZoom *)(d))
+
+GType gst_digital_zoom_get_type (void);
+
+typedef struct _GstDigitalZoom GstDigitalZoom;
+typedef struct _GstDigitalZoomClass GstDigitalZoomClass;
+
+/**
+ * GstDigitalZoom:
+ *
+ */
+struct _GstDigitalZoom
+{
+  GstBin parent;
+
+  GstPad *srcpad;
+  GstPad *sinkpad;
+
+  gboolean elements_created;
+  GstElement *videocrop;
+  GstElement *videoscale;
+  GstElement *capsfilter;
+
+  GstPad *capsfilter_sinkpad;
+
+  gfloat zoom;
+};
+
+
+/**
+ * GstDigitalZoomClass:
+ *
+ */
+struct _GstDigitalZoomClass
+{
+  GstBinClass parent;
+};
+
+G_END_DECLS
+
+#endif /* __GST_DIGITAL_ZOOM_H__ */
Index: b/gst/camerabin2/gstplugin.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstplugin.c
@@ -0,0 +1,46 @@
+/* GStreamer
+ * Copyright (C) <2010> Thiago Santos <thiago.sousa.santos@collabora.co.uk>
+ *
+ * gstplugin.c: camerabin2 plugin registering
+ *
+ * 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 "gstviewfinderbin.h"
+#include "gstwrappercamerabinsrc.h"
+#include "gstcamerabin2.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  if (!gst_viewfinder_bin_plugin_init (plugin))
+    return FALSE;
+  if (!gst_wrapper_camera_bin_src_plugin_init (plugin))
+    return FALSE;
+  if (!gst_camera_bin2_plugin_init (plugin))
+    return FALSE;
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    camerabin, "Take image snapshots and record movies from camera",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
Index: b/gst/camerabin2/gstviewfinderbin.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstviewfinderbin.c
@@ -0,0 +1,369 @@
+/* GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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.
+ */
+/**
+ * SECTION:element-viewfinderbin
+ * @title: gstviewfinderbin
+ *
+ * The gstviewfinderbin element is a displaying element for camerabin2.
+ *
+ * ## Example launch line
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! viewfinderbin
+ * ]|
+ * Feeds the viewfinderbin with video test data.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstviewfinderbin.h"
+#include "camerabingeneral.h"
+#include <gst/pbutils/pbutils.h>
+
+#include <gst/gst-i18n-plugin.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_viewfinder_bin_debug);
+#define GST_CAT_DEFAULT gst_viewfinder_bin_debug
+
+enum
+{
+  PROP_0,
+  PROP_VIDEO_SINK,
+  PROP_DISABLE_CONVERTERS
+};
+
+#define DEFAULT_DISABLE_CONVERTERS FALSE
+
+/* pad templates */
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw(ANY)")
+    );
+
+/* class initialization */
+#define gst_viewfinder_bin_parent_class parent_class
+G_DEFINE_TYPE (GstViewfinderBin, gst_viewfinder_bin, GST_TYPE_BIN);
+
+static void gst_viewfinder_bin_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * spec);
+static void gst_viewfinder_bin_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * spec);
+
+static void
+gst_viewfinder_bin_set_video_sink (GstViewfinderBin * vfbin, GstElement * sink);
+
+
+/* Element class functions */
+static GstStateChangeReturn
+gst_viewfinder_bin_change_state (GstElement * element, GstStateChange trans);
+
+static void
+gst_viewfinder_bin_dispose (GObject * object)
+{
+  GstViewfinderBin *viewfinderbin = GST_VIEWFINDER_BIN_CAST (object);
+
+  if (viewfinderbin->user_video_sink) {
+    gst_object_unref (viewfinderbin->user_video_sink);
+    viewfinderbin->user_video_sink = NULL;
+  }
+
+  if (viewfinderbin->video_sink) {
+    gst_object_unref (viewfinderbin->video_sink);
+    viewfinderbin->video_sink = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose ((GObject *) viewfinderbin);
+}
+
+static void
+gst_viewfinder_bin_class_init (GstViewfinderBinClass * klass)
+{
+  GObjectClass *gobject_klass;
+  GstElementClass *element_class;
+
+  gobject_klass = (GObjectClass *) klass;
+  element_class = GST_ELEMENT_CLASS (klass);
+
+  element_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_viewfinder_bin_change_state);
+
+  gobject_klass->dispose = gst_viewfinder_bin_dispose;
+  gobject_klass->set_property = gst_viewfinder_bin_set_property;
+  gobject_klass->get_property = gst_viewfinder_bin_get_property;
+
+  g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK,
+      g_param_spec_object ("video-sink", "Video Sink",
+          "the video output element to use (NULL = default)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_klass, PROP_DISABLE_CONVERTERS,
+      g_param_spec_boolean ("disable-converters", "Disable conversion elements",
+          "If video converters should be disabled (must be set on NULL)",
+          DEFAULT_DISABLE_CONVERTERS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_add_static_pad_template (element_class, &sink_template);
+
+  gst_element_class_set_static_metadata (element_class, "Viewfinder Bin",
+      "Sink/Video", "Viewfinder Bin used in camerabin2",
+      "Thiago Santos <thiago.sousa.santos@collabora.com>");
+}
+
+static void
+gst_viewfinder_bin_init (GstViewfinderBin * viewfinderbin)
+{
+  GstPadTemplate *templ = gst_static_pad_template_get (&sink_template);
+  viewfinderbin->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink",
+      templ);
+  gst_object_unref (templ);
+  gst_element_add_pad (GST_ELEMENT_CAST (viewfinderbin),
+      viewfinderbin->ghostpad);
+
+  viewfinderbin->disable_converters = DEFAULT_DISABLE_CONVERTERS;
+}
+
+static gboolean
+gst_viewfinder_bin_create_elements (GstViewfinderBin * vfbin)
+{
+  GstElement *csp = NULL;
+  GstElement *videoscale = NULL;
+  GstPad *firstpad = NULL;
+  const gchar *missing_element_name;
+  gboolean newsink = FALSE;
+  gboolean updated_converters = FALSE;
+
+  GST_DEBUG_OBJECT (vfbin, "Creating internal elements");
+
+  /* First check if we need to add/replace the internal sink */
+  if (vfbin->video_sink) {
+    if (vfbin->user_video_sink && vfbin->video_sink != vfbin->user_video_sink) {
+      gst_bin_remove (GST_BIN_CAST (vfbin), vfbin->video_sink);
+      gst_object_unref (vfbin->video_sink);
+      vfbin->video_sink = NULL;
+    }
+  }
+
+  if (!vfbin->video_sink) {
+    if (vfbin->user_video_sink)
+      vfbin->video_sink = gst_object_ref (vfbin->user_video_sink);
+    else {
+      vfbin->video_sink = gst_element_factory_make ("autovideosink",
+          "vfbin-sink");
+      if (!vfbin->video_sink) {
+        missing_element_name = "autovideosink";
+        goto missing_element;
+      }
+    }
+
+    gst_bin_add (GST_BIN_CAST (vfbin), gst_object_ref (vfbin->video_sink));
+    newsink = TRUE;
+  }
+
+  /* check if we want add/remove the conversion elements */
+  if (vfbin->elements_created && vfbin->disable_converters) {
+    /* remove the elements, user doesn't want them */
+
+    gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), NULL);
+    csp = gst_bin_get_by_name (GST_BIN_CAST (vfbin), "vfbin-csp");
+    videoscale = gst_bin_get_by_name (GST_BIN_CAST (vfbin), "vfbin-videoscale");
+
+    gst_bin_remove (GST_BIN_CAST (vfbin), csp);
+    gst_bin_remove (GST_BIN_CAST (vfbin), videoscale);
+
+    gst_object_unref (csp);
+    gst_object_unref (videoscale);
+
+    updated_converters = TRUE;
+  } else if (!vfbin->elements_created && !vfbin->disable_converters) {
+    gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), NULL);
+
+    /* add the elements, user wants them */
+    csp = gst_element_factory_make ("videoconvert", "vfbin-csp");
+    if (!csp) {
+      missing_element_name = "videoconvert";
+      goto missing_element;
+    }
+    gst_bin_add (GST_BIN_CAST (vfbin), csp);
+
+    videoscale = gst_element_factory_make ("videoscale", "vfbin-videoscale");
+    if (!videoscale) {
+      missing_element_name = "videoscale";
+      goto missing_element;
+    }
+    gst_bin_add (GST_BIN_CAST (vfbin), videoscale);
+
+    gst_element_link_pads_full (csp, "src", videoscale, "sink",
+        GST_PAD_LINK_CHECK_NOTHING);
+
+    vfbin->elements_created = TRUE;
+    GST_DEBUG_OBJECT (vfbin, "Elements successfully created and linked");
+
+    updated_converters = TRUE;
+  }
+  /* otherwise, just leave it as is */
+
+  /* if sink was replaced -> link it to the internal converters */
+  if (newsink && !vfbin->disable_converters) {
+    gboolean unref = FALSE;
+    if (!videoscale) {
+      videoscale = gst_bin_get_by_name (GST_BIN_CAST (vfbin),
+          "vfbin-videoscale");
+      unref = TRUE;
+    }
+
+    if (!gst_element_link_pads_full (videoscale, "src", vfbin->video_sink,
+            "sink", GST_PAD_LINK_CHECK_CAPS)) {
+      GST_ELEMENT_ERROR (vfbin, CORE, NEGOTIATION, (NULL),
+          ("linking videoscale and viewfindersink failed"));
+    }
+
+    if (unref)
+      gst_object_unref (videoscale);
+    videoscale = NULL;
+  }
+
+  /* Check if we need a new ghostpad target */
+  if (updated_converters || (newsink && vfbin->disable_converters)) {
+    if (vfbin->disable_converters) {
+      firstpad = gst_element_get_static_pad (vfbin->video_sink, "sink");
+    } else {
+      /* csp should always exist at this point */
+      firstpad = gst_element_get_static_pad (csp, "sink");
+    }
+  }
+
+  /* need to change the ghostpad target if firstpad is set */
+  if (firstpad) {
+    if (!gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), firstpad))
+      goto error;
+    gst_object_unref (firstpad);
+    firstpad = NULL;
+  }
+
+  return TRUE;
+
+missing_element:
+  gst_element_post_message (GST_ELEMENT_CAST (vfbin),
+      gst_missing_element_message_new (GST_ELEMENT_CAST (vfbin),
+          missing_element_name));
+  GST_ELEMENT_ERROR (vfbin, CORE, MISSING_PLUGIN,
+      (_("Missing element '%s' - check your GStreamer installation."),
+          missing_element_name), (NULL));
+  goto error;
+
+error:
+  GST_WARNING_OBJECT (vfbin, "Creating internal elements failed");
+  if (firstpad)
+    gst_object_unref (firstpad);
+  return FALSE;
+}
+
+static GstStateChangeReturn
+gst_viewfinder_bin_change_state (GstElement * element, GstStateChange trans)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (element);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_viewfinder_bin_create_elements (vfbin)) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_viewfinder_bin_set_video_sink (GstViewfinderBin * vfbin, GstElement * sink)
+{
+  GST_INFO_OBJECT (vfbin, "Setting video sink to %" GST_PTR_FORMAT, sink);
+
+  if (vfbin->user_video_sink != sink) {
+    if (vfbin->user_video_sink) {
+      gst_object_unref (vfbin->user_video_sink);
+    }
+    vfbin->user_video_sink = sink;
+    if (sink)
+      gst_object_ref (sink);
+  }
+}
+
+static void
+gst_viewfinder_bin_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (object);
+
+  switch (prop_id) {
+    case PROP_VIDEO_SINK:
+      gst_viewfinder_bin_set_video_sink (vfbin, g_value_get_object (value));
+      break;
+    case PROP_DISABLE_CONVERTERS:
+      vfbin->disable_converters = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_viewfinder_bin_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (object);
+
+  switch (prop_id) {
+    case PROP_VIDEO_SINK:
+      g_value_set_object (value, vfbin->video_sink);
+      break;
+    case PROP_DISABLE_CONVERTERS:
+      g_value_set_boolean (value, vfbin->disable_converters);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+gboolean
+gst_viewfinder_bin_plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_viewfinder_bin_debug, "viewfinderbin", 0,
+      "ViewFinderBin");
+  return gst_element_register (plugin, "viewfinderbin", GST_RANK_NONE,
+      gst_viewfinder_bin_get_type ());
+}
Index: b/gst/camerabin2/gstviewfinderbin.h
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstviewfinderbin.h
@@ -0,0 +1,60 @@
+/* GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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_VIEWFINDER_BIN_H_
+#define _GST_VIEWFINDER_BIN_H_
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_VIEWFINDER_BIN   (gst_viewfinder_bin_get_type())
+#define GST_VIEWFINDER_BIN(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIEWFINDER_BIN,GstViewfinderBin))
+#define GST_VIEWFINDER_BIN_CAST(obj)   ((GstViewfinderBin *) obj)
+#define GST_VIEWFINDER_BIN_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIEWFINDER_BIN,GstViewfinderBinClass))
+#define GST_IS_VIEWFINDER_BIN(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIEWFINDER_BIN))
+#define GST_IS_VIEWFINDER_BIN_CLASS(obj)   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIEWFINDER_BIN))
+
+typedef struct _GstViewfinderBin GstViewfinderBin;
+typedef struct _GstViewfinderBinClass GstViewfinderBinClass;
+
+struct _GstViewfinderBin
+{
+  GstBin bin;
+
+  GstPad *ghostpad;
+
+  GstElement *video_sink;
+  GstElement *user_video_sink;
+
+  gboolean elements_created;
+
+  gboolean disable_converters;
+};
+
+struct _GstViewfinderBinClass
+{
+  GstBinClass bin_class;
+};
+
+GType gst_viewfinder_bin_get_type (void);
+gboolean gst_viewfinder_bin_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif
Index: b/gst/camerabin2/gstwrappercamerabinsrc.c
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstwrappercamerabinsrc.c
@@ -0,0 +1,1178 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Texas Instruments, Inc
+ * Copyright (C) 2011 Thiago Santos <thiago.sousa.santos@collabora.com>
+ *
+ * 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.
+ */
+
+
+/**
+ * SECTION:element-wrappercamerabinsrc
+ * @title: wrappercamerabinsrc
+ *
+ * A camera bin src element that wraps a default video source with a single
+ * pad into the 3pad model that camerabin2 expects.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <gst/interfaces/photography.h>
+#include <gst/gst-i18n-plugin.h>
+
+#include "gstwrappercamerabinsrc.h"
+#include "gstdigitalzoom.h"
+#include "camerabingeneral.h"
+
+enum
+{
+  PROP_0,
+  PROP_VIDEO_SRC,
+  PROP_VIDEO_SRC_FILTER
+};
+
+GST_DEBUG_CATEGORY (wrapper_camera_bin_src_debug);
+#define GST_CAT_DEFAULT wrapper_camera_bin_src_debug
+
+#define gst_wrapper_camera_bin_src_parent_class parent_class
+G_DEFINE_TYPE (GstWrapperCameraBinSrc, gst_wrapper_camera_bin_src,
+    GST_TYPE_BASE_CAMERA_SRC);
+
+static GstStaticPadTemplate vfsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate imgsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate vidsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static void set_capsfilter_caps (GstWrapperCameraBinSrc * self,
+    GstCaps * new_caps);
+
+static void
+gst_wrapper_camera_bin_src_dispose (GObject * object)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object);
+
+  if (self->src_pad) {
+    gst_object_unref (self->src_pad);
+    self->src_pad = NULL;
+  }
+  if (self->video_tee_sink) {
+    gst_object_unref (self->video_tee_sink);
+    self->video_tee_sink = NULL;
+  }
+  if (self->video_tee_vf_pad) {
+    gst_object_unref (self->video_tee_vf_pad);
+    self->video_tee_vf_pad = NULL;
+  }
+  if (self->app_vid_src) {
+    gst_object_unref (self->app_vid_src);
+    self->app_vid_src = NULL;
+  }
+  if (self->app_vid_filter) {
+    gst_object_unref (self->app_vid_filter);
+    self->app_vid_filter = NULL;
+  }
+  if (self->srcfilter_pad) {
+    gst_object_unref (self->srcfilter_pad);
+    self->srcfilter_pad = NULL;
+  }
+  gst_caps_replace (&self->image_capture_caps, NULL);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_wrapper_camera_bin_src_finalize (GstWrapperCameraBinSrc * self)
+{
+  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (self));
+}
+
+static void
+gst_wrapper_camera_bin_src_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object);
+
+  switch (prop_id) {
+    case PROP_VIDEO_SRC:
+      if (GST_STATE (self) != GST_STATE_NULL) {
+        GST_ELEMENT_ERROR (self, CORE, FAILED,
+            ("camerasrc must be in NULL state when setting the video source element"),
+            (NULL));
+      } else {
+        if (self->app_vid_src)
+          gst_object_unref (self->app_vid_src);
+        self->app_vid_src = g_value_get_object (value);
+        if (self->app_vid_src)
+          gst_object_ref (self->app_vid_src);
+      }
+      break;
+    case PROP_VIDEO_SRC_FILTER:
+      if (GST_STATE (self) != GST_STATE_NULL) {
+        GST_ELEMENT_ERROR (self, CORE, FAILED,
+            ("camerasrc must be in NULL state when setting the video source filter element"),
+            (NULL));
+      } else {
+        if (self->app_vid_filter)
+          gst_object_unref (self->app_vid_filter);
+        self->app_vid_filter = g_value_get_object (value);
+        if (self->app_vid_filter)
+          gst_object_ref (self->app_vid_filter);
+      }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_wrapper_camera_bin_src_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object);
+
+  switch (prop_id) {
+    case PROP_VIDEO_SRC:
+      if (self->src_vid_src)
+        g_value_set_object (value, self->src_vid_src);
+      else
+        g_value_set_object (value, self->app_vid_src);
+      break;
+    case PROP_VIDEO_SRC_FILTER:
+      if (self->video_filter)
+        g_value_set_object (value, self->video_filter);
+      else
+        g_value_set_object (value, self->app_vid_filter);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_wrapper_camera_bin_src_reset_src_zoom (GstWrapperCameraBinSrc * self)
+{
+  if (self->src_crop) {
+    g_object_set (self->src_crop, "top", 0, "left", 0, "bottom", 0, "right", 0,
+        NULL);
+  }
+}
+
+static void
+gst_wrapper_camera_bin_reset_video_src_caps (GstWrapperCameraBinSrc * self,
+    GstCaps * new_filter_caps)
+{
+  GST_DEBUG_OBJECT (self, "Resetting src caps to %" GST_PTR_FORMAT,
+      new_filter_caps);
+  if (self->src_vid_src) {
+    GstCaps *src_neg_caps;      /* negotiated caps on src_filter */
+    gboolean ret = FALSE;
+
+    /* After pipe was negotiated src_filter do not have any filter caps.
+     * In this situation we should compare negotiated caps on capsfilter pad
+     * with requested range of caps. If one of this caps intersect,
+     * then we can avoid resetting.
+     */
+    src_neg_caps = gst_pad_get_current_caps (self->srcfilter_pad);
+    if (src_neg_caps && new_filter_caps && gst_caps_is_fixed (new_filter_caps))
+      ret = gst_caps_can_intersect (src_neg_caps, new_filter_caps);
+    else if (new_filter_caps == NULL) {
+      /* If new_filter_caps = NULL, then some body wont to empty
+       * capsfilter (set to ANY). In this case we will need to reset pipe,
+       * but if capsfilter is actually empthy, then we can avoid
+       * one more resetting.
+       */
+      GstCaps *old_filter_caps; /* range of caps on capsfilter */
+
+      g_object_get (G_OBJECT (self->src_filter),
+          "caps", &old_filter_caps, NULL);
+      ret = gst_caps_is_any (old_filter_caps);
+      gst_caps_unref (old_filter_caps);
+    }
+    if (src_neg_caps)
+      gst_caps_unref (src_neg_caps);
+
+    if (ret) {
+      GST_DEBUG_OBJECT (self, "Negotiated caps on srcfilter intersect "
+          "with requested caps, do not reset it.");
+      return;
+    }
+
+    set_capsfilter_caps (self, new_filter_caps);
+  }
+}
+
+static void
+gst_wrapper_camera_bin_src_set_output (GstWrapperCameraBinSrc * self,
+    GstPad * old_pad, GstPad * output_pad)
+{
+  GstQuery *drain = gst_query_new_drain ();
+  gst_pad_peer_query (self->src_pad, drain);
+  gst_query_unref (drain);
+
+  if (old_pad)
+    gst_ghost_pad_set_target (GST_GHOST_PAD (old_pad), NULL);
+  if (output_pad)
+    gst_ghost_pad_set_target (GST_GHOST_PAD (output_pad), self->src_pad);
+}
+
+/**
+ * gst_wrapper_camera_bin_src_imgsrc_probe:
+ *
+ * Buffer probe called before sending each buffer to image queue.
+ */
+static GstPadProbeReturn
+gst_wrapper_camera_bin_src_imgsrc_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer data)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data);
+  GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC (data);
+  GstBuffer *buffer = GST_BUFFER (info->data);
+  GstPadProbeReturn ret = GST_PAD_PROBE_DROP;
+
+  GST_LOG_OBJECT (self, "Image probe, mode %d, capture count %d bufsize: %"
+      G_GSIZE_FORMAT, camerasrc->mode, self->image_capture_count,
+      gst_buffer_get_size (buffer));
+
+  g_mutex_lock (&camerasrc->capturing_mutex);
+  if (self->image_capture_count > 0) {
+    GstSample *sample;
+    GstCaps *caps;
+    ret = GST_PAD_PROBE_OK;
+    self->image_capture_count--;
+
+    /* post preview */
+    /* TODO This can likely be optimized if the viewfinder caps is the same as
+     * the preview caps, avoiding another scaling of the same buffer. */
+    GST_DEBUG_OBJECT (self, "Posting preview for image");
+    caps = gst_pad_get_current_caps (pad);
+    sample = gst_sample_new (buffer, caps, NULL, NULL);
+    gst_base_camera_src_post_preview (camerasrc, sample);
+    gst_caps_unref (caps);
+    gst_sample_unref (sample);
+
+    if (self->image_capture_count == 0) {
+      GstCaps *anycaps = gst_caps_new_any ();
+
+      /* Get back to viewfinder */
+      gst_wrapper_camera_bin_src_reset_src_zoom (self);
+      gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps);
+      gst_wrapper_camera_bin_src_set_output (self, self->imgsrc, self->vfsrc);
+      gst_base_camera_src_finish_capture (camerasrc);
+
+      gst_caps_unref (anycaps);
+    }
+  }
+  g_mutex_unlock (&camerasrc->capturing_mutex);
+  return ret;
+}
+
+/**
+ * gst_wrapper_camera_bin_src_vidsrc_probe:
+ *
+ * Buffer probe called before sending each buffer to video queue.
+ */
+static GstPadProbeReturn
+gst_wrapper_camera_bin_src_vidsrc_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer data)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data);
+  GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC_CAST (self);
+  GstPadProbeReturn ret = GST_PAD_PROBE_DROP;
+  GstBuffer *buffer = GST_BUFFER (info->data);
+
+  GST_LOG_OBJECT (self, "Video probe, mode %d, capture status %d",
+      camerasrc->mode, self->video_rec_status);
+
+  /* TODO do we want to lock for every buffer? */
+  /*
+   * Note that we can use gst_pad_push_event here because we are a buffer
+   * probe.
+   */
+  /* TODO shouldn't access this directly */
+  g_mutex_lock (&camerasrc->capturing_mutex);
+  if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) {
+    /* NOP */
+  } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) {
+    GstClockTime ts;
+    GstSegment segment;
+    GstCaps *caps;
+    GstSample *sample;
+
+    GST_DEBUG_OBJECT (self, "Starting video recording");
+    self->video_rec_status = GST_VIDEO_RECORDING_STATUS_RUNNING;
+
+    ts = GST_BUFFER_TIMESTAMP (buffer);
+    if (!GST_CLOCK_TIME_IS_VALID (ts))
+      ts = 0;
+    gst_segment_init (&segment, GST_FORMAT_TIME);
+    segment.start = ts;
+    gst_pad_push_event (self->vidsrc, gst_event_new_segment (&segment));
+
+    /* post preview */
+    GST_DEBUG_OBJECT (self, "Posting preview for video");
+    caps = gst_pad_get_current_caps (pad);
+    sample = gst_sample_new (buffer, caps, NULL, NULL);
+    gst_base_camera_src_post_preview (camerasrc, sample);
+    gst_caps_unref (caps);
+    gst_sample_unref (sample);
+
+    ret = GST_PAD_PROBE_OK;
+  } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_FINISHING) {
+    GstPad *peer;
+
+    /* send eos */
+    GST_DEBUG_OBJECT (self, "Finishing video recording, pushing eos");
+
+    peer = gst_pad_get_peer (self->vidsrc);
+
+    if (peer) {
+      /* send to the peer as we don't want our pads with eos flag */
+      gst_pad_send_event (peer, gst_event_new_eos ());
+      gst_object_unref (peer);
+    } else {
+      GST_WARNING_OBJECT (camerasrc, "No peer pad for vidsrc");
+    }
+    self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE;
+
+    gst_pad_unlink (self->src_pad, self->video_tee_sink);
+    gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, self->vfsrc);
+    gst_base_camera_src_finish_capture (camerasrc);
+  } else {
+    ret = GST_PAD_PROBE_OK;
+  }
+  g_mutex_unlock (&camerasrc->capturing_mutex);
+  return ret;
+}
+
+static void
+gst_wrapper_camera_bin_src_caps_cb (GstPad * pad, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (user_data);
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (user_data);
+  GstCaps *caps;
+  GstStructure *in_st = NULL;
+
+  caps = gst_pad_get_current_caps (pad);
+
+  GST_DEBUG_OBJECT (self, "src-filter caps changed to %" GST_PTR_FORMAT, caps);
+
+  if (caps && gst_caps_get_size (caps)) {
+    in_st = gst_caps_get_structure (caps, 0);
+    if (in_st) {
+      gst_structure_get_int (in_st, "width", &bcamsrc->width);
+      gst_structure_get_int (in_st, "height", &bcamsrc->height);
+
+      GST_DEBUG_OBJECT (self, "Source dimensions now: %dx%d", bcamsrc->width,
+          bcamsrc->height);
+    }
+  }
+
+  /* Update zoom */
+  gst_base_camera_src_setup_zoom (bcamsrc);
+
+  if (caps)
+    gst_caps_unref (caps);
+};
+
+static void
+gst_wrapper_camera_bin_src_max_zoom_cb (GObject * self, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GstBaseCameraSrc *bcamsrc = (GstBaseCameraSrc *) user_data;
+
+  g_object_get (self, "max-zoom", &bcamsrc->max_zoom, NULL);
+  g_object_notify (G_OBJECT (bcamsrc), "max-zoom");
+}
+
+static gboolean
+gst_wrapper_camera_bin_src_src_event (GstPad * pad, GstObject * parent,
+    GstEvent * event)
+{
+  gboolean ret = TRUE;
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (parent);
+
+  GST_DEBUG_OBJECT (self, "Handling event %p %" GST_PTR_FORMAT, event, event);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_RECONFIGURE:
+      if (pad == self->imgsrc) {
+        GST_DEBUG_OBJECT (self, "Image mode reconfigure event received");
+        self->image_renegotiate = TRUE;
+      } else if (pad == self->vidsrc) {
+        GST_DEBUG_OBJECT (self, "Video mode reconfigure event received");
+        self->video_renegotiate = TRUE;
+      }
+      if (pad == self->imgsrc || pad == self->vidsrc) {
+        gst_event_unref (event);
+        return ret;
+      }
+      break;
+    default:
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+  }
+
+  return ret;
+}
+
+/**
+ * check_and_replace_src
+ * @self: #GstWrapperCamerabinSrcCameraSrc object
+ *
+ * Checks if the current videosrc needs to be replaced
+ */
+static gboolean
+check_and_replace_src (GstWrapperCameraBinSrc * self)
+{
+  GstBin *cbin = GST_BIN_CAST (self);
+  GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC_CAST (self);
+
+  if (self->src_vid_src && self->src_vid_src == self->app_vid_src) {
+    GST_DEBUG_OBJECT (self, "No need to change current videosrc");
+    return TRUE;
+  }
+
+  if (self->src_vid_src) {
+    GST_DEBUG_OBJECT (self, "Removing old video source");
+    if (self->src_max_zoom_signal_id) {
+      g_signal_handler_disconnect (self->src_vid_src,
+          self->src_max_zoom_signal_id);
+      self->src_max_zoom_signal_id = 0;
+    }
+    if (self->src_event_probe_id) {
+      GstPad *pad;
+      pad = gst_element_get_static_pad (self->src_vid_src, "src");
+      gst_pad_remove_probe (pad, self->src_event_probe_id);
+      gst_object_unref (pad);
+      self->src_event_probe_id = 0;
+    }
+    gst_bin_remove (GST_BIN_CAST (self), self->src_vid_src);
+    self->src_vid_src = NULL;
+  }
+
+  GST_DEBUG_OBJECT (self, "Adding new video source");
+
+  /* Add application set or default video src element */
+  if (!(self->src_vid_src = gst_camerabin_setup_default_element (cbin,
+              self->app_vid_src, "autovideosrc", DEFAULT_VIDEOSRC,
+              "camerasrc-real-src"))) {
+    self->src_vid_src = NULL;
+    goto fail;
+  }
+
+  if (!gst_bin_add (cbin, self->src_vid_src)) {
+    goto fail;
+  }
+
+  /* check if we already have the next element to link to */
+  if (self->src_crop) {
+    if (!gst_element_link_pads (self->src_vid_src, "src", self->src_crop,
+            "sink")) {
+      goto fail;
+    }
+  }
+
+  /* we listen for changes to max-zoom in the video src so that
+   * we can proxy them to the basecamerasrc property */
+  if (g_object_class_find_property (G_OBJECT_GET_CLASS (bcamsrc), "max-zoom")) {
+    self->src_max_zoom_signal_id =
+        g_signal_connect (G_OBJECT (self->src_vid_src), "notify::max-zoom",
+        (GCallback) gst_wrapper_camera_bin_src_max_zoom_cb, bcamsrc);
+  }
+
+  return TRUE;
+
+fail:
+  if (self->src_vid_src)
+    gst_element_set_state (self->src_vid_src, GST_STATE_NULL);
+  return FALSE;
+}
+
+/**
+ * gst_wrapper_camera_bin_src_construct_pipeline:
+ * @bcamsrc: camerasrc object
+ *
+ * This function creates and links the elements of the camerasrc bin
+ * videosrc ! cspconv ! srcfilter ! cspconv ! capsfilter ! crop ! scale ! \
+ * capsfilter
+ *
+ * Returns: TRUE, if elements were successfully created, FALSE otherwise
+ */
+static gboolean
+gst_wrapper_camera_bin_src_construct_pipeline (GstBaseCameraSrc * bcamsrc)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc);
+  GstBin *cbin = GST_BIN (bcamsrc);
+  GstElement *filter_csp;
+  GstElement *src_csp;
+  GstElement *capsfilter;
+  GstElement *video_recording_tee;
+  gboolean ret = FALSE;
+  GstPad *tee_pad;
+
+  /* checks and adds a new video src if needed */
+  if (!check_and_replace_src (self))
+    goto done;
+
+  if (!self->elements_created) {
+
+    GST_DEBUG_OBJECT (self, "constructing pipeline");
+
+    if (!(self->src_crop =
+            gst_camerabin_create_and_add_element (cbin, "videocrop",
+                "src-crop")))
+      goto done;
+
+    if (!gst_camerabin_create_and_add_element (cbin, "videoconvert",
+            "src-videoconvert"))
+      goto done;
+
+    if (self->app_vid_filter) {
+      self->video_filter = gst_object_ref (self->app_vid_filter);
+
+      if (!gst_camerabin_add_element (cbin, self->video_filter))
+        goto done;
+      if (!gst_camerabin_create_and_add_element (cbin, "videoconvert",
+              "filter-videoconvert"))
+        goto done;
+    }
+
+    if (!(self->src_filter =
+            gst_camerabin_create_and_add_element (cbin, "capsfilter",
+                "src-capsfilter")))
+      goto done;
+
+    /* attach to notify::caps on the first capsfilter and use a callback
+     * to recalculate the zoom properties when these caps change and to
+     * propagate the caps to the second capsfilter */
+    self->srcfilter_pad = gst_element_get_static_pad (self->src_filter, "src");
+    g_signal_connect (self->srcfilter_pad, "notify::caps",
+        G_CALLBACK (gst_wrapper_camera_bin_src_caps_cb), self);
+
+    if (!(self->digitalzoom = g_object_new (GST_TYPE_DIGITAL_ZOOM, NULL))) {
+      GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
+          (_("Digitalzoom element couldn't be created")), (NULL));
+
+      goto done;
+    }
+    if (!gst_camerabin_add_element_full (GST_BIN_CAST (self), NULL,
+            self->digitalzoom, "sink"))
+      goto done;
+
+    /* keep a 'tee' element that has 2 source pads, one is linked to the
+     * vidsrc pad and the other is linked as needed to the viewfinder
+     * when video recording is happening */
+    video_recording_tee = gst_element_factory_make ("tee", "video_rec_tee");
+    gst_bin_add (GST_BIN_CAST (self), video_recording_tee);     /* TODO check returns */
+    self->video_tee_vf_pad =
+        gst_element_get_request_pad (video_recording_tee, "src_%u");
+    self->video_tee_sink =
+        gst_element_get_static_pad (video_recording_tee, "sink");
+    tee_pad = gst_element_get_request_pad (video_recording_tee, "src_%u");
+    gst_ghost_pad_set_target (GST_GHOST_PAD (self->vidsrc), tee_pad);
+    gst_object_unref (tee_pad);
+
+    /* viewfinder pad */
+    self->src_pad = gst_element_get_static_pad (self->digitalzoom, "src");
+    gst_ghost_pad_set_target (GST_GHOST_PAD (self->vfsrc), self->src_pad);
+
+    gst_pad_set_active (self->vfsrc, TRUE);
+    gst_pad_set_active (self->imgsrc, TRUE);    /* XXX ??? */
+    gst_pad_set_active (self->vidsrc, TRUE);    /* XXX ??? */
+
+    gst_pad_add_probe (self->imgsrc, GST_PAD_PROBE_TYPE_BUFFER,
+        gst_wrapper_camera_bin_src_imgsrc_probe, self, NULL);
+    gst_pad_add_probe (self->video_tee_sink, GST_PAD_PROBE_TYPE_BUFFER,
+        gst_wrapper_camera_bin_src_vidsrc_probe, self, NULL);
+  }
+
+  /* Do this even if pipeline is constructed */
+
+  if (self->video_filter) {
+    /* check if we need to replace the current one */
+    if (self->video_filter != self->app_vid_filter) {
+      gst_bin_remove (cbin, self->video_filter);
+      gst_object_unref (self->video_filter);
+      self->video_filter = NULL;
+      filter_csp = gst_bin_get_by_name (cbin, "filter-videoconvert");
+      gst_bin_remove (cbin, filter_csp);
+      gst_object_unref (filter_csp);
+      filter_csp = NULL;
+    }
+  }
+
+  if (!self->video_filter) {
+    if (self->app_vid_filter) {
+      self->video_filter = gst_object_ref (self->app_vid_filter);
+      filter_csp = gst_element_factory_make ("videoconvert",
+          "filter-videoconvert");
+      gst_bin_add_many (cbin, self->video_filter, filter_csp, NULL);
+      src_csp = gst_bin_get_by_name (cbin, "src-videoconvert");
+      capsfilter = gst_bin_get_by_name (cbin, "src-capsfilter");
+      if (gst_pad_is_linked (gst_element_get_static_pad (src_csp, "src")))
+        gst_element_unlink (src_csp, capsfilter);
+      if (!gst_element_link_many (src_csp, self->video_filter, filter_csp,
+              capsfilter, NULL)) {
+        gst_object_unref (src_csp);
+        gst_object_unref (capsfilter);
+        goto done;
+      }
+      gst_object_unref (src_csp);
+      gst_object_unref (capsfilter);
+    }
+  }
+  ret = TRUE;
+  self->elements_created = TRUE;
+done:
+  return ret;
+}
+
+/**
+ * adapt_image_capture:
+ * @self: camerasrc object
+ * @in_caps: caps object that describes incoming image format
+ *
+ * Adjust capsfilters and crop according image capture caps if necessary.
+ * The captured image format from video source might be different from
+ * what application requested, so we can try to fix that in camerabin.
+ *
+ */
+static void
+adapt_image_capture (GstWrapperCameraBinSrc * self, GstCaps * in_caps)
+{
+  GstStructure *in_st, *req_st;
+  gint in_width = 0, in_height = 0, req_width = 0, req_height = 0, crop = 0;
+  gdouble ratio_w, ratio_h;
+
+  GST_LOG_OBJECT (self, "in caps: %" GST_PTR_FORMAT, in_caps);
+  GST_LOG_OBJECT (self, "requested caps: %" GST_PTR_FORMAT,
+      self->image_capture_caps);
+
+  in_st = gst_caps_get_structure (in_caps, 0);
+  gst_structure_get_int (in_st, "width", &in_width);
+  gst_structure_get_int (in_st, "height", &in_height);
+
+  req_st = gst_caps_get_structure (self->image_capture_caps, 0);
+  gst_structure_get_int (req_st, "width", &req_width);
+  gst_structure_get_int (req_st, "height", &req_height);
+
+  GST_INFO_OBJECT (self, "we requested %dx%d, and got %dx%d", req_width,
+      req_height, in_width, in_height);
+
+  /* Crop if requested aspect ratio differs from incoming frame aspect ratio */
+  if (self->src_crop) {
+    gint base_crop_top = 0, base_crop_bottom = 0;
+    gint base_crop_left = 0, base_crop_right = 0;
+
+    ratio_w = (gdouble) in_width / req_width;
+    ratio_h = (gdouble) in_height / req_height;
+
+    if (ratio_w < ratio_h) {
+      crop = in_height - (req_height * ratio_w);
+      base_crop_top = crop / 2;
+      base_crop_bottom = crop / 2;
+    } else {
+      crop = in_width - (req_width * ratio_h);
+      base_crop_left = crop / 2;
+      base_crop_right += crop / 2;
+    }
+
+    GST_INFO_OBJECT (self,
+        "setting base crop: left:%d, right:%d, top:%d, bottom:%d",
+        base_crop_left, base_crop_right, base_crop_top, base_crop_bottom);
+    g_object_set (G_OBJECT (self->src_crop),
+        "top", base_crop_top, "bottom", base_crop_bottom,
+        "left", base_crop_left, "right", base_crop_right, NULL);
+  }
+
+  /* Update capsfilters */
+  set_capsfilter_caps (self, self->image_capture_caps);
+}
+
+/**
+ * img_capture_prepared:
+ * @data: camerasrc object
+ * @caps: caps describing the prepared image format
+ *
+ * Callback which is called after image capture has been prepared.
+ */
+static void
+img_capture_prepared (gpointer data, GstCaps * caps)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data);
+
+  GST_INFO_OBJECT (self, "image capture prepared");
+
+  /* It is possible we are about to get something else that we requested */
+  if (!gst_caps_can_intersect (self->image_capture_caps, caps)) {
+    adapt_image_capture (self, caps);
+  } else {
+    set_capsfilter_caps (self, self->image_capture_caps);
+  }
+}
+
+static GstPadProbeReturn
+start_image_capture (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
+{
+  GstWrapperCameraBinSrc *self = udata;
+  GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (self);
+  GstPhotography *photography =
+      (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc),
+      GST_TYPE_PHOTOGRAPHY);
+  GstCaps *caps;
+
+  GST_DEBUG_OBJECT (self, "Starting image capture");
+
+  /* unlink from the viewfinder, link to the imagesrc pad to wait for
+   * the buffer to pass */
+  gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, self->imgsrc);
+
+  if (self->image_renegotiate) {
+    self->image_renegotiate = FALSE;
+
+    /* clean capsfilter caps so they don't interfere here */
+    g_object_set (self->src_filter, "caps", NULL, NULL);
+
+    caps = gst_pad_get_allowed_caps (self->imgsrc);
+    gst_caps_replace (&self->image_capture_caps, caps);
+    gst_caps_unref (caps);
+
+    /* We caught this event in the src pad event handler and now we want to
+     * actually push it upstream */
+    gst_pad_mark_reconfigure (pad);
+  }
+
+  if (photography) {
+    GST_DEBUG_OBJECT (self, "prepare image capture caps %" GST_PTR_FORMAT,
+        self->image_capture_caps);
+    if (!gst_photography_prepare_for_capture (photography,
+            (GstPhotographyCapturePrepared) img_capture_prepared,
+            self->image_capture_caps, self)) {
+      GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
+          ("Failed to prepare image capture"),
+          ("Prepare capture call didn't succeed for the given caps"));
+      self->image_capture_count = 0;
+    }
+    gst_object_unref (photography);
+  } else {
+    gst_wrapper_camera_bin_reset_video_src_caps (self,
+        self->image_capture_caps);
+  }
+
+  self->image_capture_probe = 0;
+  return GST_PAD_PROBE_REMOVE;
+}
+
+static GstPadProbeReturn
+start_video_capture (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
+{
+  GstWrapperCameraBinSrc *self = udata;
+  GstCaps *caps;
+
+  GST_DEBUG_OBJECT (self, "Starting video capture");
+
+  if (self->video_renegotiate) {
+    GstCaps *anycaps = gst_caps_new_any ();
+    gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps);
+    gst_caps_unref (anycaps);
+
+    /* clean capsfilter caps so they don't interfere here */
+    g_object_set (self->src_filter, "caps", NULL, NULL);
+  }
+
+  /* unlink from the viewfinder, link to the imagesrc pad, wait for
+   * the buffer to pass */
+  gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, NULL);
+  gst_pad_link (self->src_pad, self->video_tee_sink);
+  gst_ghost_pad_set_target (GST_GHOST_PAD (self->vfsrc),
+      self->video_tee_vf_pad);
+
+  if (self->video_renegotiate) {
+    GST_DEBUG_OBJECT (self, "Getting allowed videosrc caps");
+    caps = gst_pad_get_allowed_caps (self->vidsrc);
+    GST_DEBUG_OBJECT (self, "Video src caps %" GST_PTR_FORMAT, caps);
+
+    self->video_renegotiate = FALSE;
+    gst_wrapper_camera_bin_reset_video_src_caps (self, caps);
+    gst_caps_unref (caps);
+  }
+  self->video_capture_probe = 0;
+
+  return GST_PAD_PROBE_REMOVE;
+}
+
+static gboolean
+gst_wrapper_camera_bin_src_set_mode (GstBaseCameraSrc * bcamsrc,
+    GstCameraBinMode mode)
+{
+  GstPhotography *photography =
+      (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc),
+      GST_TYPE_PHOTOGRAPHY);
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc);
+
+  if (mode == MODE_IMAGE) {
+    self->image_renegotiate = TRUE;
+  } else {
+    self->video_renegotiate = TRUE;
+  }
+  self->mode = mode;
+
+  if (photography) {
+    if (g_object_class_find_property (G_OBJECT_GET_CLASS (photography),
+            "capture-mode")) {
+      g_object_set (G_OBJECT (photography), "capture-mode", mode, NULL);
+    }
+    gst_object_unref (photography);
+  } else {
+    GstCaps *anycaps = gst_caps_new_any ();
+    gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps);
+    gst_caps_unref (anycaps);
+  }
+
+  return TRUE;
+}
+
+static gboolean
+set_videosrc_zoom (GstWrapperCameraBinSrc * self, gfloat zoom)
+{
+  gboolean ret = FALSE;
+
+  if (g_object_class_find_property (G_OBJECT_GET_CLASS (self->src_vid_src),
+          "zoom")) {
+    g_object_set (G_OBJECT (self->src_vid_src), "zoom", zoom, NULL);
+    ret = TRUE;
+  }
+  return ret;
+}
+
+static void
+gst_wrapper_camera_bin_src_set_zoom (GstBaseCameraSrc * bcamsrc, gfloat zoom)
+{
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc);
+
+  GST_INFO_OBJECT (self, "setting zoom %f", zoom);
+
+  if (set_videosrc_zoom (self, zoom)) {
+    g_object_set (self->digitalzoom, "zoom", (gfloat) 1.0, NULL);
+    GST_INFO_OBJECT (self, "zoom set using videosrc");
+  } else {
+    GST_INFO_OBJECT (self, "zoom set using digitalzoom");
+    g_object_set (self->digitalzoom, "zoom", zoom, NULL);
+  }
+}
+
+/**
+ * update_aspect_filter:
+ * @self: camerasrc object
+ * @new_caps: new caps of next buffers arriving to view finder sink element
+ *
+ * Updates aspect ratio capsfilter to maintain aspect ratio, if we need to
+ * scale frames for showing them in view finder.
+ */
+static void
+update_aspect_filter (GstWrapperCameraBinSrc * self, GstCaps * new_caps)
+{
+  /* XXX why not instead add a preserve-aspect-ratio property to videoscale? */
+#if 0
+  if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_SCALE) {
+    GstCaps *sink_caps, *ar_caps;
+    GstStructure *st;
+    gint in_w = 0, in_h = 0, sink_w = 0, sink_h = 0, target_w = 0, target_h = 0;
+    gdouble ratio_w, ratio_h;
+    GstPad *sink_pad;
+    const GValue *range;
+
+    sink_pad = gst_element_get_static_pad (camera->view_sink, "sink");
+
+    if (sink_pad) {
+      sink_caps = gst_pad_get_caps (sink_pad);
+      gst_object_unref (sink_pad);
+      if (sink_caps) {
+        if (!gst_caps_is_any (sink_caps)) {
+          GST_DEBUG_OBJECT (camera, "sink element caps %" GST_PTR_FORMAT,
+              sink_caps);
+          /* Get maximum resolution that view finder sink accepts */
+          st = gst_caps_get_structure (sink_caps, 0);
+          if (gst_structure_has_field_typed (st, "width", GST_TYPE_INT_RANGE)) {
+            range = gst_structure_get_value (st, "width");
+            sink_w = gst_value_get_int_range_max (range);
+          }
+          if (gst_structure_has_field_typed (st, "height", GST_TYPE_INT_RANGE)) {
+            range = gst_structure_get_value (st, "height");
+            sink_h = gst_value_get_int_range_max (range);
+          }
+          GST_DEBUG_OBJECT (camera, "sink element accepts max %dx%d", sink_w,
+              sink_h);
+
+          /* Get incoming frames' resolution */
+          if (sink_h && sink_w) {
+            st = gst_caps_get_structure (new_caps, 0);
+            gst_structure_get_int (st, "width", &in_w);
+            gst_structure_get_int (st, "height", &in_h);
+            GST_DEBUG_OBJECT (camera, "new caps with %dx%d", in_w, in_h);
+          }
+        }
+        gst_caps_unref (sink_caps);
+      }
+    }
+
+    /* If we get bigger frames than view finder sink accepts, then we scale.
+       If we scale we need to adjust aspect ratio capsfilter caps in order
+       to maintain aspect ratio while scaling. */
+    if (in_w && in_h && (in_w > sink_w || in_h > sink_h)) {
+      ratio_w = (gdouble) sink_w / in_w;
+      ratio_h = (gdouble) sink_h / in_h;
+
+      if (ratio_w < ratio_h) {
+        target_w = sink_w;
+        target_h = (gint) (ratio_w * in_h);
+      } else {
+        target_w = (gint) (ratio_h * in_w);
+        target_h = sink_h;
+      }
+
+      GST_DEBUG_OBJECT (camera, "setting %dx%d filter to maintain aspect ratio",
+          target_w, target_h);
+      ar_caps = gst_caps_copy (new_caps);
+      gst_caps_set_simple (ar_caps, "width", G_TYPE_INT, target_w, "height",
+          G_TYPE_INT, target_h, NULL);
+    } else {
+      GST_DEBUG_OBJECT (camera, "no scaling");
+      ar_caps = new_caps;
+    }
+
+    GST_DEBUG_OBJECT (camera, "aspect ratio filter caps %" GST_PTR_FORMAT,
+        ar_caps);
+    g_object_set (G_OBJECT (camera->aspect_filter), "caps", ar_caps, NULL);
+    if (ar_caps != new_caps)
+      gst_caps_unref (ar_caps);
+  }
+#endif
+}
+
+
+/**
+ * set_capsfilter_caps:
+ * @self: camerasrc object
+ * @new_caps: pointer to caps object to set
+ *
+ * Set given caps to camerabin capsfilters.
+ */
+static void
+set_capsfilter_caps (GstWrapperCameraBinSrc * self, GstCaps * new_caps)
+{
+  GST_INFO_OBJECT (self, "new_caps:%" GST_PTR_FORMAT, new_caps);
+
+  /* Update zoom */
+  gst_base_camera_src_setup_zoom (GST_BASE_CAMERA_SRC (self));
+
+  /* Update capsfilters */
+  g_object_set (G_OBJECT (self->src_filter), "caps", new_caps, NULL);
+  update_aspect_filter (self, new_caps);
+  GST_INFO_OBJECT (self, "updated");
+}
+
+static gboolean
+gst_wrapper_camera_bin_src_start_capture (GstBaseCameraSrc * camerasrc)
+{
+  GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc);
+  GstPad *pad;
+  gboolean ret = TRUE;
+
+  pad = gst_element_get_static_pad (src->src_vid_src, "src");
+
+  /* TODO should we access this directly? Maybe a macro is better? */
+  if (src->mode == MODE_IMAGE) {
+    src->image_capture_count = 1;
+
+    src->image_capture_probe =
+        gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, start_image_capture,
+        src, NULL);
+  } else if (src->mode == MODE_VIDEO) {
+    if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) {
+      src->video_rec_status = GST_VIDEO_RECORDING_STATUS_STARTING;
+      src->video_capture_probe =
+          gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, start_video_capture,
+          src, NULL);
+    }
+  } else {
+    g_assert_not_reached ();
+    ret = FALSE;
+  }
+  gst_object_unref (pad);
+  return ret;
+}
+
+static void
+gst_wrapper_camera_bin_src_stop_capture (GstBaseCameraSrc * camerasrc)
+{
+  GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc);
+
+  /* TODO should we access this directly? Maybe a macro is better? */
+  if (src->mode == MODE_VIDEO) {
+    if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) {
+      GST_DEBUG_OBJECT (src, "Aborting, had not started recording");
+      src->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE;
+
+    } else if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_RUNNING) {
+      GST_DEBUG_OBJECT (src, "Marking video recording as finishing");
+      src->video_rec_status = GST_VIDEO_RECORDING_STATUS_FINISHING;
+    }
+  } else {
+    /* TODO check what happens when we try to stop a image capture */
+  }
+}
+
+static GstStateChangeReturn
+gst_wrapper_camera_bin_src_change_state (GstElement * element,
+    GstStateChange trans)
+{
+  GstStateChangeReturn ret;
+  GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (element);
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    goto end;
+
+  switch (trans) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      self->video_renegotiate = TRUE;
+      self->image_renegotiate = TRUE;
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      break;
+    default:
+      break;
+  }
+
+end:
+  return ret;
+}
+
+static void
+gst_wrapper_camera_bin_src_class_init (GstWrapperCameraBinSrcClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBaseCameraSrcClass *gstbasecamerasrc_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+  gstbasecamerasrc_class = GST_BASE_CAMERA_SRC_CLASS (klass);
+
+  gobject_class->dispose = gst_wrapper_camera_bin_src_dispose;
+  gobject_class->finalize =
+      (GObjectFinalizeFunc) gst_wrapper_camera_bin_src_finalize;
+  gobject_class->set_property = gst_wrapper_camera_bin_src_set_property;
+  gobject_class->get_property = gst_wrapper_camera_bin_src_get_property;
+
+  /* g_object_class_install_property .... */
+  g_object_class_install_property (gobject_class, PROP_VIDEO_SRC,
+      g_param_spec_object ("video-source", "Video source",
+          "The video source element to be used",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_VIDEO_SRC_FILTER,
+      g_param_spec_object ("video-source-filter", "Video source filter",
+          "Optional video source filter element",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gstelement_class->change_state = gst_wrapper_camera_bin_src_change_state;
+
+  gstbasecamerasrc_class->construct_pipeline =
+      gst_wrapper_camera_bin_src_construct_pipeline;
+  gstbasecamerasrc_class->set_zoom = gst_wrapper_camera_bin_src_set_zoom;
+  gstbasecamerasrc_class->set_mode = gst_wrapper_camera_bin_src_set_mode;
+  gstbasecamerasrc_class->start_capture =
+      gst_wrapper_camera_bin_src_start_capture;
+  gstbasecamerasrc_class->stop_capture =
+      gst_wrapper_camera_bin_src_stop_capture;
+
+  GST_DEBUG_CATEGORY_INIT (wrapper_camera_bin_src_debug, "wrappercamerabinsrc",
+      0, "wrapper camera src");
+
+  gst_element_class_add_static_pad_template (gstelement_class, &vfsrc_template);
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &imgsrc_template);
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &vidsrc_template);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Wrapper camera src element for camerabin2", "Source/Video",
+      "Wrapper camera src element for camerabin2",
+      "Thiago Santos <thiago.sousa.santos@collabora.com>");
+}
+
+static void
+gst_wrapper_camera_bin_src_init (GstWrapperCameraBinSrc * self)
+{
+  self->vfsrc =
+      gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME,
+      GST_PAD_SRC);
+  gst_element_add_pad (GST_ELEMENT (self), self->vfsrc);
+
+  self->imgsrc =
+      gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME,
+      GST_PAD_SRC);
+  gst_element_add_pad (GST_ELEMENT (self), self->imgsrc);
+
+  self->vidsrc =
+      gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME,
+      GST_PAD_SRC);
+  gst_element_add_pad (GST_ELEMENT (self), self->vidsrc);
+
+  gst_pad_set_event_function (self->imgsrc,
+      GST_DEBUG_FUNCPTR (gst_wrapper_camera_bin_src_src_event));
+  gst_pad_set_event_function (self->vidsrc,
+      GST_DEBUG_FUNCPTR (gst_wrapper_camera_bin_src_src_event));
+
+  /* TODO where are variables reset? */
+  self->image_capture_count = 0;
+  self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE;
+  self->video_renegotiate = TRUE;
+  self->image_renegotiate = TRUE;
+  self->mode = GST_BASE_CAMERA_SRC_CAST (self)->mode;
+  self->app_vid_filter = NULL;
+}
+
+gboolean
+gst_wrapper_camera_bin_src_plugin_init (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "wrappercamerabinsrc", GST_RANK_NONE,
+      gst_wrapper_camera_bin_src_get_type ());
+}
Index: b/gst/camerabin2/gstwrappercamerabinsrc.h
===================================================================
--- /dev/null
+++ b/gst/camerabin2/gstwrappercamerabinsrc.h
@@ -0,0 +1,131 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Texas Instruments, Inc
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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_WRAPPER_CAMERA_BIN_SRC_H__
+#define __GST_WRAPPER_CAMERA_BIN_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/basecamerabinsrc/gstbasecamerasrc.h>
+#include <gst/basecamerabinsrc/gstcamerabinpreview.h>
+#include "camerabingeneral.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_WRAPPER_CAMERA_BIN_SRC \
+  (gst_wrapper_camera_bin_src_get_type())
+#define GST_WRAPPER_CAMERA_BIN_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WRAPPER_CAMERA_BIN_SRC,GstWrapperCameraBinSrc))
+#define GST_WRAPPER_CAMERA_BIN_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_WRAPPER_CAMERA_BIN_SRC,GstWrapperCameraBinSrcClass))
+#define GST_IS_WRAPPER_CAMERA_BIN_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WRAPPER_CAMERA_BIN_SRC))
+#define GST_IS_WRAPPER_CAMERA_BIN_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_WRAPPER_CAMERA_BIN_SRC))
+    GType gst_wrapper_camera_bin_src_get_type (void);
+
+typedef struct _GstWrapperCameraBinSrc GstWrapperCameraBinSrc;
+typedef struct _GstWrapperCameraBinSrcClass GstWrapperCameraBinSrcClass;
+
+enum GstVideoRecordingStatus {
+  GST_VIDEO_RECORDING_STATUS_DONE,
+  GST_VIDEO_RECORDING_STATUS_STARTING,
+  GST_VIDEO_RECORDING_STATUS_RUNNING,
+  GST_VIDEO_RECORDING_STATUS_FINISHING
+};
+
+
+/**
+ * GstWrapperCameraBinSrc:
+ *
+ */
+struct _GstWrapperCameraBinSrc
+{
+  GstBaseCameraSrc parent;
+
+  GstCameraBinMode mode;
+
+  GstPad *srcfilter_pad;
+  GstPad *vfsrc;
+  GstPad *imgsrc;
+  GstPad *vidsrc;
+
+  /* video recording controls */
+  gint video_rec_status;
+
+  /* image capture controls */
+  gint image_capture_count;
+
+  /* source elements */
+  GstElement *src_vid_src;
+  GstElement *video_filter;
+  GstElement *src_filter;
+  GstElement *digitalzoom;
+
+  /* Pad from our last element that is linked
+   * with the output pads */
+  GstPad *src_pad;
+
+  GstPad *video_tee_vf_pad;
+  GstPad *video_tee_sink;
+
+  gboolean elements_created;
+
+  gulong src_event_probe_id;
+  gulong src_max_zoom_signal_id;
+  gulong image_capture_probe;
+  gulong video_capture_probe;
+
+  /* Application configurable elements */
+  GstElement *app_vid_src;
+  GstElement *app_vid_filter;
+
+  /* Caps that videosrc supports */
+  GstCaps *allowed_caps;
+
+  /* Optional crop for frames. Used to crop frames e.g.
+     due to wrong aspect ratio. Done before the crop related to zooming. */
+  GstElement *src_crop;
+
+  /* Caps applied to capsfilters when in view finder mode */
+  GstCaps *view_finder_caps;
+
+  /* Caps applied to capsfilters when taking still image */
+  GstCaps *image_capture_caps;
+  gboolean image_renegotiate;
+  gboolean video_renegotiate;
+};
+
+
+/**
+ * GstWrapperCameraBinSrcClass:
+ *
+ */
+struct _GstWrapperCameraBinSrcClass
+{
+  GstBaseCameraSrcClass parent;
+};
+
+gboolean gst_wrapper_camera_bin_src_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif /* __GST_WRAPPER_CAMERA_BIN_SRC_H__ */
Index: b/gst/camerabin2/meson.build
===================================================================
--- /dev/null
+++ b/gst/camerabin2/meson.build
@@ -0,0 +1,20 @@
+camerabin_sources = [
+  'gstdigitalzoom.c',
+  'gstviewfinderbin.c',
+  'camerabingeneral.c',
+  'gstwrappercamerabinsrc.c',
+  'gstcamerabin2.c',
+  'gstplugin.c',
+]
+
+gstcamerabin = library('gstcamerabin',
+  camerabin_sources,
+  c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+  include_directories : [configinc, libsinc],
+  link_with : gstbasecamerabin,
+  dependencies : [gstbasecamerabin_dep, gstphotography_dep, gsttag_dep, gstapp_dep, gstpbutils_dep, gstbase_dep],
+  install : true,
+  install_dir : plugins_install_dir,
+)
+pkgconfig.generate(gstcamerabin, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstcamerabin]
Index: b/gst/meson.build
===================================================================
--- a/gst/meson.build
+++ b/gst/meson.build
@@ -1,5 +1,5 @@
 foreach plugin : ['alpha', 'apetag', 'audiofx', 'audioparsers', 'auparse',
-                  'autodetect', 'avi', 'cutter', 'debugutils', 'deinterlace',
+                  'autodetect', 'avi', 'camerabin2', 'cutter', 'debugutils', 'deinterlace',
                   'dtmf', 'effectv', 'equalizer', 'flv', 'flx', 'goom',
                   'goom2k1', 'icydemux', 'id3demux', 'imagefreeze',
                   'interleave', 'isomp4', 'law', 'level', 'matroska',
Index: b/meson_options.txt
===================================================================
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,6 +6,7 @@
 option('auparse', type : 'feature', value : 'auto')
 option('autodetect', type : 'feature', value : 'auto')
 option('avi', type : 'feature', value : 'auto')
+option('camerabin2', type : 'feature', value : 'auto')
 option('cutter', type : 'feature', value : 'auto')
 option('debugutils', type : 'feature', value : 'auto')
 option('deinterlace', type : 'feature', value : 'auto')
Index: b/tests/check/elements/camerabin.c
===================================================================
--- /dev/null
+++ b/tests/check/elements/camerabin.c
@@ -0,0 +1,1998 @@
+/* GStreamer
+ *
+ * unit test for camerabin basic operations
+ * Copyright (C) 2010 Nokia Corporation <multimedia@maemo.org>
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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 <glib.h>
+#include <glib/gstdio.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/check/gstcheck.h>
+#include <gst/basecamerabinsrc/gstbasecamerasrc.h>
+#include <gst/base/gstpushsrc.h>
+#include <gst/interfaces/photography.h>
+#include <gst/pbutils/encoding-profile.h>
+
+#define IMAGE_FILENAME "image"
+#define VIDEO_FILENAME "video"
+#define CAPTURE_COUNT 3
+#define VIDEO_DURATION 5
+
+#define VIDEO_PAD_SUPPORTED_CAPS "video/x-raw, format=RGB, width=600, height=480"
+#define IMAGE_PAD_SUPPORTED_CAPS "video/x-raw, format=RGB, width=800, height=600"
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw, format=RGB"));
+
+static GstStaticPadTemplate vfsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate imgsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate vidsrc_template =
+GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME,
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+/* custom test camera src element */
+#define GST_TYPE_TEST_CAMERA_SRC \
+  (gst_test_camera_src_get_type())
+#define GST_TEST_CAMERA_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TEST_CAMERA_SRC,GstTestCameraSrc))
+#define GST_TEST_CAMERA_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TEST_CAMERA_SRC,GstTestCameraSrcClass))
+#define GST_TEST_CAMERA_SRC_CAST(obj) ((GstTestCameraSrc *)obj)
+
+typedef struct _GstTestCameraSrc GstTestCameraSrc;
+typedef struct _GstTestCameraSrcClass GstTestCameraSrcClass;
+struct _GstTestCameraSrc
+{
+  GstBaseCameraSrc element;
+
+  GstPad *vfpad;
+  GstPad *vidpad;
+  GstPad *imgpad;
+
+  GstCameraBinMode mode;
+};
+
+struct _GstTestCameraSrcClass
+{
+  GstBaseCameraSrcClass parent_class;
+};
+
+GType gst_test_camera_src_get_type (void);
+
+G_DEFINE_TYPE (GstTestCameraSrc, gst_test_camera_src, GST_TYPE_BASE_CAMERA_SRC);
+
+static gboolean
+gst_test_camera_src_set_mode (GstBaseCameraSrc * src, GstCameraBinMode mode)
+{
+  GstTestCameraSrc *self = GST_TEST_CAMERA_SRC (src);
+
+  self->mode = mode;
+  return TRUE;
+}
+
+static gboolean
+gst_test_camera_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+  GstTestCameraSrc *self = (GstTestCameraSrc *) GST_PAD_PARENT (pad);
+  GstCaps *result = NULL;
+  gboolean ret = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CAPS:
+      if (pad == self->vfpad) {
+        result = gst_caps_new_any ();
+      } else if (pad == self->vidpad) {
+        result = gst_caps_from_string (VIDEO_PAD_SUPPORTED_CAPS);
+      } else if (pad == self->imgpad) {
+        result = gst_caps_from_string (IMAGE_PAD_SUPPORTED_CAPS);
+      } else {
+        g_assert_not_reached ();
+      }
+      if (result) {
+        GstCaps *filter;
+
+        gst_query_parse_caps (query, &filter);
+        if (filter) {
+          GstCaps *tmp;
+          tmp = gst_caps_intersect (result, filter);
+          gst_caps_replace (&result, tmp);
+          gst_caps_unref (tmp);
+        }
+        gst_query_set_caps_result (query, result);
+        gst_caps_unref (result);
+        ret = TRUE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_test_camera_src_class_init (GstTestCameraSrcClass * klass)
+{
+  GstBaseCameraSrcClass *gstbasecamera_class;
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  gstbasecamera_class = GST_BASE_CAMERA_SRC_CLASS (klass);
+  gstbasecamera_class->set_mode = gst_test_camera_src_set_mode;
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Test Camera Src",
+      "Camera/Src",
+      "Some test camera src",
+      "Thiago Santos <thiago.sousa.santos@collabora.com>");
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &vidsrc_template);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &imgsrc_template);
+  gst_element_class_add_static_pad_template (gstelement_class, &vfsrc_template);
+}
+
+static void
+gst_test_camera_src_init (GstTestCameraSrc * self)
+{
+  GstElementClass *gstelement_class = GST_ELEMENT_GET_CLASS (self);
+  GstPadTemplate *template;
+
+  /* create pads */
+  template = gst_element_class_get_pad_template (gstelement_class,
+      GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME);
+  self->vfpad = gst_pad_new_from_template (template,
+      GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME);
+  gst_element_add_pad (GST_ELEMENT_CAST (self), self->vfpad);
+
+  template = gst_element_class_get_pad_template (gstelement_class,
+      GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME);
+  self->imgpad = gst_pad_new_from_template (template,
+      GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME);
+  gst_element_add_pad (GST_ELEMENT_CAST (self), self->imgpad);
+
+  template = gst_element_class_get_pad_template (gstelement_class,
+      GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME);
+  self->vidpad = gst_pad_new_from_template (template,
+      GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME);
+  gst_element_add_pad (GST_ELEMENT_CAST (self), self->vidpad);
+
+  /* add get caps functions */
+  gst_pad_set_query_function (self->vfpad, gst_test_camera_src_query);
+  gst_pad_set_query_function (self->vidpad, gst_test_camera_src_query);
+  gst_pad_set_query_function (self->imgpad, gst_test_camera_src_query);
+}
+
+/* end of custom test camera src element */
+
+/* custom video source element that implements GstPhotography iface */
+
+#define GST_TYPE_TEST_VIDEO_SRC \
+  (gst_test_video_src_get_type())
+#define GST_TEST_VIDEO_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TEST_VIDEO_SRC,GstTestVideoSrc))
+#define GST_TEST_VIDEO_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TEST_VIDEO_SRC,GstTestVideoSrcClass))
+#define GST_TEST_VIDEO_SRC_CAST(obj) ((GstTestVideoSrc *)obj)
+
+typedef struct _GstTestVideoSrc GstTestVideoSrc;
+typedef struct _GstTestVideoSrcClass GstTestVideoSrcClass;
+struct _GstTestVideoSrc
+{
+  GstPushSrc element;
+
+  gint width, height;
+  GstCaps *caps;
+
+  /* if TRUE, this element will only output resolutions with       *
+   * same width and height (square frames). This allows us testing *
+   * extra cropping feature with GstPhotography interface captures */
+  gboolean enable_resolution_restriction;
+};
+
+struct _GstTestVideoSrcClass
+{
+  GstPushSrcClass parent_class;
+};
+
+GType gst_test_video_src_get_type (void);
+
+enum
+{
+  PROP_0,
+  PROP_WB_MODE,
+  PROP_COLOR_TONE,
+  PROP_SCENE_MODE,
+  PROP_FLASH_MODE,
+  PROP_FLICKER_MODE,
+  PROP_FOCUS_MODE,
+  PROP_CAPABILITIES,
+  PROP_EV_COMP,
+  PROP_ISO_SPEED,
+  PROP_APERTURE,
+  PROP_EXPOSURE_TIME,
+  PROP_IMAGE_PREVIEW_SUPPORTED_CAPS,
+  PROP_IMAGE_CAPTURE_SUPPORTED_CAPS,
+  PROP_ZOOM,
+  PROP_COLOR_TEMPERATURE,
+  PROP_WHITE_POINT,
+  PROP_ANALOG_GAIN,
+  PROP_LENS_FOCUS,
+  PROP_MIN_EXPOSURE_TIME,
+  PROP_MAX_EXPORURE_TIME,
+  PROP_NOISE_REDUCTION,
+  PROP_EXPOSURE_MODE
+};
+
+static gboolean
+gst_test_video_src_prepare_for_capture (GstPhotography * photo,
+    GstPhotographyCapturePrepared func, GstCaps * capture_caps,
+    gpointer user_data)
+{
+  GstCaps *caps;
+  GstTestVideoSrc *testvideosrc = GST_TEST_VIDEO_SRC (photo);
+
+  if (testvideosrc->enable_resolution_restriction) {
+    GstStructure *str = gst_caps_get_structure (capture_caps, 0);
+    gint width, height;
+
+    gst_structure_get_int (str, "width", &width);
+    gst_structure_get_int (str, "height", &height);
+
+    width = height = MAX (width, height);
+    str = gst_structure_copy (str);
+    gst_structure_set (str, "width", G_TYPE_INT, width, "height", G_TYPE_INT,
+        height, NULL);
+    caps = gst_caps_new_full (str, NULL);
+    caps = gst_caps_fixate (caps);
+    fail_unless (testvideosrc->caps == NULL);
+    testvideosrc->caps = gst_caps_ref (caps);
+  } else {
+    caps = gst_caps_ref (capture_caps);
+  }
+
+  func (user_data, caps);
+  gst_caps_unref (caps);
+  return TRUE;
+}
+
+static void
+gst_test_video_src_photography_init (gpointer g_iface, gpointer iface_data)
+{
+  GstPhotographyInterface *iface = g_iface;
+
+  iface->prepare_for_capture = gst_test_video_src_prepare_for_capture;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GstTestVideoSrc, gst_test_video_src, GST_TYPE_PUSH_SRC,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_PHOTOGRAPHY,
+        gst_test_video_src_photography_init));
+
+static void
+gst_test_video_src_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  /* don't care */
+}
+
+static void
+gst_test_video_src_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  /* don't care */
+}
+
+static gboolean
+gst_test_video_src_set_caps (GstBaseSrc * src, GstCaps * caps)
+{
+  GstTestVideoSrc *self = GST_TEST_VIDEO_SRC (src);
+  GstStructure *structure = gst_caps_get_structure (caps, 0);
+
+
+  fail_unless (gst_structure_get_int (structure, "width", &self->width));
+  fail_unless (gst_structure_get_int (structure, "height", &self->height));
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_test_video_src_alloc (GstPushSrc * src, GstBuffer ** buf)
+{
+  GstTestVideoSrc *self = GST_TEST_VIDEO_SRC (src);
+  guint8 *data;
+  gsize data_size;
+
+  if (self->caps) {
+    gst_base_src_set_caps (GST_BASE_SRC (self), self->caps);
+    gst_caps_unref (self->caps);
+    self->caps = NULL;
+  }
+
+  data_size = self->width * self->height * 3;   /* RGB size */
+  data = g_malloc (data_size);
+  *buf = gst_buffer_new_wrapped (data, data_size);
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_test_video_src_fill (GstPushSrc * src, GstBuffer * buf)
+{
+  /* NOP */
+  return GST_FLOW_OK;
+}
+
+static void
+gst_test_video_src_class_init (GstTestVideoSrcClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+  GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
+  GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Test Camera Video Src",
+      "Video/Src",
+      "Test camera video src", "Thiago Santos <thiagoss@osg.samsung.com>");
+
+  gst_element_class_add_static_pad_template (gstelement_class, &src_template);
+
+  gobject_class->get_property = gst_test_video_src_get_property;
+  gobject_class->set_property = gst_test_video_src_set_property;
+
+  gstbasesrc_class->set_caps = gst_test_video_src_set_caps;
+  gstpushsrc_class->alloc = gst_test_video_src_alloc;
+  gstpushsrc_class->fill = gst_test_video_src_fill;
+
+  /* photography interface properties */
+  g_object_class_override_property (gobject_class, PROP_WB_MODE,
+      GST_PHOTOGRAPHY_PROP_WB_MODE);
+  g_object_class_override_property (gobject_class, PROP_COLOR_TONE,
+      GST_PHOTOGRAPHY_PROP_COLOR_TONE);
+  g_object_class_override_property (gobject_class, PROP_SCENE_MODE,
+      GST_PHOTOGRAPHY_PROP_SCENE_MODE);
+  g_object_class_override_property (gobject_class, PROP_FLASH_MODE,
+      GST_PHOTOGRAPHY_PROP_FLASH_MODE);
+  g_object_class_override_property (gobject_class, PROP_FLICKER_MODE,
+      GST_PHOTOGRAPHY_PROP_FLICKER_MODE);
+  g_object_class_override_property (gobject_class, PROP_FOCUS_MODE,
+      GST_PHOTOGRAPHY_PROP_FOCUS_MODE);
+  g_object_class_override_property (gobject_class, PROP_CAPABILITIES,
+      GST_PHOTOGRAPHY_PROP_CAPABILITIES);
+  g_object_class_override_property (gobject_class, PROP_EV_COMP,
+      GST_PHOTOGRAPHY_PROP_EV_COMP);
+  g_object_class_override_property (gobject_class, PROP_ISO_SPEED,
+      GST_PHOTOGRAPHY_PROP_ISO_SPEED);
+  g_object_class_override_property (gobject_class, PROP_APERTURE,
+      GST_PHOTOGRAPHY_PROP_APERTURE);
+  g_object_class_override_property (gobject_class, PROP_EXPOSURE_TIME,
+      GST_PHOTOGRAPHY_PROP_EXPOSURE_TIME);
+  g_object_class_override_property (gobject_class,
+      PROP_IMAGE_PREVIEW_SUPPORTED_CAPS,
+      GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS);
+  g_object_class_override_property (gobject_class,
+      PROP_IMAGE_CAPTURE_SUPPORTED_CAPS,
+      GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS);
+  g_object_class_override_property (gobject_class, PROP_ZOOM,
+      GST_PHOTOGRAPHY_PROP_ZOOM);
+  g_object_class_override_property (gobject_class, PROP_COLOR_TEMPERATURE,
+      GST_PHOTOGRAPHY_PROP_COLOR_TEMPERATURE);
+  g_object_class_override_property (gobject_class, PROP_WHITE_POINT,
+      GST_PHOTOGRAPHY_PROP_WHITE_POINT);
+  g_object_class_override_property (gobject_class, PROP_ANALOG_GAIN,
+      GST_PHOTOGRAPHY_PROP_ANALOG_GAIN);
+  g_object_class_override_property (gobject_class, PROP_LENS_FOCUS,
+      GST_PHOTOGRAPHY_PROP_LENS_FOCUS);
+  g_object_class_override_property (gobject_class, PROP_MIN_EXPOSURE_TIME,
+      GST_PHOTOGRAPHY_PROP_MIN_EXPOSURE_TIME);
+  g_object_class_override_property (gobject_class, PROP_MAX_EXPORURE_TIME,
+      GST_PHOTOGRAPHY_PROP_MAX_EXPOSURE_TIME);
+  g_object_class_override_property (gobject_class, PROP_NOISE_REDUCTION,
+      GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION);
+  g_object_class_override_property (gobject_class, PROP_EXPOSURE_MODE,
+      GST_PHOTOGRAPHY_PROP_EXPOSURE_MODE);
+}
+
+static void
+gst_test_video_src_init (GstTestVideoSrc * self)
+{
+  gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
+}
+
+/* end of custom test camera src element */
+/* end of custom video source element that implements GstPhotography iface */
+
+
+static GstElement *camera;
+static GstElement *testsrc;
+static GstBus *bus = NULL;
+static GMainLoop *main_loop;
+static gint capture_count = 0;
+guint32 test_id = 0;
+static gchar *image_filename;
+static gchar *video_filename;
+
+static GstSample *preview_sample;
+static gchar *preview_filename;
+static GstCaps *preview_caps;
+static GstTagList *tags_found;
+
+static gboolean
+validity_bus_cb (GstBus * bus, GstMessage * message, gpointer data);
+
+static GstMessage *wait_for_element_message (GstElement * camera,
+    const gchar * name, GstClockTime timeout);
+
+static void
+validate_taglist_foreach (const GstTagList * list, const gchar * tag,
+    gpointer user_data)
+{
+  GstTagList *other = GST_TAG_LIST (user_data);
+
+  const GValue *val1 = gst_tag_list_get_value_index (list, tag, 0);
+  const GValue *val2 = gst_tag_list_get_value_index (other, tag, 0);
+
+  GST_DEBUG ("checking tag '%s'", tag);
+
+  fail_if (val1 == NULL);
+  fail_if (val2 == NULL);
+
+  fail_unless (gst_value_compare (val1, val2) == GST_VALUE_EQUAL);
+}
+
+
+/* helper function for filenames */
+static gchar *
+make_test_file_name (const gchar * base_name, gint num)
+{
+  /* num == -1 means to keep the %d in the resulting string to be used on
+   * multifilesink like location */
+  if (num == -1) {
+    return g_strdup_printf ("%s" G_DIR_SEPARATOR_S
+        "gstcamerabintest_%s_%u_%%03d.cap", g_get_tmp_dir (), base_name,
+        test_id);
+  } else {
+    return g_strdup_printf ("%s" G_DIR_SEPARATOR_S
+        "gstcamerabintest_%s_%u_%03d.cap", g_get_tmp_dir (), base_name,
+        test_id, num);
+  }
+}
+
+static const gchar *
+make_const_file_name (const gchar * filename, gint num)
+{
+  static gchar file_name[1000];
+
+  /* num == -1 means to keep the %d in the resulting string to be used on
+   * multifilesink like location */
+  g_snprintf (file_name, 999, filename, num);
+
+  return file_name;
+}
+
+/* configuration */
+
+static gboolean
+capture_bus_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+  GMainLoop *loop = (GMainLoop *) data;
+  const GstStructure *st;
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err = NULL;
+      gchar *debug = NULL;
+
+      gst_message_parse_error (message, &err, &debug);
+      GST_WARNING ("ERROR: %s [%s]", err->message, debug);
+      g_error_free (err);
+      g_free (debug);
+      /* Write debug graph to file */
+      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (camera),
+          GST_DEBUG_GRAPH_SHOW_ALL, "camerabin.error");
+
+      fail_if (TRUE, "error while capturing");
+      g_main_loop_quit (loop);
+      break;
+    }
+    case GST_MESSAGE_WARNING:{
+      GError *err = NULL;
+      gchar *debug = NULL;
+
+      gst_message_parse_warning (message, &err, &debug);
+      GST_WARNING ("WARNING: %s [%s]", err->message, debug);
+      g_error_free (err);
+      g_free (debug);
+      /* Write debug graph to file */
+      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (camera),
+          GST_DEBUG_GRAPH_SHOW_ALL, "camerabin.warning");
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      GST_DEBUG ("eos");
+      g_main_loop_quit (loop);
+      break;
+    default:
+      st = gst_message_get_structure (message);
+      if (st && gst_structure_has_name (st, "image-done")) {
+        GST_INFO ("image captured");
+      } else if (st && gst_structure_has_name (st,
+              GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME)) {
+        GstSample *sample;
+        const GValue *value;
+
+        value = gst_structure_get_value (st, "sample");
+        fail_unless (value != NULL);
+        sample = gst_value_get_sample (value);
+
+        if (preview_sample)
+          gst_sample_unref (preview_sample);
+        preview_sample = gst_sample_ref (sample);
+        g_free (preview_filename);
+        preview_filename = g_strdup (gst_structure_get_string (st, "location"));
+      }
+      break;
+  }
+  return TRUE;
+}
+
+static void
+check_preview_image (GstElement * camera, const gchar * filename, gint index)
+{
+  gchar *prev_filename = NULL;
+
+  if (!preview_sample && camera) {
+    GstMessage *msg = wait_for_element_message (camera,
+        GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME, GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+  }
+  fail_unless (preview_sample != NULL);
+  if (filename) {
+    if (index >= 0) {
+      prev_filename = g_strdup_printf (filename, index);
+    } else {
+      prev_filename = g_strdup (filename);
+    }
+    fail_unless (preview_filename != NULL);
+    fail_unless (strcmp (preview_filename, prev_filename) == 0);
+  }
+  if (preview_caps) {
+    fail_unless (gst_sample_get_caps (preview_sample) != NULL);
+    fail_unless (gst_caps_can_intersect (gst_sample_get_caps (preview_sample),
+            preview_caps));
+  }
+  g_free (prev_filename);
+
+  /* clean up preview info for next capture */
+  g_free (preview_filename);
+  preview_filename = NULL;
+  if (preview_sample)
+    gst_sample_unref (preview_sample);
+  preview_sample = NULL;
+}
+
+static void
+extract_jpeg_tags (const gchar * filename, gint num)
+{
+  GstBus *bus;
+  GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+  const gchar *filepath = make_const_file_name (filename, num);
+  gchar *pipeline_str = g_strdup_printf ("filesrc location=%s ! "
+      "jpegparse ! fakesink", filepath);
+  GstElement *pipeline;
+
+  pipeline = gst_parse_launch (pipeline_str, NULL);
+  fail_unless (pipeline != NULL);
+  g_free (pipeline_str);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+  gst_bus_add_watch (bus, (GstBusFunc) validity_bus_cb, loop);
+
+  gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  g_main_loop_run (loop);
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+
+  gst_bus_remove_watch (bus);
+  gst_object_unref (bus);
+  gst_object_unref (pipeline);
+  g_main_loop_unref (loop);
+}
+
+static void
+setup_camerabin_common (void)
+{
+  test_id = g_random_int ();
+
+  main_loop = g_main_loop_new (NULL, TRUE);
+
+  camera = gst_check_setup_element ("camerabin");
+  fail_unless (camera != NULL, "failed to create camerabin element");
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (camera));
+  gst_bus_add_watch (bus, (GstBusFunc) capture_bus_cb, main_loop);
+
+  tags_found = NULL;
+  capture_count = 0;
+  image_filename = make_test_file_name (IMAGE_FILENAME, -1);
+  video_filename = make_test_file_name (VIDEO_FILENAME, -1);
+}
+
+static void
+setup_wrappercamerabinsrc_videotestsrc (void)
+{
+  GstElement *vfbin;
+  GstElement *fakevideosink;
+  GstElement *src;
+  GstElement *testsrc;
+  GstElement *audiosrc;
+
+  GST_INFO ("init");
+
+  setup_camerabin_common ();
+
+  fakevideosink = gst_element_factory_make ("fakesink", NULL);
+  fail_unless (fakevideosink != NULL, "failed to create fakesink element");
+  src = gst_element_factory_make ("wrappercamerabinsrc", NULL);
+  fail_unless (src != NULL, "failed to create wrappercamerabinsrc element");
+  testsrc = gst_element_factory_make ("videotestsrc", NULL);
+  fail_unless (testsrc != NULL, "failed to create videotestsrc element");
+  audiosrc = gst_element_factory_make ("audiotestsrc", NULL);
+  fail_unless (audiosrc != NULL, "failed to create audiotestsrc element");
+
+  preview_caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+      320, "height", G_TYPE_INT, 240, NULL);
+
+  g_object_set (G_OBJECT (testsrc), "is-live", TRUE, NULL);
+  g_object_set (G_OBJECT (audiosrc), "is-live", TRUE, NULL);
+  g_object_set (G_OBJECT (src), "video-source", testsrc, NULL);
+  g_object_set (G_OBJECT (camera), "camera-source", src, "preview-caps",
+      preview_caps, "post-previews", TRUE, "audio-source", audiosrc, NULL);
+  gst_object_unref (src);
+  gst_object_unref (testsrc);
+  gst_object_unref (audiosrc);
+
+  vfbin = gst_bin_get_by_name (GST_BIN (camera), "vf-bin");
+  g_object_set (G_OBJECT (vfbin), "video-sink", fakevideosink, NULL);
+  gst_object_unref (vfbin);
+  gst_object_unref (fakevideosink);
+
+  GST_INFO ("init finished");
+}
+
+static void
+setup_test_camerasrc (void)
+{
+  GstElement *vfbin;
+  GstElement *fakevideosink;
+  GstElement *src;
+  GstElement *audiosrc;
+
+  GST_INFO ("init");
+
+  setup_camerabin_common ();
+
+  fakevideosink = gst_element_factory_make ("fakesink", NULL);
+  fail_unless (fakevideosink != NULL, "failed to create fakesink element");
+  src = gst_element_factory_make ("wrappercamerabinsrc", NULL);
+  fail_unless (src != NULL, "failed to create wrappercamerabinsrc element");
+  testsrc = g_object_new (GST_TYPE_TEST_VIDEO_SRC, NULL);
+  fail_unless (testsrc != NULL, "failed to create testvideosrc element");
+  g_object_set (testsrc, "name", "testsrc", NULL);
+  audiosrc = gst_element_factory_make ("audiotestsrc", NULL);
+  fail_unless (audiosrc != NULL, "failed to create audiotestsrc element");
+
+  preview_caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+      320, "height", G_TYPE_INT, 240, NULL);
+
+  g_object_set (G_OBJECT (audiosrc), "is-live", TRUE, NULL);
+  g_object_set (G_OBJECT (src), "video-source", testsrc, NULL);
+  g_object_set (G_OBJECT (camera), "camera-source", src, "preview-caps",
+      preview_caps, "post-previews", TRUE, "audio-source", audiosrc, NULL);
+  gst_object_unref (src);
+  gst_object_unref (testsrc);
+  gst_object_unref (audiosrc);
+
+  vfbin = gst_bin_get_by_name (GST_BIN (camera), "vf-bin");
+  g_object_set (G_OBJECT (vfbin), "video-sink", fakevideosink, NULL);
+  gst_object_unref (vfbin);
+  gst_object_unref (fakevideosink);
+
+  GST_INFO ("init finished");
+}
+
+static void
+teardown (void)
+{
+  gst_element_set_state (camera, GST_STATE_NULL);
+
+  if (camera)
+    gst_check_teardown_element (camera);
+  camera = NULL;
+
+  if (bus) {
+    gst_bus_remove_watch (bus);
+    gst_object_unref (bus);
+  }
+
+  if (main_loop)
+    g_main_loop_unref (main_loop);
+  main_loop = NULL;
+
+  if (preview_caps)
+    gst_caps_unref (preview_caps);
+  preview_caps = NULL;
+
+  if (preview_sample)
+    gst_sample_unref (preview_sample);
+  preview_sample = NULL;
+
+  g_free (preview_filename);
+  preview_filename = NULL;
+
+  if (tags_found)
+    gst_tag_list_unref (tags_found);
+  tags_found = NULL;
+
+  g_free (video_filename);
+  video_filename = NULL;
+
+  g_free (image_filename);
+  image_filename = NULL;
+
+  GST_INFO ("done");
+}
+
+static gboolean
+validity_bus_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+  GMainLoop *loop = (GMainLoop *) data;
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err = NULL;
+      gchar *debug = NULL;
+
+      gst_message_parse_error (message, &err, &debug);
+
+      GST_ERROR ("Error: %s : %s", err->message, debug);
+      g_error_free (err);
+      g_free (debug);
+
+      fail_if (TRUE, "validating captured data failed");
+      g_main_loop_quit (loop);
+    }
+      break;
+    case GST_MESSAGE_EOS:
+      g_main_loop_quit (loop);
+      GST_DEBUG ("eos");
+      break;
+    case GST_MESSAGE_TAG:{
+      GstTagList *taglist = NULL;
+
+      gst_message_parse_tag (message, &taglist);
+      if (tags_found) {
+        gst_tag_list_insert (tags_found, taglist, GST_TAG_MERGE_REPLACE);
+        gst_tag_list_unref (taglist);
+      } else {
+        tags_found = taglist;
+      }
+      GST_DEBUG ("tags: %" GST_PTR_FORMAT, tags_found);
+    }
+      break;
+    default:
+      break;
+  }
+  return TRUE;
+}
+
+/* checks that tags in @tags_a are in @tags_b */
+static gboolean
+taglist_is_subset (GstTagList * tags_a, GstTagList * tags_b)
+{
+  gst_tag_list_foreach (tags_a, validate_taglist_foreach, tags_b);
+  return TRUE;
+}
+
+/* Validate captured files by playing them with playbin
+ * and checking that no errors occur. */
+#define WITH_AUDIO TRUE
+#define NO_AUDIO FALSE
+static gboolean
+check_file_validity (const gchar * filename, gint num, GstTagList * taglist,
+    gint width, gint height, gboolean has_audio)
+{
+  GstBus *bus;
+  GstPad *pad;
+  GstCaps *caps;
+  gint caps_width, caps_height;
+  GstState state;
+
+  GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+  GstElement *playbin = gst_element_factory_make ("playbin", NULL);
+  GstElement *fakevideo = gst_element_factory_make ("fakesink", NULL);
+  GstElement *fakeaudio = gst_element_factory_make ("fakesink", NULL);
+  gchar *uri = g_strconcat ("file://", make_const_file_name (filename, num),
+      NULL);
+
+  GST_DEBUG ("checking uri: %s", uri);
+  g_object_set (G_OBJECT (playbin), "uri", uri, "video-sink", fakevideo,
+      "audio-sink", fakeaudio, NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (playbin));
+  gst_bus_add_watch (bus, (GstBusFunc) validity_bus_cb, loop);
+
+  gst_element_set_state (playbin, GST_STATE_PAUSED);
+  gst_element_get_state (playbin, &state, NULL, GST_SECOND * 3);
+
+  if (width != 0 && height != 0) {
+    g_signal_emit_by_name (playbin, "get-video-pad", 0, &pad, NULL);
+    g_assert (pad != NULL);
+    caps = gst_pad_get_current_caps (pad);
+
+    g_assert (gst_structure_get_int (gst_caps_get_structure (caps, 0),
+            "width", &caps_width));
+    g_assert (gst_structure_get_int (gst_caps_get_structure (caps, 0),
+            "height", &caps_height));
+
+    g_assert (width == caps_width);
+    g_assert (height == caps_height);
+
+    gst_caps_unref (caps);
+    gst_object_unref (pad);
+  }
+  if (has_audio) {
+    g_signal_emit_by_name (playbin, "get-audio-pad", 0, &pad, NULL);
+    g_assert (pad != NULL);
+    gst_object_unref (pad);
+  }
+
+  gst_element_set_state (playbin, GST_STATE_PLAYING);
+  g_main_loop_run (loop);
+  gst_element_set_state (playbin, GST_STATE_NULL);
+
+  /* special handling for images (jpg) as jpegparse isn't plugged by
+   * default due to its current low rank */
+  if (taglist && strstr (filename, "image")) {
+    extract_jpeg_tags (filename, num);
+  }
+
+  if (taglist) {
+    fail_unless (tags_found != NULL);
+    fail_unless (taglist_is_subset (taglist, tags_found));
+  }
+
+  g_free (uri);
+  gst_bus_remove_watch (bus);
+  gst_object_unref (bus);
+  gst_object_unref (playbin);
+  g_main_loop_unref (loop);
+
+  return TRUE;
+}
+
+static void
+remove_file (const gchar * fn_template, guint num)
+{
+  const gchar *fn;
+
+  fn = make_const_file_name (fn_template, num);
+  GST_INFO ("removing %s", fn);
+  g_unlink (fn);
+}
+
+static GstPadProbeReturn
+filter_buffer_count (GstPad * pad, GstPadProbeInfo * info, gpointer data)
+{
+  gint *counter = data;
+
+  (*counter)++;
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstMessage *
+wait_for_element_message (GstElement * camera, const gchar * name,
+    GstClockTime timeout)
+{
+  GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (camera));
+  GstMessage *msg;
+
+  while (1) {
+    msg = gst_bus_timed_pop_filtered (bus, timeout, GST_MESSAGE_ERROR |
+        GST_MESSAGE_EOS | GST_MESSAGE_ELEMENT);
+
+    if (msg) {
+      if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ELEMENT) {
+        const GstStructure *st = gst_message_get_structure (msg);
+        if (gst_structure_has_name (st,
+                GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME)) {
+          GstSample *sample;
+          const GValue *value;
+
+          value = gst_structure_get_value (st, "sample");
+          fail_unless (value != NULL);
+          sample = gst_value_get_sample (value);
+
+          if (preview_sample)
+            gst_sample_unref (preview_sample);
+          preview_sample = gst_sample_ref (sample);
+          g_free (preview_filename);
+          preview_filename =
+              g_strdup (gst_structure_get_string (st, "location"));
+        }
+
+        if (gst_structure_has_name (st, name))
+          break;
+        else
+          gst_message_unref (msg);
+      } else {
+        gst_message_unref (msg);
+        msg = NULL;
+        break;
+      }
+    }
+  }
+
+  gst_object_unref (bus);
+  return msg;
+}
+
+static void
+wait_for_idle_state (void)
+{
+  gboolean idle = FALSE;
+
+  /* not the ideal way, but should be enough for testing */
+  while (idle == FALSE) {
+    g_object_get (camera, "idle", &idle, NULL);
+    if (idle)
+      break;
+
+    GST_LOG ("waiting for idle state..");
+    g_usleep (G_USEC_PER_SEC / 5);
+  }
+  fail_unless (idle);
+}
+
+static void
+run_single_image_capture_test (GstCaps * viewfinder_caps, GstCaps * image_caps)
+{
+  gboolean idle;
+  GstMessage *msg;
+  if (!camera)
+    return;
+
+  /* set still image mode */
+  g_object_set (camera, "mode", 1, "location", image_filename, NULL);
+
+  if (viewfinder_caps)
+    g_object_set (camera, "viewfinder-caps", viewfinder_caps, NULL);
+  if (image_caps)
+    g_object_set (camera, "image-capture-caps", image_caps, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (idle);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+
+  msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+  fail_unless (msg != NULL);
+  gst_message_unref (msg);
+
+  /* check that we got a preview image */
+  check_preview_image (camera, image_filename, 0);
+
+  wait_for_idle_state ();
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+  check_file_validity (image_filename, 0, NULL, 0, 0, NO_AUDIO);
+  remove_file (image_filename, 0);
+}
+
+GST_START_TEST (test_single_image_capture)
+{
+  run_single_image_capture_test (NULL, NULL);
+}
+
+GST_END_TEST;
+
+
+/* Verify that incompatible caps can be used in viewfinder and image capture
+ * at the same time */
+GST_START_TEST (test_single_image_capture_with_different_caps)
+{
+  GstCaps *vf_caps =
+      gst_caps_from_string ("video/x-raw, width=480, height=320");
+  GstCaps *img_caps =
+      gst_caps_from_string ("video/x-raw, width=800, height=600");
+  run_single_image_capture_test (vf_caps, img_caps);
+  gst_caps_unref (vf_caps);
+  gst_caps_unref (img_caps);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_multiple_image_captures)
+{
+  gboolean idle;
+  gint i;
+  gint widths[] = { 800, 640, 1280 };
+  gint heights[] = { 600, 480, 1024 };
+
+  if (!camera)
+    return;
+
+  /* set still image mode */
+  g_object_set (camera, "mode", 1, "location", image_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  fail_unless (camera != NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (idle);
+  GST_INFO ("starting capture");
+
+  for (i = 0; i < 3; i++) {
+    GstMessage *msg;
+    GstCaps *caps;
+
+    caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+        widths[i], "height", G_TYPE_INT, heights[i], NULL);
+
+    g_object_set (camera, "image-capture-caps", caps, NULL);
+    gst_caps_unref (caps);
+
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    if (msg)
+      gst_message_unref (msg);
+
+    check_preview_image (camera, image_filename, i);
+  }
+
+  wait_for_idle_state ();
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+  for (i = 0; i < 3; i++) {
+    check_file_validity (image_filename, i, NULL, widths[i], heights[i],
+        NO_AUDIO);
+    remove_file (image_filename, i);
+  }
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_video_recording)
+{
+  GstMessage *msg;
+  gboolean idle;
+  if (!camera)
+    return;
+
+  /* Set video recording mode */
+  g_object_set (camera, "mode", 2, "location", video_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (idle);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (!idle);
+
+  /* Record for one seconds  */
+  g_timeout_add_seconds (VIDEO_DURATION, (GSourceFunc) g_main_loop_quit,
+      main_loop);
+  g_main_loop_run (main_loop);
+
+  g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+  check_preview_image (camera, video_filename, 0);
+
+  msg = wait_for_element_message (camera, "video-done", GST_CLOCK_TIME_NONE);
+  fail_unless (msg != NULL);
+  gst_message_unref (msg);
+
+  wait_for_idle_state ();
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  check_file_validity (video_filename, 0, NULL, 0, 0, WITH_AUDIO);
+  remove_file (video_filename, 0);
+
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_multiple_video_recordings)
+{
+  gboolean idle;
+  gint i;
+  gint widths[] = { 800, 640, 1280 };
+  gint heights[] = { 600, 480, 1024 };
+  gint fr[] = { 20, 30, 5 };
+
+  if (!camera)
+    return;
+
+  /* Set video recording mode */
+  g_object_set (camera, "mode", 2, "location", video_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (idle);
+  for (i = 0; i < 3; i++) {
+    GstMessage *msg;
+    GstCaps *caps;
+
+    caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+        widths[i], "height", G_TYPE_INT, heights[i], "framerate",
+        GST_TYPE_FRACTION, fr[i], 1, NULL);
+
+    g_object_set (camera, "video-capture-caps", caps, NULL);
+
+    gst_caps_unref (caps);
+
+    GST_LOG ("starting #%d with caps %" GST_PTR_FORMAT, i, caps);
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    g_object_get (camera, "idle", &idle, NULL);
+    fail_unless (!idle);
+
+    g_timeout_add_seconds (VIDEO_DURATION, (GSourceFunc) g_main_loop_quit,
+        main_loop);
+    g_main_loop_run (main_loop);
+
+    GST_LOG ("stopping run %d", i);
+    g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+    msg = wait_for_element_message (camera, "video-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+
+    GST_LOG ("video done, checking preview image");
+    check_preview_image (camera, video_filename, i);
+
+    GST_LOG ("waiting for idle state");
+    wait_for_idle_state ();
+    GST_LOG ("finished run %d", i);
+  }
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  for (i = 0; i < 3; i++) {
+    check_file_validity (video_filename, i, NULL, widths[i], heights[i],
+        WITH_AUDIO);
+    remove_file (video_filename, i);
+  }
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_image_video_cycle)
+{
+  gint i;
+
+  if (!camera)
+    return;
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+
+  GST_INFO ("starting capture");
+  for (i = 0; i < 2; i++) {
+    GstMessage *msg;
+    const gchar *img_filename;
+    const gchar *vid_filename;
+
+    wait_for_idle_state ();
+
+    /* take a picture */
+    img_filename = make_const_file_name (image_filename, i);
+    g_object_set (camera, "mode", 1, NULL);
+    g_object_set (camera, "location", img_filename, NULL);
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+
+    check_preview_image (camera, img_filename, i);
+
+    /* now go to video */
+    vid_filename = make_const_file_name (video_filename, i);
+    g_object_set (camera, "mode", 2, NULL);
+    g_object_set (camera, "location", vid_filename, NULL);
+
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+    g_timeout_add_seconds (VIDEO_DURATION, (GSourceFunc) g_main_loop_quit,
+        main_loop);
+    g_main_loop_run (main_loop);
+    g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+    msg = wait_for_element_message (camera, "video-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+
+    check_preview_image (camera, vid_filename, i);
+  }
+
+  wait_for_idle_state ();
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  /* validate all the files */
+  for (i = 0; i < 2; i++) {
+    check_file_validity (image_filename, i, NULL, 0, 0, NO_AUDIO);
+    remove_file (image_filename, i);
+    check_file_validity (video_filename, i, NULL, 0, 0, WITH_AUDIO);
+    remove_file (video_filename, i);
+  }
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_image_capture_previews)
+{
+  gint i;
+  gint widths[] = { 800, 640, 1280 };
+  gint heights[] = { 600, 480, 1024 };
+
+  if (!camera)
+    return;
+
+  /* set still image mode */
+  g_object_set (camera, "mode", 1, "location", image_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  fail_unless (camera != NULL);
+  GST_INFO ("starting capture");
+
+  for (i = 0; i < 3; i++) {
+    GstMessage *msg;
+    GstCaps *caps;
+
+    caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+        widths[i], "height", G_TYPE_INT, heights[i], NULL);
+
+    g_object_set (camera, "preview-caps", caps, NULL);
+    gst_caps_replace (&preview_caps, caps);
+    gst_caps_unref (caps);
+
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+
+    check_preview_image (camera, image_filename, i);
+    remove_file (image_filename, i);
+
+    if (preview_sample)
+      gst_sample_unref (preview_sample);
+    preview_sample = NULL;
+    gst_caps_replace (&preview_caps, NULL);
+  }
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_image_capture_with_tags)
+{
+  gint i;
+  GstTagList *taglists[3];
+
+  if (!camera)
+    return;
+
+  taglists[0] = gst_tag_list_new (GST_TAG_COMMENT, "test1",
+      GST_TAG_GEO_LOCATION_LATITUDE, 36.6, GST_TAG_GEO_LOCATION_LONGITUDE,
+      -12.5,
+      GST_TAG_COPYRIGHT, "My copyright notice",
+      GST_TAG_DEVICE_MANUFACTURER, "MyFavoriteBrand",
+      GST_TAG_DEVICE_MODEL, "123v42.1",
+      GST_TAG_DESCRIPTION, "some description",
+      GST_TAG_APPLICATION_NAME, "camerabin test",
+      GST_TAG_GEO_LOCATION_ELEVATION, 300.85, NULL);
+  taglists[1] = gst_tag_list_new (GST_TAG_COMMENT, "test2",
+      GST_TAG_GEO_LOCATION_LATITUDE, 1.6, GST_TAG_GEO_LOCATION_LONGITUDE,
+      0.0,
+      GST_TAG_COPYRIGHT, "some cp",
+      GST_TAG_DEVICE_MANUFACTURER, "ABRAND",
+      GST_TAG_DEVICE_MODEL, "abcd",
+      GST_TAG_DESCRIPTION, "desc",
+      GST_TAG_APPLICATION_NAME, "another cam test",
+      GST_TAG_GEO_LOCATION_ELEVATION, 10.0, NULL);
+  taglists[2] = gst_tag_list_new (GST_TAG_COMMENT, "test3",
+      GST_TAG_GEO_LOCATION_LATITUDE, 1.3, GST_TAG_GEO_LOCATION_LONGITUDE,
+      -5.0,
+      GST_TAG_COPYRIGHT, "CC",
+      GST_TAG_DEVICE_MANUFACTURER, "Homemade",
+      GST_TAG_DEVICE_MODEL, "xpto",
+      GST_TAG_DESCRIPTION, "another  description",
+      GST_TAG_APPLICATION_NAME, "cam2 test",
+      GST_TAG_GEO_LOCATION_ELEVATION, 0.0, NULL);
+
+  /* set still image mode */
+  g_object_set (camera, "mode", 1, "location", image_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  fail_unless (camera != NULL);
+  GST_INFO ("starting capture");
+
+  for (i = 0; i < 3; i++) {
+    GstMessage *msg;
+    gst_tag_setter_merge_tags (GST_TAG_SETTER (camera), taglists[i],
+        GST_TAG_MERGE_REPLACE);
+
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+  }
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  for (i = 0; i < 3; i++) {
+    check_file_validity (image_filename, i, taglists[i], 0, 0, NO_AUDIO);
+    gst_tag_list_unref (taglists[i]);
+    remove_file (image_filename, i);
+  }
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_video_capture_with_tags)
+{
+  gint i;
+  GstTagList *taglists[3];
+
+  if (!camera)
+    return;
+
+  taglists[0] = gst_tag_list_new (GST_TAG_COMMENT, "test1", NULL);
+  taglists[1] = gst_tag_list_new (GST_TAG_COMMENT, "test2", NULL);
+  taglists[2] = gst_tag_list_new (GST_TAG_COMMENT, "test3", NULL);
+
+  /* set video mode */
+  g_object_set (camera, "mode", 2, "location", video_filename, NULL);
+
+  /* set a profile that has xmp support for more tags being saved */
+  {
+    GstEncodingContainerProfile *profile;
+    GstCaps *caps;
+
+    caps =
+        gst_caps_new_simple ("video/quicktime", "variant", G_TYPE_STRING,
+        "apple", NULL);
+    profile = gst_encoding_container_profile_new ("qt", "jpeg+qt", caps, NULL);
+    gst_caps_unref (caps);
+
+    caps = gst_caps_new_simple ("image/jpeg", NULL, NULL);
+    if (!gst_encoding_container_profile_add_profile (profile,
+            (GstEncodingProfile *) gst_encoding_video_profile_new (caps,
+                NULL, NULL, 1))) {
+      GST_WARNING_OBJECT (camera, "Failed to create encoding profiles");
+    }
+    gst_caps_unref (caps);
+
+    g_object_set (camera, "video-profile", profile, NULL);
+    gst_encoding_profile_unref (profile);
+  }
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  fail_unless (camera != NULL);
+  GST_INFO ("starting capture");
+
+  for (i = 0; i < 3; i++) {
+    GstMessage *msg;
+
+    gst_tag_setter_merge_tags (GST_TAG_SETTER (camera), taglists[i],
+        GST_TAG_MERGE_REPLACE);
+
+    g_signal_emit_by_name (camera, "start-capture", NULL);
+
+    g_timeout_add_seconds (3, (GSourceFunc) g_main_loop_quit, main_loop);
+    g_main_loop_run (main_loop);
+
+    g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+    msg = wait_for_element_message (camera, "video-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+    gst_message_unref (msg);
+  }
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  for (i = 0; i < 3; i++) {
+    check_file_validity (video_filename, i, taglists[i], 0, 0, NO_AUDIO);
+    gst_tag_list_unref (taglists[i]);
+    remove_file (video_filename, i);
+  }
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_supported_caps)
+{
+  GstCaps *padcaps = NULL;
+  GstCaps *expectedcaps;
+  GstElement *src;
+
+  if (!camera)
+    return;
+
+  src = g_object_new (GST_TYPE_TEST_CAMERA_SRC, NULL);
+  g_object_set (camera, "camera-source", src, NULL);
+  gst_object_unref (src);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  g_assert (camera != NULL);
+
+  expectedcaps = gst_caps_from_string (VIDEO_PAD_SUPPORTED_CAPS);
+  g_object_get (G_OBJECT (camera), "video-capture-supported-caps", &padcaps,
+      NULL);
+  g_assert (expectedcaps != NULL);
+  g_assert (padcaps != NULL);
+  g_assert (gst_caps_is_equal (padcaps, expectedcaps));
+  gst_caps_unref (expectedcaps);
+  gst_caps_unref (padcaps);
+
+  expectedcaps = gst_caps_from_string (IMAGE_PAD_SUPPORTED_CAPS);
+  g_object_get (G_OBJECT (camera), "image-capture-supported-caps", &padcaps,
+      NULL);
+  g_assert (expectedcaps != NULL);
+  g_assert (padcaps != NULL);
+  g_assert (gst_caps_is_equal (padcaps, expectedcaps));
+  gst_caps_unref (expectedcaps);
+  gst_caps_unref (padcaps);
+
+  gst_element_set_state (camera, GST_STATE_NULL);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_idle_property)
+{
+  GstMessage *msg;
+  gboolean idle;
+  if (!camera)
+    return;
+
+  /* Set video recording mode */
+  g_object_set (camera, "mode", 2, "location", video_filename, NULL);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (idle);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (!idle);
+
+  /* emit a second start-capture that should be ignored */
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+  g_object_get (camera, "idle", &idle, NULL);
+  fail_unless (!idle);
+
+  /* Record for one seconds  */
+  g_timeout_add_seconds (VIDEO_DURATION, (GSourceFunc) g_main_loop_quit,
+      main_loop);
+  g_main_loop_run (main_loop);
+
+  g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+  msg = wait_for_element_message (camera, "video-done", GST_CLOCK_TIME_NONE);
+  fail_unless (msg != NULL);
+  gst_message_unref (msg);
+
+  check_preview_image (camera, video_filename, 0);
+
+  wait_for_idle_state ();
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  check_file_validity (video_filename, 0, NULL, 0, 0, WITH_AUDIO);
+  remove_file (video_filename, 0);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_image_custom_filter)
+{
+  GstElement *vf_filter;
+  GstElement *image_filter;
+  GstElement *preview_filter;
+  GstPad *pad;
+  gint vf_probe_counter = 0;
+  gint image_probe_counter = 0;
+  gint preview_probe_counter = 0;
+
+  if (!camera)
+    return;
+
+  vf_filter = gst_element_factory_make ("identity", "vf-filter");
+  image_filter = gst_element_factory_make ("identity", "img-filter");
+  preview_filter = gst_element_factory_make ("identity", "preview-filter");
+
+  pad = gst_element_get_static_pad (vf_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &vf_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (image_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &image_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (preview_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &preview_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  /* set still image mode and filters */
+  g_object_set (camera, "mode", 1,
+      "location", image_filename,
+      "viewfinder-filter", vf_filter, "image-filter", image_filter,
+      "preview-filter", preview_filter, NULL);
+
+  gst_object_unref (vf_filter);
+  gst_object_unref (preview_filter);
+  gst_object_unref (image_filter);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+
+  g_timeout_add_seconds (3, (GSourceFunc) g_main_loop_quit, main_loop);
+  g_main_loop_run (main_loop);
+
+  /* check that we got a preview image */
+  check_preview_image (camera, image_filename, 0);
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+  check_file_validity (image_filename, 0, NULL, 0, 0, NO_AUDIO);
+  remove_file (image_filename, 0);
+
+  fail_unless (vf_probe_counter > 0);
+  fail_unless (image_probe_counter == 1);
+  fail_unless (preview_probe_counter == 1);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_video_custom_filter)
+{
+  GstElement *vf_filter;
+  GstElement *video_filter;
+  GstElement *preview_filter;
+  GstElement *audio_filter;
+  GstPad *pad;
+  gint vf_probe_counter = 0;
+  gint video_probe_counter = 0;
+  gint preview_probe_counter = 0;
+  gint audio_probe_counter = 0;
+
+  if (!camera)
+    return;
+
+  vf_filter = gst_element_factory_make ("identity", "vf-filter");
+  video_filter = gst_element_factory_make ("identity", "video-filter");
+  preview_filter = gst_element_factory_make ("identity", "preview-filter");
+  audio_filter = gst_element_factory_make ("identity", "audio-filter");
+
+  pad = gst_element_get_static_pad (vf_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &vf_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (video_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &video_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (audio_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &audio_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  pad = gst_element_get_static_pad (preview_filter, "src");
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, filter_buffer_count,
+      &preview_probe_counter, NULL);
+  gst_object_unref (pad);
+
+  /* set still image mode and filters */
+  g_object_set (camera, "mode", 2,
+      "location", video_filename,
+      "viewfinder-filter", vf_filter, "video-filter", video_filter,
+      "preview-filter", preview_filter, "audio-filter", audio_filter, NULL);
+
+  gst_object_unref (vf_filter);
+  gst_object_unref (preview_filter);
+  gst_object_unref (video_filter);
+  gst_object_unref (audio_filter);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  GST_INFO ("starting capture");
+  fail_unless (camera != NULL);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+
+  g_timeout_add_seconds (VIDEO_DURATION, (GSourceFunc) g_main_loop_quit,
+      main_loop);
+  g_main_loop_run (main_loop);
+  g_signal_emit_by_name (camera, "stop-capture", NULL);
+
+  /* check that we got a preview image */
+  check_preview_image (camera, video_filename, 0);
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+  check_file_validity (video_filename, 0, NULL, 0, 0, WITH_AUDIO);
+  remove_file (video_filename, 0);
+
+  fail_unless (vf_probe_counter > 0);
+  fail_unless (video_probe_counter > 0);
+  fail_unless (audio_probe_counter > 0);
+  fail_unless (preview_probe_counter == 1);
+}
+
+GST_END_TEST;
+
+#define LOCATION_SWITCHING_FILENAMES_COUNT 5
+
+static gboolean
+image_location_switch_do_capture (gpointer data)
+{
+  gchar **filenames = data;
+  if (capture_count >= LOCATION_SWITCHING_FILENAMES_COUNT) {
+    g_main_loop_quit (main_loop);
+  }
+
+  g_object_set (camera, "location", filenames[capture_count], NULL);
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+  capture_count++;
+  return FALSE;
+}
+
+static void
+image_location_switch_readyforcapture (GObject * obj, GParamSpec * pspec,
+    gpointer user_data)
+{
+  gboolean ready;
+
+  g_object_get (obj, "ready-for-capture", &ready, NULL);
+  if (ready) {
+    g_idle_add (image_location_switch_do_capture, user_data);
+  }
+};
+
+/*
+ * Tests that setting the location and then doing an image
+ * capture will set this capture resulting filename to the
+ * correct location.
+ *
+ * There was a bug in which setting the location, issuing a capture 
+ * and then setting a new location would cause this capture to have
+ * the location set after this capture. This test should prevent it
+ * from happening again.
+ */
+GST_START_TEST (test_image_location_switching)
+{
+  gchar *filenames[LOCATION_SWITCHING_FILENAMES_COUNT + 1];
+  gint i;
+  glong notify_id;
+  GstCaps *caps;
+  GstElement *src;
+  GstMessage *msg;
+
+  if (!camera)
+    return;
+
+  g_object_get (camera, "camera-source", &src, NULL);
+
+  for (i = 0; i < LOCATION_SWITCHING_FILENAMES_COUNT; i++) {
+    filenames[i] = make_test_file_name ("image-switching-filename-test", i);
+  }
+  filenames[LOCATION_SWITCHING_FILENAMES_COUNT] = NULL;
+
+  /* set still image mode */
+  g_object_set (camera, "mode", 1, NULL);
+  caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+      800, "height", G_TYPE_INT, 600, NULL);
+  g_object_set (camera, "image-capture-caps", caps, NULL);
+  gst_caps_unref (caps);
+
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
+    GST_WARNING ("setting camerabin to PLAYING failed");
+    gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+    gst_object_unref (camera);
+    camera = NULL;
+  }
+  fail_unless (camera != NULL);
+  GST_INFO ("starting capture");
+
+  notify_id = g_signal_connect (G_OBJECT (src),
+      "notify::ready-for-capture",
+      G_CALLBACK (image_location_switch_readyforcapture), filenames);
+
+  g_idle_add (image_location_switch_do_capture, filenames);
+  g_main_loop_run (main_loop);
+
+  while (1) {
+    const gchar *filename;
+
+    msg = wait_for_element_message (camera, "image-done", GST_CLOCK_TIME_NONE);
+    fail_unless (msg != NULL);
+
+    filename =
+        gst_structure_get_string (gst_message_get_structure (msg), "filename");
+    if (strcmp (filename,
+            filenames[LOCATION_SWITCHING_FILENAMES_COUNT - 1]) == 0) {
+      gst_message_unref (msg);
+      break;
+    }
+    gst_message_unref (msg);
+  }
+
+  gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
+
+  for (i = 0; i < LOCATION_SWITCHING_FILENAMES_COUNT; i++) {
+    GST_INFO ("Checking for file: %s", filenames[i]);
+    fail_unless (g_file_test (filenames[i], G_FILE_TEST_IS_REGULAR));
+  }
+
+  for (i = 0; i < LOCATION_SWITCHING_FILENAMES_COUNT; i++) {
+    g_unlink (filenames[i]);
+    g_free (filenames[i]);
+  }
+  g_signal_handler_disconnect (src, notify_id);
+  gst_object_unref (src);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_photography_iface_image_capture)
+{
+  run_single_image_capture_test (NULL, NULL);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_photography_iface_image_capture_with_caps)
+{
+  GstCaps *caps = gst_caps_from_string ("video/x-raw, width=800, height=600");
+
+  run_single_image_capture_test (NULL, caps);
+  gst_caps_unref (caps);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_photography_iface_image_capture_with_caps_and_restriction)
+{
+  GstCaps *caps = gst_caps_from_string ("video/x-raw, width=800, height=600");
+
+  /* the source will actually provide an image with 800x800 resolution */
+  GST_TEST_VIDEO_SRC (testsrc)->enable_resolution_restriction = TRUE;
+
+  run_single_image_capture_test (NULL, caps);
+  gst_caps_unref (caps);
+}
+
+GST_END_TEST;
+
+
+typedef struct _TestCaseDef
+{
+  const gchar *name;
+  gpointer setup_func;
+} TestCaseDef;
+
+TestCaseDef tests[] = {
+  {"wrappercamerabinsrc", setup_wrappercamerabinsrc_videotestsrc}
+};
+
+static Suite *
+camerabin_suite (void)
+{
+  GstRegistry *reg = gst_registry_get ();
+  Suite *s = suite_create ("camerabin");
+  gint i;
+  TCase *tc_generic = tcase_create ("generic");
+  TCase *tc_phography_iface = tcase_create ("photography-iface");
+
+  if (!gst_registry_check_feature_version (reg, "jpegenc", 1, 0, 0)
+      || !gst_registry_check_feature_version (reg, "theoraenc", 1, 0, 0)
+      || !gst_registry_check_feature_version (reg, "vorbisenc", 1, 0, 0)
+      || !gst_registry_check_feature_version (reg, "oggmux", 1, 0, 0)) {
+    GST_WARNING ("Skipping camerabin tests because some required element is "
+        " missing (jpegenc, theoraenc, vorbisenc, oggmux)");
+    goto end;
+  }
+
+  suite_add_tcase (s, tc_generic);
+  tcase_add_checked_fixture (tc_generic, setup_wrappercamerabinsrc_videotestsrc,
+      teardown);
+  tcase_add_test (tc_generic, test_supported_caps);
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++) {
+    TCase *tc_basic = tcase_create (tests[i].name);
+    suite_add_tcase (s, tc_basic);
+
+    /* Increase timeout due to video recording */
+    tcase_set_timeout (tc_basic, 60);
+    tcase_add_checked_fixture (tc_basic, tests[i].setup_func, teardown);
+
+    tcase_add_test (tc_basic, test_single_image_capture);
+    tcase_add_test (tc_basic, test_single_image_capture_with_different_caps);
+    tcase_add_test (tc_basic, test_single_video_recording);
+    tcase_add_test (tc_basic, test_image_video_cycle);
+    tcase_add_test (tc_basic, test_multiple_image_captures);
+    tcase_add_test (tc_basic, test_multiple_video_recordings);
+
+    tcase_add_test (tc_basic, test_image_capture_previews);
+    tcase_add_test (tc_basic, test_image_capture_with_tags);
+
+    tcase_add_test (tc_basic, test_video_capture_with_tags);
+
+    tcase_add_test (tc_basic, test_idle_property);
+
+    tcase_add_test (tc_basic, test_image_custom_filter);
+    tcase_add_test (tc_basic, test_video_custom_filter);
+
+    tcase_add_test (tc_basic, test_image_location_switching);
+  }
+
+  /* This is the GstPhotography interface test case. It was added in 0.10
+   * to make it easy for integrating with hardware and providing lower
+   * delays from action to capture.
+   * There is also has a feature in wrappercamerabinsrc that allows
+   * captures with the interface to have a different(higher) resolution than
+   * requested and wrappercamerabinsrc will crop to the requested one.
+   * This doesn't make sense and seems to be very hardware specific but we
+   * can't simply remove it at this point.
+   *
+   * FIXME 2.0: revisit GstPhotography interface and its interaction with
+   * camerabin */
+  suite_add_tcase (s, tc_phography_iface);
+  tcase_add_checked_fixture (tc_phography_iface, setup_test_camerasrc,
+      teardown);
+  tcase_add_test (tc_phography_iface, test_photography_iface_image_capture);
+  tcase_add_test (tc_phography_iface,
+      test_photography_iface_image_capture_with_caps);
+  tcase_add_test (tc_phography_iface,
+      test_photography_iface_image_capture_with_caps_and_restriction);
+
+end:
+  return s;
+}
+
+GST_CHECK_MAIN (camerabin);
Index: b/tests/check/meson.build
===================================================================
--- a/tests/check/meson.build
+++ b/tests/check/meson.build
@@ -30,6 +30,7 @@
   [ 'elements/alpha' ],
   [ 'elements/avimux', false, [gstriff_dep] ],
   [ 'elements/avisubtitle', false, [gstriff_dep] ],
+  [ 'elements/camerabin' ],
   [ 'elements/capssetter' ],
   [ 'elements/aacparse', false, [libparser_dep] ],
   [ 'elements/ac3parse', false, [libparser_dep] ],
@@ -178,7 +179,7 @@
  pulsesink pulsesrc pulsemixer v4l2src'''
 
 # FIXME: check, also + PTHREAD_CFLAGS
-test_deps = [gst_dep, gstbase_dep, gstnet_dep, gstcheck_dep, gstaudio_dep,
+test_deps = [gst_dep, gstbase_dep, gstbasecamerabin_dep, gstphotography_dep, gstnet_dep, gstcheck_dep, gstaudio_dep,
   gstvideo_dep, gstpbutils_dep, gstrtp_dep, gstrtsp_dep, gsttag_dep,
   gstapp_dep, gio_dep, valgrind_dep] + glib_deps
 
Index: b/tests/examples/camerabin2/gst-camera2.c
===================================================================
--- /dev/null
+++ b/tests/examples/camerabin2/gst-camera2.c
@@ -0,0 +1,338 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@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.
+ */
+/*
+ * This is a demo application to test the camerabin element.
+ * If you have question don't hesitate in contact me edgard.lima@gmail.com
+ */
+
+/*
+ * Includes
+ */
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "gst-camera2.h"
+
+#include <string.h>
+
+#include <gst/pbutils/encoding-profile.h>
+#include <gst/gst.h>
+#include <gst/video/videooverlay.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+
+#define UI_FILE CAMERA_APPS_UIDIR G_DIR_SEPARATOR_S "gst-camera2.ui"
+
+static GstElement *camera;
+static GtkBuilder *builder;
+static GtkWidget *ui_main_window;
+
+typedef struct
+{
+  const gchar *name;
+  GstEncodingProfile *(*create_profile) ();
+} GstCameraVideoFormat;
+
+static GstEncodingProfile *
+create_ogg_profile (void)
+{
+  GstEncodingContainerProfile *container;
+  GstCaps *caps = NULL;
+
+  caps = gst_caps_new_empty_simple ("application/ogg");
+  container = gst_encoding_container_profile_new ("ogg", NULL, caps, NULL);
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_empty_simple ("video/x-theora");
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_video_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_empty_simple ("audio/x-vorbis");
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_audio_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  return (GstEncodingProfile *) container;
+}
+
+static GstEncodingProfile *
+create_webm_profile (void)
+{
+  GstEncodingContainerProfile *container;
+  GstCaps *caps = NULL;
+
+  caps = gst_caps_new_empty_simple ("video/webm");
+  container = gst_encoding_container_profile_new ("webm", NULL, caps, NULL);
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_empty_simple ("video/x-vp8");
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_video_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_empty_simple ("audio/x-vorbis");
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_audio_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  return (GstEncodingProfile *) container;
+}
+
+static GstEncodingProfile *
+create_mp4_profile (void)
+{
+  GstEncodingContainerProfile *container;
+  GstCaps *caps = NULL;
+
+  caps =
+      gst_caps_new_simple ("video/quicktime", "variant", G_TYPE_STRING, "iso",
+      NULL);
+  container = gst_encoding_container_profile_new ("mp4", NULL, caps, NULL);
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_empty_simple ("video/x-h264");
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_video_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  caps = gst_caps_new_simple ("audio/mpeg", "version", G_TYPE_INT, 4, NULL);
+  gst_encoding_container_profile_add_profile (container, (GstEncodingProfile *)
+      gst_encoding_audio_profile_new (caps, NULL, NULL, 1));
+  gst_caps_unref (caps);
+
+  return (GstEncodingProfile *) container;
+}
+
+GstCameraVideoFormat formats[] = {
+  {"ogg (theora/vorbis)", create_ogg_profile}
+  ,
+  {"webm (vp8/vorbis)", create_webm_profile}
+  ,
+  {"mp4 (h264+aac)", create_mp4_profile}
+  ,
+  {NULL, NULL}
+};
+
+void
+on_mainWindow_delete_event (GtkWidget * widget, GdkEvent * event, gpointer data)
+{
+  gtk_main_quit ();
+}
+
+void
+on_captureButton_clicked (GtkButton * button, gpointer user_data)
+{
+  g_signal_emit_by_name (camera, "start-capture", NULL);
+}
+
+void
+on_stopCaptureButton_clicked (GtkButton * button, gpointer user_data)
+{
+  g_signal_emit_by_name (camera, "stop-capture", NULL);
+}
+
+void
+on_imageRButton_toggled (GtkToggleButton * button, gpointer user_data)
+{
+  if (gtk_toggle_button_get_active (button)) {
+    g_object_set (camera, "mode", 1, NULL);     /* Image mode */
+  }
+}
+
+void
+on_videoRButton_toggled (GtkToggleButton * button, gpointer user_data)
+{
+  if (gtk_toggle_button_get_active (button)) {
+    g_object_set (camera, "mode", 2, NULL);     /* Video mode */
+  }
+}
+
+void
+on_viewfinderArea_realize (GtkWidget * widget, gpointer data)
+{
+  gdk_window_ensure_native (gtk_widget_get_window (widget));
+}
+
+void
+on_formatComboBox_changed (GtkWidget * widget, gpointer data)
+{
+  GstEncodingProfile *profile = NULL;
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
+
+  if (formats[index].create_profile) {
+    profile = formats[index].create_profile ();
+  }
+
+  g_return_if_fail (profile != NULL);
+  gst_element_set_state (camera, GST_STATE_NULL);
+  g_object_set (camera, "video-profile", profile, NULL);
+  gst_encoding_profile_unref (profile);
+
+  if (GST_STATE_CHANGE_FAILURE == gst_element_set_state (camera,
+          GST_STATE_PLAYING)) {
+    GtkWidget *dialog =
+        gtk_message_dialog_new (GTK_WINDOW (ui_main_window), GTK_DIALOG_MODAL,
+        GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+        "Could not initialize camerabin with the "
+        "selected format. Your system might not have the required plugins installed.\n"
+        "Please select another format.");
+
+    gtk_dialog_run (GTK_DIALOG (dialog));
+
+    gtk_widget_destroy (dialog);
+  }
+}
+
+void
+on_zoomScale_value_changed (GtkWidget * widget, gpointer data)
+{
+  g_object_set (camera, "zoom",
+      (gfloat) gtk_range_get_value (GTK_RANGE (widget)), NULL);
+}
+
+static GstBusSyncReply
+bus_sync_callback (GstBus * bus, GstMessage * message, gpointer data)
+{
+  GtkWidget *ui_drawing;
+
+  if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
+    return GST_BUS_PASS;
+
+  if (!gst_message_has_name (message, "prepare-window-handle"))
+    return GST_BUS_PASS;
+
+  /* FIXME: make sure to get XID in main thread */
+  ui_drawing = GTK_WIDGET (gtk_builder_get_object (builder, "viewfinderArea"));
+  gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (message->src),
+      GDK_WINDOW_XID (gtk_widget_get_window (ui_drawing)));
+
+  gst_message_unref (message);
+  return GST_BUS_DROP;
+}
+
+
+static gboolean
+bus_callback (GstBus * bus, GstMessage * message, gpointer data)
+{
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_WARNING:{
+      GError *err;
+      gchar *debug;
+
+      gst_message_parse_warning (message, &err, &debug);
+      g_print ("Warning: %s\n", err->message);
+      g_error_free (err);
+      g_free (debug);
+      break;
+    }
+    case GST_MESSAGE_ERROR:{
+      GError *err = NULL;
+      gchar *debug = NULL;
+
+      gst_message_parse_error (message, &err, &debug);
+      g_print ("Error: %s : %s\n", err->message, debug);
+      g_error_free (err);
+      g_free (debug);
+
+      gtk_main_quit ();
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      /* end-of-stream */
+      g_print ("Eos\n");
+      gtk_main_quit ();
+      break;
+    case GST_MESSAGE_ELEMENT:
+    {
+      //handle_element_message (message);
+      break;
+    }
+    default:
+      /* unhandled message */
+      break;
+  }
+  return TRUE;
+}
+
+static gboolean
+init_gtkwidgets_data (void)
+{
+  gint i;
+  GtkComboBoxText *combobox =
+      GTK_COMBO_BOX_TEXT (gtk_builder_get_object (builder, "formatComboBox"));
+
+  /* init formats combobox */
+  i = 0;
+  while (formats[i].name) {
+    gtk_combo_box_text_append_text (combobox, formats[i].name);
+    i++;
+  }
+
+  /* default to the first one -> ogg */
+  gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), 0);
+  return TRUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+  GError *error = NULL;
+  GstBus *bus;
+
+  /* FIXME: add support for Gdk Wayland backend, code currently assumes X11 */
+  if (g_getenv ("GDK_BACKEND") == NULL)
+    g_setenv ("GDK_BACKEND", "x11", TRUE);
+
+  gst_init (&argc, &argv);
+  gtk_init (&argc, &argv);
+
+  builder = gtk_builder_new ();
+  if (!gtk_builder_add_from_file (builder, UI_FILE, &error)) {
+    g_warning ("Error: %s", error->message);
+    g_error_free (error);
+    return 1;
+  }
+
+  camera = gst_element_factory_make ("camerabin", "camera");
+  bus = gst_pipeline_get_bus (GST_PIPELINE (camera));
+  gst_bus_add_watch (bus, bus_callback, NULL);
+  gst_bus_set_sync_handler (bus, bus_sync_callback, NULL, NULL);
+  gst_object_unref (bus);
+
+  if (!init_gtkwidgets_data ()) {
+    goto error;
+  }
+
+  ui_main_window = GTK_WIDGET (gtk_builder_get_object (builder, "mainWindow"));
+  gtk_builder_connect_signals (builder, NULL);
+  gtk_widget_show_all (ui_main_window);
+
+  gst_element_set_state (camera, GST_STATE_PLAYING);
+
+  gtk_main ();
+
+error:
+  gst_element_set_state (camera, GST_STATE_NULL);
+  gst_object_unref (camera);
+  return 0;
+}
Index: b/tests/examples/camerabin2/gst-camera2.h
===================================================================
--- /dev/null
+++ b/tests/examples/camerabin2/gst-camera2.h
@@ -0,0 +1,54 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * 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.
+ */
+/*
+ * This is a demo application to test the camerabin element.
+ * If you have question don't hesitate in contact me edgard.lima@gmail.com
+ */
+
+#ifndef __GST_CAMERA_BIN_H__
+#define __GST_CAMERA_BIN_H__
+
+#include <gtk/gtk.h>
+
+void
+on_mainWindow_delete_event (GtkWidget * widget, GdkEvent * event, gpointer data);
+
+void
+on_captureButton_clicked (GtkButton * button, gpointer user_data);
+
+void
+on_stopCaptureButton_clicked (GtkButton * button, gpointer user_data);
+
+void
+on_imageRButton_toggled (GtkToggleButton * button, gpointer user_data);
+
+void
+on_videoRButton_toggled (GtkToggleButton * button, gpointer user_data);
+
+void
+on_viewfinderArea_realize (GtkWidget * widget, gpointer data);
+
+void
+on_formatComboBox_changed (GtkWidget * widget, gpointer data);
+
+void
+on_zoomScale_value_changed (GtkWidget * widget, gpointer data);
+
+#endif /* __GST_CAMERA_BIN_H__ */
Index: b/tests/examples/camerabin2/gst-camera2.ui
===================================================================
--- /dev/null
+++ b/tests/examples/camerabin2/gst-camera2.ui
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkAdjustment" id="zoomadjustment">
+    <property name="lower">1</property>
+    <property name="upper">10</property>
+    <property name="value">1</property>
+    <property name="step_increment">0.10000000000000001</property>
+    <property name="page_increment">1</property>
+  </object>
+  <object class="GtkWindow" id="mainWindow">
+    <property name="can_focus">False</property>
+    <property name="default_width">800</property>
+    <property name="default_height">600</property>
+    <signal name="delete-event" handler="on_mainWindow_delete_event" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkRadioButton" id="imageRButton">
+                <property name="label" translatable="yes">Image</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="xalign">0</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+                <signal name="toggled" handler="on_imageRButton_toggled" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkRadioButton" id="videoRButton">
+                <property name="label" translatable="yes">Video</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="xalign">0</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+                <property name="group">imageRButton</property>
+                <signal name="toggled" handler="on_videoRButton_toggled" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkDrawingArea" id="viewfinderArea">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <signal name="realize" handler="on_viewfinderArea_realize" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box4">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="label4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Actions</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="captureButton">
+                    <property name="label" translatable="yes">Capture</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_captureButton_clicked" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="stopCaptureButton">
+                    <property name="label" translatable="yes">Stop Capture</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_stopCaptureButton_clicked" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Video format</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">5</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkComboBoxText" id="formatComboBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <signal name="changed" handler="on_formatComboBox_changed" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Zoom</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">5</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScale" id="zoomScale">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="adjustment">zoomadjustment</property>
+                    <property name="fill_level">10</property>
+                    <property name="round_digits">1</property>
+                    <property name="value_pos">right</property>
+                    <signal name="value-changed" handler="on_zoomScale_value_changed" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">6</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
Index: b/tests/examples/camerabin2/gst-camerabin2-test.c
===================================================================
--- /dev/null
+++ b/tests/examples/camerabin2/gst-camerabin2-test.c
@@ -0,0 +1,1324 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Nokia Corporation <multimedia@maemo.org>
+ * Copyright (C) 2011 Thiago Santos <thiago.sousa.santos@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.
+ */
+
+ /*
+    TODO review
+    Examples:
+    ./gst-camerabin2-test --image-width=2048 --image-height=1536
+    ./gst-camerabin2-test --mode=2 --capture-time=10 --image-width=848 --image-height=480 --view-framerate-num=2825 \
+    --view-framerate-den=100
+
+    gst-camerabin2-test --help
+    Usage:
+    gst-camerabin2-test [OPTION...]
+
+    camerabin command line test application.
+
+    Help Options:
+    -h, --help                        Show help options
+    --help-all                        Show all help options
+    --help-gst                        Show GStreamer Options
+
+    Application Options:
+    --ev-compensation                 EV compensation (-2.5..2.5, default = 0)
+    --aperture                        Aperture (size of lens opening, default = 0 (auto))
+    --flash-mode                      Flash mode (default = 0 (auto))
+    --scene-mode                      Scene mode (default = 6 (auto))
+    --exposure                        Exposure (default = 0 (auto))
+    --iso-speed                       ISO speed (default = 0 (auto))
+    --white-balance-mode              White balance mode (default = 0 (auto))
+    --colour-tone-mode                Colour tone mode (default = 0 (auto))
+    --directory                       Directory for capture file(s) (default is current directory)
+    --mode                            Capture mode (default = 0 (image), 1 = video)
+    --capture-time                    Time to capture video in seconds (default = 10)
+    --capture-total                   Total number of captures to be done (default = 1)
+    --zoom                            Zoom (100 = 1x (default), 200 = 2x etc.)
+    --wrapper-source                  Camera source wrapper used for setting the video source
+    --video-source                    Video source used in still capture and video recording
+    --video-device                    Video device to be set on the video source (e.g. /dev/video0)
+    --audio-source                    Audio source used in video recording
+    --image-pp                        List of image post-processing elements separated with comma
+    --viewfinder-sink                 Viewfinder sink (default = fakesink)
+    --image-width                     Width for capture (only used if the caps
+    arguments aren't set)
+    --image-height                    Height for capture (only used if the caps
+    arguments aren't set)
+    --view-framerate-num              Framerate numerator for viewfinder
+    --view-framerate-den              Framerate denominator for viewfinder
+    --preview-caps                    Preview caps (e.g. video/x-raw-rgb,width=320,height=240)
+    --viewfinder-filter               Filter to process all frames going to viewfinder sink
+    --x-width                         X window width (default = 320)
+    --x-height                        X window height (default = 240)
+    --no-xwindow                      Do not create XWindow
+    --encoding-target                 Video encoding target name
+    --encoding-profile                Video encoding profile name
+    --encoding-profile-filename       Video encoding profile filename
+    --image-capture-caps              Image capture caps (e.g. video/x-raw-rgb,width=640,height=480)
+    --viewfinder-caps                 Viewfinder caps (e.g. video/x-raw-rgb,width=640,height=480)
+    --video-capture-caps              Video capture caps (e.g. video/x-raw-rgb,width=640,height=480)
+    --performance-measure             Collect timing information about the
+    captures and provides performance statistics at the end
+    --performance-targets             A list of doubles that are the performance target
+    times for each of the measured timestamps. The order is
+    startup time, change mode time, shot to save, shot to snapshot,
+    shot to shot, preview to precapture, shot to buffer.
+    e.g. 3.5,1.0,5.0,2.5,5.0,1.5,1.0
+    * Startup time -> time it takes for camerabin to reach playing
+    * Change mode time -> time it takes for camerabin to change to the selected
+    mode in playing
+    * Shot to save -> time it takes from start-capture to having the image saved
+    to disk
+    * Shot to snapshot -> time it takes from start-capture to getting a snapshot
+    * Shot to shot -> time from one start-capture to the next one
+    * Preview to precapture -> time it takes from getting the snapshot to the
+    next buffer that reaches the viewfinder
+    * Shot to buffer -> time it takes from start-capture to the moment a buffer
+    is pushed out of the camera source
+
+  */
+
+/*
+ * Includes
+ */
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#define GST_USE_UNSTABLE_API 1
+
+#include <gst/gst.h>
+#include <gst/video/videooverlay.h>
+#include <gst/interfaces/photography.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gst/pbutils/encoding-profile.h>
+#include <gst/pbutils/encoding-target.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+/*
+ * debug logging
+ */
+GST_DEBUG_CATEGORY_STATIC (camerabin_test);
+#define GST_CAT_DEFAULT camerabin_test
+
+#define TIME_DIFF(a,b) ((((gint64)(a)) - ((gint64)(b))) / (gdouble) GST_SECOND)
+
+#define TIME_FORMAT "02d.%09u"
+#define TIMEDIFF_FORMAT "0.6lf"
+
+#define TIME_ARGS(t) \
+        (GST_CLOCK_TIME_IS_VALID (t) && (t) < 99 * GST_SECOND) ? \
+        (gint) ((((GstClockTime)(t)) / GST_SECOND) % 60) : 99, \
+        (GST_CLOCK_TIME_IS_VALID (t) && ((t) < 99 * GST_SECOND)) ? \
+        (guint) (((GstClockTime)(t)) % GST_SECOND) : 999999999
+
+#define TIMEDIFF_ARGS(t) (t)
+
+typedef struct _CaptureTiming
+{
+  GstClockTime start_capture;
+  GstClockTime got_preview;
+  GstClockTime capture_done;
+  GstClockTime precapture;
+  GstClockTime camera_capture;
+} CaptureTiming;
+
+typedef struct _CaptureTimingStats
+{
+  GstClockTime shot_to_shot;
+  GstClockTime shot_to_save;
+  GstClockTime shot_to_snapshot;
+  GstClockTime preview_to_precapture;
+  GstClockTime shot_to_buffer;
+} CaptureTimingStats;
+
+static void
+capture_timing_stats_add (CaptureTimingStats * a, CaptureTimingStats * b)
+{
+  a->shot_to_shot += b->shot_to_shot;
+  a->shot_to_snapshot += b->shot_to_snapshot;
+  a->shot_to_save += b->shot_to_save;
+  a->preview_to_precapture += b->preview_to_precapture;
+  a->shot_to_buffer += b->shot_to_buffer;
+}
+
+static void
+capture_timing_stats_div (CaptureTimingStats * stats, gint div)
+{
+  stats->shot_to_shot /= div;
+  stats->shot_to_snapshot /= div;
+  stats->shot_to_save /= div;
+  stats->preview_to_precapture /= div;
+  stats->shot_to_buffer /= div;
+}
+
+#define PRINT_STATS(d,s) g_print ("%02d | %" TIME_FORMAT " | %" \
+    TIME_FORMAT "   | %" TIME_FORMAT " | %" TIME_FORMAT \
+    "    | %" TIME_FORMAT "\n", d, \
+    TIME_ARGS ((s)->shot_to_save), TIME_ARGS ((s)->shot_to_snapshot), \
+    TIME_ARGS ((s)->shot_to_shot), \
+    TIME_ARGS ((s)->preview_to_precapture), \
+    TIME_ARGS ((s)->shot_to_buffer))
+
+#define SHOT_TO_SAVE(t) ((t)->capture_done - (t)->start_capture)
+#define SHOT_TO_SNAPSHOT(t) ((t)->got_preview - (t)->start_capture)
+#define PREVIEW_TO_PRECAPTURE(t) ((t)->precapture - (t)->got_preview)
+#define SHOT_TO_BUFFER(t) ((t)->camera_capture - (t)->start_capture)
+
+/*
+ * Global vars
+ */
+static GstElement *camerabin = NULL;
+static GstElement *viewfinder_sink = NULL;
+static gulong camera_probe_id = 0;
+static gulong viewfinder_probe_id = 0;
+static GMainLoop *loop = NULL;
+
+/* commandline options */
+static gchar *videosrc_name = NULL;
+static gchar *videodevice_name = NULL;
+static gchar *audiosrc_name = NULL;
+static gchar *wrappersrc_name = NULL;
+static gchar *imagepp_name = NULL;
+static gchar *vfsink_name = NULL;
+static gint image_width = 0;
+static gint image_height = 0;
+static gint view_framerate_num = 0;
+static gint view_framerate_den = 0;
+static gboolean no_xwindow = FALSE;
+static gchar *gep_targetname = NULL;
+static gchar *gep_profilename = NULL;
+static gchar *gep_filename = NULL;
+static gchar *image_capture_caps_str = NULL;
+static gchar *viewfinder_caps_str = NULL;
+static gchar *video_capture_caps_str = NULL;
+static gchar *audio_capture_caps_str = NULL;
+static gboolean performance_measure = FALSE;
+static gchar *performance_targets_str = NULL;
+static gchar *camerabin_flags = NULL;
+
+
+#define MODE_VIDEO 2
+#define MODE_IMAGE 1
+static gint mode = MODE_IMAGE;
+static gint zoom = 100;
+
+static gint capture_time = 10;
+static gint capture_count = 0;
+static gint capture_total = 1;
+static gulong stop_capture_cb_id = 0;
+
+/* photography interface command line options */
+#define EV_COMPENSATION_NONE -G_MAXFLOAT
+#define APERTURE_NONE -G_MAXINT
+#define FLASH_MODE_NONE -G_MAXINT
+#define SCENE_MODE_NONE -G_MAXINT
+#define EXPOSURE_NONE -G_MAXINT64
+#define ISO_SPEED_NONE -G_MAXINT
+#define WHITE_BALANCE_MODE_NONE -G_MAXINT
+#define COLOR_TONE_MODE_NONE -G_MAXINT
+static gfloat ev_compensation = EV_COMPENSATION_NONE;
+static gint aperture = APERTURE_NONE;
+static gint flash_mode = FLASH_MODE_NONE;
+static gint scene_mode = SCENE_MODE_NONE;
+static gint64 exposure = EXPOSURE_NONE;
+static gint iso_speed = ISO_SPEED_NONE;
+static gint wb_mode = WHITE_BALANCE_MODE_NONE;
+static gint color_mode = COLOR_TONE_MODE_NONE;
+
+static gchar *viewfinder_filter = NULL;
+
+static int x_width = 320;
+static int x_height = 240;
+
+/* test configuration for common callbacks */
+static GString *filename = NULL;
+
+static gchar *preview_caps_name = NULL;
+
+/* X window variables */
+static Display *display = NULL;
+static Window window = 0;
+
+/* timing data */
+static GstClockTime initial_time = 0;
+static GstClockTime startup_time = 0;
+static GstClockTime change_mode_before = 0;
+static GstClockTime change_mode_after = 0;
+static GList *capture_times = NULL;
+
+static GstClockTime target_startup;
+static GstClockTime target_change_mode;
+static GstClockTime target_shot_to_shot;
+static GstClockTime target_shot_to_save;
+static GstClockTime target_shot_to_snapshot;
+static GstClockTime target_preview_to_precapture;
+static GstClockTime target_shot_to_buffer;
+
+
+/*
+ * Prototypes
+ */
+static gboolean run_pipeline (gpointer user_data);
+static void set_metadata (GstElement * camera);
+
+static void
+create_host_window (void)
+{
+  unsigned long valuemask;
+  XSetWindowAttributes attributes;
+
+  display = XOpenDisplay (NULL);
+  if (display) {
+    window =
+        XCreateSimpleWindow (display, DefaultRootWindow (display), 0, 0,
+        x_width, x_height, 0, 0, 0);
+    if (window) {
+      valuemask = CWOverrideRedirect;
+      attributes.override_redirect = True;
+      XChangeWindowAttributes (display, window, valuemask, &attributes);
+      XSetWindowBackgroundPixmap (display, window, None);
+      XMapRaised (display, window);
+      XSync (display, FALSE);
+    } else {
+      GST_DEBUG ("could not create X window!");
+    }
+  } else {
+    GST_DEBUG ("could not open display!");
+  }
+}
+
+static GstPadProbeReturn
+camera_src_get_timestamp_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer udata)
+{
+  CaptureTiming *timing;
+
+  timing = (CaptureTiming *) g_list_first (capture_times)->data;
+  timing->camera_capture = gst_util_get_timestamp ();
+
+  return GST_PAD_PROBE_REMOVE;
+}
+
+static GstPadProbeReturn
+viewfinder_get_timestamp_probe (GstPad * pad, GstPadProbeInfo * info,
+    gpointer udata)
+{
+  CaptureTiming *timing;
+
+  timing = (CaptureTiming *) g_list_first (capture_times)->data;
+  timing->precapture = gst_util_get_timestamp ();
+
+  return GST_PAD_PROBE_REMOVE;
+}
+
+static GstBusSyncReply
+sync_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
+{
+  const GstStructure *st;
+  const GValue *image;
+  GstBuffer *buf = NULL;
+  gchar *preview_filename = NULL;
+  FILE *f = NULL;
+  size_t written;
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ELEMENT:{
+      st = gst_message_get_structure (message);
+      if (st) {
+        if (gst_message_has_name (message, "prepare-xwindow-id")) {
+          if (!no_xwindow && window) {
+            gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY
+                (GST_MESSAGE_SRC (message)), window);
+            gst_message_unref (message);
+            message = NULL;
+            return GST_BUS_DROP;
+          }
+        } else if (gst_structure_has_name (st, "preview-image")) {
+          CaptureTiming *timing;
+
+          GST_DEBUG ("preview-image");
+
+          timing = (CaptureTiming *) g_list_first (capture_times)->data;
+          timing->got_preview = gst_util_get_timestamp ();
+
+          {
+            /* set up probe to check when the viewfinder gets data */
+            GstPad *pad = gst_element_get_static_pad (viewfinder_sink, "sink");
+
+            viewfinder_probe_id =
+                gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
+                viewfinder_get_timestamp_probe, NULL, NULL);
+
+            gst_object_unref (pad);
+          }
+
+          /* extract preview-image from msg */
+          image = gst_structure_get_value (st, "buffer");
+          if (image) {
+            buf = gst_value_get_buffer (image);
+            preview_filename = g_strdup_printf ("test_vga.rgb");
+            f = g_fopen (preview_filename, "w");
+            if (f) {
+              GstMapInfo map;
+
+              gst_buffer_map (buf, &map, GST_MAP_READ);
+              written = fwrite (map.data, map.size, 1, f);
+              gst_buffer_unmap (buf, &map);
+              if (!written) {
+                g_print ("error writing file\n");
+              }
+              fclose (f);
+            } else {
+              g_print ("error opening file for raw image writing\n");
+            }
+            g_free (preview_filename);
+          }
+        }
+      }
+      break;
+    }
+    case GST_MESSAGE_STATE_CHANGED:
+      if (GST_MESSAGE_SRC (message) == (GstObject *) camerabin) {
+        GstState newstate;
+
+        gst_message_parse_state_changed (message, NULL, &newstate, NULL);
+        if (newstate == GST_STATE_PLAYING) {
+          startup_time = gst_util_get_timestamp ();
+        }
+      }
+      break;
+    default:
+      /* unhandled message */
+      break;
+  }
+  return GST_BUS_PASS;
+}
+
+static gboolean
+bus_callback (GstBus * bus, GstMessage * message, gpointer data)
+{
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err;
+      gchar *debug;
+
+      gst_message_parse_error (message, &err, &debug);
+      g_print ("Error: %s\n", err->message);
+      g_clear_error (&err);
+      g_free (debug);
+
+      /* Write debug graph to file */
+      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (camerabin),
+          GST_DEBUG_GRAPH_SHOW_ALL, "camerabin.error");
+
+      g_main_loop_quit (loop);
+      break;
+    }
+    case GST_MESSAGE_STATE_CHANGED:
+      if (GST_IS_BIN (GST_MESSAGE_SRC (message))) {
+        GstState oldstate, newstate;
+
+        gst_message_parse_state_changed (message, &oldstate, &newstate, NULL);
+        GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "state-changed: %s -> %s",
+            gst_element_state_get_name (oldstate),
+            gst_element_state_get_name (newstate));
+      }
+      break;
+    case GST_MESSAGE_EOS:
+      /* end-of-stream */
+      GST_INFO ("got eos() - should not happen");
+      g_main_loop_quit (loop);
+      break;
+    case GST_MESSAGE_ELEMENT:
+      if (GST_MESSAGE_SRC (message) == (GstObject *) camerabin) {
+        const GstStructure *structure = gst_message_get_structure (message);
+
+        if (gst_structure_has_name (structure, "image-done")) {
+          CaptureTiming *timing;
+#ifndef GST_DISABLE_GST_DEBUG
+          const gchar *fname = gst_structure_get_string (structure, "filename");
+
+          GST_DEBUG ("image done: %s", fname);
+#endif
+          timing = (CaptureTiming *) g_list_first (capture_times)->data;
+          timing->capture_done = gst_util_get_timestamp ();
+
+          if (capture_count < capture_total) {
+            g_idle_add ((GSourceFunc) run_pipeline, NULL);
+          } else {
+            g_main_loop_quit (loop);
+          }
+        }
+      }
+      break;
+    default:
+      /* unhandled message */
+      break;
+  }
+  return TRUE;
+}
+
+/*
+ * Helpers
+ */
+
+static void
+cleanup_pipeline (void)
+{
+  if (camerabin) {
+    GST_INFO_OBJECT (camerabin, "stopping and destroying");
+    gst_element_set_state (camerabin, GST_STATE_NULL);
+    gst_object_unref (camerabin);
+    camerabin = NULL;
+  }
+}
+
+static GstElement *
+create_ipp_bin (void)
+{
+  GstElement *bin = NULL, *element = NULL;
+  GstPad *pad = NULL;
+  gchar **elements;
+  GList *element_list = NULL, *current = NULL, *next = NULL;
+  int i;
+
+  bin = gst_bin_new ("ippbin");
+
+  elements = g_strsplit (imagepp_name, ",", 0);
+
+  for (i = 0; elements[i] != NULL; i++) {
+    element = gst_element_factory_make (elements[i], NULL);
+    if (element) {
+      element_list = g_list_append (element_list, element);
+      gst_bin_add (GST_BIN (bin), element);
+    } else
+      GST_WARNING ("Could create element %s for ippbin", elements[i]);
+  }
+
+  for (i = 1; i < g_list_length (element_list); i++) {
+    current = g_list_nth (element_list, i - 1);
+    next = g_list_nth (element_list, i);
+    gst_element_link (current->data, next->data);
+  }
+
+  current = g_list_first (element_list);
+  pad = gst_element_get_static_pad (current->data, "sink");
+  gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
+  gst_object_unref (GST_OBJECT (pad));
+
+  current = g_list_last (element_list);
+  pad = gst_element_get_static_pad (current->data, "src");
+  gst_element_add_pad (bin, gst_ghost_pad_new ("src", pad));
+  gst_object_unref (GST_OBJECT (pad));
+
+  g_list_free (element_list);
+  g_strfreev (elements);
+
+  return bin;
+}
+
+static GstEncodingProfile *
+load_encoding_profile (void)
+{
+  GstEncodingProfile *prof = NULL;
+  GstEncodingTarget *target = NULL;
+  GError *error = NULL;
+
+  /* if profile file was given, try to load profile from there */
+  if (gep_filename && gep_profilename) {
+    target = gst_encoding_target_load_from_file (gep_filename, &error);
+    if (!target) {
+      GST_WARNING ("Could not load target %s from file %s", gep_targetname,
+          gep_filename);
+      if (error) {
+        GST_WARNING ("Error from file loading: %s", error->message);
+        g_clear_error (&error);
+      }
+    } else {
+      prof = gst_encoding_target_get_profile (target, gep_profilename);
+      if (prof)
+        GST_DEBUG ("Loaded encoding profile %s from %s", gep_profilename,
+            gep_filename);
+      else
+        GST_WARNING
+            ("Could not load specified encoding profile %s from file %s",
+            gep_profilename, gep_filename);
+    }
+    /* if we could not load profile from file then try to find one from system */
+  } else if (gep_profilename && gep_targetname) {
+    prof = gst_encoding_profile_find (gep_targetname, gep_profilename, NULL);
+    if (prof)
+      GST_DEBUG ("Loaded encoding profile %s from target %s", gep_profilename,
+          gep_targetname);
+  } else
+    GST_DEBUG
+        ("Encoding profile not set, using camerabin default encoding profile");
+
+  return prof;
+}
+
+static gboolean
+setup_pipeline_element (GstElement * element, const gchar * property_name,
+    const gchar * element_name, GstElement ** res_elem)
+{
+  gboolean res = TRUE;
+  GstElement *elem = NULL;
+
+  if (element_name) {
+    GError *error = NULL;
+
+    elem = gst_parse_launch (element_name, &error);
+    if (elem) {
+      g_object_set (element, property_name, elem, NULL);
+      g_object_unref (elem);
+    } else {
+      GST_WARNING ("can't create element '%s' for property '%s'", element_name,
+          property_name);
+      if (error) {
+        GST_ERROR ("%s", error->message);
+        g_clear_error (&error);
+      }
+      res = FALSE;
+    }
+  } else {
+    GST_DEBUG ("no element for property '%s' given", property_name);
+  }
+  if (res_elem)
+    *res_elem = elem;
+  return res;
+}
+
+static void
+set_camerabin_caps_from_string (void)
+{
+  GstCaps *caps = NULL;
+  if (image_capture_caps_str != NULL) {
+    caps = gst_caps_from_string (image_capture_caps_str);
+    if (GST_CAPS_IS_SIMPLE (caps) && image_width > 0 && image_height > 0) {
+      gst_caps_set_simple (caps, "width", G_TYPE_INT, image_width, "height",
+          G_TYPE_INT, image_height, NULL);
+    }
+    GST_DEBUG ("setting image-capture-caps: %" GST_PTR_FORMAT, caps);
+    g_object_set (camerabin, "image-capture-caps", caps, NULL);
+    gst_caps_unref (caps);
+  }
+
+  if (viewfinder_caps_str != NULL) {
+    caps = gst_caps_from_string (viewfinder_caps_str);
+    if (GST_CAPS_IS_SIMPLE (caps) && view_framerate_num > 0
+        && view_framerate_den > 0) {
+      gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
+          view_framerate_num, view_framerate_den, NULL);
+    }
+    GST_DEBUG ("setting viewfinder-caps: %" GST_PTR_FORMAT, caps);
+    g_object_set (camerabin, "viewfinder-caps", caps, NULL);
+    gst_caps_unref (caps);
+  }
+
+  if (video_capture_caps_str != NULL) {
+    caps = gst_caps_from_string (video_capture_caps_str);
+    GST_DEBUG ("setting video-capture-caps: %" GST_PTR_FORMAT, caps);
+    g_object_set (camerabin, "video-capture-caps", caps, NULL);
+    gst_caps_unref (caps);
+  }
+
+  if (audio_capture_caps_str != NULL) {
+    caps = gst_caps_from_string (audio_capture_caps_str);
+    GST_DEBUG ("setting audio-capture-caps: %" GST_PTR_FORMAT, caps);
+    g_object_set (camerabin, "audio-capture-caps", caps, NULL);
+    gst_caps_unref (caps);
+  }
+}
+
+static gboolean
+setup_pipeline (void)
+{
+  gboolean res = TRUE;
+  GstBus *bus;
+  GstElement *sink = NULL, *ipp = NULL;
+  GstEncodingProfile *prof = NULL;
+
+  initial_time = gst_util_get_timestamp ();
+
+  camerabin = gst_element_factory_make ("camerabin", NULL);
+  if (NULL == camerabin) {
+    g_warning ("can't create camerabin element\n");
+    goto error;
+  }
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (camerabin));
+  /* Add sync handler for time critical messages that need to be handled fast */
+  gst_bus_set_sync_handler (bus, sync_bus_callback, NULL, NULL);
+  /* Handle normal messages asynchronously */
+  gst_bus_add_watch (bus, bus_callback, NULL);
+  gst_object_unref (bus);
+
+  GST_INFO_OBJECT (camerabin, "camerabin created");
+
+  if (camerabin_flags)
+    gst_util_set_object_arg (G_OBJECT (camerabin), "flags", camerabin_flags);
+  else
+    gst_util_set_object_arg (G_OBJECT (camerabin), "flags", "");
+
+  if (videosrc_name) {
+    GstElement *wrapper;
+    GstElement *videosrc;
+
+    if (wrappersrc_name)
+      wrapper = gst_element_factory_make (wrappersrc_name, NULL);
+    else
+      wrapper = gst_element_factory_make ("wrappercamerabinsrc", NULL);
+
+    if (setup_pipeline_element (wrapper, "video-source", videosrc_name, NULL)) {
+      g_object_set (camerabin, "camera-source", wrapper, NULL);
+      g_object_unref (wrapper);
+    } else {
+      GST_WARNING ("Failed to set videosrc to %s", videosrc_name);
+    }
+
+    g_object_get (wrapper, "video-source", &videosrc, NULL);
+    if (videosrc && videodevice_name &&
+        g_object_class_find_property (G_OBJECT_GET_CLASS (videosrc),
+            "device")) {
+      g_object_set (videosrc, "device", videodevice_name, NULL);
+    }
+  }
+
+  /* configure used elements */
+  res &=
+      setup_pipeline_element (camerabin, "audio-source", audiosrc_name, NULL);
+  res &=
+      setup_pipeline_element (camerabin, "viewfinder-sink", vfsink_name, &sink);
+  res &=
+      setup_pipeline_element (camerabin, "viewfinder-filter", viewfinder_filter,
+      NULL);
+
+  if (imagepp_name) {
+    ipp = create_ipp_bin ();
+    if (ipp) {
+      g_object_set (camerabin, "image-filter", ipp, NULL);
+      g_object_unref (ipp);
+    } else
+      GST_WARNING ("Could not create ipp elements");
+  }
+
+  prof = load_encoding_profile ();
+  if (prof) {
+    g_object_set (G_OBJECT (camerabin), "video-profile", prof, NULL);
+    gst_encoding_profile_unref (prof);
+  }
+
+  GST_INFO_OBJECT (camerabin, "elements created");
+
+  if (sink) {
+    g_object_set (sink, "sync", TRUE, NULL);
+  } else {
+    /* Get the inner viewfinder sink, this uses fixed names given
+     * by default in camerabin */
+    sink = gst_bin_get_by_name (GST_BIN (camerabin), "vf-bin");
+    g_assert (sink);
+    gst_object_unref (sink);
+
+    sink = gst_bin_get_by_name (GST_BIN (sink), "vfbin-sink");
+    g_assert (sink);
+    gst_object_unref (sink);
+  }
+  viewfinder_sink = sink;
+
+  GST_INFO_OBJECT (camerabin, "elements configured");
+
+  /* configure a resolution and framerate */
+  if (image_width > 0 && image_height > 0) {
+    if (mode == MODE_VIDEO) {
+      GstCaps *caps = NULL;
+      if (view_framerate_num > 0)
+        caps = gst_caps_new_full (gst_structure_new ("video/x-raw",
+                "width", G_TYPE_INT, image_width,
+                "height", G_TYPE_INT, image_height,
+                "framerate", GST_TYPE_FRACTION, view_framerate_num,
+                view_framerate_den, NULL), NULL);
+      else
+        caps = gst_caps_new_full (gst_structure_new ("video/x-raw",
+                "width", G_TYPE_INT, image_width,
+                "height", G_TYPE_INT, image_height, NULL), NULL);
+
+      g_object_set (camerabin, "video-capture-caps", caps, NULL);
+      gst_caps_unref (caps);
+    } else {
+      GstCaps *caps = gst_caps_new_full (gst_structure_new ("video/x-raw",
+              "width", G_TYPE_INT, image_width,
+              "height", G_TYPE_INT, image_height, NULL), NULL);
+
+      g_object_set (camerabin, "image-capture-caps", caps, NULL);
+      gst_caps_unref (caps);
+    }
+  }
+
+  set_camerabin_caps_from_string ();
+
+  /* change to the wrong mode if timestamping if performance mode is on so
+   * we can change it back and measure the time after in playing */
+  if (performance_measure) {
+    g_object_set (camerabin, "mode",
+        mode == MODE_VIDEO ? MODE_IMAGE : MODE_VIDEO, NULL);
+  }
+
+  if (GST_STATE_CHANGE_FAILURE ==
+      gst_element_set_state (camerabin, GST_STATE_READY)) {
+    g_warning ("can't set camerabin to ready\n");
+    goto error;
+  }
+  GST_INFO_OBJECT (camerabin, "camera ready");
+
+  if (GST_STATE_CHANGE_FAILURE ==
+      gst_element_set_state (camerabin, GST_STATE_PLAYING)) {
+    g_warning ("can't set camerabin to playing\n");
+    goto error;
+  }
+
+  GST_INFO_OBJECT (camerabin, "camera started");
+
+  /* do the mode change timestamping if performance mode is on */
+  if (performance_measure) {
+    change_mode_before = gst_util_get_timestamp ();
+    g_object_set (camerabin, "mode", mode, NULL);
+    change_mode_after = gst_util_get_timestamp ();
+  }
+
+  return TRUE;
+error:
+  cleanup_pipeline ();
+  return FALSE;
+}
+
+static void
+stop_capture_cb (GObject * self, GParamSpec * pspec, gpointer user_data)
+{
+  gboolean idle = FALSE;
+
+  g_object_get (camerabin, "idle", &idle, NULL);
+
+  if (idle) {
+    if (capture_count < capture_total) {
+      g_idle_add ((GSourceFunc) run_pipeline, NULL);
+    } else {
+      g_main_loop_quit (loop);
+    }
+  }
+
+  g_signal_handler_disconnect (camerabin, stop_capture_cb_id);
+}
+
+static gboolean
+stop_capture (gpointer user_data)
+{
+  stop_capture_cb_id = g_signal_connect (camerabin, "notify::idle",
+      (GCallback) stop_capture_cb, camerabin);
+  g_signal_emit_by_name (camerabin, "stop-capture", 0);
+  return FALSE;
+}
+
+static void
+set_metadata (GstElement * camera)
+{
+  GstTagSetter *setter = GST_TAG_SETTER (camera);
+  GstDateTime *datetime;
+  gchar *desc_str;
+
+  datetime = gst_date_time_new_now_local_time ();
+
+  desc_str = g_strdup_printf ("captured by %s", g_get_real_name ());
+
+  gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE,
+      GST_TAG_DATE_TIME, datetime,
+      GST_TAG_DESCRIPTION, desc_str,
+      GST_TAG_TITLE, "gst-camerabin-test capture",
+      GST_TAG_GEO_LOCATION_LONGITUDE, 1.0,
+      GST_TAG_GEO_LOCATION_LATITUDE, 2.0,
+      GST_TAG_GEO_LOCATION_ELEVATION, 3.0,
+      GST_TAG_DEVICE_MANUFACTURER, "gst-camerabin-test manufacturer",
+      GST_TAG_DEVICE_MODEL, "gst-camerabin-test model", NULL);
+
+  g_free (desc_str);
+  gst_date_time_unref (datetime);
+}
+
+static gboolean
+run_pipeline (gpointer user_data)
+{
+  GstCaps *preview_caps = NULL;
+  gchar *filename_str = NULL;
+  GstElement *video_source = NULL;
+  const gchar *filename_suffix;
+  CaptureTiming *timing;
+
+  g_object_set (camerabin, "mode", mode, NULL);
+
+  if (preview_caps_name != NULL) {
+    preview_caps = gst_caps_from_string (preview_caps_name);
+    if (preview_caps) {
+      g_object_set (camerabin, "preview-caps", preview_caps, NULL);
+      GST_DEBUG ("Preview caps set");
+    } else
+      GST_DEBUG ("Preview caps set but could not create caps from string");
+  }
+
+  set_metadata (camerabin);
+
+  /* Construct filename */
+  if (mode == MODE_VIDEO)
+    filename_suffix = ".mp4";
+  else
+    filename_suffix = ".jpg";
+  filename_str =
+      g_strdup_printf ("%s/test_%04u%s", filename->str, capture_count,
+      filename_suffix);
+  GST_DEBUG ("Setting filename: %s", filename_str);
+  g_object_set (camerabin, "location", filename_str, NULL);
+  g_free (filename_str);
+
+  g_object_get (camerabin, "camera-source", &video_source, NULL);
+  if (video_source) {
+    if (GST_IS_ELEMENT (video_source) && GST_IS_PHOTOGRAPHY (video_source)) {
+      /* Set GstPhotography interface options. If option not given as
+         command-line parameter use default of the source element. */
+      if (scene_mode != SCENE_MODE_NONE)
+        g_object_set (video_source, "scene-mode", scene_mode, NULL);
+      if (ev_compensation != EV_COMPENSATION_NONE)
+        g_object_set (video_source, "ev-compensation", ev_compensation, NULL);
+      if (aperture != APERTURE_NONE)
+        g_object_set (video_source, "aperture", aperture, NULL);
+      if (flash_mode != FLASH_MODE_NONE)
+        g_object_set (video_source, "flash-mode", flash_mode, NULL);
+      if (exposure != EXPOSURE_NONE)
+        g_object_set (video_source, "exposure", exposure, NULL);
+      if (iso_speed != ISO_SPEED_NONE)
+        g_object_set (video_source, "iso-speed", iso_speed, NULL);
+      if (wb_mode != WHITE_BALANCE_MODE_NONE)
+        g_object_set (video_source, "white-balance-mode", wb_mode, NULL);
+      if (color_mode != COLOR_TONE_MODE_NONE)
+        g_object_set (video_source, "colour-tone-mode", color_mode, NULL);
+    }
+    g_object_unref (video_source);
+  } else {
+    video_source = gst_bin_get_by_name (GST_BIN (camerabin), "camerasrc");
+    gst_object_unref (video_source);
+  }
+  g_object_set (camerabin, "zoom", zoom / 100.0f, NULL);
+
+  capture_count++;
+
+  timing = g_slice_new0 (CaptureTiming);
+  capture_times = g_list_prepend (capture_times, timing);
+
+  /* set pad probe to check when buffer leaves the camera source */
+  if (mode == MODE_IMAGE) {
+    GstPad *pad;
+
+    pad = gst_element_get_static_pad (video_source, "imgsrc");
+    camera_probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
+        camera_src_get_timestamp_probe, NULL, NULL);
+
+    gst_object_unref (pad);
+  }
+  timing->start_capture = gst_util_get_timestamp ();
+  g_signal_emit_by_name (camerabin, "start-capture", 0);
+
+  if (mode == MODE_VIDEO) {
+    g_timeout_add ((capture_time * 1000), (GSourceFunc) stop_capture, NULL);
+  }
+
+  return FALSE;
+}
+
+static void
+parse_target_values (void)
+{
+  gdouble startup = 0, change_mode = 0, shot_to_save = 0, shot_to_snapshot = 0;
+  gdouble shot_to_shot = 0, preview_to_precapture = 0, shot_to_buffer = 0;
+
+  if (performance_targets_str == NULL)
+    return;
+
+  /*
+     startup time, change mode time, shot to save, shot to snapshot,
+     shot to shot, preview to precapture, shot to buffer.
+   */
+  sscanf (performance_targets_str, "%lf,%lf,%lf,%lf,%lf,%lf,%lf",
+      &startup, &change_mode, &shot_to_save,
+      &shot_to_snapshot, &shot_to_shot, &preview_to_precapture,
+      &shot_to_buffer);
+
+  target_startup = (GstClockTime) (startup * GST_SECOND);
+  target_change_mode = (GstClockTime) (change_mode * GST_SECOND);
+  target_shot_to_save = (GstClockTime) (shot_to_save * GST_SECOND);
+  target_shot_to_snapshot = (GstClockTime) (shot_to_snapshot * GST_SECOND);
+  target_shot_to_shot = (GstClockTime) (shot_to_shot * GST_SECOND);
+  target_preview_to_precapture =
+      (GstClockTime) (preview_to_precapture * GST_SECOND);
+  target_shot_to_buffer = (GstClockTime) (shot_to_buffer * GST_SECOND);
+}
+
+static void
+print_performance_data (void)
+{
+  GList *iter;
+  gint i = 0;
+  GstClockTime last_start = 0;
+  CaptureTimingStats max;
+  CaptureTimingStats min;
+  CaptureTimingStats avg;
+  CaptureTimingStats avg_wo_first;
+  GstClockTime shot_to_shot;
+
+  if (!performance_measure)
+    return;
+
+  parse_target_values ();
+
+  /* Initialize stats */
+  min.shot_to_shot = -1;
+  min.shot_to_save = -1;
+  min.shot_to_snapshot = -1;
+  min.preview_to_precapture = -1;
+  min.shot_to_buffer = -1;
+  memset (&avg, 0, sizeof (CaptureTimingStats));
+  memset (&avg_wo_first, 0, sizeof (CaptureTimingStats));
+  memset (&max, 0, sizeof (CaptureTimingStats));
+
+  g_print ("-- Performance results --\n");
+  g_print ("Startup time: %" TIME_FORMAT "; Target: %" TIME_FORMAT "\n",
+      TIME_ARGS (startup_time - initial_time), TIME_ARGS (target_startup));
+  g_print ("Change mode time: %" TIME_FORMAT "; Target: %" TIME_FORMAT "\n",
+      TIME_ARGS (change_mode_after - change_mode_before),
+      TIME_ARGS (target_change_mode));
+
+  g_print
+      ("\n   | Shot to save |Shot to snapshot| Shot to shot |"
+      "Preview to precap| Shot to buffer\n");
+  capture_times = g_list_reverse (capture_times);
+  for (iter = capture_times; iter; iter = g_list_next (iter)) {
+    CaptureTiming *t = (CaptureTiming *) iter->data;
+    CaptureTimingStats stats;
+
+    stats.shot_to_save = SHOT_TO_SAVE (t);
+    stats.shot_to_snapshot = SHOT_TO_SNAPSHOT (t);
+    stats.shot_to_shot = i == 0 ? 0 : t->start_capture - last_start;
+    stats.preview_to_precapture = PREVIEW_TO_PRECAPTURE (t);
+    stats.shot_to_buffer = SHOT_TO_BUFFER (t);
+
+    PRINT_STATS (i, &stats);
+
+    if (i != 0) {
+      capture_timing_stats_add (&avg_wo_first, &stats);
+    }
+    capture_timing_stats_add (&avg, &stats);
+
+    if (stats.shot_to_save < min.shot_to_save) {
+      min.shot_to_save = stats.shot_to_save;
+    }
+    if (stats.shot_to_snapshot < min.shot_to_snapshot) {
+      min.shot_to_snapshot = stats.shot_to_snapshot;
+    }
+    if (stats.shot_to_shot < min.shot_to_shot && stats.shot_to_shot > 0) {
+      min.shot_to_shot = stats.shot_to_shot;
+    }
+    if (stats.preview_to_precapture < min.preview_to_precapture) {
+      min.preview_to_precapture = stats.preview_to_precapture;
+    }
+    if (stats.shot_to_buffer < min.shot_to_buffer) {
+      min.shot_to_buffer = stats.shot_to_buffer;
+    }
+
+
+    if (stats.shot_to_save > max.shot_to_save) {
+      max.shot_to_save = stats.shot_to_save;
+    }
+    if (stats.shot_to_snapshot > max.shot_to_snapshot) {
+      max.shot_to_snapshot = stats.shot_to_snapshot;
+    }
+    if (stats.shot_to_shot > max.shot_to_shot) {
+      max.shot_to_shot = stats.shot_to_shot;
+    }
+    if (stats.preview_to_precapture > max.preview_to_precapture) {
+      max.preview_to_precapture = stats.preview_to_precapture;
+    }
+    if (stats.shot_to_buffer > max.shot_to_buffer) {
+      max.shot_to_buffer = stats.shot_to_buffer;
+    }
+
+    last_start = t->start_capture;
+    i++;
+  }
+
+  if (i == 0)
+    return;
+
+  if (i > 1)
+    shot_to_shot = avg.shot_to_shot / (i - 1);
+  else
+    shot_to_shot = GST_CLOCK_TIME_NONE;
+  capture_timing_stats_div (&avg, i);
+  avg.shot_to_shot = shot_to_shot;
+  if (i > 1)
+    capture_timing_stats_div (&avg_wo_first, i - 1);
+  else {
+    memset (&avg_wo_first, 0, sizeof (CaptureTimingStats));
+  }
+
+  g_print ("\n    Stats             |     MIN      |     MAX      |"
+      "     AVG      | AVG wo First |   Target     | Diff \n");
+  g_print ("Shot to shot          | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIME_FORMAT " | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIMEDIFF_FORMAT "\n",
+      TIME_ARGS (min.shot_to_shot), TIME_ARGS (max.shot_to_shot),
+      TIME_ARGS (avg.shot_to_shot),
+      TIME_ARGS (avg_wo_first.shot_to_shot),
+      TIME_ARGS (target_shot_to_shot),
+      TIMEDIFF_ARGS (TIME_DIFF (avg.shot_to_shot, target_shot_to_shot)));
+  g_print ("Shot to save          | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIME_FORMAT " | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIMEDIFF_FORMAT "\n",
+      TIME_ARGS (min.shot_to_save), TIME_ARGS (max.shot_to_save),
+      TIME_ARGS (avg.shot_to_save),
+      TIME_ARGS (avg_wo_first.shot_to_save),
+      TIME_ARGS (target_shot_to_save),
+      TIMEDIFF_ARGS (TIME_DIFF (avg.shot_to_save, target_shot_to_save)));
+  g_print ("Shot to snapshot      | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIME_FORMAT " | %" TIME_FORMAT " | %" TIME_FORMAT
+      " | %" TIMEDIFF_FORMAT "\n",
+      TIME_ARGS (min.shot_to_snapshot),
+      TIME_ARGS (max.shot_to_snapshot),
+      TIME_ARGS (avg.shot_to_snapshot),
+      TIME_ARGS (avg_wo_first.shot_to_snapshot),
+      TIME_ARGS (target_shot_to_snapshot),
+      TIMEDIFF_ARGS (TIME_DIFF (avg.shot_to_snapshot,
+              target_shot_to_snapshot)));
+  g_print ("Preview to precapture | %" TIME_FORMAT " | %" TIME_FORMAT " | %"
+      TIME_FORMAT " | %" TIME_FORMAT " | %" TIME_FORMAT " | %" TIMEDIFF_FORMAT
+      "\n", TIME_ARGS (min.preview_to_precapture),
+      TIME_ARGS (max.preview_to_precapture),
+      TIME_ARGS (avg.preview_to_precapture),
+      TIME_ARGS (avg_wo_first.preview_to_precapture),
+      TIME_ARGS (target_preview_to_precapture),
+      TIMEDIFF_ARGS (TIME_DIFF (avg.preview_to_precapture,
+              target_preview_to_precapture)));
+  g_print ("Shot to buffer        | %" TIME_FORMAT " | %" TIME_FORMAT " | %"
+      TIME_FORMAT " | %" TIME_FORMAT " | %" TIME_FORMAT " | %" TIMEDIFF_FORMAT
+      "\n", TIME_ARGS (min.shot_to_buffer), TIME_ARGS (max.shot_to_buffer),
+      TIME_ARGS (avg.shot_to_buffer), TIME_ARGS (avg_wo_first.shot_to_buffer),
+      TIME_ARGS (target_shot_to_buffer),
+      TIMEDIFF_ARGS (TIME_DIFF (avg.shot_to_buffer, target_shot_to_buffer)));
+}
+
+int
+main (int argc, char *argv[])
+{
+  gchar *target_times = NULL;
+  gchar *ev_option = NULL;
+  gchar *fn_option = NULL;
+
+  GOptionEntry options[] = {
+    {"ev-compensation", '\0', 0, G_OPTION_ARG_STRING, &ev_option,
+        "EV compensation for source element GstPhotography interface", NULL},
+    {"aperture", '\0', 0, G_OPTION_ARG_INT, &aperture,
+          "Aperture (size of lens opening) for source element GstPhotography interface",
+        NULL},
+    {"flash-mode", '\0', 0, G_OPTION_ARG_INT,
+          &flash_mode,
+        "Flash mode for source element GstPhotography interface", NULL},
+    {"scene-mode", '\0', 0, G_OPTION_ARG_INT,
+          &scene_mode,
+        "Scene mode for source element GstPhotography interface", NULL},
+    {"exposure", '\0', 0, G_OPTION_ARG_INT64,
+          &exposure,
+          "Exposure time (in ms) for source element GstPhotography interface",
+        NULL},
+    {"iso-speed", '\0', 0, G_OPTION_ARG_INT,
+          &iso_speed,
+        "ISO speed for source element GstPhotography interface", NULL},
+    {"white-balance-mode", '\0', 0, G_OPTION_ARG_INT,
+          &wb_mode,
+        "White balance mode for source element GstPhotography interface", NULL},
+    {"colour-tone-mode", '\0', 0, G_OPTION_ARG_INT,
+          &color_mode,
+        "Colour tone mode for source element GstPhotography interface", NULL},
+    {"directory", '\0', 0, G_OPTION_ARG_STRING, &fn_option,
+        "Directory for capture file(s) (default is current directory)", NULL},
+    {"mode", '\0', 0, G_OPTION_ARG_INT, &mode,
+        "Capture mode (default = 1 (image), 2 = video)", NULL},
+    {"capture-time", '\0', 0, G_OPTION_ARG_INT,
+          &capture_time,
+        "Time to capture video in seconds (default = 10)", NULL},
+    {"capture-total", '\0', 0, G_OPTION_ARG_INT, &capture_total,
+        "Total number of captures to be done (default = 1)", NULL},
+    {"zoom", '\0', 0, G_OPTION_ARG_INT, &zoom,
+        "Zoom (100 = 1x (default), 200 = 2x etc.)", NULL},
+    {"wrapper-source", '\0', 0, G_OPTION_ARG_STRING, &wrappersrc_name,
+          "Camera source wrapper used for setting the video source (default is wrappercamerabinsrc)",
+        NULL},
+    {"video-source", '\0', 0, G_OPTION_ARG_STRING, &videosrc_name,
+        "Video source used in still capture and video recording", NULL},
+    {"video-device", '\0', 0, G_OPTION_ARG_STRING, &videodevice_name,
+        "Video device to be set on the video source", NULL},
+    {"audio-source", '\0', 0, G_OPTION_ARG_STRING, &audiosrc_name,
+        "Audio source used in video recording", NULL},
+    {"image-pp", '\0', 0, G_OPTION_ARG_STRING, &imagepp_name,
+        "List of image post-processing elements separated with comma", NULL},
+    {"viewfinder-sink", '\0', 0, G_OPTION_ARG_STRING, &vfsink_name,
+        "Viewfinder sink (default = fakesink)", NULL},
+    {"image-width", '\0', 0, G_OPTION_ARG_INT, &image_width,
+        "Width for image capture", NULL},
+    {"image-height", '\0', 0, G_OPTION_ARG_INT, &image_height,
+        "Height for image capture", NULL},
+    {"view-framerate-num", '\0', 0, G_OPTION_ARG_INT, &view_framerate_num,
+        "Framerate numerator for viewfinder", NULL},
+    {"view-framerate-den", '\0', 0, G_OPTION_ARG_INT, &view_framerate_den,
+        "Framerate denominator for viewfinder", NULL},
+    {"preview-caps", '\0', 0, G_OPTION_ARG_STRING, &preview_caps_name,
+        "Preview caps (e.g. video/x-raw-rgb,width=320,height=240)", NULL},
+    {"viewfinder-filter", '\0', 0, G_OPTION_ARG_STRING, &viewfinder_filter,
+        "Filter to process all frames going to viewfinder sink", NULL},
+    {"x-width", '\0', 0, G_OPTION_ARG_INT, &x_width,
+        "X window width (default = 320)", NULL},
+    {"x-height", '\0', 0, G_OPTION_ARG_INT, &x_height,
+        "X window height (default = 240)", NULL},
+    {"no-xwindow", '\0', 0, G_OPTION_ARG_NONE, &no_xwindow,
+        "Do not create XWindow", NULL},
+    {"encoding-target", '\0', 0, G_OPTION_ARG_STRING, &gep_targetname,
+        "Video encoding target name", NULL},
+    {"encoding-profile", '\0', 0, G_OPTION_ARG_STRING, &gep_profilename,
+        "Video encoding profile name", NULL},
+    {"encoding-profile-filename", '\0', 0, G_OPTION_ARG_STRING, &gep_filename,
+        "Video encoding profile filename", NULL},
+    {"image-capture-caps", '\0', 0,
+          G_OPTION_ARG_STRING, &image_capture_caps_str,
+        "Image capture caps (e.g. video/x-raw-rgb,width=640,height=480)", NULL},
+    {"viewfinder-caps", '\0', 0, G_OPTION_ARG_STRING,
+          &viewfinder_caps_str,
+        "Viewfinder caps (e.g. video/x-raw-rgb,width=640,height=480)", NULL},
+    {"video-capture-caps", '\0', 0,
+          G_OPTION_ARG_STRING, &video_capture_caps_str,
+        "Video capture caps (e.g. video/x-raw-rgb,width=640,height=480)", NULL},
+    {"audio-capture-caps", '\0', 0,
+          G_OPTION_ARG_STRING, &audio_capture_caps_str,
+          "Audio capture caps (e.g. audio/x-raw-int,width=16,depth=16,rate=44100,channels=2)",
+        NULL},
+    {"performance-measure", '\0', 0,
+          G_OPTION_ARG_NONE, &performance_measure,
+          "If performance information should be printed at the end of execution",
+        NULL},
+    {"performance-targets", '\0', 0,
+          G_OPTION_ARG_STRING, &performance_targets_str,
+          "Comma separated list of doubles representing the target values in "
+          "seconds. The order is: startup time, change mode time, shot to save"
+          ", shot to snapshot, shot to shot, preview to shot, shot to buffer. "
+          "e.g. 3.5,1.0,5.0,2.5,5.0,1.5,1.0",
+        NULL},
+    {"flags", '\0', 0, G_OPTION_ARG_STRING, &camerabin_flags,
+        "camerabin element flags (default = 0)", NULL},
+    {NULL}
+  };
+
+  GOptionContext *ctx;
+  GError *err = NULL;
+
+  ctx = g_option_context_new ("\n\ncamerabin command line test application.");
+  g_option_context_add_main_entries (ctx, options, NULL);
+  g_option_context_add_group (ctx, gst_init_get_option_group ());
+  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
+    g_print ("Error initializing: %s\n", err->message);
+    g_option_context_free (ctx);
+    g_clear_error (&err);
+    exit (1);
+  }
+  g_option_context_free (ctx);
+
+  GST_DEBUG_CATEGORY_INIT (camerabin_test, "camerabin-test", 0,
+      "camerabin test");
+
+  /* if we fail to create xwindow should we care? */
+  if (!no_xwindow)
+    create_host_window ();
+
+  /* FIXME: error handling */
+  if (ev_option != NULL)
+    ev_compensation = strtod (ev_option, (char **) NULL);
+
+  if (vfsink_name == NULL)
+    vfsink_name = g_strdup ("fakesink");
+
+  filename = g_string_new (fn_option);
+  if (filename->len == 0)
+    filename = g_string_append (filename, ".");
+
+  /* init */
+  if (setup_pipeline ()) {
+    loop = g_main_loop_new (NULL, FALSE);
+    g_idle_add ((GSourceFunc) run_pipeline, NULL);
+    g_main_loop_run (loop);
+    cleanup_pipeline ();
+    g_main_loop_unref (loop);
+  }
+
+  /* performance */
+  if (performance_measure) {
+    print_performance_data ();
+  }
+
+  /* free */
+  {
+    GList *iter;
+
+    for (iter = capture_times; iter; iter = g_list_next (iter)) {
+      g_slice_free (CaptureTiming, iter->data);
+    }
+    g_list_free (capture_times);
+  }
+
+  g_string_free (filename, TRUE);
+  g_free (ev_option);
+  g_free (wrappersrc_name);
+  g_free (videosrc_name);
+  g_free (videodevice_name);
+  g_free (audiosrc_name);
+  g_free (imagepp_name);
+  g_free (vfsink_name);
+  g_free (target_times);
+  g_free (gep_targetname);
+  g_free (gep_profilename);
+  g_free (gep_filename);
+  g_free (performance_targets_str);
+
+  if (window)
+    XDestroyWindow (display, window);
+
+  if (display)
+    XCloseDisplay (display);
+
+  return 0;
+}
Index: b/tests/examples/camerabin2/meson.build
===================================================================
--- /dev/null
+++ b/tests/examples/camerabin2/meson.build
@@ -0,0 +1,24 @@
+gtk_dep = dependency('gtk+-3.0', required : get_option('examples'))
+gdk_x11_dep = dependency('gdk-x11-3.0', required : get_option('examples'))
+if gtk_dep.found() and gdk_x11_dep.found()
+  camera2_args = [
+    '-DGST_USE_UNSTABLE_API',
+    '-DCAMERA_APPS_UIDIR="@0@"'.format(meson.current_source_dir()),
+    cc.get_supported_link_arguments('-fvisibility=default'),
+  ]
+  gmodule_export_dep = dependency('gmodule-export-2.0')
+  executable('gst-camera2', 'gst-camera2.c',
+    include_directories : [configinc],
+    dependencies : [gstphotography_dep, gtk_dep, gdk_x11_dep, gst_dep, gstvideo_dep, gstpbutils_dep, gmodule_export_dep],
+    c_args : gst_plugins_bad_args + camera2_args,
+    install: false)
+endif
+
+x11_dep = dependency('x11', required : get_option('examples'))
+if x11_dep.found()
+  executable('gst-camerabin2-test', 'gst-camerabin2-test.c',
+    include_directories : [configinc],
+    dependencies : [gstphotography_dep, x11_dep, gst_dep, gstvideo_dep, gstpbutils_dep],
+    c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+    install: false)
+endif
Index: b/tests/examples/meson.build
===================================================================
--- a/tests/examples/meson.build
+++ b/tests/examples/meson.build
@@ -1,5 +1,6 @@
 subdir('audiofx')
 subdir('cairo')
+subdir('camerabin2')
 subdir('level')
 subdir('qt')
 
Index: b/meson.build
===================================================================
--- a/meson.build
+++ b/meson.build
@@ -23,6 +23,12 @@
 gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor)
 
 api_version = '1.0'
+soversion = 0
+# maintaining compatibility with the previous libtool versioning
+# current = minor * 100 + micro
+curversion = gst_version_minor * 100 + gst_version_micro
+libversion = '@0@.@1@.0'.format(soversion, curversion)
+osxversion = curversion + 1
 
 plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0')
 plugins = []
@@ -53,11 +59,25 @@
   add_project_link_arguments('-Wl,-Bsymbolic-functions', language : 'c')
 endif
 
+cdata = configuration_data()
+
 # Symbol visibility
-if cc.has_argument('-fvisibility=hidden')
+if cc.get_id() == 'msvc'
+  export_define = '__declspec(dllexport) extern'
+elif cc.has_argument('-fvisibility=hidden')
   add_project_arguments('-fvisibility=hidden', language: 'c')
+  add_project_arguments('-fvisibility=hidden', language: 'cpp')
+  #if have_objc
+  #  add_project_arguments('-fvisibility=hidden', language: 'objc')
+  #endif
+  export_define = 'extern __attribute__ ((visibility ("default")))'
+else
+  export_define = 'extern'
 endif
 
+# Passing this through the command line would be too messy
+cdata.set('GST_API_EXPORT', export_define)
+
 # Disable strict aliasing
 if cc.has_argument('-fno-strict-aliasing')
   add_project_arguments('-fno-strict-aliasing', language: 'c')
@@ -87,7 +107,7 @@
   add_project_arguments('-DG_DISABLE_CHECKS', language: 'c')
 endif
 
-cdata = configuration_data()
+#cdata = configuration_data()
 
 check_headers = [
   ['HAVE_DLFCN_H', 'dlfcn.h'],
@@ -232,6 +252,11 @@
 cdata.set_quoted('GST_PACKAGE_NAME', gst_package_name)
 cdata.set_quoted('GST_PACKAGE_ORIGIN', get_option('package-origin'))
 
+# FIXME: This should be exposed as a configuration option
+if host_system == 'linux'
+  cdata.set_quoted('DEFAULT_VIDEOSRC', 'v4l2src')
+endif
+
 # Mandatory GST deps
 gst_dep = dependency('gstreamer-1.0', version : gst_req,
   fallback : ['gstreamer', 'gst_dep'])
@@ -422,6 +447,10 @@
   message('GStreamer debug system is enabled')
 endif
 
+gst_plugins_bad_args = ['-DHAVE_CONFIG_H']
+configinc = include_directories('.')
+libsinc = include_directories('gst-libs')
+
 presetdir = join_paths(get_option('datadir'), 'gstreamer-' + api_version, 'presets')
 
 python3 = import('python').find_installation()
@@ -432,6 +461,16 @@
   plugins_pkgconfig_install_dir = disabler()
 endif
 
+gir = find_program('g-ir-scanner', required : get_option('introspection'))
+gnome = import('gnome')
+build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())
+gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \
+    'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
+    'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
+    'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
+    'gst_init(NULL,NULL);', '--quiet']
+
+subdir('gst-libs')
 subdir('gst')
 subdir('sys')
 subdir('ext')
Index: b/gst-libs/gst/interfaces/meson.build
===================================================================
--- /dev/null
+++ b/gst-libs/gst/interfaces/meson.build
@@ -0,0 +1,30 @@
+photography_sources = ['photography.c']
+photo_headers = ['photography.h', 'photography-prelude.h']
+install_headers(photo_headers, subdir : 'gstreamer-1.0/gst/interfaces')
+
+photo_enums = gnome.mkenums_simple('photography-enumtypes',
+  sources : photo_headers,
+  body_prefix : '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif',
+  header_prefix : '#include <gst/interfaces/photography-prelude.h>',
+  decorator: 'GST_PHOTOGRAPHY_API',
+  install_header: true,
+  install_dir : join_paths(get_option('includedir'), 'gstreamer-1.0/gst/interfaces'))
+
+photoenum_c = photo_enums[0]
+photoenum_h = photo_enums[1]
+
+gstphotography = library('gstphotography-' + api_version,
+  photography_sources, photoenum_h, photoenum_c,
+  c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API', '-DBUILDING_GST_PHOTOGRAPHY'],
+  include_directories : [configinc, libsinc],
+  version : libversion,
+  soversion : soversion,
+  darwin_versions : osxversion,
+  install : true,
+  dependencies : [gst_dep],
+)
+
+gstphotography_dep = declare_dependency(link_with : gstphotography,
+  include_directories : [libsinc],
+  dependencies : [gst_dep],
+  sources : [photoenum_h])
Index: b/gst-libs/gst/interfaces/photography-prelude.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/interfaces/photography-prelude.h
@@ -0,0 +1,35 @@
+/* GStreamer Photography Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * photography-prelude.h: prelude include header for gst-photography library
+ *
+ * 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_PHOTOGRAPHY_PRELUDE_H__
+#define __GST_PHOTOGRAPHY_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_PHOTOGRAPHY_API
+# ifdef BUILDING_GST_PHOTOGRAPHY
+#  define GST_PHOTOGRAPHY_API GST_API_EXPORT         /* from config.h */
+# else
+#  define GST_PHOTOGRAPHY_API GST_API_IMPORT
+# endif
+#endif
+
+#endif /* __GST_PHOTOGRAPHY_PRELUDE_H__ */
Index: b/gst-libs/gst/interfaces/photography.c
===================================================================
--- /dev/null
+++ b/gst-libs/gst/interfaces/photography.c
@@ -0,0 +1,732 @@
+/* GStreamer
+ *
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * photography.c: photography interface for digital imaging
+ *
+ *
+ * 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 "photography.h"
+
+/**
+ * SECTION:gstphotography
+ * @short_description: Interface for digital image capture elements
+ * @stability: Unstable
+ *
+ * The interface allows access to some common digital image capture parameters.
+ *
+ * > The GstPhotography interface is unstable API and may change in future.
+ * > One can define GST_USE_UNSTABLE_API to acknowledge and avoid this warning.
+ */
+
+static void gst_photography_iface_base_init (GstPhotographyInterface * iface);
+static void gst_photography_iface_class_init (gpointer g_class);
+
+GType
+gst_photography_get_type (void)
+{
+  static GType gst_photography_type = 0;
+
+  if (!gst_photography_type) {
+    static const GTypeInfo gst_photography_info = {
+      sizeof (GstPhotographyInterface),
+      (GBaseInitFunc) gst_photography_iface_base_init,  /* base_init */
+      NULL,                     /* base_finalize */
+      (GClassInitFunc) gst_photography_iface_class_init,        /* class_init */
+      NULL,                     /* class_finalize */
+      NULL,                     /* class_data */
+      0,
+      0,                        /* n_preallocs */
+      NULL,                     /* instance_init */
+    };
+
+    gst_photography_type = g_type_register_static (G_TYPE_INTERFACE,
+        "GstPhotography", &gst_photography_info, 0);
+  }
+
+  return gst_photography_type;
+}
+
+static void
+gst_photography_iface_base_init (GstPhotographyInterface * iface)
+{
+  /* default virtual functions */
+  iface->get_ev_compensation = NULL;
+  iface->get_iso_speed = NULL;
+  iface->get_aperture = NULL;
+  iface->get_exposure = NULL;
+  iface->get_white_balance_mode = NULL;
+  iface->get_color_tone_mode = NULL;
+  iface->get_scene_mode = NULL;
+  iface->get_flash_mode = NULL;
+  iface->get_noise_reduction = NULL;
+  iface->get_zoom = NULL;
+  iface->get_flicker_mode = NULL;
+  iface->get_focus_mode = NULL;
+
+  iface->set_ev_compensation = NULL;
+  iface->set_iso_speed = NULL;
+  iface->set_aperture = NULL;
+  iface->set_exposure = NULL;
+  iface->set_white_balance_mode = NULL;
+  iface->set_color_tone_mode = NULL;
+  iface->set_scene_mode = NULL;
+  iface->set_flash_mode = NULL;
+  iface->set_noise_reduction = NULL;
+  iface->set_zoom = NULL;
+  iface->set_flicker_mode = NULL;
+  iface->set_focus_mode = NULL;
+
+  iface->get_capabilities = NULL;
+  iface->prepare_for_capture = NULL;
+  iface->set_autofocus = NULL;
+  iface->set_config = NULL;
+  iface->get_config = NULL;
+  iface->set_exposure_mode = NULL;
+  iface->get_exposure_mode = NULL;
+  iface->set_analog_gain = NULL;
+  iface->get_analog_gain = NULL;
+  iface->set_lens_focus = NULL;
+  iface->get_lens_focus = NULL;
+  iface->set_color_temperature = NULL;
+  iface->get_color_temperature = NULL;
+  iface->set_min_exposure_time = NULL;
+  iface->get_min_exposure_time = NULL;
+  iface->set_max_exposure_time = NULL;
+  iface->get_max_exposure_time = NULL;
+}
+
+#define GST_PHOTOGRAPHY_FUNC_TEMPLATE(function_name, param_type) \
+gboolean \
+gst_photography_set_ ## function_name (GstPhotography * photo, param_type param) \
+{ \
+  GstPhotographyInterface *iface; \
+  g_return_val_if_fail (photo != NULL, FALSE); \
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo); \
+  if (iface->set_ ## function_name) { \
+    return iface->set_ ## function_name (photo, param); \
+  } \
+  return FALSE; \
+} \
+gboolean \
+gst_photography_get_ ## function_name (GstPhotography * photo, param_type * param) \
+{ \
+  GstPhotographyInterface *iface; \
+  g_return_val_if_fail (photo != NULL, FALSE); \
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo); \
+  if (iface->get_ ## function_name) { \
+    return iface->get_ ## function_name (photo, param); \
+  } \
+  return FALSE; \
+}
+
+
+/**
+ * gst_photography_set_ev_compensation:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @ev_comp: ev compensation value to set
+ *
+ * Set the ev compensation value for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_ev_compensation:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @ev_comp: ev compensation value to get
+ *
+ * Get the ev compensation value for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (ev_compensation, gfloat);
+
+/**
+ * gst_photography_set_iso_speed:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @iso_speed: ISO speed value to set
+ *
+ * Set the ISO value (light sensivity) for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_iso_speed:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @iso_speed: ISO speed value to get
+ *
+ * Get the ISO value (light sensivity) for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (iso_speed, guint);
+
+/**
+ * gst_photography_set_aperture:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @aperture: aperture value to set
+ *
+ * Set the aperture value for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_aperture:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @aperture: aperture value to get
+ *
+ * Get the aperture value for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (aperture, guint);
+
+/**
+ * gst_photography_set_exposure:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @exposure: exposure time to set
+ *
+ * Set the fixed exposure time (in us) for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_exposure:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @exposure: exposure time to get
+ *
+ * Get the fixed exposure time (in us) for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (exposure, guint32);
+
+/**
+ * gst_photography_set_white_balance_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @wb_mode: #GstPhotographyWhiteBalanceMode to set
+ *
+ * Set the white balance mode for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_white_balance_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @wb_mode: #GstPhotographyWhiteBalanceMode to get
+ *
+ * Get the white balance mode for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (white_balance_mode,
+    GstPhotographyWhiteBalanceMode);
+
+/**
+ * gst_photography_set_color_tone_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @tone_mode: #GstPhotographyColorToneMode to set
+ *
+ * Set the color tone mode for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_color_tone_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @tone_mode: #GstPhotographyColorToneMode to get
+ *
+ * Get the color tone mode for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (color_tone_mode, GstPhotographyColorToneMode);
+
+/**
+ * gst_photography_set_scene_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @scene_mode: #GstPhotographySceneMode to set
+ *
+ * Set the scene mode for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_scene_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @scene_mode: #GstPhotographySceneMode to get
+ *
+ * Get the scene mode for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (scene_mode, GstPhotographySceneMode);
+
+/**
+ * gst_photography_set_flash_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @flash_mode: #GstPhotographyFlashMode to set
+ *
+ * Set the flash mode for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_flash_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @flash_mode: #GstPhotographyFlashMode to get
+ *
+ * Get the flash mode for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (flash_mode, GstPhotographyFlashMode);
+
+/**
+ * gst_photography_set_noise_reduction:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @noise_reduction: #GstPhotographyNoiseReductionMode to set
+ *
+ * Set the noise reduction mode for the #GstElement
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_noise_reduction:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @noise_reduction: #GstPhotographyNoiseReductionMode to get
+ *
+ * Get the noise reduction mode for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (noise_reduction, GstPhotographyNoiseReduction);
+
+/**
+ * gst_photography_set_zoom:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @zoom: zoom value to set
+ *
+ * Set the zoom value for the #GstElement.
+ * E.g. 1.0 to get original image and 3.0 for 3x zoom and so on.
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_zoom:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @zoom: zoom value to get
+ *
+ * Get the zoom value for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (zoom, gfloat);
+
+/**
+ * gst_photography_set_flicker_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @flicker_mode: flicker mode value to set
+ *
+ * Set the flicker mode value for the #GstElement.
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_flicker_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @flicker_mode: flicker mode value to get
+ *
+ * Get the flicker mode value for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (flicker_mode,
+    GstPhotographyFlickerReductionMode);
+
+/**
+ * gst_photography_set_focus_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @focus_mode: focus mode value to set
+ *
+ * Set the focus mode value for the #GstElement.
+ *
+ * Returns: %TRUE if setting the value succeeded, %FALSE otherwise
+ */
+/**
+ * gst_photography_get_focus_mode:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @focus_mode: focus_mode value to get
+ *
+ * Get the focus mode value for the #GstElement
+ *
+ * Returns: %TRUE if getting the value succeeded, %FALSE otherwise
+ */
+GST_PHOTOGRAPHY_FUNC_TEMPLATE (focus_mode, GstPhotographyFocusMode);
+
+/**
+ * gst_photography_get_capabilities:
+ * @photo: #GstPhotography interface of a #GstElement
+ *
+ * Get #GstPhotographyCaps bitmask value that indicates what photography
+ * interface features the #GstElement supports
+ *
+ * Returns: #GstPhotographyCaps value
+ */
+GstPhotographyCaps
+gst_photography_get_capabilities (GstPhotography * photo)
+{
+  GstPhotographyInterface *iface;
+  g_return_val_if_fail (photo != NULL, GST_PHOTOGRAPHY_CAPS_NONE);
+
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo);
+  if (iface->get_capabilities) {
+    return iface->get_capabilities (photo);
+  } else {
+    return GST_PHOTOGRAPHY_CAPS_NONE;
+  }
+}
+
+/**
+ * gst_photography_prepare_for_capture:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @func: callback that is called after capturing has been prepared
+ * @capture_caps: #GstCaps defining the desired format of the captured image
+ * @user_data: user data that will be passed to the callback @func
+ *
+ * Start preparations for capture. Preparations can take indeterminate
+ * amount of time and @func callback is called after preparations are
+ * done. Image capture will begin after callback returns.
+ *
+ * Returns: %TRUE if preparations were started (caps were OK), otherwise %FALSE.
+ */
+gboolean
+gst_photography_prepare_for_capture (GstPhotography * photo,
+    GstPhotographyCapturePrepared func, GstCaps * capture_caps,
+    gpointer user_data)
+{
+  GstPhotographyInterface *iface;
+  gboolean ret = TRUE;
+
+  g_return_val_if_fail (photo != NULL, FALSE);
+
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo);
+  if (iface->prepare_for_capture) {
+    ret = iface->prepare_for_capture (photo, func, capture_caps, user_data);
+  }
+
+  return ret;
+}
+
+/**
+ * gst_photography_set_autofocus:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @on: %TRUE to start autofocusing, %FALSE to stop autofocusing
+ *
+ * Start or stop autofocusing. %GST_PHOTOGRAPHY_AUTOFOCUS_DONE
+ * message is posted to bus when autofocusing has finished.
+ */
+void
+gst_photography_set_autofocus (GstPhotography * photo, gboolean on)
+{
+  GstPhotographyInterface *iface;
+  g_return_if_fail (photo != NULL);
+
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo);
+  if (iface->set_autofocus) {
+    iface->set_autofocus (photo, on);
+  }
+}
+
+/**
+ * gst_photography_set_config:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @config: #GstPhotographySettings containing the configuration
+ *
+ * Set all configuration settings at once.
+ *
+ * Returns: TRUE if configuration was set successfully, otherwise FALSE.
+ */
+gboolean
+gst_photography_set_config (GstPhotography * photo,
+    GstPhotographySettings * config)
+{
+  GstPhotographyInterface *iface;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (photo != NULL, FALSE);
+
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo);
+  if (iface->set_config) {
+    ret = iface->set_config (photo, config);
+  }
+
+  return ret;
+}
+
+/**
+ * gst_photography_get_config:
+ * @photo: #GstPhotography interface of a #GstElement
+ * @config: #GstPhotographySettings containing the configuration
+ *
+ * Get all configuration settings at once.
+ *
+ * Returns: TRUE if configuration was got successfully, otherwise FALSE.
+ */
+gboolean
+gst_photography_get_config (GstPhotography * photo,
+    GstPhotographySettings * config)
+{
+  GstPhotographyInterface *iface;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (photo != NULL, FALSE);
+
+  iface = GST_PHOTOGRAPHY_GET_INTERFACE (photo);
+  if (iface->get_config) {
+    ret = iface->get_config (photo, config);
+  }
+
+  return ret;
+}
+
+/* Photography class initialization stuff */
+static void
+gst_photography_iface_class_init (gpointer g_class)
+{
+  /* create interface signals and properties here. */
+
+  /* White balance */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_WB_MODE,
+          "White balance mode property",
+          "White balance affects the color temperature of the photo",
+          GST_TYPE_PHOTOGRAPHY_WHITE_BALANCE_MODE,
+          GST_PHOTOGRAPHY_WB_MODE_AUTO,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Color tone */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_COLOR_TONE,
+          "Color tone mode property",
+          "Color tone setting changes color shading in the photo",
+          GST_TYPE_PHOTOGRAPHY_COLOR_TONE_MODE,
+          GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Scene mode */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_SCENE_MODE,
+          "Scene mode property",
+          "Scene mode works as a preset for different photo shooting mode settings",
+          GST_TYPE_PHOTOGRAPHY_SCENE_MODE,
+          GST_PHOTOGRAPHY_SCENE_MODE_AUTO,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Flash mode */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FLASH_MODE,
+          "Flash mode property",
+          "Flash mode defines how the flash light should be used",
+          GST_TYPE_PHOTOGRAPHY_FLASH_MODE,
+          GST_PHOTOGRAPHY_FLASH_MODE_AUTO,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Flicker reduction mode */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FLICKER_MODE,
+          "Flicker reduction mode property",
+          "Flicker reduction mode defines a line frequency for flickering prevention",
+          GST_TYPE_PHOTOGRAPHY_FLICKER_REDUCTION_MODE,
+          GST_PHOTOGRAPHY_FLICKER_REDUCTION_OFF,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Focus mode */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FOCUS_MODE,
+          "Focus mode property",
+          "Focus mode defines the range of focal lengths to use in autofocus search",
+          GST_TYPE_PHOTOGRAPHY_FOCUS_MODE,
+          GST_PHOTOGRAPHY_FOCUS_MODE_AUTO,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Capabilities */
+  g_object_interface_install_property (g_class,
+      g_param_spec_ulong (GST_PHOTOGRAPHY_PROP_CAPABILITIES,
+          "Photo capabilities bitmask",
+          "Tells the photo capabilities of the device",
+          0, G_MAXULONG, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /* EV_compensation */
+  g_object_interface_install_property (g_class,
+      g_param_spec_float (GST_PHOTOGRAPHY_PROP_EV_COMP,
+          "EV compensation property",
+          "EV compensation affects the brightness of the image",
+          -2.5, 2.5, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* ISO value */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_ISO_SPEED,
+          "ISO speed property",
+          "ISO speed defines the light sensitivity (0 = auto)",
+          0, 6400, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Aperture */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_APERTURE,
+          "Aperture property",
+          "Aperture defines the size of lens opening  (0 = auto)",
+          0, G_MAXUINT8, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Exposure */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_EXPOSURE_TIME,
+          "Exposure time in milliseconds",
+          "Exposure time defines how long the shutter will stay open (0 = auto)",
+          0, G_MAXUINT32, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:image-capture-supported-caps:
+   *
+   * Query caps that describe supported formats for image capture. Sometimes
+   * element may support different formats for image capture than for video
+   * streaming.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_boxed (GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS,
+          "Image capture supported caps",
+          "Caps describing supported image capture formats", GST_TYPE_CAPS,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:image-preview-supported-caps:
+   *
+   * Query caps that describe supported formats for preview image. Sometimes
+   * element may support different formats for preview image than for video
+   * streaming.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_boxed (GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS,
+          "Image preview supported caps",
+          "Caps describing supported image preview formats", GST_TYPE_CAPS,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /* Zoom */
+  g_object_interface_install_property (g_class,
+      g_param_spec_float (GST_PHOTOGRAPHY_PROP_ZOOM,
+          "Zoom property",
+          "How much the resulted image will be zoomed",
+          1.0f, 10.0f, 1.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:color-temperature:
+   *
+   * Color temperature parameter for manual white balance.
+   * Control color temperature in Kelvin units.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_COLOR_TEMPERATURE,
+          "Color temperature in Kelvin units",
+          "Color temperature in Kelvin units for manual white balance",
+          0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:white-point:
+   *
+   * White point parameter for manual white balance.
+   * Describes the color "white" as raw values.
+   *
+   * FIXME: check and document correct representation for white point
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_value_array (GST_PHOTOGRAPHY_PROP_WHITE_POINT,
+          "White point",
+          "Describe color white as raw values",
+          g_param_spec_uint ("raw-value", "Raw value",
+              "Raw value", 0, G_MAXUINT, 0,
+              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS),
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:analog-gain:
+   *
+   * Linear multiplicative value how much amplification is applied to the signal
+   * before A-D conversion.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_float (GST_PHOTOGRAPHY_PROP_ANALOG_GAIN,
+          "Analog gain applied to the sensor",
+          "Analog gain applied to the sensor",
+          1.0f, G_MAXFLOAT, 1.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:lens-focus:
+   *
+   * Manual changing of lens focus in diopter units.
+   * Intended use with GST_PHOTOGRAPHY_FOCUS_MODE_MANUAL focus mode, otherwise
+   * to be ignored.
+   *
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_float (GST_PHOTOGRAPHY_PROP_LENS_FOCUS,
+          "Manual lens focus",
+          "Focus point in diopter units",
+          0.0f, G_MAXFLOAT, 0.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:min-exposure-time:
+   *
+   * Minimum exposure time for automatic exposure mode.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_MIN_EXPOSURE_TIME,
+          "Minimum exposure time",
+          "Minimum exposure time for automatic exposure mode",
+          0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstPhotography:max-exposure-time:
+   *
+   * Maximum exposure time for automatic exposure mode.
+   */
+  g_object_interface_install_property (g_class,
+      g_param_spec_uint (GST_PHOTOGRAPHY_PROP_MAX_EXPOSURE_TIME,
+          "Maximum exposure time",
+          "Maximum exposure time for automatic exposure mode",
+          0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Noise Reduction, Bayer an YCC noise reduction are enabled by default */
+  g_object_interface_install_property (g_class,
+      g_param_spec_flags (GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION,
+          "Noise Reduction settings",
+          "Which noise reduction modes are enabled (0 = disabled)",
+          GST_TYPE_PHOTOGRAPHY_NOISE_REDUCTION,
+          0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /* Exposure mode */
+  g_object_interface_install_property (g_class,
+      g_param_spec_enum (GST_PHOTOGRAPHY_PROP_EXPOSURE_MODE,
+          "Exposure mode property",
+          "Exposure mode to either automatic or manual",
+          GST_TYPE_PHOTOGRAPHY_EXPOSURE_MODE,
+          0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
Index: b/gst-libs/gst/interfaces/photography.h
===================================================================
--- /dev/null
+++ b/gst-libs/gst/interfaces/photography.h
@@ -0,0 +1,685 @@
+/* GStreamer
+ *
+ * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
+ *
+ * photography.h: photography interface for digital imaging
+ *
+ * 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_PHOTOGRAPHY_H__
+#define __GST_PHOTOGRAPHY_H__
+
+#ifndef GST_USE_UNSTABLE_API
+#warning "The GstPhotography interface is unstable API and may change in future."
+#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." 
+#endif
+
+#include <gst/gst.h>
+#include <gst/interfaces/photography-prelude.h>
+#include <gst/interfaces/photography-enumtypes.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PHOTOGRAPHY \
+  (gst_photography_get_type ())
+#define GST_PHOTOGRAPHY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PHOTOGRAPHY, GstPhotography))
+#define GST_IS_PHOTOGRAPHY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PHOTOGRAPHY))
+#define GST_PHOTOGRAPHY_GET_INTERFACE(inst) \
+  (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_PHOTOGRAPHY, GstPhotographyInterface))
+
+
+/**
+ * GST_PHOTOGRAPHY_AUTOFOCUS_DONE:
+ *
+ * Name of custom GstMessage that will be posted to #GstBus when autofocusing
+ * is complete.
+ * This message contains following fields:
+ *
+ * * `status` (#GstPhotographyFocusStatus): Tells if focusing succeeded or failed.
+ *
+ * * `focus-window-rows` (#G_TYPE_INT): Tells number of focus matrix rows.
+ *
+ * * `focus-window-columns` (#G_TYPE_INT): Tells number of focus matrix columns.
+ *
+ * * `focus-window-mask` (#G_TYPE_INT): Bitmask containing rows x columns bits
+ *   which mark the focus points in the focus matrix. Lowest bit (LSB) always
+ *   represents the top-left corner of the focus matrix. This field is only valid
+ *   when focusing status is SUCCESS.
+ */
+#define GST_PHOTOGRAPHY_AUTOFOCUS_DONE "autofocus-done"
+
+/**
+ * GST_PHOTOGRAPHY_SHAKE_RISK:
+ *
+ * Name of custom GstMessage that is posted to #GstBus during autofocusing
+ * process. It is posted if there is change in the risk of captured image
+ * becoming "shaken" due to camera movement and too long exposure time.
+ *
+ * This message contains following fields:
+ *
+ * * `status` (#GstPhotographyShakeRisk): Tells risk level of capturing shaken image.
+ */
+#define GST_PHOTOGRAPHY_SHAKE_RISK "shake-risk"
+
+/* Maximum white point values used in #GstPhotographySettings */
+#define MAX_WHITE_POINT_VALUES 4
+
+/* Interface property names */
+#define GST_PHOTOGRAPHY_PROP_WB_MODE            "white-balance-mode"
+#define GST_PHOTOGRAPHY_PROP_COLOR_TONE         "color-tone-mode"
+#define GST_PHOTOGRAPHY_PROP_SCENE_MODE         "scene-mode"
+#define GST_PHOTOGRAPHY_PROP_FLASH_MODE         "flash-mode"
+#define GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION    "noise-reduction"
+#define GST_PHOTOGRAPHY_PROP_FOCUS_STATUS       "focus-status"
+#define GST_PHOTOGRAPHY_PROP_CAPABILITIES       "capabilities"
+#define GST_PHOTOGRAPHY_PROP_SHAKE_RISK         "shake-risk"
+#define GST_PHOTOGRAPHY_PROP_EV_COMP            "ev-compensation"
+#define GST_PHOTOGRAPHY_PROP_ISO_SPEED          "iso-speed"
+#define GST_PHOTOGRAPHY_PROP_APERTURE           "aperture"
+#define GST_PHOTOGRAPHY_PROP_EXPOSURE_TIME      "exposure-time"
+#define GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS \
+                                                "image-capture-supported-caps"
+#define GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS \
+                                                "image-preview-supported-caps"
+#define GST_PHOTOGRAPHY_PROP_FLICKER_MODE       "flicker-mode"
+#define GST_PHOTOGRAPHY_PROP_FOCUS_MODE         "focus-mode"
+#define GST_PHOTOGRAPHY_PROP_ZOOM               "zoom"
+#define GST_PHOTOGRAPHY_PROP_COLOR_TEMPERATURE  "color-temperature"
+#define GST_PHOTOGRAPHY_PROP_WHITE_POINT        "white-point"
+#define GST_PHOTOGRAPHY_PROP_ANALOG_GAIN        "analog-gain"
+#define GST_PHOTOGRAPHY_PROP_EXPOSURE_MODE      "exposure-mode"
+#define GST_PHOTOGRAPHY_PROP_LENS_FOCUS         "lens-focus"
+#define GST_PHOTOGRAPHY_PROP_MIN_EXPOSURE_TIME  "min-exposure-time"
+#define GST_PHOTOGRAPHY_PROP_MAX_EXPOSURE_TIME  "max-exposure-time"
+
+/**
+ * GstPhotography:
+ *
+ * Opaque #GstPhotography data structure.
+ */
+typedef struct _GstPhotography GstPhotography;
+
+/**
+ * GstPhotographyNoiseReduction:
+ * @GST_PHOTOGRAPHY_NOISE_REDUCTION_BAYER: Adaptive noise reduction on Bayer
+ * format
+ * @GST_PHOTOGRAPHY_NOISE_REDUCTION_YCC: reduces the noise on Y and 2-chroma
+ * images.
+ * @GST_PHOTOGRAPHY_NOISE_REDUCTION_TEMPORAL: Multi-frame adaptive NR,
+ * provided for the video mode
+ * @GST_PHOTOGRAPHY_NOISE_REDUCTION_FIXED: Fixed Pattern Noise refers to noise
+ * that does not change between frames. The noise is removed from the sensor
+ * image, by subtracting a previously-captured black image in memory.
+ * @GST_PHOTOGRAPHY_NOISE_REDUCTION_EXTRA: Extra Noise Reduction. In the case
+ * of high-ISO capturing, some noise remains after YCC NR. XNR reduces this
+ * remaining noise.
+ *
+ * Noise Reduction features of a photography capture or filter element.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_NOISE_REDUCTION_BAYER    = (1 << 0),
+  GST_PHOTOGRAPHY_NOISE_REDUCTION_YCC      = (1 << 1),
+  GST_PHOTOGRAPHY_NOISE_REDUCTION_TEMPORAL = (1 << 2),
+  GST_PHOTOGRAPHY_NOISE_REDUCTION_FIXED    = (1 << 3),
+  GST_PHOTOGRAPHY_NOISE_REDUCTION_EXTRA    = (1 << 4)
+} GstPhotographyNoiseReduction;
+
+/**
+ * GstPhotographyWhiteBalanceMode:
+ * @GST_PHOTOGRAPHY_WB_MODE_AUTO: Choose white balance mode automatically
+ * @GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT: Mode for daylight conditions
+ * @GST_PHOTOGRAPHY_WB_MODE_CLOUDY: Mode for cloudy conditions
+ * @GST_PHOTOGRAPHY_WB_MODE_SUNSET: Mode for sunset conditions
+ * @GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN: Mode for tungsten lighting
+ * @GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT: Mode for fluorescent lighting
+ * @GST_PHOTOGRAPHY_WB_MODE_MANUAL: Disable automatic white balance adjustment
+ * and keep current values.
+ * @GST_PHOTOGRAPHY_WB_MODE_WARM_FLUORESCENT: Mode for warm fluorescent lighting (Since: 1.2)
+ * @GST_PHOTOGRAPHY_WB_MODE_SHADE: Mode for shade lighting (Since: 1.2)
+ *
+ * Modes for white balance control.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_WB_MODE_AUTO = 0,
+  GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT,
+  GST_PHOTOGRAPHY_WB_MODE_CLOUDY,
+  GST_PHOTOGRAPHY_WB_MODE_SUNSET,
+  GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN,
+  GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT,
+  GST_PHOTOGRAPHY_WB_MODE_MANUAL,
+  GST_PHOTOGRAPHY_WB_MODE_WARM_FLUORESCENT,
+  GST_PHOTOGRAPHY_WB_MODE_SHADE
+} GstPhotographyWhiteBalanceMode;
+
+/**
+ * GstPhotographyColorToneMode:
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL: No effects
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_SEPIA: Sepia
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_NEGATIVE: Negative
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_GRAYSCALE: Grayscale
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_NATURAL: Natural
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_VIVID: Vivid
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_COLORSWAP: Colorswap
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_SOLARIZE: Solarize
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_OUT_OF_FOCUS: Out of focus
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKY_BLUE: Sky blue
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_GRASS_GREEN: Grass green
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKIN_WHITEN: Skin whiten
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_POSTERIZE: Posterize (Since: 1.2)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_WHITEBOARD: Whiteboard (Since: 1.2)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_BLACKBOARD: Blackboard (Since: 1.2)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_AQUA: Aqua (Since: 1.2)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_EMBOSS: Emboss (Since: 1.18)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKETCH: Sketch (Since: 1.18)
+ * @GST_PHOTOGRAPHY_COLOR_TONE_MODE_NEON: Neon (Since: 1.18)
+ *
+ *
+ * Modes for special color effects.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL = 0,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_SEPIA,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_NEGATIVE,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_GRAYSCALE,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_NATURAL,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_VIVID,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_COLORSWAP,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_SOLARIZE,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_OUT_OF_FOCUS,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKY_BLUE,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_GRASS_GREEN,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKIN_WHITEN,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_POSTERIZE,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_WHITEBOARD,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_BLACKBOARD,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_AQUA,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_EMBOSS,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_SKETCH,
+  GST_PHOTOGRAPHY_COLOR_TONE_MODE_NEON
+} GstPhotographyColorToneMode;
+
+/**
+ * GstPhotographySceneMode:
+ * @GST_PHOTOGRAPHY_SCENE_MODE_MANUAL: Set individual options manually
+ * @GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP: Mode for close objects
+ * @GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT: Mode for portraits
+ * @GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE: Mode for landscapes
+ * @GST_PHOTOGRAPHY_SCENE_MODE_SPORT: Mode for scene with fast motion
+ * @GST_PHOTOGRAPHY_SCENE_MODE_NIGHT: Mode for night conditions
+ * @GST_PHOTOGRAPHY_SCENE_MODE_AUTO: Choose scene mode automatically
+ * @GST_PHOTOGRAPHY_SCENE_MODE_ACTION: Take photos of fast moving
+ *     objects (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT: Take people pictures
+ *     at night (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_THEATRE: Take photos in a theater (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_BEACH: Take pictures on the beach (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_SNOW: Take pictures on the snow (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_SUNSET: Take sunset photos (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO: Avoid blurry pictures
+ *     (for example, due to hand shake) (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS: For shooting firework
+ *     displays (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_PARTY: Take indoor low-light shot (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT: Capture the naturally warm color
+ *     of scenes lit by candles (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_BARCODE: Applications are looking for
+ *     a barcode (Since: 1.2)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_BACKLIGHT: Backlit photos (Since: 1.18)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_FLOWERS: For shooting flowers (Since: 1.18)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_AR: Specific for Augmented Reality (Since: 1.18)
+ * @GST_PHOTOGRAPHY_SCENE_MODE_HDR: High Dynamic Range photography (Since: 1.18)
+ *
+ * Each mode contains preset #GstPhotography options in order to produce
+ * good capturing result in certain scene.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_SCENE_MODE_MANUAL = 0,
+  GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP,
+  GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT,
+  GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE,
+  GST_PHOTOGRAPHY_SCENE_MODE_SPORT,
+  GST_PHOTOGRAPHY_SCENE_MODE_NIGHT,
+  GST_PHOTOGRAPHY_SCENE_MODE_AUTO,
+  GST_PHOTOGRAPHY_SCENE_MODE_ACTION,
+  GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT,
+  GST_PHOTOGRAPHY_SCENE_MODE_THEATRE,
+  GST_PHOTOGRAPHY_SCENE_MODE_BEACH,
+  GST_PHOTOGRAPHY_SCENE_MODE_SNOW,
+  GST_PHOTOGRAPHY_SCENE_MODE_SUNSET,
+  GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO,
+  GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS,
+  GST_PHOTOGRAPHY_SCENE_MODE_PARTY,
+  GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT,
+  GST_PHOTOGRAPHY_SCENE_MODE_BARCODE,
+  GST_PHOTOGRAPHY_SCENE_MODE_BACKLIGHT,
+  GST_PHOTOGRAPHY_SCENE_MODE_FLOWERS,
+  GST_PHOTOGRAPHY_SCENE_MODE_AR,
+  GST_PHOTOGRAPHY_SCENE_MODE_HDR
+} GstPhotographySceneMode;
+
+/**
+ * GstPhotographyFlashMode:
+ * @GST_PHOTOGRAPHY_FLASH_MODE_AUTO: Fire flash automatically according to
+ * lighting conditions.
+ * @GST_PHOTOGRAPHY_FLASH_MODE_OFF: Never fire flash
+ * @GST_PHOTOGRAPHY_FLASH_MODE_ON: Always fire flash
+ * @GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN: Fill in flash
+ * @GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE: Flash mode for reducing chance of
+ * capturing red eyes
+ *
+ * Modes for flash control.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_FLASH_MODE_AUTO = 0,
+  GST_PHOTOGRAPHY_FLASH_MODE_OFF,
+  GST_PHOTOGRAPHY_FLASH_MODE_ON,
+  GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN,
+  GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE
+} GstPhotographyFlashMode;
+
+/**
+ * GstPhotographyFocusStatus:
+ * @GST_PHOTOGRAPHY_FOCUS_STATUS_NONE: No status available
+ * @GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING: Focusing is ongoing
+ * @GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL: Focusing failed
+ * @GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS: Focusing succeeded
+ *
+ * Status of the focusing operation, used in #GST_PHOTOGRAPHY_AUTOFOCUS_DONE
+ * message.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_FOCUS_STATUS_NONE = 0,
+  GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING,
+  GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL,
+  GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS
+} GstPhotographyFocusStatus;
+
+/**
+ * GstPhotographyCaps:
+ *
+ * Bitmask that indicates which #GstPhotography interface features an instance
+ * supports.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_CAPS_NONE              = (0 << 0),
+  GST_PHOTOGRAPHY_CAPS_EV_COMP           = (1 << 0),
+  GST_PHOTOGRAPHY_CAPS_ISO_SPEED         = (1 << 1),
+  GST_PHOTOGRAPHY_CAPS_WB_MODE           = (1 << 2),
+  GST_PHOTOGRAPHY_CAPS_TONE              = (1 << 3),
+  GST_PHOTOGRAPHY_CAPS_SCENE             = (1 << 4),
+  GST_PHOTOGRAPHY_CAPS_FLASH             = (1 << 5),
+  GST_PHOTOGRAPHY_CAPS_ZOOM              = (1 << 6),
+  GST_PHOTOGRAPHY_CAPS_FOCUS             = (1 << 7),
+  GST_PHOTOGRAPHY_CAPS_APERTURE          = (1 << 8),
+  GST_PHOTOGRAPHY_CAPS_EXPOSURE          = (1 << 9),
+  GST_PHOTOGRAPHY_CAPS_SHAKE             = (1 << 10),
+  GST_PHOTOGRAPHY_CAPS_WHITE_BALANCE     = (1 << 11),
+  GST_PHOTOGRAPHY_CAPS_NOISE_REDUCTION   = (1 << 12),
+  GST_PHOTOGRAPHY_CAPS_FLICKER_REDUCTION = (1 << 13),
+  GST_PHOTOGRAPHY_CAPS_ALL               = (~0)
+} GstPhotographyCaps;
+
+/**
+ * GstPhotographyShakeRisk:
+ * @GST_PHOTOGRAPHY_SHAKE_RISK_LOW: Low risk
+ * @GST_PHOTOGRAPHY_SHAKE_RISK_MEDIUM: Medium risk
+ * @GST_PHOTOGRAPHY_SHAKE_RISK_HIGH: High risk
+ *
+ * Risk level of captured image becoming "shaken" due to camera movement and
+ * too long exposure time. Used in #GST_PHOTOGRAPHY_SHAKE_RISK #GstMessage.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_SHAKE_RISK_LOW = 0,
+  GST_PHOTOGRAPHY_SHAKE_RISK_MEDIUM,
+  GST_PHOTOGRAPHY_SHAKE_RISK_HIGH,
+} GstPhotographyShakeRisk;
+
+/**
+ * GstPhotographyFlickerReductionMode:
+ * @GST_PHOTOGRAPHY_FLICKER_REDUCTION_OFF: Disable flicker reduction
+ * @GST_PHOTOGRAPHY_FLICKER_REDUCTION_50HZ: 50Hz flicker reduction
+ * @GST_PHOTOGRAPHY_FLICKER_REDUCTION_60HZ: 60Hz flicker reduction
+ * @GST_PHOTOGRAPHY_FLICKER_REDUCTION_AUTO: Choose mode automatically
+ *
+ * Reduce flicker in video caused by light source fluctuation.
+ */
+typedef enum
+{
+  GST_PHOTOGRAPHY_FLICKER_REDUCTION_OFF = 0,
+  GST_PHOTOGRAPHY_FLICKER_REDUCTION_50HZ,
+  GST_PHOTOGRAPHY_FLICKER_REDUCTION_60HZ,
+  GST_PHOTOGRAPHY_FLICKER_REDUCTION_AUTO,
+} GstPhotographyFlickerReductionMode;
+
+/**
+ * GstPhotographyFocusMode:
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_AUTO: Choose focus mode automatically
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_MACRO: Mode for focusing objects close to lens
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_PORTRAIT: Mode for portraits
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY: Mode for landscapes and far away objects
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL: Mode for maximum depth of field, keeping
+ * focus acceptable both in infinify and as close objects as possible
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_EXTENDED: Extended focus mode
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL: Continuous autofocus mode
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_EXTENDED: Extended continuous
+ * autofocus mode
+ * @GST_PHOTOGRAPHY_FOCUS_MODE_MANUAL: Disable automatic focusing
+ * and keep current value. #GstPhotography:lens-focus property can
+ * be used to change focus manually.
+ *
+ * Choose mode for focusing algorithm.
+ */
+typedef enum {
+    GST_PHOTOGRAPHY_FOCUS_MODE_AUTO = 0,
+    GST_PHOTOGRAPHY_FOCUS_MODE_MACRO,
+    GST_PHOTOGRAPHY_FOCUS_MODE_PORTRAIT,
+    GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY,
+    GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL,
+    GST_PHOTOGRAPHY_FOCUS_MODE_EXTENDED,
+    GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL,
+    GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_EXTENDED,
+    GST_PHOTOGRAPHY_FOCUS_MODE_MANUAL
+} GstPhotographyFocusMode;
+
+/**
+ * GstPhotographyExposureMode:
+ * @GST_PHOTOGRAPHY_EXPOSURE_MODE_AUTO: Adjust exposure automatically
+ * @GST_PHOTOGRAPHY_EXPOSURE_MODE_MANUAL: Disable automatic exposure adjustment
+ * and keep current values.
+ *
+ */
+typedef enum {
+    GST_PHOTOGRAPHY_EXPOSURE_MODE_AUTO = 0,
+    GST_PHOTOGRAPHY_EXPOSURE_MODE_MANUAL
+} GstPhotographyExposureMode;
+
+/**
+ * GstPhotographySettings:
+ *
+ * Structure containing all #GstPhotography settings, used to set all
+ * settings in one call with @gst_photography_set_config().
+ */
+typedef struct
+{
+  GstPhotographyWhiteBalanceMode wb_mode;
+  GstPhotographyColorToneMode tone_mode;
+  GstPhotographySceneMode scene_mode;
+  GstPhotographyFlashMode flash_mode;
+  guint32 exposure_time;
+  guint aperture;
+  gfloat ev_compensation;
+  guint iso_speed;
+  gfloat zoom;
+  GstPhotographyFlickerReductionMode flicker_mode;
+  GstPhotographyFocusMode focus_mode;
+  GstPhotographyNoiseReduction noise_reduction;
+  GstPhotographyExposureMode exposure_mode;
+  guint color_temperature;
+  guint white_point[MAX_WHITE_POINT_VALUES];
+  gfloat analog_gain;
+  gfloat lens_focus;
+  guint min_exposure_time;
+  guint max_exposure_time;
+  /* FIXME: add padding? */
+} GstPhotographySettings;
+
+/**
+ * GstPhotographyCapturePrepared:
+ * @data: user data that has been given, when registering the callback
+ * @configured_caps: #GstCaps defining the configured capture format.
+ *     Ownership of these caps stays in the element.
+ *
+ * This callback will be called when the element has finished preparations
+ * and is ready for image capture. The next buffer that element produces
+ * will be of @configured_caps format, so this callback allows the application
+ * to e.g. reconfigure capsfilters in pipeline if any.
+ */
+typedef void (*GstPhotographyCapturePrepared) (gpointer data, const GstCaps *configured_caps);
+
+/**
+ * GstPhotographyInterface:
+ * @parent: parent interface type.
+ * @get_ev_compensation: vmethod to get ev exposure compensation value
+ * @get_iso_speed: vmethod to get iso speed (light sensitivity) value
+ * @get_aperture: vmethod to get aperture value
+ * @get_exposure: vmethod to get exposure time value
+ * @get_white_balance_mode: vmethod to get white balance mode value
+ * @get_color_tone_mode: vmethod to get color tone mode value
+ * @get_scene_mode: vmethod to get scene mode value
+ * @get_flash_mode: vmethod to get flash mode value
+ * @get_noise_reduction: vmethod to get noise reduction mode value
+ * @get_zoom: vmethod to get zoom factor value
+ * @set_ev_compensation: vmethod to set ev exposure compensation value
+ * @set_iso_speed: vmethod to set iso speed (light sensitivity) value
+ * @set_aperture: vmethod to set aperture value
+ * @set_exposure: vmethod to set exposure time value
+ * @set_white_balance_mode: vmethod to set white balance mode value
+ * @set_color_tone_mode: vmethod to set color tone mode value
+ * @set_scene_mode: vmethod to set scene mode value
+ * @set_flash_mode: vmethod to set flash mode value
+ * @set_noise_reduction: vmethod to set noise reduction mode value
+ * @set_zoom: vmethod to set zoom factor value
+ * @get_capabilities: vmethod to get supported capabilities of the interface
+ * @prepare_for_capture: vmethod to tell the element to prepare for capturing
+ * @set_autofocus: vmethod to set autofocus on/off
+ * @set_config: vmethod to set all configuration parameters at once
+ * @get_config: vmethod to get all configuration parameters at once
+ * @get_image_capture_supported_caps: vmethod to get caps describing supported image capture formats
+ * @set_exposure_mode: vmethod to set exposure mode (Since: 1.18)
+ * @get_exposure_mode: vmethod to get exposure mode (Since: 1.18)
+ * @set_analog_gain: vmethod to set analog gain (Since: 1.18)
+ * @get_analog_gain: vmethod to get analog gain (Since: 1.18)
+ * @set_lens_focus: vmethod to set lens focus (Since: 1.18)
+ * @get_lens_focus: vmethod to get lens focus (Since: 1.18)
+ * @set_color_temperature: vmethod to set color temperature (Since: 1.18)
+ * @get_color_temperature: vmethod to get color temperature (Since: 1.18)
+ * @set_min_exposure_time: vmethod to set min exposure time (Since: 1.18)
+ * @get_min_exposure_time: vmethod to get min exposure time (Since: 1.18)
+ * @set_max_exposure_time: vmethod to set max exposure time (Since: 1.18)
+ * @get_max_exposure_time: vmethod to get max exposure time (Since: 1.18)
+ *
+ * #GstPhotographyInterface interface.
+ */
+typedef struct _GstPhotographyInterface
+{
+  GTypeInterface parent;
+
+  /* virtual functions */
+  gboolean (*get_ev_compensation)    (GstPhotography * photo, gfloat * ev_comp);
+  gboolean (*get_iso_speed)          (GstPhotography * photo, guint * iso_speed);
+  gboolean (*get_aperture)           (GstPhotography * photo, guint * aperture);
+  gboolean (*get_exposure)           (GstPhotography * photo, guint32 * exposure);
+  gboolean (*get_white_balance_mode) (GstPhotography * photo, GstPhotographyWhiteBalanceMode * wb_mode);
+  gboolean (*get_color_tone_mode)    (GstPhotography * photo, GstPhotographyColorToneMode * tone_mode);
+  gboolean (*get_scene_mode)         (GstPhotography * photo, GstPhotographySceneMode * scene_mode);
+  gboolean (*get_flash_mode)         (GstPhotography * photo, GstPhotographyFlashMode * flash_mode);
+  gboolean (*get_zoom)               (GstPhotography * photo, gfloat * zoom);
+  gboolean (*get_flicker_mode)       (GstPhotography * photo, GstPhotographyFlickerReductionMode * flicker_mode);
+  gboolean (*get_focus_mode)         (GstPhotography * photo, GstPhotographyFocusMode * focus_mode);
+
+  gboolean (*set_ev_compensation)    (GstPhotography * photo, gfloat ev_comp);
+  gboolean (*set_iso_speed)          (GstPhotography * photo, guint iso_speed);
+  gboolean (*set_aperture)           (GstPhotography * photo, guint aperture);
+  gboolean (*set_exposure)           (GstPhotography * photo, guint32 exposure);
+  gboolean (*set_white_balance_mode) (GstPhotography * photo, GstPhotographyWhiteBalanceMode wb_mode);
+  gboolean (*set_color_tone_mode)    (GstPhotography * photo, GstPhotographyColorToneMode tone_mode);
+  gboolean (*set_scene_mode)         (GstPhotography * photo, GstPhotographySceneMode scene_mode);
+  gboolean (*set_flash_mode)         (GstPhotography * photo, GstPhotographyFlashMode flash_mode);
+  gboolean (*set_zoom)               (GstPhotography * photo, gfloat zoom);
+  gboolean (*set_flicker_mode)       (GstPhotography * photo, GstPhotographyFlickerReductionMode flicker_mode);
+  gboolean (*set_focus_mode)         (GstPhotography * photo, GstPhotographyFocusMode focus_mode);
+
+  GstPhotographyCaps (*get_capabilities) (GstPhotography * photo);
+
+  gboolean (*prepare_for_capture)    (GstPhotography * photo, GstPhotographyCapturePrepared func, GstCaps *capture_caps, gpointer user_data);
+
+  void     (*set_autofocus)          (GstPhotography * photo, gboolean on);
+
+  gboolean (*set_config)             (GstPhotography * photo, GstPhotographySettings * config);
+  gboolean (*get_config)             (GstPhotography * photo, GstPhotographySettings * config);
+
+  gboolean (*get_noise_reduction)    (GstPhotography * photo, GstPhotographyNoiseReduction * noise_reduction);
+  gboolean (*set_noise_reduction)    (GstPhotography * photo, GstPhotographyNoiseReduction noise_reduction);
+
+  gboolean (*set_exposure_mode) (GstPhotography * photo, GstPhotographyExposureMode exposure_mode);
+  gboolean (*get_exposure_mode) (GstPhotography * photo, GstPhotographyExposureMode * exposure_mode);
+  gboolean (*set_analog_gain) (GstPhotography * photo, gfloat analog_gain);
+  gboolean (*get_analog_gain) (GstPhotography * photo, gfloat * analog_gain);
+  gboolean (*set_lens_focus) (GstPhotography * photo, gfloat lens_focus);
+  gboolean (*get_lens_focus) (GstPhotography * photo, gfloat * lens_focus);
+  gboolean (*set_color_temperature) (GstPhotography * photo, guint color_temperature);
+  gboolean (*get_color_temperature) (GstPhotography * photo, guint * color_temperature);
+  gboolean (*set_min_exposure_time) (GstPhotography * photo, guint min_exposure_time);
+  gboolean (*get_min_exposure_time) (GstPhotography * photo, guint * min_exposure_time);
+  gboolean (*set_max_exposure_time) (GstPhotography * photo, guint max_exposure_time);
+  gboolean (*get_max_exposure_time) (GstPhotography * photo, guint * max_exposure_time);
+
+  /* FIXME: remove padding, not needed for interfaces */
+  /*< private > */
+  gpointer _gst_reserved[GST_PADDING];
+} GstPhotographyInterface;
+
+GST_PHOTOGRAPHY_API
+GType gst_photography_get_type (void);
+
+/* virtual class function wrappers */
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_ev_compensation (GstPhotography * photo,
+                                              gfloat * ev_comp);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_iso_speed       (GstPhotography * photo,
+                                              guint * iso_speed);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_aperture        (GstPhotography * photo,
+                                              guint * aperture);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_exposure        (GstPhotography * photo,
+                                              guint32 * exposure);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_white_balance_mode (GstPhotography * photo,
+                                                 GstPhotographyWhiteBalanceMode * wb_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_color_tone_mode (GstPhotography * photo,
+                                              GstPhotographyColorToneMode * tone_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_scene_mode      (GstPhotography * photo,
+                                              GstPhotographySceneMode * scene_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_flash_mode      (GstPhotography * photo,
+                                              GstPhotographyFlashMode * flash_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_noise_reduction (GstPhotography * photo,
+                                              GstPhotographyNoiseReduction * noise_reduction);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_zoom            (GstPhotography * photo, gfloat * zoom);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_flicker_mode    (GstPhotography * photo,
+                                              GstPhotographyFlickerReductionMode * mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_focus_mode      (GstPhotography * photo,
+                                              GstPhotographyFocusMode * mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_ev_compensation (GstPhotography * photo,
+                                              gfloat ev_comp);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_iso_speed       (GstPhotography * photo,
+                                              guint iso_speed);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_aperture        (GstPhotography * photo, guint aperture);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_exposure        (GstPhotography * photo, guint exposure);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_white_balance_mode (GstPhotography * photo,
+                                                 GstPhotographyWhiteBalanceMode wb_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_color_tone_mode (GstPhotography * photo,
+                                              GstPhotographyColorToneMode tone_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_scene_mode      (GstPhotography * photo,
+                                              GstPhotographySceneMode scene_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_flash_mode      (GstPhotography * photo,
+                                              GstPhotographyFlashMode flash_mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_noise_reduction (GstPhotography * photo,
+                                              GstPhotographyNoiseReduction noise_reduction);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_zoom            (GstPhotography * photo, gfloat zoom);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_flicker_mode    (GstPhotography * photo,
+                                              GstPhotographyFlickerReductionMode mode);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_focus_mode      (GstPhotography * photo,
+                                              GstPhotographyFocusMode mode);
+
+GST_PHOTOGRAPHY_API
+GstPhotographyCaps gst_photography_get_capabilities (GstPhotography * photo);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_prepare_for_capture (GstPhotography * photo,
+                                              GstPhotographyCapturePrepared func,
+                                              GstCaps *capture_caps,
+                                              gpointer user_data);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_set_config    (GstPhotography         * photo,
+                                        GstPhotographySettings * config);
+
+GST_PHOTOGRAPHY_API
+gboolean gst_photography_get_config    (GstPhotography         * photo,
+                                        GstPhotographySettings * config);
+
+GST_PHOTOGRAPHY_API
+void     gst_photography_set_autofocus (GstPhotography * photo, gboolean on);
+
+G_END_DECLS
+
+#endif /* __GST_PHOTOGRAPHY_H__ */
Index: b/gst-libs/meson.build
===================================================================
--- /dev/null
+++ b/gst-libs/meson.build
@@ -0,0 +1 @@
+subdir('gst')
Index: b/gst-libs/gst/meson.build
===================================================================
--- /dev/null
+++ b/gst-libs/gst/meson.build
@@ -0,0 +1,2 @@
+subdir('basecamerabinsrc')
+subdir('interfaces')
