From 3751eaeb794c613e0b15ad8003297b4617d908bc Mon Sep 17 00:00:00 2001 From: Nokia Corporation Date: Thu, 5 Feb 2009 15:48:32 +0200 Subject: [PATCH] Add camerabin element. --- configure.ac | 2 + gst/camerabin/Makefile.am | 46 + gst/camerabin/camerabingeneral.c | 262 +++ gst/camerabin/camerabingeneral.h | 62 + gst/camerabin/camerabinimage.c | 571 ++++++ gst/camerabin/camerabinimage.h | 90 + gst/camerabin/camerabinvideo.c | 826 +++++++++ gst/camerabin/camerabinvideo.h | 136 ++ gst/camerabin/gstcamerabin-marshal.list | 6 + gst/camerabin/gstcamerabin.c | 2762 ++++++++++++++++++++++++++++++ gst/camerabin/gstcamerabin.h | 168 ++ gst/camerabin/gstcamerabincolorbalance.c | 81 + gst/camerabin/gstcamerabincolorbalance.h | 28 + gst/camerabin/gstcamerabinphotography.c | 222 +++ gst/camerabin/gstcamerabinphotography.h | 30 + gst/camerabin/gstcamerabinxoverlay.c | 73 + gst/camerabin/gstcamerabinxoverlay.h | 28 + 17 files changed, 5393 insertions(+) create mode 100644 gst/camerabin/Makefile.am create mode 100644 gst/camerabin/camerabingeneral.c create mode 100644 gst/camerabin/camerabingeneral.h create mode 100644 gst/camerabin/camerabinimage.c create mode 100644 gst/camerabin/camerabinimage.h create mode 100644 gst/camerabin/camerabinvideo.c create mode 100644 gst/camerabin/camerabinvideo.h create mode 100644 gst/camerabin/gstcamerabin-marshal.list create mode 100644 gst/camerabin/gstcamerabin.c create mode 100644 gst/camerabin/gstcamerabin.h create mode 100644 gst/camerabin/gstcamerabincolorbalance.c create mode 100644 gst/camerabin/gstcamerabincolorbalance.h create mode 100644 gst/camerabin/gstcamerabinphotography.c create mode 100644 gst/camerabin/gstcamerabinphotography.h create mode 100644 gst/camerabin/gstcamerabinxoverlay.c create mode 100644 gst/camerabin/gstcamerabinxoverlay.h diff --git a/configure.ac b/configure.ac index 7ec7cea..3cc51ab 100644 --- a/configure.ac +++ b/configure.ac @@ -239,6 +239,7 @@ dnl these are all the gst plug-ins, compilable without additional libs AG_GST_CHECK_PLUGIN(aacparse) AG_GST_CHECK_PLUGIN(aiffparse) AG_GST_CHECK_PLUGIN(amrparse) +AG_GST_CHECK_PLUGIN(camerabin) AG_GST_CHECK_PLUGIN(legacyresample) AG_GST_CHECK_PLUGIN(bayer) AG_GST_CHECK_PLUGIN(cdxaparse) @@ -1412,6 +1413,7 @@ gst/aiffparse/Makefile gst/amrparse/Makefile gst/legacyresample/Makefile gst/bayer/Makefile +gst/camerabin/Makefile gst/cdxaparse/Makefile gst/dccp/Makefile gst/deinterlace/Makefile diff --git a/gst/camerabin/Makefile.am b/gst/camerabin/Makefile.am new file mode 100644 index 0000000..8a5cc38 --- /dev/null +++ b/gst/camerabin/Makefile.am @@ -0,0 +1,46 @@ +glib_enum_prefix = gst_camerabin + +include $(top_srcdir)/common/glib-gen.mak + +built_sources = gstcamerabin-marshal.c +built_headers = gstcamerabin-marshal.h + +BUILT_SOURCES = $(built_sources) $(built_headers) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = gstcamerabin-marshal.list + +plugin_LTLIBRARIES = libgstcamerabin.la + +libgstcamerabin_la_SOURCES = gstcamerabin.c \ + gstcamerabinxoverlay.c \ + gstcamerabincolorbalance.c \ + camerabinimage.c \ + camerabinvideo.c \ + camerabingeneral.c \ + gstcamerabinphotography.c + +nodist_libgstcamerabin_la_SOURCES = $(built_sources) + +# libcamerabin_ladir = $(includedir)/gstreamer-@GST_MAJORMINOR@/camerabin + +# libcamerabin_la_HEADERS = gstcamerabin.h + +libgstcamerabin_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) + +libgstcamerabin_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) \ + -lgstinterfaces-$(GST_MAJORMINOR) \ + $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-$(GST_MAJORMINOR).la + +libgstcamerabin_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstcamerabin__la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstcamerabin.h \ + gstcamerabinxoverlay.h \ + gstcamerabincolorbalance.h \ + camerabinimage.h \ + camerabinvideo.h \ + camerabingeneral.h \ + gstcamerabinphotography.h \ + $(built_headers) diff --git a/gst/camerabin/camerabingeneral.c b/gst/camerabin/camerabingeneral.c new file mode 100644 index 0000000..627e032 --- /dev/null +++ b/gst/camerabin/camerabingeneral.c @@ -0,0 +1,262 @@ +/* + * 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:camerabingeneral + * @short_description: helper functions for #GstCameraBin and it's modules + * + * Common helper functions for #GstCameraBin, #GstCameraBinImage and + * #GstCameraBinVideo. + * + */ + +#include "camerabingeneral.h" +#include + +GST_DEBUG_CATEGORY (gst_camerabin_debug); + +static gboolean +camerabin_general_dbg_have_event (GstPad * pad, GstEvent * event, + gpointer u_data) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstElement *elem = (GstElement *) u_data; + gchar *elem_name = gst_element_get_name (elem); + gchar *pad_name = gst_pad_get_name (pad); + + gboolean update; + gdouble rate; + GstFormat format; + gint64 start, stop, pos; + gst_event_parse_new_segment (event, &update, &rate, &format, &start, + &stop, &pos); + + GST_DEBUG ("element %s, pad %s, new_seg_start =%" GST_TIME_FORMAT + ", new_seg_stop =%" GST_TIME_FORMAT + ", new_seg_pos =%" GST_TIME_FORMAT "\n", elem_name, pad_name, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (pos)); + + g_free (pad_name); + g_free (elem_name); + } + break; + default: + break; + } + + return TRUE; +} + +static gboolean +camerabin_general_dbg_have_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstElement *elem = (GstElement *) u_data; + gchar *elem_name = gst_element_get_name (elem); + gchar *pad_name = gst_pad_get_name (pad); + + GST_DEBUG ("element %s, pad %s, buf_ts =%" GST_TIME_FORMAT "\n", elem_name, + pad_name, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + + g_free (pad_name); + g_free (elem_name); + + return TRUE; + +} + +void +camerabin_general_dbg_set_probe (GstElement * elem, gchar * pad_name, + gboolean buf, gboolean evt) +{ + GstPad *pad = gst_element_get_static_pad (elem, pad_name); + + if (buf) + gst_pad_add_buffer_probe (pad, + G_CALLBACK (camerabin_general_dbg_have_buffer), elem); + if (evt) + gst_pad_add_event_probe (pad, + G_CALLBACK (camerabin_general_dbg_have_event), elem); + + gst_object_unref (pad); +} + +/** + * gst_camerabin_add_element: + * @bin: add an element to this bin + * @new_elem: new element to be added + * + * Adds given element to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. Raises an error if adding + * or linking failed. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_add_element (GstBin * bin, GstElement * new_elem) +{ + gboolean ret = FALSE; + + ret = gst_camerabin_try_add_element (bin, new_elem); + + if (!ret) { + gchar *elem_name = gst_element_get_name (new_elem); + GST_ELEMENT_ERROR (bin, CORE, NEGOTIATION, (NULL), + ("linking %s failed", elem_name)); + g_free (elem_name); + } + + return ret; +} + +/** + * gst_camerabin_try_add_element: + * @bin: tries adding an element to this bin + * @new_elem: new element to be added + * + * Adds given element to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_try_add_element (GstBin * bin, GstElement * new_elem) +{ + GstPad *bin_pad; + GstElement *bin_elem; + gboolean ret = TRUE; + + if (!bin || !new_elem) { + return FALSE; + } + + /* Get pads for linking */ + GST_DEBUG ("finding unconnected src pad"); + bin_pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC); + GST_DEBUG ("unconnected pad %s:%s", GST_DEBUG_PAD_NAME (bin_pad)); + /* Add to bin */ + gst_bin_add (GST_BIN (bin), new_elem); + /* Link, if unconnected pad was found, otherwise just add it to bin */ + if (bin_pad) { + bin_elem = gst_pad_get_parent_element (bin_pad); + gst_object_unref (bin_pad); + if (!gst_element_link (bin_elem, new_elem)) { + gst_bin_remove (bin, new_elem); + ret = FALSE; + } + gst_object_unref (bin_elem); + } + + return ret; +} + +/** + * gst_camerabin_create_and_add_element: + * @bin: tries adding an element to this bin + * @elem_name: name of the element to be created + * + * Creates an element according to given name and + * adds it to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. + * + * Returns: pointer to the new element if successful, NULL otherwise. + */ +GstElement * +gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name) +{ + GstElement *new_elem = NULL; + + GST_DEBUG ("adding %s", elem_name); + new_elem = gst_element_factory_make (elem_name, NULL); + if (!new_elem) { + GST_ELEMENT_ERROR (bin, CORE, MISSING_PLUGIN, (NULL), + ("could not create \"%s\" element.", elem_name)); + } else if (!gst_camerabin_add_element (bin, new_elem)) { + new_elem = NULL; + } + + return new_elem; +} + +/** + * gst_camerabin_remove_elements_from_bin: + * @bin: removes all elements from this bin + * + * Removes all elements from this @bin. + */ +void +gst_camerabin_remove_elements_from_bin (GstBin * bin) +{ + GstIterator *iter = NULL; + gpointer data = NULL; + GstElement *elem = NULL; + gboolean done = FALSE; + + iter = gst_bin_iterate_elements (bin); + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK: + elem = GST_ELEMENT (data); + gst_bin_remove (bin, elem); + /* Iterator increased the element refcount, so unref */ + gst_object_unref (elem); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (bin, "error in iterating elements"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); +} + +/** + * gst_camerabin_drop_eos_probe: + * @pad: pad receiving the event + * @event: received event + * @u_data: not used + * + * Event probe that drop all eos events. + * + * Returns: FALSE to drop the event, TRUE otherwise + */ +gboolean +gst_camerabin_drop_eos_probe (GstPad * pad, GstEvent * event, gpointer u_data) +{ + gboolean ret = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + GST_DEBUG ("dropping eos in %s:%s", GST_DEBUG_PAD_NAME (pad)); + ret = FALSE; + break; + default: + break; + } + return ret; +} diff --git a/gst/camerabin/camerabingeneral.h b/gst/camerabin/camerabingeneral.h new file mode 100644 index 0000000..e05bb1f --- /dev/null +++ b/gst/camerabin/camerabingeneral.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#ifndef __CAMERABIN_GENERAL_H_ +#define __CAMERABIN_GENERAL_H_ + +#include +#include + +#include + + +typedef struct timeval TIME_TYPE; +#define GET_TIME(t) do { gettimeofday(&(t), NULL); } while(0) +#define DIFF_TIME(t2,t1,d) do { d = ((t2).tv_sec - (t1).tv_sec) * 1000000 + \ + (t2).tv_usec - (t1).tv_usec; } while(0) + +#define _INIT_TIMER_BLOCK TIME_TYPE t1, t2; guint32 d; do {;}while (0) + +#define _OPEN_TIMER_BLOCK { GET_TIME(t1); do {;}while (0) +#define _CLOSE_TIMER_BLOCK GET_TIME(t2); DIFF_TIME(t2,t1,d); \ + GST_DEBUG("elapsed time = %u\n", d); \ + } do {;}while (0) + + +extern void +camerabin_general_dbg_set_probe (GstElement * elem, gchar * pad_name, + gboolean buf, gboolean evt); + +gboolean gst_camerabin_try_add_element (GstBin * bin, GstElement * new_elem); + +gboolean gst_camerabin_add_element (GstBin * bin, GstElement * new_elem); + +GstElement *gst_camerabin_create_and_add_element (GstBin * bin, + const gchar * elem_name); + +void gst_camerabin_remove_elements_from_bin (GstBin * bin); + +gboolean +gst_camerabin_drop_eos_probe (GstPad * pad, GstEvent * event, gpointer u_data); + +GST_DEBUG_CATEGORY_EXTERN (gst_camerabin_debug); +#define GST_CAT_DEFAULT gst_camerabin_debug + +#endif /* #ifndef __CAMERABIN_GENERAL_H_ */ diff --git a/gst/camerabin/camerabinimage.c b/gst/camerabin/camerabinimage.c new file mode 100644 index 0000000..e4ff6b8 --- /dev/null +++ b/gst/camerabin/camerabinimage.c @@ -0,0 +1,571 @@ +/* + * 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:camerabinimage + * @short_description: image capturing module of #GstCameraBin + * + * + * + * + * The pipeline for this module is: + * + * + * + *----------------------------------------------------------------------------- + * (src0) -> queue -> + * -> [post proc] -> tee < + * (src1) -> imageenc -> metadatamuxer -> filesink + *----------------------------------------------------------------------------- + * + * + * + * The property of elements are: + * + * queue - "max-size-buffers", 1, "leaky", 2, + * + * The image bin opens file for image writing in READY to PAUSED state change. + * The image bin closes the file in PAUSED to READY state change. + * + * + * + */ + +/* + * includes + */ + +#include + +#include "camerabinimage.h" +#include "camerabingeneral.h" + +#include "string.h" + +/* default internal element names */ + +#define DEFAULT_SINK "filesink" +#define DEFAULT_ENC "jpegenc" +#define DEFAULT_META_MUX "metadatamux" + +enum +{ + PROP_0, + PROP_FILENAME +}; + +static gboolean gst_camerabin_image_create_elements (GstCameraBinImage * img); +static void gst_camerabin_image_destroy_elements (GstCameraBinImage * img); + +static void gst_camerabin_image_dispose (GstCameraBinImage * sink); +static GstStateChangeReturn +gst_camerabin_image_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_camerabin_image_send_event (GstElement * element, + GstEvent * event); +static void gst_camerabin_image_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_camerabin_image_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +GST_BOILERPLATE (GstCameraBinImage, gst_camerabin_image, GstBin, GST_TYPE_BIN); + +static const GstElementDetails gst_camerabin_image_details = +GST_ELEMENT_DETAILS ("Image capture bin for camerabin", + "Bin/Image", + "Process and store image data", + "Edgard Lima \n" + "Nokia Corporation "); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static void +gst_camerabin_image_base_init (gpointer klass) +{ + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (eklass, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (eklass, + gst_static_pad_template_get (&src_template)); + gst_element_class_set_details (eklass, &gst_camerabin_image_details); +} + +static void +gst_camerabin_image_class_init (GstCameraBinImageClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = + (GObjectFinalizeFunc) GST_DEBUG_FUNCPTR (gst_camerabin_image_dispose); + eklass->change_state = GST_DEBUG_FUNCPTR (gst_camerabin_image_change_state); + eklass->send_event = GST_DEBUG_FUNCPTR (gst_camerabin_image_send_event); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_camerabin_image_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_camerabin_image_get_property); + + /** + * GstCameraBinImage:filename + * + * This property can be used to specify the filename of the image. + * + **/ + g_object_class_install_property (gobject_class, PROP_FILENAME, + g_param_spec_string ("filename", "Filename", + "Filename of the image to save", NULL, G_PARAM_READWRITE)); +} + +static void +gst_camerabin_image_init (GstCameraBinImage * img, + GstCameraBinImageClass * g_class) +{ + img->filename = g_string_new (""); + + img->pad_tee_enc = NULL; + img->pad_tee_view = NULL; + + img->post = NULL; + img->tee = NULL; + img->enc = NULL; + img->user_enc = NULL; + img->meta_mux = NULL; + img->sink = NULL; + img->queue = NULL; + + /* Create src and sink ghost pads */ + img->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); + gst_element_add_pad (GST_ELEMENT (img), img->sinkpad); + + img->srcpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (img), img->srcpad); + + img->elements_created = FALSE; +} + +static void +gst_camerabin_image_dispose (GstCameraBinImage * img) +{ + g_string_free (img->filename, TRUE); + img->filename = NULL; + + if (img->user_enc) { + gst_object_unref (img->user_enc); + img->user_enc = NULL; + } + + if (img->post) { + gst_object_unref (img->post); + img->post = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) img); +} + +static GstStateChangeReturn +gst_camerabin_image_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCameraBinImage *img = GST_CAMERABIN_IMAGE (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_camerabin_image_create_elements (img)) { + return GST_STATE_CHANGE_FAILURE; + } + /* Allow setting filename when image bin in READY state */ + gst_element_set_locked_state (img->sink, TRUE); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_element_set_locked_state (img->sink, FALSE); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Set sink to NULL in order to write the file _now_ */ + GST_INFO ("write img file: %s", img->filename->str); + gst_element_set_locked_state (img->sink, TRUE); + gst_element_set_state (img->sink, GST_STATE_NULL); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + gst_camerabin_image_destroy_elements (img); + break; + default: + break; + } + + return ret; +} + +gboolean +gst_camerabin_image_send_event (GstElement * element, GstEvent * event) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (element); + gboolean ret = FALSE; + + GST_INFO ("got %s event", GST_EVENT_TYPE_NAME (event)); + + if (GST_EVENT_IS_DOWNSTREAM (event)) { + ret = gst_pad_send_event (bin->sinkpad, event); + } else { + if (bin->sink) { + ret = gst_element_send_event (bin->sink, event); + } else { + GST_WARNING ("upstream event handling failed"); + } + } + + return ret; +} + +static void +gst_camerabin_image_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_string_assign (bin->filename, g_value_get_string (value)); + if (bin->sink) { + g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str, + NULL); + } else { + GST_INFO ("no sink, not setting name yet"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camerabin_image_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, bin->filename->str); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* + * static helper functions implementation + */ + +/** + * metadata_write_probe: + * @pad: sink pad of metadata muxer + * @buffer: received buffer + * @u_data: image bin object + * + * Buffer probe that sets Xmp.dc.type and Xmp.dc.format tags + * to metadata muxer based on preceding element src pad caps. + * + * Returns: TRUE always + */ +static gboolean +metadata_write_probe (GstPad * pad, GstBuffer * buffer, gpointer u_data) +{ + /* Add XMP tags */ + GstCameraBinImage *img = NULL; + GstTagSetter *setter = NULL; + GstPad *srcpad = NULL; + GstCaps *caps = NULL; + GstStructure *st = NULL; + + img = GST_CAMERABIN_IMAGE (u_data); + + g_return_val_if_fail (img != NULL, TRUE); + + setter = GST_TAG_SETTER (img->meta_mux); + + if (!setter) { + GST_WARNING_OBJECT (img, "setting tags failed"); + goto done; + } + + /* Xmp.dc.type tag */ + gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE, + GST_TAG_CODEC, "Image", NULL); + /* Xmp.dc.format tag */ + if (img->enc) { + srcpad = gst_element_get_static_pad (img->enc, "src"); + } + GST_LOG_OBJECT (img, "srcpad:%" GST_PTR_FORMAT, srcpad); + if (srcpad) { + caps = gst_pad_get_negotiated_caps (srcpad); + GST_LOG_OBJECT (img, "caps:%" GST_PTR_FORMAT, caps); + if (caps) { + /* If there are many structures, we can't know which one to use */ + if (gst_caps_get_size (caps) != 1) { + GST_WARNING_OBJECT (img, "can't decide structure for format tag"); + goto done; + } + st = gst_caps_get_structure (caps, 0); + if (st) { + GST_DEBUG_OBJECT (img, "Xmp.dc.format:%s", gst_structure_get_name (st)); + gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE, + GST_TAG_VIDEO_CODEC, gst_structure_get_name (st), NULL); + } + } + } +done: + if (caps) + gst_caps_unref (caps); + if (srcpad) + gst_object_unref (srcpad); + + return TRUE; +} + + +/** + * gst_camerabin_image_create_elements: + * @img: a pointer to #GstCameraBinImage object + * + * This function creates needed #GstElements and resources to capture images. + * Use gst_camerabin_image_destroy_elements to release these resources. + * + * Image bin: + * img->sinkpad ! [ post process !] tee name=t0 ! encoder ! metadata ! filesink + * t0. ! queue ! img->srcpad + * + * Returns: %TRUE if succeeded or FALSE if failed + */ +static gboolean +gst_camerabin_image_create_elements (GstCameraBinImage * img) +{ + GstPad *sinkpad = NULL, *img_sinkpad = NULL, *img_srcpad = NULL; + gboolean ret = FALSE; + GstBin *imgbin = NULL; + + g_return_val_if_fail (img != NULL, FALSE); + + GST_DEBUG ("creating image capture elements"); + + imgbin = GST_BIN (img); + + if (img->elements_created) { + GST_WARNING ("elements already created"); + ret = TRUE; + goto done; + } else { + img->elements_created = TRUE; + } + + /* Create image pre/post-processing element if any */ + if (img->post) { + if (!gst_camerabin_add_element (imgbin, img->post)) { + goto done; + } + img_sinkpad = gst_element_get_static_pad (img->post, "sink"); + } + + /* Create tee */ + if (!(img->tee = gst_camerabin_create_and_add_element (imgbin, "tee"))) { + goto done; + } + + /* Set up sink ghost pad for img bin */ + if (!img_sinkpad) { + img_sinkpad = gst_element_get_static_pad (img->tee, "sink"); + } + gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), img_sinkpad); + + /* Add colorspace converter */ + img->pad_tee_enc = gst_element_get_request_pad (img->tee, "src%d"); + if (!gst_camerabin_create_and_add_element (imgbin, "ffmpegcolorspace")) { + goto done; + } + + /* Create image encoder */ + if (img->user_enc) { + img->enc = img->user_enc; + if (!gst_camerabin_add_element (imgbin, img->enc)) { + goto done; + } + } else if (!(img->enc = + gst_camerabin_create_and_add_element (imgbin, DEFAULT_ENC))) { + goto done; + } + + /* Create metadata element */ + if (!(img->meta_mux = + gst_camerabin_create_and_add_element (imgbin, DEFAULT_META_MUX))) { + goto done; + } + /* Add probe for XMP metadata writing */ + sinkpad = gst_element_get_static_pad (img->meta_mux, "sink"); + gst_pad_add_buffer_probe (sinkpad, G_CALLBACK (metadata_write_probe), img); + gst_object_unref (sinkpad); + /* Set "Intel" exif byte-order if possible */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (img->meta_mux), + "exif-byte-order")) { + g_object_set (G_OBJECT (img->meta_mux), "exif-byte-order", 1, NULL); + } + + /* Create file sink element */ + if (!(img->sink = + gst_camerabin_create_and_add_element (imgbin, DEFAULT_SINK))) { + goto done; + } + + /* Create queue element leading to view finder, attaches it to the tee */ + img->pad_tee_view = gst_element_get_request_pad (img->tee, "src%d"); + if (!(img->queue = gst_camerabin_create_and_add_element (imgbin, "queue"))) { + goto done; + } + + /* Set properties */ + g_object_set (G_OBJECT (img->sink), "location", img->filename->str, NULL); + g_object_set (G_OBJECT (img->sink), "async", FALSE, NULL); + + g_object_set (G_OBJECT (img->queue), "max-size-buffers", 1, "leaky", 2, NULL); + + /* Set up src ghost pad for img bin */ + img_srcpad = gst_element_get_static_pad (img->queue, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD (img->srcpad), img_srcpad); + + /* Never let image bin eos events reach view finder */ + gst_pad_add_event_probe (img->srcpad, + G_CALLBACK (gst_camerabin_drop_eos_probe), img); + + ret = TRUE; + +done: + + if (img_srcpad) { + gst_object_unref (img_srcpad); + } + if (img_sinkpad) { + gst_object_unref (img_sinkpad); + } + if (!ret) { + gst_camerabin_image_destroy_elements (img); + } + + return ret; +} + + +/** + * gst_camerabin_image_destroy_elements: + * @img: a pointer to #GstCameraBinImage object + * + * This function releases resources allocated in + * gst_camerabin_image_create_elements. + * + */ +static void +gst_camerabin_image_destroy_elements (GstCameraBinImage * img) +{ + GST_LOG ("destroying img elements"); + if (img->pad_tee_enc) { + gst_element_release_request_pad (img->tee, img->pad_tee_enc); + img->pad_tee_enc = NULL; + } + + if (img->pad_tee_view) { + gst_element_release_request_pad (img->tee, img->pad_tee_view); + img->pad_tee_view = NULL; + } + + gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD (img->srcpad), NULL); + + gst_camerabin_remove_elements_from_bin (GST_BIN (img)); + + img->post = NULL; + img->tee = NULL; + img->enc = NULL; + img->meta_mux = NULL; + img->sink = NULL; + img->queue = NULL; + + img->elements_created = FALSE; +} + +void +gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder) +{ + if (img->user_enc) + gst_object_unref (img->user_enc); + if (encoder) + gst_object_ref (encoder); + + img->user_enc = encoder; +} + +void +gst_camerabin_image_set_postproc (GstCameraBinImage * img, + GstElement * postproc) +{ + if (img->post) + gst_object_unref (img->post); + if (postproc) + gst_object_ref (postproc); + + img->post = postproc; +} + +GstElement * +gst_camerabin_image_get_encoder (GstCameraBinImage * img) +{ + GstElement *enc; + + if (img->user_enc) { + enc = img->user_enc; + } else { + enc = img->enc; + } + + return enc; +} + +GstElement * +gst_camerabin_image_get_postproc (GstCameraBinImage * img) +{ + return img->post; +} diff --git a/gst/camerabin/camerabinimage.h b/gst/camerabin/camerabinimage.h new file mode 100644 index 0000000..c05f549 --- /dev/null +++ b/gst/camerabin/camerabinimage.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef __CAMERABIN_IMAGE_H__ +#define __CAMERABIN_IMAGE_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CAMERABIN_IMAGE (gst_camerabin_image_get_type()) +#define GST_CAMERABIN_IMAGE_CAST(obj) ((GstCameraBinImage*)(obj)) +#define GST_CAMERABIN_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImage)) +#define GST_CAMERABIN_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImageClass)) +#define GST_IS_CAMERABIN_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN_IMAGE)) +#define GST_IS_CAMERABIN_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_IMAGE)) + +/** + * GstCameraBinImage: + * + * The opaque #GstCameraBinImage structure. + */ + +typedef struct _GstCameraBinImage GstCameraBinImage; +typedef struct _GstCameraBinImageClass GstCameraBinImageClass; + +struct _GstCameraBinImage +{ + GstBin parent; + GString *filename; + + /* Ghost pads of image bin */ + GstPad *sinkpad; + GstPad *srcpad; + + /* Tee src pad leading to image encoder */ + GstPad *pad_tee_enc; + /* Tee src pad leading to view finder */ + GstPad *pad_tee_view; + + GstElement *post; + + GstElement *tee; + GstElement *enc; + GstElement *user_enc; + GstElement *meta_mux; + GstElement *sink; + GstElement *queue; + + gboolean elements_created; +}; + +struct _GstCameraBinImageClass +{ + GstBinClass parent_class; +}; + +GType gst_camerabin_image_get_type (void); + +void +gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder); + +void +gst_camerabin_image_set_postproc (GstCameraBinImage * img, + GstElement * postproc); + +GstElement *gst_camerabin_image_get_encoder (GstCameraBinImage * img); + +GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img); + +G_END_DECLS + +#endif /* #ifndef __CAMERABIN_IMAGE_H__ */ diff --git a/gst/camerabin/camerabinvideo.c b/gst/camerabin/camerabinvideo.c new file mode 100644 index 0000000..14a9506 --- /dev/null +++ b/gst/camerabin/camerabinvideo.c @@ -0,0 +1,826 @@ +/* + * 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:camerabinvideo + * @short_description: video recording module of #GstCameraBin + * + * + * + * + * The pipeline for this module is: + * + * + * + *----------------------------------------------------------------------------- + * audiosrc -> audio_queue -> audioconvert -> volume -> audioenc + * > videomux -> filesink + * video_queue -> [timeoverlay] -> videoenc + * -> [post proc] -> tee < + * queue -> + *----------------------------------------------------------------------------- + * + * + * + * The properties of elements are: + * + * queue - "leaky", 2 (Leaky on downstream (old buffers)) + * + * + * + */ + +/* + * includes + */ + + +#include +#include "camerabingeneral.h" + +#include "camerabinvideo.h" + +/* + * defines and static global vars + */ + +/* internal element names */ + +#define DEFAULT_AUD_SRC "pulsesrc" +#define DEFAULT_AUD_ENC "vorbisenc" +#define DEFAULT_VID_ENC "theoraenc" +#define DEFAULT_MUX "oggmux" +#define DEFAULT_SINK "filesink" + +#define USE_AUDIO_CONVERSION 1 + +enum +{ + PROP_0, + PROP_FILENAME +}; + +static void gst_camerabin_video_dispose (GstCameraBinVideo * sink); +static void gst_camerabin_video_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_camerabin_video_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClock *gst_camerabin_video_provide_clock (GstElement * elem); +static GstStateChangeReturn +gst_camerabin_video_change_state (GstElement * element, + GstStateChange transition); + +static + gboolean camerabin_video_pad_tee_src0_have_buffer (GstPad * pad, + GstBuffer * buffer, gpointer u_data); +static gboolean camerabin_video_pad_aud_src_have_buffer (GstPad * pad, + GstBuffer * buffer, gpointer u_data); +static gboolean camerabin_video_sink_have_event (GstPad * pad, GstEvent * event, + gpointer u_data); +static gboolean gst_camerabin_video_create_elements (GstCameraBinVideo * vid); +static void gst_camerabin_video_destroy_elements (GstCameraBinVideo * vid); + +GST_BOILERPLATE (GstCameraBinVideo, gst_camerabin_video, GstBin, GST_TYPE_BIN); + +static const GstElementDetails gst_camerabin_video_details = +GST_ELEMENT_DETAILS ("Video capture bin for camerabin", + "Bin/Video", + "Process and store video data", + "Edgard Lima \n" + "Nokia Corporation "); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + +/* GObject methods implementation */ + +static void +gst_camerabin_video_base_init (gpointer klass) +{ + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (eklass, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (eklass, + gst_static_pad_template_get (&src_template)); + gst_element_class_set_details (eklass, &gst_camerabin_video_details); +} + +static void +gst_camerabin_video_class_init (GstCameraBinVideoClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = + (GObjectFinalizeFunc) GST_DEBUG_FUNCPTR (gst_camerabin_video_dispose); + eklass->change_state = GST_DEBUG_FUNCPTR (gst_camerabin_video_change_state); + + eklass->provide_clock = GST_DEBUG_FUNCPTR (gst_camerabin_video_provide_clock); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_camerabin_video_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_camerabin_video_get_property); + + /** + * GstCameraBinVideo:filename: + * + * This property can be used to specify the filename of the video. + * + **/ + g_object_class_install_property (gobject_class, PROP_FILENAME, + g_param_spec_string ("filename", "Filename", + "Filename of the video to save", NULL, G_PARAM_READWRITE)); +} + +static void +gst_camerabin_video_init (GstCameraBinVideo * vid, + GstCameraBinVideoClass * g_class) +{ + vid->filename = g_string_new (""); + + vid->user_post = NULL; + vid->user_vid_enc = NULL; + vid->user_aud_enc = NULL; + vid->user_aud_src = NULL; + vid->user_mux = NULL; + + vid->aud_src = NULL; + vid->sink = NULL; + vid->tee = NULL; + vid->volume = NULL; + vid->video_queue = NULL; + + vid->tee_video_srcpad = NULL; + vid->tee_vf_srcpad = NULL; + + vid->pending_eos = NULL; + + /* Create src and sink ghost pads */ + vid->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); + gst_element_add_pad (GST_ELEMENT (vid), vid->sinkpad); + + vid->srcpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (vid), vid->srcpad); + + /* Add probe for handling eos when stopping recording */ + gst_pad_add_event_probe (vid->sinkpad, + G_CALLBACK (camerabin_video_sink_have_event), vid); +} + +static void +gst_camerabin_video_dispose (GstCameraBinVideo * vid) +{ + GST_DEBUG_OBJECT (vid, "disposing"); + + g_string_free (vid->filename, TRUE); + vid->filename = NULL; + + if (vid->user_post) { + gst_object_unref (vid->user_post); + vid->user_post = NULL; + } + + if (vid->user_vid_enc) { + gst_object_unref (vid->user_vid_enc); + vid->user_vid_enc = NULL; + } + + if (vid->user_aud_enc) { + gst_object_unref (vid->user_aud_enc); + vid->user_aud_enc = NULL; + } + + if (vid->user_aud_src) { + gst_object_unref (vid->user_aud_src); + vid->user_aud_src = NULL; + } + + if (vid->user_mux) { + gst_object_unref (vid->user_mux); + vid->user_mux = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) vid); +} + + +static void +gst_camerabin_video_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBinVideo *bin = GST_CAMERABIN_VIDEO (object); + + switch (prop_id) { + case PROP_FILENAME: + g_string_assign (bin->filename, g_value_get_string (value)); + if (bin->sink) { + g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str, + NULL); + } else { + GST_INFO ("no sink, not setting name yet"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camerabin_video_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBinVideo *bin = GST_CAMERABIN_VIDEO (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, bin->filename->str); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* GstElement methods implementation */ + +static GstClock * +gst_camerabin_video_provide_clock (GstElement * elem) +{ + GstElement *aud_src = GST_CAMERABIN_VIDEO (elem)->aud_src; + if (aud_src) { + return gst_element_provide_clock (aud_src); + } else { + return NULL; + } +} + +static GstStateChangeReturn +gst_camerabin_video_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCameraBinVideo *vid = GST_CAMERABIN_VIDEO (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_camerabin_video_create_elements (vid)) { + return GST_STATE_CHANGE_FAILURE; + } + /* Don't change sink to READY yet to allow changing the + filename in READY state. */ + gst_element_set_locked_state (vid->sink, TRUE); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + vid->calculate_adjust_ts_video = TRUE; + vid->calculate_adjust_ts_aud = TRUE; + g_object_set (G_OBJECT (vid->sink), "async", FALSE, NULL); + gst_element_set_locked_state (vid->sink, FALSE); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + vid->calculate_adjust_ts_video = TRUE; + vid->calculate_adjust_ts_aud = TRUE; + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Set sink to NULL in order to write the file _now_ */ + GST_INFO ("write vid file: %s", vid->filename->str); + gst_element_set_locked_state (vid->sink, TRUE); + gst_element_set_state (vid->sink, GST_STATE_NULL); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + if (vid->pending_eos) { + /* Video bin is still paused, so push eos directly to video queue */ + GST_DEBUG_OBJECT (vid, "pushing pending eos"); + gst_pad_push_event (vid->tee_video_srcpad, vid->pending_eos); + vid->pending_eos = NULL; + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Reset counters related to timestamp rewriting */ + vid->adjust_ts_video = 0; + vid->last_ts_video = 0; + vid->adjust_ts_aud = 0; + vid->last_ts_aud = 0; + + if (vid->pending_eos) { + gst_event_unref (vid->pending_eos); + vid->pending_eos = NULL; + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_camerabin_video_destroy_elements (vid); + break; + default: + break; + } + + return ret; +} + +/* + * static helper functions implementation + */ + +/** + * camerabin_video_pad_tee_src0_have_buffer: + * @pad: tee src pad leading to video encoding + * @event: received buffer + * @u_data: video bin object + * + * Buffer probe for rewriting video buffer timestamps. + * + * Returns: TRUE always + */ +static gboolean +camerabin_video_pad_tee_src0_have_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBinVideo *vid = (GstCameraBinVideo *) u_data; + + GST_LOG ("buffer in with size %d ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + + if (G_UNLIKELY (vid->calculate_adjust_ts_video)) { + GstEvent *event; + GstObject *tee; + GstPad *sinkpad; + vid->adjust_ts_video = GST_BUFFER_TIMESTAMP (buffer) - vid->last_ts_video; + vid->calculate_adjust_ts_video = FALSE; + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + 0, GST_CLOCK_TIME_NONE, vid->last_ts_video); + /* Send the newsegment to both view finder and video bin */ + tee = gst_pad_get_parent (pad); + sinkpad = gst_element_get_static_pad (GST_ELEMENT (tee), "sink"); + gst_pad_send_event (sinkpad, event); + gst_object_unref (tee); + gst_object_unref (sinkpad); + GST_LOG_OBJECT (vid, "vid ts adjustment: %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid->adjust_ts_video)); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + GST_BUFFER_TIMESTAMP (buffer) -= vid->adjust_ts_video; + vid->last_ts_video = GST_BUFFER_TIMESTAMP (buffer); + if (GST_BUFFER_DURATION_IS_VALID (buffer)) + vid->last_ts_video += GST_BUFFER_DURATION (buffer); + + + GST_LOG ("buffer out with size %d ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + return TRUE; +} + +/** + * camerabin_video_pad_aud_src_have_buffer: + * @pad: audio source src pad + * @event: received buffer + * @u_data: video bin object + * + * Buffer probe for rewriting audio buffer timestamps. + * + * Returns: TRUE always + */ +static gboolean +camerabin_video_pad_aud_src_have_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBinVideo *vid = (GstCameraBinVideo *) u_data; + + if (vid->calculate_adjust_ts_aud) { + GstEvent *event; + GstPad *peerpad = NULL; + vid->adjust_ts_aud = GST_BUFFER_TIMESTAMP (buffer) - vid->last_ts_aud; + vid->calculate_adjust_ts_aud = FALSE; + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + 0, GST_CLOCK_TIME_NONE, vid->last_ts_aud); + peerpad = gst_pad_get_peer (pad); + if (peerpad) { + gst_pad_send_event (peerpad, event); + gst_object_unref (peerpad); + } + GST_LOG_OBJECT (vid, "aud ts adjustment: %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid->adjust_ts_aud)); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + GST_BUFFER_TIMESTAMP (buffer) -= vid->adjust_ts_aud; + vid->last_ts_aud = GST_BUFFER_TIMESTAMP (buffer); + if (GST_BUFFER_DURATION_IS_VALID (buffer)) + vid->last_ts_aud += GST_BUFFER_DURATION (buffer); + GST_LOG ("buffer out with size %d ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + return TRUE; +} + +/** + * camerabin_video_sink_have_event: + * @pad: video bin sink pad + * @event: received event + * @u_data: video bin object + * + * Event probe for video bin eos handling. + * Copies the eos event to audio branch of video bin. + * + * Returns: FALSE to drop the event, TRUE otherwise + */ +static gboolean +camerabin_video_sink_have_event (GstPad * pad, GstEvent * event, + gpointer u_data) +{ + GstCameraBinVideo *vid = (GstCameraBinVideo *) u_data; + gboolean ret = TRUE; + + GST_DEBUG_OBJECT (vid, "got videobin sink event: %s", + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + if (vid->aud_src) { + GST_DEBUG_OBJECT (vid, "copying %s to audio branch", + GST_EVENT_TYPE_NAME (event)); + gst_element_send_event (vid->aud_src, gst_event_copy (event)); + } + + /* If we're paused, we can't pass eos to video now to avoid blocking. + Instead send eos when changing to playing next time. */ + if (GST_STATE (GST_ELEMENT (vid)) == GST_STATE_PAUSED) { + GST_DEBUG_OBJECT (vid, "paused, delay eos sending"); + vid->pending_eos = gst_event_ref (event); + ret = FALSE; /* Drop the event */ + } + break; + default: + break; + } + return ret; +} + +/** + * gst_camerabin_video_create_elements: + * @vid: a pointer to #GstCameraBinVideo + * + * This function creates the needed #GstElements and resources to record videos. + * Use gst_camerabin_video_destroy_elements() to free these resources. + * + * Returns: %TRUE if succeeded or FALSE if failed + */ +static gboolean +gst_camerabin_video_create_elements (GstCameraBinVideo * vid) +{ + GstPad *pad = NULL, *vid_sinkpad = NULL, *vid_srcpad = NULL; + GstBin *vidbin = GST_BIN (vid); + GstElement *queue = NULL; + + vid->adjust_ts_video = 0; + vid->last_ts_video = 0; + vid->calculate_adjust_ts_video = FALSE; + + vid->adjust_ts_aud = 0; + vid->last_ts_aud = 0; + vid->calculate_adjust_ts_aud = FALSE; + + /* Add video post processing element if any */ + if (vid->user_post) { + if (!gst_camerabin_add_element (vidbin, vid->user_post)) { + goto error; + } + vid_sinkpad = gst_element_get_static_pad (vid->user_post, "sink"); + } + + /* Add tee element */ + if (!(vid->tee = gst_camerabin_create_and_add_element (vidbin, "tee"))) { + goto error; + } + + /* Set up sink ghost pad for video bin */ + if (!vid_sinkpad) { + vid_sinkpad = gst_element_get_static_pad (vid->tee, "sink"); + } + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->sinkpad), vid_sinkpad); + + + /* Add queue element for video */ + vid->tee_video_srcpad = gst_element_get_request_pad (vid->tee, "src%d"); + if (!(vid->video_queue = + gst_camerabin_create_and_add_element (vidbin, "queue"))) { + goto error; + } + + /* Add probe for rewriting video timestamps */ + gst_pad_add_buffer_probe (vid->tee_video_srcpad, + G_CALLBACK (camerabin_video_pad_tee_src0_have_buffer), vid); + + +#ifdef USE_TIMEOVERLAY + /* Add timeoverlay element to visualize elapsed time for debugging */ + if (!(gst_camerabin_create_and_add_element (vidbin, "timeoverlay"))) { + goto error; + } +#endif + + /* Add user set or default video encoder element */ + if (vid->user_vid_enc) { + vid->vid_enc = vid->user_vid_enc; + if (!gst_camerabin_add_element (vidbin, vid->vid_enc)) { + goto error; + } + } else if (!(vid->vid_enc = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_VID_ENC))) { + goto error; + } + + /* Add user set or default muxer element */ + if (vid->user_mux) { + vid->muxer = vid->user_mux; + if (!gst_camerabin_add_element (vidbin, vid->muxer)) { + goto error; + } + } else if (!(vid->muxer = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_MUX))) { + goto error; + } + + /* Add sink element for storing the video */ + if (!(vid->sink = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_SINK))) { + goto error; + } + g_object_set (G_OBJECT (vid->sink), "location", vid->filename->str, NULL); + + + /* Add user set or default audio source element */ + if (vid->user_aud_src) { + vid->aud_src = vid->user_aud_src; + if (!gst_camerabin_add_element (vidbin, vid->aud_src)) { + goto error; + } + } else if (!(vid->aud_src = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_AUD_SRC))) { + goto error; + } + + /* Add queue element for audio */ + if (!(queue = gst_camerabin_create_and_add_element (vidbin, "queue"))) { + goto error; + } + queue = NULL; + + /* Add optional audio conversion and volume elements and + raise no errors if adding them fails */ +#ifdef USE_AUDIO_CONVERSION + if (!gst_camerabin_try_add_element (vidbin, + gst_element_factory_make ("audioconvert", NULL))) { + GST_WARNING_OBJECT (vid, "unable to add audio conversion element"); + /* gst_camerabin_try_add_element() destroyed the element */ + } +#endif + vid->volume = gst_element_factory_make ("volume", NULL); + if (!gst_camerabin_try_add_element (vidbin, vid->volume)) { + GST_WARNING_OBJECT (vid, "unable to add volume element"); + /* gst_camerabin_try_add_element() destroyed the element */ + vid->volume = NULL; + } + + /* Add user set or default audio encoder element */ + if (vid->user_aud_enc) { + vid->aud_enc = vid->user_aud_enc; + if (!gst_camerabin_add_element (vidbin, vid->aud_enc)) { + goto error; + } + } else if (!(vid->aud_enc = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_AUD_ENC))) { + goto error; + } + + /* Link audio part to the muxer */ + if (!gst_element_link (vid->aud_enc, vid->muxer)) { + GST_ELEMENT_ERROR (vid, CORE, NEGOTIATION, (NULL), + ("linking audio encoder and muxer failed")); + goto error; + } + + /* Add queue leading out of the video bin and to view finder */ + vid->tee_vf_srcpad = gst_element_get_request_pad (vid->tee, "src%d"); + if (!(queue = gst_camerabin_create_and_add_element (vidbin, "queue"))) { + goto error; + } + /* Set queue leaky, we don't want to block video encoder feed, but + prefer leaking view finder buffers instead. */ + g_object_set (G_OBJECT (queue), "leaky", 2, NULL); + + /* Set up src ghost pad for video bin */ + vid_srcpad = gst_element_get_static_pad (queue, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->srcpad), vid_srcpad); + /* Never let video bin eos events reach view finder */ + gst_pad_add_event_probe (vid_srcpad, + G_CALLBACK (gst_camerabin_drop_eos_probe), vid); + + pad = gst_element_get_static_pad (vid->aud_src, "src"); + gst_pad_add_buffer_probe (pad, + G_CALLBACK (camerabin_video_pad_aud_src_have_buffer), vid); + gst_object_unref (pad); + + GST_DEBUG ("created video elements"); + + return TRUE; + +error: + + gst_camerabin_video_destroy_elements (vid); + + return FALSE; + +} + +/** + * gst_camerabin_video_destroy_elements: + * @vid: a pointer to #GstCameraBinVideo + * + * This function destroys all the elements created by + * gst_camerabin_video_create_elements(). + * + */ +static void +gst_camerabin_video_destroy_elements (GstCameraBinVideo * vid) +{ + GST_DEBUG ("destroying video elements"); + + /* Release tee request pads */ + if (vid->tee_video_srcpad) { + gst_element_release_request_pad (vid->tee, vid->tee_video_srcpad); + vid->tee_video_srcpad = NULL; + } + if (vid->tee_vf_srcpad) { + gst_element_release_request_pad (vid->tee, vid->tee_vf_srcpad); + vid->tee_vf_srcpad = NULL; + } + + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->srcpad), NULL); + + gst_camerabin_remove_elements_from_bin (GST_BIN (vid)); + + vid->aud_src = NULL; + vid->sink = NULL; + vid->tee = NULL; + vid->volume = NULL; + vid->video_queue = NULL; + vid->vid_enc = NULL; + vid->aud_enc = NULL; + vid->muxer = NULL; + + if (vid->pending_eos) { + gst_event_unref (vid->pending_eos); + vid->pending_eos = NULL; + } + + return; +} + +/* + * Set & get mute and video capture elements + */ + +void +gst_camerabin_video_set_mute (GstCameraBinVideo * vid, gboolean mute) +{ + if (vid && vid->volume) { + GST_DEBUG_OBJECT (vid, "setting mute %s", mute ? "on" : "off"); + g_object_set (vid->volume, "mute", mute, NULL); + } +} + +void +gst_camerabin_video_set_post (GstCameraBinVideo * vid, GstElement * post) +{ + GstElement **user_post; + GST_DEBUG_OBJECT (vid, "setting video post processing: %" GST_PTR_FORMAT, + post); + GST_OBJECT_LOCK (vid); + user_post = &vid->user_post; + gst_object_replace ((GstObject **) user_post, GST_OBJECT (post)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_video_enc (GstCameraBinVideo * vid, + GstElement * video_enc) +{ + GstElement **user_vid_enc; + GST_DEBUG_OBJECT (vid, "setting video encoder: %" GST_PTR_FORMAT, video_enc); + GST_OBJECT_LOCK (vid); + user_vid_enc = &vid->user_vid_enc; + gst_object_replace ((GstObject **) user_vid_enc, GST_OBJECT (video_enc)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_audio_enc (GstCameraBinVideo * vid, + GstElement * audio_enc) +{ + GstElement **user_aud_enc; + GST_DEBUG_OBJECT (vid, "setting audio encoder: %" GST_PTR_FORMAT, audio_enc); + GST_OBJECT_LOCK (vid); + user_aud_enc = &vid->user_aud_enc; + gst_object_replace ((GstObject **) user_aud_enc, GST_OBJECT (audio_enc)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_muxer (GstCameraBinVideo * vid, GstElement * muxer) +{ + GstElement **user_mux; + GST_DEBUG_OBJECT (vid, "setting muxer: %" GST_PTR_FORMAT, muxer); + GST_OBJECT_LOCK (vid); + user_mux = &vid->user_mux; + gst_object_replace ((GstObject **) user_mux, GST_OBJECT (muxer)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_audio_src (GstCameraBinVideo * vid, + GstElement * audio_src) +{ + GstElement **user_aud_src; + GST_DEBUG_OBJECT (vid, "setting audio source: %" GST_PTR_FORMAT, audio_src); + GST_OBJECT_LOCK (vid); + user_aud_src = &vid->user_aud_src; + gst_object_replace ((GstObject **) user_aud_src, GST_OBJECT (audio_src)); + GST_OBJECT_UNLOCK (vid); +} + +gboolean +gst_camerabin_video_get_mute (GstCameraBinVideo * vid) +{ + gboolean mute = ARG_DEFAULT_MUTE; + + if (vid && vid->volume) { + g_object_get (vid->volume, "mute", &mute, NULL); + } + return mute; +} + +GstElement * +gst_camerabin_video_get_post (GstCameraBinVideo * vid) +{ + return vid->user_post; +} + +GstElement * +gst_camerabin_video_get_video_enc (GstCameraBinVideo * vid) +{ + return vid->vid_enc ? vid->vid_enc : vid->user_vid_enc; +} + +GstElement * +gst_camerabin_video_get_audio_enc (GstCameraBinVideo * vid) +{ + return vid->aud_enc ? vid->aud_enc : vid->user_aud_enc; +} + +GstElement * +gst_camerabin_video_get_muxer (GstCameraBinVideo * vid) +{ + return vid->muxer ? vid->muxer : vid->user_mux; +} + +GstElement * +gst_camerabin_video_get_audio_src (GstCameraBinVideo * vid) +{ + return vid->aud_src ? vid->aud_src : vid->user_aud_src; +} diff --git a/gst/camerabin/camerabinvideo.h b/gst/camerabin/camerabinvideo.h new file mode 100644 index 0000000..9f4f515 --- /dev/null +++ b/gst/camerabin/camerabinvideo.h @@ -0,0 +1,136 @@ +/* + * 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. + */ + +#ifndef __CAMERABIN_VIDEO_H__ +#define __CAMERABIN_VIDEO_H__ + +#include + +G_BEGIN_DECLS + +//#define USE_TIMEOVERLAY 1 + +#define ARG_DEFAULT_MUTE FALSE + +#define GST_TYPE_CAMERABIN_VIDEO (gst_camerabin_video_get_type()) +#define GST_CAMERABIN_VIDEO_CAST(obj) ((GstCameraBinVideo*)(obj)) +#define GST_CAMERABIN_VIDEO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN_VIDEO,GstCameraBinVideo)) +#define GST_CAMERABIN_VIDEO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN_VIDEO,GstCameraBinVideoClass)) +#define GST_IS_CAMERABIN_VIDEO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN_VIDEO)) +#define GST_IS_CAMERABIN_VIDEO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_VIDEO)) + +/** + * GstCameraBinVideo: + * + * The opaque #GstCameraBinVideo structure. + */ + +typedef struct _GstCameraBinVideo GstCameraBinVideo; +typedef struct _GstCameraBinVideoClass GstCameraBinVideoClass; + +struct _GstCameraBinVideo +{ + GstBin parent; + + GString *filename; + + /* A/V timestamp rewriting */ + guint64 adjust_ts_video; + guint64 last_ts_video; + gboolean calculate_adjust_ts_video; + + guint64 adjust_ts_aud; + guint64 last_ts_aud; + gboolean calculate_adjust_ts_aud; + + /* Sink and src pads of video bin */ + GstPad *sinkpad; + GstPad *srcpad; + + /* Tee src pads leading to video encoder and view finder */ + GstPad *tee_video_srcpad; + GstPad *tee_vf_srcpad; + + /* User set elements */ + GstElement *user_post; /* Video post processing */ + GstElement *user_vid_enc; + GstElement *user_aud_enc; + GstElement *user_aud_src; + GstElement *user_mux; + + /* Other elements */ + GstElement *aud_src; /* Audio source */ + GstElement *sink; /* Sink for recorded video */ + GstElement *tee; /* Split output to view finder and recording sink */ + GstElement *volume; /* Volume for muting */ + GstElement *video_queue; /* Buffer for raw video frames */ + GstElement *vid_enc; /* Video encoder */ + GstElement *aud_enc; /* Audio encoder */ + GstElement *muxer; /* Muxer */ + + GstEvent *pending_eos; +}; + +struct _GstCameraBinVideoClass +{ + GstBinClass parent_class; +}; + +GType gst_camerabin_video_get_type (void); + +/* + * external function prototypes + */ + +void gst_camerabin_video_set_mute (GstCameraBinVideo * vid, gboolean mute); + +void gst_camerabin_video_set_post (GstCameraBinVideo * vid, GstElement * post); + +void +gst_camerabin_video_set_video_enc (GstCameraBinVideo * vid, + GstElement * video_enc); + +void +gst_camerabin_video_set_audio_enc (GstCameraBinVideo * vid, + GstElement * audio_enc); + +void +gst_camerabin_video_set_muxer (GstCameraBinVideo * vid, GstElement * muxer); + +void +gst_camerabin_video_set_audio_src (GstCameraBinVideo * vid, + GstElement * audio_src); + + +gboolean gst_camerabin_video_get_mute (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_post (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_video_enc (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_audio_enc (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_muxer (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_audio_src (GstCameraBinVideo * vid); + +G_END_DECLS + +#endif /* #ifndef __CAMERABIN_VIDEO_H__ */ diff --git a/gst/camerabin/gstcamerabin-marshal.list b/gst/camerabin/gstcamerabin-marshal.list new file mode 100644 index 0000000..ef70f75 --- /dev/null +++ b/gst/camerabin/gstcamerabin-marshal.list @@ -0,0 +1,6 @@ +# glib-genmarshal --header --prefix=gst_camerabin camerabin_marshal.marshal > camerabin_marshal.h +# glib-genmarshal --body --prefix=gst_camerabin camerabin_marshal.marshal > camerabin_marshal.c + +VOID:INT,INT,INT,INT +VOID:INT,INT + diff --git a/gst/camerabin/gstcamerabin.c b/gst/camerabin/gstcamerabin.c new file mode 100644 index 0000000..d3881f6 --- /dev/null +++ b/gst/camerabin/gstcamerabin.c @@ -0,0 +1,2762 @@ +/* + * 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 _(" ") */ + +#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: use autovideosrc + * v4l2camsrc should have GST_RANK_PRIMARY in maemo and _NONE in plugins-bad + * for now */ +static const char SRC_VID_SRC[] = "v4l2camsrc"; +/*static const char SRC_VID_SRC[] = "v4l2src"; */ + +static const char ZOOM_CROP[] = "videocrop"; +static const char ZOOM_SCALE[] = "videoscale"; + +static const char CAPS_FILTER[] = "capsfilter"; + +static const char COLOR_CONVERTER[] = "ffmpegcolorspace"; + +static const char SRC_OUT_SEL[] = "output-selector"; + +static const char VIEW_IN_SEL[] = "input-selector"; +static const char VIEW_SCALE[] = "videoscale"; +static const char 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, SRC_VID_SRC))) + goto done; +#ifdef USE_COLOR_CONVERTER + if (!gst_camerabin_create_and_add_element (cbin, COLOR_CONVERTER)) + goto done; +#endif + if (!(camera->src_filter = + gst_camerabin_create_and_add_element (cbin, CAPS_FILTER))) + goto done; + if (!(camera->src_zoom_crop = + gst_camerabin_create_and_add_element (cbin, ZOOM_CROP))) + goto done; + if (!(camera->src_zoom_scale = + gst_camerabin_create_and_add_element (cbin, ZOOM_SCALE))) + goto done; + if (!(camera->src_zoom_filter = + gst_camerabin_create_and_add_element (cbin, CAPS_FILTER))) + goto done; + if (!(camera->src_out_sel = + gst_camerabin_create_and_add_element (cbin, SRC_OUT_SEL))) + 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), + VIEW_IN_SEL))) { + 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), + VIEW_SCALE))) { + goto error; + } +#ifdef USE_COLOR_CONVERTER + if (!gst_camerabin_create_and_add_element (GST_BIN (camera), COLOR_CONVERTER)) { + 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), 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; + } + + 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 autovideosink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + + /** + * GstCameraBin:videosrc: + * + * Set up a video source element. + * By default "v4l2camsrc" 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 v4l2camsrc)", + 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", "Nokia", "http://www.nokia.com/") diff --git a/gst/camerabin/gstcamerabin.h b/gst/camerabin/gstcamerabin.h new file mode 100644 index 0000000..809ce72 --- /dev/null +++ b/gst/camerabin/gstcamerabin.h @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#ifndef __GST_CAMERABIN_H__ +#define __GST_CAMERABIN_H__ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "camerabinimage.h" +#include "camerabinvideo.h" + +G_BEGIN_DECLS + +/* #defines don't like whitespacey bits */ +#define GST_TYPE_CAMERABIN \ + (gst_camerabin_get_type()) +#define GST_CAMERABIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN,GstCameraBin)) +#define GST_CAMERABIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN,GstCameraBinClass)) +#define GST_IS_CAMERABIN(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN)) +#define GST_IS_CAMERABIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN)) + +typedef struct _GstCameraBin GstCameraBin; +typedef struct _GstCameraBinClass GstCameraBinClass; + +/** + * GstCameraBin: + * + * The opaque #GstCameraBin structure. + */ + +struct _GstCameraBin +{ + GstPipeline parent; + + /* private */ + GString *filename; + gint mode; /* MODE_IMAGE or MODE_VIDEO */ + guint num_img_buffers; /* no of image buffers captured */ + gboolean stop_requested; /* TRUE if capturing stop needed */ + gboolean paused; /* TRUE if capturing paused */ + + /* resolution and frames per second of image captured by v4l2 device */ + gint width; + gint height; + gint fps_n; + gint fps_d; + + /* Caps applied to capsfilters when taking still image */ + GstCaps *image_capture_caps; + + /* Caps applied to capsfilters when in view finder mode */ + GstCaps *view_finder_caps; + + /* Caps that videosrc supports */ + GstCaps *allowed_caps; + + /* The digital zoom (from 100% to 1000%) */ + gint zoom; + + /* concurrency control */ + GMutex *capture_mutex; + GCond *cond; + gboolean capturing; + + /* pad names for output and input selectors */ + GstPad *pad_src_view; + GstPad *pad_view_src; + GstPad *pad_src_img; + GstPad *pad_view_img; + GstPad *pad_src_vid; + GstPad *pad_view_vid; + + GstPad *srcpad_zoom_filter; + + GstElement *imgbin; /* bin that holds image capturing elements */ + GstElement *vidbin; /* bin that holds video capturing elements */ + GstElement *active_bin; /* image or video bin that is currently in use */ + + /* source elements */ + GstElement *src_vid_src; + GstElement *src_filter; + GstElement *src_zoom_crop; + GstElement *src_zoom_scale; + GstElement *src_zoom_filter; + GstElement *src_out_sel; + + /* view finder elements */ + GstElement *view_in_sel; + GstElement *view_scale; + GstElement *view_sink; + + /* User configurable elements */ + GstElement *user_vid_src; + GstElement *user_vf_sink; + + /* Night mode handling */ + gboolean night_mode; + gint pre_night_fps_n; + gint pre_night_fps_d; +}; + +/** + * GstCameraBinClass: + * + * The #GstCameraBin class structure. + */ +struct _GstCameraBinClass +{ + GstPipelineClass parent_class; + + /* action signals */ + + void (*user_start) (GstCameraBin * camera); + void (*user_stop) (GstCameraBin * camera); + void (*user_pause) (GstCameraBin * camera); + void (*user_res_fps) (GstCameraBin * camera, gint width, gint height, + gint fps_n, gint fps_d); + void (*user_image_res) (GstCameraBin * camera, gint width, gint height); + + /* signals (callback) */ + + gboolean (*img_done) (GstCameraBin * camera, GString * filename); +}; + +/** + * GstCameraBinMode: + * @MODE_IMAGE: image capture + * @MODE_VIDEO: video capture + * + * Capture mode to use. + */ +typedef enum +{ + MODE_IMAGE = 0, + MODE_VIDEO +} GstCameraBinMode; + +GType gst_camerabin_get_type (void); + +G_END_DECLS + +#endif /* #ifndef __GST_CAMERABIN_H__ */ diff --git a/gst/camerabin/gstcamerabincolorbalance.c b/gst/camerabin/gstcamerabincolorbalance.c new file mode 100644 index 0000000..73a325d --- /dev/null +++ b/gst/camerabin/gstcamerabincolorbalance.c @@ -0,0 +1,81 @@ +/* + * 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. + */ + +/* + * Includes + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstcamerabincolorbalance.h" +#include "gstcamerabin.h" + +/* + * static functions implementation + */ + +static const GList * +gst_camerabin_color_balance_list_channels (GstColorBalance * cb) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + return gst_color_balance_list_channels (cbl); + } else { + return NULL; + } +} + +static void +gst_camerabin_color_balance_set_value (GstColorBalance * cb, + GstColorBalanceChannel * channel, gint value) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + gst_color_balance_set_value (cbl, channel, value); + } +} + +static gint +gst_camerabin_color_balance_get_value (GstColorBalance * cb, + GstColorBalanceChannel * channel) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + return gst_color_balance_get_value (cbl, channel); + } else { + return 0; + } +} + +/* + * extern functions implementation + */ + +void +gst_camerabin_color_balance_init (GstColorBalanceClass * iface) +{ + /* FIXME: to get the same type as v4l2src */ + GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE; + iface->list_channels = gst_camerabin_color_balance_list_channels; + iface->set_value = gst_camerabin_color_balance_set_value; + iface->get_value = gst_camerabin_color_balance_get_value; +} diff --git a/gst/camerabin/gstcamerabincolorbalance.h b/gst/camerabin/gstcamerabincolorbalance.h new file mode 100644 index 0000000..442a23b --- /dev/null +++ b/gst/camerabin/gstcamerabincolorbalance.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef __GST_CAMERA_COLOR_BALANCE_H__ +#define __GST_CAMERA_COLOR_BALANCE_H__ + +#include + +extern void gst_camerabin_color_balance_init (GstColorBalanceClass * iface); + +#endif /* #ifndef __GST_CAMERA_COLOR_BALANCE_H__ */ diff --git a/gst/camerabin/gstcamerabinphotography.c b/gst/camerabin/gstcamerabinphotography.c new file mode 100644 index 0000000..8a95bcd --- /dev/null +++ b/gst/camerabin/gstcamerabinphotography.c @@ -0,0 +1,222 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * Photography interface implementation for camerabin. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstcamerabinphotography.h" +#include "gstcamerabin.h" + +GST_DEBUG_CATEGORY_STATIC (camerabinphoto_debug); +#define GST_CAT_DEFAULT camerabinphoto_debug + +#define PHOTOGRAPHY_IS_OK(photo_elem) (GST_IS_ELEMENT (photo_elem) && \ + gst_element_implements_interface (photo_elem, GST_TYPE_PHOTOGRAPHY)) + +#define GST_PHOTOGRAPHY_IMPL_TEMPLATE(function_name, param_type) \ +static gboolean \ +gst_camerabin_set_ ## function_name (GstPhotography *photo, param_type param) \ +{ \ + GstCameraBin *camera; \ + gboolean ret = FALSE; \ + g_return_val_if_fail (photo != NULL, FALSE); \ + camera = GST_CAMERABIN (photo); \ + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { \ + ret = gst_photography_set_ ## function_name (GST_PHOTOGRAPHY (camera->src_vid_src), param); \ + } \ + return ret; \ +} \ +static gboolean \ +gst_camerabin_get_ ## function_name (GstPhotography *photo, param_type * param) \ +{ \ + GstCameraBin *camera; \ + gboolean ret = FALSE; \ + g_return_val_if_fail (photo != NULL, FALSE); \ + camera = GST_CAMERABIN (photo); \ + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { \ + ret = gst_photography_get_ ## function_name (GST_PHOTOGRAPHY (camera->src_vid_src), param); \ + } \ + return ret; \ +} + +GST_PHOTOGRAPHY_IMPL_TEMPLATE (ev_compensation, gfloat); +GST_PHOTOGRAPHY_IMPL_TEMPLATE (iso_speed, guint); +GST_PHOTOGRAPHY_IMPL_TEMPLATE (white_balance_mode, GstWhiteBalanceMode); +GST_PHOTOGRAPHY_IMPL_TEMPLATE (colour_tone_mode, GstColourToneMode); +GST_PHOTOGRAPHY_IMPL_TEMPLATE (flash_mode, GstFlashMode); + +static gboolean +gst_camerabin_set_zoom (GstPhotography * photo, gfloat zoom) +{ + GstCameraBin *camera; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* camerabin can zoom by itself */ + g_object_set (camera, "zoom", (gint) (CLAMP (zoom, 1.0, 10.0) * 100), NULL); + + return TRUE; +} + +static gboolean +gst_camerabin_get_zoom (GstPhotography * photo, gfloat * zoom) +{ + GstCameraBin *camera; + gint cb_zoom = 0; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + g_object_get (camera, "zoom", &cb_zoom, NULL); + *zoom = (gfloat) (cb_zoom / 100.0); + + return TRUE; +} + +static gboolean +gst_camerabin_set_scene_mode (GstPhotography * photo, GstSceneMode scene_mode) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + if (scene_mode == GST_PHOTOGRAPHY_SCENE_MODE_NIGHT) { + GST_DEBUG ("enabling night mode, lowering fps"); + /* Make camerabin select the lowest allowed frame rate */ + camera->night_mode = TRUE; + /* Remember frame rate before setting night mode */ + camera->pre_night_fps_n = camera->fps_n; + camera->pre_night_fps_d = camera->fps_d; + g_signal_emit_by_name (camera, "user-res-fps", camera->width, + camera->height, 0, 0, 0); + } else { + if (camera->night_mode) { + GST_DEBUG ("disabling night mode, restoring fps to %d/%d", + camera->pre_night_fps_n, camera->pre_night_fps_d); + camera->night_mode = FALSE; + g_signal_emit_by_name (camera, "user-res-fps", camera->width, + camera->height, camera->pre_night_fps_n, camera->pre_night_fps_d, 0); + } + } + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = gst_photography_set_scene_mode (GST_PHOTOGRAPHY (camera->src_vid_src), + scene_mode); + } + return ret; +} + +static gboolean +gst_camerabin_get_scene_mode (GstPhotography * photo, GstSceneMode * scene_mode) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = gst_photography_get_scene_mode (GST_PHOTOGRAPHY (camera->src_vid_src), + scene_mode); + } + return ret; +} + +static GstPhotoCaps +gst_camerabin_get_capabilities (GstPhotography * photo) +{ + GstCameraBin *camera; + /* camerabin can zoom by itself */ + GstPhotoCaps pcaps = GST_PHOTOGRAPHY_CAPS_ZOOM; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + if (GST_IS_ELEMENT (camera->src_vid_src) && + gst_element_implements_interface (camera->src_vid_src, + GST_TYPE_PHOTOGRAPHY)) { + GstPhotography *p2 = GST_PHOTOGRAPHY (camera->src_vid_src); + pcaps |= gst_photography_get_capabilities (p2); + } + + return pcaps; +} + +static void +gst_camerabin_set_autofocus (GstPhotography * photo, gboolean on) +{ + GstCameraBin *camera; + + g_return_if_fail (photo != NULL); + + camera = GST_CAMERABIN (photo); + + GST_DEBUG_OBJECT (camera, "setting autofocus %s", on ? "ON" : "OFF"); + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + gst_photography_set_autofocus (GST_PHOTOGRAPHY (camera->src_vid_src), on); + } +} + + +void +gst_camerabin_photography_init (GstPhotographyInterface * iface) +{ + GST_DEBUG_CATEGORY_INIT (camerabinphoto_debug, "camerabinphoto", 0, + "Camerabin photography interface debugging"); + + GST_INFO ("initing"); + + iface->set_ev_compensation = gst_camerabin_set_ev_compensation; + iface->get_ev_compensation = gst_camerabin_get_ev_compensation; + + iface->set_iso_speed = gst_camerabin_set_iso_speed; + iface->get_iso_speed = gst_camerabin_get_iso_speed; + + iface->set_white_balance_mode = gst_camerabin_set_white_balance_mode; + iface->get_white_balance_mode = gst_camerabin_get_white_balance_mode; + + iface->set_colour_tone_mode = gst_camerabin_set_colour_tone_mode; + iface->get_colour_tone_mode = gst_camerabin_get_colour_tone_mode; + + iface->set_scene_mode = gst_camerabin_set_scene_mode; + iface->get_scene_mode = gst_camerabin_get_scene_mode; + + iface->set_flash_mode = gst_camerabin_set_flash_mode; + iface->get_flash_mode = gst_camerabin_get_flash_mode; + + iface->set_zoom = gst_camerabin_set_zoom; + iface->get_zoom = gst_camerabin_get_zoom; + + iface->get_capabilities = gst_camerabin_get_capabilities; + + iface->set_autofocus = gst_camerabin_set_autofocus; +} diff --git a/gst/camerabin/gstcamerabinphotography.h b/gst/camerabin/gstcamerabinphotography.h new file mode 100644 index 0000000..721efab --- /dev/null +++ b/gst/camerabin/gstcamerabinphotography.h @@ -0,0 +1,30 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * Photography interface implementation for camerabin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_CAMERABIN_PHOTOGRAPHY_H__ +#define __GST_CAMERABIN_PHOTOGRAPHY_H__ + +#include + +void gst_camerabin_photography_init (GstPhotographyInterface * iface); + +#endif /* #ifndef __GST_CAMERABIN_PHOTOGRAPHY_H__ */ diff --git a/gst/camerabin/gstcamerabinxoverlay.c b/gst/camerabin/gstcamerabinxoverlay.c new file mode 100644 index 0000000..7a84765 --- /dev/null +++ b/gst/camerabin/gstcamerabinxoverlay.c @@ -0,0 +1,73 @@ +/* + * 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. + */ + +/* + * Includes + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstcamerabinxoverlay.h" +#include "gstcamerabin.h" + +/* + * static functions implementation + */ + +static void +gst_camerabin_expose (GstXOverlay * overlay) +{ + if (overlay && GST_CAMERABIN (overlay)->view_sink) { + GstXOverlay *xoverlay = GST_X_OVERLAY (GST_CAMERABIN (overlay)->view_sink); + gst_x_overlay_expose (xoverlay); + } +} + +static void +gst_camerabin_set_xwindow_id (GstXOverlay * overlay, gulong xwindow_id) +{ + if (overlay && GST_CAMERABIN (overlay)->view_sink) { + GstXOverlay *xoverlay = GST_X_OVERLAY (GST_CAMERABIN (overlay)->view_sink); + gst_x_overlay_set_xwindow_id (xoverlay, xwindow_id); + } +} + +static void +gst_camerabin_set_event_handling (GstXOverlay * overlay, gboolean handle_events) +{ + if (overlay && GST_CAMERABIN (overlay)->view_sink) { + GstXOverlay *xoverlay = GST_X_OVERLAY (GST_CAMERABIN (overlay)->view_sink); + gst_x_overlay_handle_events (xoverlay, handle_events); + } +} + +/* + * extern functions implementation + */ + +void +gst_camerabin_xoverlay_init (GstXOverlayClass * iface) +{ + iface->set_xwindow_id = gst_camerabin_set_xwindow_id; + iface->expose = gst_camerabin_expose; + iface->handle_events = gst_camerabin_set_event_handling; +} diff --git a/gst/camerabin/gstcamerabinxoverlay.h b/gst/camerabin/gstcamerabinxoverlay.h new file mode 100644 index 0000000..b9e9d9b --- /dev/null +++ b/gst/camerabin/gstcamerabinxoverlay.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef __GST_CAMERAXOVERLAY_H__ +#define __GST_CAMERAXOVERLAY_H__ + +#include + +extern void gst_camerabin_xoverlay_init (GstXOverlayClass * iface); + +#endif /* #ifndef __GST_CAMERAXOVERLAY_H__ */ -- 2.7.4