diff -r 567bb019e3e3 -r 7e817e7e631c gst_plugins_good/gst/camerabin/gstcamerabin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gst_plugins_good/gst/camerabin/gstcamerabin.c Wed Sep 01 12:16:41 2010 +0100 @@ -0,0 +1,2769 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * 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:gstcamerabin + * @short_description: camera capture bin + * + * + * + * 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. + * + * + * + * + * CameraBin structure + * Structural decomposition of CameraBin object. + * + * + * + * + * Example launch line + * + * + * gst-launch -v -m camerabin filename=test.jpeg + * + * + * + * + * Image capture + * + * 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. + * + * + * + * Video capture + * + * 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. + * + * + * + * Photography interface + * + * 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. + * + * + * + * States + * + * 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. + * + * + * + * + * + * 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). + * + * + * + */ + +/* + * 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 +#endif + + +#include +#include + +#include +/* FIXME: include #include and use _(" ") */ + +#ifndef __SYMBIAN32__ +#include +#else +#include +#endif + +#ifdef __SYMBIAN32__ +#include +#endif + + + + +#include "gstcamerabin.h" +#include "gstcamerabinxoverlay.h" +#include "gstcamerabincolorbalance.h" +#include "gstcamerabinphotography.h" + +#include "camerabingeneral.h" + +#include "gstcamerabin-marshal.h" + +#ifdef __SYMBIAN32__ +#include +#endif +/* + * 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); + +/* + * 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); + +static const GValue *gst_camerabin_find_better_framerate (GstCameraBin * camera, + GstStructure * st, const GValue * orig_framerate); +/* + * 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_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; + } +#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); + unconnected_pad = gst_bin_find_unconnected_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; + } + + 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 (!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 (!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 (!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 (!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); +} + +/* + * img_capture_prepared: + * @data: camerabin object + * + * Callback which is called after image capture has been prepared. + */ +static void +img_capture_prepared (gpointer data) +{ + GstCameraBin *camera = GST_CAMERABIN (data); + + GST_INFO_OBJECT (camera, "image capture prepared"); + + if (camera->image_capture_caps) { + /* Set capsfilters to match arriving image data */ + gst_camerabin_set_capsfilter_caps (camera, camera->image_capture_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; + gint width = 0, height = 0, fps_n = 0, fps_d = 0; + GstStructure *st; + + 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); + if (camera->image_capture_caps) { + st = gst_caps_get_structure (camera->image_capture_caps, 0); + } else { + st = gst_caps_get_structure (camera->view_finder_caps, 0); + } + gst_structure_get_int (st, "width", &width); + gst_structure_get_int (st, "height", &height); + gst_structure_get_fraction (st, "framerate", &fps_n, &fps_d); + /* Set image capture resolution and frame rate */ + g_signal_emit_by_name (camera->src_vid_src, "user-res-fps", + width, height, fps_n, fps_d, 0); + + /* 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); + } + + /* Start preparations for image capture */ + gst_photography_prepare_for_capture (GST_PHOTOGRAPHY (camera->src_vid_src), + (GstPhotoCapturePrepared) img_capture_prepared, 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; + g_mutex_unlock (camera->capture_mutex); + } else { + GST_WARNING_OBJECT (camera, "imagebin state change failed"); + gst_element_set_state (camera->imgbin, GST_STATE_NULL); + } + } +} + +/* + * 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->pad_src_img)) { + /* 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) { + /* Restore filter caps for view finder mode if necessary. + The v4l2camsrc switches automatically to view finder + resolution after hi-res still image capture. */ + if (camera->image_capture_caps) { + gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); + } + 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 (pad, 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 to image bin is blocked due to waiting for eos */ + if (camera->pad_src_img && gst_pad_is_blocked (camera->pad_src_img)) { + gst_pad_set_blocked_async (camera->pad_src_img, 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; +} + +/* + * 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 \n" + "Edgard Lima " + }; + 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 + * + * 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_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; + + /* 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"); + /* Unblock pad to process next buffer */ + gst_pad_set_blocked_async (camera->pad_src_img, 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)