camerabin: preview image sending optimization
authorLasse Laukkanen <ext-lasse.2.laukkanen@nokia.com>
Wed, 27 May 2009 08:33:01 +0000 (11:33 +0300)
committerStefan Kost <ensonic@users.sf.net>
Fri, 5 Jun 2009 12:51:31 +0000 (15:51 +0300)
 * decouple image capturing from image post-processing and encoding
 * post image-captured message after image is captured
 * post preview-image message with snapshot of captured image

gst/camerabin/Makefile.am
gst/camerabin/camerabinimage.c
gst/camerabin/camerabinimage.h
gst/camerabin/camerabinpreview.c [new file with mode: 0644]
gst/camerabin/camerabinpreview.h [new file with mode: 0644]
gst/camerabin/gstcamerabin.c
gst/camerabin/gstcamerabin.h
tests/check/elements/camerabin.c

index 060de0c4a2e0d85badb53d41b03fa326c032e049..4f3822436709595e54d2d6b83f7db8e54a385d71 100644 (file)
@@ -18,6 +18,7 @@ libgstcamerabin_la_SOURCES = gstcamerabin.c             \
                          camerabinimage.c           \
                          camerabinvideo.c           \
                          camerabingeneral.c         \
+                         camerabinpreview.c         \
                          gstcamerabinphotography.c
 
 nodist_libgstcamerabin_la_SOURCES = $(built_sources)
@@ -36,4 +37,5 @@ noinst_HEADERS = gstcamerabin.h             \
                 camerabinimage.h           \
                 camerabinvideo.h           \
                 camerabingeneral.h         \
+                camerabinpreview.h         \
                 gstcamerabinphotography.h 
index 99bf20b6c1bbf480617867b21889222b0cc7015d..338d2e3d9794f5d507b4a10baa96e3916df44a37 100644 (file)
  * <informalexample>
  * <programlisting>
  *-----------------------------------------------------------------------------
- *                      (src0) -> queue ->
- * -> [post proc] -> tee <
- *                      (src1) -> imageenc -> metadatamuxer -> filesink
+ *
+ * -> [post proc] -> csp -> imageenc -> metadatamuxer -> filesink
+ *
  *-----------------------------------------------------------------------------
  * </programlisting>
  * </informalexample>
  *
- * The property of elements are:
- *
- *   queue - "max-size-buffers", 1, "leaky", 2,
- *
  * The image bin opens file for image writing in READY to PAUSED state change.
  * The image bin closes the file in PAUSED to READY state change.
  *
@@ -150,24 +146,16 @@ gst_camerabin_image_init (GstCameraBinImage * img,
 {
   img->filename = g_string_new ("");
 
-  img->pad_tee_enc = NULL;
-  img->pad_tee_view = NULL;
-
   img->post = NULL;
-  img->tee = NULL;
   img->enc = NULL;
   img->user_enc = NULL;
   img->meta_mux = NULL;
   img->sink = NULL;
-  img->queue = NULL;
 
   /* Create src and sink ghost pads */
   img->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK);
   gst_element_add_pad (GST_ELEMENT (img), img->sinkpad);
 
-  img->srcpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC);
-  gst_element_add_pad (GST_ELEMENT (img), img->srcpad);
-
   img->elements_created = FALSE;
 }
 
@@ -378,17 +366,17 @@ done:
  * Use gst_camerabin_image_destroy_elements to release these resources.
  *
  * Image bin:
- *  img->sinkpad ! [ post process !] tee name=t0 ! encoder ! metadata ! filesink
- *   t0. ! queue ! img->srcpad
+ *  img->sinkpad ! [ post process !] csp ! encoder ! metadata ! filesink
  *
  * Returns: %TRUE if succeeded or FALSE if failed
  */
 static gboolean
 gst_camerabin_image_create_elements (GstCameraBinImage * img)
 {
-  GstPad *sinkpad = NULL, *img_sinkpad = NULL, *img_srcpad = NULL;
+  GstPad *sinkpad = NULL, *img_sinkpad = NULL;
   gboolean ret = FALSE;
   GstBin *imgbin = NULL;
+  GstElement *csp = NULL;
 
   g_return_val_if_fail (img != NULL, FALSE);
 
@@ -412,23 +400,18 @@ gst_camerabin_image_create_elements (GstCameraBinImage * img)
     img_sinkpad = gst_element_get_static_pad (img->post, "sink");
   }
 
-  /* Create tee */
-  if (!(img->tee = gst_camerabin_create_and_add_element (imgbin, "tee"))) {
+  /* Add colorspace converter */
+  if (!(csp =
+          gst_camerabin_create_and_add_element (imgbin, "ffmpegcolorspace"))) {
     goto done;
   }
 
   /* Set up sink ghost pad for img bin */
   if (!img_sinkpad) {
-    img_sinkpad = gst_element_get_static_pad (img->tee, "sink");
+    img_sinkpad = gst_element_get_static_pad (csp, "sink");
   }
   gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), img_sinkpad);
 
-  /* Add colorspace converter */
-  img->pad_tee_enc = gst_element_get_request_pad (img->tee, "src%d");
-  if (!gst_camerabin_create_and_add_element (imgbin, "ffmpegcolorspace")) {
-    goto done;
-  }
-
   /* Create image encoder */
   if (img->user_enc) {
     img->enc = img->user_enc;
@@ -461,33 +444,14 @@ gst_camerabin_image_create_elements (GstCameraBinImage * img)
     goto done;
   }
 
-  /* Create queue element leading to view finder, attaches it to the tee */
-  img->pad_tee_view = gst_element_get_request_pad (img->tee, "src%d");
-  if (!(img->queue = gst_camerabin_create_and_add_element (imgbin, "queue"))) {
-    goto done;
-  }
-
   /* Set properties */
   g_object_set (G_OBJECT (img->sink), "location", img->filename->str, NULL);
   g_object_set (G_OBJECT (img->sink), "async", FALSE, NULL);
 
-  g_object_set (G_OBJECT (img->queue), "max-size-buffers", 1, "leaky", 2, NULL);
-
-  /* Set up src ghost pad for img bin */
-  img_srcpad = gst_element_get_static_pad (img->queue, "src");
-  gst_ghost_pad_set_target (GST_GHOST_PAD (img->srcpad), img_srcpad);
-
-  /* Never let image bin eos events reach view finder */
-  gst_pad_add_event_probe (img->srcpad,
-      G_CALLBACK (gst_camerabin_drop_eos_probe), img);
-
   ret = TRUE;
 
 done:
 
-  if (img_srcpad) {
-    gst_object_unref (img_srcpad);
-  }
   if (img_sinkpad) {
     gst_object_unref (img_sinkpad);
   }
@@ -511,26 +475,14 @@ static void
 gst_camerabin_image_destroy_elements (GstCameraBinImage * img)
 {
   GST_LOG ("destroying img elements");
-  if (img->pad_tee_enc) {
-    gst_element_release_request_pad (img->tee, img->pad_tee_enc);
-    img->pad_tee_enc = NULL;
-  }
-
-  if (img->pad_tee_view) {
-    gst_element_release_request_pad (img->tee, img->pad_tee_view);
-    img->pad_tee_view = NULL;
-  }
 
   gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), NULL);
-  gst_ghost_pad_set_target (GST_GHOST_PAD (img->srcpad), NULL);
 
   gst_camerabin_remove_elements_from_bin (GST_BIN (img));
 
-  img->tee = NULL;
   img->enc = NULL;
   img->meta_mux = NULL;
   img->sink = NULL;
-  img->queue = NULL;
 
   img->elements_created = FALSE;
 }
@@ -538,7 +490,7 @@ gst_camerabin_image_destroy_elements (GstCameraBinImage * img)
 void
 gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder)
 {
-  GST_DEBUG ("setting encoder %" GST_PTR_FORMAT, encoder);
+  GST_DEBUG ("setting image encoder %" GST_PTR_FORMAT, encoder);
   if (img->user_enc)
     gst_object_unref (img->user_enc);
   if (encoder)
@@ -551,7 +503,7 @@ void
 gst_camerabin_image_set_postproc (GstCameraBinImage * img,
     GstElement * postproc)
 {
-  GST_DEBUG ("setting post processing element %" GST_PTR_FORMAT, postproc);
+  GST_DEBUG ("setting image postprocessing element %" GST_PTR_FORMAT, postproc);
   if (img->post)
     gst_object_unref (img->post);
   if (postproc)
index c05f5498a2692c25aeea56cebe88a9953c892469..8214e9cbb5d466d67734138ca02eb2d798b2348c 100644 (file)
 #include <gst/gstbin.h>
 
 G_BEGIN_DECLS
-
 #define GST_TYPE_CAMERABIN_IMAGE             (gst_camerabin_image_get_type())
 #define GST_CAMERABIN_IMAGE_CAST(obj)        ((GstCameraBinImage*)(obj))
 #define GST_CAMERABIN_IMAGE(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImage))
 #define GST_CAMERABIN_IMAGE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImageClass))
 #define GST_IS_CAMERABIN_IMAGE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN_IMAGE))
 #define GST_IS_CAMERABIN_IMAGE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_IMAGE))
-
 /**
  * GstCameraBinImage:
  *
  * The opaque #GstCameraBinImage structure.
  */
-
 typedef struct _GstCameraBinImage GstCameraBinImage;
 typedef struct _GstCameraBinImageClass GstCameraBinImageClass;
 
@@ -48,21 +45,12 @@ struct _GstCameraBinImage
 
   /* Ghost pads of image bin */
   GstPad *sinkpad;
-  GstPad *srcpad;
-
-  /* Tee src pad leading to image encoder */
-  GstPad *pad_tee_enc;
-  /* Tee src pad leading to view finder */
-  GstPad *pad_tee_view;
 
   GstElement *post;
-
-  GstElement *tee;
   GstElement *enc;
   GstElement *user_enc;
   GstElement *meta_mux;
   GstElement *sink;
-  GstElement *queue;
 
   gboolean elements_created;
 };
@@ -86,5 +74,4 @@ GstElement *gst_camerabin_image_get_encoder (GstCameraBinImage * img);
 GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img);
 
 G_END_DECLS
-
-#endif /* #ifndef __CAMERABIN_IMAGE_H__ */
+#endif                          /* #ifndef __CAMERABIN_IMAGE_H__ */
diff --git a/gst/camerabin/camerabinpreview.c b/gst/camerabin/camerabinpreview.c
new file mode 100644 (file)
index 0000000..b64b214
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+* 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., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <string.h>
+
+#include "camerabingeneral.h"
+#include "camerabinpreview.h"
+
+static void
+save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data)
+{
+  GstBuffer **p_buf = (GstBuffer **) data;
+
+  *p_buf = gst_buffer_ref (buf);
+
+  GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT,
+      *p_buf, GST_BUFFER_CAPS (*p_buf));
+}
+
+static gboolean
+create_element (const gchar * factory_name, const gchar * elem_name,
+    GstElement ** element, GError ** err)
+{
+  *element = gst_element_factory_make (factory_name, elem_name);
+  if (*element)
+    return TRUE;
+
+  if (err && *err == NULL) {
+    *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN,
+        "cannot create element '%s' - please check your GStreamer installation",
+        factory_name);
+  }
+
+  return FALSE;
+}
+
+
+/**
+ * gst_camerabin_preview_create_pipeline:
+ * @camera: camerabin object
+ *
+ * Create a preview converter pipeline.
+ *
+ * Returns: TRUE if pipeline was constructed, otherwise FALSE.
+ */
+gboolean
+gst_camerabin_preview_create_pipeline (GstCameraBin * camera)
+{
+  GstElement *src, *csp, *filter, *vscale, *sink;
+  GError *error = NULL;
+
+  if (!camera->preview_caps) {
+    return FALSE;
+  }
+
+  /* Destroy old pipeline, if any */
+  gst_camerabin_preview_destroy_pipeline (camera);
+
+  GST_DEBUG ("creating elements");
+
+  if (!create_element ("appsrc", "prev_src", &src, &error) ||
+      !create_element ("videoscale", NULL, &vscale, &error) ||
+      !create_element ("ffmpegcolorspace", NULL, &csp, &error) ||
+      !create_element ("capsfilter", NULL, &filter, &error) ||
+      !create_element ("fakesink", "prev_sink", &sink, &error))
+    goto no_elements;
+
+  camera->preview_pipeline = gst_pipeline_new ("preview-pipeline");
+  if (camera->preview_pipeline == NULL)
+    goto no_pipeline;
+
+  GST_DEBUG ("adding elements");
+  gst_bin_add_many (GST_BIN (camera->preview_pipeline),
+      src, csp, filter, vscale, sink, NULL);
+
+  g_object_set (filter, "caps", camera->preview_caps, NULL);
+  g_object_set (sink, "preroll-queue-len", 1, "signal-handoffs", TRUE, NULL);
+  g_object_set (vscale, "method", 0, NULL);
+
+  /* FIXME: linking is still way too expensive, profile this properly */
+  GST_DEBUG ("linking src->vscale");
+  if (!gst_element_link_pads (src, "src", vscale, "sink"))
+    return FALSE;
+
+  GST_DEBUG ("linking vscale->csp");
+  if (!gst_element_link_pads (vscale, "src", csp, "sink"))
+    return FALSE;
+
+  GST_DEBUG ("linking csp->capsfilter");
+  if (!gst_element_link_pads (csp, "src", filter, "sink"))
+    return FALSE;
+
+  GST_DEBUG ("linking capsfilter->sink");
+  if (!gst_element_link_pads (filter, "src", sink, "sink"))
+    return FALSE;
+
+  return TRUE;
+
+  /* ERRORS */
+no_elements:
+  {
+    g_warning ("Could not make preview pipeline: %s", error->message);
+    g_error_free (error);
+    return FALSE;
+  }
+no_pipeline:
+  {
+    g_warning ("Could not make preview pipeline: %s",
+        "no pipeline (unknown error)");
+    return FALSE;
+  }
+}
+
+
+/**
+ * gst_camerabin_preview_destroy_pipeline:
+ * @camera: camerabin object
+ *
+ * Destroy preview converter pipeline.
+ */
+void
+gst_camerabin_preview_destroy_pipeline (GstCameraBin * camera)
+{
+  if (camera->preview_pipeline) {
+    gst_element_set_state (camera->preview_pipeline, GST_STATE_NULL);
+    gst_object_unref (camera->preview_pipeline);
+    camera->preview_pipeline = NULL;
+  }
+}
+
+
+/**
+ * gst_camerabin_preview_convert:
+ * @camera: camerabin object
+ * @buf: #GstBuffer that contains the frame to be converted
+ *
+ * Create a preview image of the given frame.
+ *
+ * Returns: converted preview image, or NULL if operation failed.
+ */
+GstBuffer *
+gst_camerabin_preview_convert (GstCameraBin * camera, GstBuffer * buf)
+{
+  GstMessage *msg;
+  GstBuffer *result = NULL;
+  GError *error = NULL;
+  GstBus *bus;
+  GstElement *src, *sink;
+  GstBufferFlag bflags;
+  GstFlowReturn fret;
+
+  g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL);
+
+  if (camera->preview_pipeline == NULL) {
+    GST_WARNING ("pipeline is NULL");
+    goto no_pipeline;
+  }
+
+  src = gst_bin_get_by_name (GST_BIN (camera->preview_pipeline), "prev_src");
+  sink = gst_bin_get_by_name (GST_BIN (camera->preview_pipeline), "prev_sink");
+
+  if (!src || !sink) {
+    GST_WARNING ("pipeline doesn't have src / sink elements");
+    goto no_pipeline;
+  }
+
+  g_object_set (src, "size", (gint64) GST_BUFFER_SIZE (buf),
+      "blocksize", (guint32) GST_BUFFER_SIZE (buf),
+      "caps", GST_BUFFER_CAPS (buf), "num-buffers", 1, NULL);
+
+  g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result);
+
+  bflags = GST_BUFFER_FLAGS (buf);
+  GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_READONLY);
+
+  GST_DEBUG ("running conversion pipeline");
+  gst_element_set_state (camera->preview_pipeline, GST_STATE_PLAYING);
+
+  g_signal_emit_by_name (src, "push-buffer", buf, &fret);
+
+  /* TODO: do we need to use a bus poll, can we just register a callback to the bus? */
+  bus = gst_element_get_bus (camera->preview_pipeline);
+  msg =
+      gst_bus_poll (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS, 25 * GST_SECOND);
+
+  if (msg) {
+    switch (GST_MESSAGE_TYPE (msg)) {
+      case GST_MESSAGE_EOS:{
+        if (result) {
+          GST_DEBUG ("preview image successful: result = %p", result);
+        } else {
+          GST_WARNING ("EOS but no result frame?!");
+        }
+        break;
+      }
+      case GST_MESSAGE_ERROR:{
+        gchar *dbg = NULL;
+
+        gst_message_parse_error (msg, &error, &dbg);
+        if (error) {
+          g_warning ("Could not make preview image: %s", error->message);
+          GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg));
+          g_error_free (error);
+        } else {
+          g_warning ("Could not make preview image (and NULL error!)");
+        }
+        g_free (dbg);
+        result = NULL;
+        break;
+      }
+      default:{
+        g_return_val_if_reached (NULL);
+      }
+    }
+  } else {
+    g_warning ("Could not make preview image: %s", "timeout during conversion");
+    result = NULL;
+  }
+
+  g_signal_handlers_disconnect_by_func (sink, G_CALLBACK (save_result),
+      &result);
+  gst_element_set_state (camera->preview_pipeline, GST_STATE_READY);
+
+  GST_BUFFER_FLAGS (buf) = bflags;
+
+  return result;
+
+  /* ERRORS */
+no_pipeline:
+  {
+    g_warning ("Could not make preview image: %s",
+        "no pipeline (unknown error)");
+    return NULL;
+  }
+}
diff --git a/gst/camerabin/camerabinpreview.h b/gst/camerabin/camerabinpreview.h
new file mode 100644 (file)
index 0000000..cadefed
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+* 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., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __CAMERABINPREVIEW_H__
+#define __CAMERABINPREVIEW_H__
+
+#include <gst/gst.h>
+
+#include "gstcamerabin.h"
+
+G_BEGIN_DECLS
+    gboolean gst_camerabin_preview_create_pipeline (GstCameraBin * camera);
+
+void gst_camerabin_preview_destroy_pipeline (GstCameraBin * camera);
+
+GstBuffer *gst_camerabin_preview_convert (GstCameraBin * camera,
+    GstBuffer * buf);
+
+G_END_DECLS
+#endif                          /* __CAMERABINPREVIEW_H__ */
index dc0dde5e46e457d87e7314c25524b577238f4a0d..22bf1465478e265d0dcdaa1c40c2a14c59f83a78 100644 (file)
  * <refsect2>
  * <title>Example launch line</title>
  * |[
- * gst-launch -v -m camerabin filename=test.jpeg
+ * gst-launch -v -m camerabin
  * ]|
  * </refsect2>
  * <refsect2>
  * <title>Image capture</title>
  * <para>
  * Taking still images is initiated with the #GstCameraBin::user-start action
- * signal. Once the image has captured, #GstCameraBin::img-done signal is fired.
- * It allows to decide wheter to take another picture (burst capture, bracketing
- * shot) or stop capturing. The last captured image is shown
- * until one switches back to view finder using #GstCameraBin::user-stop action
- * signal.
+ * signal. Once the image has been captured, "image-captured" gst message is
+ * posted to the bus and capturing another image is possible. If application 
+ * has set #GstCameraBin:preview-caps property, then a "preview-image" gst
+ * message is posted to bus containing preview image formatted according to
+ * specified caps. Eventually when image has been saved #GstCameraBin::img-done
+ * signal is emitted.
  * 
  * Available resolutions can be taken from the #GstCameraBin:inputcaps property.
  * Image capture resolution can be set with #GstCameraBin::user-image-res
 /*
  * The pipeline in the camerabin is
  *
- *                                   "image bin"
- * videosrc ! crop ! scale ! out-sel <------> in-sel ! scale ! ffmpegcsp ! vfsink
- *                                   "video bin"
+ * videosrc [ ! ffmpegcsp ] ! capsfilter ! crop ! scale ! capsfilter ! \
+ *     out-sel name=osel ! queue name=img_q
  *
- * it is possible to have 'ffmpegcolorspace' and 'capsfilter' just after
- * v4l2camsrc
+ * View finder:
+ * osel. ! in-sel name=isel ! scale ! capsfilter [ ! ffmpegcsp ] ! vfsink
+ *
+ * Image bin:
+ * img_q. [ ! ipp ] ! ffmpegcsp ! imageenc ! metadatamux ! filesink
+ *
+ * Video bin:
+ * osel. ! tee name=t ! queue ! videoenc ! videomux name=mux ! filesink
+ * t. ! queue ! isel.
+ * audiosrc ! queue ! audioconvert ! volume ! audioenc ! mux.
  *
  * The properties of elements are:
  *
- *   vfsink - "sync", FALSE, "qos", FALSE
+ *   vfsink - "sync", FALSE, "qos", FALSE, "async", FALSE
  *   output-selector - "resend-latest", FALSE
  *   input-selector - "select-all", TRUE
  */
 #include "gstcamerabinphotography.h"
 
 #include "camerabingeneral.h"
+#include "camerabinpreview.h"
 
 #include "gstcamerabin-marshal.h"
 
@@ -176,7 +185,8 @@ enum
   ARG_VIDEO_SRC,
   ARG_AUDIO_SRC,
   ARG_INPUT_CAPS,
-  ARG_FILTER_CAPS
+  ARG_FILTER_CAPS,
+  ARG_PREVIEW_CAPS
 };
 
 /*
@@ -216,6 +226,11 @@ static guint camerabin_signals[LAST_SIGNAL];
 
 #define DEFAULT_VIEW_SINK "autovideosink"
 
+#define CAMERABIN_MAX_VF_WIDTH 848
+#define CAMERABIN_MAX_VF_HEIGHT 848
+#define PREVIEW_MESSAGE_NAME "preview-image"
+#define IMG_CAPTURED_MESSAGE_NAME "image-captured"
+
 /*
  * static helper functions declaration
  */
@@ -258,6 +273,12 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
 static gboolean
 gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
     gpointer u_data);
+static gboolean
+gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj,
+    gpointer u_data);
+static gboolean
+gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer,
+    gpointer u_data);
 
 static void gst_camerabin_reset_to_view_finder (GstCameraBin * camera);
 
@@ -557,12 +578,6 @@ camerabin_create_src_elements (GstCameraBin * camera)
           gst_camerabin_create_and_add_element (cbin, "output-selector")))
     goto done;
 
-  camera->srcpad_videosrc =
-      gst_element_get_static_pad (camera->src_vid_src, "src");
-
-  camera->srcpad_zoom_filter =
-      gst_element_get_static_pad (camera->src_zoom_filter, "src");
-
   /* Set default "driver-name" for v4l2camsrc if not set */
   if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src),
           "driver-name")) {
@@ -633,14 +648,16 @@ camerabin_create_view_elements (GstCameraBin * camera)
       && (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) {
     pads = g_list_next (pads);
   }
-  camera->pad_view_img = GST_PAD (pads->data);
+  camera->pad_view_src = GST_PAD (pads->data);
 
+  /* Add videoscale in case we need to downscale frame for view finder */
   if (!(camera->view_scale =
           gst_camerabin_create_and_add_element (GST_BIN (camera),
               "videoscale"))) {
     goto error;
   }
 
+  /* Add capsfilter to maintain aspect ratio while scaling */
   if (!(camera->aspect_filter =
           gst_camerabin_create_and_add_element (GST_BIN (camera),
               "capsfilter"))) {
@@ -690,30 +707,44 @@ camerabin_create_elements (GstCameraBin * camera)
     goto done;
   }
 
-  /* Add image bin */
   camera->pad_src_img =
       gst_element_get_request_pad (camera->src_out_sel, "src%d");
-  if (!gst_camerabin_add_element (GST_BIN (camera), camera->imgbin)) {
-    goto done;
-  }
+
   gst_pad_add_buffer_probe (camera->pad_src_img,
       G_CALLBACK (gst_camerabin_have_img_buffer), camera);
 
-  /* Create view finder elements, this also links it to image bin */
-  if (!camerabin_create_view_elements (camera)) {
-    GST_WARNING_OBJECT (camera, "creating view failed");
+  /* Add image queue */
+  if (!(camera->img_queue =
+          gst_camerabin_create_and_add_element (GST_BIN (camera), "queue"))) {
+    goto done;
+  }
+
+  /* To avoid deadlock, we won't restrict the image queue size */
+  /* FIXME: actually we would like to have some kind of restriction here (size),
+     but deadlocks must be handled somehow... */
+  g_object_set (G_OBJECT (camera->img_queue), "max-size-time",
+      G_GUINT64_CONSTANT (0), NULL);
+  g_object_set (G_OBJECT (camera->img_queue), "max-size-bytes",
+      G_GUINT64_CONSTANT (0), NULL);
+  g_object_set (G_OBJECT (camera->img_queue), "max-size-buffers",
+      G_GUINT64_CONSTANT (0), NULL);
+
+  camera->pad_src_queue = gst_element_get_static_pad (camera->img_queue, "src");
+
+  gst_pad_add_data_probe (camera->pad_src_queue,
+      G_CALLBACK (gst_camerabin_have_queue_data), camera);
+
+  /* Add image bin */
+  if (!gst_camerabin_add_element (GST_BIN (camera), camera->imgbin)) {
     goto done;
   }
 
-  /* Link output selector ! view_finder */
   camera->pad_src_view =
       gst_element_get_request_pad (camera->src_out_sel, "src%d");
-  camera->pad_view_src =
-      gst_element_get_request_pad (camera->view_in_sel, "sink%d");
-  link_ret = gst_pad_link (camera->pad_src_view, camera->pad_view_src);
-  if (GST_PAD_LINK_FAILED (link_ret)) {
-    GST_ELEMENT_ERROR (camera, CORE, NEGOTIATION,
-        ("linking view finder failed"), (NULL));
+
+  /* Create view finder elements */
+  if (!camerabin_create_view_elements (camera)) {
+    GST_WARNING_OBJECT (camera, "creating view finder elements failed");
     goto done;
   }
 
@@ -772,10 +803,6 @@ camerabin_destroy_elements (GstCameraBin * camera)
     gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_vid);
     camera->pad_src_vid = NULL;
   }
-  if (camera->pad_view_img) {
-    gst_element_release_request_pad (camera->view_in_sel, camera->pad_view_img);
-    camera->pad_view_img = NULL;
-  }
   if (camera->pad_src_img) {
     gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_img);
     camera->pad_src_img = NULL;
@@ -789,14 +816,9 @@ camerabin_destroy_elements (GstCameraBin * camera)
     camera->pad_src_view = NULL;
   }
 
-  if (camera->srcpad_zoom_filter) {
-    gst_object_unref (camera->srcpad_zoom_filter);
-    camera->srcpad_zoom_filter = NULL;
-  }
-
-  if (camera->srcpad_videosrc) {
-    gst_object_unref (camera->srcpad_videosrc);
-    camera->srcpad_videosrc = NULL;
+  if (camera->pad_src_queue) {
+    gst_object_unref (camera->pad_src_queue);
+    camera->pad_src_queue = NULL;
   }
 
   camera->view_sink = NULL;
@@ -861,13 +883,23 @@ camerabin_dispose_elements (GstCameraBin * camera)
     gst_caps_unref (camera->allowed_caps);
     camera->allowed_caps = NULL;
   }
+
+  if (camera->preview_caps) {
+    gst_caps_unref (camera->preview_caps);
+    camera->preview_caps = NULL;
+  }
+
+  if (camera->event_tags) {
+    gst_tag_list_free (camera->event_tags);
+    camera->event_tags = NULL;
+  }
 }
 
 /*
  * gst_camerabin_image_capture_continue:
  * @camera: camerabin object
  *
- * Check if application wants to continue image capturing by using g_signal.
+ * Notify application that image has been saved with a signal.
  *
  * Returns TRUE if another image should be captured, FALSE otherwise.
  */
@@ -919,7 +951,16 @@ gst_camerabin_change_mode (GstCameraBin * camera, gint mode)
       gst_element_set_state (camera->active_bin, GST_STATE_NULL);
     }
     if (camera->mode == MODE_IMAGE) {
+      GstStateChangeReturn state_ret;
+
       camera->active_bin = camera->imgbin;
+      state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY);
+
+      if (state_ret == GST_STATE_CHANGE_FAILURE) {
+        GST_WARNING_OBJECT (camera, "state change failed");
+        gst_element_set_state (camera->active_bin, GST_STATE_NULL);
+        camera->active_bin = NULL;
+      }
     } else if (camera->mode == MODE_VIDEO) {
       camera->active_bin = camera->vidbin;
     }
@@ -1109,6 +1150,46 @@ failed:
   return caps;
 }
 
+/*
+ * gst_camerabin_send_img_queue_event:
+ * @camera: camerabin object
+ * @event: event to be sent
+ *
+ * Send the given event to image queue.
+ */
+static void
+gst_camerabin_send_img_queue_event (GstCameraBin * camera, GstEvent * event)
+{
+  GstPad *queue_sink;
+
+  g_return_if_fail (camera != NULL);
+  g_return_if_fail (event != NULL);
+
+  queue_sink = gst_element_get_static_pad (camera->img_queue, "sink");
+  gst_pad_send_event (queue_sink, event);
+  gst_object_unref (queue_sink);
+}
+
+/*
+ * gst_camerabin_send_img_queue_custom_event:
+ * @camera: camerabin object
+ * @ev_struct: event structure to be sent
+ *
+ * Generate and send a custom event to image queue.
+ */
+static void
+gst_camerabin_send_img_queue_custom_event (GstCameraBin * camera,
+    GstStructure * ev_struct)
+{
+  GstEvent *event;
+
+  g_return_if_fail (camera != NULL);
+  g_return_if_fail (ev_struct != NULL);
+
+  event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, ev_struct);
+  gst_camerabin_send_img_queue_event (camera, event);
+}
+
 /*
  * gst_camerabin_rewrite_tags_to_bin:
  * @bin: bin holding tag setter elements
@@ -1271,7 +1352,13 @@ gst_camerabin_rewrite_tags (GstCameraBin * camera)
   }
 
   /* Write tags */
-  gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list);
+  if (camera->active_bin == camera->vidbin) {
+    gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list);
+  } else {
+    /* Image tags need to be sent as a serialized event into image queue */
+    GstEvent *tagevent = gst_event_new_tag (gst_tag_list_copy (list));
+    gst_camerabin_send_img_queue_event (camera, tagevent);
+  }
 
   gst_tag_list_free (list);
 }
@@ -1425,8 +1512,10 @@ img_capture_prepared (gpointer data, GstCaps * caps)
   }
   g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE,
       "active-pad", camera->pad_src_img, NULL);
-  gst_camerabin_rewrite_tags (camera);
-  gst_element_set_state (GST_ELEMENT (camera->imgbin), GST_STATE_PLAYING);
+
+  if (!GST_CAMERABIN_IMAGE (camera->imgbin)->elements_created) {
+    gst_element_set_state (camera->imgbin, GST_STATE_READY);
+  }
 }
 
 /*
@@ -1472,8 +1561,8 @@ gst_camerabin_start_image_capture (GstCameraBin * camera)
   }
 
   if (!wait_for_prepare) {
-    gst_camerabin_rewrite_tags (camera);
-    state_ret = gst_element_set_state (camera->imgbin, GST_STATE_PLAYING);
+    /* Image queue's srcpad data probe will set imagebin to PLAYING */
+    state_ret = gst_element_set_state (camera->imgbin, GST_STATE_READY);
     if (state_ret != GST_STATE_CHANGE_FAILURE) {
       g_mutex_lock (camera->capture_mutex);
       g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", TRUE,
@@ -1576,12 +1665,48 @@ image_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data)
 
   GST_DEBUG_OBJECT (camera, "%s %s:%s",
       blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad));
+}
+
+/*
+ * gst_camerabin_send_preview:
+ * @camera: camerabin object
+ * @buffer: received buffer
+ *
+ * Convert given buffer to desired preview format and send is as a #GstMessage
+ * to application.
+ *
+ * Returns: TRUE always
+ */
+static gboolean
+gst_camerabin_send_preview (GstCameraBin * camera, GstBuffer * buffer)
+{
+  GstBuffer *prev = NULL;
+  GstStructure *s;
+  GstMessage *msg;
+  gboolean ret = FALSE;
 
-  if (blocked && (pad == camera->srcpad_videosrc)) {
-    /* Send eos and block until image bin reaches eos */
-    GST_DEBUG_OBJECT (camera, "sending eos to image bin");
-    gst_element_send_event (camera->imgbin, gst_event_new_eos ());
+  GST_DEBUG_OBJECT (camera, "creating preview");
+
+  prev = gst_camerabin_preview_convert (camera, buffer);
+
+  GST_DEBUG_OBJECT (camera, "preview created: %p", prev);
+
+  if (prev) {
+    s = gst_structure_new (PREVIEW_MESSAGE_NAME,
+        "buffer", GST_TYPE_BUFFER, prev, NULL);
+
+    msg = gst_message_new_element (GST_OBJECT (camera), s);
+
+    GST_DEBUG_OBJECT (camera, "sending message with preview image");
+
+    if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) {
+      GST_WARNING_OBJECT (camera,
+          "This element has no bus, therefore no message sent!");
+    }
+    ret = TRUE;
   }
+
+  return ret;
 }
 
 /*
@@ -1590,27 +1715,19 @@ image_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data)
  * @buffer: still image frame
  * @u_data: camera bin object
  *
- * Buffer probe called before sending each buffer to image bin.
- *
- * First buffer is always passed directly to image bin. Then pad
- * is blocked in order to interleave buffers with eos events.
- * Interleaving eos events and buffers is needed when we have
- * decoupled elements in the image bin capture pipeline.
- * After image bin posts eos message, then pad is unblocked.
- * Next, image bin is changed to READY state in order to save the
- * file and the application is allowed to decide whether to
- * continue image capture. If yes, only then the next buffer is
- * passed to image bin.
+ * Buffer probe called before sending each buffer to image queue.
+ * Generates and sends preview image as gst message if requested.
  */
 static gboolean
 gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
     gpointer u_data)
 {
   GstCameraBin *camera = (GstCameraBin *) u_data;
+  GstStructure *fn_ev_struct = NULL;
   gboolean ret = TRUE;
+  GstPad *os_sink = NULL;
 
-  GST_LOG ("got buffer #%d %p with size %d", camera->num_img_buffers,
-      buffer, GST_BUFFER_SIZE (buffer));
+  GST_LOG ("got buffer %p with size %d", buffer, GST_BUFFER_SIZE (buffer));
 
   /* Image filename should be set by now */
   if (g_str_equal (camera->filename->str, "")) {
@@ -1619,54 +1736,38 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
     goto done;
   }
 
-  /* Check for first buffer after capture start, we want to
-     pass it forward directly. */
-  if (!camera->num_img_buffers) {
-    goto done;
+  if (camera->preview_caps) {
+    gst_camerabin_send_preview (camera, buffer);
   }
 
-  /* Close the file of saved image */
-  gst_element_set_state (camera->imgbin, GST_STATE_READY);
-
-  /* Reset filename to force application set new filename */
-  g_string_assign (camera->filename, "");
-
-  /* Check if the application wants to continue */
-  ret = gst_camerabin_image_capture_continue (camera);
+  gst_camerabin_rewrite_tags (camera);
 
-  if (ret && !camera->stop_requested) {
-    GST_DEBUG_OBJECT (camera, "capturing image \"%s\"", camera->filename->str);
-    g_object_set (G_OBJECT (camera->imgbin), "filename",
-        camera->filename->str, NULL);
-    gst_element_set_state (camera->imgbin, GST_STATE_PLAYING);
-  } else {
-    GST_DEBUG_OBJECT (camera, "not continuing (cont:%d, stop_req:%d)",
-        ret, camera->stop_requested);
+  /* Send a custom event which tells the filename to image queue */
+  /* NOTE: This needs to be THE FIRST event to be sent to queue for
+     every image. It triggers imgbin state change to PLAYING. */
+  fn_ev_struct = gst_structure_new ("img-filename",
+      "filename", G_TYPE_STRING, camera->filename->str, NULL);
+  GST_DEBUG_OBJECT (camera, "sending filename event to image queue");
+  gst_camerabin_send_img_queue_custom_event (camera, fn_ev_struct);
+
+  /* Add buffer probe to outputselector's sink pad. It sends
+     EOS event to image queue. */
+  os_sink = gst_element_get_static_pad (camera->src_out_sel, "sink");
+  camera->image_captured_id = gst_pad_add_buffer_probe (os_sink,
+      G_CALLBACK (gst_camerabin_have_src_buffer), camera);
+  gst_object_unref (os_sink);
 
-    /* Block dataflow to the output-selector to show preview image in
-       view finder. Continue and unblock when capture is stopped */
-    gst_pad_set_blocked_async (camera->srcpad_zoom_filter, TRUE,
-        (GstPadBlockCallback) image_pad_blocked, camera);
-    ret = FALSE;                /* Drop the buffer */
+done:
 
-    g_mutex_lock (camera->capture_mutex);
-    camera->capturing = FALSE;
-    g_cond_signal (camera->cond);
-    g_mutex_unlock (camera->capture_mutex);
-  }
+  /* HACK: v4l2camsrc changes to view finder resolution automatically
+     after one captured still image */
+  gst_camerabin_finish_image_capture (camera);
 
-done:
+  gst_camerabin_reset_to_view_finder (camera);
 
-  if (ret) {
-    camera->num_img_buffers++;
-    /* Block when next buffer arrives, we want to push eos event
-       between frames and make sure that eos reaches the filesink
-       before processing the next buffer. */
-    gst_pad_set_blocked_async (camera->srcpad_videosrc, TRUE,
-        (GstPadBlockCallback) image_pad_blocked, camera);
-  }
+  GST_DEBUG_OBJECT (camera, "switched back to viewfinder");
 
-  return ret;
+  return TRUE;
 }
 
 /*
@@ -1695,6 +1796,120 @@ gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
   return ret;
 }
 
+/*
+ * gst_camerabin_have_src_buffer:
+ * @pad: output-selector sink pad which receives frames from video source
+ * @buffer: buffer pushed to the pad
+ * @u_data: camerabin object
+ *
+ * Buffer probe for sink pad. It sends custom eos event to image queue and
+ * notifies application by sending a "image-captured" message to GstBus.
+ * This probe is installed after image has been captured and it disconnects
+ * itself after EOS has been sent.
+ */
+static gboolean
+gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer,
+    gpointer u_data)
+{
+  GstCameraBin *camera = (GstCameraBin *) u_data;
+  GstMessage *msg;
+
+  GST_LOG_OBJECT (camera, "got image buffer %p with size %d",
+      buffer, GST_BUFFER_SIZE (buffer));
+
+  /* We can't send real EOS event, since it would switch the image queue
+     into "draining mode". Therefore we send our own custom eos and
+     catch & drop it later in queue's srcpad data probe */
+  GST_DEBUG_OBJECT (camera, "sending eos to image queue");
+  gst_camerabin_send_img_queue_custom_event (camera,
+      gst_structure_new ("img-eos", NULL));
+
+  /* our work is done, disconnect */
+  gst_pad_remove_buffer_probe (pad, camera->image_captured_id);
+
+  g_mutex_lock (camera->capture_mutex);
+  camera->capturing = FALSE;
+  g_cond_signal (camera->cond);
+  g_mutex_unlock (camera->capture_mutex);
+
+  msg = gst_message_new_element (GST_OBJECT (camera),
+      gst_structure_new (IMG_CAPTURED_MESSAGE_NAME, NULL));
+
+  GST_DEBUG_OBJECT (camera, "sending 'image captured' message");
+
+  if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) {
+    GST_WARNING_OBJECT (camera,
+        "This element has no bus, therefore no message sent!");
+  }
+
+  return TRUE;
+}
+
+/*
+ * gst_camerabin_have_queue_data:
+ * @pad: image queue src pad leading to image bin
+ * @mini_obj: buffer or event pushed to the pad
+ * @u_data: camerabin object
+ *
+ * Buffer probe for image queue src pad leading to image bin. It sets imgbin
+ * into PLAYING mode when image buffer is passed to it. This probe also
+ * monitors our internal custom events and handles them accordingly.
+ */
+static gboolean
+gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj,
+    gpointer u_data)
+{
+  GstCameraBin *camera = (GstCameraBin *) u_data;
+  gboolean ret = TRUE;
+
+  if (GST_IS_BUFFER (mini_obj)) {
+    GstEvent *tagevent;
+
+    GST_LOG_OBJECT (camera, "queue sending image buffer to imgbin");
+
+    tagevent = gst_event_new_tag (gst_tag_list_copy (camera->event_tags));
+    gst_element_send_event (camera->imgbin, tagevent);
+    gst_tag_list_free (camera->event_tags);
+    camera->event_tags = gst_tag_list_new ();
+  } else if (GST_IS_EVENT (mini_obj)) {
+    const GstStructure *evs;
+    GstEvent *event;
+
+    event = GST_EVENT_CAST (mini_obj);
+    evs = gst_event_get_structure (event);
+
+    GST_LOG_OBJECT (camera, "got event %s", GST_EVENT_TYPE_NAME (event));
+
+    if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) {
+      GstTagList *tlist;
+
+      gst_event_parse_tag (event, &tlist);
+      gst_tag_list_insert (camera->event_tags, tlist, GST_TAG_MERGE_REPLACE);
+      ret = FALSE;
+    } else if (evs && gst_structure_has_name (evs, "img-filename")) {
+      const gchar *fname;
+
+      GST_LOG_OBJECT (camera, "queue setting image filename to imagebin");
+      fname = gst_structure_get_string (evs, "filename");
+      g_object_set (G_OBJECT (camera->imgbin), "filename", fname, NULL);
+
+      /* imgbin fails to start unless the filename is set */
+      gst_element_set_state (camera->imgbin, GST_STATE_PLAYING);
+      GST_LOG_OBJECT (camera, "Set imgbin to PLAYING");
+
+      ret = FALSE;
+    } else if (evs && gst_structure_has_name (evs, "img-eos")) {
+      GST_LOG_OBJECT (camera, "queue sending EOS to image pipeline");
+      gst_pad_set_blocked_async (camera->pad_src_queue, TRUE,
+          (GstPadBlockCallback) image_pad_blocked, camera);
+      gst_element_send_event (camera->imgbin, gst_event_new_eos ());
+      ret = FALSE;
+    }
+  }
+
+  return ret;
+}
+
 /*
  * gst_camerabin_reset_to_view_finder:
  * @camera: camerabin object
@@ -1708,8 +1923,8 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
   GstStateChangeReturn state_ret;
   GST_DEBUG_OBJECT (camera, "resetting");
 
-  /* Set active bin to READY state */
-  if (camera->active_bin) {
+  /* Set video bin to READY state */
+  if (camera->active_bin == camera->vidbin) {
     state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY);
     if (state_ret == GST_STATE_CHANGE_FAILURE) {
       GST_WARNING_OBJECT (camera, "state change failed");
@@ -1719,7 +1934,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
   }
 
   /* Reset counters and flags */
-  camera->num_img_buffers = 0;
   camera->stop_requested = FALSE;
   camera->paused = FALSE;
 
@@ -1729,18 +1943,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
         "active-pad", camera->pad_src_view, NULL);
   }
 
-  /* Unblock, if dataflow to output-selector is blocked due to image preview */
-  if (camera->srcpad_zoom_filter &&
-      gst_pad_is_blocked (camera->srcpad_zoom_filter)) {
-    gst_pad_set_blocked_async (camera->srcpad_zoom_filter, FALSE,
-        (GstPadBlockCallback) image_pad_blocked, camera);
-  }
-  /* Unblock, if dataflow in videosrc is blocked due to waiting for eos */
-  if (camera->srcpad_videosrc && gst_pad_is_blocked (camera->srcpad_videosrc)) {
-    gst_pad_set_blocked_async (camera->srcpad_videosrc, FALSE,
-        (GstPadBlockCallback) image_pad_blocked, camera);
-  }
-
   /* Enable view finder mode in v4l2camsrc */
   if (camera->src_vid_src &&
       g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src),
@@ -2304,14 +2506,29 @@ gst_camerabin_class_init (GstCameraBinClass * klass)
   /**
    * GstCameraBin:filter-caps:
    *
-   * Filter video source element caps using this property.
-   * This is an alternative to #GstCamerabin::user-res-fps action
-   * signal that allows more fine grained control of video source.
+   * Caps applied to capsfilter element after videosrc [ ! ffmpegcsp ].
+   * You can use this e.g. to make sure video color format matches with
+   * encoders and other elements configured to camerabin and/or change
+   * resolution and frame rate.
    */
 
   g_object_class_install_property (gobject_class, ARG_FILTER_CAPS,
       g_param_spec_boxed ("filter-caps", "Filter caps",
-          "Capsfilter caps used to control video source operation",
+          "Filter video data coming from videosrc element",
+          GST_TYPE_CAPS, G_PARAM_READWRITE));
+
+  /**
+   * GstCameraBin:preview-caps:
+   *
+   * If application wants to receive a preview image, it needs to 
+   * set this property to depict the desired image format caps. When
+   * this property is not set (NULL), message containing the preview
+   * image is not sent.
+   */
+
+  g_object_class_install_property (gobject_class, ARG_PREVIEW_CAPS,
+      g_param_spec_boxed ("preview-caps", "Preview caps",
+          "Caps defining the preview image format",
           GST_TYPE_CAPS, G_PARAM_READWRITE));
 
   /**
@@ -2408,9 +2625,7 @@ gst_camerabin_class_init (GstCameraBinClass * klass)
    * @camera: the camera bin element
    * @filename: the name of the file just saved
    *
-   * Signal emitted when the file has just been saved. To continue taking
-   * pictures set new filename using #GstCameraBin:filename property and return
-   * TRUE, otherwise return FALSE.
+   * Signal emitted when the file has just been saved.
    *
    * Don't call any #GstCameraBin method from this signal, if you do so there
    * will be a deadlock.
@@ -2455,7 +2670,6 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
 
   camera->filename = g_string_new ("");
   camera->mode = DEFAULT_MODE;
-  camera->num_img_buffers = 0;
   camera->stop_requested = FALSE;
   camera->paused = FALSE;
   camera->capturing = FALSE;
@@ -2466,6 +2680,8 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
   camera->fps_n = DEFAULT_FPS_N;
   camera->fps_d = DEFAULT_FPS_D;
 
+  camera->event_tags = gst_tag_list_new ();
+
   camera->image_capture_caps = NULL;
   camera->view_finder_caps = NULL;
   camera->allowed_caps = NULL;
@@ -2480,13 +2696,9 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
   camera->pad_src_view = NULL;
   camera->pad_view_src = NULL;
   camera->pad_src_img = NULL;
-  camera->pad_view_img = NULL;
   camera->pad_src_vid = NULL;
   camera->pad_view_vid = NULL;
 
-  camera->srcpad_zoom_filter = NULL;
-  camera->srcpad_videosrc = NULL;
-
   /* source elements */
   camera->src_vid_src = NULL;
   camera->src_filter = NULL;
@@ -2530,6 +2742,8 @@ gst_camerabin_dispose (GObject * object)
   gst_element_set_state (camera->vidbin, GST_STATE_NULL);
   gst_object_unref (camera->vidbin);
 
+  gst_camerabin_preview_destroy_pipeline (camera);
+
   camerabin_destroy_elements (camera);
 
   camerabin_dispose_elements (camera);
@@ -2590,7 +2804,7 @@ gst_camerabin_set_property (GObject * object, guint prop_id,
       break;
     case ARG_VIDEO_MUX:
       if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
-        GST_WARNING_OBJECT (camera->vidbin,
+        GST_WARNING_OBJECT (camera,
             "can't use set element until next video bin NULL to READY state change");
       }
       gst_camerabin_video_set_muxer (GST_CAMERABIN_VIDEO (camera->vidbin),
@@ -2651,7 +2865,18 @@ gst_camerabin_set_property (GObject * object, guint prop_id,
       }
       camera->view_finder_caps = gst_caps_copy (gst_value_get_caps (value));
       GST_OBJECT_UNLOCK (camera);
-      gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps);
+      if (GST_STATE (camera) != GST_STATE_NULL) {
+        gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps);
+      }
+      break;
+    case ARG_PREVIEW_CAPS:
+      GST_OBJECT_LOCK (camera);
+      if (camera->preview_caps) {
+        gst_caps_unref (camera->preview_caps);
+      }
+      camera->preview_caps = gst_caps_copy (gst_value_get_caps (value));
+      GST_OBJECT_UNLOCK (camera);
+      gst_camerabin_preview_create_pipeline (camera);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2724,6 +2949,9 @@ gst_camerabin_get_property (GObject * object, guint prop_id,
     case ARG_FILTER_CAPS:
       gst_value_set_caps (value, camera->view_finder_caps);
       break;
+    case ARG_PREVIEW_CAPS:
+      gst_value_set_caps (value, camera->preview_caps);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -2795,6 +3023,29 @@ done:
   return ret;
 }
 
+static gboolean
+gst_camerabin_imgbin_finished (gpointer u_data)
+{
+  GstCameraBin *camera = GST_CAMERABIN (u_data);
+
+  GST_DEBUG_OBJECT (camera, "Image encoding finished");
+
+  /* Close the file of saved image */
+  gst_element_set_state (camera->imgbin, GST_STATE_READY);
+  GST_DEBUG_OBJECT (camera, "Image pipeline set to READY");
+
+  /* Send img-done signal */
+  gst_camerabin_image_capture_continue (camera);
+
+  /* Unblock image queue pad to process next buffer */
+  gst_pad_set_blocked_async (camera->pad_src_queue, FALSE,
+      (GstPadBlockCallback) image_pad_blocked, camera);
+  GST_DEBUG_OBJECT (camera, "Queue srcpad unblocked");
+
+  /* disconnect automatically */
+  return FALSE;
+}
+
 /*
  * GstBin functions implementation
  */
@@ -2818,12 +3069,7 @@ gst_camerabin_handle_message_func (GstBin * bin, GstMessage * msg)
       } else if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->imgbin)) {
         /* Image eos */
         GST_DEBUG_OBJECT (camera, "got image eos message");
-
-        gst_camerabin_finish_image_capture (camera);
-
-        /* Unblock pad to process next buffer */
-        gst_pad_set_blocked_async (camera->srcpad_videosrc, FALSE,
-            (GstPadBlockCallback) image_pad_blocked, camera);
+        g_idle_add (gst_camerabin_imgbin_finished, camera);
       }
       break;
     case GST_MESSAGE_ERROR:
@@ -2878,12 +3124,11 @@ gst_camerabin_user_start (GstCameraBin * camera)
   g_mutex_unlock (camera->capture_mutex);
 
   if (camera->active_bin) {
-    g_object_set (G_OBJECT (camera->active_bin), "filename",
-        camera->filename->str, NULL);
-
     if (camera->active_bin == camera->imgbin) {
       gst_camerabin_start_image_capture (camera);
     } else if (camera->active_bin == camera->vidbin) {
+      g_object_set (G_OBJECT (camera->active_bin), "filename",
+          camera->filename->str, NULL);
       gst_camerabin_start_video_recording (camera);
     }
   }
@@ -2892,10 +3137,13 @@ gst_camerabin_user_start (GstCameraBin * camera)
 static void
 gst_camerabin_user_stop (GstCameraBin * camera)
 {
-  GST_INFO_OBJECT (camera, "stopping %s capture",
-      camera->mode ? "video" : "image");
-  gst_camerabin_do_stop (camera);
-  gst_camerabin_reset_to_view_finder (camera);
+  if (camera->active_bin == camera->vidbin) {
+    GST_INFO_OBJECT (camera, "stopping video capture");
+    gst_camerabin_do_stop (camera);
+    gst_camerabin_reset_to_view_finder (camera);
+  } else {
+    GST_INFO_OBJECT (camera, "stopping image capture isn't needed");
+  }
 }
 
 static void
index 16b242c0021a9011d2439914a3aa6e554846ac8a..cd88c7ca1f87b89cc38092480e0147beee0ce514 100644 (file)
@@ -59,7 +59,6 @@ struct _GstCameraBin
   /* private */
   GString *filename;
   gint mode;                    /* MODE_IMAGE or MODE_VIDEO */
-  guint num_img_buffers;        /* no of image buffers captured */
   gboolean stop_requested;      /* TRUE if capturing stop needed */
   gboolean paused;              /* TRUE if capturing paused */
 
@@ -69,6 +68,9 @@ struct _GstCameraBin
   gint fps_n;
   gint fps_d;
 
+  /* Image tags are collected here first before sending to imgbin */
+  GstTagList *event_tags;
+
   /* Caps applied to capsfilters when taking still image */
   GstCaps *image_capture_caps;
 
@@ -78,6 +80,9 @@ struct _GstCameraBin
   /* Caps that videosrc supports */
   GstCaps *allowed_caps;
 
+  /* Caps used to create preview image */
+  GstCaps *preview_caps;
+
   /* The digital zoom (from 100% to 1000%) */
   gint zoom;
 
@@ -90,16 +95,16 @@ struct _GstCameraBin
   GstPad *pad_src_view;
   GstPad *pad_view_src;
   GstPad *pad_src_img;
-  GstPad *pad_view_img;
   GstPad *pad_src_vid;
   GstPad *pad_view_vid;
+  GstPad *pad_src_queue;
 
-  GstPad *srcpad_zoom_filter;
-  GstPad *srcpad_videosrc;
-
+  GstElement *img_queue;        /* queue for decoupling capture from
+                                   image-postprocessing and saving */
   GstElement *imgbin;           /* bin that holds image capturing elements */
   GstElement *vidbin;           /*  bin that holds video capturing elements */
   GstElement *active_bin;       /* image or video bin that is currently in use */
+  GstElement *preview_pipeline; /* pipeline for creating preview images */
 
   /* source elements */
   GstElement *src_vid_src;
@@ -126,6 +131,9 @@ struct _GstCameraBin
 
   /* Cache the photography interface settings */
   GstPhotoSettings photo_settings;
+
+  /* Buffer probe id for captured image handling */
+  gulong image_captured_id;
 };
 
 /**
@@ -148,7 +156,7 @@ struct _GstCameraBinClass
 
   /* signals (callback) */
 
-    gboolean (*img_done) (GstCameraBin * camera, const gchar * filename);
+   gboolean (*img_done) (GstCameraBin * camera, const gchar * filename);
 };
 
 /**
@@ -167,4 +175,4 @@ typedef enum
 GType gst_camerabin_get_type (void);
 
 G_END_DECLS
-#endif /* #ifndef __GST_CAMERABIN_H__ */
+#endif                          /* #ifndef __GST_CAMERABIN_H__ */
index 7c6187d2ef87d773a4594a0f490500a08c4ba6b3..2b72c6ffa22d19fc2bea1c37eab0674901719000 100644 (file)
 #define MAX_BURST_IMAGES 10
 #define PHOTO_SETTING_DELAY_US 0
 
-static gboolean continuous = FALSE;
-static guint captured_images = 0;
-
 static GstElement *camera;
 static GCond *cam_cond;
 static GMutex *cam_mutex;
-
+static GMainLoop *main_loop;
+static guint cycle_count = 0;
 
 /* helper function for filenames */
 static const gchar *
@@ -61,6 +59,8 @@ make_test_file_name (const gchar * base_name)
   return file_name;
 }
 
+/* burst capture is not supported in camerabin for the moment */
+#ifdef ENABLE_BURST_CAPTURE
 static const gchar *
 make_test_seq_file_name (const gchar * base_name)
 {
@@ -72,30 +72,49 @@ make_test_seq_file_name (const gchar * base_name)
   GST_INFO ("capturing to: %s", file_name);
   return file_name;
 }
-
+#endif
 /* signal handlers */
 
 static gboolean
-capture_done (GstElement * elem, const gchar * filename, gpointer user_data)
+handle_image_captured_cb (gpointer data)
 {
-  captured_images++;
-
-  if (captured_images >= MAX_BURST_IMAGES) {
-    /* release the shutter button */
-    GST_DEBUG ("signal for img-done");
-    g_mutex_lock (cam_mutex);
-    g_cond_signal (cam_cond);
-    g_mutex_unlock (cam_mutex);
-    continuous = FALSE;
-  }
+  GMainLoop *loop = (GMainLoop *) data;
+
+  GST_DEBUG ("handle_image_captured_cb, cycle: %d", cycle_count);
+  if (cycle_count == 0) {
+    g_main_loop_quit (loop);
+  } else {
+    /* Set video recording mode */
+    g_object_set (camera, "mode", 1,
+        "filename", make_test_file_name (CYCLE_VIDEO_FILENAME), NULL);
+    /* Record video */
+    g_signal_emit_by_name (camera, "user-start", 0);
+    g_usleep (G_USEC_PER_SEC);
+    g_signal_emit_by_name (camera, "user-stop", 0);
+    GST_DEBUG ("video captured");
+
+    /* Set still image mode */
+    g_object_set (camera, "mode", 0,
+        "filename", make_test_file_name (CYCLE_IMAGE_FILENAME), NULL);
+    /* Take a picture */
+    g_signal_emit_by_name (camera, "user-start", 0);
 
-  if (continuous) {
-    /* Must set filename for new picture */
-    g_object_set (G_OBJECT (elem), "filename",
-        make_test_seq_file_name (BURST_IMAGE_FILENAME), NULL);
+    cycle_count--;
   }
+  GST_DEBUG ("handle_image_captured_cb done");
+  return FALSE;
+}
+
+static gboolean
+capture_done (GstElement * elem, const gchar * filename, gpointer user_data)
+{
+  GMainLoop *loop = (GMainLoop *) user_data;
 
-  return continuous;
+  g_idle_add ((GSourceFunc) handle_image_captured_cb, loop);
+
+  GST_DEBUG ("image saved");
+
+  return FALSE;
 }
 
 /* configuration */
@@ -103,7 +122,8 @@ capture_done (GstElement * elem, const gchar * filename, gpointer user_data)
 static void
 setup_camerabin_elements (GstElement * camera)
 {
-  GstElement *vfsink, *audiosrc, *videosrc;
+  GstElement *vfsink, *audiosrc, *videosrc, *audioenc, *videoenc, *imageenc,
+      *videomux;
 
   /* Use fakesink for view finder */
   vfsink = gst_element_factory_make ("fakesink", NULL);
@@ -111,9 +131,42 @@ setup_camerabin_elements (GstElement * camera)
   g_object_set (audiosrc, "is-live", TRUE, NULL);
   videosrc = gst_element_factory_make ("videotestsrc", NULL);
   g_object_set (videosrc, "is-live", TRUE, NULL);
+  audioenc = gst_element_factory_make ("vorbisenc", NULL);
+  videoenc = gst_element_factory_make ("theoraenc", NULL);
+  videomux = gst_element_factory_make ("oggmux", NULL);
+  imageenc = gst_element_factory_make ("jpegenc", NULL);
+
+  if (vfsink && audiosrc && videosrc && audioenc && videoenc && videomux
+      && imageenc) {
+    g_object_set (camera, "vfsink", vfsink, "audiosrc", audiosrc, "videosrc",
+        videosrc, "audioenc", audioenc, "videoenc", videoenc, "imageenc",
+        imageenc, "videomux", videomux, NULL);
+  }
+}
+
+static gboolean
+capture_bus_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+  GMainLoop *loop = (GMainLoop *) data;
+  const GstStructure *st;
 
-  g_object_set (camera, "vfsink", vfsink, "audiosrc", audiosrc,
-      "videosrc", videosrc, NULL);
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:
+      fail_if (TRUE, "error while capturing");
+      g_main_loop_quit (loop);
+      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-captured")) {
+        GST_DEBUG ("image-captured");
+      }
+      break;
+  }
+  return TRUE;
 }
 
 static void
@@ -121,6 +174,10 @@ setup (void)
 {
   GstTagSetter *setter;
   gchar *desc_str;
+  GstCaps *filter_caps;
+  GstBus *bus;
+
+  main_loop = g_main_loop_new (NULL, TRUE);
 
   cam_cond = g_cond_new ();
   cam_mutex = g_mutex_new ();
@@ -129,9 +186,15 @@ setup (void)
 
   setup_camerabin_elements (camera);
 
-  g_signal_connect (camera, "img-done", G_CALLBACK (capture_done), NULL);
+  g_signal_connect (camera, "img-done", G_CALLBACK (capture_done), main_loop);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (camera));
+  gst_bus_add_watch (bus, (GstBusFunc) capture_bus_cb, main_loop);
+  gst_object_unref (bus);
 
-  captured_images = 0;
+  filter_caps = gst_caps_from_string ("video/x-raw-yuv,format=(fourcc)I420");
+  g_object_set (G_OBJECT (camera), "filter-caps", filter_caps, NULL);
+  gst_caps_unref (filter_caps);
 
   /* Set some default tags */
   setter = GST_TAG_SETTER (camera);
@@ -141,8 +204,8 @@ setup (void)
       GST_TAG_DESCRIPTION, desc_str, NULL);
   g_free (desc_str);
 
-  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) !=
-      GST_STATE_CHANGE_SUCCESS) {
+  if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_FAILURE) {
     gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
     gst_object_unref (camera);
     camera = NULL;
@@ -314,39 +377,13 @@ GST_START_TEST (test_single_image_capture)
   g_object_set (camera, "mode", 0,
       "filename", make_test_file_name (SINGLE_IMAGE_FILENAME), NULL);
 
-  continuous = FALSE;
-
   /* Test photography iface settings */
   gst_element_get_state (GST_ELEMENT (camera), NULL, NULL, (2 * GST_SECOND));
   test_photography_settings (camera);
 
-  g_signal_emit_by_name (camera, "user-start", 0);
-  g_signal_emit_by_name (camera, "user-stop", 0);
-}
-
-GST_END_TEST;
-
-GST_START_TEST (test_burst_image_capture)
-{
-  if (!camera)
-    return;
-
-  /* set still image mode */
-  g_object_set (camera, "mode", 0,
-      "filename", make_test_seq_file_name (BURST_IMAGE_FILENAME), NULL);
-
-  /* set burst mode */
-  continuous = TRUE;
-
   g_signal_emit_by_name (camera, "user-start", 0);
 
-  GST_DEBUG ("waiting for img-done");
-  g_mutex_lock (cam_mutex);
-  g_cond_wait (cam_cond, cam_mutex);
-  g_mutex_unlock (cam_mutex);
-  GST_DEBUG ("received img-done");
-
-  g_signal_emit_by_name (camera, "user-stop", 0);
+  g_main_loop_run (main_loop);
 }
 
 GST_END_TEST;
@@ -370,48 +407,35 @@ GST_END_TEST;
 
 GST_START_TEST (test_image_video_cycle)
 {
-  guint i;
-
   if (!camera)
     return;
 
-  continuous = FALSE;
-
-  for (i = 0; i < 2; i++) {
-    /* Set still image mode */
-    g_object_set (camera, "mode", 0,
-        "filename", make_test_file_name (CYCLE_IMAGE_FILENAME), NULL);
+  cycle_count = 2;
 
-    /* Take a picture */
-    g_signal_emit_by_name (camera, "user-start", 0);
-    g_signal_emit_by_name (camera, "user-stop", 0);
-    GST_DEBUG ("image captured");
+  /* set still image mode */
+  g_object_set (camera, "mode", 0,
+      "filename", make_test_file_name (CYCLE_IMAGE_FILENAME), NULL);
 
-    /* Set video recording mode */
-    g_object_set (camera, "mode", 1,
-        "filename", make_test_file_name (CYCLE_VIDEO_FILENAME), NULL);
+  g_signal_emit_by_name (camera, "user-start", 0);
 
-    /* Record video */
-    g_signal_emit_by_name (camera, "user-start", 0);
-    g_usleep (G_USEC_PER_SEC);
-    g_signal_emit_by_name (camera, "user-stop", 0);
-    GST_DEBUG ("video captured");
-  }
+  g_main_loop_run (main_loop);
 }
 
 GST_END_TEST;
 
 GST_START_TEST (validate_captured_image_files)
 {
-  GString *filename;
-  gint i;
-
   if (!camera)
     return;
 
   /* validate single image */
   check_file_validity (SINGLE_IMAGE_FILENAME);
 
+/* burst capture is not supported in camerabin for the moment */
+#ifdef ENABLE_BURST_CAPTURE
+  GString *filename;
+  gint i;
+
   /* validate burst mode images */
   filename = g_string_new ("");
   for (i = 0; i < MAX_BURST_IMAGES; i++) {
@@ -419,7 +443,7 @@ GST_START_TEST (validate_captured_image_files)
     check_file_validity (filename->str);
   }
   g_string_free (filename, TRUE);
-
+#endif
   /* validate cycled image */
   check_file_validity (CYCLE_IMAGE_FILENAME);
 }
@@ -453,7 +477,6 @@ camerabin_suite (void)
   tcase_set_timeout (tc_basic, 20);
   tcase_add_checked_fixture (tc_basic, setup, teardown);
   tcase_add_test (tc_basic, test_single_image_capture);
-  tcase_add_test (tc_basic, test_burst_image_capture);
   tcase_add_test (tc_basic, test_video_recording);
   tcase_add_test (tc_basic, test_image_video_cycle);