gstreamer/gst/camerabin/camerabinimage.c
Stefan Kost 80408b1de2 camerabin: set imagebin to PAUSED on capture and delayed filename setting
We need to set imagebin to PAUSED to not fail the bufferalloc. We also need to
keep the filesinks state locked until we have the filename for the run.
2009-06-14 10:56:30 +03:00

550 lines
15 KiB
C

/*
* GStreamer
* Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:camerabinimage
* @short_description: image capturing module of #GstCameraBin
*
* <refsect2>
* <para>
*
* The pipeline for this module is:
*
* <informalexample>
* <programlisting>
*-----------------------------------------------------------------------------
*
* -> [post proc] -> csp -> imageenc -> metadatamuxer -> filesink
*
*-----------------------------------------------------------------------------
* </programlisting>
* </informalexample>
*
* 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.
*
* </para>
* </refsect2>
*/
/*
* includes
*/
#include <gst/gst.h>
#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 <edgard.lima@indt.org.br>\n"
"Nokia Corporation <multimedia@maemo.org>");
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->post = NULL;
img->enc = NULL;
img->user_enc = NULL;
img->meta_mux = NULL;
img->sink = 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->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);
GstObject *camerabin = NULL;
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);
GST_INFO_OBJECT (img, "locking imagebin->sink state to %s",
gst_element_state_get_name (GST_STATE (img->sink)));
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!g_str_equal (img->filename->str, "")) {
GST_INFO_OBJECT (img, "preparing image with filename: %s",
img->filename->str);
gst_element_set_locked_state (img->sink, FALSE);
} else {
GST_INFO_OBJECT (img, "keep sink locked, we have no filename yet");
}
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
/* Set sink to NULL in order to write the file _now_ */
GST_INFO_OBJECT (img, "write image with filename: %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_PAUSED_TO_PLAYING:
camerabin = gst_element_get_parent (img);
/* Write debug graph to file */
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (camerabin),
GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE |
GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, "imagebin.playing");
gst_object_unref (camerabin);
break;
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));
GST_INFO_OBJECT (bin, "received filename: '%s'", bin->filename->str);
if (bin->sink) {
if (!g_str_equal (bin->filename->str, "")) {
g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str,
NULL);
gst_element_set_locked_state (bin->sink, FALSE);
gst_element_sync_state_with_parent (bin->sink);
} else {
GST_INFO_OBJECT (bin, "empty filename");
}
} else {
GST_INFO_OBJECT (bin, "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 !] csp ! encoder ! metadata ! filesink
*
* Returns: %TRUE if succeeded or FALSE if failed
*/
static gboolean
gst_camerabin_image_create_elements (GstCameraBinImage * img)
{
GstPad *sinkpad = NULL, *img_sinkpad = NULL;
gboolean ret = FALSE;
GstBin *imgbin = NULL;
GstElement *csp = 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");
}
/* Add colorspace converter */
if (!(csp =
gst_camerabin_create_and_add_element (imgbin, "ffmpegcolorspace"))) {
goto done;
}
/* Set up sink ghost pad for img bin */
if (!img_sinkpad) {
img_sinkpad = gst_element_get_static_pad (csp, "sink");
}
gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), img_sinkpad);
/* 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;
}
/* Set properties */
g_object_set (G_OBJECT (img->sink), "location", img->filename->str, NULL);
g_object_set (G_OBJECT (img->sink), "async", FALSE, NULL);
ret = TRUE;
done:
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");
gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), NULL);
gst_camerabin_remove_elements_from_bin (GST_BIN (img));
img->enc = NULL;
img->meta_mux = NULL;
img->sink = NULL;
img->elements_created = FALSE;
}
void
gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder)
{
GST_DEBUG ("setting image encoder %" GST_PTR_FORMAT, 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)
{
GST_DEBUG ("setting image postprocessing element %" GST_PTR_FORMAT, 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;
}