diff options
-rw-r--r-- | gst/camerabin/Makefile.am | 2 | ||||
-rw-r--r-- | gst/camerabin/camerabinimage.c | 72 | ||||
-rw-r--r-- | gst/camerabin/camerabinimage.h | 15 | ||||
-rw-r--r-- | gst/camerabin/camerabinpreview.c | 257 | ||||
-rw-r--r-- | gst/camerabin/camerabinpreview.h | 37 | ||||
-rw-r--r-- | gst/camerabin/gstcamerabin.c | 550 | ||||
-rw-r--r-- | gst/camerabin/gstcamerabin.h | 22 | ||||
-rw-r--r-- | tests/check/elements/camerabin.c | 185 |
8 files changed, 827 insertions, 313 deletions
diff --git a/gst/camerabin/Makefile.am b/gst/camerabin/Makefile.am index 060de0c4..4f382243 100644 --- a/gst/camerabin/Makefile.am +++ b/gst/camerabin/Makefile.am @@ -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 diff --git a/gst/camerabin/camerabinimage.c b/gst/camerabin/camerabinimage.c index 99bf20b6..338d2e3d 100644 --- a/gst/camerabin/camerabinimage.c +++ b/gst/camerabin/camerabinimage.c @@ -30,17 +30,13 @@ * <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) diff --git a/gst/camerabin/camerabinimage.h b/gst/camerabin/camerabinimage.h index c05f5498..8214e9cb 100644 --- a/gst/camerabin/camerabinimage.h +++ b/gst/camerabin/camerabinimage.h @@ -24,20 +24,17 @@ #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 index 00000000..b64b2143 --- /dev/null +++ b/gst/camerabin/camerabinpreview.c @@ -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 index 00000000..cadefed6 --- /dev/null +++ b/gst/camerabin/camerabinpreview.h @@ -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__ */ diff --git a/gst/camerabin/gstcamerabin.c b/gst/camerabin/gstcamerabin.c index dc0dde5e..22bf1465 100644 --- a/gst/camerabin/gstcamerabin.c +++ b/gst/camerabin/gstcamerabin.c @@ -36,18 +36,19 @@ * <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 @@ -106,16 +107,23 @@ /* * 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 */ @@ -139,6 +147,7 @@ #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; } @@ -1110,6 +1151,46 @@ failed: } /* + * 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 * @list: tag list to be written @@ -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; } /* @@ -1696,6 +1797,120 @@ gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, } /* + * 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 diff --git a/gst/camerabin/gstcamerabin.h b/gst/camerabin/gstcamerabin.h index 16b242c0..cd88c7ca 100644 --- a/gst/camerabin/gstcamerabin.h +++ b/gst/camerabin/gstcamerabin.h @@ -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__ */ diff --git a/tests/check/elements/camerabin.c b/tests/check/elements/camerabin.c index 7c6187d2..2b72c6ff 100644 --- a/tests/check/elements/camerabin.c +++ b/tests/check/elements/camerabin.c @@ -40,13 +40,11 @@ #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); |