camerabin: preview image sending optimization

* 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
This commit is contained in:
Lasse Laukkanen 2009-05-27 11:33:01 +03:00 committed by Stefan Kost
parent bbf48697fb
commit 6a47f6f594
8 changed files with 826 additions and 312 deletions

View file

@ -18,6 +18,7 @@ libgstcamerabin_la_SOURCES = gstcamerabin.c \
camerabinimage.c \ camerabinimage.c \
camerabinvideo.c \ camerabinvideo.c \
camerabingeneral.c \ camerabingeneral.c \
camerabinpreview.c \
gstcamerabinphotography.c gstcamerabinphotography.c
nodist_libgstcamerabin_la_SOURCES = $(built_sources) nodist_libgstcamerabin_la_SOURCES = $(built_sources)
@ -36,4 +37,5 @@ noinst_HEADERS = gstcamerabin.h \
camerabinimage.h \ camerabinimage.h \
camerabinvideo.h \ camerabinvideo.h \
camerabingeneral.h \ camerabingeneral.h \
camerabinpreview.h \
gstcamerabinphotography.h gstcamerabinphotography.h

View file

@ -30,17 +30,13 @@
* <informalexample> * <informalexample>
* <programlisting> * <programlisting>
*----------------------------------------------------------------------------- *-----------------------------------------------------------------------------
* (src0) -> queue -> *
* -> [post proc] -> tee < * -> [post proc] -> csp -> imageenc -> metadatamuxer -> filesink
* (src1) -> imageenc -> metadatamuxer -> filesink *
*----------------------------------------------------------------------------- *-----------------------------------------------------------------------------
* </programlisting> * </programlisting>
* </informalexample> * </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 opens file for image writing in READY to PAUSED state change.
* The image bin closes the file in PAUSED to READY 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->filename = g_string_new ("");
img->pad_tee_enc = NULL;
img->pad_tee_view = NULL;
img->post = NULL; img->post = NULL;
img->tee = NULL;
img->enc = NULL; img->enc = NULL;
img->user_enc = NULL; img->user_enc = NULL;
img->meta_mux = NULL; img->meta_mux = NULL;
img->sink = NULL; img->sink = NULL;
img->queue = NULL;
/* Create src and sink ghost pads */ /* Create src and sink ghost pads */
img->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); img->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK);
gst_element_add_pad (GST_ELEMENT (img), img->sinkpad); 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; img->elements_created = FALSE;
} }
@ -378,17 +366,17 @@ done:
* Use gst_camerabin_image_destroy_elements to release these resources. * Use gst_camerabin_image_destroy_elements to release these resources.
* *
* Image bin: * Image bin:
* img->sinkpad ! [ post process !] tee name=t0 ! encoder ! metadata ! filesink * img->sinkpad ! [ post process !] csp ! encoder ! metadata ! filesink
* t0. ! queue ! img->srcpad
* *
* Returns: %TRUE if succeeded or FALSE if failed * Returns: %TRUE if succeeded or FALSE if failed
*/ */
static gboolean static gboolean
gst_camerabin_image_create_elements (GstCameraBinImage * img) 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; gboolean ret = FALSE;
GstBin *imgbin = NULL; GstBin *imgbin = NULL;
GstElement *csp = NULL;
g_return_val_if_fail (img != NULL, FALSE); 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"); img_sinkpad = gst_element_get_static_pad (img->post, "sink");
} }
/* Create tee */ /* Add colorspace converter */
if (!(img->tee = gst_camerabin_create_and_add_element (imgbin, "tee"))) { if (!(csp =
gst_camerabin_create_and_add_element (imgbin, "ffmpegcolorspace"))) {
goto done; goto done;
} }
/* Set up sink ghost pad for img bin */ /* Set up sink ghost pad for img bin */
if (!img_sinkpad) { 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); 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 */ /* Create image encoder */
if (img->user_enc) { if (img->user_enc) {
img->enc = img->user_enc; img->enc = img->user_enc;
@ -461,33 +444,14 @@ gst_camerabin_image_create_elements (GstCameraBinImage * img)
goto done; 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 */ /* Set properties */
g_object_set (G_OBJECT (img->sink), "location", img->filename->str, NULL); 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->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; ret = TRUE;
done: done:
if (img_srcpad) {
gst_object_unref (img_srcpad);
}
if (img_sinkpad) { if (img_sinkpad) {
gst_object_unref (img_sinkpad); gst_object_unref (img_sinkpad);
} }
@ -511,26 +475,14 @@ static void
gst_camerabin_image_destroy_elements (GstCameraBinImage * img) gst_camerabin_image_destroy_elements (GstCameraBinImage * img)
{ {
GST_LOG ("destroying img elements"); 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->sinkpad), NULL);
gst_ghost_pad_set_target (GST_GHOST_PAD (img->srcpad), NULL);
gst_camerabin_remove_elements_from_bin (GST_BIN (img)); gst_camerabin_remove_elements_from_bin (GST_BIN (img));
img->tee = NULL;
img->enc = NULL; img->enc = NULL;
img->meta_mux = NULL; img->meta_mux = NULL;
img->sink = NULL; img->sink = NULL;
img->queue = NULL;
img->elements_created = FALSE; img->elements_created = FALSE;
} }
@ -538,7 +490,7 @@ gst_camerabin_image_destroy_elements (GstCameraBinImage * img)
void void
gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder) 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) if (img->user_enc)
gst_object_unref (img->user_enc); gst_object_unref (img->user_enc);
if (encoder) if (encoder)
@ -551,7 +503,7 @@ void
gst_camerabin_image_set_postproc (GstCameraBinImage * img, gst_camerabin_image_set_postproc (GstCameraBinImage * img,
GstElement * postproc) 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) if (img->post)
gst_object_unref (img->post); gst_object_unref (img->post);
if (postproc) if (postproc)

View file

@ -24,20 +24,17 @@
#include <gst/gstbin.h> #include <gst/gstbin.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define GST_TYPE_CAMERABIN_IMAGE (gst_camerabin_image_get_type()) #define GST_TYPE_CAMERABIN_IMAGE (gst_camerabin_image_get_type())
#define GST_CAMERABIN_IMAGE_CAST(obj) ((GstCameraBinImage*)(obj)) #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(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_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(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)) #define GST_IS_CAMERABIN_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_IMAGE))
/** /**
* GstCameraBinImage: * GstCameraBinImage:
* *
* The opaque #GstCameraBinImage structure. * The opaque #GstCameraBinImage structure.
*/ */
typedef struct _GstCameraBinImage GstCameraBinImage; typedef struct _GstCameraBinImage GstCameraBinImage;
typedef struct _GstCameraBinImageClass GstCameraBinImageClass; typedef struct _GstCameraBinImageClass GstCameraBinImageClass;
@ -48,21 +45,12 @@ struct _GstCameraBinImage
/* Ghost pads of image bin */ /* Ghost pads of image bin */
GstPad *sinkpad; 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 *post;
GstElement *tee;
GstElement *enc; GstElement *enc;
GstElement *user_enc; GstElement *user_enc;
GstElement *meta_mux; GstElement *meta_mux;
GstElement *sink; GstElement *sink;
GstElement *queue;
gboolean elements_created; gboolean elements_created;
}; };
@ -86,5 +74,4 @@ GstElement *gst_camerabin_image_get_encoder (GstCameraBinImage * img);
GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img); GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img);
G_END_DECLS G_END_DECLS
#endif /* #ifndef __CAMERABIN_IMAGE_H__ */
#endif /* #ifndef __CAMERABIN_IMAGE_H__ */

View file

@ -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;
}
}

View file

@ -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__ */

View file

@ -36,18 +36,19 @@
* <refsect2> * <refsect2>
* <title>Example launch line</title> * <title>Example launch line</title>
* |[ * |[
* gst-launch -v -m camerabin filename=test.jpeg * gst-launch -v -m camerabin
* ]| * ]|
* </refsect2> * </refsect2>
* <refsect2> * <refsect2>
* <title>Image capture</title> * <title>Image capture</title>
* <para> * <para>
* Taking still images is initiated with the #GstCameraBin::user-start action * Taking still images is initiated with the #GstCameraBin::user-start action
* signal. Once the image has captured, #GstCameraBin::img-done signal is fired. * signal. Once the image has been captured, "image-captured" gst message is
* It allows to decide wheter to take another picture (burst capture, bracketing * posted to the bus and capturing another image is possible. If application
* shot) or stop capturing. The last captured image is shown * has set #GstCameraBin:preview-caps property, then a "preview-image" gst
* until one switches back to view finder using #GstCameraBin::user-stop action * message is posted to bus containing preview image formatted according to
* signal. * specified caps. Eventually when image has been saved #GstCameraBin::img-done
* signal is emitted.
* *
* Available resolutions can be taken from the #GstCameraBin:inputcaps property. * Available resolutions can be taken from the #GstCameraBin:inputcaps property.
* Image capture resolution can be set with #GstCameraBin::user-image-res * Image capture resolution can be set with #GstCameraBin::user-image-res
@ -106,16 +107,23 @@
/* /*
* The pipeline in the camerabin is * The pipeline in the camerabin is
* *
* "image bin" * videosrc [ ! ffmpegcsp ] ! capsfilter ! crop ! scale ! capsfilter ! \
* videosrc ! crop ! scale ! out-sel <------> in-sel ! scale ! ffmpegcsp ! vfsink * out-sel name=osel ! queue name=img_q
* "video bin"
* *
* it is possible to have 'ffmpegcolorspace' and 'capsfilter' just after * View finder:
* v4l2camsrc * 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: * The properties of elements are:
* *
* vfsink - "sync", FALSE, "qos", FALSE * vfsink - "sync", FALSE, "qos", FALSE, "async", FALSE
* output-selector - "resend-latest", FALSE * output-selector - "resend-latest", FALSE
* input-selector - "select-all", TRUE * input-selector - "select-all", TRUE
*/ */
@ -139,6 +147,7 @@
#include "gstcamerabinphotography.h" #include "gstcamerabinphotography.h"
#include "camerabingeneral.h" #include "camerabingeneral.h"
#include "camerabinpreview.h"
#include "gstcamerabin-marshal.h" #include "gstcamerabin-marshal.h"
@ -176,7 +185,8 @@ enum
ARG_VIDEO_SRC, ARG_VIDEO_SRC,
ARG_AUDIO_SRC, ARG_AUDIO_SRC,
ARG_INPUT_CAPS, 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 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 * static helper functions declaration
*/ */
@ -258,6 +273,12 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
static gboolean static gboolean
gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data); 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); 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"))) gst_camerabin_create_and_add_element (cbin, "output-selector")))
goto done; 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 */ /* Set default "driver-name" for v4l2camsrc if not set */
if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src),
"driver-name")) { "driver-name")) {
@ -633,14 +648,16 @@ camerabin_create_view_elements (GstCameraBin * camera)
&& (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) { && (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) {
pads = g_list_next (pads); 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 = if (!(camera->view_scale =
gst_camerabin_create_and_add_element (GST_BIN (camera), gst_camerabin_create_and_add_element (GST_BIN (camera),
"videoscale"))) { "videoscale"))) {
goto error; goto error;
} }
/* Add capsfilter to maintain aspect ratio while scaling */
if (!(camera->aspect_filter = if (!(camera->aspect_filter =
gst_camerabin_create_and_add_element (GST_BIN (camera), gst_camerabin_create_and_add_element (GST_BIN (camera),
"capsfilter"))) { "capsfilter"))) {
@ -690,30 +707,44 @@ camerabin_create_elements (GstCameraBin * camera)
goto done; goto done;
} }
/* Add image bin */
camera->pad_src_img = camera->pad_src_img =
gst_element_get_request_pad (camera->src_out_sel, "src%d"); 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, gst_pad_add_buffer_probe (camera->pad_src_img,
G_CALLBACK (gst_camerabin_have_img_buffer), camera); G_CALLBACK (gst_camerabin_have_img_buffer), camera);
/* Create view finder elements, this also links it to image bin */ /* Add image queue */
if (!camerabin_create_view_elements (camera)) { if (!(camera->img_queue =
GST_WARNING_OBJECT (camera, "creating view failed"); 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; goto done;
} }
/* Link output selector ! view_finder */
camera->pad_src_view = camera->pad_src_view =
gst_element_get_request_pad (camera->src_out_sel, "src%d"); 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"); /* Create view finder elements */
link_ret = gst_pad_link (camera->pad_src_view, camera->pad_view_src); if (!camerabin_create_view_elements (camera)) {
if (GST_PAD_LINK_FAILED (link_ret)) { GST_WARNING_OBJECT (camera, "creating view finder elements failed");
GST_ELEMENT_ERROR (camera, CORE, NEGOTIATION,
("linking view finder failed"), (NULL));
goto done; goto done;
} }
@ -772,10 +803,6 @@ camerabin_destroy_elements (GstCameraBin * camera)
gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_vid); gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_vid);
camera->pad_src_vid = NULL; 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) { if (camera->pad_src_img) {
gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_img); gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_img);
camera->pad_src_img = NULL; camera->pad_src_img = NULL;
@ -789,14 +816,9 @@ camerabin_destroy_elements (GstCameraBin * camera)
camera->pad_src_view = NULL; camera->pad_src_view = NULL;
} }
if (camera->srcpad_zoom_filter) { if (camera->pad_src_queue) {
gst_object_unref (camera->srcpad_zoom_filter); gst_object_unref (camera->pad_src_queue);
camera->srcpad_zoom_filter = NULL; camera->pad_src_queue = NULL;
}
if (camera->srcpad_videosrc) {
gst_object_unref (camera->srcpad_videosrc);
camera->srcpad_videosrc = NULL;
} }
camera->view_sink = NULL; camera->view_sink = NULL;
@ -861,13 +883,23 @@ camerabin_dispose_elements (GstCameraBin * camera)
gst_caps_unref (camera->allowed_caps); gst_caps_unref (camera->allowed_caps);
camera->allowed_caps = NULL; 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: * gst_camerabin_image_capture_continue:
* @camera: camerabin object * @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. * 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); gst_element_set_state (camera->active_bin, GST_STATE_NULL);
} }
if (camera->mode == MODE_IMAGE) { if (camera->mode == MODE_IMAGE) {
GstStateChangeReturn state_ret;
camera->active_bin = camera->imgbin; 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) { } else if (camera->mode == MODE_VIDEO) {
camera->active_bin = camera->vidbin; camera->active_bin = camera->vidbin;
} }
@ -1109,6 +1150,46 @@ failed:
return caps; 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: * gst_camerabin_rewrite_tags_to_bin:
* @bin: bin holding tag setter elements * @bin: bin holding tag setter elements
@ -1271,7 +1352,13 @@ gst_camerabin_rewrite_tags (GstCameraBin * camera)
} }
/* Write tags */ /* 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); 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, g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE,
"active-pad", camera->pad_src_img, NULL); "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) { if (!wait_for_prepare) {
gst_camerabin_rewrite_tags (camera); /* Image queue's srcpad data probe will set imagebin to PLAYING */
state_ret = gst_element_set_state (camera->imgbin, GST_STATE_PLAYING); state_ret = gst_element_set_state (camera->imgbin, GST_STATE_READY);
if (state_ret != GST_STATE_CHANGE_FAILURE) { if (state_ret != GST_STATE_CHANGE_FAILURE) {
g_mutex_lock (camera->capture_mutex); g_mutex_lock (camera->capture_mutex);
g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", TRUE, 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", GST_DEBUG_OBJECT (camera, "%s %s:%s",
blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad)); blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad));
}
if (blocked && (pad == camera->srcpad_videosrc)) { /*
/* Send eos and block until image bin reaches eos */ * gst_camerabin_send_preview:
GST_DEBUG_OBJECT (camera, "sending eos to image bin"); * @camera: camerabin object
gst_element_send_event (camera->imgbin, gst_event_new_eos ()); * @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;
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 * @buffer: still image frame
* @u_data: camera bin object * @u_data: camera bin object
* *
* Buffer probe called before sending each buffer to image bin. * Buffer probe called before sending each buffer to image queue.
* * Generates and sends preview image as gst message if requested.
* 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.
*/ */
static gboolean static gboolean
gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer, gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data) gpointer u_data)
{ {
GstCameraBin *camera = (GstCameraBin *) u_data; GstCameraBin *camera = (GstCameraBin *) u_data;
GstStructure *fn_ev_struct = NULL;
gboolean ret = TRUE; gboolean ret = TRUE;
GstPad *os_sink = NULL;
GST_LOG ("got buffer #%d %p with size %d", camera->num_img_buffers, GST_LOG ("got buffer %p with size %d", buffer, GST_BUFFER_SIZE (buffer));
buffer, GST_BUFFER_SIZE (buffer));
/* Image filename should be set by now */ /* Image filename should be set by now */
if (g_str_equal (camera->filename->str, "")) { if (g_str_equal (camera->filename->str, "")) {
@ -1619,54 +1736,38 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
goto done; goto done;
} }
/* Check for first buffer after capture start, we want to if (camera->preview_caps) {
pass it forward directly. */ gst_camerabin_send_preview (camera, buffer);
if (!camera->num_img_buffers) {
goto done;
} }
/* Close the file of saved image */ gst_camerabin_rewrite_tags (camera);
gst_element_set_state (camera->imgbin, GST_STATE_READY);
/* Reset filename to force application set new filename */ /* Send a custom event which tells the filename to image queue */
g_string_assign (camera->filename, ""); /* 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);
/* Check if the application wants to continue */ /* Add buffer probe to outputselector's sink pad. It sends
ret = gst_camerabin_image_capture_continue (camera); EOS event to image queue. */
os_sink = gst_element_get_static_pad (camera->src_out_sel, "sink");
if (ret && !camera->stop_requested) { camera->image_captured_id = gst_pad_add_buffer_probe (os_sink,
GST_DEBUG_OBJECT (camera, "capturing image \"%s\"", camera->filename->str); G_CALLBACK (gst_camerabin_have_src_buffer), camera);
g_object_set (G_OBJECT (camera->imgbin), "filename", gst_object_unref (os_sink);
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);
/* 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 */
g_mutex_lock (camera->capture_mutex);
camera->capturing = FALSE;
g_cond_signal (camera->cond);
g_mutex_unlock (camera->capture_mutex);
}
done: done:
if (ret) { /* HACK: v4l2camsrc changes to view finder resolution automatically
camera->num_img_buffers++; after one captured still image */
/* Block when next buffer arrives, we want to push eos event gst_camerabin_finish_image_capture (camera);
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);
}
return ret; gst_camerabin_reset_to_view_finder (camera);
GST_DEBUG_OBJECT (camera, "switched back to viewfinder");
return TRUE;
} }
/* /*
@ -1695,6 +1796,120 @@ gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
return ret; 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: * gst_camerabin_reset_to_view_finder:
* @camera: camerabin object * @camera: camerabin object
@ -1708,8 +1923,8 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
GstStateChangeReturn state_ret; GstStateChangeReturn state_ret;
GST_DEBUG_OBJECT (camera, "resetting"); GST_DEBUG_OBJECT (camera, "resetting");
/* Set active bin to READY state */ /* Set video bin to READY state */
if (camera->active_bin) { if (camera->active_bin == camera->vidbin) {
state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY); state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY);
if (state_ret == GST_STATE_CHANGE_FAILURE) { if (state_ret == GST_STATE_CHANGE_FAILURE) {
GST_WARNING_OBJECT (camera, "state change failed"); GST_WARNING_OBJECT (camera, "state change failed");
@ -1719,7 +1934,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
} }
/* Reset counters and flags */ /* Reset counters and flags */
camera->num_img_buffers = 0;
camera->stop_requested = FALSE; camera->stop_requested = FALSE;
camera->paused = FALSE; camera->paused = FALSE;
@ -1729,18 +1943,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera)
"active-pad", camera->pad_src_view, NULL); "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 */ /* Enable view finder mode in v4l2camsrc */
if (camera->src_vid_src && if (camera->src_vid_src &&
g_object_class_find_property (G_OBJECT_GET_CLASS (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: * GstCameraBin:filter-caps:
* *
* Filter video source element caps using this property. * Caps applied to capsfilter element after videosrc [ ! ffmpegcsp ].
* This is an alternative to #GstCamerabin::user-res-fps action * You can use this e.g. to make sure video color format matches with
* signal that allows more fine grained control of video source. * 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_object_class_install_property (gobject_class, ARG_FILTER_CAPS,
g_param_spec_boxed ("filter-caps", "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)); GST_TYPE_CAPS, G_PARAM_READWRITE));
/** /**
@ -2408,9 +2625,7 @@ gst_camerabin_class_init (GstCameraBinClass * klass)
* @camera: the camera bin element * @camera: the camera bin element
* @filename: the name of the file just saved * @filename: the name of the file just saved
* *
* Signal emitted when the file has just been saved. To continue taking * Signal emitted when the file has just been saved.
* pictures set new filename using #GstCameraBin:filename property and return
* TRUE, otherwise return FALSE.
* *
* Don't call any #GstCameraBin method from this signal, if you do so there * Don't call any #GstCameraBin method from this signal, if you do so there
* will be a deadlock. * will be a deadlock.
@ -2455,7 +2670,6 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
camera->filename = g_string_new (""); camera->filename = g_string_new ("");
camera->mode = DEFAULT_MODE; camera->mode = DEFAULT_MODE;
camera->num_img_buffers = 0;
camera->stop_requested = FALSE; camera->stop_requested = FALSE;
camera->paused = FALSE; camera->paused = FALSE;
camera->capturing = FALSE; camera->capturing = FALSE;
@ -2466,6 +2680,8 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
camera->fps_n = DEFAULT_FPS_N; camera->fps_n = DEFAULT_FPS_N;
camera->fps_d = DEFAULT_FPS_D; camera->fps_d = DEFAULT_FPS_D;
camera->event_tags = gst_tag_list_new ();
camera->image_capture_caps = NULL; camera->image_capture_caps = NULL;
camera->view_finder_caps = NULL; camera->view_finder_caps = NULL;
camera->allowed_caps = NULL; camera->allowed_caps = NULL;
@ -2480,13 +2696,9 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
camera->pad_src_view = NULL; camera->pad_src_view = NULL;
camera->pad_view_src = NULL; camera->pad_view_src = NULL;
camera->pad_src_img = NULL; camera->pad_src_img = NULL;
camera->pad_view_img = NULL;
camera->pad_src_vid = NULL; camera->pad_src_vid = NULL;
camera->pad_view_vid = NULL; camera->pad_view_vid = NULL;
camera->srcpad_zoom_filter = NULL;
camera->srcpad_videosrc = NULL;
/* source elements */ /* source elements */
camera->src_vid_src = NULL; camera->src_vid_src = NULL;
camera->src_filter = NULL; camera->src_filter = NULL;
@ -2530,6 +2742,8 @@ gst_camerabin_dispose (GObject * object)
gst_element_set_state (camera->vidbin, GST_STATE_NULL); gst_element_set_state (camera->vidbin, GST_STATE_NULL);
gst_object_unref (camera->vidbin); gst_object_unref (camera->vidbin);
gst_camerabin_preview_destroy_pipeline (camera);
camerabin_destroy_elements (camera); camerabin_destroy_elements (camera);
camerabin_dispose_elements (camera); camerabin_dispose_elements (camera);
@ -2590,7 +2804,7 @@ gst_camerabin_set_property (GObject * object, guint prop_id,
break; break;
case ARG_VIDEO_MUX: case ARG_VIDEO_MUX:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { 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"); "can't use set element until next video bin NULL to READY state change");
} }
gst_camerabin_video_set_muxer (GST_CAMERABIN_VIDEO (camera->vidbin), 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)); camera->view_finder_caps = gst_caps_copy (gst_value_get_caps (value));
GST_OBJECT_UNLOCK (camera); 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; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 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: case ARG_FILTER_CAPS:
gst_value_set_caps (value, camera->view_finder_caps); gst_value_set_caps (value, camera->view_finder_caps);
break; break;
case ARG_PREVIEW_CAPS:
gst_value_set_caps (value, camera->preview_caps);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -2795,6 +3023,29 @@ done:
return ret; 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 * 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)) { } else if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->imgbin)) {
/* Image eos */ /* Image eos */
GST_DEBUG_OBJECT (camera, "got image eos message"); GST_DEBUG_OBJECT (camera, "got image eos message");
g_idle_add (gst_camerabin_imgbin_finished, camera);
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);
} }
break; break;
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
@ -2878,12 +3124,11 @@ gst_camerabin_user_start (GstCameraBin * camera)
g_mutex_unlock (camera->capture_mutex); g_mutex_unlock (camera->capture_mutex);
if (camera->active_bin) { if (camera->active_bin) {
g_object_set (G_OBJECT (camera->active_bin), "filename",
camera->filename->str, NULL);
if (camera->active_bin == camera->imgbin) { if (camera->active_bin == camera->imgbin) {
gst_camerabin_start_image_capture (camera); gst_camerabin_start_image_capture (camera);
} else if (camera->active_bin == camera->vidbin) { } 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); gst_camerabin_start_video_recording (camera);
} }
} }
@ -2892,10 +3137,13 @@ gst_camerabin_user_start (GstCameraBin * camera)
static void static void
gst_camerabin_user_stop (GstCameraBin * camera) gst_camerabin_user_stop (GstCameraBin * camera)
{ {
GST_INFO_OBJECT (camera, "stopping %s capture", if (camera->active_bin == camera->vidbin) {
camera->mode ? "video" : "image"); GST_INFO_OBJECT (camera, "stopping video capture");
gst_camerabin_do_stop (camera); gst_camerabin_do_stop (camera);
gst_camerabin_reset_to_view_finder (camera); gst_camerabin_reset_to_view_finder (camera);
} else {
GST_INFO_OBJECT (camera, "stopping image capture isn't needed");
}
} }
static void static void

View file

@ -59,7 +59,6 @@ struct _GstCameraBin
/* private */ /* private */
GString *filename; GString *filename;
gint mode; /* MODE_IMAGE or MODE_VIDEO */ 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 stop_requested; /* TRUE if capturing stop needed */
gboolean paused; /* TRUE if capturing paused */ gboolean paused; /* TRUE if capturing paused */
@ -69,6 +68,9 @@ struct _GstCameraBin
gint fps_n; gint fps_n;
gint fps_d; gint fps_d;
/* Image tags are collected here first before sending to imgbin */
GstTagList *event_tags;
/* Caps applied to capsfilters when taking still image */ /* Caps applied to capsfilters when taking still image */
GstCaps *image_capture_caps; GstCaps *image_capture_caps;
@ -78,6 +80,9 @@ struct _GstCameraBin
/* Caps that videosrc supports */ /* Caps that videosrc supports */
GstCaps *allowed_caps; GstCaps *allowed_caps;
/* Caps used to create preview image */
GstCaps *preview_caps;
/* The digital zoom (from 100% to 1000%) */ /* The digital zoom (from 100% to 1000%) */
gint zoom; gint zoom;
@ -90,16 +95,16 @@ struct _GstCameraBin
GstPad *pad_src_view; GstPad *pad_src_view;
GstPad *pad_view_src; GstPad *pad_view_src;
GstPad *pad_src_img; GstPad *pad_src_img;
GstPad *pad_view_img;
GstPad *pad_src_vid; GstPad *pad_src_vid;
GstPad *pad_view_vid; GstPad *pad_view_vid;
GstPad *pad_src_queue;
GstPad *srcpad_zoom_filter; GstElement *img_queue; /* queue for decoupling capture from
GstPad *srcpad_videosrc; image-postprocessing and saving */
GstElement *imgbin; /* bin that holds image capturing elements */ GstElement *imgbin; /* bin that holds image capturing elements */
GstElement *vidbin; /* bin that holds video capturing elements */ GstElement *vidbin; /* bin that holds video capturing elements */
GstElement *active_bin; /* image or video bin that is currently in use */ GstElement *active_bin; /* image or video bin that is currently in use */
GstElement *preview_pipeline; /* pipeline for creating preview images */
/* source elements */ /* source elements */
GstElement *src_vid_src; GstElement *src_vid_src;
@ -126,6 +131,9 @@ struct _GstCameraBin
/* Cache the photography interface settings */ /* Cache the photography interface settings */
GstPhotoSettings photo_settings; GstPhotoSettings photo_settings;
/* Buffer probe id for captured image handling */
gulong image_captured_id;
}; };
/** /**
@ -148,7 +156,7 @@ struct _GstCameraBinClass
/* signals (callback) */ /* 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); GType gst_camerabin_get_type (void);
G_END_DECLS G_END_DECLS
#endif /* #ifndef __GST_CAMERABIN_H__ */ #endif /* #ifndef __GST_CAMERABIN_H__ */

View file

@ -40,13 +40,11 @@
#define MAX_BURST_IMAGES 10 #define MAX_BURST_IMAGES 10
#define PHOTO_SETTING_DELAY_US 0 #define PHOTO_SETTING_DELAY_US 0
static gboolean continuous = FALSE;
static guint captured_images = 0;
static GstElement *camera; static GstElement *camera;
static GCond *cam_cond; static GCond *cam_cond;
static GMutex *cam_mutex; static GMutex *cam_mutex;
static GMainLoop *main_loop;
static guint cycle_count = 0;
/* helper function for filenames */ /* helper function for filenames */
static const gchar * static const gchar *
@ -61,6 +59,8 @@ make_test_file_name (const gchar * base_name)
return file_name; return file_name;
} }
/* burst capture is not supported in camerabin for the moment */
#ifdef ENABLE_BURST_CAPTURE
static const gchar * static const gchar *
make_test_seq_file_name (const gchar * base_name) 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); GST_INFO ("capturing to: %s", file_name);
return file_name; return file_name;
} }
#endif
/* signal handlers */ /* signal handlers */
static gboolean
handle_image_captured_cb (gpointer data)
{
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);
cycle_count--;
}
GST_DEBUG ("handle_image_captured_cb done");
return FALSE;
}
static gboolean static gboolean
capture_done (GstElement * elem, const gchar * filename, gpointer user_data) capture_done (GstElement * elem, const gchar * filename, gpointer user_data)
{ {
captured_images++; GMainLoop *loop = (GMainLoop *) user_data;
if (captured_images >= MAX_BURST_IMAGES) { g_idle_add ((GSourceFunc) handle_image_captured_cb, loop);
/* 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;
}
if (continuous) { GST_DEBUG ("image saved");
/* Must set filename for new picture */
g_object_set (G_OBJECT (elem), "filename",
make_test_seq_file_name (BURST_IMAGE_FILENAME), NULL);
}
return continuous; return FALSE;
} }
/* configuration */ /* configuration */
@ -103,7 +122,8 @@ capture_done (GstElement * elem, const gchar * filename, gpointer user_data)
static void static void
setup_camerabin_elements (GstElement * camera) setup_camerabin_elements (GstElement * camera)
{ {
GstElement *vfsink, *audiosrc, *videosrc; GstElement *vfsink, *audiosrc, *videosrc, *audioenc, *videoenc, *imageenc,
*videomux;
/* Use fakesink for view finder */ /* Use fakesink for view finder */
vfsink = gst_element_factory_make ("fakesink", NULL); vfsink = gst_element_factory_make ("fakesink", NULL);
@ -111,9 +131,42 @@ setup_camerabin_elements (GstElement * camera)
g_object_set (audiosrc, "is-live", TRUE, NULL); g_object_set (audiosrc, "is-live", TRUE, NULL);
videosrc = gst_element_factory_make ("videotestsrc", NULL); videosrc = gst_element_factory_make ("videotestsrc", NULL);
g_object_set (videosrc, "is-live", TRUE, 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);
g_object_set (camera, "vfsink", vfsink, "audiosrc", audiosrc, if (vfsink && audiosrc && videosrc && audioenc && videoenc && videomux
"videosrc", videosrc, NULL); && 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;
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 static void
@ -121,6 +174,10 @@ setup (void)
{ {
GstTagSetter *setter; GstTagSetter *setter;
gchar *desc_str; gchar *desc_str;
GstCaps *filter_caps;
GstBus *bus;
main_loop = g_main_loop_new (NULL, TRUE);
cam_cond = g_cond_new (); cam_cond = g_cond_new ();
cam_mutex = g_mutex_new (); cam_mutex = g_mutex_new ();
@ -129,9 +186,15 @@ setup (void)
setup_camerabin_elements (camera); 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);
captured_images = 0; bus = gst_pipeline_get_bus (GST_PIPELINE (camera));
gst_bus_add_watch (bus, (GstBusFunc) capture_bus_cb, main_loop);
gst_object_unref (bus);
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 */ /* Set some default tags */
setter = GST_TAG_SETTER (camera); setter = GST_TAG_SETTER (camera);
@ -141,8 +204,8 @@ setup (void)
GST_TAG_DESCRIPTION, desc_str, NULL); GST_TAG_DESCRIPTION, desc_str, NULL);
g_free (desc_str); g_free (desc_str);
if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) != if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) ==
GST_STATE_CHANGE_SUCCESS) { GST_STATE_CHANGE_FAILURE) {
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL); gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
gst_object_unref (camera); gst_object_unref (camera);
camera = NULL; camera = NULL;
@ -314,39 +377,13 @@ GST_START_TEST (test_single_image_capture)
g_object_set (camera, "mode", 0, g_object_set (camera, "mode", 0,
"filename", make_test_file_name (SINGLE_IMAGE_FILENAME), NULL); "filename", make_test_file_name (SINGLE_IMAGE_FILENAME), NULL);
continuous = FALSE;
/* Test photography iface settings */ /* Test photography iface settings */
gst_element_get_state (GST_ELEMENT (camera), NULL, NULL, (2 * GST_SECOND)); gst_element_get_state (GST_ELEMENT (camera), NULL, NULL, (2 * GST_SECOND));
test_photography_settings (camera); test_photography_settings (camera);
g_signal_emit_by_name (camera, "user-start", 0); g_signal_emit_by_name (camera, "user-start", 0);
g_signal_emit_by_name (camera, "user-stop", 0);
}
GST_END_TEST; g_main_loop_run (main_loop);
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);
} }
GST_END_TEST; GST_END_TEST;
@ -370,48 +407,35 @@ GST_END_TEST;
GST_START_TEST (test_image_video_cycle) GST_START_TEST (test_image_video_cycle)
{ {
guint i;
if (!camera) if (!camera)
return; return;
continuous = FALSE; cycle_count = 2;
for (i = 0; i < 2; i++) { /* set still image mode */
/* Set still image mode */ g_object_set (camera, "mode", 0,
g_object_set (camera, "mode", 0, "filename", make_test_file_name (CYCLE_IMAGE_FILENAME), NULL);
"filename", make_test_file_name (CYCLE_IMAGE_FILENAME), NULL);
/* Take a picture */ g_signal_emit_by_name (camera, "user-start", 0);
g_signal_emit_by_name (camera, "user-start", 0);
g_signal_emit_by_name (camera, "user-stop", 0);
GST_DEBUG ("image captured");
/* Set video recording mode */ g_main_loop_run (main_loop);
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");
}
} }
GST_END_TEST; GST_END_TEST;
GST_START_TEST (validate_captured_image_files) GST_START_TEST (validate_captured_image_files)
{ {
GString *filename;
gint i;
if (!camera) if (!camera)
return; return;
/* validate single image */ /* validate single image */
check_file_validity (SINGLE_IMAGE_FILENAME); 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 */ /* validate burst mode images */
filename = g_string_new (""); filename = g_string_new ("");
for (i = 0; i < MAX_BURST_IMAGES; i++) { for (i = 0; i < MAX_BURST_IMAGES; i++) {
@ -419,7 +443,7 @@ GST_START_TEST (validate_captured_image_files)
check_file_validity (filename->str); check_file_validity (filename->str);
} }
g_string_free (filename, TRUE); g_string_free (filename, TRUE);
#endif
/* validate cycled image */ /* validate cycled image */
check_file_validity (CYCLE_IMAGE_FILENAME); check_file_validity (CYCLE_IMAGE_FILENAME);
} }
@ -453,7 +477,6 @@ camerabin_suite (void)
tcase_set_timeout (tc_basic, 20); tcase_set_timeout (tc_basic, 20);
tcase_add_checked_fixture (tc_basic, setup, teardown); tcase_add_checked_fixture (tc_basic, setup, teardown);
tcase_add_test (tc_basic, test_single_image_capture); 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_video_recording);
tcase_add_test (tc_basic, test_image_video_cycle); tcase_add_test (tc_basic, test_image_video_cycle);