gstreamer/gst/camerabin/gstcamerabin.c
Stefan Kost fab9dfa5d0 camerabin: rework signaling and tests
Revert the GString change. There are no marshallers for it. A better change is
now described in http://bugzilla.gnome.org/show_bug.cgi?id=573370.
Test should work again.
2009-02-27 12:35:14 +02:00

2902 lines
89 KiB
C

/*
* GStreamer
* Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-camerabin
*
* GstCameraBin is a high-level camera object that encapsulates the gstreamer
* internals and provides a task based API for the application. It consists of
* three main data paths: view-finder, image capture and video capture.
*
* <informalfigure>
* <mediaobject>
* <imageobject><imagedata fileref="camerabin.png"/></imageobject>
* <textobject><phrase>CameraBin structure</phrase></textobject>
* <caption><para>Structural decomposition of CameraBin object.</para></caption>
* </mediaobject>
* </informalfigure>
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch -v -m camerabin filename=test.jpeg
* ]|
* </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.
*
* Available resolutions can be taken from the #GstCameraBin:inputcaps property.
* Image capture resolution can be set with #GstCameraBin::user-image-res
* action signal.
* </para>
* </refsect2>
* <refsect2>
* <title>Video capture</title>
* <para>
* Video capture is started with the #GstCameraBin::user-start action signal too.
* In addition to image capture one can use #GstCameraBin::user-pause to
* pause recording and #GstCameraBin::user-stop to end recording.
*
* Available resolutions and fps can be taken from the #GstCameraBin:inputcaps
* property. #GstCameraBin::user-res-fps action signal can be used to set frame
* rate and resolution for the video recording and view finder as well.
* </para>
* </refsect2>
* <refsect2>
* <title>Photography interface</title>
* <para>
* GstCameraBin implements gst photography interface, which can be used to set
* and get different settings related to digital imaging. Since currently many
* of these settings require low-level support the photography interface support
* is dependent on video src element. In practice photography interface settings
* cannot be used successfully until in PAUSED state when the video src has
* opened the video device.
* </para>
* </refsect2>
* <refsect2>
* <title>States</title>
* <para>
* Elements within GstCameraBin are created and destroyed when switching
* between NULL and READY states. Therefore element properties should be set
* in NULL state. User set elements are not unreffed until GstCameraBin is
* unreffed or replaced by a new user set element. Initially only elements needed
* for view finder mode are created to speed up startup. Image bin and video bin
* elements are created when setting the mode or starting capture.
* </para>
* </refsect2>
* <refsect2>
* <note>
* <para>
* Since the muxers tested so far have problems with discontinous buffers, QoS
* has been disabled, and then in order to record video, you MUST ensure that
* there is enough CPU to encode the video. Thus choose smart resolution and
* frames per second values. It is also highly recommended to avoid color
* conversions; make sure all the elements involved work with the same colorspace
* (i.e. rgb or yuv i420 or whatelse).
* </para>
* </note>
* </refsect2>
*/
/*
* The pipeline in the camerabin is
*
* "image bin"
* videosrc ! crop ! scale ! out-sel <------> in-sel ! scale ! ffmpegcsp ! vfsink
* "video bin"
*
* it is possible to have 'ffmpegcolorspace' and 'capsfilter' just after
* v4l2camsrc
*
* The properties of elements are:
*
* vfsink - "sync", FALSE, "qos", FALSE
* output-selector - "resend-latest", FALSE
* input-selector - "select-all", TRUE
*/
/*
* includes
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <gst/gst.h>
/* FIXME: include #include <gst/gst-i18n-plugin.h> and use _(" ") */
#include "gstcamerabin.h"
#include "gstcamerabinxoverlay.h"
#include "gstcamerabincolorbalance.h"
#include "gstcamerabinphotography.h"
#include "camerabingeneral.h"
#include "gstcamerabin-marshal.h"
/*
* enum and types
*/
enum
{
/* action signals */
USER_START_SIGNAL,
USER_STOP_SIGNAL,
USER_PAUSE_SIGNAL,
USER_RES_FPS_SIGNAL,
USER_IMAGE_RES_SIGNAL,
/* emit signals */
IMG_DONE_SIGNAL,
LAST_SIGNAL
};
enum
{
ARG_0,
ARG_FILENAME,
ARG_MODE,
ARG_MUTE,
ARG_ZOOM,
ARG_IMAGE_POST,
ARG_IMAGE_ENC,
ARG_VIDEO_POST,
ARG_VIDEO_ENC,
ARG_AUDIO_ENC,
ARG_VIDEO_MUX,
ARG_VF_SINK,
ARG_VIDEO_SRC,
ARG_AUDIO_SRC,
ARG_INPUT_CAPS,
ARG_FILTER_CAPS
};
/*
* defines and static global vars
*/
static guint camerabin_signals[LAST_SIGNAL];
#define GST_TYPE_CAMERABIN_MODE (gst_camerabin_mode_get_type ())
/* default and range values for args */
#define DEFAULT_MODE MODE_IMAGE
#define DEFAULT_ZOOM 100
#define DEFAULT_WIDTH 640
#define DEFAULT_HEIGHT 480
#define DEFAULT_CAPTURE_WIDTH 800
#define DEFAULT_CAPTURE_HEIGHT 600
#define DEFAULT_FPS_N 0 /* makes it use the default */
#define DEFAULT_FPS_D 1
#define CAMERABIN_DEFAULT_VF_CAPS "video/x-raw-yuv,format=(fourcc)I420"
/* Using "bilinear" as default zoom method */
#define CAMERABIN_DEFAULT_ZOOM_METHOD 1
#define MIN_ZOOM 100
#define MAX_ZOOM 1000
#define ZOOM_1X MIN_ZOOM
#define DEFAULT_V4L2CAMSRC_DRIVER_NAME "omap3cam"
/* internal element names */
#define USE_COLOR_CONVERTER 1
/* FIXME: Make sure this can work with autovideosrc and use that. */
#define DEFAULT_SRC_VID_SRC "v4l2src"
#define DEFAULT_VIEW_SINK "autovideosink"
/*
* static helper functions declaration
*/
static void camerabin_setup_src_elements (GstCameraBin * camera);
static gboolean camerabin_create_src_elements (GstCameraBin * camera);
static void camerabin_setup_view_elements (GstCameraBin * camera);
static gboolean camerabin_create_view_elements (GstCameraBin * camera);
static gboolean camerabin_create_elements (GstCameraBin * camera);
static void camerabin_destroy_elements (GstCameraBin * camera);
static void camerabin_dispose_elements (GstCameraBin * camera);
static void gst_camerabin_change_mode (GstCameraBin * camera, gint mode);
static void
gst_camerabin_change_filename (GstCameraBin * camera, const gchar * name);
static void gst_camerabin_setup_zoom (GstCameraBin * camera);
static GstCaps *gst_camerabin_get_allowed_input_caps (GstCameraBin * camera);
static void gst_camerabin_rewrite_tags (GstCameraBin * camera);
static void
gst_camerabin_set_capsfilter_caps (GstCameraBin * camera, GstCaps * new_caps);
static void gst_camerabin_start_image_capture (GstCameraBin * camera);
static void gst_camerabin_start_video_recording (GstCameraBin * camera);
static gboolean
gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data);
static gboolean
gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data);
static void gst_camerabin_reset_to_view_finder (GstCameraBin * camera);
static void gst_camerabin_do_stop (GstCameraBin * camera);
static void
gst_camerabin_set_allowed_framerate (GstCameraBin * camera,
GstCaps * filter_caps);
static const GValue *gst_camerabin_find_better_framerate (GstCameraBin * camera,
GstStructure * st, const GValue * orig_framerate);
static void
gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps);
/*
* GObject callback functions declaration
*/
static void gst_camerabin_base_init (gpointer gclass);
static void gst_camerabin_class_init (GstCameraBinClass * klass);
static void
gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass);
static void gst_camerabin_dispose (GObject * object);
static void gst_camerabin_finalize (GObject * object);
static void gst_camerabin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_camerabin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
/*
* GstElement function declarations
*/
static GstStateChangeReturn
gst_camerabin_change_state (GstElement * element, GstStateChange transition);
/*
* GstBin function declarations
*/
static void
gst_camerabin_handle_message_func (GstBin * bin, GstMessage * message);
/*
* Action signal function declarations
*/
static void gst_camerabin_user_start (GstCameraBin * camera);
static void gst_camerabin_user_stop (GstCameraBin * camera);
static void gst_camerabin_user_pause (GstCameraBin * camera);
static void
gst_camerabin_user_res_fps (GstCameraBin * camera, gint width, gint height,
gint fps_n, gint fps_d);
static void
gst_camerabin_user_image_res (GstCameraBin * camera, gint width, gint height);
/*
* GST BOILERPLATE and GObject types
*/
static GType
gst_camerabin_mode_get_type (void)
{
static GType gtype = 0;
if (gtype == 0) {
static const GEnumValue values[] = {
{MODE_IMAGE, "Still image capture (default)", "mode-image"},
{MODE_VIDEO, "Video recording", "mode-video"},
{0, NULL, NULL}
};
gtype = g_enum_register_static ("GstCameraBinMode", values);
}
return gtype;
}
static gboolean
gst_camerabin_iface_supported (GstImplementsInterface * iface, GType iface_type)
{
GstCameraBin *camera = GST_CAMERABIN (iface);
if (iface_type == GST_TYPE_X_OVERLAY) {
if (camera->view_sink) {
return GST_IS_X_OVERLAY (camera->view_sink);
}
} else if (iface_type == GST_TYPE_COLOR_BALANCE) {
if (camera->src_vid_src) {
return GST_IS_COLOR_BALANCE (camera->src_vid_src);
}
} else if (iface_type == GST_TYPE_TAG_SETTER) {
/* Note: Tag setter elements aren't
present when image and video bin in NULL */
GstElement *setter;
setter = gst_bin_get_by_interface (GST_BIN (camera), iface_type);
if (setter) {
gst_object_unref (setter);
return TRUE;
} else {
return FALSE;
}
} else if (iface_type == GST_TYPE_PHOTOGRAPHY) {
if (camera->src_vid_src) {
return GST_IS_PHOTOGRAPHY (camera->src_vid_src);
}
}
return FALSE;
}
static void
gst_camerabin_interface_init (GstImplementsInterfaceClass * klass)
{
/*
* default virtual functions
*/
klass->supported = gst_camerabin_iface_supported;
}
static void
camerabin_init_interfaces (GType type)
{
static const GInterfaceInfo camerabin_info = {
(GInterfaceInitFunc) gst_camerabin_interface_init,
NULL,
NULL,
};
static const GInterfaceInfo camerabin_xoverlay_info = {
(GInterfaceInitFunc) gst_camerabin_xoverlay_init,
NULL,
NULL,
};
static const GInterfaceInfo camerabin_color_balance_info = {
(GInterfaceInitFunc) gst_camerabin_color_balance_init,
NULL,
NULL,
};
static const GInterfaceInfo camerabin_tagsetter_info = {
NULL,
NULL,
NULL,
};
static const GInterfaceInfo camerabin_photography_info = {
(GInterfaceInitFunc) gst_camerabin_photography_init,
NULL,
NULL,
};
g_type_add_interface_static (type,
GST_TYPE_IMPLEMENTS_INTERFACE, &camerabin_info);
g_type_add_interface_static (type, GST_TYPE_X_OVERLAY,
&camerabin_xoverlay_info);
g_type_add_interface_static (type, GST_TYPE_COLOR_BALANCE,
&camerabin_color_balance_info);
g_type_add_interface_static (type, GST_TYPE_TAG_SETTER,
&camerabin_tagsetter_info);
g_type_add_interface_static (type, GST_TYPE_PHOTOGRAPHY,
&camerabin_photography_info);
}
GST_BOILERPLATE_FULL (GstCameraBin, gst_camerabin, GstPipeline,
GST_TYPE_PIPELINE, camerabin_init_interfaces);
/*
* static helper functions implementation
*/
/*
* camerabin_setup_src_elements:
* @camera: camerabin object
*
* This function updates camerabin capsfilters according
* to fps, resolution and zoom that have been configured
* to camerabin.
*/
static void
camerabin_setup_src_elements (GstCameraBin * camera)
{
GstStructure *st;
GstCaps *new_caps;
gboolean detect_framerate = FALSE;
if (!camera->view_finder_caps) {
st = gst_structure_from_string (CAMERABIN_DEFAULT_VF_CAPS, NULL);
} else {
st = gst_structure_copy (gst_caps_get_structure (camera->view_finder_caps,
0));
}
if (camera->width > 0 && camera->height > 0) {
gst_structure_set (st,
"width", G_TYPE_INT, camera->width,
"height", G_TYPE_INT, camera->height, NULL);
}
if (camera->fps_n > 0 && camera->fps_d > 0) {
if (camera->night_mode) {
GST_WARNING_OBJECT (camera,
"night mode, lowest allowed fps will be forced");
camera->pre_night_fps_n = camera->fps_n;
camera->pre_night_fps_d = camera->fps_d;
detect_framerate = TRUE;
} else {
gst_structure_set (st,
"framerate", GST_TYPE_FRACTION, camera->fps_n, camera->fps_d, NULL);
new_caps = gst_caps_new_full (st, NULL);
}
} else {
GST_DEBUG_OBJECT (camera, "no framerate specified");
detect_framerate = TRUE;
}
if (detect_framerate) {
GST_DEBUG_OBJECT (camera, "detecting allowed framerate");
/* Remove old framerate if any */
if (gst_structure_has_field (st, "framerate")) {
gst_structure_remove_field (st, "framerate");
}
new_caps = gst_caps_new_full (st, NULL);
/* Set allowed framerate for the resolution */
gst_camerabin_set_allowed_framerate (camera, new_caps);
}
/* Set default zoom method */
g_object_set (camera->src_zoom_scale, "method",
CAMERABIN_DEFAULT_ZOOM_METHOD, NULL);
gst_caps_replace (&camera->view_finder_caps, new_caps);
/* Set caps for view finder mode */
gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps);
}
/*
* camerabin_create_src_elements:
* @camera: camerabin object
*
* This function creates and links upstream side elements for camerabin.
* videosrc ! cspconv ! capsfilter ! crop ! scale ! capsfilter ! out-sel !
*
* Returns: TRUE, if elements were successfully created, FALSE otherwise
*/
static gboolean
camerabin_create_src_elements (GstCameraBin * camera)
{
gboolean ret = FALSE;
GstBin *cbin = GST_BIN (camera);
gchar *driver_name = NULL;
if (camera->user_vid_src) {
camera->src_vid_src = camera->user_vid_src;
if (!gst_camerabin_add_element (cbin, camera->src_vid_src)) {
camera->src_vid_src = NULL;
goto done;
}
} else if (!(camera->src_vid_src =
gst_camerabin_create_and_add_element (cbin, DEFAULT_SRC_VID_SRC)))
goto done;
#ifdef USE_COLOR_CONVERTER
if (!gst_camerabin_create_and_add_element (cbin, "ffmpegcolorspace"))
goto done;
#endif
if (!(camera->src_filter =
gst_camerabin_create_and_add_element (cbin, "capsfilter")))
goto done;
if (!(camera->src_zoom_crop =
gst_camerabin_create_and_add_element (cbin, "videocrop")))
goto done;
if (!(camera->src_zoom_scale =
gst_camerabin_create_and_add_element (cbin, "videoscale")))
goto done;
if (!(camera->src_zoom_filter =
gst_camerabin_create_and_add_element (cbin, "capsfilter")))
goto done;
if (!(camera->src_out_sel =
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")) {
g_object_get (G_OBJECT (camera->src_vid_src), "driver-name",
&driver_name, NULL);
if (!driver_name) {
g_object_set (G_OBJECT (camera->src_vid_src), "driver-name",
DEFAULT_V4L2CAMSRC_DRIVER_NAME, NULL);
}
}
ret = TRUE;
done:
return ret;
}
/*
* camerabin_setup_view_elements:
* @camera: camerabin object
*
* This function configures properties for view finder sink element.
*/
static void
camerabin_setup_view_elements (GstCameraBin * camera)
{
GST_DEBUG_OBJECT (camera, "setting view finder properties");
g_object_set (G_OBJECT (camera->view_in_sel), "select-all", TRUE, NULL);
/* Set properties for view finder sink */
/* Find the actual sink if using bin like autovideosink */
if (GST_IS_BIN (camera->view_sink)) {
GList *child = NULL, *children = GST_BIN_CHILDREN (camera->view_sink);
for (child = children; child != NULL; child = g_list_next (children)) {
GObject *ch = G_OBJECT (child->data);
if (g_object_class_find_property (G_OBJECT_GET_CLASS (ch), "sync")) {
g_object_set (G_OBJECT (ch), "sync", FALSE, "qos", FALSE, "async",
FALSE, NULL);
}
}
} else {
g_object_set (G_OBJECT (camera->view_sink), "sync", FALSE, "qos", FALSE,
"async", FALSE, NULL);
}
}
/*
* camerabin_create_view_elements:
* @camera: camerabin object
*
* This function creates and links downstream side elements for camerabin.
* ! scale ! cspconv ! view finder sink
*
* Returns: TRUE, if elements were successfully created, FALSE otherwise
*/
static gboolean
camerabin_create_view_elements (GstCameraBin * camera)
{
const GList *pads;
if (!(camera->view_in_sel =
gst_camerabin_create_and_add_element (GST_BIN (camera),
"input-selector"))) {
goto error;
}
/* Look for recently added input selector sink pad, we need to release it later */
pads = GST_ELEMENT_PADS (camera->view_in_sel);
while (pads != NULL
&& (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) {
pads = g_list_next (pads);
}
camera->pad_view_img = GST_PAD (pads->data);
if (!(camera->view_scale =
gst_camerabin_create_and_add_element (GST_BIN (camera),
"videoscale"))) {
goto error;
}
if (!(camera->aspect_filter =
gst_camerabin_create_and_add_element (GST_BIN (camera),
"capsfilter"))) {
goto error;
}
#ifdef USE_COLOR_CONVERTER
if (!gst_camerabin_create_and_add_element (GST_BIN (camera),
"ffmpegcolorspace")) {
goto error;
}
#endif
if (camera->user_vf_sink) {
camera->view_sink = camera->user_vf_sink;
if (!gst_camerabin_add_element (GST_BIN (camera), camera->view_sink)) {
goto error;
}
} else if (!(camera->view_sink =
gst_camerabin_create_and_add_element (GST_BIN (camera),
DEFAULT_VIEW_SINK))) {
goto error;
}
return TRUE;
error:
return FALSE;
}
/*
* camerabin_create_elements:
* @camera: camerabin object
*
* This function creates and links all elements for camerabin,
*
* Returns: TRUE, if elements were successfully created, FALSE otherwise
*/
static gboolean
camerabin_create_elements (GstCameraBin * camera)
{
gboolean ret = FALSE;
GstPadLinkReturn link_ret = GST_PAD_LINK_REFUSED;
GstPad *unconnected_pad;
GST_LOG_OBJECT (camera, "creating elems");
/* Create "src" elements */
if (!camerabin_create_src_elements (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");
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));
goto done;
}
/* Set view finder active as default */
g_object_set (G_OBJECT (camera->src_out_sel), "active-pad",
camera->pad_src_view, NULL);
/* Add video bin */
camera->pad_src_vid =
gst_element_get_request_pad (camera->src_out_sel, "src%d");
if (!gst_camerabin_add_element (GST_BIN (camera), camera->vidbin)) {
goto done;
}
gst_pad_add_buffer_probe (camera->pad_src_vid,
G_CALLBACK (gst_camerabin_have_vid_buffer), camera);
/* Link video bin ! view finder */
unconnected_pad = gst_bin_find_unlinked_pad (GST_BIN (camera), GST_PAD_SRC);
camera->pad_view_vid =
gst_element_get_request_pad (camera->view_in_sel, "sink%d");
link_ret = gst_pad_link (unconnected_pad, camera->pad_view_vid);
gst_object_unref (unconnected_pad);
if (GST_PAD_LINK_FAILED (link_ret)) {
GST_ELEMENT_ERROR (camera, CORE, NEGOTIATION, (NULL),
("linking video bin and view finder failed"));
goto done;
}
ret = TRUE;
done:
if (FALSE == ret)
camerabin_destroy_elements (camera);
return ret;
}
/*
* camerabin_destroy_elements:
* @camera: camerabin object
*
* This function removes all elements from camerabin.
*/
static void
camerabin_destroy_elements (GstCameraBin * camera)
{
GST_DEBUG_OBJECT (camera, "destroying elements");
/* Release request pads */
if (camera->pad_view_vid) {
gst_element_release_request_pad (camera->view_in_sel, camera->pad_view_vid);
camera->pad_view_vid = NULL;
}
if (camera->pad_src_vid) {
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;
}
if (camera->pad_view_src) {
gst_element_release_request_pad (camera->view_in_sel, camera->pad_view_src);
camera->pad_view_src = NULL;
}
if (camera->pad_src_view) {
gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_view);
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;
}
camera->view_sink = NULL;
camera->view_scale = NULL;
camera->view_in_sel = NULL;
camera->src_out_sel = NULL;
camera->src_filter = NULL;
camera->src_zoom_crop = NULL;
camera->src_zoom_scale = NULL;
camera->src_zoom_filter = NULL;
camera->src_vid_src = NULL;
camera->active_bin = NULL;
/* Remove elements */
gst_camerabin_remove_elements_from_bin (GST_BIN (camera));
}
/*
* camerabin_dispose_elements:
* @camera: camerabin object
*
* This function releases all allocated camerabin resources.
*/
static void
camerabin_dispose_elements (GstCameraBin * camera)
{
if (camera->capture_mutex) {
g_mutex_free (camera->capture_mutex);
camera->capture_mutex = NULL;
}
if (camera->cond) {
g_cond_free (camera->cond);
camera->cond = NULL;
}
if (camera->filename) {
g_string_free (camera->filename, TRUE);
camera->filename = NULL;
}
/* Unref user set elements */
if (camera->user_vf_sink) {
gst_object_unref (camera->user_vf_sink);
camera->user_vf_sink = NULL;
}
if (camera->user_vid_src) {
gst_object_unref (camera->user_vid_src);
camera->user_vid_src = NULL;
}
if (camera->image_capture_caps) {
gst_caps_unref (camera->image_capture_caps);
camera->image_capture_caps = NULL;
}
if (camera->view_finder_caps) {
gst_caps_unref (camera->view_finder_caps);
camera->view_finder_caps = NULL;
}
if (camera->allowed_caps) {
gst_caps_unref (camera->allowed_caps);
camera->allowed_caps = NULL;
}
}
/*
* gst_camerabin_image_capture_continue:
* @camera: camerabin object
* @filename: new filename set by user
* @cont: TRUE to continue image capture, FALSE otherwise
*
* Check if user wants to continue image capturing by using g_signal.
*/
static void
gst_camerabin_image_capture_continue (GstCameraBin * camera, GString * filename,
gboolean * cont)
{
GST_DEBUG_OBJECT (camera, "emitting img_done signal, filename: %s",
filename->str);
g_signal_emit (G_OBJECT (camera), camerabin_signals[IMG_DONE_SIGNAL], 0,
filename, cont);
GST_DEBUG_OBJECT (camera, "emitted img_done, new filename: %s, continue: %d",
filename->str, *cont);
}
/*
* gst_camerabin_change_mode:
* @camera: camerabin object
* @mode: image or video mode
*
* Change camerabin mode between image and video capture.
* Changing mode will stop ongoing capture.
*/
static void
gst_camerabin_change_mode (GstCameraBin * camera, gint mode)
{
if (camera->mode != mode || !camera->active_bin) {
GST_DEBUG_OBJECT (camera, "setting mode: %d", mode);
/* Interrupt ongoing capture */
gst_camerabin_do_stop (camera);
camera->mode = mode;
if (camera->active_bin) {
gst_element_set_state (camera->active_bin, GST_STATE_NULL);
}
if (camera->mode == MODE_IMAGE) {
camera->active_bin = camera->imgbin;
} else if (camera->mode == MODE_VIDEO) {
camera->active_bin = camera->vidbin;
}
gst_camerabin_reset_to_view_finder (camera);
}
}
/*
* gst_camerabin_change_filename:
* @camera: camerabin object
* @name: new filename for capture
*
* Change filename for image or video capture.
* Changing filename will stop ongoing capture.
*/
static void
gst_camerabin_change_filename (GstCameraBin * camera, const gchar * name)
{
if (0 != strcmp (camera->filename->str, name)) {
GST_DEBUG_OBJECT (camera, "changing filename from %s to %s",
camera->filename->str, name);
/* Interrupt ongoing capture */
gst_camerabin_do_stop (camera);
gst_camerabin_reset_to_view_finder (camera);
if (camera->active_bin) {
g_object_set (G_OBJECT (camera->active_bin), "filename", name, NULL);
}
g_string_assign (camera->filename, name);
}
}
/*
* gst_camerabin_setup_zoom:
* @camera: camerabin object
*
* Apply zoom configured to camerabin to capture.
*/
static void
gst_camerabin_setup_zoom (GstCameraBin * camera)
{
gint zoom;
gboolean done = FALSE;
g_return_if_fail (camera != NULL);
g_return_if_fail (camera->src_zoom_crop != NULL);
zoom = g_atomic_int_get (&camera->zoom);
g_return_if_fail (zoom);
if (GST_IS_ELEMENT (camera->src_vid_src) &&
gst_element_implements_interface (camera->src_vid_src,
GST_TYPE_PHOTOGRAPHY)) {
/* Try setting (hardware) zoom using photography interface */
GstPhotography *photo;
GstPhotoCaps pcaps;
photo = GST_PHOTOGRAPHY (camera->src_vid_src);
pcaps = gst_photography_get_capabilities (photo);
if (pcaps & GST_PHOTOGRAPHY_CAPS_ZOOM) {
done = gst_photography_set_zoom (photo, (gfloat) zoom / 100.0);
}
}
if (!done) {
/* Update capsfilters to apply the (software) zoom */
gint w2_crop = 0;
gint h2_crop = 0;
GstPad *pad_zoom_sink = NULL;
GST_INFO_OBJECT (camera, "zoom: %d, orig size: %dx%d", zoom,
camera->width, camera->height);
if (zoom != ZOOM_1X) {
w2_crop = (camera->width - (camera->width * ZOOM_1X / zoom)) / 2;
h2_crop = (camera->height - (camera->height * ZOOM_1X / zoom)) / 2;
}
pad_zoom_sink = gst_element_get_static_pad (camera->src_zoom_crop, "sink");
GST_INFO_OBJECT (camera,
"sw cropping: left:%d, right:%d, top:%d, bottom:%d", w2_crop, w2_crop,
h2_crop, h2_crop);
GST_PAD_STREAM_LOCK (pad_zoom_sink);
g_object_set (camera->src_zoom_crop, "left", w2_crop, "right", w2_crop,
"top", h2_crop, "bottom", h2_crop, NULL);
GST_PAD_STREAM_UNLOCK (pad_zoom_sink);
gst_object_unref (pad_zoom_sink);
}
GST_LOG_OBJECT (camera, "zoom set");
}
/*
* gst_camerabin_get_allowed_input_caps:
* @camera: camerabin object
*
* Retrieve caps from videosrc describing formats it supports
*
* Returns: caps object from videosrc
*/
static GstCaps *
gst_camerabin_get_allowed_input_caps (GstCameraBin * camera)
{
GstCaps *caps = NULL;
GstPad *pad = NULL, *peer_pad = NULL;
GstState state;
gboolean temp_videosrc_pause = FALSE;
GstElement *videosrc;
g_return_val_if_fail (camera != NULL, NULL);
videosrc = camera->src_vid_src ? camera->src_vid_src : camera->user_vid_src;
if (!videosrc) {
GST_WARNING_OBJECT (camera, "no videosrc, can't get allowed caps");
goto failed;
}
if (camera->allowed_caps) {
GST_DEBUG_OBJECT (camera, "returning cached caps");
goto done;
}
pad = gst_element_get_static_pad (videosrc, "src");
if (!pad) {
GST_WARNING_OBJECT (camera, "no srcpad in videosrc");
goto failed;
}
state = GST_STATE (videosrc);
/* Make this function work also in READY and NULL state */
if (state == GST_STATE_READY || state == GST_STATE_NULL) {
GST_DEBUG_OBJECT (camera, "setting videosrc to paused temporarily");
temp_videosrc_pause = TRUE;
peer_pad = gst_pad_get_peer (pad);
if (peer_pad) {
gst_pad_unlink (pad, peer_pad);
}
/* Set videosrc to PAUSED to open video device */
gst_element_set_locked_state (videosrc, TRUE);
gst_element_set_state (videosrc, GST_STATE_PAUSED);
}
camera->allowed_caps = gst_pad_get_caps (pad);
/* Restore state and re-link if necessary */
if (temp_videosrc_pause) {
GST_DEBUG_OBJECT (camera, "restoring videosrc state %d", state);
/* Reset videosrc to NULL state, some drivers seem to need this */
gst_element_set_state (videosrc, GST_STATE_NULL);
gst_element_set_state (videosrc, state);
if (peer_pad) {
gst_pad_link (pad, peer_pad);
gst_object_unref (peer_pad);
}
gst_element_set_locked_state (videosrc, FALSE);
}
gst_object_unref (pad);
done:
if (camera->allowed_caps) {
caps = gst_caps_copy (camera->allowed_caps);
}
failed:
GST_INFO_OBJECT (camera, "allowed caps:%" GST_PTR_FORMAT, caps);
return caps;
}
/*
* gst_camerabin_rewrite_tags_to_bin:
* @bin: bin holding tag setter elements
* @list: tag list to be written
*
* This function looks for certain tag setters from given bin
* and REPLACES ALL setter tags with given tag list
*
*/
static void
gst_camerabin_rewrite_tags_to_bin (GstBin * bin, const GstTagList * list)
{
GstElement *setter;
GstElementFactory *setter_factory;
const gchar *klass;
GstIterator *iter;
GstIteratorResult res = GST_ITERATOR_OK;
gpointer data;
iter = gst_bin_iterate_all_by_interface (bin, GST_TYPE_TAG_SETTER);
while (res == GST_ITERATOR_OK || res == GST_ITERATOR_RESYNC) {
res = gst_iterator_next (iter, &data);
switch (res) {
case GST_ITERATOR_DONE:
break;
case GST_ITERATOR_RESYNC:
gst_iterator_resync (iter);
break;
case GST_ITERATOR_ERROR:
GST_WARNING ("error iterating tag setters");
break;
case GST_ITERATOR_OK:
setter = GST_ELEMENT (data);
GST_LOG ("iterating tag setters: %" GST_PTR_FORMAT, setter);
setter_factory = gst_element_get_factory (setter);
klass = gst_element_factory_get_klass (setter_factory);
/* FIXME: check if tags should be written to all tag setters,
set tags only to Muxer elements for now */
if (g_strrstr (klass, "Muxer")) {
GST_DEBUG ("replacement tags %" GST_PTR_FORMAT, list);
gst_tag_setter_merge_tags (GST_TAG_SETTER (setter), list,
GST_TAG_MERGE_REPLACE_ALL);
}
gst_object_unref (setter);
break;
default:
break;
}
}
gst_iterator_free (iter);
}
/*
* gst_camerabin_get_internal_tags:
* @camera: the camera bin element
*
* Returns tag list containing metadata from camerabin
* and it's elements
*/
static GstTagList *
gst_camerabin_get_internal_tags (GstCameraBin * camera)
{
GstTagList *list = gst_tag_list_new ();
GstColorBalance *balance = NULL;
const GList *controls = NULL, *item;
GstColorBalanceChannel *channel;
gint min_value, max_value, mid_value, cur_value;
if (camera->active_bin == camera->vidbin) {
/* FIXME: check if internal video tag setting is needed */
goto done;
}
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"image-width", camera->width, "image-height", camera->height, NULL);
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"capture-digital-zoom", camera->zoom, 100, NULL);
if (gst_element_implements_interface (GST_ELEMENT (camera),
GST_TYPE_COLOR_BALANCE)) {
balance = GST_COLOR_BALANCE (camera);
}
if (balance) {
controls = gst_color_balance_list_channels (balance);
}
for (item = controls; item; item = g_list_next (item)) {
channel = item->data;
min_value = channel->min_value;
max_value = channel->max_value;
/* the default value would probably better */
mid_value = min_value + ((max_value - min_value) / 2);
cur_value = gst_color_balance_get_value (balance, channel);
if (!g_ascii_strcasecmp (channel->label, "brightness")) {
/* The value of brightness. The unit is the APEX value (Additive System of Photographic Exposure).
* Ordinarily it is given in the range of -99.99 to 99.99. Note that
* if the numerator of the recorded value is 0xFFFFFFFF, Unknown shall be indicated.
*
* BrightnessValue (Bv) = log2 ( B/NK )
* Note that: B:cd/cm² (candela per square centimeter), N,K: constant
*
* http://johnlind.tripod.com/science/scienceexposure.html
*
*/
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"capture-brightness", cur_value, 1, NULL);
} else if (!g_ascii_strcasecmp (channel->label, "contrast")) {
/* 0 = Normal, 1 = Soft, 2 = Hard */
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"capture-contrast",
(cur_value == mid_value) ? 0 : ((cur_value < mid_value) ? 1 : 2),
NULL);
} else if (!g_ascii_strcasecmp (channel->label, "gain")) {
/* 0 = Normal, 1 = Low Up, 2 = High Up, 3 = Low Down, 4 = Hight Down */
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"capture-gain",
(guint) (cur_value == mid_value) ? 0 : ((cur_value <
mid_value) ? 1 : 3), NULL);
} else if (!g_ascii_strcasecmp (channel->label, "saturation")) {
/* 0 = Normal, 1 = Low, 2 = High */
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
"capture-saturation",
(cur_value == mid_value) ? 0 : ((cur_value < mid_value) ? 1 : 2),
NULL);
}
}
done:
return list;
}
/*
* gst_camerabin_rewrite_tags:
* @camera: the camera bin element
*
* Merges application set tags to camerabin internal tags,
* and writes them using image or video bin tag setters.
*/
static void
gst_camerabin_rewrite_tags (GstCameraBin * camera)
{
const GstTagList *app_tag_list = NULL;
GstTagList *list = NULL;
/* Get application set tags */
app_tag_list = gst_tag_setter_get_tag_list (GST_TAG_SETTER (camera));
/* Get tags from camerabin and it's elements */
list = gst_camerabin_get_internal_tags (camera);
if (app_tag_list) {
gst_tag_list_insert (list, app_tag_list, GST_TAG_MERGE_REPLACE);
}
/* Write tags */
gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list);
gst_tag_list_free (list);
}
/*
* gst_camerabin_set_capsfilter_caps:
* @camera: camerabin object
* @new_caps: pointer to caps object to set
*
* Set given caps to camerabin capsfilters.
*/
static void
gst_camerabin_set_capsfilter_caps (GstCameraBin * camera, GstCaps * new_caps)
{
GstStructure *st;
GST_INFO_OBJECT (camera, "new_caps:%" GST_PTR_FORMAT, new_caps);
st = gst_caps_get_structure (new_caps, 0);
gst_structure_get_int (st, "width", &camera->width);
gst_structure_get_int (st, "height", &camera->height);
if (gst_structure_has_field (st, "framerate")) {
gst_structure_get_fraction (st, "framerate", &camera->fps_n,
&camera->fps_d);
}
/* Update zoom */
gst_camerabin_setup_zoom (camera);
/* Update capsfilters */
g_object_set (G_OBJECT (camera->src_filter), "caps", new_caps, NULL);
g_object_set (G_OBJECT (camera->src_zoom_filter), "caps", new_caps, NULL);
gst_camerabin_update_aspect_filter (camera, new_caps);
}
static void
gst_camerabin_adapt_video_resolution (GstCameraBin * camera, GstCaps * caps)
{
GstStructure *st;
gint width = 0, height = 0;
GstCaps *filter_caps = NULL;
/* Get width and height from caps */
st = gst_caps_get_structure (caps, 0);
gst_structure_get_int (st, "width", &width);
gst_structure_get_int (st, "height", &height);
GST_DEBUG_OBJECT (camera,
"changing %dx%d -> %dx%d filter to %" GST_PTR_FORMAT,
camera->width, camera->height, width, height, camera->src_filter);
/* Apply the width and height to filter caps */
g_object_get (G_OBJECT (camera->src_filter), "caps", &filter_caps, NULL);
filter_caps = gst_caps_make_writable (filter_caps);
gst_caps_set_simple (filter_caps, "width", G_TYPE_INT, width,
"height", G_TYPE_INT, height, NULL);
g_object_set (G_OBJECT (camera->src_filter), "caps", filter_caps, NULL);
gst_caps_unref (filter_caps);
/* FIXME: implement cropping according to requested aspect ratio */
}
/*
* img_capture_prepared:
* @data: camerabin object
* @caps: caps describing the prepared image format
*
* Callback which is called after image capture has been prepared.
*/
static void
img_capture_prepared (gpointer data, GstCaps * caps)
{
GstCameraBin *camera = GST_CAMERABIN (data);
GstStructure *st, *new_st;
gint i;
const gchar *field_name;
GST_INFO_OBJECT (camera, "image capture prepared");
/* It is possible we are about to get something else that we requested */
if (!gst_caps_is_equal (camera->image_capture_caps, caps)) {
/* If capture preparation has added new fields to requested caps,
we need to copy them */
st = gst_caps_get_structure (camera->image_capture_caps, 0);
new_st = gst_structure_copy (st);
st = gst_caps_get_structure (caps, 0);
for (i = 0; i < gst_structure_n_fields (st); i++) {
field_name = gst_structure_nth_field_name (st, i);
if (!gst_structure_has_field (new_st, field_name)) {
GST_DEBUG_OBJECT (camera, "new field in prepared caps: %s", field_name);
gst_structure_set_value (new_st, field_name,
gst_structure_get_value (st, field_name));
}
}
gst_caps_replace (&camera->image_capture_caps,
gst_caps_new_full (new_st, NULL));
}
/* Update capsfilters */
gst_camerabin_set_capsfilter_caps (camera, camera->image_capture_caps);
/* If incoming buffer resolution is different from what application
requested, then we need to fix this in camerabin */
gst_camerabin_adapt_video_resolution (camera, 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);
}
/*
* gst_camerabin_start_image_capture:
* @camera: camerabin object
*
* Initiates image capture.
*/
static void
gst_camerabin_start_image_capture (GstCameraBin * camera)
{
GstStateChangeReturn state_ret;
gboolean wait_for_prepare = FALSE, ret = FALSE;
GST_INFO_OBJECT (camera, "starting image capture");
if (GST_IS_ELEMENT (camera->src_vid_src) &&
gst_element_implements_interface (camera->src_vid_src,
GST_TYPE_PHOTOGRAPHY)) {
/* Start image capture preparations using photography iface */
wait_for_prepare = TRUE;
g_mutex_lock (camera->capture_mutex);
/* Enable still image capture mode in v4l2camsrc */
if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src),
"capture-mode")) {
g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 1, NULL);
}
if (!camera->image_capture_caps) {
camera->image_capture_caps = gst_caps_copy (camera->view_finder_caps);
}
/* Start preparations for image capture */
GST_DEBUG_OBJECT (camera, "prepare image capture caps %" GST_PTR_FORMAT,
camera->image_capture_caps);
ret =
gst_photography_prepare_for_capture (GST_PHOTOGRAPHY
(camera->src_vid_src), (GstPhotoCapturePrepared) img_capture_prepared,
camera->image_capture_caps, camera);
camera->capturing = TRUE;
g_mutex_unlock (camera->capture_mutex);
}
if (!wait_for_prepare) {
gst_camerabin_rewrite_tags (camera);
state_ret = gst_element_set_state (camera->imgbin, GST_STATE_PLAYING);
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,
"active-pad", camera->pad_src_img, NULL);
camera->capturing = TRUE;
ret = TRUE;
g_mutex_unlock (camera->capture_mutex);
} else {
GST_WARNING_OBJECT (camera, "imagebin state change failed");
gst_element_set_state (camera->imgbin, GST_STATE_NULL);
}
}
if (!ret) {
GST_WARNING_OBJECT (camera, "starting image capture failed");
}
}
/*
* gst_camerabin_start_video_recording:
* @camera: camerabin object
*
* Initiates video recording.
*/
static void
gst_camerabin_start_video_recording (GstCameraBin * camera)
{
GstStateChangeReturn state_ret;
/* FIXME: how to ensure resolution and fps is supported by CPU?
* use a queue overrun signal?
*/
GST_INFO_OBJECT (camera, "starting video capture");
gst_camerabin_rewrite_tags (camera);
/* Pause the pipeline in order to distribute new clock in paused_to_playing */
/* audio src timestamps will be 0 without state change to READY. ??? */
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_READY);
gst_element_set_locked_state (camera->vidbin, FALSE);
state_ret = gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED);
if (state_ret != GST_STATE_CHANGE_FAILURE) {
g_mutex_lock (camera->capture_mutex);
g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE,
"active-pad", camera->pad_src_vid, NULL);
/* Enable video mode in v4l2camsrc */
if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src),
"capture-mode")) {
g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 2, NULL);
}
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING);
gst_element_set_locked_state (camera->vidbin, TRUE);
camera->capturing = TRUE;
g_mutex_unlock (camera->capture_mutex);
} else {
GST_WARNING_OBJECT (camera, "videobin state change failed");
gst_element_set_state (camera->vidbin, GST_STATE_NULL);
gst_camerabin_reset_to_view_finder (camera);
}
}
/*
* gst_camerabin_send_video_eos:
* @camera: camerabin object
*
* Generate and send eos event to video bin in order to
* finish recording properly.
*/
static void
gst_camerabin_send_video_eos (GstCameraBin * camera)
{
GstPad *videopad;
g_return_if_fail (camera != NULL);
/* Send eos event to video bin */
GST_INFO_OBJECT (camera, "sending eos to videobin");
videopad = gst_element_get_static_pad (camera->vidbin, "sink");
gst_pad_send_event (videopad, gst_event_new_eos ());
gst_object_unref (videopad);
}
/*
* image_pad_blocked:
* @pad: pad to block/unblock
* @blocked: TRUE to block, FALSE to unblock
* @u_data: camera bin object
*
* Sends eos event to image bin if blocking pad leading to image bin.
* The pad will be unblocked when image bin posts eos message.
*/
static void
image_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data)
{
GstCameraBin *camera;
camera = (GstCameraBin *) user_data;
GST_DEBUG_OBJECT (camera, "%s %s:%s",
blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad));
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_camerabin_have_img_buffer:
* @pad: output-selector src pad leading to image bin
* @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.
*/
static gboolean
gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data)
{
GstCameraBin *camera = (GstCameraBin *) u_data;
gboolean ret = TRUE;
GST_LOG ("got buffer #%d %p with size %d", camera->num_img_buffers,
buffer, GST_BUFFER_SIZE (buffer));
/* Image filename should be set by now */
if (g_str_equal (camera->filename->str, "")) {
GST_DEBUG_OBJECT (camera, "filename not set, dropping buffer");
ret = FALSE;
goto done;
}
/* Check for first buffer after capture start, we want to
pass it forward directly. */
if (!camera->num_img_buffers) {
goto done;
}
/* Close the file of saved image */
gst_element_set_state (camera->imgbin, GST_STATE_READY);
/* Check if the application wants to continue */
gst_camerabin_image_capture_continue (camera, camera->filename, &ret);
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);
/* Reset filename to force application set new filename */
g_string_assign (camera->filename, "");
/* 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:
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);
}
return ret;
}
/*
* gst_camerabin_have_vid_buffer:
* @pad: output-selector src pad leading to video bin
* @buffer: buffer pushed to the pad
* @u_data: camerabin object
*
* Buffer probe for src pad leading to video bin.
* Sends eos event to video bin if stop requested and drops
* all buffers after this.
*/
static gboolean
gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer,
gpointer u_data)
{
GstCameraBin *camera = (GstCameraBin *) u_data;
gboolean ret = TRUE;
GST_LOG ("got video buffer %p with size %d",
buffer, GST_BUFFER_SIZE (buffer));
if (camera->stop_requested) {
gst_camerabin_send_video_eos (camera);
ret = FALSE; /* Drop buffer */
}
return ret;
}
/*
* gst_camerabin_reset_to_view_finder:
* @camera: camerabin object
*
* Stop capturing and set camerabin to view finder mode.
* Reset capture counters and flags.
*/
static void
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) {
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;
}
}
/* Reset counters and flags */
camera->num_img_buffers = 0;
camera->stop_requested = FALSE;
camera->paused = FALSE;
if (camera->src_out_sel) {
/* Set selector to forward data to view finder */
g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE,
"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),
"capture-mode")) {
g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 0, NULL);
}
GST_DEBUG_OBJECT (camera, "reset done");
}
/*
* gst_camerabin_do_stop:
* @camera: camerabin object
*
* Raise flag to indicate to image and video bin capture stop.
* Stopping paused video recording handled as a special case.
* Wait for ongoing capturing to finish.
*/
static void
gst_camerabin_do_stop (GstCameraBin * camera)
{
g_mutex_lock (camera->capture_mutex);
if (camera->capturing) {
GST_DEBUG_OBJECT (camera, "mark stop");
camera->stop_requested = TRUE;
/* Take special care when stopping paused video capture */
if ((camera->active_bin == camera->vidbin) && camera->paused) {
/* Send eos event to video bin before setting it to playing */
gst_camerabin_send_video_eos (camera);
/* We must change to playing now in order to get video bin eos events
and buffered data through and finish recording properly */
gst_element_set_state (GST_ELEMENT (camera->vidbin), GST_STATE_PLAYING);
camera->paused = FALSE;
}
GST_DEBUG_OBJECT (camera, "waiting for capturing to finish");
g_cond_wait (camera->cond, camera->capture_mutex);
GST_DEBUG_OBJECT (camera, "capturing finished");
}
g_mutex_unlock (camera->capture_mutex);
}
/*
* gst_camerabin_default_signal_img_done:
* @camera: camerabin object
* @fname: new filename
*
* Default handler for #GstCameraBin::img-done signal,
* stops always capture.
*
* Returns: FALSE always
*/
static gboolean
gst_camerabin_default_signal_img_done (GstCameraBin * camera, GString * fname)
{
return FALSE;
}
/*
* gst_camerabin_set_allowed_framerate:
* @camera: camerabin object
* @filter_caps: update allowed framerate to these caps
*
* Find allowed frame rate from video source that matches with
* resolution in @filter_caps. Set found frame rate to @filter_caps.
*/
static void
gst_camerabin_set_allowed_framerate (GstCameraBin * camera,
GstCaps * filter_caps)
{
GstStructure *structure;
GstCaps *allowed_caps = NULL, *intersect = NULL;
const GValue *framerate = NULL;
guint caps_size, i;
/* Get supported caps from video src that matches with new filter caps */
GST_INFO_OBJECT (camera, "filter caps:%" GST_PTR_FORMAT, filter_caps);
allowed_caps = gst_camerabin_get_allowed_input_caps (camera);
intersect = gst_caps_intersect (allowed_caps, filter_caps);
GST_INFO_OBJECT (camera, "intersect caps:%" GST_PTR_FORMAT, intersect);
/* Find the best framerate from the caps */
caps_size = gst_caps_get_size (intersect);
for (i = 0; i < caps_size; i++) {
structure = gst_caps_get_structure (intersect, i);
framerate =
gst_camerabin_find_better_framerate (camera, structure, framerate);
}
if (GST_VALUE_HOLDS_FRACTION (framerate)) {
gst_caps_set_simple (filter_caps,
"framerate", GST_TYPE_FRACTION,
gst_value_get_fraction_numerator (framerate),
gst_value_get_fraction_denominator (framerate), NULL);
}
if (allowed_caps) {
gst_caps_unref (allowed_caps);
}
if (intersect) {
gst_caps_unref (intersect);
}
}
/**
* get_srcpad_current_format:
* @element: element to get the format from
*
* Helper function to get the negotiated fourcc
* format from @element src pad.
*
* Returns: negotiated format (fourcc), 0 if not found
*/
static guint32
get_srcpad_current_format (GstElement * element)
{
GstPad *srcpad = NULL;
GstCaps *srccaps = NULL;
GstStructure *structure;
guint32 format = 0;
g_return_val_if_fail (element != NULL, 0);
if ((srcpad = gst_element_get_static_pad (element, "src")) == NULL) {
goto no_pad;
}
if ((srccaps = gst_pad_get_negotiated_caps (srcpad)) == NULL) {
goto no_caps;
}
GST_LOG ("negotiated caps %" GST_PTR_FORMAT, srccaps);
structure = gst_caps_get_structure (srccaps, 0);
if (gst_structure_has_field (structure, "format")) {
gst_structure_get_fourcc (structure, "format", &format);
}
gst_caps_unref (srccaps);
no_caps:
gst_object_unref (srcpad);
no_pad:
GST_DEBUG ("current format for %" GST_PTR_FORMAT ": %" GST_FOURCC_FORMAT,
element, GST_FOURCC_ARGS (format));
return format;
}
/*
* gst_camerabin_find_better_framerate:
* @camera: camerabin object
* @st: structure that contains framerate candidates
* @orig_framerate: best framerate so far
*
* Looks for framerate better than @orig_framerate from @st structure.
* In night mode lowest framerate is considered best, otherwise highest is
* best.
*
* Returns: @orig_framerate or better if found
*/
static const GValue *
gst_camerabin_find_better_framerate (GstCameraBin * camera, GstStructure * st,
const GValue * orig_framerate)
{
const GValue *framerate = NULL;
guint i, i_best, list_size;
gint res, comparison;
if (camera->night_mode) {
GST_LOG_OBJECT (camera, "finding min framerate");
comparison = GST_VALUE_LESS_THAN;
} else {
GST_LOG_OBJECT (camera, "finding max framerate");
comparison = GST_VALUE_GREATER_THAN;
}
if (gst_structure_has_field (st, "framerate")) {
framerate = gst_structure_get_value (st, "framerate");
/* Handle framerate lists */
if (GST_VALUE_HOLDS_LIST (framerate)) {
list_size = gst_value_list_get_size (framerate);
GST_LOG_OBJECT (camera, "finding framerate from list");
for (i = 0, i_best = 0; i < list_size; i++) {
res = gst_value_compare (gst_value_list_get_value (framerate, i),
gst_value_list_get_value (framerate, i_best));
if (comparison == res) {
i_best = i;
}
}
GST_LOG_OBJECT (camera, "found best framerate from index %d", i_best);
framerate = gst_value_list_get_value (framerate, i_best);
}
/* Handle framerate ranges */
if (GST_VALUE_HOLDS_FRACTION_RANGE (framerate)) {
if (camera->night_mode) {
GST_LOG_OBJECT (camera, "getting min framerate from range");
framerate = gst_value_get_fraction_range_min (framerate);
} else {
GST_LOG_OBJECT (camera, "getting max framerate from range");
framerate = gst_value_get_fraction_range_max (framerate);
}
}
}
/* Check if we found better framerate */
if (orig_framerate && framerate) {
res = gst_value_compare (orig_framerate, framerate);
if (comparison == res) {
GST_LOG_OBJECT (camera, "original framerate was the best");
framerate = orig_framerate;
}
}
return framerate;
}
/*
* gst_camerabin_update_aspect_filter:
* @camera: camerabin object
* @new_caps: new caps of next buffers arriving to view finder sink element
*
* Updates aspect ratio capsfilter to maintain aspect ratio, if we need to
* scale frames for showing them in view finder.
*/
static void
gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps)
{
GstCaps *sink_caps, *ar_caps;
GstStructure *st;
gint in_w = 0, in_h = 0, sink_w = 0, sink_h = 0, target_w = 0, target_h = 0;
gdouble ratio_w, ratio_h;
GstPad *sink_pad;
const GValue *range;
sink_pad = gst_element_get_static_pad (camera->view_sink, "sink");
if (sink_pad) {
sink_caps = gst_pad_get_caps (sink_pad);
gst_object_unref (sink_pad);
if (sink_caps && !gst_caps_is_any (sink_caps)) {
GST_DEBUG_OBJECT (camera, "sink element caps %" GST_PTR_FORMAT,
sink_caps);
/* Get maximum resolution that view finder sink accepts */
st = gst_caps_get_structure (sink_caps, 0);
if (gst_structure_has_field_typed (st, "width", GST_TYPE_INT_RANGE)) {
range = gst_structure_get_value (st, "width");
sink_w = gst_value_get_int_range_max (range);
}
if (gst_structure_has_field_typed (st, "height", GST_TYPE_INT_RANGE)) {
range = gst_structure_get_value (st, "height");
sink_h = gst_value_get_int_range_max (range);
}
gst_caps_unref (sink_caps);
GST_DEBUG_OBJECT (camera, "sink element accepts max %dx%d", sink_w,
sink_h);
/* Get incoming frames' resolution */
if (sink_h && sink_w) {
st = gst_caps_get_structure (new_caps, 0);
gst_structure_get_int (st, "width", &in_w);
gst_structure_get_int (st, "height", &in_h);
GST_DEBUG_OBJECT (camera, "new caps with %dx%d", in_w, in_h);
}
}
}
/* If we get bigger frames than view finder sink accepts, then we scale.
If we scale we need to adjust aspect ratio capsfilter caps in order
to maintain aspect ratio while scaling. */
if (in_w && in_h && (in_w > sink_w || in_h > sink_h)) {
ratio_w = (gdouble) sink_w / in_w;
ratio_h = (gdouble) sink_h / in_h;
if (ratio_w < ratio_h) {
target_w = sink_w;
target_h = (gint) (ratio_w * in_h);
} else {
target_w = (gint) (ratio_h * in_w);
target_h = sink_h;
}
GST_DEBUG_OBJECT (camera, "setting %dx%d filter to maintain aspect ratio",
target_w, target_h);
ar_caps = gst_caps_copy (new_caps);
gst_caps_set_simple (ar_caps, "width", G_TYPE_INT, target_w, "height",
G_TYPE_INT, target_h, NULL);
} else {
GST_DEBUG_OBJECT (camera, "no scaling");
ar_caps = gst_caps_ref (new_caps);
}
GST_DEBUG_OBJECT (camera, "aspect ratio filter caps %" GST_PTR_FORMAT,
ar_caps);
g_object_set (G_OBJECT (camera->aspect_filter), "caps", ar_caps, NULL);
gst_caps_unref (ar_caps);
}
/*
* GObject callback functions implementation
*/
static void
gst_camerabin_base_init (gpointer gclass)
{
static GstElementDetails element_details = {
"Camera Bin",
"Generic/Bin/Camera",
"Handle lot of features present in DSC",
"Nokia Corporation <multimedia@maemo.org>\n"
"Edgard Lima <edgard.lima@indt.org.br>"
};
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
gst_element_class_set_details (element_class, &element_details);
}
static void
gst_camerabin_class_init (GstCameraBinClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBinClass *gstbin_class;
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
gstbin_class = GST_BIN_CLASS (klass);
/* gobject */
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_camerabin_dispose);
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_camerabin_finalize);
gobject_class->set_property = gst_camerabin_set_property;
gobject_class->get_property = gst_camerabin_get_property;
/**
* GstCameraBin:filename:
*
* Set filename for the still image capturing or video capturing.
*/
g_object_class_install_property (gobject_class, ARG_FILENAME,
g_param_spec_string ("filename", "Filename",
"Filename of the image or video to save", "", G_PARAM_READWRITE));
/**
* GstCameraBin:mode:
*
* Set the mode of operation: still image capturing or video recording.
* Setting the mode will create and destroy image bin or video bin elements
* according to the mode. You can set this property at any time, changing
* the mode will stop ongoing capture.
*/
g_object_class_install_property (gobject_class, ARG_MODE,
g_param_spec_enum ("mode", "Mode",
"The capture mode (still image capture or video recording)",
GST_TYPE_CAMERABIN_MODE, DEFAULT_MODE, G_PARAM_READWRITE));
/**
* GstCameraBin:mute:
*
* Mute audio in video recording mode.
* Set this property only when #GstCameraBin is in READY, PAUSED or PLAYING.
*/
g_object_class_install_property (gobject_class, ARG_MUTE,
g_param_spec_boolean ("mute", "Mute",
"True to mute the recording. False to record with audio",
ARG_DEFAULT_MUTE, G_PARAM_READWRITE));
/**
* GstCameraBin:zoom:
*
* Set up the zoom applied to the frames.
* Set this property only when #GstCameraBin is in READY, PAUSED or PLAYING.
*/
g_object_class_install_property (gobject_class, ARG_ZOOM,
g_param_spec_int ("zoom", "Zoom",
"The zoom. 100 for 1x, 200 for 2x and so on",
MIN_ZOOM, MAX_ZOOM, DEFAULT_ZOOM, G_PARAM_READWRITE));
/**
* GstCameraBin:imagepp:
*
* Set up an element to do image post processing.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_IMAGE_POST,
g_param_spec_object ("imagepp", "Image post processing element",
"Image Post-Processing GStreamer element (default is NULL)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:imageenc:
*
* Set up an image encoder (for example, jpegenc or pngenc) element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_IMAGE_ENC,
g_param_spec_object ("imageenc", "Image encoder",
"Image encoder GStreamer element (default is jpegenc)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:videopp:
*
* Set up an element to do video post processing.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_VIDEO_POST,
g_param_spec_object ("videopp", "Video post processing element",
"Video post processing GStreamer element (default is NULL)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:videoenc:
*
* Set up a video encoder element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_VIDEO_ENC,
g_param_spec_object ("videoenc", "Video encoder",
"Video encoder GStreamer element (default is theoraenc)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:audioenc:
*
* Set up an audio encoder element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_AUDIO_ENC,
g_param_spec_object ("audioenc", "Audio encoder",
"Audio encoder GStreamer element (default is vorbisenc)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:videomux:
*
* Set up a video muxer element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_VIDEO_MUX,
g_param_spec_object ("videomux", "Video muxer",
"Video muxer GStreamer element (default is oggmux)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:vfsink:
*
* Set up a sink element to render frames in view finder.
* By default "autovideosink" will be the sink element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_VF_SINK,
g_param_spec_object ("vfsink", "View finder sink",
"View finder sink GStreamer element (default is " DEFAULT_VIEW_SINK
")", GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:videosrc:
*
* Set up a video source element.
* By default "v4l2src" will be the src element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_VIDEO_SRC,
g_param_spec_object ("videosrc", "Video source element",
"Video source GStreamer element (default is " DEFAULT_SRC_VID_SRC ")",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:audiosrc:
*
* Set up an audio source element.
* By default "pulsesrc" will be the source element.
* This property can only be set while #GstCameraBin is in NULL state.
* The ownership of the element will be taken by #GstCameraBin.
*/
g_object_class_install_property (gobject_class, ARG_AUDIO_SRC,
g_param_spec_object ("audiosrc", "Audio source element",
"Audio source GStreamer element (default is pulsesrc)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE));
/**
* GstCameraBin:inputcaps:
*
* The allowed modes of operation of the video source. Have in mind that it
* doesn't mean #GstCameraBin can operate in all those modes,
* it depends also on the other elements in the pipeline. Remember to
* gst_caps_unref after using it.
*/
g_object_class_install_property (gobject_class, ARG_INPUT_CAPS,
g_param_spec_boxed ("inputcaps", "Input caps",
"The allowed modes of the video source operation",
GST_TYPE_CAPS, G_PARAM_READABLE));
/**
* 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.
*/
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",
GST_TYPE_CAPS, G_PARAM_READWRITE));
/**
* GstCameraBin::user-start:
* @camera: the camera bin element
*
* Starts image capture or video recording depending on the Mode.
* If there is a capture already going on, does nothing.
* Resumes video recording if it has been paused.
*/
camerabin_signals[USER_START_SIGNAL] =
g_signal_new ("user-start",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GstCameraBinClass, user_start),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
/**
* GstCameraBin::user-stop:
* @camera: the camera bin element
*
* Stops still image preview, continuous image capture and video
* recording and returns to the view finder mode.
*/
camerabin_signals[USER_STOP_SIGNAL] =
g_signal_new ("user-stop",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GstCameraBinClass, user_stop),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
/**
* GstCameraBin::user-pause:
* @camera: the camera bin element
*
* Pauses video recording or resumes paused video recording.
* If in image mode or not recording, does nothing.
*/
camerabin_signals[USER_PAUSE_SIGNAL] =
g_signal_new ("user-pause",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GstCameraBinClass, user_pause),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
/**
* GstCameraBin::user-res-fps:
* @camera: the camera bin element
* @width: number of horizontal pixels
* @height: number of vertical pixels
* @fps_n: frames per second numerator
* @fps_d: frames per second denominator
*
* Changes the frame resolution and frames per second of the video source.
* The application must be aware of the resolutions supported by the camera.
* Supported resolutions and frame rates can be get using input-caps property.
*
* Setting @fps_n or @fps_d to 0 configures maximum framerate for the
* given resolution, unless in night mode when minimum is configured.
*/
camerabin_signals[USER_RES_FPS_SIGNAL] =
g_signal_new ("user-res-fps",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GstCameraBinClass, user_res_fps),
NULL, NULL, __gst_camerabin_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4,
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
/**
* GstCameraBin::user-image-res:
* @camera: the camera bin element
* @width: number of horizontal pixels
* @height: number of vertical pixels
*
* Changes the resolution used for still image capture.
* Does not affect view finder mode and video recording.
* Use this action signal in PAUSED or PLAYING state.
*/
camerabin_signals[USER_IMAGE_RES_SIGNAL] =
g_signal_new ("user-image-res",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GstCameraBinClass, user_image_res),
NULL, NULL, __gst_camerabin_marshal_VOID__INT_INT, G_TYPE_NONE, 2,
G_TYPE_INT, G_TYPE_INT);
/**
* GstCameraBin::img-done:
* @camera: the camera bin element
* @filename: the name of the file just saved as a GString*
*
* Signal emited when the file has just been saved. To continue taking
* pictures just update @filename and return TRUE, otherwise return FALSE.
*
* Don't call any #GstCameraBin method from this signal, if you do so there
* will be a deadlock.
*/
camerabin_signals[IMG_DONE_SIGNAL] =
g_signal_new ("img-done", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstCameraBinClass, img_done),
g_signal_accumulator_true_handled, NULL,
__gst_camerabin_marshal_BOOLEAN__POINTER, G_TYPE_BOOLEAN, 1,
G_TYPE_POINTER);
klass->user_start = gst_camerabin_user_start;
klass->user_stop = gst_camerabin_user_stop;
klass->user_pause = gst_camerabin_user_pause;
klass->user_res_fps = gst_camerabin_user_res_fps;
klass->user_image_res = gst_camerabin_user_image_res;
klass->img_done = gst_camerabin_default_signal_img_done;
/* gstelement */
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_camerabin_change_state);
/* gstbin */
/* override handle_message to peek when video or image bin reaches eos */
gstbin_class->handle_message =
GST_DEBUG_FUNCPTR (gst_camerabin_handle_message_func);
}
/* initialize the new element
* instantiate pads and add them to element
* set functions
* initialize structure
*/
static void
gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass)
{
/* GstElementClass *klass = GST_ELEMENT_GET_CLASS (camera); */
camera->filename = g_string_new ("");
camera->mode = DEFAULT_MODE;
camera->num_img_buffers = 0;
camera->stop_requested = FALSE;
camera->paused = FALSE;
camera->capturing = FALSE;
camera->night_mode = FALSE;
camera->width = DEFAULT_WIDTH;
camera->height = DEFAULT_HEIGHT;
camera->fps_n = DEFAULT_FPS_N;
camera->fps_d = DEFAULT_FPS_D;
camera->image_capture_caps = NULL;
camera->view_finder_caps = NULL;
camera->allowed_caps = NULL;
camera->zoom = DEFAULT_ZOOM;
/* concurrency control */
camera->capture_mutex = g_mutex_new ();
camera->cond = g_cond_new ();
/* pad names for output and input selectors */
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;
camera->src_zoom_crop = NULL;
camera->src_zoom_scale = NULL;
camera->src_zoom_filter = NULL;
camera->src_out_sel = NULL;
camera->user_vf_sink = NULL;
/* image capture bin */
camera->imgbin = g_object_new (GST_TYPE_CAMERABIN_IMAGE, NULL);
gst_object_ref (camera->imgbin);
/* video capture bin */
camera->vidbin = g_object_new (GST_TYPE_CAMERABIN_VIDEO, NULL);
gst_object_ref (camera->vidbin);
camera->active_bin = NULL;
/* view finder elements */
camera->view_in_sel = NULL;
camera->view_scale = NULL;
camera->view_sink = NULL;
}
static void
gst_camerabin_dispose (GObject * object)
{
GstCameraBin *camera;
camera = GST_CAMERABIN (object);
GST_DEBUG_OBJECT (camera, "disposing");
gst_element_set_state (camera->imgbin, GST_STATE_NULL);
gst_object_unref (camera->imgbin);
gst_element_set_state (camera->vidbin, GST_STATE_NULL);
gst_object_unref (camera->vidbin);
camerabin_destroy_elements (camera);
camerabin_dispose_elements (camera);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_camerabin_finalize (GObject * object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_camerabin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstCameraBin *camera = GST_CAMERABIN (object);
switch (prop_id) {
case ARG_MUTE:
gst_camerabin_video_set_mute (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_boolean (value));
break;
case ARG_ZOOM:
g_atomic_int_set (&camera->zoom, g_value_get_int (value));
gst_camerabin_setup_zoom (camera);
break;
case ARG_MODE:
gst_camerabin_change_mode (camera, g_value_get_enum (value));
break;
case ARG_FILENAME:
gst_camerabin_change_filename (camera, g_value_get_string (value));
break;
case ARG_VIDEO_POST:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next video bin NULL to READY state change");
}
gst_camerabin_video_set_post (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_object (value));
break;
case ARG_VIDEO_ENC:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next video bin NULL to READY state change");
}
gst_camerabin_video_set_video_enc (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_object (value));
break;
case ARG_AUDIO_ENC:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next video bin NULL to READY state change");
}
gst_camerabin_video_set_audio_enc (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_object (value));
break;
case ARG_VIDEO_MUX:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera->vidbin,
"can't use set element until next video bin NULL to READY state change");
}
gst_camerabin_video_set_muxer (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_object (value));
break;
case ARG_IMAGE_POST:
if (GST_STATE (camera->imgbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next image bin NULL to READY state change");
}
gst_camerabin_image_set_postproc (GST_CAMERABIN_IMAGE (camera->imgbin),
g_value_get_object (value));
break;
case ARG_IMAGE_ENC:
if (GST_STATE (camera->imgbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next image bin NULL to READY state change");
}
gst_camerabin_image_set_encoder (GST_CAMERABIN_IMAGE (camera->imgbin),
g_value_get_object (value));
break;
case ARG_VF_SINK:
if (GST_STATE (camera) != GST_STATE_NULL) {
GST_ELEMENT_ERROR (camera, CORE, FAILED,
("camerabin must be in NULL state when setting the view finder element"),
(NULL));
} else {
if (camera->user_vf_sink)
gst_object_unref (camera->user_vf_sink);
camera->user_vf_sink = g_value_get_object (value);
gst_object_ref (camera->user_vf_sink);
}
break;
case ARG_VIDEO_SRC:
if (GST_STATE (camera) != GST_STATE_NULL) {
GST_ELEMENT_ERROR (camera, CORE, FAILED,
("camerabin must be in NULL state when setting the video source element"),
(NULL));
} else {
if (camera->user_vid_src)
gst_object_unref (camera->user_vid_src);
camera->user_vid_src = g_value_get_object (value);
gst_object_ref (camera->user_vid_src);
}
break;
case ARG_AUDIO_SRC:
if (GST_STATE (camera->vidbin) != GST_STATE_NULL) {
GST_WARNING_OBJECT (camera,
"can't use set element until next video bin NULL to READY state change");
}
gst_camerabin_video_set_audio_src (GST_CAMERABIN_VIDEO (camera->vidbin),
g_value_get_object (value));
break;
case ARG_FILTER_CAPS:
GST_OBJECT_LOCK (camera);
if (camera->view_finder_caps) {
gst_caps_unref (camera->view_finder_caps);
}
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);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_camerabin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstCameraBin *camera = GST_CAMERABIN (object);
switch (prop_id) {
case ARG_FILENAME:
g_value_set_string (value, camera->filename->str);
break;
case ARG_MODE:
g_value_set_enum (value, camera->mode);
break;
case ARG_MUTE:
g_value_set_boolean (value,
gst_camerabin_video_get_mute (GST_CAMERABIN_VIDEO (camera->vidbin)));
break;
case ARG_ZOOM:
g_value_set_int (value, g_atomic_int_get (&camera->zoom));
break;
case ARG_IMAGE_POST:
g_value_set_object (value,
gst_camerabin_image_get_postproc (GST_CAMERABIN_IMAGE
(camera->imgbin)));
break;
case ARG_IMAGE_ENC:
g_value_set_object (value,
gst_camerabin_image_get_encoder (GST_CAMERABIN_IMAGE
(camera->imgbin)));
break;
case ARG_VIDEO_POST:
g_value_set_object (value,
gst_camerabin_video_get_post (GST_CAMERABIN_VIDEO (camera->vidbin)));
break;
case ARG_VIDEO_ENC:
g_value_set_object (value,
gst_camerabin_video_get_video_enc (GST_CAMERABIN_VIDEO
(camera->vidbin)));
break;
case ARG_AUDIO_ENC:
g_value_set_object (value,
gst_camerabin_video_get_audio_enc (GST_CAMERABIN_VIDEO
(camera->vidbin)));
break;
case ARG_VIDEO_MUX:
g_value_set_object (value,
gst_camerabin_video_get_muxer (GST_CAMERABIN_VIDEO (camera->vidbin)));
break;
case ARG_VF_SINK:
g_value_set_object (value, camera->user_vf_sink);
break;
case ARG_VIDEO_SRC:
g_value_set_object (value, camera->src_vid_src);
break;
case ARG_AUDIO_SRC:
g_value_set_object (value,
gst_camerabin_video_get_audio_src (GST_CAMERABIN_VIDEO
(camera->vidbin)));
break;
case ARG_INPUT_CAPS:
gst_value_set_caps (value, gst_camerabin_get_allowed_input_caps (camera));
break;
case ARG_FILTER_CAPS:
gst_value_set_caps (value, camera->view_finder_caps);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*
* GstElement functions implementation
*/
static GstStateChangeReturn
gst_camerabin_change_state (GstElement * element, GstStateChange transition)
{
GstCameraBin *camera = GST_CAMERABIN (element);
GstStateChangeReturn ret;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!camerabin_create_elements (camera)) {
ret = GST_STATE_CHANGE_FAILURE;
goto done;
}
/* Lock to control image and video bin state separately
from view finder */
gst_element_set_locked_state (camera->imgbin, TRUE);
gst_element_set_locked_state (camera->vidbin, TRUE);
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
camerabin_setup_src_elements (camera);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
/* If using autovideosink, set view finder sink properties
now that actual sink has been created. */
camerabin_setup_view_elements (camera);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
gst_element_set_locked_state (camera->imgbin, FALSE);
gst_element_set_locked_state (camera->vidbin, FALSE);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_LOG_OBJECT (camera, "PAUSED to READY");
g_mutex_lock (camera->capture_mutex);
if (camera->capturing) {
GST_WARNING_OBJECT (camera, "was capturing when changing to READY");
camera->capturing = FALSE;
/* Reset capture and don't wait for capturing to finish properly.
Proper capturing should have been finished before going to READY. */
gst_camerabin_reset_to_view_finder (camera);
g_cond_signal (camera->cond);
}
g_mutex_unlock (camera->capture_mutex);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
camerabin_destroy_elements (camera);
break;
default:
break;
}
done:
return ret;
}
/*
* GstBin functions implementation
*/
/* Peek eos messages but don't interfere with bin msg handling */
static void
gst_camerabin_handle_message_func (GstBin * bin, GstMessage * msg)
{
GstCameraBin *camera = GST_CAMERABIN (bin);
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_EOS:
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->vidbin)) {
/* Video eos */
GST_DEBUG_OBJECT (camera,
"got video eos message, stopping video capture");
g_mutex_lock (camera->capture_mutex);
camera->capturing = FALSE;
g_cond_signal (camera->cond);
g_mutex_unlock (camera->capture_mutex);
} else if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->imgbin)) {
/* Image eos */
GST_DEBUG_OBJECT (camera, "got image eos message");
/* Still image capture buffer handled, restore filter caps */
if (camera->image_capture_caps) {
gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps);
}
/* Unblock pad to process next buffer */
gst_pad_set_blocked_async (camera->srcpad_videosrc, FALSE,
(GstPadBlockCallback) image_pad_blocked, camera);
}
break;
default:
break;
}
GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
}
/*
* Action signal function implementation
*/
static void
gst_camerabin_user_start (GstCameraBin * camera)
{
GST_INFO_OBJECT (camera, "starting capture");
if (camera->paused) {
gst_camerabin_user_pause (camera);
return;
}
if (!camera->active_bin) {
GST_INFO_OBJECT (camera, "mode not explicitly set by application");
gst_camerabin_change_mode (camera, camera->mode);
}
if (g_str_equal (camera->filename->str, "")) {
GST_ELEMENT_ERROR (camera, CORE, FAILED,
("set filename before starting capture"), (NULL));
return;
}
g_mutex_lock (camera->capture_mutex);
if (camera->capturing) {
GST_WARNING_OBJECT (camera, "capturing \"%s\" ongoing, set new filename",
camera->filename->str);
g_mutex_unlock (camera->capture_mutex);
return;
}
g_mutex_unlock (camera->capture_mutex);
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) {
gst_camerabin_start_video_recording (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);
}
static void
gst_camerabin_user_pause (GstCameraBin * camera)
{
if (camera->active_bin == camera->vidbin) {
if (!camera->paused) {
GST_INFO_OBJECT (camera, "pausing capture");
/* Bring all camerabin elements to PAUSED */
gst_element_set_locked_state (camera->vidbin, FALSE);
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED);
/* Switch to view finder mode */
g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE,
"active-pad", camera->pad_src_view, NULL);
/* Enable view finder mode in v4l2camsrc */
if (g_object_class_find_property (G_OBJECT_GET_CLASS
(camera->src_vid_src), "capture-mode")) {
g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 0, NULL);
}
/* Set view finder to PLAYING and leave videobin PAUSED */
gst_element_set_locked_state (camera->vidbin, TRUE);
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING);
camera->paused = TRUE;
} else {
GST_INFO_OBJECT (camera, "unpausing capture");
/* Bring all camerabin elements to PAUSED */
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED);
/* Switch to video recording mode */
g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", TRUE,
"active-pad", camera->pad_src_vid, NULL);
/* Enable video recording mode in v4l2camsrc */
if (g_object_class_find_property (G_OBJECT_GET_CLASS
(camera->src_vid_src), "capture-mode")) {
g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 2, NULL);
}
/* Bring all camerabin elements to PLAYING */
gst_element_set_locked_state (camera->vidbin, FALSE);
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING);
gst_element_set_locked_state (camera->vidbin, TRUE);
camera->paused = FALSE;
}
GST_DEBUG_OBJECT (camera, "pause done");
} else {
GST_WARNING ("pausing in image capture mode disabled");
}
}
static void
gst_camerabin_user_res_fps (GstCameraBin * camera, gint width, gint height,
gint fps_n, gint fps_d)
{
GstState state;
GST_INFO_OBJECT (camera, "switching resolution to %dx%d and fps to %d/%d",
width, height, fps_n, fps_d);
state = GST_STATE (camera);
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_READY);
camera->width = width;
camera->height = height;
camera->fps_n = fps_n;
camera->fps_d = fps_d;
gst_element_set_state (GST_ELEMENT (camera), state);
}
static void
gst_camerabin_user_image_res (GstCameraBin * camera, gint width, gint height)
{
GstStructure *structure;
GstCaps *new_caps = NULL;
guint32 format = 0;
g_return_if_fail (camera != NULL);
if (width && height && camera->view_finder_caps) {
/* Use view finder mode caps as a basis */
structure = gst_caps_get_structure (camera->view_finder_caps, 0);
/* Set new resolution for image capture */
new_caps = gst_caps_new_simple (gst_structure_get_name (structure),
"width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL);
/* Set format according to current videosrc format */
format = get_srcpad_current_format (camera->src_vid_src);
if (format) {
gst_caps_set_simple (new_caps, "format", GST_TYPE_FOURCC, format, NULL);
}
/* Set allowed framerate for the resolution. */
gst_camerabin_set_allowed_framerate (camera, new_caps);
/* Reset the format to match with view finder mode caps */
if (gst_structure_get_fourcc (structure, "format", &format)) {
gst_caps_set_simple (new_caps, "format", GST_TYPE_FOURCC, format, NULL);
}
}
GST_INFO_OBJECT (camera,
"init filter caps for image capture %" GST_PTR_FORMAT, new_caps);
gst_caps_replace (&camera->image_capture_caps, new_caps);
}
/* entry point to initialize the plug-in
* initialize the plug-in itself
* register the element factories and pad templates
* register the features
*/
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_camerabin_debug, "camerabin", 0, "CameraBin");
return gst_element_register (plugin, "camerabin",
GST_RANK_NONE, GST_TYPE_CAMERABIN);
}
/* this is the structure that gstreamer looks for to register plugins
*/
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"camerabin",
"High level api for DC (Digital Camera) application",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)