diff options
author | Dave Robillard <dave@drobilla.net> | 2009-06-19 21:01:54 -0400 |
---|---|---|
committer | Dave Robillard <dave@drobilla.net> | 2009-06-19 21:01:54 -0400 |
commit | 925e83ee60c5406b2e5f0f39b0da0f90370efc27 (patch) | |
tree | ce01d298ed0c5d31853fa43aedc2af0779417816 /gst | |
parent | 7f3bcd484b465d8216ac419754450adf07e9b0d2 (diff) | |
parent | c70dbe94b5ff9a0993d852605d40c21020c59552 (diff) | |
download | gst-plugins-bad-925e83ee60c5406b2e5f0f39b0da0f90370efc27.tar.gz gst-plugins-bad-925e83ee60c5406b2e5f0f39b0da0f90370efc27.tar.bz2 gst-plugins-bad-925e83ee60c5406b2e5f0f39b0da0f90370efc27.zip |
Merge branch 'master' of git://anongit.freedesktop.org/gstreamer/gst-plugins-bad into fdo
Diffstat (limited to 'gst')
58 files changed, 5958 insertions, 982 deletions
diff --git a/gst/aacparse/gstaacparse.c b/gst/aacparse/gstaacparse.c index 07906347..6db4b619 100644 --- a/gst/aacparse/gstaacparse.c +++ b/gst/aacparse/gstaacparse.c @@ -664,8 +664,12 @@ gst_aacparse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) } } - GST_BUFFER_DURATION (buffer) = AAC_FRAME_DURATION (aacparse); - GST_BUFFER_TIMESTAMP (buffer) = aacparse->ts; + /* ADIF: only send an initial 0 timestamp downstream, + * then admit we have no idea and let downstream (decoder) handle it */ + if (aacparse->header_type != DSPAAC_HEADER_ADIF || !aacparse->ts) { + GST_BUFFER_DURATION (buffer) = AAC_FRAME_DURATION (aacparse); + GST_BUFFER_TIMESTAMP (buffer) = aacparse->ts; + } if (GST_CLOCK_TIME_IS_VALID (aacparse->ts)) aacparse->ts += GST_BUFFER_DURATION (buffer); diff --git a/gst/camerabin/Makefile.am b/gst/camerabin/Makefile.am index 3691af85..d5085da9 100644 --- a/gst/camerabin/Makefile.am +++ b/gst/camerabin/Makefile.am @@ -14,28 +14,28 @@ EXTRA_DIST = gstcamerabin-marshal.list plugin_LTLIBRARIES = libgstcamerabin.la libgstcamerabin_la_SOURCES = gstcamerabin.c \ - gstcamerabinxoverlay.c \ gstcamerabincolorbalance.c \ camerabinimage.c \ camerabinvideo.c \ camerabingeneral.c \ + camerabinpreview.c \ gstcamerabinphotography.c nodist_libgstcamerabin_la_SOURCES = $(built_sources) libgstcamerabin_la_CFLAGS = \ $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DGST_USE_UNSTABLE_API libgstcamerabin_la_LIBADD = \ + $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-$(GST_MAJORMINOR).la \ $(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) \ - -lgstinterfaces-$(GST_MAJORMINOR) \ - $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-$(GST_MAJORMINOR).la + -lgstinterfaces-$(GST_MAJORMINOR) 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 \ + camerabinpreview.h \ gstcamerabinphotography.h diff --git a/gst/camerabin/camerabingeneral.c b/gst/camerabin/camerabingeneral.c index 627e0328..d9d9a202 100644 --- a/gst/camerabin/camerabingeneral.c +++ b/gst/camerabin/camerabingeneral.c @@ -150,9 +150,9 @@ gst_camerabin_try_add_element (GstBin * bin, GstElement * new_elem) } /* 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)); + GST_DEBUG ("adding %" GST_PTR_FORMAT " to %s:%s", new_elem, + 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 */ @@ -185,7 +185,6 @@ 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), diff --git a/gst/camerabin/camerabinimage.c b/gst/camerabin/camerabinimage.c index e4ff6b86..da979f0f 100644 --- a/gst/camerabin/camerabinimage.c +++ b/gst/camerabin/camerabinimage.c @@ -30,17 +30,13 @@ * <informalexample> * <programlisting> *----------------------------------------------------------------------------- - * (src0) -> queue -> - * -> [post proc] -> tee < - * (src1) -> imageenc -> metadatamuxer -> filesink + * + * -> [post proc] -> csp -> imageenc -> metadatamuxer -> filesink + * *----------------------------------------------------------------------------- * </programlisting> * </informalexample> * - * The property of elements are: - * - * queue - "max-size-buffers", 1, "leaky", 2, - * * The image bin opens file for image writing in READY to PAUSED state change. * The image bin closes the file in PAUSED to READY state change. * @@ -150,24 +146,16 @@ gst_camerabin_image_init (GstCameraBinImage * img, { img->filename = g_string_new (""); - img->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; } @@ -196,6 +184,7 @@ gst_camerabin_image_change_state (GstElement * element, { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstCameraBinImage *img = GST_CAMERABIN_IMAGE (element); + GstObject *camerabin = NULL; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: @@ -204,15 +193,25 @@ gst_camerabin_image_change_state (GstElement * element, } /* 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: - gst_element_set_locked_state (img->sink, FALSE); + 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 ("write img file: %s", img->filename->str); + 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); + g_string_assign (img->filename, ""); break; default: break; @@ -221,6 +220,14 @@ gst_camerabin_image_change_state (GstElement * element, 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; @@ -261,11 +268,18 @@ gst_camerabin_image_set_property (GObject * object, guint prop_id, 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) { - g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str, - NULL); + 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 ("no sink, not setting name yet"); + GST_INFO_OBJECT (bin, "no sink, not setting name yet"); } break; default: @@ -369,17 +383,17 @@ done: * 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 + * 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, *img_srcpad = NULL; + GstPad *sinkpad = NULL, *img_sinkpad = NULL; gboolean ret = FALSE; GstBin *imgbin = NULL; + GstElement *csp = NULL; g_return_val_if_fail (img != NULL, FALSE); @@ -403,23 +417,18 @@ gst_camerabin_image_create_elements (GstCameraBinImage * img) img_sinkpad = gst_element_get_static_pad (img->post, "sink"); } - /* Create tee */ - if (!(img->tee = gst_camerabin_create_and_add_element (imgbin, "tee"))) { + /* 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 (img->tee, "sink"); + img_sinkpad = gst_element_get_static_pad (csp, "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; @@ -452,33 +461,14 @@ gst_camerabin_image_create_elements (GstCameraBinImage * img) 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); } @@ -502,27 +492,14 @@ 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; } @@ -530,6 +507,7 @@ gst_camerabin_image_destroy_elements (GstCameraBinImage * img) 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) @@ -542,6 +520,7 @@ 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) diff --git a/gst/camerabin/camerabinimage.h b/gst/camerabin/camerabinimage.h index c05f5498..8214e9cb 100644 --- a/gst/camerabin/camerabinimage.h +++ b/gst/camerabin/camerabinimage.h @@ -24,20 +24,17 @@ #include <gst/gstbin.h> 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; @@ -48,21 +45,12 @@ struct _GstCameraBinImage /* 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; }; @@ -86,5 +74,4 @@ GstElement *gst_camerabin_image_get_encoder (GstCameraBinImage * img); GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img); G_END_DECLS - -#endif /* #ifndef __CAMERABIN_IMAGE_H__ */ +#endif /* #ifndef __CAMERABIN_IMAGE_H__ */ diff --git a/gst/camerabin/camerabinpreview.c b/gst/camerabin/camerabinpreview.c new file mode 100644 index 00000000..b64b2143 --- /dev/null +++ b/gst/camerabin/camerabinpreview.c @@ -0,0 +1,257 @@ +/* +* GStreamer +* Copyright (C) 2009 Nokia Corporation <multimedia@maemo.org> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <string.h> + +#include "camerabingeneral.h" +#include "camerabinpreview.h" + +static void +save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer **p_buf = (GstBuffer **) data; + + *p_buf = gst_buffer_ref (buf); + + GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT, + *p_buf, GST_BUFFER_CAPS (*p_buf)); +} + +static gboolean +create_element (const gchar * factory_name, const gchar * elem_name, + GstElement ** element, GError ** err) +{ + *element = gst_element_factory_make (factory_name, elem_name); + if (*element) + return TRUE; + + if (err && *err == NULL) { + *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "cannot create element '%s' - please check your GStreamer installation", + factory_name); + } + + return FALSE; +} + + +/** + * gst_camerabin_preview_create_pipeline: + * @camera: camerabin object + * + * Create a preview converter pipeline. + * + * Returns: TRUE if pipeline was constructed, otherwise FALSE. + */ +gboolean +gst_camerabin_preview_create_pipeline (GstCameraBin * camera) +{ + GstElement *src, *csp, *filter, *vscale, *sink; + GError *error = NULL; + + if (!camera->preview_caps) { + return FALSE; + } + + /* Destroy old pipeline, if any */ + gst_camerabin_preview_destroy_pipeline (camera); + + GST_DEBUG ("creating elements"); + + if (!create_element ("appsrc", "prev_src", &src, &error) || + !create_element ("videoscale", NULL, &vscale, &error) || + !create_element ("ffmpegcolorspace", NULL, &csp, &error) || + !create_element ("capsfilter", NULL, &filter, &error) || + !create_element ("fakesink", "prev_sink", &sink, &error)) + goto no_elements; + + camera->preview_pipeline = gst_pipeline_new ("preview-pipeline"); + if (camera->preview_pipeline == NULL) + goto no_pipeline; + + GST_DEBUG ("adding elements"); + gst_bin_add_many (GST_BIN (camera->preview_pipeline), + src, csp, filter, vscale, sink, NULL); + + g_object_set (filter, "caps", camera->preview_caps, NULL); + g_object_set (sink, "preroll-queue-len", 1, "signal-handoffs", TRUE, NULL); + g_object_set (vscale, "method", 0, NULL); + + /* FIXME: linking is still way too expensive, profile this properly */ + GST_DEBUG ("linking src->vscale"); + if (!gst_element_link_pads (src, "src", vscale, "sink")) + return FALSE; + + GST_DEBUG ("linking vscale->csp"); + if (!gst_element_link_pads (vscale, "src", csp, "sink")) + return FALSE; + + GST_DEBUG ("linking csp->capsfilter"); + if (!gst_element_link_pads (csp, "src", filter, "sink")) + return FALSE; + + GST_DEBUG ("linking capsfilter->sink"); + if (!gst_element_link_pads (filter, "src", sink, "sink")) + return FALSE; + + return TRUE; + + /* ERRORS */ +no_elements: + { + g_warning ("Could not make preview pipeline: %s", error->message); + g_error_free (error); + return FALSE; + } +no_pipeline: + { + g_warning ("Could not make preview pipeline: %s", + "no pipeline (unknown error)"); + return FALSE; + } +} + + +/** + * gst_camerabin_preview_destroy_pipeline: + * @camera: camerabin object + * + * Destroy preview converter pipeline. + */ +void +gst_camerabin_preview_destroy_pipeline (GstCameraBin * camera) +{ + if (camera->preview_pipeline) { + gst_element_set_state (camera->preview_pipeline, GST_STATE_NULL); + gst_object_unref (camera->preview_pipeline); + camera->preview_pipeline = NULL; + } +} + + +/** + * gst_camerabin_preview_convert: + * @camera: camerabin object + * @buf: #GstBuffer that contains the frame to be converted + * + * Create a preview image of the given frame. + * + * Returns: converted preview image, or NULL if operation failed. + */ +GstBuffer * +gst_camerabin_preview_convert (GstCameraBin * camera, GstBuffer * buf) +{ + GstMessage *msg; + GstBuffer *result = NULL; + GError *error = NULL; + GstBus *bus; + GstElement *src, *sink; + GstBufferFlag bflags; + GstFlowReturn fret; + + g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL); + + if (camera->preview_pipeline == NULL) { + GST_WARNING ("pipeline is NULL"); + goto no_pipeline; + } + + src = gst_bin_get_by_name (GST_BIN (camera->preview_pipeline), "prev_src"); + sink = gst_bin_get_by_name (GST_BIN (camera->preview_pipeline), "prev_sink"); + + if (!src || !sink) { + GST_WARNING ("pipeline doesn't have src / sink elements"); + goto no_pipeline; + } + + g_object_set (src, "size", (gint64) GST_BUFFER_SIZE (buf), + "blocksize", (guint32) GST_BUFFER_SIZE (buf), + "caps", GST_BUFFER_CAPS (buf), "num-buffers", 1, NULL); + + g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result); + + bflags = GST_BUFFER_FLAGS (buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_READONLY); + + GST_DEBUG ("running conversion pipeline"); + gst_element_set_state (camera->preview_pipeline, GST_STATE_PLAYING); + + g_signal_emit_by_name (src, "push-buffer", buf, &fret); + + /* TODO: do we need to use a bus poll, can we just register a callback to the bus? */ + bus = gst_element_get_bus (camera->preview_pipeline); + msg = + gst_bus_poll (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS, 25 * GST_SECOND); + + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS:{ + if (result) { + GST_DEBUG ("preview image successful: result = %p", result); + } else { + GST_WARNING ("EOS but no result frame?!"); + } + break; + } + case GST_MESSAGE_ERROR:{ + gchar *dbg = NULL; + + gst_message_parse_error (msg, &error, &dbg); + if (error) { + g_warning ("Could not make preview image: %s", error->message); + GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg)); + g_error_free (error); + } else { + g_warning ("Could not make preview image (and NULL error!)"); + } + g_free (dbg); + result = NULL; + break; + } + default:{ + g_return_val_if_reached (NULL); + } + } + } else { + g_warning ("Could not make preview image: %s", "timeout during conversion"); + result = NULL; + } + + g_signal_handlers_disconnect_by_func (sink, G_CALLBACK (save_result), + &result); + gst_element_set_state (camera->preview_pipeline, GST_STATE_READY); + + GST_BUFFER_FLAGS (buf) = bflags; + + return result; + + /* ERRORS */ +no_pipeline: + { + g_warning ("Could not make preview image: %s", + "no pipeline (unknown error)"); + return NULL; + } +} diff --git a/gst/camerabin/camerabinpreview.h b/gst/camerabin/camerabinpreview.h new file mode 100644 index 00000000..cadefed6 --- /dev/null +++ b/gst/camerabin/camerabinpreview.h @@ -0,0 +1,37 @@ +/* +* GStreamer +* Copyright (C) 2009 Nokia Corporation <multimedia@maemo.org> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#ifndef __CAMERABINPREVIEW_H__ +#define __CAMERABINPREVIEW_H__ + +#include <gst/gst.h> + +#include "gstcamerabin.h" + +G_BEGIN_DECLS + gboolean gst_camerabin_preview_create_pipeline (GstCameraBin * camera); + +void gst_camerabin_preview_destroy_pipeline (GstCameraBin * camera); + +GstBuffer *gst_camerabin_preview_convert (GstCameraBin * camera, + GstBuffer * buf); + +G_END_DECLS +#endif /* __CAMERABINPREVIEW_H__ */ diff --git a/gst/camerabin/camerabinvideo.c b/gst/camerabin/camerabinvideo.c index 14a95064..fef9ac26 100644 --- a/gst/camerabin/camerabinvideo.c +++ b/gst/camerabin/camerabinvideo.c @@ -290,6 +290,7 @@ gst_camerabin_video_change_state (GstElement * element, { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstCameraBinVideo *vid = GST_CAMERABIN_VIDEO (element); + GstObject *camerabin = NULL; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: @@ -325,6 +326,13 @@ gst_camerabin_video_change_state (GstElement * element, switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + camerabin = gst_element_get_parent (vid); + /* 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, "videobin.playing"); + gst_object_unref (camerabin); + if (vid->pending_eos) { /* Video bin is still paused, so push eos directly to video queue */ GST_DEBUG_OBJECT (vid, "pushing pending eos"); @@ -641,7 +649,7 @@ gst_camerabin_video_create_elements (GstCameraBinVideo * vid) } /* 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); + g_object_set (G_OBJECT (queue), "leaky", 2, "max-size-buffers", 1, NULL); /* Set up src ghost pad for video bin */ vid_srcpad = gst_element_get_static_pad (queue, "src"); diff --git a/gst/camerabin/gstcamerabin.c b/gst/camerabin/gstcamerabin.c index 2ddb34e0..f1cd897a 100644 --- a/gst/camerabin/gstcamerabin.c +++ b/gst/camerabin/gstcamerabin.c @@ -36,18 +36,19 @@ * <refsect2> * <title>Example launch line</title> * |[ - * gst-launch -v -m camerabin filename=test.jpeg + * gst-launch -v -m camerabin * ]| * </refsect2> * <refsect2> * <title>Image capture</title> * <para> * Taking still images is initiated with the #GstCameraBin::user-start action - * signal. Once the image has captured, #GstCameraBin::img-done signal is fired. - * It allows to decide wheter to take another picture (burst capture, bracketing - * shot) or stop capturing. The last captured image is shown - * until one switches back to view finder using #GstCameraBin::user-stop action - * signal. + * signal. Once the image has been captured, "image-captured" gst message is + * posted to the bus and capturing another image is possible. If application + * has set #GstCameraBin:preview-caps property, then a "preview-image" gst + * message is posted to bus containing preview image formatted according to + * specified caps. Eventually when image has been saved #GstCameraBin::img-done + * signal is emitted. * * Available resolutions can be taken from the #GstCameraBin:inputcaps property. * Image capture resolution can be set with #GstCameraBin::user-image-res @@ -74,7 +75,8 @@ * 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. + * opened the video device. However it is possible to configure photography + * settings in NULL state and camerabin will try applying them later. * </para> * </refsect2> * <refsect2> @@ -105,16 +107,23 @@ /* * The pipeline in the camerabin is * - * "image bin" - * videosrc ! crop ! scale ! out-sel <------> in-sel ! scale ! ffmpegcsp ! vfsink - * "video bin" + * videosrc [ ! ffmpegcsp ] ! capsfilter ! crop ! scale ! capsfilter ! \ + * out-sel name=osel ! queue name=img_q * - * it is possible to have 'ffmpegcolorspace' and 'capsfilter' just after - * v4l2camsrc + * View finder: + * osel. ! in-sel name=isel ! scale ! capsfilter [ ! ffmpegcsp ] ! vfsink + * + * Image bin: + * img_q. [ ! ipp ] ! ffmpegcsp ! imageenc ! metadatamux ! filesink + * + * Video bin: + * osel. ! tee name=t ! queue ! videoenc ! videomux name=mux ! filesink + * t. ! queue ! isel. + * audiosrc ! queue ! audioconvert ! volume ! audioenc ! mux. * * The properties of elements are: * - * vfsink - "sync", FALSE, "qos", FALSE + * vfsink - "sync", FALSE, "qos", FALSE, "async", FALSE * output-selector - "resend-latest", FALSE * input-selector - "select-all", TRUE */ @@ -134,11 +143,11 @@ /* FIXME: include #include <gst/gst-i18n-plugin.h> and use _(" ") */ #include "gstcamerabin.h" -#include "gstcamerabinxoverlay.h" #include "gstcamerabincolorbalance.h" #include "gstcamerabinphotography.h" #include "camerabingeneral.h" +#include "camerabinpreview.h" #include "gstcamerabin-marshal.h" @@ -176,7 +185,8 @@ enum ARG_VIDEO_SRC, ARG_AUDIO_SRC, ARG_INPUT_CAPS, - ARG_FILTER_CAPS + ARG_FILTER_CAPS, + ARG_PREVIEW_CAPS }; /* @@ -216,6 +226,11 @@ static guint camerabin_signals[LAST_SIGNAL]; #define DEFAULT_VIEW_SINK "autovideosink" +#define CAMERABIN_MAX_VF_WIDTH 848 +#define CAMERABIN_MAX_VF_HEIGHT 848 +#define PREVIEW_MESSAGE_NAME "preview-image" +#define IMG_CAPTURED_MESSAGE_NAME "image-captured" + /* * static helper functions declaration */ @@ -258,6 +273,12 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer, static gboolean gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, gpointer u_data); +static gboolean +gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj, + gpointer u_data); +static gboolean +gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data); static void gst_camerabin_reset_to_view_finder (GstCameraBin * camera); @@ -275,6 +296,8 @@ static const GValue *gst_camerabin_find_better_framerate (GstCameraBin * camera, static void gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps); +static void gst_camerabin_finish_image_capture (GstCameraBin * camera); + /* * GObject callback functions declaration */ @@ -355,11 +378,7 @@ 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 (iface_type == GST_TYPE_COLOR_BALANCE) { if (camera->src_vid_src) { return GST_IS_COLOR_BALANCE (camera->src_vid_src); } @@ -375,9 +394,8 @@ gst_camerabin_iface_supported (GstImplementsInterface * iface, GType iface_type) return FALSE; } } else if (iface_type == GST_TYPE_PHOTOGRAPHY) { - if (camera->src_vid_src) { - return GST_IS_PHOTOGRAPHY (camera->src_vid_src); - } + /* Always support photography interface */ + return TRUE; } return FALSE; @@ -402,12 +420,6 @@ camerabin_init_interfaces (GType type) 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, @@ -428,9 +440,6 @@ camerabin_init_interfaces (GType type) 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); @@ -470,6 +479,14 @@ camerabin_setup_src_elements (GstCameraBin * camera) 0)); } + /* Update photography interface settings */ + if (GST_IS_ELEMENT (camera->src_vid_src) && + gst_element_implements_interface (camera->src_vid_src, + GST_TYPE_PHOTOGRAPHY)) { + gst_photography_set_config (GST_PHOTOGRAPHY (camera->src_vid_src), + &camera->photo_settings); + } + if (camera->width > 0 && camera->height > 0) { gst_structure_set (st, "width", G_TYPE_INT, camera->width, @@ -561,12 +578,6 @@ camerabin_create_src_elements (GstCameraBin * camera) gst_camerabin_create_and_add_element (cbin, "output-selector"))) goto done; - camera->srcpad_videosrc = - gst_element_get_static_pad (camera->src_vid_src, "src"); - - camera->srcpad_zoom_filter = - gst_element_get_static_pad (camera->src_zoom_filter, "src"); - /* Set default "driver-name" for v4l2camsrc if not set */ if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), "driver-name")) { @@ -637,14 +648,16 @@ camerabin_create_view_elements (GstCameraBin * camera) && (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) { pads = g_list_next (pads); } - camera->pad_view_img = GST_PAD (pads->data); + camera->pad_view_src = GST_PAD (pads->data); + /* Add videoscale in case we need to downscale frame for view finder */ if (!(camera->view_scale = gst_camerabin_create_and_add_element (GST_BIN (camera), "videoscale"))) { goto error; } + /* Add capsfilter to maintain aspect ratio while scaling */ if (!(camera->aspect_filter = gst_camerabin_create_and_add_element (GST_BIN (camera), "capsfilter"))) { @@ -694,30 +707,44 @@ camerabin_create_elements (GstCameraBin * 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"); + /* Add image queue */ + if (!(camera->img_queue = + gst_camerabin_create_and_add_element (GST_BIN (camera), "queue"))) { + goto done; + } + + /* To avoid deadlock, we won't restrict the image queue size */ + /* FIXME: actually we would like to have some kind of restriction here (size), + but deadlocks must be handled somehow... */ + g_object_set (G_OBJECT (camera->img_queue), "max-size-time", + G_GUINT64_CONSTANT (0), NULL); + g_object_set (G_OBJECT (camera->img_queue), "max-size-bytes", + G_GUINT64_CONSTANT (0), NULL); + g_object_set (G_OBJECT (camera->img_queue), "max-size-buffers", + G_GUINT64_CONSTANT (0), NULL); + + camera->pad_src_queue = gst_element_get_static_pad (camera->img_queue, "src"); + + gst_pad_add_data_probe (camera->pad_src_queue, + G_CALLBACK (gst_camerabin_have_queue_data), camera); + + /* Add image bin */ + if (!gst_camerabin_add_element (GST_BIN (camera), camera->imgbin)) { goto done; } - /* 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)); + + /* Create view finder elements */ + if (!camerabin_create_view_elements (camera)) { + GST_WARNING_OBJECT (camera, "creating view finder elements failed"); goto done; } @@ -776,10 +803,6 @@ camerabin_destroy_elements (GstCameraBin * camera) 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; @@ -793,14 +816,9 @@ camerabin_destroy_elements (GstCameraBin * camera) camera->pad_src_view = NULL; } - if (camera->srcpad_zoom_filter) { - gst_object_unref (camera->srcpad_zoom_filter); - camera->srcpad_zoom_filter = NULL; - } - - if (camera->srcpad_videosrc) { - gst_object_unref (camera->srcpad_videosrc); - camera->srcpad_videosrc = NULL; + if (camera->pad_src_queue) { + gst_object_unref (camera->pad_src_queue); + camera->pad_src_queue = NULL; } camera->view_sink = NULL; @@ -865,13 +883,23 @@ camerabin_dispose_elements (GstCameraBin * camera) gst_caps_unref (camera->allowed_caps); camera->allowed_caps = NULL; } + + if (camera->preview_caps) { + gst_caps_unref (camera->preview_caps); + camera->preview_caps = NULL; + } + + if (camera->event_tags) { + gst_tag_list_free (camera->event_tags); + camera->event_tags = NULL; + } } /* * gst_camerabin_image_capture_continue: * @camera: camerabin object * - * Check if application wants to continue image capturing by using g_signal. + * Notify application that image has been saved with a signal. * * Returns TRUE if another image should be captured, FALSE otherwise. */ @@ -923,7 +951,16 @@ gst_camerabin_change_mode (GstCameraBin * camera, gint mode) gst_element_set_state (camera->active_bin, GST_STATE_NULL); } if (camera->mode == MODE_IMAGE) { + GstStateChangeReturn state_ret; + camera->active_bin = camera->imgbin; + state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY); + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + GST_WARNING_OBJECT (camera, "state change failed"); + gst_element_set_state (camera->active_bin, GST_STATE_NULL); + camera->active_bin = NULL; + } } else if (camera->mode == MODE_VIDEO) { camera->active_bin = camera->vidbin; } @@ -1114,6 +1151,46 @@ failed: } /* + * gst_camerabin_send_img_queue_event: + * @camera: camerabin object + * @event: event to be sent + * + * Send the given event to image queue. + */ +static void +gst_camerabin_send_img_queue_event (GstCameraBin * camera, GstEvent * event) +{ + GstPad *queue_sink; + + g_return_if_fail (camera != NULL); + g_return_if_fail (event != NULL); + + queue_sink = gst_element_get_static_pad (camera->img_queue, "sink"); + gst_pad_send_event (queue_sink, event); + gst_object_unref (queue_sink); +} + +/* + * gst_camerabin_send_img_queue_custom_event: + * @camera: camerabin object + * @ev_struct: event structure to be sent + * + * Generate and send a custom event to image queue. + */ +static void +gst_camerabin_send_img_queue_custom_event (GstCameraBin * camera, + GstStructure * ev_struct) +{ + GstEvent *event; + + g_return_if_fail (camera != NULL); + g_return_if_fail (ev_struct != NULL); + + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, ev_struct); + gst_camerabin_send_img_queue_event (camera, event); +} + +/* * gst_camerabin_rewrite_tags_to_bin: * @bin: bin holding tag setter elements * @list: tag list to be written @@ -1275,7 +1352,13 @@ gst_camerabin_rewrite_tags (GstCameraBin * camera) } /* Write tags */ - gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list); + if (camera->active_bin == camera->vidbin) { + gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list); + } else { + /* Image tags need to be sent as a serialized event into image queue */ + GstEvent *tagevent = gst_event_new_tag (gst_tag_list_copy (list)); + gst_camerabin_send_img_queue_event (camera, tagevent); + } gst_tag_list_free (list); } @@ -1313,18 +1396,36 @@ gst_camerabin_set_capsfilter_caps (GstCameraBin * camera, GstCaps * new_caps) gst_camerabin_update_aspect_filter (camera, new_caps); } +/* + * gst_camerabin_adapt_video_resolution: + * @camera: camerabin object + * @caps: caps describing the next incoming buffer format + * + * This function adjusts capsfilter and crop elements in order to modify + * the incoming buffer to the resolution that application requested. + * + */ static void gst_camerabin_adapt_video_resolution (GstCameraBin * camera, GstCaps * caps) { GstStructure *st; gint width = 0, height = 0; GstCaps *filter_caps = NULL; + gint top, bottom, left, right, crop; + gdouble ratio_w, ratio_h; + + g_return_if_fail (camera->width != 0 && camera->height != 0); /* Get width and height from caps */ st = gst_caps_get_structure (caps, 0); gst_structure_get_int (st, "width", &width); gst_structure_get_int (st, "height", &height); + if (width == camera->width && height == camera->height) { + GST_DEBUG_OBJECT (camera, "no adaptation with resolution needed"); + return; + } + GST_DEBUG_OBJECT (camera, "changing %dx%d -> %dx%d filter to %" GST_PTR_FORMAT, camera->width, camera->height, width, height, camera->src_filter); @@ -1336,7 +1437,31 @@ gst_camerabin_adapt_video_resolution (GstCameraBin * camera, GstCaps * caps) "height", G_TYPE_INT, height, NULL); g_object_set (G_OBJECT (camera->src_filter), "caps", filter_caps, NULL); gst_caps_unref (filter_caps); - /* FIXME: implement cropping according to requested aspect ratio */ + + /* Crop if requested aspect ratio differs from incoming frame aspect ratio */ + + /* Don't override original crop values in case we have zoom applied */ + g_object_get (G_OBJECT (camera->src_zoom_crop), "top", &top, "bottom", + &bottom, "left", &left, "right", &right, NULL); + + ratio_w = (gdouble) width / camera->width; + ratio_h = (gdouble) height / camera->height; + + if (ratio_w < ratio_h) { + crop = height - (camera->height * ratio_w); + top += crop / 2; + bottom += crop / 2; + } else { + crop = width - (camera->width * ratio_h); + left += crop / 2; + right += crop / 2; + } + + GST_INFO_OBJECT (camera, + "updating crop: left:%d, right:%d, top:%d, bottom:%d", left, right, top, + bottom); + g_object_set (G_OBJECT (camera->src_zoom_crop), "top", top, "bottom", bottom, + "left", left, "right", right, NULL); } /* @@ -1353,11 +1478,13 @@ img_capture_prepared (gpointer data, GstCaps * caps) GstStructure *st, *new_st; gint i; const gchar *field_name; + gboolean adapt = FALSE; GST_INFO_OBJECT (camera, "image capture prepared"); /* It is possible we are about to get something else that we requested */ if (!gst_caps_is_equal (camera->image_capture_caps, caps)) { + adapt = TRUE; /* If capture preparation has added new fields to requested caps, we need to copy them */ st = gst_caps_get_structure (camera->image_capture_caps, 0); @@ -1378,14 +1505,17 @@ img_capture_prepared (gpointer data, GstCaps * caps) /* Update capsfilters */ gst_camerabin_set_capsfilter_caps (camera, camera->image_capture_caps); - /* If incoming buffer resolution is different from what application - requested, then we need to fix this in camerabin */ - gst_camerabin_adapt_video_resolution (camera, caps); - + if (adapt) { + /* If incoming buffer resolution is different from what application + requested, then we can fix this in camerabin */ + gst_camerabin_adapt_video_resolution (camera, caps); + } g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE, "active-pad", camera->pad_src_img, NULL); - gst_camerabin_rewrite_tags (camera); - gst_element_set_state (GST_ELEMENT (camera->imgbin), GST_STATE_PLAYING); + + if (!GST_CAMERABIN_IMAGE (camera->imgbin)->elements_created) { + gst_element_set_state (camera->imgbin, GST_STATE_READY); + } } /* @@ -1431,8 +1561,11 @@ gst_camerabin_start_image_capture (GstCameraBin * camera) } if (!wait_for_prepare) { - gst_camerabin_rewrite_tags (camera); - state_ret = gst_element_set_state (camera->imgbin, GST_STATE_PLAYING); + /* Image queue's srcpad data probe will set imagebin to PLAYING */ + state_ret = gst_element_set_state (camera->imgbin, GST_STATE_PAUSED); + GST_DEBUG_OBJECT (camera, "setting imagebin to paused: %s", + gst_element_state_change_return_get_name (state_ret)); + 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, @@ -1535,12 +1668,48 @@ image_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data) GST_DEBUG_OBJECT (camera, "%s %s:%s", blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad)); +} + +/* + * gst_camerabin_send_preview: + * @camera: camerabin object + * @buffer: received buffer + * + * Convert given buffer to desired preview format and send is as a #GstMessage + * to application. + * + * Returns: TRUE always + */ +static gboolean +gst_camerabin_send_preview (GstCameraBin * camera, GstBuffer * buffer) +{ + GstBuffer *prev = NULL; + GstStructure *s; + GstMessage *msg; + gboolean ret = FALSE; + + GST_DEBUG_OBJECT (camera, "creating preview"); + + prev = gst_camerabin_preview_convert (camera, buffer); - if (blocked && (pad == camera->srcpad_videosrc)) { - /* Send eos and block until image bin reaches eos */ - GST_DEBUG_OBJECT (camera, "sending eos to image bin"); - gst_element_send_event (camera->imgbin, gst_event_new_eos ()); + GST_DEBUG_OBJECT (camera, "preview created: %p", prev); + + if (prev) { + s = gst_structure_new (PREVIEW_MESSAGE_NAME, + "buffer", GST_TYPE_BUFFER, prev, NULL); + + msg = gst_message_new_element (GST_OBJECT (camera), s); + + GST_DEBUG_OBJECT (camera, "sending message with preview image"); + + if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) { + GST_WARNING_OBJECT (camera, + "This element has no bus, therefore no message sent!"); + } + ret = TRUE; } + + return ret; } /* @@ -1549,27 +1718,19 @@ image_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data) * @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. + * Buffer probe called before sending each buffer to image queue. + * Generates and sends preview image as gst message if requested. */ static gboolean gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer, gpointer u_data) { GstCameraBin *camera = (GstCameraBin *) u_data; + GstStructure *fn_ev_struct = NULL; gboolean ret = TRUE; + GstPad *os_sink = NULL; - GST_LOG ("got buffer #%d %p with size %d", camera->num_img_buffers, - buffer, GST_BUFFER_SIZE (buffer)); + GST_LOG ("got buffer %p with size %d", buffer, GST_BUFFER_SIZE (buffer)); /* Image filename should be set by now */ if (g_str_equal (camera->filename->str, "")) { @@ -1578,54 +1739,38 @@ gst_camerabin_have_img_buffer (GstPad * pad, GstBuffer * buffer, goto done; } - /* Check for first buffer after capture start, we want to - pass it forward directly. */ - if (!camera->num_img_buffers) { - goto done; + if (camera->preview_caps) { + gst_camerabin_send_preview (camera, buffer); } - /* Close the file of saved image */ - gst_element_set_state (camera->imgbin, GST_STATE_READY); - - /* Reset filename to force application set new filename */ - g_string_assign (camera->filename, ""); - - /* Check if the application wants to continue */ - ret = gst_camerabin_image_capture_continue (camera); + gst_camerabin_rewrite_tags (camera); - 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); + /* Send a custom event which tells the filename to image queue */ + /* NOTE: This needs to be THE FIRST event to be sent to queue for + every image. It triggers imgbin state change to PLAYING. */ + fn_ev_struct = gst_structure_new ("img-filename", + "filename", G_TYPE_STRING, camera->filename->str, NULL); + GST_DEBUG_OBJECT (camera, "sending filename event to image queue"); + gst_camerabin_send_img_queue_custom_event (camera, fn_ev_struct); + + /* Add buffer probe to outputselector's sink pad. It sends + EOS event to image queue. */ + os_sink = gst_element_get_static_pad (camera->src_out_sel, "sink"); + camera->image_captured_id = gst_pad_add_buffer_probe (os_sink, + G_CALLBACK (gst_camerabin_have_src_buffer), camera); + gst_object_unref (os_sink); - /* 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 */ +done: - g_mutex_lock (camera->capture_mutex); - camera->capturing = FALSE; - g_cond_signal (camera->cond); - g_mutex_unlock (camera->capture_mutex); - } + /* HACK: v4l2camsrc changes to view finder resolution automatically + after one captured still image */ + gst_camerabin_finish_image_capture (camera); -done: + gst_camerabin_reset_to_view_finder (camera); - if (ret) { - camera->num_img_buffers++; - /* Block when next buffer arrives, we want to push eos event - between frames and make sure that eos reaches the filesink - before processing the next buffer. */ - gst_pad_set_blocked_async (camera->srcpad_videosrc, TRUE, - (GstPadBlockCallback) image_pad_blocked, camera); - } + GST_DEBUG_OBJECT (camera, "switched back to viewfinder"); - return ret; + return TRUE; } /* @@ -1655,6 +1800,120 @@ gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, } /* + * gst_camerabin_have_src_buffer: + * @pad: output-selector sink pad which receives frames from video source + * @buffer: buffer pushed to the pad + * @u_data: camerabin object + * + * Buffer probe for sink pad. It sends custom eos event to image queue and + * notifies application by sending a "image-captured" message to GstBus. + * This probe is installed after image has been captured and it disconnects + * itself after EOS has been sent. + */ +static gboolean +gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + GstMessage *msg; + + GST_LOG_OBJECT (camera, "got image buffer %p with size %d", + buffer, GST_BUFFER_SIZE (buffer)); + + /* We can't send real EOS event, since it would switch the image queue + into "draining mode". Therefore we send our own custom eos and + catch & drop it later in queue's srcpad data probe */ + GST_DEBUG_OBJECT (camera, "sending eos to image queue"); + gst_camerabin_send_img_queue_custom_event (camera, + gst_structure_new ("img-eos", NULL)); + + /* our work is done, disconnect */ + gst_pad_remove_buffer_probe (pad, camera->image_captured_id); + + g_mutex_lock (camera->capture_mutex); + camera->capturing = FALSE; + g_cond_signal (camera->cond); + g_mutex_unlock (camera->capture_mutex); + + msg = gst_message_new_element (GST_OBJECT (camera), + gst_structure_new (IMG_CAPTURED_MESSAGE_NAME, NULL)); + + GST_DEBUG_OBJECT (camera, "sending 'image captured' message"); + + if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) { + GST_WARNING_OBJECT (camera, + "This element has no bus, therefore no message sent!"); + } + + return TRUE; +} + +/* + * gst_camerabin_have_queue_data: + * @pad: image queue src pad leading to image bin + * @mini_obj: buffer or event pushed to the pad + * @u_data: camerabin object + * + * Buffer probe for image queue src pad leading to image bin. It sets imgbin + * into PLAYING mode when image buffer is passed to it. This probe also + * monitors our internal custom events and handles them accordingly. + */ +static gboolean +gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + gboolean ret = TRUE; + + if (GST_IS_BUFFER (mini_obj)) { + GstEvent *tagevent; + + GST_LOG_OBJECT (camera, "queue sending image buffer to imgbin"); + + tagevent = gst_event_new_tag (gst_tag_list_copy (camera->event_tags)); + gst_element_send_event (camera->imgbin, tagevent); + gst_tag_list_free (camera->event_tags); + camera->event_tags = gst_tag_list_new (); + } else if (GST_IS_EVENT (mini_obj)) { + const GstStructure *evs; + GstEvent *event; + + event = GST_EVENT_CAST (mini_obj); + evs = gst_event_get_structure (event); + + GST_LOG_OBJECT (camera, "got event %s", GST_EVENT_TYPE_NAME (event)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + GstTagList *tlist; + + gst_event_parse_tag (event, &tlist); + gst_tag_list_insert (camera->event_tags, tlist, GST_TAG_MERGE_REPLACE); + ret = FALSE; + } else if (evs && gst_structure_has_name (evs, "img-filename")) { + const gchar *fname; + + GST_LOG_OBJECT (camera, "queue setting image filename to imagebin"); + fname = gst_structure_get_string (evs, "filename"); + g_object_set (G_OBJECT (camera->imgbin), "filename", fname, NULL); + + /* imgbin fails to start unless the filename is set */ + gst_element_set_state (camera->imgbin, GST_STATE_PLAYING); + GST_LOG_OBJECT (camera, "Set imgbin to PLAYING"); + + ret = FALSE; + } else if (evs && gst_structure_has_name (evs, "img-eos")) { + GST_LOG_OBJECT (camera, "queue sending EOS to image pipeline"); + gst_pad_set_blocked_async (camera->pad_src_queue, TRUE, + (GstPadBlockCallback) image_pad_blocked, camera); + gst_element_send_event (camera->imgbin, gst_event_new_eos ()); + ret = FALSE; + } + } + + return ret; +} + +/* * gst_camerabin_reset_to_view_finder: * @camera: camerabin object * @@ -1667,8 +1926,8 @@ 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) { + /* Set video bin to READY state */ + if (camera->active_bin == camera->vidbin) { 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"); @@ -1678,7 +1937,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera) } /* Reset counters and flags */ - camera->num_img_buffers = 0; camera->stop_requested = FALSE; camera->paused = FALSE; @@ -1688,18 +1946,6 @@ gst_camerabin_reset_to_view_finder (GstCameraBin * camera) "active-pad", camera->pad_src_view, NULL); } - /* Unblock, if dataflow to output-selector is blocked due to image preview */ - if (camera->srcpad_zoom_filter && - gst_pad_is_blocked (camera->srcpad_zoom_filter)) { - gst_pad_set_blocked_async (camera->srcpad_zoom_filter, FALSE, - (GstPadBlockCallback) image_pad_blocked, camera); - } - /* Unblock, if dataflow in videosrc is blocked due to waiting for eos */ - if (camera->srcpad_videosrc && gst_pad_is_blocked (camera->srcpad_videosrc)) { - gst_pad_set_blocked_async (camera->srcpad_videosrc, FALSE, - (GstPadBlockCallback) image_pad_blocked, camera); - } - /* Enable view finder mode in v4l2camsrc */ if (camera->src_vid_src && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), @@ -2023,6 +2269,26 @@ gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps) } /* + * gst_camerabin_finish_image_capture: + * @camera: camerabin object + * + * Perform finishing operations after image capture is done and + * returning back to view finder mode. + */ +static void +gst_camerabin_finish_image_capture (GstCameraBin * camera) +{ + if (camera->image_capture_caps) { + /* If we used specific caps for image capture we need to + restore the caps and zoom/crop for view finder mode */ + GST_DEBUG_OBJECT (camera, "resetting crop in camerabin"); + g_object_set (camera->src_zoom_crop, "left", 0, "right", 0, + "top", 0, "bottom", 0, NULL); + gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); + } +} + +/* * GObject callback functions implementation */ @@ -2243,14 +2509,29 @@ gst_camerabin_class_init (GstCameraBinClass * klass) /** * 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. + * Caps applied to capsfilter element after videosrc [ ! ffmpegcsp ]. + * You can use this e.g. to make sure video color format matches with + * encoders and other elements configured to camerabin and/or change + * resolution and frame rate. */ g_object_class_install_property (gobject_class, ARG_FILTER_CAPS, g_param_spec_boxed ("filter-caps", "Filter caps", - "Capsfilter caps used to control video source operation", + "Filter video data coming from videosrc element", + GST_TYPE_CAPS, G_PARAM_READWRITE)); + + /** + * GstCameraBin:preview-caps: + * + * If application wants to receive a preview image, it needs to + * set this property to depict the desired image format caps. When + * this property is not set (NULL), message containing the preview + * image is not sent. + */ + + g_object_class_install_property (gobject_class, ARG_PREVIEW_CAPS, + g_param_spec_boxed ("preview-caps", "Preview caps", + "Caps defining the preview image format", GST_TYPE_CAPS, G_PARAM_READWRITE)); /** @@ -2347,9 +2628,7 @@ gst_camerabin_class_init (GstCameraBinClass * klass) * @camera: the camera bin element * @filename: the name of the file just saved * - * Signal emitted when the file has just been saved. To continue taking - * pictures set new filename using #GstCameraBin:filename property and return - * TRUE, otherwise return FALSE. + * Signal emitted when the file has just been saved. * * Don't call any #GstCameraBin method from this signal, if you do so there * will be a deadlock. @@ -2394,7 +2673,6 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass) camera->filename = g_string_new (""); camera->mode = DEFAULT_MODE; - camera->num_img_buffers = 0; camera->stop_requested = FALSE; camera->paused = FALSE; camera->capturing = FALSE; @@ -2405,6 +2683,8 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass) camera->fps_n = DEFAULT_FPS_N; camera->fps_d = DEFAULT_FPS_D; + camera->event_tags = gst_tag_list_new (); + camera->image_capture_caps = NULL; camera->view_finder_caps = NULL; camera->allowed_caps = NULL; @@ -2419,13 +2699,9 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass) camera->pad_src_view = NULL; camera->pad_view_src = NULL; camera->pad_src_img = NULL; - camera->pad_view_img = NULL; camera->pad_src_vid = NULL; camera->pad_view_vid = NULL; - camera->srcpad_zoom_filter = NULL; - camera->srcpad_videosrc = NULL; - /* source elements */ camera->src_vid_src = NULL; camera->src_filter = NULL; @@ -2450,6 +2726,8 @@ gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass) camera->view_in_sel = NULL; camera->view_scale = NULL; camera->view_sink = NULL; + + memset (&camera->photo_settings, 0, sizeof (GstPhotoSettings)); } static void @@ -2467,6 +2745,8 @@ gst_camerabin_dispose (GObject * object) gst_element_set_state (camera->vidbin, GST_STATE_NULL); gst_object_unref (camera->vidbin); + gst_camerabin_preview_destroy_pipeline (camera); + camerabin_destroy_elements (camera); camerabin_dispose_elements (camera); @@ -2527,7 +2807,7 @@ gst_camerabin_set_property (GObject * object, guint prop_id, break; case ARG_VIDEO_MUX: if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { - GST_WARNING_OBJECT (camera->vidbin, + GST_WARNING_OBJECT (camera, "can't use set element until next video bin NULL to READY state change"); } gst_camerabin_video_set_muxer (GST_CAMERABIN_VIDEO (camera->vidbin), @@ -2588,7 +2868,18 @@ gst_camerabin_set_property (GObject * object, guint prop_id, } 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); + if (GST_STATE (camera) != GST_STATE_NULL) { + gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); + } + break; + case ARG_PREVIEW_CAPS: + GST_OBJECT_LOCK (camera); + if (camera->preview_caps) { + gst_caps_unref (camera->preview_caps); + } + camera->preview_caps = gst_caps_copy (gst_value_get_caps (value)); + GST_OBJECT_UNLOCK (camera); + gst_camerabin_preview_create_pipeline (camera); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2661,6 +2952,9 @@ gst_camerabin_get_property (GObject * object, guint prop_id, case ARG_FILTER_CAPS: gst_value_set_caps (value, camera->view_finder_caps); break; + case ARG_PREVIEW_CAPS: + gst_value_set_caps (value, camera->preview_caps); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2732,6 +3026,29 @@ done: return ret; } +static gboolean +gst_camerabin_imgbin_finished (gpointer u_data) +{ + GstCameraBin *camera = GST_CAMERABIN (u_data); + + GST_DEBUG_OBJECT (camera, "Image encoding finished"); + + /* Close the file of saved image */ + gst_element_set_state (camera->imgbin, GST_STATE_READY); + GST_DEBUG_OBJECT (camera, "Image pipeline set to READY"); + + /* Send img-done signal */ + gst_camerabin_image_capture_continue (camera); + + /* Unblock image queue pad to process next buffer */ + gst_pad_set_blocked_async (camera->pad_src_queue, FALSE, + (GstPadBlockCallback) image_pad_blocked, camera); + GST_DEBUG_OBJECT (camera, "Queue srcpad unblocked"); + + /* disconnect automatically */ + return FALSE; +} + /* * GstBin functions implementation */ @@ -2755,17 +3072,20 @@ gst_camerabin_handle_message_func (GstBin * bin, GstMessage * msg) } else if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->imgbin)) { /* Image eos */ GST_DEBUG_OBJECT (camera, "got image eos message"); - - /* Still image capture buffer handled, restore filter caps */ - if (camera->image_capture_caps) { - gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); - } - - /* Unblock pad to process next buffer */ - gst_pad_set_blocked_async (camera->srcpad_videosrc, FALSE, - (GstPadBlockCallback) image_pad_blocked, camera); + g_idle_add (gst_camerabin_imgbin_finished, camera); } break; + case GST_MESSAGE_ERROR: + GST_DEBUG_OBJECT (camera, "error from child %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (msg)); + g_mutex_lock (camera->capture_mutex); + if (camera->capturing) { + gst_camerabin_finish_image_capture (camera); + camera->capturing = FALSE; + g_cond_signal (camera->cond); + } + g_mutex_unlock (camera->capture_mutex); + break; default: break; } @@ -2807,12 +3127,11 @@ gst_camerabin_user_start (GstCameraBin * camera) g_mutex_unlock (camera->capture_mutex); if (camera->active_bin) { - 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) { + g_object_set (G_OBJECT (camera->active_bin), "filename", + camera->filename->str, NULL); gst_camerabin_start_video_recording (camera); } } @@ -2821,10 +3140,13 @@ gst_camerabin_user_start (GstCameraBin * 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); + if (camera->active_bin == camera->vidbin) { + GST_INFO_OBJECT (camera, "stopping video capture"); + gst_camerabin_do_stop (camera); + gst_camerabin_reset_to_view_finder (camera); + } else { + GST_INFO_OBJECT (camera, "stopping image capture isn't needed"); + } } static void @@ -2891,6 +3213,9 @@ gst_camerabin_user_res_fps (GstCameraBin * camera, gint width, gint height, GST_INFO_OBJECT (camera, "switching resolution to %dx%d and fps to %d/%d", width, height, fps_n, fps_d); + /* Interrupt ongoing capture */ + gst_camerabin_do_stop (camera); + gst_element_get_state (GST_ELEMENT (camera), &state, &pending, 0); if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING) { GST_INFO_OBJECT (camera, diff --git a/gst/camerabin/gstcamerabin.h b/gst/camerabin/gstcamerabin.h index 1bb3f489..cd88c7ca 100644 --- a/gst/camerabin/gstcamerabin.h +++ b/gst/camerabin/gstcamerabin.h @@ -59,7 +59,6 @@ struct _GstCameraBin /* 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 */ @@ -69,6 +68,9 @@ struct _GstCameraBin gint fps_n; gint fps_d; + /* Image tags are collected here first before sending to imgbin */ + GstTagList *event_tags; + /* Caps applied to capsfilters when taking still image */ GstCaps *image_capture_caps; @@ -78,6 +80,9 @@ struct _GstCameraBin /* Caps that videosrc supports */ GstCaps *allowed_caps; + /* Caps used to create preview image */ + GstCaps *preview_caps; + /* The digital zoom (from 100% to 1000%) */ gint zoom; @@ -90,16 +95,16 @@ struct _GstCameraBin 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 *pad_src_queue; - GstPad *srcpad_zoom_filter; - GstPad *srcpad_videosrc; - + GstElement *img_queue; /* queue for decoupling capture from + image-postprocessing and saving */ 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 */ + GstElement *preview_pipeline; /* pipeline for creating preview images */ /* source elements */ GstElement *src_vid_src; @@ -123,6 +128,12 @@ struct _GstCameraBin gboolean night_mode; gint pre_night_fps_n; gint pre_night_fps_d; + + /* Cache the photography interface settings */ + GstPhotoSettings photo_settings; + + /* Buffer probe id for captured image handling */ + gulong image_captured_id; }; /** @@ -145,7 +156,7 @@ struct _GstCameraBinClass /* signals (callback) */ - gboolean (*img_done) (GstCameraBin * camera, const gchar * filename); + gboolean (*img_done) (GstCameraBin * camera, const gchar * filename); }; /** @@ -164,4 +175,4 @@ typedef enum GType gst_camerabin_get_type (void); G_END_DECLS -#endif /* #ifndef __GST_CAMERABIN_H__ */ +#endif /* #ifndef __GST_CAMERABIN_H__ */ diff --git a/gst/camerabin/gstcamerabinphotography.c b/gst/camerabin/gstcamerabinphotography.c index 8a95bcd7..cb17abca 100644 --- a/gst/camerabin/gstcamerabinphotography.c +++ b/gst/camerabin/gstcamerabinphotography.c @@ -24,6 +24,7 @@ #include <config.h> #endif +#include <string.h> #include "gstcamerabinphotography.h" #include "gstcamerabin.h" @@ -32,38 +33,206 @@ GST_DEBUG_CATEGORY_STATIC (camerabinphoto_debug); #define PHOTOGRAPHY_IS_OK(photo_elem) (GST_IS_ELEMENT (photo_elem) && \ gst_element_implements_interface (photo_elem, GST_TYPE_PHOTOGRAPHY)) +static void +gst_camerabin_handle_scene_mode (GstCameraBin * camera, + GstSceneMode scene_mode); + +static gboolean +gst_camerabin_set_ev_compensation (GstPhotography * photo, + gfloat ev_compensation) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* Cache the setting */ + camera->photo_settings.ev_compensation = ev_compensation; + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = + gst_photography_set_ev_compensation (GST_PHOTOGRAPHY + (camera->src_vid_src), ev_compensation); + } + return ret; +} + +static gboolean +gst_camerabin_get_ev_compensation (GstPhotography * photo, + gfloat * ev_compensation) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); -#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); + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = + gst_photography_get_ev_compensation (GST_PHOTOGRAPHY + (camera->src_vid_src), ev_compensation); + } + return ret; +} + +static gboolean +gst_camerabin_set_iso_speed (GstPhotography * photo, guint iso_speed) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* Cache the setting */ + camera->photo_settings.iso_speed = iso_speed; + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = gst_photography_set_iso_speed (GST_PHOTOGRAPHY (camera->src_vid_src), + iso_speed); + } + return ret; +} + +static gboolean +gst_camerabin_get_iso_speed (GstPhotography * photo, guint * iso_speed) +{ + 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_iso_speed (GST_PHOTOGRAPHY (camera->src_vid_src), + iso_speed); + } + return ret; +} + +static gboolean +gst_camerabin_set_white_balance_mode (GstPhotography * photo, + GstWhiteBalanceMode white_balance_mode) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* Cache the setting */ + camera->photo_settings.wb_mode = white_balance_mode; + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = + gst_photography_set_white_balance_mode (GST_PHOTOGRAPHY + (camera->src_vid_src), white_balance_mode); + } + return ret; +} + +static gboolean +gst_camerabin_get_white_balance_mode (GstPhotography * photo, + GstWhiteBalanceMode * white_balance_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_white_balance_mode (GST_PHOTOGRAPHY + (camera->src_vid_src), white_balance_mode); + } + return ret; +} + +static gboolean +gst_camerabin_set_colour_tone_mode (GstPhotography * photo, + GstColourToneMode colour_tone_mode) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* Cache the setting */ + camera->photo_settings.tone_mode = colour_tone_mode; + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = + gst_photography_set_colour_tone_mode (GST_PHOTOGRAPHY + (camera->src_vid_src), colour_tone_mode); + } + return ret; +} + +static gboolean +gst_camerabin_get_colour_tone_mode (GstPhotography * photo, + GstColourToneMode * colour_tone_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_colour_tone_mode (GST_PHOTOGRAPHY + (camera->src_vid_src), colour_tone_mode); + } + return ret; +} + +static gboolean +gst_camerabin_set_flash_mode (GstPhotography * photo, GstFlashMode flash_mode) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + camera = GST_CAMERABIN (photo); + + /* Cache the setting */ + camera->photo_settings.flash_mode = flash_mode; + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = gst_photography_set_flash_mode (GST_PHOTOGRAPHY (camera->src_vid_src), + flash_mode); + } + return ret; +} + +static gboolean +gst_camerabin_get_flash_mode (GstPhotography * photo, GstFlashMode * flash_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_flash_mode (GST_PHOTOGRAPHY (camera->src_vid_src), + flash_mode); + } + return ret; +} static gboolean gst_camerabin_set_zoom (GstPhotography * photo, gfloat zoom) @@ -106,24 +275,10 @@ gst_camerabin_set_scene_mode (GstPhotography * photo, GstSceneMode scene_mode) 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); - } - } + /* Cache the setting */ + camera->photo_settings.scene_mode = scene_mode; + + gst_camerabin_handle_scene_mode (camera, scene_mode); if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { ret = gst_photography_set_scene_mode (GST_PHOTOGRAPHY (camera->src_vid_src), @@ -186,6 +341,69 @@ gst_camerabin_set_autofocus (GstPhotography * photo, gboolean on) } } +static gboolean +gst_camerabin_set_config (GstPhotography * photo, GstPhotoSettings * config) +{ + GstCameraBin *camera; + gboolean ret = FALSE; + g_return_val_if_fail (photo != NULL, FALSE); + camera = GST_CAMERABIN (photo); + + /* Cache the settings */ + memcpy (&camera->photo_settings, config, sizeof (GstPhotoSettings)); + + /* Handle night mode */ + gst_camerabin_handle_scene_mode (camera, config->scene_mode); + + if (PHOTOGRAPHY_IS_OK (camera->src_vid_src)) { + ret = + gst_photography_set_config (GST_PHOTOGRAPHY (camera->src_vid_src), + config); + } + return ret; +} + +static gboolean +gst_camerabin_get_config (GstPhotography * photo, GstPhotoSettings * config) +{ + 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_config (GST_PHOTOGRAPHY (camera->src_vid_src), + config); + } + return ret; +} + +static void +gst_camerabin_handle_scene_mode (GstCameraBin * camera, GstSceneMode scene_mode) +{ + if (scene_mode == GST_PHOTOGRAPHY_SCENE_MODE_NIGHT) { + if (!camera->night_mode) { + 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 { + GST_DEBUG ("night mode already enabled"); + } + } 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); + } + } +} void gst_camerabin_photography_init (GstPhotographyInterface * iface) @@ -219,4 +437,7 @@ gst_camerabin_photography_init (GstPhotographyInterface * iface) iface->get_capabilities = gst_camerabin_get_capabilities; iface->set_autofocus = gst_camerabin_set_autofocus; + + iface->set_config = gst_camerabin_set_config; + iface->get_config = gst_camerabin_get_config; } diff --git a/gst/camerabin/gstcamerabinxoverlay.c b/gst/camerabin/gstcamerabinxoverlay.c deleted file mode 100644 index 7a84765a..00000000 --- a/gst/camerabin/gstcamerabinxoverlay.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -/* - * Includes - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#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 deleted file mode 100644 index b9e9d9b6..00000000 --- a/gst/camerabin/gstcamerabinxoverlay.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -#ifndef __GST_CAMERAXOVERLAY_H__ -#define __GST_CAMERAXOVERLAY_H__ - -#include <gst/interfaces/xoverlay.h> - -extern void gst_camerabin_xoverlay_init (GstXOverlayClass * iface); - -#endif /* #ifndef __GST_CAMERAXOVERLAY_H__ */ diff --git a/gst/debugutils/Makefile.am b/gst/debugutils/Makefile.am index d1e1430e..3b93fa91 100644 --- a/gst/debugutils/Makefile.am +++ b/gst/debugutils/Makefile.am @@ -2,7 +2,7 @@ plugin_LTLIBRARIES = libgstdebugutilsbad.la libgstdebugutilsbad_la_SOURCES = fpsdisplaysink.c debugutilsbad.c libgstdebugutilsbad_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -libgstdebugutilsbad_la_LIBADD = $(GST_BASE_LIBS) -lgstinterfaces-$(GST_MAJORMINOR) +libgstdebugutilsbad_la_LIBADD = $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstinterfaces-$(GST_MAJORMINOR) libgstdebugutilsbad_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstdebugutilsbad_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/gst/dvdspu/gstdvdspu.c b/gst/dvdspu/gstdvdspu.c index f5fce4bc..af8bc99e 100644 --- a/gst/dvdspu/gstdvdspu.c +++ b/gst/dvdspu/gstdvdspu.c @@ -716,12 +716,11 @@ gst_dvd_spu_redraw_still (GstDVDSpu * dvdspu, gboolean force) static void gst_dvd_spu_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event) { - const GstStructure *structure = gst_event_get_structure (event); - const gchar *event_type = gst_structure_get_string (structure, "event"); gboolean hl_change = FALSE; GST_INFO_OBJECT (dvdspu, "DVD event of type %s on subp pad OOB=%d", - event_type, (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB)); + gst_structure_get_string (event->structure, "event"), + (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB)); switch (dvdspu->spu_input_type) { case SPU_INPUT_TYPE_VOBSUB: diff --git a/gst/frei0r/Makefile.am b/gst/frei0r/Makefile.am new file mode 100644 index 00000000..400801d9 --- /dev/null +++ b/gst/frei0r/Makefile.am @@ -0,0 +1,15 @@ +plugin_LTLIBRARIES = libgstfrei0r.la + +libgstfrei0r_la_SOURCES = \ + gstfrei0r.c \ + gstfrei0rfilter.c \ + gstfrei0rsrc.c \ + gstfrei0rmixer.c + +libgstfrei0r_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) +libgstfrei0r_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) \ + -lgstvideo-@GST_MAJORMINOR@ +libgstfrei0r_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstfrei0r_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstfrei0r.h gstfrei0rfilter.h gstfrei0rsrc.h gstfrei0rmixer.h frei0r.h diff --git a/gst/frei0r/frei0r.h b/gst/frei0r/frei0r.h new file mode 100644 index 00000000..f6b81d2b --- /dev/null +++ b/gst/frei0r/frei0r.h @@ -0,0 +1,567 @@ +/** @mainpage frei0r - a minimalistic plugin API for video effects + * + * @section sec_intro Introduction + * + * This is frei0r - a minimalistic plugin API for video effects. + * + * The main emphasis is on simplicity - there are many different applications + * that use video effects, and they all have different requirements regarding + * their internal plugin API. And that's why frei0r does not try to be a + * one-in-all general video plugin API, but instead an API for the most + * common video effects: simple filters, sources and mixers that can be + * controlled by parameters. + * + * It's our hope that this way these simple effects can be shared between + * many applications, avoiding their reimplementation by different + * projects. + * + * On the other hand, this is not meant as a competing standard to + * more ambitious efforts that try to satisfy the needs of many different + * applications and more complex effects. + * + * + * @section sec_overview Overview + * + * If you are new to frei0r, the best thing is probably to have + * a look at the <a href="frei0r_8h-source.html">frei0r header</a>, + * which is quite simple. + * + * After that, you might want to look at the + * <a href="frei0r_8h.html">frei0r functions</a> in more detail. + * + * When developing a new frei0r effect, you have to choose + * - which effect type to use (\ref PLUGIN_TYPE), + * - which color model to use (\ref COLOR_MODEL), and + * - which parameter types (\ref PARAM_TYPE) your effect will support. + * + * To round things up, you should decide whether your effect should have + * an associated icon (\ref icons), and where it will be installed + * (\ref pluglocations). + * + * @section sec_changes Changes + * + * @subsection sec_changes_1_0_1_1 From frei0r 1.0 to frei0r 1.1 + * + * - added specifications for plugin locations + * - added specifications for frei0r icons + * - added RGBA8888 color model + * - added packed32 color model + * - added better specification of color models + * - added string type + * - added bounds to resolution (8 <= width, height <= 2048) + * - width and height must be an integer multiple of 8 + * - frame data must be 16 byte aligned + * - improved update specification (must not change parameters, + * must restore fpu state) + * - added note for applications to ignore effects with unknown fields + * - added new plugin types mixer2 and mixer3 + * - added section about \ref concurrency + */ + + +/** + * \addtogroup pluglocations Plugin Locations + * @section sec_pluglocations Plugin Locations + * + * For Unix platforms there are rules for the location of frei0r plugins. + * + * frei0r 1.x plugin files should be located in + * + * - (1) /usr/lib/frei0r-1/\<vendor\> + * - (2) /usr/local/lib/frei0r-1/\<vendor\> + * - (3) $HOME/.frei0r-1/lib/\<vendor\> + * + * Examples: + * + * - /usr/lib/frei0r-1/mob/flippo.so + * - /usr/lib/frei0r-1/drone/flippo.so + * - /usr/local/lib/frei0r-1/gephex/coma/invert0r.so + * - /home/martin/.frei0r-1/lib/martin/test.so + * + * Like in these examples plugins should be placed in "vendor" subdirs + * to reduce name clashes. + * + * @subsection sec_order Plugin Loading Order + * + * The application shall load plugins in the following order: 3, 2, 1. + * If a name clash occurs (two or more frei0r plugins with identical + * effect name), the plugins in directory 3 have precedence over plugins + * in directory 2, and those in directory 2 have precedence over plugins + * in directory 1. + * + * This makes it possible for users to "override" effects that are + * installed in system wide directories by placing plugins in their + * home directory. + * + * The order of loading plugins inside each of the directories + * 1, 2, and 3 is not defined. + */ + +/** + *\addtogroup icons Icons for frei0r effects + * @section sec_icons Icons for frei0r effects + * + * Each frei0r effect can have an associated icon. + * + * @subsection sec_icon_format Icon Format + * + * The format of frei0r icons must be png. + * Recommended resolution is 64x64. + * The icon filename of an effect with effect name "frei0r" + * must be "frei0r.png". + * + * @subsection sec_icon_location Icon location + * + * The exact location where the application should look for the + * plugin is platform dependant. + * + * For Windows platforms, the icon should be at the same place as + * the plugin containing the effect. + * + * For Unix platforms, the following mapping from plugin location + * to icon location must be used: + * + * Let \<plugin_path\>/\<plugin\> be a frei0r plugin with name \<effect_name\>. + * Then the corresponding icon (if any) shall be located in + * \<icon_path\>/\<effect_name\>.png. + * \<icon_path\> can be obtained in the following way: + * + * @verbatim + <plugin_path> | <icon_path> + ---------------------------------------------------------------------------- + $HOME/.frei0r-1/lib/<vendor> | $HOME/.frei0r-1/icons/<vendor> + /usr/local/lib/frei0r-1/<vendor> | /usr/local/share/frei0r-1/icons/<vendor> + /usr/lib/frei0r-1/<vendor> | /usr/share/frei0r-1/icons/<vendor> + * | <plugin_path> + @endverbatim + * + * (The wildcard '*' stands for any other plugin_path) + * + * For other platforms, no location is defined. We recommend to use the + * plugin path where possible. + */ + +/** + * \addtogroup concurrency Concurrency + * @section sec_concurrency Concurrency + * + * - \ref f0r_init + * - \ref f0r_deinit + * + * These methods must not be called more than once. It is obvious that no + * concurrent calls are allowed. + * + * + * - \ref f0r_get_plugin_info + * - \ref f0r_get_param_info + * - \ref f0r_construct + * - \ref f0r_destruct + * + * Concurrent calls of these functions are allowed. + * + * + * - \ref f0r_set_param_value + * - \ref f0r_get_param_value + * - \ref f0r_update + * - \ref f0r_update2 + * + * If a thread is in one of these methods its allowed for another thread to + * enter one of theses methods for a different effect instance. But for one + * effect instance only one thread is allowed to execute any of these methods. + */ + + + +/** \file + * \brief This file defines the frei0r api, version 1.1. + * + * A conforming plugin must implement and export all functions declared in + * this header. + * + * A conforming application must accept only those plugins which use + * allowed values for the described fields. + */ + +#ifndef INCLUDED_FREI0R_H +#define INCLUDED_FREI0R_H + +#include <inttypes.h> + +/** + * The frei0r API major version + */ +#define FREI0R_MAJOR_VERSION 1 + +/** + * The frei0r API minor version + */ +#define FREI0R_MINOR_VERSION 1 + +//--------------------------------------------------------------------------- + +/** + * f0r_init() is called once when the plugin is loaded by the application. + * \see f0r_deinit + */ +int f0r_init(); + +/** + * f0r_deinit is called once when the plugin is unloaded by the application. + * \see f0r_init + */ +void f0r_deinit(); + +//--------------------------------------------------------------------------- + +/** \addtogroup PLUGIN_TYPE Type of the Plugin + * These defines determine whether the plugin is a + * source, a filter or one of the two mixer types + * @{ + */ + +/** one input and one output */ +#define F0R_PLUGIN_TYPE_FILTER 0 +/** just one output */ +#define F0R_PLUGIN_TYPE_SOURCE 1 +/** two inputs and one output */ +#define F0R_PLUGIN_TYPE_MIXER2 2 +/** three inputs and one output */ +#define F0R_PLUGIN_TYPE_MIXER3 3 + +/** @} */ + +//--------------------------------------------------------------------------- + +/** \addtogroup COLOR_MODEL Color Models + * List of supported color models. + * + * Note: the color models are endian independent, because the + * color components are defined by their positon in memory, not + * by their significance in an uint32_t value. + * + * For effects that work on the color components, + * RGBA8888 is the recommended color model for frei0r-1.1 effects. + * For effects that only work on pixels, PACKED32 is the recommended + * color model since it helps the application to avoid unnecessary + * color conversions. + * + * Effects can choose an appropriate color model, applications must support + * all color models and do conversions if necessary. Source effects + * must not use the PACKED32 color model because the application must know + * in which color model the created framebuffers are represented. + * + * For each color model, a frame consists of width*height pixels which + * are stored row-wise and consecutively in memory. The size of a pixel is + * 4 bytes. There is no extra pitch parameter + * (i.e. the pitch is simply width*4). + * + * The following additional constraints must be honored: + * - The top-most line of a frame is stored first in memory. + * - A frame must be aligned to a 16 byte border in memory. + * - The width and height of a frame must be positive + * - The width and height of a frame must be integer multiples of 8 + * + * These constraints make sure that each line is stored at an address aligned + * to 16 byte. + */ +/*@{*/ +/** + * In BGRA8888, each pixel is represented by 4 consecutive + * unsigned bytes, where the first byte value represents + * the blue, the second the green, and the third the red color + * component of the pixel. The last value represents the + * alpha value. + */ +#define F0R_COLOR_MODEL_BGRA8888 0 + +/** + * In RGBA8888, each pixel is represented by 4 consecutive + * unsigned bytes, where the first byte value represents + * the red, the second the green, and the third the blue color + * component of the pixel. The last value represents the + * alpha value. + */ +#define F0R_COLOR_MODEL_RGBA8888 1 + +/** + * In PACKED32, each pixel is represented by 4 consecutive + * bytes, but it is not defined how the color componets are + * stored. The true color format could be RGBA8888, + * BGRA8888, a packed 32 bit YUV format, or any other + * color format that stores pixels in 32 bit. + * + * This is useful for effects that don't work on color but + * only on pixels (for example a mirror effect). + * + * Note that source effects must not use this color model. + */ +#define F0R_COLOR_MODEL_PACKED32 2 +/*@}*/ + +/** + * The f0r_plugin_info_t structure is filled in by the plugin + * to tell the application about its name, type, number of parameters, + * and version. + * + * An application should ignore (i.e. not use) frei0r effects that + * have unknown values in the plugin_type or color_model field. + * It should also ignore effects with a too high frei0r_version. + * + * This is necessary to be able to extend the frei0r spec (e.g. + * by adding new color models or plugin types) in a way that does not + * result in crashes when loading effects that make use of these + * extensions into an older application. + * + * All strings are unicode, 0-terminated, and the encoding is utf-8. + */ +typedef struct f0r_plugin_info +{ + const char* name; /**< The (short) name of the plugin */ + const char* author; /**< The plugin author */ + /** The plugin type + * \see PLUGIN_TYPE + */ + int plugin_type; + int color_model; /**< The color model used */ + int frei0r_version; /**< The frei0r major version this plugin is built for*/ + int major_version; /**< The major version of the plugin */ + int minor_version; /**< The minor version of the plugin */ + int num_params; /**< The number of parameters of the plugin */ + const char* explanation; /**< An optional explanation string */ +} f0r_plugin_info_t; + + +/** + * Is called once after init. The plugin has to fill in the values in info. + * + * \param info Pointer to an info struct allocated by the application. + */ +void f0r_get_plugin_info(f0r_plugin_info_t* info); + +//--------------------------------------------------------------------------- + +/** \addtogroup PARAM_TYPE Parameter Types + * + * @{ + */ + + +/** + * Parameter type for boolean values + * \see f0r_param_bool + */ +#define F0R_PARAM_BOOL 0 + +/** + * Parameter type for doubles + * \see f0r_param_double + */ +#define F0R_PARAM_DOUBLE 1 + +/** + * Parameter type for color + * \see f0r_param_color + */ +#define F0R_PARAM_COLOR 2 +/** + * Parameter type for position + * \see f0r_param_position + */ +#define F0R_PARAM_POSITION 3 + +/** + * Parameter type for string + * \see f0r_param_string + */ +#define F0R_PARAM_STRING 4 + +/** + * The boolean type. The allowed range of values is [0, 1]. + * [0, 0.5[ is mapped to false and [0.5, 1] is mapped to true. + */ +typedef double f0r_param_bool; + +/** + * The double type. The allowed range of values is [0, 1]. + */ +typedef double f0r_param_double; + +/** + * The color type. All three color components are in the range [0, 1]. + */ +typedef struct f0r_param_color +{ + float r; /**< red color component */ + float g; /**< green color component */ + float b; /**< blue color component */ +} f0r_param_color_t; + +/** + * The position type. Both position coordinates are in the range [0, 1]. + */ +typedef struct f0r_param_position +{ + double x; /**< x coordinate */ + double y; /**< y coordinate */ +} f0r_param_position_t; + + +/** + * The string type. + * Zero terminated array of 8-bit values in utf-8 encoding + */ +typedef char f0r_param_string; + +/** @} */ + + +/** + * Similar to f0r_plugin_info_t, this structure is filled by the plugin + * for every parameter. + * + * All strings are unicode, 0-terminated, and the encoding is utf-8. + */ +typedef struct f0r_param_info +{ + const char* name; /**<The (short) name of the param */ + int type; /**<The type (see the F0R_PARAM_* defines) */ + const char* explanation; /**<Optional explanation (can be 0) */ +} f0r_param_info_t; + +/** + * f0r_get_param_info is called by the application to query the type of + * each parameter. + * + * \param info is allocated by the application and filled by the plugin + * \param param_index the index of the parameter to be queried (from 0 to + * num_params-1) + */ +void f0r_get_param_info(f0r_param_info_t* info, int param_index); + +//--------------------------------------------------------------------------- + +/** + * Transparent instance pointer of the frei0r effect. + */ +typedef void* f0r_instance_t; + +/** + * Constructor for effect instances. The plugin returns a pointer to + * its internal instance structure. + * + * The resolution has to be an integer multiple of 8, + * must be greater than 0 and be at most 2048 in both dimensions. + * + * \param width The x-resolution of the processed video frames + * \param height The y-resolution of the processed video frames + * \returns 0 on failure or a pointer != 0 on success + * + * \see f0r_destruct + */ +f0r_instance_t f0r_construct(unsigned int width, unsigned int height); + +/** + * Destroys an effect instance. + * + * \param instance The pointer to the plugins internal instance structure. + * + * \see f0r_construct + */ +void f0r_destruct(f0r_instance_t instance); + +//--------------------------------------------------------------------------- + +/** + * Transparent parameter handle. + */ +typedef void* f0r_param_t; + +/** + * This function allows the application to set the parameter values of an + * effect instance. Validity of the parameter pointer is handled by the + * application thus the data must be copied by the effect. + * + * \param instance the effect instance + * \param param pointer to the parameter value + * \param param_index index of the parameter + * + * \see f0r_get_param_value + */ +void f0r_set_param_value(f0r_instance_t instance, + f0r_param_t param, int param_index); + +/** + * This function allows the application to query the parameter values of an + * effect instance. + * + * \param instance the effect instance + * \param param pointer to the parameter value + * \param param_index index of the parameter + * + * \see f0r_set_param_value + */ +void f0r_get_param_value(f0r_instance_t instance, + f0r_param_t param, int param_index); + +//--------------------------------------------------------------------------- + +/** + * This is where the core effect processing happens. The application calls it + * after it has set the necessary parameter values. + * inframe and outframe must be aligned to an integer multiple of 16 bytes + * in memory. + * + * This funcition should not alter the parameters of the effect in any + * way (\ref f0r_get_param_value should return the same values after a call + * to \ref f0r_update as before the call). + * + * The function is responsible to restore the fpu state (e.g. rounding mode) + * and mmx state if applicable before it returns to the caller. + * + * The host mustn't call \ref f0r_update for effects of type + * \ref F0R_PLUGIN_TYPE_MIXER2 and \ref F0R_PLUGIN_TYPE_MIXER3. + * + * \param instance the effect instance + * \param time the application time in seconds but with subsecond resolution + * (e.g. milli-second resolution). The resolution should be at least + * the inter-frame period of the application. + * \param inframe the incoming video frame (can be zero for sources) + * \param outframe the resulting video frame + * + * \see f0r_update2 + */ +void f0r_update(f0r_instance_t instance, + double time, const uint32_t* inframe, uint32_t* outframe); + +//--------------------------------------------------------------------------- + +/** + * For effects of type \ref F0R_PLUGIN_TYPE_SOURCE or + * \ref F0R_PLUGIN_TYPE_FILTER this method is optional. The \ref f0r_update + * method must still be exported for these two effect types. If both are + * provided the behavior of them must be the same. + * + * Effects of type \ref F0R_PLUGIN_TYPE_MIXER2 or \ref F0R_PLUGIN_TYPE_MIXER3 must provide the new \ref f0r_update2 method. + + * \param instance the effect instance + * \param time the application time in seconds but with subsecond resolution + * (e.g. milli-second resolution). The resolution should be at least + * the inter-frame period of the application. + * \param inframe1 the first incoming video frame (can be zero for sources) + * \param inframe2 the second incoming video frame + (can be zero for sources and filters) + * \param inframe3 the third incoming video frame + (can be zero for sources, filters and mixer3) + * \param outframe the resulting video frame + * + * \see f0r_update + */ +void f0r_update2(f0r_instance_t instance, + double time, + const uint32_t* inframe1, + const uint32_t* inframe2, + const uint32_t* inframe3, + uint32_t* outframe); +//--------------------------------------------------------------------------- + +#endif diff --git a/gst/frei0r/gstfrei0r.c b/gst/frei0r/gstfrei0r.c new file mode 100644 index 00000000..0f7ba5c5 --- /dev/null +++ b/gst/frei0r/gstfrei0r.c @@ -0,0 +1,598 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstfrei0r.h" +#include "gstfrei0rfilter.h" +#include "gstfrei0rsrc.h" +#include "gstfrei0rmixer.h" + +#include <string.h> + +GST_DEBUG_CATEGORY (frei0r_debug); +#define GST_CAT_DEFAULT frei0r_debug + +GstCaps * +gst_frei0r_caps_from_color_model (gint color_model) +{ + switch (color_model) { + case F0R_COLOR_MODEL_BGRA8888: + return gst_caps_from_string (GST_VIDEO_CAPS_BGRA); + case F0R_COLOR_MODEL_RGBA8888: + return gst_caps_from_string (GST_VIDEO_CAPS_RGBA); + case F0R_COLOR_MODEL_PACKED32: + return gst_caps_from_string (GST_VIDEO_CAPS_BGRA " ; " + GST_VIDEO_CAPS_RGBA " ; " + GST_VIDEO_CAPS_ABGR " ; " + GST_VIDEO_CAPS_ARGB " ; " + GST_VIDEO_CAPS_BGRx " ; " + GST_VIDEO_CAPS_RGBx " ; " + GST_VIDEO_CAPS_xBGR " ; " + GST_VIDEO_CAPS_xRGB " ; " GST_VIDEO_CAPS_YUV ("AYUV")); + default: + break; + } + + return NULL; +} + +void +gst_frei0r_klass_install_properties (GObjectClass * gobject_class, + GstFrei0rFuncTable * ftable, GstFrei0rProperty * properties, + gint n_properties) +{ + gint i, count = 1; + f0r_instance_t *instance = ftable->construct (640, 480); + + g_assert (instance); + + for (i = 0; i < n_properties; i++) { + f0r_param_info_t *param_info = &properties[i].info; + gchar *prop_name; + + ftable->get_param_info (param_info, i); + + prop_name = g_ascii_strdown (param_info->name, -1); + g_strcanon (prop_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-'); + + properties[i].prop_id = count; + properties[i].prop_idx = i; + + ftable->get_param_value (instance, &properties[i].default_value, i); + if (param_info->type == F0R_PARAM_STRING) + properties[i].default_value.data.s = + g_strdup (properties[i].default_value.data.s); + + switch (param_info->type) { + case F0R_PARAM_BOOL: + g_object_class_install_property (gobject_class, count++, + g_param_spec_boolean (prop_name, param_info->name, + param_info->explanation, FALSE, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + properties[i].n_prop_ids = 1; + break; + case F0R_PARAM_DOUBLE: + g_object_class_install_property (gobject_class, count++, + g_param_spec_double (prop_name, param_info->name, + param_info->explanation, -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + properties[i].n_prop_ids = 1; + break; + case F0R_PARAM_STRING: + g_object_class_install_property (gobject_class, count++, + g_param_spec_string (prop_name, param_info->name, + param_info->explanation, NULL, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + properties[i].n_prop_ids = 1; + break; + case F0R_PARAM_COLOR:{ + gchar *prop_name_full; + gchar *prop_nick_full; + + prop_name_full = g_strconcat (prop_name, "-r", NULL); + prop_nick_full = g_strconcat (param_info->name, "-R", NULL); + g_object_class_install_property (gobject_class, count++, + g_param_spec_float (prop_name_full, prop_nick_full, + param_info->explanation, 0.0, 1.0, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + g_free (prop_name_full); + g_free (prop_nick_full); + + prop_name_full = g_strconcat (prop_name, "-g", NULL); + prop_nick_full = g_strconcat (param_info->name, "-G", NULL); + g_object_class_install_property (gobject_class, count++, + g_param_spec_float (prop_name_full, param_info->name, + param_info->explanation, 0.0, 1.0, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + g_free (prop_name_full); + g_free (prop_nick_full); + + prop_name_full = g_strconcat (prop_name, "-b", NULL); + prop_nick_full = g_strconcat (param_info->name, "-B", NULL); + g_object_class_install_property (gobject_class, count++, + g_param_spec_float (prop_name_full, param_info->name, + param_info->explanation, 0.0, 1.0, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + g_free (prop_name_full); + g_free (prop_nick_full); + + properties[i].n_prop_ids = 3; + break; + } + case F0R_PARAM_POSITION:{ + gchar *prop_name_full; + gchar *prop_nick_full; + + prop_name_full = g_strconcat (prop_name, "-x", NULL); + prop_nick_full = g_strconcat (param_info->name, "-X", NULL); + g_object_class_install_property (gobject_class, count++, + g_param_spec_double (prop_name_full, param_info->name, + param_info->explanation, 0.0, 1.0, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + g_free (prop_name_full); + g_free (prop_nick_full); + + prop_name_full = g_strconcat (prop_name, "-Y", NULL); + prop_nick_full = g_strconcat (param_info->name, "-X", NULL); + g_object_class_install_property (gobject_class, count++, + g_param_spec_double (prop_name_full, param_info->name, + param_info->explanation, 0.0, 1.0, 0.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); + g_free (prop_name_full); + g_free (prop_nick_full); + + properties[i].n_prop_ids = 2; + break; + } + default: + g_assert_not_reached (); + break; + } + + g_free (prop_name); + } + + ftable->destruct (instance); +} + +GstFrei0rPropertyValue * +gst_frei0r_property_cache_init (GstFrei0rProperty * properties, + gint n_properties) +{ + gint i; + GstFrei0rPropertyValue *ret = g_new0 (GstFrei0rPropertyValue, n_properties); + + for (i = 0; i < n_properties; i++) { + memcpy (&ret[i].data, &properties[i].default_value, + sizeof (GstFrei0rPropertyValue)); + + if (properties[i].info.type == F0R_PARAM_STRING) + ret[i].data.s = g_strdup (ret[i].data.s); + } + + return ret; +} + +void +gst_frei0r_property_cache_free (GstFrei0rProperty * properties, + GstFrei0rPropertyValue * property_cache, gint n_properties) +{ + gint i; + + for (i = 0; i < n_properties; i++) { + if (properties[i].info.type == F0R_PARAM_STRING) + g_free (property_cache[i].data.s); + } + g_free (property_cache); +} + +f0r_instance_t * +gst_frei0r_instance_construct (GstFrei0rFuncTable * ftable, + GstFrei0rProperty * properties, gint n_properties, + GstFrei0rPropertyValue * property_cache, gint width, gint height) +{ + f0r_instance_t *instance = ftable->construct (width, height); + gint i; + + for (i = 0; i < n_properties; i++) { + if (properties[i].info.type == F0R_PARAM_STRING) + ftable->set_param_value (instance, property_cache[i].data.s, i); + else + ftable->set_param_value (instance, &property_cache[i].data, i); + } + + return instance; +} + +gboolean +gst_frei0r_get_property (f0r_instance_t * instance, GstFrei0rFuncTable * ftable, + GstFrei0rProperty * properties, gint n_properties, + GstFrei0rPropertyValue * property_cache, guint prop_id, GValue * value) +{ + gint i; + GstFrei0rProperty *prop = NULL; + + for (i = 0; i < n_properties; i++) { + if (properties[i].prop_id <= prop_id && + properties[i].prop_id + properties[i].n_prop_ids > prop_id) { + prop = &properties[i]; + break; + } + } + + if (!prop) + return FALSE; + + switch (prop->info.type) { + case F0R_PARAM_BOOL:{ + gdouble d; + + if (instance) + ftable->get_param_value (instance, &d, prop->prop_idx); + else + d = property_cache[prop->prop_idx].data.b ? 1.0 : 0.0; + + g_value_set_boolean (value, (d < 0.5) ? FALSE : TRUE); + break; + } + case F0R_PARAM_DOUBLE:{ + gdouble d; + + if (instance) + ftable->get_param_value (instance, &d, prop->prop_idx); + else + d = property_cache[prop->prop_idx].data.d; + + g_value_set_double (value, d); + break; + } + case F0R_PARAM_STRING:{ + const gchar *s; + + if (instance) + ftable->get_param_value (instance, &s, prop->prop_idx); + else + s = property_cache[prop->prop_idx].data.s; + g_value_set_string (value, s); + break; + } + case F0R_PARAM_COLOR:{ + f0r_param_color_t color; + + if (instance) + ftable->get_param_value (instance, &color, prop->prop_idx); + else + color = property_cache[prop->prop_idx].data.color; + + switch (prop_id - prop->prop_id) { + case 0: + g_value_set_float (value, color.r); + break; + case 1: + g_value_set_float (value, color.g); + break; + case 2: + g_value_set_float (value, color.b); + break; + } + break; + } + case F0R_PARAM_POSITION:{ + f0r_param_position_t position; + + if (instance) + ftable->get_param_value (instance, &position, prop->prop_idx); + else + position = property_cache[prop->prop_idx].data.position; + + switch (prop_id - prop->prop_id) { + case 0: + g_value_set_double (value, position.x); + break; + case 1: + g_value_set_double (value, position.y); + break; + } + break; + } + default: + g_assert_not_reached (); + break; + } + + return TRUE; +} + +gboolean +gst_frei0r_set_property (f0r_instance_t * instance, GstFrei0rFuncTable * ftable, + GstFrei0rProperty * properties, gint n_properties, + GstFrei0rPropertyValue * property_cache, guint prop_id, + const GValue * value) +{ + GstFrei0rProperty *prop = NULL; + gint i; + + for (i = 0; i < n_properties; i++) { + if (properties[i].prop_id <= prop_id && + properties[i].prop_id + properties[i].n_prop_ids > prop_id) { + prop = &properties[i]; + break; + } + } + + if (!prop) + return FALSE; + + switch (prop->info.type) { + case F0R_PARAM_BOOL:{ + gboolean b = g_value_get_boolean (value); + gdouble d = b ? 1.0 : 0.0; + + if (instance) + ftable->set_param_value (instance, &d, prop->prop_idx); + property_cache[prop->prop_idx].data.b = b; + break; + } + case F0R_PARAM_DOUBLE:{ + gdouble d = g_value_get_double (value); + + if (instance) + ftable->set_param_value (instance, &d, prop->prop_idx); + property_cache[prop->prop_idx].data.d = d; + break; + } + case F0R_PARAM_STRING:{ + gchar *s = g_value_dup_string (value); + + /* Copies the string */ + if (instance) + ftable->set_param_value (instance, s, prop->prop_idx); + property_cache[prop->prop_idx].data.s = s; + break; + } + case F0R_PARAM_COLOR:{ + gfloat f = g_value_get_float (value); + f0r_param_color_t *color = &property_cache[prop->prop_idx].data.color; + + switch (prop_id - prop->prop_id) { + case 0: + color->r = f; + break; + case 1: + color->g = f; + break; + case 2: + color->b = f; + break; + default: + g_assert_not_reached (); + } + + if (instance) + ftable->set_param_value (instance, color, prop->prop_idx); + break; + } + case F0R_PARAM_POSITION:{ + gdouble d = g_value_get_double (value); + f0r_param_position_t *position = + &property_cache[prop->prop_idx].data.position; + + switch (prop_id - prop->prop_id) { + case 0: + position->x = d; + break; + case 1: + position->y = d; + break; + default: + g_assert_not_reached (); + } + if (instance) + ftable->set_param_value (instance, position, prop->prop_idx); + break; + } + default: + g_assert_not_reached (); + break; + } + + return TRUE; +} + +static gboolean +register_plugin (GstPlugin * plugin, const gchar * filename) +{ + GModule *module; + gboolean ret = FALSE; + GstFrei0rFuncTable ftable = { NULL, }; + gint i; + f0r_plugin_info_t info = { NULL, }; + f0r_instance_t *instance = NULL; + + GST_DEBUG ("Registering plugin '%s'", filename); + + module = g_module_open (filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + if (!module) { + GST_WARNING ("Failed to load plugin"); + return FALSE; + } + + if (!g_module_symbol (module, "f0r_init", (gpointer *) & ftable.init)) { + GST_INFO ("No frei0r plugin"); + g_module_close (module); + return FALSE; + } + + if (!g_module_symbol (module, "f0r_deinit", (gpointer *) & ftable.deinit) || + !g_module_symbol (module, "f0r_construct", + (gpointer *) & ftable.construct) + || !g_module_symbol (module, "f0r_destruct", + (gpointer *) & ftable.destruct) + || !g_module_symbol (module, "f0r_get_plugin_info", + (gpointer *) & ftable.get_plugin_info) + || !g_module_symbol (module, "f0r_get_param_info", + (gpointer *) & ftable.get_param_info) + || !g_module_symbol (module, "f0r_set_param_value", + (gpointer *) & ftable.set_param_value) + || !g_module_symbol (module, "f0r_get_param_value", + (gpointer *) & ftable.get_param_value)) + goto invalid_frei0r_plugin; + + /* One of these must exist */ + g_module_symbol (module, "f0r_update", (gpointer *) & ftable.update); + g_module_symbol (module, "f0r_update2", (gpointer *) & ftable.update2); + + if (!ftable.init ()) { + GST_WARNING ("Failed to initialize plugin"); + g_module_close (module); + return FALSE; + } + + if (!ftable.update && !ftable.update2) + goto invalid_frei0r_plugin; + + ftable.get_plugin_info (&info); + + if (info.frei0r_version > 1) { + GST_WARNING ("Unsupported frei0r version %d", info.frei0r_version); + ftable.deinit (); + g_module_close (module); + return FALSE; + } + + if (info.color_model > F0R_COLOR_MODEL_PACKED32) { + GST_WARNING ("Unsupported color model %d", info.color_model); + ftable.deinit (); + g_module_close (module); + return FALSE; + } + + for (i = 0; i < info.num_params; i++) { + f0r_param_info_t pinfo = { NULL, }; + + ftable.get_param_info (&pinfo, i); + if (pinfo.type > F0R_PARAM_STRING) { + GST_WARNING ("Unsupported parameter type %d", pinfo.type); + ftable.deinit (); + g_module_close (module); + return FALSE; + } + } + + instance = ftable.construct (640, 480); + if (!instance) { + GST_WARNING ("Failed to instanciate plugin '%s'", info.name); + ftable.deinit (); + g_module_close (module); + return FALSE; + } + ftable.destruct (instance); + + switch (info.plugin_type) { + case F0R_PLUGIN_TYPE_FILTER: + ret = gst_frei0r_filter_register (plugin, &info, &ftable); + break; + case F0R_PLUGIN_TYPE_SOURCE: + ret = gst_frei0r_src_register (plugin, &info, &ftable); + break; + case F0R_PLUGIN_TYPE_MIXER2: + case F0R_PLUGIN_TYPE_MIXER3: + ret = gst_frei0r_mixer_register (plugin, &info, &ftable); + break; + default: + break; + } + + if (!ret) + goto invalid_frei0r_plugin; + + return ret; + +invalid_frei0r_plugin: + GST_ERROR ("Invalid frei0r plugin"); + ftable.deinit (); + g_module_close (module); + + return FALSE; +} + +static gboolean +register_plugins (GstPlugin * plugin, const gchar * path) +{ + GDir *dir; + gchar *filename; + const gchar *entry_name; + gboolean ret = FALSE; + + GST_DEBUG ("Scanning director '%s' for frei0r plugins", path); + + dir = g_dir_open (path, 0, NULL); + if (!dir) + return FALSE; + + while ((entry_name = g_dir_read_name (dir))) { + filename = g_build_filename (path, entry_name, NULL); + if ((g_str_has_suffix (filename, G_MODULE_SUFFIX) +#ifdef GST_EXTRA_MODULE_SUFFIX + || g_str_has_suffix (filename, GST_EXTRA_MODULE_SUFFIX) +#endif + ) && g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { + ret |= register_plugin (plugin, filename); + } else if (g_file_test (filename, G_FILE_TEST_IS_DIR)) { + ret |= register_plugins (plugin, filename); + } + g_free (filename); + } + g_dir_close (dir); + + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + const gchar *homedir; + gchar *path; + + GST_DEBUG_CATEGORY_INIT (frei0r_debug, "frei0r", 0, "frei0r"); + + gst_plugin_add_dependency_simple (plugin, + "HOME/.frei0r-1/lib", + "/usr/lib/frei0r-1:/usr/local/lib/frei0r-1", + NULL, GST_PLUGIN_DEPENDENCY_FLAG_RECURSE); + + register_plugins (plugin, "/usr/lib/frei0r-1"); + register_plugins (plugin, "/usr/local/lib/frei0r-1"); + + homedir = g_get_home_dir (); + path = g_build_filename (homedir, ".frei0r-1", NULL); + register_plugins (plugin, path); + g_free (path); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "frei0r", + "frei0r plugin library", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/frei0r/gstfrei0r.h b/gst/frei0r/gstfrei0r.h new file mode 100644 index 00000000..91e5e097 --- /dev/null +++ b/gst/frei0r/gstfrei0r.h @@ -0,0 +1,91 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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_FREI0R_H__ +#define __GST_FREI0R_H__ + +#include <gst/gst.h> + +#include "frei0r.h" + +G_BEGIN_DECLS + +typedef struct _GstFrei0rFuncTable GstFrei0rFuncTable; +typedef struct _GstFrei0rProperty GstFrei0rProperty; +typedef struct _GstFrei0rPropertyValue GstFrei0rPropertyValue; + +struct _GstFrei0rPropertyValue { + union { + gboolean b; + gdouble d; + gchar *s; + f0r_param_position_t position; + f0r_param_color_t color; + } data; +}; + +struct _GstFrei0rProperty { + guint prop_id; + guint n_prop_ids; + + gint prop_idx; + f0r_param_info_t info; + + GstFrei0rPropertyValue default_value; +}; + +struct _GstFrei0rFuncTable { + int (*init) (void); + void (*deinit) (void); + + f0r_instance_t (*construct) (unsigned int width, unsigned int height); + void (*destruct) (f0r_instance_t instance); + + void (*get_plugin_info) (f0r_plugin_info_t* info); + void (*get_param_info) (f0r_param_info_t* info, int param_index); + + void (*set_param_value) (f0r_instance_t instance, + f0r_param_t param, int param_index); + void (*get_param_value) (f0r_instance_t instance, + f0r_param_t param, int param_index); + + void (*update) (f0r_instance_t instance, + double time, const uint32_t* inframe, uint32_t* outframe); + void (*update2) (f0r_instance_t instance, + double time, + const uint32_t* inframe1, + const uint32_t* inframe2, + const uint32_t* inframe3, + uint32_t* outframe); +}; + +void gst_frei0r_klass_install_properties (GObjectClass *gobject_class, GstFrei0rFuncTable *ftable, GstFrei0rProperty *properties, gint n_properties); + +f0r_instance_t * gst_frei0r_instance_construct (GstFrei0rFuncTable *ftable, GstFrei0rProperty *properties, gint n_properties, GstFrei0rPropertyValue *property_cache, gint width, gint height); + +GstFrei0rPropertyValue * gst_frei0r_property_cache_init (GstFrei0rProperty *properties, gint n_properties); +void gst_frei0r_property_cache_free (GstFrei0rProperty *properties, GstFrei0rPropertyValue *property_cache, gint n_properties); + +GstCaps * gst_frei0r_caps_from_color_model (gint color_model); +gboolean gst_frei0r_get_property (f0r_instance_t *instance, GstFrei0rFuncTable *ftable, GstFrei0rProperty *properties, gint n_properties, GstFrei0rPropertyValue *property_cache, guint prop_id, GValue *value); +gboolean gst_frei0r_set_property (f0r_instance_t *instance, GstFrei0rFuncTable *ftable, GstFrei0rProperty *properties, gint n_properties, GstFrei0rPropertyValue *property_cache, guint prop_id, const GValue *value); + +G_END_DECLS + +#endif /* __GST_FREI0R_H__ */ diff --git a/gst/frei0r/gstfrei0rfilter.c b/gst/frei0r/gstfrei0rfilter.c new file mode 100644 index 00000000..43d8fc67 --- /dev/null +++ b/gst/frei0r/gstfrei0rfilter.c @@ -0,0 +1,241 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "gstfrei0r.h" +#include "gstfrei0rfilter.h" + +GST_DEBUG_CATEGORY_EXTERN (frei0r_debug); +#define GST_CAT_DEFAULT frei0r_debug + +typedef struct +{ + f0r_plugin_info_t info; + GstFrei0rFuncTable ftable; +} GstFrei0rFilterClassData; + +static gboolean +gst_frei0r_filter_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (trans); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (trans); + GstVideoFormat fmt; + gint width, height; + + if (!gst_video_format_parse_caps (incaps, &fmt, &width, &height)) + return FALSE; + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + self->f0r_instance = + gst_frei0r_instance_construct (klass->ftable, klass->properties, + klass->n_properties, self->property_cache, width, height); + + return TRUE; +} + +static gboolean +gst_frei0r_filter_stop (GstBaseTransform * trans) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (trans); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (trans); + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + return TRUE; +} + +static GstFlowReturn +gst_frei0r_filter_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (trans); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (trans); + gdouble time; + + if (!self->f0r_instance) + return GST_FLOW_NOT_NEGOTIATED; + + time = ((gdouble) GST_BUFFER_TIMESTAMP (inbuf)) / GST_SECOND; + + if (klass->ftable->update2) + klass->ftable->update2 (self->f0r_instance, time, + (const guint32 *) GST_BUFFER_DATA (inbuf), NULL, NULL, + (guint32 *) GST_BUFFER_DATA (outbuf)); + else + klass->ftable->update (self->f0r_instance, time, + (const guint32 *) GST_BUFFER_DATA (inbuf), + (guint32 *) GST_BUFFER_DATA (outbuf)); + + return GST_FLOW_OK; +} + +static void +gst_frei0r_filter_finalize (GObject * object) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (object); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (object); + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + if (self->property_cache) + gst_frei0r_property_cache_free (klass->properties, self->property_cache, + klass->n_properties); + self->property_cache = NULL; + + G_OBJECT_CLASS (g_type_class_peek_parent (klass))->finalize (object); +} + +static void +gst_frei0r_filter_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (object); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (object); + + if (!gst_frei0r_get_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gst_frei0r_filter_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFrei0rFilter *self = GST_FREI0R_FILTER (object); + GstFrei0rFilterClass *klass = GST_FREI0R_FILTER_GET_CLASS (object); + + if (!gst_frei0r_set_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gst_frei0r_filter_class_init (GstFrei0rFilterClass * klass, + GstFrei0rFilterClassData * class_data) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBaseTransformClass *gsttrans_class = (GstBaseTransformClass *) klass; + GstPadTemplate *templ; + GstCaps *caps; + gchar *author; + + klass->ftable = &class_data->ftable; + klass->info = &class_data->info; + + gobject_class->finalize = gst_frei0r_filter_finalize; + gobject_class->set_property = gst_frei0r_filter_set_property; + gobject_class->get_property = gst_frei0r_filter_get_property; + + klass->n_properties = klass->info->num_params; + klass->properties = g_new0 (GstFrei0rProperty, klass->n_properties); + + gst_frei0r_klass_install_properties (gobject_class, klass->ftable, + klass->properties, klass->n_properties); + + author = + g_strdup_printf + ("Sebastian Dröge <sebastian.droege@collabora.co.uk>, %s", + class_data->info.author); + gst_element_class_set_details_simple (gstelement_class, class_data->info.name, + "Filter/Effect/Video", class_data->info.explanation, author); + g_free (author); + + caps = gst_frei0r_caps_from_color_model (class_data->info.color_model); + + templ = + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_caps_ref (caps)); + gst_element_class_add_pad_template (gstelement_class, templ); + + templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps); + gst_element_class_add_pad_template (gstelement_class, templ); + + gsttrans_class->set_caps = GST_DEBUG_FUNCPTR (gst_frei0r_filter_set_caps); + gsttrans_class->stop = GST_DEBUG_FUNCPTR (gst_frei0r_filter_stop); + gsttrans_class->transform = GST_DEBUG_FUNCPTR (gst_frei0r_filter_transform); +} + +static void +gst_frei0r_filter_init (GstFrei0rFilter * self, GstFrei0rFilterClass * klass) +{ + self->property_cache = + gst_frei0r_property_cache_init (klass->properties, klass->n_properties); +} + +gboolean +gst_frei0r_filter_register (GstPlugin * plugin, const f0r_plugin_info_t * info, + const GstFrei0rFuncTable * ftable) +{ + GTypeInfo typeinfo = { + sizeof (GstFrei0rFilterClass), + NULL, + NULL, + (GClassInitFunc) gst_frei0r_filter_class_init, + NULL, + NULL, + sizeof (GstFrei0rFilter), + 0, + (GInstanceInitFunc) gst_frei0r_filter_init + }; + GType type; + gchar *type_name, *tmp; + GstFrei0rFilterClassData *class_data; + gboolean ret = FALSE; + + tmp = g_strdup_printf ("frei0r-filter-%s", info->name); + type_name = g_ascii_strdown (tmp, -1); + g_free (tmp); + g_strcanon (type_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-'); + + if (g_type_from_name (type_name)) { + GST_WARNING ("Type '%s' already exists", type_name); + return FALSE; + } + + class_data = g_new0 (GstFrei0rFilterClassData, 1); + memcpy (&class_data->info, info, sizeof (f0r_plugin_info_t)); + memcpy (&class_data->ftable, ftable, sizeof (GstFrei0rFuncTable)); + typeinfo.class_data = class_data; + + type = + g_type_register_static (GST_TYPE_VIDEO_FILTER, type_name, &typeinfo, 0); + ret = gst_element_register (plugin, type_name, GST_RANK_NONE, type); + + g_free (type_name); + return ret; +} diff --git a/gst/frei0r/gstfrei0rfilter.h b/gst/frei0r/gstfrei0rfilter.h new file mode 100644 index 00000000..ded2172b --- /dev/null +++ b/gst/frei0r/gstfrei0rfilter.h @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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_FREI0R_FILTER_H__ +#define __GST_FREI0R_FILTER_H__ + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/video/gstvideofilter.h> + +#include "frei0r.h" +#include "gstfrei0r.h" + +G_BEGIN_DECLS + +#define GST_FREI0R_FILTER(obj) \ + ((GstFrei0rFilter *) obj) +#define GST_FREI0R_FILTER_CLASS(klass) \ + ((GstFrei0rFilterClass *) klass) +#define GST_FREI0R_FILTER_GET_CLASS(obj) \ + ((GstFrei0rFilterClass *) g_type_class_peek (G_TYPE_FROM_INSTANCE (obj))) + +typedef struct _GstFrei0rFilter GstFrei0rFilter; +typedef struct _GstFrei0rFilterClass GstFrei0rFilterClass; + +struct _GstFrei0rFilter { + GstVideoFilter parent; + + f0r_instance_t *f0r_instance; + GstFrei0rPropertyValue *property_cache; +}; + +struct _GstFrei0rFilterClass { + GstVideoFilterClass parent; + + f0r_plugin_info_t *info; + GstFrei0rFuncTable *ftable; + + GstFrei0rProperty *properties; + gint n_properties; +}; + +gboolean gst_frei0r_filter_register (GstPlugin *plugin, const f0r_plugin_info_t *info, const GstFrei0rFuncTable *ftable); + +G_END_DECLS + +#endif /* __GST_FREI0R_FILTER_H__ */ diff --git a/gst/frei0r/gstfrei0rmixer.c b/gst/frei0r/gstfrei0rmixer.c new file mode 100644 index 00000000..745c330e --- /dev/null +++ b/gst/frei0r/gstfrei0rmixer.c @@ -0,0 +1,782 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "gstfrei0r.h" +#include "gstfrei0rmixer.h" + +GST_DEBUG_CATEGORY_EXTERN (frei0r_debug); +#define GST_CAT_DEFAULT frei0r_debug + +typedef struct +{ + f0r_plugin_info_t info; + GstFrei0rFuncTable ftable; +} GstFrei0rMixerClassData; + +static void +gst_frei0r_mixer_reset (GstFrei0rMixer * self) +{ + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (self); + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + gst_caps_replace (&self->caps, NULL); + gst_event_replace (&self->newseg_event, NULL); +} + +static void +gst_frei0r_mixer_finalize (GObject * object) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (object); + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (object); + + if (self->property_cache) + gst_frei0r_property_cache_free (klass->properties, self->property_cache, + klass->n_properties); + self->property_cache = NULL; + + if (self->collect) + gst_object_unref (self->collect); + self->collect = NULL; + + G_OBJECT_CLASS (g_type_class_peek_parent (klass))->finalize (object); +} + +static void +gst_frei0r_mixer_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (object); + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (object); + + if (!gst_frei0r_get_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gst_frei0r_mixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (object); + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (object); + + if (!gst_frei0r_set_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static GstStateChangeReturn +gst_frei0r_mixer_change_state (GstElement * element, GstStateChange transition) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (element); + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (self); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_collect_pads_start (self->collect); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + /* Stop before calling the parent's state change function as + * GstCollectPads might take locks and we would deadlock in that + * case + */ + if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) + gst_collect_pads_stop (self->collect); + + ret = + GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->change_state + (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_frei0r_mixer_reset (self); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static GstCaps * +gst_frei0r_mixer_get_caps (GstPad * pad) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + GstCaps *caps = NULL; + + if (self->caps) { + caps = gst_caps_ref (self->caps); + } else { + GstCaps *tmp, *tmp1; + + tmp = gst_caps_copy (gst_pad_get_pad_template_caps (self->src)); + tmp1 = gst_pad_peer_get_caps (pad); + if (tmp1) { + caps = gst_caps_intersect (tmp, tmp1); + gst_caps_unref (tmp1); + gst_caps_unref (tmp); + } else { + caps = tmp; + } + + tmp = caps; + tmp1 = gst_pad_peer_get_caps (self->sink0); + if (tmp1) { + caps = gst_caps_intersect (tmp, tmp1); + gst_caps_unref (tmp); + gst_caps_unref (tmp1); + } + + tmp = caps; + tmp1 = gst_pad_peer_get_caps (self->sink1); + if (tmp1) { + caps = gst_caps_intersect (tmp, tmp1); + gst_caps_unref (tmp); + gst_caps_unref (tmp1); + } + + if (self->sink2) { + tmp = caps; + tmp1 = gst_pad_peer_get_caps (self->sink2); + if (tmp1) { + caps = gst_caps_intersect (tmp, tmp1); + gst_caps_unref (tmp); + gst_caps_unref (tmp1); + } + } + } + + gst_object_unref (self); + + return caps; +} + +static gboolean +gst_frei0r_mixer_set_caps (GstPad * pad, GstCaps * caps) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (self); + gboolean ret = TRUE; + + gst_caps_replace (&self->caps, caps); + + if (pad != self->src) + ret &= gst_pad_set_caps (self->src, caps); + if (pad != self->sink0) + ret &= gst_pad_set_caps (self->sink0, caps); + if (pad != self->sink1) + ret &= gst_pad_set_caps (self->sink1, caps); + if (pad != self->sink2 && self->sink2) + ret &= gst_pad_set_caps (self->sink2, caps); + + if (ret) { + if (!gst_video_format_parse_caps (caps, &self->fmt, &self->width, + &self->height)) { + ret = FALSE; + goto out; + } + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + self->f0r_instance = + gst_frei0r_instance_construct (klass->ftable, klass->properties, + klass->n_properties, self->property_cache, self->width, self->height); + + } +out: + + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_frei0r_mixer_src_query_duration (GstFrei0rMixer * self, GstQuery * query) +{ + gint64 min; + gboolean res; + GstFormat format; + GstIterator *it; + gboolean done; + + /* parse format */ + gst_query_parse_duration (query, &format, NULL); + + min = -1; + res = TRUE; + done = FALSE; + + /* Take minimum of all durations */ + it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self)); + while (!done) { + GstIteratorResult ires; + gpointer item; + + ires = gst_iterator_next (it, &item); + switch (ires) { + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_OK: + { + GstPad *pad = GST_PAD_CAST (item); + gint64 duration; + + /* ask sink peer for duration */ + res &= gst_pad_query_peer_duration (pad, &format, &duration); + /* take min from all valid return values */ + if (res) { + /* valid unknown length, stop searching */ + if (duration == -1) { + min = duration; + done = TRUE; + } + /* else see if smaller than current min */ + else if (duration < min) + min = duration; + } + gst_object_unref (pad); + break; + } + case GST_ITERATOR_RESYNC: + min = -1; + res = TRUE; + gst_iterator_resync (it); + break; + default: + res = FALSE; + done = TRUE; + break; + } + } + gst_iterator_free (it); + + if (res) { + /* and store the min */ + GST_DEBUG_OBJECT (self, "Total duration in format %s: %" + GST_TIME_FORMAT, gst_format_get_name (format), GST_TIME_ARGS (min)); + gst_query_set_duration (query, format, min); + } + + return res; +} + +static gboolean +gst_frei0r_mixer_src_query_latency (GstFrei0rMixer * self, GstQuery * query) +{ + GstClockTime min, max; + gboolean live; + gboolean res; + GstIterator *it; + gboolean done; + + res = TRUE; + done = FALSE; + + live = FALSE; + min = 0; + max = GST_CLOCK_TIME_NONE; + + /* Take maximum of all latency values */ + it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self)); + while (!done) { + GstIteratorResult ires; + gpointer item; + + ires = gst_iterator_next (it, &item); + switch (ires) { + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_OK: + { + GstPad *pad = GST_PAD_CAST (item); + GstQuery *peerquery; + GstClockTime min_cur, max_cur; + gboolean live_cur; + + peerquery = gst_query_new_latency (); + + /* Ask peer for latency */ + res &= gst_pad_peer_query (pad, peerquery); + + /* take max from all valid return values */ + if (res) { + gst_query_parse_latency (peerquery, &live_cur, &min_cur, &max_cur); + + if (min_cur > min) + min = min_cur; + + if (max_cur != GST_CLOCK_TIME_NONE && + ((max != GST_CLOCK_TIME_NONE && max_cur > max) || + (max == GST_CLOCK_TIME_NONE))) + max = max_cur; + + live = live || live_cur; + } + + gst_query_unref (peerquery); + gst_object_unref (pad); + break; + } + case GST_ITERATOR_RESYNC: + live = FALSE; + min = 0; + max = GST_CLOCK_TIME_NONE; + res = TRUE; + gst_iterator_resync (it); + break; + default: + res = FALSE; + done = TRUE; + break; + } + } + gst_iterator_free (it); + + if (res) { + /* store the results */ + GST_DEBUG_OBJECT (self, "Calculated total latency: live %s, min %" + GST_TIME_FORMAT ", max %" GST_TIME_FORMAT, + (live ? "yes" : "no"), GST_TIME_ARGS (min), GST_TIME_ARGS (max)); + gst_query_set_latency (query, live, min, max); + } + + return res; +} + +static gboolean +gst_frei0r_mixer_src_query (GstPad * pad, GstQuery * query) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + gboolean ret = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + ret = gst_pad_query (self->sink0, query); + break; + case GST_QUERY_DURATION: + ret = gst_frei0r_mixer_src_query_duration (self, query); + break; + case GST_QUERY_LATENCY: + ret = gst_frei0r_mixer_src_query_latency (self, query); + break; + default: + ret = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_frei0r_mixer_sink_query (GstPad * pad, GstQuery * query) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + gboolean ret = gst_pad_query (self->src, query); + + gst_object_unref (self); + + return ret; +} + +static gboolean +forward_event_func (GstPad * pad, GValue * ret, GstEvent * event) +{ + gst_event_ref (event); + GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event)); + if (!gst_pad_push_event (pad, event)) { + g_value_set_boolean (ret, FALSE); + GST_WARNING_OBJECT (pad, "Sending event %p (%s) failed.", + event, GST_EVENT_TYPE_NAME (event)); + } else { + GST_LOG_OBJECT (pad, "Sent event %p (%s).", + event, GST_EVENT_TYPE_NAME (event)); + } + gst_object_unref (pad); + return TRUE; +} + +static gboolean +forward_event (GstFrei0rMixer * self, GstEvent * event) +{ + GstIterator *it; + GValue vret = { 0 }; + + GST_LOG_OBJECT (self, "Forwarding event %p (%s)", event, + GST_EVENT_TYPE_NAME (event)); + + g_value_init (&vret, G_TYPE_BOOLEAN); + g_value_set_boolean (&vret, TRUE); + it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self)); + gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret, + event); + gst_iterator_free (it); + gst_event_unref (event); + + return g_value_get_boolean (&vret); +} + +static gboolean +gst_frei0r_mixer_src_event (GstPad * pad, GstEvent * event) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + gboolean ret = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_QOS: + /* QoS might be tricky */ + ret = FALSE; + break; + case GST_EVENT_SEEK: + { + GstSeekFlags flags; + + /* parse the seek parameters */ + gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL); + + /* check if we are flushing */ + if (flags & GST_SEEK_FLAG_FLUSH) { + /* make sure we accept nothing anymore and return WRONG_STATE */ + gst_collect_pads_set_flushing (self->collect, TRUE); + + /* flushing seek, start flush downstream, the flush will be done + * when all pads received a FLUSH_STOP. */ + gst_pad_push_event (self->src, gst_event_new_flush_start ()); + } + + ret = forward_event (self, event); + break; + } + case GST_EVENT_NAVIGATION: + /* navigation is rather pointless. */ + ret = FALSE; + break; + default: + /* just forward the rest for now */ + ret = forward_event (self, event); + break; + } + + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_frei0r_mixer_sink0_event (GstPad * pad, GstEvent * event) +{ + GstFrei0rMixer *self = GST_FREI0R_MIXER (gst_pad_get_parent (pad)); + gboolean ret = FALSE; + + GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event), + GST_DEBUG_PAD_NAME (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + gst_event_replace (&self->newseg_event, event); + break; + default: + break; + } + + /* now GstCollectPads can take care of the rest, e.g. EOS */ + ret = self->collect_event (pad, event); + + gst_object_unref (self); + + return ret; +} + +static GstFlowReturn +gst_frei0r_mixer_collected (GstCollectPads * pads, GstFrei0rMixer * self) +{ + GstBuffer *inbuf0 = NULL, *inbuf1 = NULL, *inbuf2 = NULL; + GstBuffer *outbuf = NULL; + GstFlowReturn ret = GST_FLOW_OK; + GSList *l; + GstFrei0rMixerClass *klass = GST_FREI0R_MIXER_GET_CLASS (self); + gdouble time; + + if (G_UNLIKELY (!self->f0r_instance)) + return GST_FLOW_NOT_NEGOTIATED; + + if (self->newseg_event) { + gst_pad_push_event (self->src, self->newseg_event); + self->newseg_event = NULL; + } + + if ((ret = + gst_pad_alloc_buffer_and_set_caps (self->src, GST_BUFFER_OFFSET_NONE, + gst_video_format_get_size (self->fmt, self->width, self->height), + GST_PAD_CAPS (self->src), &outbuf)) != GST_FLOW_OK) + return ret; + + for (l = pads->data; l; l = l->next) { + GstCollectData *cdata = l->data; + + if (cdata->pad == self->sink0) + inbuf0 = gst_collect_pads_pop (pads, cdata); + else if (cdata->pad == self->sink1) + inbuf1 = gst_collect_pads_pop (pads, cdata); + else if (cdata->pad == self->sink2) + inbuf2 = gst_collect_pads_pop (pads, cdata); + } + + if (!inbuf0 || !inbuf1 || (!inbuf2 && self->sink2)) + goto eos; + + gst_buffer_copy_metadata (outbuf, inbuf0, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS); + time = ((gdouble) GST_BUFFER_TIMESTAMP (outbuf)) / GST_SECOND; + + klass->ftable->update2 (self->f0r_instance, time, + (const guint32 *) GST_BUFFER_DATA (inbuf0), + (const guint32 *) GST_BUFFER_DATA (inbuf1), + (inbuf2) ? (const guint32 *) GST_BUFFER_DATA (inbuf2) : NULL, + (guint32 *) GST_BUFFER_DATA (outbuf)); + + gst_buffer_unref (inbuf0); + gst_buffer_unref (inbuf1); + if (inbuf2) + gst_buffer_unref (inbuf2); + + ret = gst_pad_push (self->src, outbuf); + + return ret; + +eos: + { + GST_DEBUG_OBJECT (self, "no data available, must be EOS"); + gst_buffer_unref (outbuf); + + if (inbuf0) + gst_buffer_unref (inbuf0); + if (inbuf1) + gst_buffer_unref (inbuf1); + if (inbuf2) + gst_buffer_unref (inbuf2); + + gst_pad_push_event (self->src, gst_event_new_eos ()); + return GST_FLOW_UNEXPECTED; + } +} + +static void +gst_frei0r_mixer_class_init (GstFrei0rMixerClass * klass, + GstFrei0rMixerClassData * class_data) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstPadTemplate *templ; + GstCaps *caps; + gchar *author; + + klass->ftable = &class_data->ftable; + klass->info = &class_data->info; + + gobject_class->finalize = gst_frei0r_mixer_finalize; + gobject_class->set_property = gst_frei0r_mixer_set_property; + gobject_class->get_property = gst_frei0r_mixer_get_property; + + klass->n_properties = klass->info->num_params; + klass->properties = g_new0 (GstFrei0rProperty, klass->n_properties); + + gst_frei0r_klass_install_properties (gobject_class, klass->ftable, + klass->properties, klass->n_properties); + + author = + g_strdup_printf + ("Sebastian Dröge <sebastian.droege@collabora.co.uk>, %s", + class_data->info.author); + gst_element_class_set_details_simple (gstelement_class, class_data->info.name, + "Filter/Editor/Video", class_data->info.explanation, author); + g_free (author); + + caps = gst_frei0r_caps_from_color_model (class_data->info.color_model); + + templ = + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_caps_ref (caps)); + gst_element_class_add_pad_template (gstelement_class, templ); + + templ = + gst_pad_template_new ("sink_0", GST_PAD_SINK, GST_PAD_ALWAYS, + gst_caps_ref (caps)); + gst_element_class_add_pad_template (gstelement_class, templ); + + templ = + gst_pad_template_new ("sink_1", GST_PAD_SINK, GST_PAD_ALWAYS, + gst_caps_ref (caps)); + gst_element_class_add_pad_template (gstelement_class, templ); + + if (klass->info->plugin_type == F0R_PLUGIN_TYPE_MIXER3) { + templ = + gst_pad_template_new ("sink_2", GST_PAD_SINK, GST_PAD_ALWAYS, + gst_caps_ref (caps)); + gst_element_class_add_pad_template (gstelement_class, templ); + } + gst_caps_unref (caps); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_change_state); +} + +static void +gst_frei0r_mixer_init (GstFrei0rMixer * self, GstFrei0rMixerClass * klass) +{ + self->property_cache = + gst_frei0r_property_cache_init (klass->properties, klass->n_properties); + + self->collect = gst_collect_pads_new (); + gst_collect_pads_set_function (self->collect, + (GstCollectPadsFunction) gst_frei0r_mixer_collected, self); + + self->src = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_CLASS (klass), "src"), "src"); + gst_pad_set_getcaps_function (self->src, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_get_caps)); + gst_pad_set_setcaps_function (self->src, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_set_caps)); + gst_pad_set_query_function (self->src, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_src_query)); + gst_pad_set_event_function (self->src, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_src_event)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->src); + + self->sink0 = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_CLASS (klass), "sink_0"), "sink_0"); + gst_pad_set_getcaps_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_get_caps)); + gst_pad_set_setcaps_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_set_caps)); + gst_pad_set_query_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_sink_query)); + gst_collect_pads_add_pad (self->collect, self->sink0, + sizeof (GstCollectData)); + self->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (self->sink0); + gst_pad_set_event_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_sink0_event)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->sink0); + + self->sink1 = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_CLASS (klass), "sink_1"), "sink_1"); + gst_pad_set_getcaps_function (self->sink1, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_get_caps)); + gst_pad_set_setcaps_function (self->sink1, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_set_caps)); + gst_pad_set_query_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_sink_query)); + gst_collect_pads_add_pad (self->collect, self->sink1, + sizeof (GstCollectData)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->sink1); + + if (klass->info->plugin_type == F0R_PLUGIN_TYPE_MIXER3) { + self->sink2 = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_CLASS (klass), "sink_2"), "sink_2"); + gst_pad_set_getcaps_function (self->sink2, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_get_caps)); + gst_pad_set_setcaps_function (self->sink2, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_set_caps)); + gst_pad_set_query_function (self->sink0, + GST_DEBUG_FUNCPTR (gst_frei0r_mixer_sink_query)); + gst_collect_pads_add_pad (self->collect, self->sink2, + sizeof (GstCollectData)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->sink2); + } + +} + +gboolean +gst_frei0r_mixer_register (GstPlugin * plugin, const f0r_plugin_info_t * info, + const GstFrei0rFuncTable * ftable) +{ + GTypeInfo typeinfo = { + sizeof (GstFrei0rMixerClass), + NULL, + NULL, + (GClassInitFunc) gst_frei0r_mixer_class_init, + NULL, + NULL, + sizeof (GstFrei0rMixer), + 0, + (GInstanceInitFunc) gst_frei0r_mixer_init + }; + GType type; + gchar *type_name, *tmp; + GstFrei0rMixerClassData *class_data; + gboolean ret = FALSE; + + if (ftable->update2 == NULL) + return FALSE; + + tmp = g_strdup_printf ("frei0r-mixer-%s", info->name); + type_name = g_ascii_strdown (tmp, -1); + g_free (tmp); + g_strcanon (type_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-'); + + if (g_type_from_name (type_name)) { + GST_WARNING ("Type '%s' already exists", type_name); + return FALSE; + } + + class_data = g_new0 (GstFrei0rMixerClassData, 1); + memcpy (&class_data->info, info, sizeof (f0r_plugin_info_t)); + memcpy (&class_data->ftable, ftable, sizeof (GstFrei0rFuncTable)); + typeinfo.class_data = class_data; + + type = g_type_register_static (GST_TYPE_ELEMENT, type_name, &typeinfo, 0); + ret = gst_element_register (plugin, type_name, GST_RANK_NONE, type); + + g_free (type_name); + return ret; +} diff --git a/gst/frei0r/gstfrei0rmixer.h b/gst/frei0r/gstfrei0rmixer.h new file mode 100644 index 00000000..548b70ec --- /dev/null +++ b/gst/frei0r/gstfrei0rmixer.h @@ -0,0 +1,75 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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_FREI0R_MIXER_H__ +#define __GST_FREI0R_MIXER_H__ + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/base/gstcollectpads.h> + +#include "frei0r.h" +#include "gstfrei0r.h" + +G_BEGIN_DECLS + +#define GST_FREI0R_MIXER(obj) \ + ((GstFrei0rMixer *) obj) +#define GST_FREI0R_MIXER_CLASS(klass) \ + ((GstFrei0rMixerClass *) klass) +#define GST_FREI0R_MIXER_GET_CLASS(obj) \ + ((GstFrei0rMixerClass *) g_type_class_peek (G_TYPE_FROM_INSTANCE (obj))) + +typedef struct _GstFrei0rMixer GstFrei0rMixer; +typedef struct _GstFrei0rMixerClass GstFrei0rMixerClass; + +struct _GstFrei0rMixer { + GstElement parent; + + GstCollectPads *collect; + GstPad *src; + GstPad *sink0, *sink1, *sink2; + + GstCaps *caps; + GstVideoFormat fmt; + gint width, height; + + GstEvent *newseg_event; + + GstPadEventFunction collect_event; + + f0r_instance_t *f0r_instance; + GstFrei0rPropertyValue *property_cache; +}; + +struct _GstFrei0rMixerClass { + GstElementClass parent; + + f0r_plugin_info_t *info; + GstFrei0rFuncTable *ftable; + + GstFrei0rProperty *properties; + gint n_properties; +}; + +gboolean gst_frei0r_mixer_register (GstPlugin *plugin, const f0r_plugin_info_t *info, const GstFrei0rFuncTable *ftable); + +G_END_DECLS + +#endif /* __GST_FREI0R_MIXER_H__ */ diff --git a/gst/frei0r/gstfrei0rsrc.c b/gst/frei0r/gstfrei0rsrc.c new file mode 100644 index 00000000..a713e1e9 --- /dev/null +++ b/gst/frei0r/gstfrei0rsrc.c @@ -0,0 +1,411 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "gstfrei0r.h" +#include "gstfrei0rsrc.h" + +GST_DEBUG_CATEGORY_EXTERN (frei0r_debug); +#define GST_CAT_DEFAULT frei0r_debug + +typedef struct +{ + f0r_plugin_info_t info; + GstFrei0rFuncTable ftable; +} GstFrei0rSrcClassData; + +static gboolean +gst_frei0r_src_set_caps (GstBaseSrc * src, GstCaps * caps) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (src); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (src); + + if (!gst_video_format_parse_caps (caps, &self->fmt, &self->width, + &self->height) + || !gst_video_parse_caps_framerate (caps, &self->fps_n, &self->fps_d)) + return FALSE; + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + self->f0r_instance = + gst_frei0r_instance_construct (klass->ftable, klass->properties, + klass->n_properties, self->property_cache, self->width, self->height); + + return TRUE; +} + +static GstCaps * +gst_frei0r_src_get_caps (GstBaseSrc * src) +{ + if (GST_PAD_CAPS (GST_BASE_SRC_PAD (src))) + return gst_caps_ref (GST_PAD_CAPS (GST_BASE_SRC_PAD (src))); + else + return + gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src))); +} + +static GstFlowReturn +gst_frei0r_src_create (GstPushSrc * src, GstBuffer ** buf) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (src); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (src); + guint size, newsize; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *outbuf = NULL; + gdouble time; + + *buf = NULL; + + if (G_UNLIKELY (!self->f0r_instance)) + return GST_FLOW_NOT_NEGOTIATED; + + newsize = gst_video_format_get_size (self->fmt, self->width, self->height); + + ret = + gst_pad_alloc_buffer_and_set_caps (GST_BASE_SRC_PAD (src), + GST_BUFFER_OFFSET_NONE, newsize, GST_PAD_CAPS (GST_BASE_SRC_PAD (src)), + &outbuf); + if (ret != GST_FLOW_OK) + return ret; + + /* Format might have changed */ + size = GST_BUFFER_SIZE (outbuf); + newsize = gst_video_format_get_size (self->fmt, self->width, self->height); + + if (size != newsize) { + gst_buffer_unref (outbuf); + outbuf = gst_buffer_new_and_alloc (newsize); + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (GST_BASE_SRC_PAD (src))); + } + + GST_BUFFER_TIMESTAMP (outbuf) = + gst_util_uint64_scale (self->n_frames, GST_SECOND * self->fps_d, + self->fps_n); + GST_BUFFER_OFFSET (outbuf) = self->n_frames; + self->n_frames++; + GST_BUFFER_OFFSET_END (outbuf) = self->n_frames; + GST_BUFFER_DURATION (outbuf) = + gst_util_uint64_scale (self->n_frames, GST_SECOND * self->fps_d, + self->fps_n) - GST_BUFFER_TIMESTAMP (outbuf); + + time = ((gdouble) GST_BUFFER_TIMESTAMP (outbuf)) / GST_SECOND; + + if (klass->ftable->update2) + klass->ftable->update2 (self->f0r_instance, time, NULL, NULL, NULL, + (guint32 *) GST_BUFFER_DATA (outbuf)); + else + klass->ftable->update (self->f0r_instance, time, NULL, + (guint32 *) GST_BUFFER_DATA (outbuf)); + + *buf = outbuf; + + return GST_FLOW_OK; +} + +static gboolean +gst_frei0r_src_start (GstBaseSrc * basesrc) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (basesrc); + + self->n_frames = 0; + + return TRUE; +} + +static gboolean +gst_frei0r_src_stop (GstBaseSrc * basesrc) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (basesrc); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (basesrc); + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + return TRUE; +} + +static gboolean +gst_frei0r_src_is_seekable (GstBaseSrc * psrc) +{ + return TRUE; +} + +static gboolean +gst_frei0r_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) +{ + GstClockTime time; + GstFrei0rSrc *self = GST_FREI0R_SRC (bsrc); + + segment->time = segment->start; + time = segment->last_stop; + + /* now move to the time indicated */ + if (self->fps_n) { + self->n_frames = gst_util_uint64_scale (time, + self->fps_n, self->fps_d * GST_SECOND); + } else { + self->n_frames = 0; + } + + return TRUE; +} + +static gboolean +gst_frei0r_src_query (GstBaseSrc * bsrc, GstQuery * query) +{ + gboolean res; + GstFrei0rSrc *self = GST_FREI0R_SRC (bsrc); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (self); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + if (src_fmt == dest_fmt) { + dest_val = src_val; + goto done; + } + + switch (src_fmt) { + case GST_FORMAT_DEFAULT: + switch (dest_fmt) { + case GST_FORMAT_TIME: + /* frames to time */ + if (self->fps_n) { + dest_val = gst_util_uint64_scale (src_val, + self->fps_d * GST_SECOND, self->fps_n); + } else { + dest_val = 0; + } + break; + default: + goto error; + } + break; + case GST_FORMAT_TIME: + switch (dest_fmt) { + case GST_FORMAT_DEFAULT: + /* time to frames */ + if (self->fps_n) { + dest_val = gst_util_uint64_scale (src_val, + self->fps_n, self->fps_d * GST_SECOND); + } else { + dest_val = 0; + } + break; + default: + goto error; + } + break; + default: + goto error; + } + done: + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + res = TRUE; + break; + } + default: + res = + GST_BASE_SRC_CLASS (g_type_class_peek_parent (klass))->query (bsrc, + query); + } + return res; + + /* ERROR */ +error: + { + GST_DEBUG_OBJECT (self, "query failed"); + return FALSE; + } +} + +static void +gst_frei0r_src_src_fixate (GstPad * pad, GstCaps * caps) +{ + GstStructure *structure; + + structure = gst_caps_get_structure (caps, 0); + + gst_structure_fixate_field_nearest_int (structure, "width", 320); + gst_structure_fixate_field_nearest_int (structure, "height", 240); + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); +} + +static void +gst_frei0r_src_finalize (GObject * object) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (object); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (object); + + if (self->f0r_instance) { + klass->ftable->destruct (self->f0r_instance); + self->f0r_instance = NULL; + } + + if (self->property_cache) + gst_frei0r_property_cache_free (klass->properties, self->property_cache, + klass->n_properties); + self->property_cache = NULL; + + G_OBJECT_CLASS (g_type_class_peek_parent (klass))->finalize (object); +} + +static void +gst_frei0r_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (object); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (object); + + if (!gst_frei0r_get_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gst_frei0r_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFrei0rSrc *self = GST_FREI0R_SRC (object); + GstFrei0rSrcClass *klass = GST_FREI0R_SRC_GET_CLASS (object); + + if (!gst_frei0r_set_property (self->f0r_instance, klass->ftable, + klass->properties, klass->n_properties, self->property_cache, prop_id, + value)) + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gst_frei0r_src_class_init (GstFrei0rSrcClass * klass, + GstFrei0rSrcClassData * class_data) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass; + GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass; + GstPadTemplate *templ; + GstCaps *caps; + gchar *author; + + klass->ftable = &class_data->ftable; + klass->info = &class_data->info; + + gobject_class->finalize = gst_frei0r_src_finalize; + gobject_class->set_property = gst_frei0r_src_set_property; + gobject_class->get_property = gst_frei0r_src_get_property; + + klass->n_properties = klass->info->num_params; + klass->properties = g_new0 (GstFrei0rProperty, klass->n_properties); + + gst_frei0r_klass_install_properties (gobject_class, klass->ftable, + klass->properties, klass->n_properties); + + author = + g_strdup_printf + ("Sebastian Dröge <sebastian.droege@collabora.co.uk>, %s", + class_data->info.author); + gst_element_class_set_details_simple (gstelement_class, class_data->info.name, + "Src/Video", class_data->info.explanation, author); + g_free (author); + + caps = gst_frei0r_caps_from_color_model (class_data->info.color_model); + + templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, caps); + gst_element_class_add_pad_template (gstelement_class, templ); + + gstbasesrc_class->set_caps = gst_frei0r_src_set_caps; + gstbasesrc_class->get_caps = gst_frei0r_src_get_caps; + gstbasesrc_class->is_seekable = gst_frei0r_src_is_seekable; + gstbasesrc_class->do_seek = gst_frei0r_src_do_seek; + gstbasesrc_class->query = gst_frei0r_src_query; + gstbasesrc_class->start = gst_frei0r_src_start; + gstbasesrc_class->stop = gst_frei0r_src_stop; + + gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_frei0r_src_create); +} + +static void +gst_frei0r_src_init (GstFrei0rSrc * self, GstFrei0rSrcClass * klass) +{ + GstPad *pad = GST_BASE_SRC_PAD (self); + + self->property_cache = + gst_frei0r_property_cache_init (klass->properties, klass->n_properties); + + gst_pad_set_fixatecaps_function (pad, gst_frei0r_src_src_fixate); + + gst_base_src_set_format (GST_BASE_SRC_CAST (self), GST_FORMAT_TIME); +} + +gboolean +gst_frei0r_src_register (GstPlugin * plugin, const f0r_plugin_info_t * info, + const GstFrei0rFuncTable * ftable) +{ + GTypeInfo typeinfo = { + sizeof (GstFrei0rSrcClass), + NULL, + NULL, + (GClassInitFunc) gst_frei0r_src_class_init, + NULL, + NULL, + sizeof (GstFrei0rSrc), + 0, + (GInstanceInitFunc) gst_frei0r_src_init + }; + GType type; + gchar *type_name, *tmp; + GstFrei0rSrcClassData *class_data; + gboolean ret = FALSE; + + tmp = g_strdup_printf ("frei0r-src-%s", info->name); + type_name = g_ascii_strdown (tmp, -1); + g_free (tmp); + g_strcanon (type_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-'); + + if (g_type_from_name (type_name)) { + GST_WARNING ("Type '%s' already exists", type_name); + return FALSE; + } + + class_data = g_new0 (GstFrei0rSrcClassData, 1); + memcpy (&class_data->info, info, sizeof (f0r_plugin_info_t)); + memcpy (&class_data->ftable, ftable, sizeof (GstFrei0rFuncTable)); + typeinfo.class_data = class_data; + + type = g_type_register_static (GST_TYPE_PUSH_SRC, type_name, &typeinfo, 0); + ret = gst_element_register (plugin, type_name, GST_RANK_NONE, type); + + g_free (type_name); + return ret; +} diff --git a/gst/frei0r/gstfrei0rsrc.h b/gst/frei0r/gstfrei0rsrc.h new file mode 100644 index 00000000..291a644b --- /dev/null +++ b/gst/frei0r/gstfrei0rsrc.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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_FREI0R_SRC_H__ +#define __GST_FREI0R_SRC_H__ + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/base/gstpushsrc.h> + +#include "frei0r.h" +#include "gstfrei0r.h" + +G_BEGIN_DECLS + +#define GST_FREI0R_SRC(obj) \ + ((GstFrei0rSrc *) obj) +#define GST_FREI0R_SRC_CLASS(klass) \ + ((GstFrei0rSrcClass *) klass) +#define GST_FREI0R_SRC_GET_CLASS(obj) \ + ((GstFrei0rSrcClass *) g_type_class_peek (G_TYPE_FROM_INSTANCE (obj))) + +typedef struct _GstFrei0rSrc GstFrei0rSrc; +typedef struct _GstFrei0rSrcClass GstFrei0rSrcClass; + +struct _GstFrei0rSrc { + GstPushSrc parent; + + f0r_instance_t *f0r_instance; + GstFrei0rPropertyValue *property_cache; + + GstVideoFormat fmt; + gint width, height; + gint fps_n, fps_d; + + guint64 n_frames; +}; + +struct _GstFrei0rSrcClass { + GstPushSrcClass parent; + + f0r_plugin_info_t *info; + GstFrei0rFuncTable *ftable; + + GstFrei0rProperty *properties; + gint n_properties; +}; + +gboolean gst_frei0r_src_register (GstPlugin *plugin, const f0r_plugin_info_t *info, const GstFrei0rFuncTable *ftable); + +G_END_DECLS + +#endif /* __GST_FREI0R_SRC_H__ */ diff --git a/gst/hdvparse/Makefile.am b/gst/hdvparse/Makefile.am index d7eb4d28..a77d5387 100644 --- a/gst/hdvparse/Makefile.am +++ b/gst/hdvparse/Makefile.am @@ -7,7 +7,7 @@ noinst_HEADERS = \ gsthdvparse.h libgsthdvparse_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) -libgsthdvparse_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) +libgsthdvparse_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(LIBM) libgsthdvparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgsthdvparse_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/gst/hdvparse/gsthdvparse.c b/gst/hdvparse/gsthdvparse.c index 9914cfa2..8114f3b8 100644 --- a/gst/hdvparse/gsthdvparse.c +++ b/gst/hdvparse/gsthdvparse.c @@ -35,6 +35,8 @@ #include "config.h" #endif +#include <math.h> + #include <gst/gst.h> #include <gst/base/gstbasetransform.h> @@ -55,90 +57,25 @@ enum PROP_0, }; -static gchar *aperture_table[] = { - "???", - "cls", - "1.0", - "1.2", - "1.4", - "1.6", - "1.7", - "1.8", - "2.0", - "2.2", - "2.4", - "2.6", - "2.8", - "3.1", - "3.4", - "3.7", - "4.0", - "4.4", - "4.8", - "5.2", - "5.6", - "6.2", - "6.8", - "7.3", - "8.0", - "8.7", - "9.6", - "10", - "11", - "12", - "14", - "14", - "16", - "17", - "18", - "6.7" -}; -/* Observations from my HDV Camera (Canon HV20 Pal) - * FIXME : replace with with code once we've figured out the algorithm. - * Shutter speed 0x4f 0x50 - * ------------------------------------ - * 1/6 F3 95 - * 1/8 90 91 - * 1/12 FA 8A - * 1/15 C8 88 - * 1/24 7D 85 - * 1/30 64 84 - * 1/48 BE 82 - * 1/60 32 82 - * 1/100 51 81 - * 1/250 87 80 - * 1/500 43 80 - * 1/1000 22 80 - * 1/2000 11 80 - */ -typedef struct -{ - guint vala, valb, shutter; -} Shutter_t; - -static Shutter_t shutter_table[] = { - {0xf3, 0x95, 6}, - {0x90, 0x91, 8}, - {0xfa, 0x8a, 12}, - {0xc8, 0x88, 15}, - {0x7d, 0x85, 24}, - {0x64, 0x84, 30}, - {0xbe, 0x82, 48}, - {0x32, 0x82, 60}, - {0x51, 0x81, 100}, - {0x87, 0x80, 250}, - {0x43, 0x80, 500}, - {0x22, 0x80, 1000}, - {0x11, 0x80, 2000} -}; + +#define CLOCK_BASE 9LL +#define CLOCK_FREQ (CLOCK_BASE * 10000) + +#define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ + GST_MSECOND/10, CLOCK_BASE)) +#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ + CLOCK_BASE, GST_MSECOND/10)) + +/* If set to 1, then extra validation will be applied to check + * for complete spec compliance wherever applicable. */ +#define VALIDATE 0 /* Binary-coded decimal reading macro */ #define BCD(c) ( ((((c) >> 4) & 0x0f) * 10) + ((c) & 0x0f) ) /* Same as before, but with a mask */ #define BCD_M(c, mask) (BCD ((c) & (mask))) - /* the capabilities of the inputs and outputs. * * describe the real formats here. @@ -146,13 +83,14 @@ static Shutter_t shutter_table[] = { static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("private/hdv-a1") + GST_STATIC_CAPS ("hdv/aux-v;hdv/aux-a") ); static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("private/hdv-a1,parsed=(boolean)True") + GST_STATIC_CAPS + ("hdv/aux-v,parsed=(boolean)True;hdv/aux-a,parsed=(boolean)True") ); /* debug category for fltering log messages @@ -167,6 +105,8 @@ GST_BOILERPLATE_FULL (GstHDVParse, gst_hdvparse, GstBaseTransform, static GstFlowReturn gst_hdvparse_transform_ip (GstBaseTransform * base, GstBuffer * outbuf); +static GstCaps *gst_hdvparse_transform_caps (GstBaseTransform * trans, + GstPadDirection dir, GstCaps * incaps); /* GObject vmethod implementations */ @@ -194,6 +134,8 @@ gst_hdvparse_class_init (GstHDVParseClass * klass) { GST_BASE_TRANSFORM_CLASS (klass)->transform_ip = GST_DEBUG_FUNCPTR (gst_hdvparse_transform_ip); + GST_BASE_TRANSFORM_CLASS (klass)->transform_caps = + GST_DEBUG_FUNCPTR (gst_hdvparse_transform_caps); } /* initialize the new element @@ -208,123 +150,714 @@ gst_hdvparse_init (GstHDVParse * filter, GstHDVParseClass * klass) gst_base_transform_set_passthrough (transform, TRUE); } -static guint -get_shutter_speed (guint8 vala, guint8 valb) +static GstCaps * +gst_hdvparse_transform_caps (GstBaseTransform * trans, GstPadDirection dir, + GstCaps * incaps) { - guint i; + GstCaps *res = NULL; + GstStructure *st = gst_caps_get_structure (incaps, 0); - for (i = 0; i < G_N_ELEMENTS (shutter_table); i++) - if (shutter_table[i].vala == vala && shutter_table[i].valb == valb) - return shutter_table[i].shutter; - GST_WARNING ("Unknown shutter speed ! vala:0x%02x, valb:0x%02x", vala, valb); - return 0; + GST_WARNING_OBJECT (trans, "dir:%d, incaps:%" GST_PTR_FORMAT, dir, incaps); + + if (dir == GST_PAD_SINK) { + res = gst_caps_new_simple (gst_structure_get_name (st), + "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + } else { + res = gst_caps_new_simple (gst_structure_get_name (st), NULL); + } + + return res; } -static void -gst_hdvparse_parse (GstHDVParse * filter, GstBuffer * buf) + +static inline const gchar * +sfr_to_framerate (guint8 sfr) { - guint8 *data = GST_BUFFER_DATA (buf); - guint apertured, shutter; - gfloat gain; - gboolean dst = FALSE; - GstStructure *str; - GstMessage *msg; - - GST_MEMDUMP_OBJECT (filter, "BUFFER", data, GST_BUFFER_SIZE (buf)); - - str = gst_structure_empty_new ("HDV"); - - /* 0x1f - 0x23 : TimeCode */ - - if (data[0x1f] != 0xff) { - guint8 tframe, tsec, tmin, thour; - gchar *timecode = NULL; - tframe = BCD (data[0x1f] & 0x3f); - tsec = BCD (data[0x20] & 0x7f); - tmin = BCD (data[0x21] & 0x7f); - thour = BCD (data[0x22] & 0x3f); - - timecode = - g_strdup_printf ("%01d:%02d:%02d.%02d", thour, tmin, tsec, tframe); - gst_structure_set (str, "timecode", G_TYPE_STRING, timecode, NULL); - g_free (timecode); - GST_LOG_OBJECT (filter, timecode); + switch (sfr) { + case 4: + return "30000/1001"; + case 3: + return "25/1"; + case 1: + return "24000/1001"; + default: + return "RESERVED"; } +} - /* 0x23 : Timezone / Dailight Saving Time */ - /* 0x24 - 0x2a : Original time */ - if (data[0x23] != 0xff) { - GDate *date = NULL; - guint tzone = 0; - guint day, month, year, hour, min, sec; - gchar *datetime; - - tzone = data[0x23]; - dst = !(tzone & 0x80); - tzone = - BCD (tzone & 0x1f) > 12 ? BCD (tzone & 0x1f) - 12 : BCD (tzone & 0x1f); - GST_LOG_OBJECT (filter, "TimeZone : %d, DST : %d", tzone, dst); - - day = BCD_M (data[0x24], 0x3f); - month = BCD_M (data[0x25], 0x1f); - year = BCD (data[0x26]); - if (year > 90) - year += 1900; - else - year += 2000; - /* 0x27: ??? */ - sec = BCD_M (data[0x28], 0x7f); - min = BCD_M (data[0x29], 0x7f); - hour = BCD_M (data[0x2a], 0x3f); - - /* FIXME : we need a date/time object ! */ - date = g_date_new_dmy (day, month, year); - datetime = - g_strdup_printf ("%02d/%02d/%02d %02d:%02d:%02d", day, month, year, - hour, min, sec); - gst_structure_set (str, "date", GST_TYPE_DATE, date, "recording-time", - G_TYPE_STRING, datetime, NULL); - g_free (datetime); - GST_LOG_OBJECT (filter, datetime); +static GstFlowReturn +parse_dv_multi_pack (GstHDVParse * filter, guint8 * data, guint64 size, + GstStructure * st) +{ + guint64 offs = 1; + + while (size / 5) { + GST_LOG ("DV pack 0x%x", data[offs]); + switch (data[offs]) { + case 0x70:{ + guint8 irispos, ae, agc, wbmode, whitebal, focusmode, focuspos; + + irispos = data[offs + 1] & 0x3f; + ae = data[offs + 2] >> 4; + agc = data[offs + 2] & 0xf; + wbmode = data[offs + 3] >> 5; + whitebal = data[offs + 3] & 0x1f; + focusmode = data[offs + 4] >> 7; + focuspos = data[offs + 4] & 0x7f; + + GST_LOG (" Consumer Camera 1"); + + GST_LOG (" Iris position %d (0x%x)", irispos, irispos); + /* Iris position = 2 ^ (IP/8) (for 0 < IP < 0x3C) */ + if (irispos < 0x3c) { + GST_LOG (" IRIS F%0.2f", powf (2.0, (((float) irispos) / 8.0))); + gst_structure_set (st, "aperture-fnumber", G_TYPE_FLOAT, + powf (2.0, (((float) irispos) / 8.0)), NULL); + } else if (irispos == 0x3d) { + GST_LOG (" IRIS < 1.0"); + } else if (irispos == 0x3e) { + GST_LOG (" IRIS closed"); + } + + /* AE Mode: + * 0 : Full automatic + * 1 : Gain Priority mode + * 2 : Shutter Priority mode + * 3 : Iris priority mode + * 4 : Manual + * ..: Reserved + * F : No information */ + GST_LOG (" AE Mode: %d (0x%x)", ae, ae); + + GST_LOG (" AGC: %d (0x%x)", agc, agc); + if (agc < 0xd) { + /* This is what the spec says.. but I'm not seeing the same on my camera :( */ + GST_LOG (" Gain:%02.2fdB", (agc * 3.0) - 3.0); + gst_structure_set (st, "gain", G_TYPE_FLOAT, (agc * 3.0) - 3.0, NULL); + } + /* White balance mode + * 0 : Automatic + * 1 : hold + * 2 : one push + * 3 : pre-set + * 7 : no-information */ + if (wbmode != 7) + GST_LOG (" White balance mode : %d (0x%x)", wbmode, wbmode); + /* White balance + * 0 : Candle + * 1 : Incandescent lamp + * 2 : low color temperature fluorescent lamp + * 3 : high color temperature fluorescent lamp + * 4 : sunlight + * 5 : cloudy weather + * F : No information + */ + if (whitebal != 0xf) + GST_LOG (" White balance : %d (0x%x)", whitebal, whitebal); + if (focuspos != 0x7f) { + GST_LOG (" Focus mode : %s", focusmode ? "MANUAL" : "AUTOMATIC"); + GST_LOG (" Focus position: %d (0x%x)", focuspos, focuspos); + } + } + break; + case 0x71:{ + guint8 v_pan, h_pan, focal_length, e_zoom; + gboolean is, zen; + + v_pan = data[offs + 1] & 0x3f; + is = data[offs + 2] >> 7; + h_pan = data[offs + 2] & 0x7f; + focal_length = data[offs + 3]; + zen = data[offs + 4] >> 7; + e_zoom = data[offs + 4] & 0x7f; + + GST_LOG (" Consumer Camera 2"); + if (v_pan != 0x3f) + GST_LOG (" Vertical Panning : %d (0x%d)", v_pan, v_pan); + if (h_pan != 0x7f) + GST_LOG (" Horizontal Panning : %d (0x%d)", h_pan, h_pan); + GST_LOG (" Stabilizer : %s", is ? "OFF" : "ON"); + if (focal_length != 0xff) + GST_LOG (" Focal Length : %f mm", + (focal_length & 0x7f) * pow (10, focal_length & 0x80)); + if (zen == 0) + GST_LOG (" Electric Zoom %02dd.%03d", e_zoom >> 5, e_zoom & 0x1f); + } + break; + case 0x7f:{ + guint16 speed; + guint16 speedint; + + GST_LOG (" Shutter"); + if (data[offs + 1] != 0xff) + GST_LOG (" Shutter Speed (1) : %d, 0x%x", + data[offs + 1], data[offs + 1]); + if (data[offs + 2] != 0xff) + GST_LOG (" Shutter Speed (1) : %d, 0x%x", + data[offs + 2], data[offs + 2]); + + speed = data[offs + 3] | (data[offs + 4] & 0x7f) << 8; + + /* The shutter speed is 1/(CSS * horizontal scanning period) */ + /* FIXME : 34000 is a value interpolated by observations */ + speedint = (int) (34000.0 / (float) speed); + /* Only the highest two decimal digits are valid */ + if (speedint > 100) + speedint = speedint / 10 * 10; + + GST_LOG (" Shutter speed : 1/%d", speedint); + gst_structure_set (st, "shutter-speed", GST_TYPE_FRACTION, + 1, speedint, NULL); + } + break; + default: + GST_MEMDUMP ("Unknown pack", data + offs, 5); + break; + } + size -= 5; + offs += 5; } + return GST_FLOW_OK; +} - /* 0x2b : Various flags, including scene-change */ - if (!((data[0x2b] & 0x20) >> 5)) { - GST_LOG_OBJECT (filter, "Scene change !"); - gst_structure_set (str, "scene-change", G_TYPE_BOOLEAN, TRUE, NULL); +static GstFlowReturn +parse_video_frame (GstHDVParse * filter, guint8 * data, guint64 size, + GstStructure * st) +{ + guint32 etn, bitrate; + guint8 nbframes, data_h, hdr_size, sfr, sdm; + guint8 aspect, framerate, profile, level, format, chroma; + guint8 gop_n, gop_m, cgms, recst, abst; + guint16 vbv_delay, width, height, vbv_buffer; + guint64 dts; + gboolean pf, tf, rf; + + GST_LOG_OBJECT (filter, "Video Frame Pack"); + + /* Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * --------------------------------- + * 0 | Size (0x39) | + * --------------------------------- + * 1 | | + * 2 | ETN | + * 3 | | + * --------------------------------- + */ + + if (data[0] != 0x39) { + GST_WARNING ("Invalid size for Video frame"); + return GST_FLOW_ERROR; + } + etn = data[3] << 16 | data[2] << 8 | data[1]; + + GST_LOG_OBJECT (filter, " ETN : %" G_GUINT32_FORMAT, etn); + + /* Pack-V Information + * --------------------------------- + * 4 | Number of Video Frames | + * --------------------------------- + * 5 | 0 | 0 | 0 | 0 | DATA-H | + * --------------------------------- + * 6 | VBV | + * 7 | DELAY | + * --------------------------------- + * 8 | HEADER SIZE | + * --------------------------------- + * 9 | | + * 10 | DTS | + * 11 | | + * 12 | | + * ----------------------------- | + * 13 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 14 |PF |TF |RF | 0 | SFR | + * --------------------------------- + */ + + nbframes = data[4]; + + if (VALIDATE && (data[5] >> 4)) + return GST_FLOW_ERROR; + data_h = data[5] & 0xf; + + vbv_delay = data[6] | data[7] << 8; + + hdr_size = data[8]; + + dts = data[9] | data[10] << 8 | data[11] << 16 | data[12] << 24; + dts |= (guint64) (data[13] & 0x1) << 32; + if (G_UNLIKELY (VALIDATE && (data[13] & 0xfe))) { + return GST_FLOW_ERROR; } - /* Check for partials */ - if (GST_BUFFER_SIZE (buf) < 0x50) { - goto beach; + pf = data[14] & 0x80; + tf = data[14] & 0x40; + rf = data[14] & 0x20; + if (G_UNLIKELY (VALIDATE && (data[14] & 0x10))) + return GST_FLOW_ERROR; + + sfr = data[14] & 0x07; + + GST_LOG_OBJECT (filter, " Pack-V Information"); + GST_LOG_OBJECT (filter, " Number of Video Frames : %d", nbframes); + GST_LOG_OBJECT (filter, " Leading PES-V picture type %s (0x%x)", + (data_h == 0x1) ? "I-picture" : "other", data_h); + GST_LOG_OBJECT (filter, " VBV Delay of first frame: %" G_GUINT32_FORMAT, + vbv_delay); + GST_LOG_OBJECT (filter, " Header Size:%d", hdr_size); + GST_LOG_OBJECT (filter, " DTS: %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT ")", + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (dts)), dts); + GST_LOG_OBJECT (filter, " Video source : %s %s %s (0x%x 0x%x 0x%x)", + pf ? "Progressive" : "Interlaced", + tf ? "TFF" : "", rf ? "RFF" : "", pf, tf, rf); + GST_LOG_OBJECT (filter, " Source Frame Rate : %s (0x%x)", + sfr_to_framerate (sfr), sfr); + + gst_structure_set (st, "DTS", G_TYPE_UINT64, MPEGTIME_TO_GSTTIME (dts), + "interlaced", G_TYPE_BOOLEAN, !pf, NULL); + + /* Search Data Mode + * --------------------------------- + * 15 | Search Data Mode | + * --------------------------------- + */ + sdm = data[15]; + GST_LOG_OBJECT (filter, " Search Data Mode : 0x%x", sdm); + GST_LOG_OBJECT (filter, " %s %s %s", + sdm & 0x2 ? "8x-Base" : "", + sdm & 0x4 ? "8x-Helper" : "", sdm & 0x10 ? "24x" : ""); + + /* Video Mode + * --------------------------------- + * 16 | Horizontal size | + * ----------------- | + * 17 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 18 | Vertical size | + * ----------------- | + * 19 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 20 | Aspect ratio | Frame Rate | + * --------------------------------- + * 21 | | + * 22 | bitrate | + * ------------------------- | + * 23 | 0 | 0 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 24 | VBV Buffer size | + * ------------------------- | + * 25 | 0 | 0 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 26 | 0 | Profile | Level | + * --------------------------------- + * 27 | 0 | Format |Chroma | 0 | 0 | + * --------------------------------- + * 28 | GOP N | GOP M | + * --------------------------------- + */ + width = data[16] | (data[17] & 0xf) << 8; + height = data[18] | (data[19] & 0xf) << 8; + if (VALIDATE && ((data[17] & 0xf0) || data[19] & 0xf0)) + return GST_FLOW_ERROR; + aspect = data[20] >> 4; + framerate = data[20] & 0xf; + bitrate = data[21] | data[22] << 8 | (data[23] & 0x3) << 16; + if (VALIDATE && (data[23] & 0xfc)) + return GST_FLOW_ERROR; + vbv_buffer = data[24] | (data[25] & 0x3) << 8; + if (VALIDATE && (data[25] & 0xfc)) + return GST_FLOW_ERROR; + profile = (data[26] >> 4) & 0x7; + level = data[26] & 0xf; + format = (data[27] >> 4) & 0x7; + chroma = (data[27] >> 2) & 0x3; + gop_n = data[28] >> 3; + gop_m = data[28] & 0x7; + + GST_LOG_OBJECT (filter, " Video Mode"); + GST_LOG_OBJECT (filter, " width:%d, height:%d", width, height); + GST_LOG_OBJECT (filter, " Aspect Ratio : %s (0x%x)", + (aspect == 0x3) ? "16/9" : "RESERVED", aspect); + GST_LOG_OBJECT (filter, " Framerate: %s (0x%x)", + sfr_to_framerate (framerate), framerate); + GST_LOG_OBJECT (filter, " Bitrate: %d bit/s", bitrate * 400); + GST_LOG_OBJECT (filter, " VBV buffer Size : %d bits", + vbv_buffer * 16 * 1024); + GST_LOG_OBJECT (filter, " MPEG Profile : %s (0x%x)", + (profile == 0x4) ? "Main" : "RESERVED", profile); + GST_LOG_OBJECT (filter, " MPEG Level : %s (0x%x)", + (level == 0x6) ? "High-1440" : "RESERVED", level); + GST_LOG_OBJECT (filter, " Video format : %s (0x%x)", + (format == 0) ? "Component" : "Reserved", format); + GST_LOG_OBJECT (filter, " Chroma : %s (0x%x)", + (chroma == 0x1) ? "4:2:0" : "RESERVED", chroma); + GST_LOG_OBJECT (filter, " GOP N/M : %d / %d", gop_n, gop_m); + + /* data availability + * --------------------------------- + * 29 | 0 | 0 | 0 | 0 | 0 |PE2|PE1|PE0| + * --------------------------------- + * PE0 : HD2 TTC is valid + * PE1 : REC DATE is valid + * PE2 : REC TIME is valid + */ + if (data[29] & 0x1) { + guint8 fr, sec, min, hr; + gboolean bf, df; + gchar *ttcs; + + /* HD2 TTC + * --------------------------------- + * 30 |BF |DF |Tens Fr|Units of Frames| + * --------------------------------- + * 31 | 1 |Tens second|Units of Second| + * --------------------------------- + * 32 | 1 |Tens minute|Units of Minute| + * --------------------------------- + * 33 | 1 | 1 |Tens Hr|Units of Hours | + * --------------------------------- + */ + bf = data[30] >> 7; + df = (data[30] >> 6) & 0x1; + fr = BCD (data[30] & 0x3f); + sec = BCD (data[31] & 0x7f); + min = BCD (data[32] & 0x7f); + hr = BCD (data[33] & 0x3f); + GST_LOG_OBJECT (filter, " HD2 Title Time Code"); + GST_LOG_OBJECT (filter, " BF:%d, Drop Frame:%d", bf, df); + ttcs = g_strdup_printf ("%02d:%02d:%02d.%02d", hr, min, sec, fr); + GST_LOG_OBJECT (filter, " Timecode %s", ttcs); + /* FIXME : Use framerate information from above to convert to GstClockTime */ + gst_structure_set (st, "title-time-code", G_TYPE_STRING, ttcs, NULL); + g_free (ttcs); + } - /* 0x43 : Aperture */ - apertured = data[0x43] & 0x3f; - if (apertured < 35) { - GST_LOG_OBJECT (filter, "Aperture : F%s", aperture_table[apertured]); - gst_structure_set (str, "aperture", G_TYPE_STRING, - aperture_table[apertured], NULL); - } else { - GST_LOG_OBJECT (filter, "Aperture : %d", apertured); + if (data[29] & 0x2) { + gboolean ds, tm; + guint8 tz, day, dow, month, year; + GDate *date; + + /* REC DATE + * --------------------------------- + * 34 |DS |TM |Tens TZ|Units of TimeZn| + * --------------------------------- + * 35 | 1 | 1 |Tens dy| Units of Days | + * --------------------------------- + * 36 | Week |TMN|Units of Months| + * --------------------------------- + * 37 | Tens of Years |Units of Years | + * --------------------------------- + */ + ds = data[32] >> 7; + tm = (data[32] >> 6) & 0x1; + tz = BCD (data[32] & 0x3f); + day = BCD (data[35] & 0x3f); + dow = data[36] >> 5; + month = BCD (data[36] & 0x1f); + year = BCD (data[37]); + + GST_LOG_OBJECT (filter, " REC DATE"); + GST_LOG_OBJECT (filter, " ds:%d, tm:%d", ds, tm); + GST_LOG_OBJECT (filter, " Timezone: %d", tz); + GST_LOG_OBJECT (filter, " Date: %d %02d/%02d/%04d", dow, day, month, year); + date = g_date_new_dmy (day, month, year); + gst_structure_set (st, "date", GST_TYPE_DATE, date, + "timezone", G_TYPE_INT, tz, + "daylight-saving", G_TYPE_BOOLEAN, ds, NULL); + g_date_free (date); + } + + if (data[29] & 0x4) { + guint8 fr, sec, min, hr; + gchar *times; + + /* REC TIME + * --------------------------------- + * 38 | 1 | 1 |Tens Fr|Units of Frames| + * --------------------------------- + * 39 | 1 |Tens second|Units of Second| + * --------------------------------- + * 40 | 1 |Tens minute|Units of Minute| + * --------------------------------- + * 41 | 1 | 1 |Tens Hr|Units of Hours | + * --------------------------------- + */ + fr = BCD (data[38] & 0x3f); + sec = BCD (data[39] & 0x7f); + min = BCD (data[40] & 0x7f); + hr = BCD (data[41] & 0x3f); + times = g_strdup_printf ("%02d:%02d:%02d", hr, min, sec); + GST_LOG_OBJECT (filter, " REC TIME %02d:%02d:%02d.%02d", hr, min, sec, fr); + gst_structure_set (st, "time", G_TYPE_STRING, times, NULL); + g_free (times); + } + + /* MISC + * --------------------------------- + * 42 | CGMS |REC|ABS| 0 | 0 | 0 | 0 | + * --------------------------------- + */ + cgms = data[42] >> 6; + recst = (data[42] >> 5) & 0x1; + abst = (data[42] >> 4) & 0x1; + + GST_LOG_OBJECT (filter, " CGMS:0x%x", cgms); + GST_LOG_OBJECT (filter, " Recording Start Point : %s", + (recst == 0) ? "PRESENT" : "ABSENT"); + GST_LOG_OBJECT (filter, " ABST : %s", + (abst == 0) ? "DISCONTINUITY" : "NO DISCONTINUITY"); + + gst_structure_set (st, "recording-start-point", G_TYPE_BOOLEAN, !recst, NULL); + + /* Extended DV Pack #1 + * 43 - 47 + */ + GST_LOG_OBJECT (filter, " Extended DV Pack #1 : 0x%x", data[43]); + + /* Extended DV Pack #1 + * 48 - 52 + */ + GST_LOG_OBJECT (filter, " Extended DV Pack #2 : 0x%x", data[48]); + + /* Extended DV Pack #1 + * 53 - 57 + */ + GST_LOG_OBJECT (filter, " Extended DV Pack #3 : 0x%x", data[53]); + + return GST_FLOW_OK; + +} + +static GstFlowReturn +parse_audio_frame (GstHDVParse * filter, guint8 * data, guint64 size, + GstStructure * st) +{ + guint32 etn; + guint8 nbmute, nbaau; + guint64 pts; + guint16 audio_comp; + guint8 bitrate, fs, compress, channel; + guint8 option, cgms; + gboolean acly, recst; + + GST_LOG_OBJECT (filter, "Audio Frame Pack"); + + /* Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * --------------------------------- + * 0 | Size (0x0f) | + * --------------------------------- + * 1 | | + * 2 | ETN | + * 3 | | + * --------------------------------- + * 4 |Nb Audio Mute | Number of AAU | + * --------------------------------- + */ + + if (data[0] != 0x0f) { + GST_WARNING ("Invalid size for audio frame"); + return GST_FLOW_ERROR; + } + etn = data[3] << 16 | data[2] << 8 | data[1]; + + GST_LOG_OBJECT (filter, " ETN : %" G_GUINT32_FORMAT, etn); + + /* Pack-A Information + * --------------------------------- + * 4 |Nb Audio Mute | Number of AAU | + * --------------------------------- + * 5 | | + * 6 | PTS | + * 7 | | + * 8 | | + * ----------------------------- | + * 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | + * --------------------------------- + * 10 | Audio | + * 11 | Compensation | + * --------------------------------- + */ + + /* Number of Audio Mute Frames */ + nbmute = data[4] >> 4; + /* Number of AAU */ + nbaau = data[4] & 0x0f; + /* PTS of the first AAU immediatly following */ + pts = (data[5] | data[6] << 8 | data[7] << 16 | data[8] << 24); + pts |= (guint64) (data[9] & 0x1) << 32; + if (G_UNLIKELY (VALIDATE && (data[9] & 0xfe))) { + return GST_FLOW_ERROR; } - /* 0x44 : Gain */ - gain = ((data[0x44] & 0xf) - 1) * 1.5; - GST_LOG_OBJECT (filter, "Gain : %03f db", gain); - gst_structure_set (str, "gain", G_TYPE_FLOAT, gain, NULL); + /* Amount of compensation */ + audio_comp = data[10] | data[11] << 8; + + GST_LOG_OBJECT (filter, " Pack-A Information"); + GST_LOG_OBJECT (filter, " Nb Audio Mute Frames : %d", nbmute); + GST_LOG_OBJECT (filter, " Nb AAU : %d", nbaau); + GST_LOG_OBJECT (filter, + " PTS : %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT ")", + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pts)), pts); + GST_LOG_OBJECT (filter, " Audio Compensation : %" G_GUINT32_FORMAT, + audio_comp); + + /* Audio Mode + * --------------------------------- + * 12 | Bitrate Index | 0 |Samplerate | + * --------------------------------- + * 13 | Compression | Channels | + * --------------------------------- + * 14 | X | Anciliary Option | + * --------------------------------- + * + * X : Anciliary data present + */ + + bitrate = data[12] >> 4; + fs = data[12] & 0x7; + if (G_UNLIKELY (VALIDATE && (data[12] & 0x08))) + return GST_FLOW_ERROR; + + compress = data[13] >> 4; + channel = data[13] & 0xf; + acly = data[14] & 0x80; + option = data[14] & 0x7f; + + GST_LOG_OBJECT (filter, " Audio Mode"); + GST_LOG_OBJECT (filter, " Bitrate : %s (0x%x)", + (bitrate == 0xe) ? "384kbps" : "RESERVED", bitrate); + GST_LOG_OBJECT (filter, " Samplerate : %s (0x%x)", + (fs == 0x1) ? "48 kHz" : "RESERVED", fs); + GST_LOG_OBJECT (filter, " Compression : %s (0x%x)", + (compress == 0x2) ? "MPEG-1 Layer II" : "RESERVED", compress); + GST_LOG_OBJECT (filter, " Channels : %s (0x%x)", + (channel == 0) ? "Stereo" : "RESERVED", channel); + GST_LOG_OBJECT (filter, " Anciliary data %s %s (0x%x)", + acly ? "PRESENT" : "ABSENT", + (option == 0xc) ? "IEC 13818-3" : "ABSENT/RESERVED", option); + /* + * --------------------------------- + * 15 | CGMS | R | 0 | 0 | 0 | 0 | 0 | + * --------------------------------- + * + * R : Recording Start Point + */ + + cgms = data[15] & 0xc0; + recst = data[15] & 0x20; + + GST_LOG_OBJECT (filter, " Misc"); + GST_LOG_OBJECT (filter, " CGMS : 0x%x", cgms); + GST_LOG_OBJECT (filter, " Recording Start Point %s", + (recst) ? "ABSENT" : "PRESENT"); + + gst_structure_set (st, "PTS", G_TYPE_UINT64, MPEGTIME_TO_GSTTIME (pts), + "recording-start-point", G_TYPE_BOOLEAN, !recst, NULL); - /* 0x4f - 0x50 : Shutter */ - shutter = get_shutter_speed (data[0x4f], data[0x50]); - GST_LOG_OBJECT (filter, "Shutter speed : 1/%d", shutter); - if (shutter) - gst_structure_set (str, "shutter-speed", GST_TYPE_FRACTION, 1, shutter, - NULL); + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_hdvparse_parse (GstHDVParse * filter, GstBuffer * buf) +{ + GstFlowReturn res = GST_FLOW_OK; + guint8 *data = GST_BUFFER_DATA (buf); + guint64 offs = 0; + guint64 insize = GST_BUFFER_SIZE (buf); + GstStructure *st; + + /* Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * --------------------------------- + * 0 | 0 | KEYWORD | + * (1) | LENGTH | + * .... + * + * KEYWORD : + * 0x00 - 0x3F : Constant length (5 bytes) + * 0x40 - 0x7F : Variable length (LENGTH + 1) + * + * LENGTH : if present, size of fields 1-N + * + * Known keyword values: + * 0x00-0x07 : AUX-V + * 0x08-0x3E : RESERVED + * 0x3F : AUX-N NO-INFO + * 0x40-0x43 : AUX-A + * 0x44-0x47 : AUX-V + * 0x48-0x4F : AUX-N + * 0x50-0x53 : AUX-SYS + * 0x54-0x7E : RESERVED + * 0x7F : AUX-N NULL PACK + */ + + st = gst_structure_empty_new ("hdv-aux"); + + while (res == GST_FLOW_OK && (offs < insize)) { + guint8 kw = data[offs] & 0x7f; + guint8 size; + + /* Variable size packs */ + if (kw >= 0x40) { + size = data[offs + 1]; + } else + size = 4; + + /* Size validation */ + GST_DEBUG ("kw:0x%x, insize:%" G_GUINT64_FORMAT ", offs:%" G_GUINT64_FORMAT + ", size:%d", kw, insize, offs, size); + if (insize < offs + size) { + res = GST_FLOW_ERROR; + goto beach; + } + + switch (kw) { + case 0x01: + GST_LOG ("BINARY GROUP"); + offs += size + 1; + break; + case 0x07: + GST_LOG ("ETN pack"); + break; + case 0x40: + GST_LOG ("Audio frame pack"); + res = parse_audio_frame (filter, data + offs + 1, size, st); + offs += size + 2; + break; + case 0x3f: + GST_LOG ("NO INFO pack"); + offs += size + 1; + break; + case 0x44: + GST_LOG ("Video frame pack"); + res = parse_video_frame (filter, data + offs + 1, size, st); + offs += size + 2; + break; + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + GST_LOG ("DV multi-pack"); + res = parse_dv_multi_pack (filter, data + offs + 1, size, st); + offs += size + 2; + break; + default: + GST_WARNING_OBJECT (filter, "Unknown AUX pack data of type 0x%x", kw); + res = GST_FLOW_ERROR; + } + } beach: - msg = gst_message_new_element (GST_OBJECT (filter), str); - gst_element_post_message (GST_ELEMENT (filter), msg); - return; + if (gst_structure_n_fields (st)) { + GstMessage *msg; + /* Emit the element message */ + msg = gst_message_new_element (GST_OBJECT (filter), st); + gst_element_post_message (GST_ELEMENT (filter), msg); + } else + gst_structure_free (st); + + return res; + } /* GstBaseTransform vmethod implementations */ @@ -334,9 +867,7 @@ gst_hdvparse_transform_ip (GstBaseTransform * base, GstBuffer * outbuf) { GstHDVParse *filter = GST_HDVPARSE (base); - gst_hdvparse_parse (filter, outbuf); - - return GST_FLOW_OK; + return gst_hdvparse_parse (filter, outbuf); } diff --git a/gst/id3tag/Makefile.am b/gst/id3tag/Makefile.am index 9595be0f..108a227b 100644 --- a/gst/id3tag/Makefile.am +++ b/gst/id3tag/Makefile.am @@ -1,9 +1,9 @@ plugin_LTLIBRARIES = libgstid3tag.la libgstid3tag_la_SOURCES = \ + gstid3mux.c \ gsttagmux.c \ - id3tag.c \ - gstid3tag.c + id3tag.c libgstid3tag_la_CFLAGS = \ $(GST_PLUGINS_BASE_CFLAGS) \ @@ -16,4 +16,4 @@ libgstid3tag_la_LIBADD = \ libgstid3tag_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstid3tag_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstid3tag.h id3tag.h gsttagmux.h +noinst_HEADERS = gstid3mux.h gsttagmux.h id3tag.h diff --git a/gst/id3tag/gstid3tag.c b/gst/id3tag/gstid3mux.c index 9c8072c0..b13bfb58 100644 --- a/gst/id3tag/gstid3tag.c +++ b/gst/id3tag/gstid3mux.c @@ -21,12 +21,12 @@ */ /** - * SECTION:element-id3tag + * SECTION:element-id3mux * @see_also: #GstID3Demux, #GstTagSetter * * This element adds ID3v2 tags to the beginning of a stream, and ID3v1 tags * to the end. - * + * * It defaults to writing ID3 version 2.3.0 tags (since those are the most * widely supported), but can optionally write version 2.4.0 tags. * @@ -37,7 +37,7 @@ * <refsect2> * <title>Example pipelines</title> * |[ - * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3tag ! filesink location=foo.mp3 + * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3mux ! filesink location=foo.mp3 * ]| A pipeline that transcodes a file from Ogg/Vorbis to mp3 format with * ID3 tags that contain the same metadata as the the Ogg/Vorbis file. * Make sure the Ogg/Vorbis file actually has comments to preserve. @@ -51,13 +51,13 @@ #include <config.h> #endif -#include "gstid3tag.h" +#include "gstid3mux.h" #include <gst/tag/tag.h> #include <string.h> -GST_DEBUG_CATEGORY (gst_id3_tag_debug); -#define GST_CAT_DEFAULT gst_id3_tag_debug +GST_DEBUG_CATEGORY (gst_id3_mux_debug); +#define GST_CAT_DEFAULT gst_id3_mux_debug enum { @@ -67,7 +67,7 @@ enum ARG_V2_MAJOR_VERSION }; -#define DEFAULT_WRITE_V1 TRUE +#define DEFAULT_WRITE_V1 FALSE #define DEFAULT_WRITE_V2 TRUE #define DEFAULT_V2_MAJOR_VERSION 3 @@ -76,20 +76,20 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-id3")); -GST_BOILERPLATE (GstId3Tag, gst_id3_tag, GstTagMux, GST_TYPE_TAG_MUX); +GST_BOILERPLATE (GstId3Mux, gst_id3_mux, GstTagMux, GST_TYPE_TAG_MUX); -static GstBuffer *gst_id3_tag_render_v2_tag (GstTagMux * mux, +static GstBuffer *gst_id3_mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist); -static GstBuffer *gst_id3_tag_render_v1_tag (GstTagMux * mux, +static GstBuffer *gst_id3_mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist); -static void gst_id3_tag_set_property (GObject * object, guint prop_id, +static void gst_id3_mux_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_id3_tag_get_property (GObject * object, guint prop_id, +static void gst_id3_mux_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void -gst_id3_tag_base_init (gpointer g_class) +gst_id3_mux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); @@ -101,18 +101,15 @@ gst_id3_tag_base_init (gpointer g_class) "Adds an ID3v2 header and ID3v1 footer to a file", "Michael Smith <msmith@songbirdnest.com>, " "Tim-Philipp Müller <tim centricular net>"); - - GST_DEBUG_CATEGORY_INIT (gst_id3_tag_debug, "id3tag", 0, - "ID3 v1 and v2 tag muxer"); } static void -gst_id3_tag_class_init (GstId3TagClass * klass) +gst_id3_mux_class_init (GstId3MuxClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; - gobject_class->set_property = gst_id3_tag_set_property; - gobject_class->get_property = gst_id3_tag_get_property; + gobject_class->set_property = gst_id3_mux_set_property; + gobject_class->get_property = gst_id3_mux_get_property; g_object_class_install_property (gobject_class, ARG_WRITE_V1, g_param_spec_boolean ("write-v1", "Write id3v1 tag", @@ -131,13 +128,13 @@ gst_id3_tag_class_init (GstId3TagClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); GST_TAG_MUX_CLASS (klass)->render_start_tag = - GST_DEBUG_FUNCPTR (gst_id3_tag_render_v2_tag); + GST_DEBUG_FUNCPTR (gst_id3_mux_render_v2_tag); - GST_TAG_MUX_CLASS (klass)->render_end_tag = gst_id3_tag_render_v1_tag; + GST_TAG_MUX_CLASS (klass)->render_end_tag = gst_id3_mux_render_v1_tag; } static void -gst_id3_tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) +gst_id3_mux_init (GstId3Mux * id3mux, GstId3MuxClass * id3mux_class) { id3mux->write_v1 = DEFAULT_WRITE_V1; id3mux->write_v2 = DEFAULT_WRITE_V2; @@ -146,10 +143,10 @@ gst_id3_tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) } static void -gst_id3_tag_set_property (GObject * object, guint prop_id, +gst_id3_mux_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstId3Tag *mux = GST_ID3TAG (object); + GstId3Mux *mux = GST_ID3_MUX (object); switch (prop_id) { case ARG_WRITE_V1: @@ -168,10 +165,10 @@ gst_id3_tag_set_property (GObject * object, guint prop_id, } static void -gst_id3_tag_get_property (GObject * object, guint prop_id, +gst_id3_mux_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { - GstId3Tag *mux = GST_ID3TAG (object); + GstId3Mux *mux = GST_ID3_MUX (object); switch (prop_id) { case ARG_WRITE_V1: @@ -190,23 +187,23 @@ gst_id3_tag_get_property (GObject * object, guint prop_id, } static GstBuffer * -gst_id3_tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) +gst_id3_mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist) { - GstId3Tag *id3mux = GST_ID3TAG (mux); + GstId3Mux *id3mux = GST_ID3_MUX (mux); if (id3mux->write_v2) - return gst_id3mux_render_v2_tag (mux, taglist, id3mux->v2_major_version); + return id3_mux_render_v2_tag (mux, taglist, id3mux->v2_major_version); else return NULL; } static GstBuffer * -gst_id3_tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) +gst_id3_mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist) { - GstId3Tag *id3mux = GST_ID3TAG (mux); + GstId3Mux *id3mux = GST_ID3_MUX (mux); if (id3mux->write_v1) - return gst_id3mux_render_v1_tag (mux, taglist); + return id3_mux_render_v1_tag (mux, taglist); else return NULL; } @@ -214,7 +211,10 @@ gst_id3_tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) static gboolean plugin_init (GstPlugin * plugin) { - if (!gst_element_register (plugin, "id3tag", GST_RANK_NONE, GST_TYPE_ID3TAG)) + GST_DEBUG_CATEGORY_INIT (gst_id3_mux_debug, "id3mux", 0, + "ID3 v1 and v2 tag muxer"); + + if (!gst_element_register (plugin, "id3mux", GST_RANK_NONE, GST_TYPE_ID3_MUX)) return FALSE; gst_tag_register_musicbrainz_tags (); diff --git a/gst/id3tag/gstid3tag.h b/gst/id3tag/gstid3mux.h index a9a1ad1c..eb5aa050 100644 --- a/gst/id3tag/gstid3tag.h +++ b/gst/id3tag/gstid3mux.h @@ -20,18 +20,18 @@ * Boston, MA 02111-1307, USA. */ -#ifndef GST_ID3TAG_H -#define GST_ID3TAG_H +#ifndef GST_ID3_MUX_H +#define GST_ID3_MUX_H #include "gsttagmux.h" #include "id3tag.h" G_BEGIN_DECLS -typedef struct _GstId3Tag GstId3Tag; -typedef struct _GstId3TagClass GstId3TagClass; +typedef struct _GstId3Mux GstId3Mux; +typedef struct _GstId3MuxClass GstId3MuxClass; -struct _GstId3Tag { +struct _GstId3Mux { GstTagMux tagmux; gboolean write_v1; @@ -40,24 +40,24 @@ struct _GstId3Tag { gint v2_major_version; }; -struct _GstId3TagClass { +struct _GstId3MuxClass { GstTagMuxClass tagmux_class; }; -#define GST_TYPE_ID3TAG \ - (gst_id3_tag_get_type()) -#define GST_ID3TAG(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3TAG,GstId3Tag)) -#define GST_ID3TAG_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3TAG,GstId3TagClass)) -#define GST_IS_ID3TAG(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3TAG)) -#define GST_IS_ID3TAG_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3TAG)) +#define GST_TYPE_ID3_MUX \ + (gst_id3_mux_get_type()) +#define GST_ID3_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3_MUX,GstId3Mux)) +#define GST_ID3_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3_MUX,GstId3MuxClass)) +#define GST_IS_ID3_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3_MUX)) +#define GST_IS_ID3_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3_MUX)) -GType gst_id3_tag_get_type (void); +GType gst_id3_mux_get_type (void); G_END_DECLS -#endif /* GST_ID3TAG_H */ +#endif /* GST_ID3_MUX_H */ diff --git a/gst/id3tag/gsttagmux.c b/gst/id3tag/gsttagmux.c index 3b7ff119..4aafb96d 100644 --- a/gst/id3tag/gsttagmux.c +++ b/gst/id3tag/gsttagmux.c @@ -57,10 +57,10 @@ gst_tag_mux_iface_init (GType tag_type) /* make sure to register a less generic type so we can easily move this * GstTagMux base class into -base without causing GType name conflicts */ -typedef GstTagMux GstId3TagMux; -typedef GstTagMuxClass GstId3TagMuxClass; +typedef GstTagMux GstId3BaseMux; +typedef GstTagMuxClass GstId3BaseMuxClass; -GST_BOILERPLATE_FULL (GstId3TagMux, gst_tag_mux, +GST_BOILERPLATE_FULL (GstId3BaseMux, gst_tag_mux, GstElement, GST_TYPE_ELEMENT, gst_tag_mux_iface_init); @@ -100,8 +100,8 @@ gst_tag_mux_base_init (gpointer g_class) gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_tag_mux_sink_template)); - GST_DEBUG_CATEGORY_INIT (gst_tag_mux_debug, "tagmux", 0, - "tag muxer base class"); + GST_DEBUG_CATEGORY_INIT (gst_tag_mux_debug, "id3basemux", 0, + "tag muxer base class for Id3Mux"); } static void diff --git a/gst/id3tag/id3tag.c b/gst/id3tag/id3tag.c index a39e2a8e..dd7bb04f 100644 --- a/gst/id3tag/id3tag.c +++ b/gst/id3tag/id3tag.c @@ -25,8 +25,8 @@ #include <gst/tag/tag.h> -GST_DEBUG_CATEGORY_EXTERN (gst_id3_tag_debug); -#define GST_CAT_DEFAULT gst_id3_tag_debug +GST_DEBUG_CATEGORY_EXTERN (gst_id3_mux_debug); +#define GST_CAT_DEFAULT gst_id3_mux_debug #define ID3V2_APIC_PICTURE_OTHER 0 #define ID3V2_APIC_PICTURE_FILE_ICON 1 @@ -1024,7 +1024,7 @@ foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata) } GstBuffer * -gst_id3mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, int version) +id3_mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, int version) { GstId3v2Tag tag; GstBuffer *buf; @@ -1062,17 +1062,17 @@ gst_id3mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, int version) #define ID3_V1_TAG_SIZE 128 typedef void (*GstId3v1WriteFunc) (const GstTagList * list, - const gchar * gst_tag, guint8 * dst, int len); + const gchar * gst_tag, guint8 * dst, int len, gboolean * wrote_tag); static void latin1_convert (const GstTagList * list, const gchar * tag, - guint8 * dst, int maxlen) + guint8 * dst, int maxlen, gboolean * wrote_tag) { gchar *str; gsize len; gchar *latin1; - if (!gst_tag_list_get_string (list, tag, &str)) + if (!gst_tag_list_get_string (list, tag, &str) || str == NULL) return; /* Convert to Latin-1 (ISO-8859-1), replacing unrepresentable characters @@ -1080,9 +1080,10 @@ latin1_convert (const GstTagList * list, const gchar * tag, latin1 = g_convert_with_fallback (str, -1, "ISO-8859-1", "UTF-8", "?", NULL, &len, NULL); - if (latin1) { + if (latin1 != NULL && *latin1 != '\0') { len = MIN (len, maxlen); memcpy (dst, latin1, len); + *wrote_tag = TRUE; g_free (latin1); } @@ -1091,7 +1092,7 @@ latin1_convert (const GstTagList * list, const gchar * tag, static void date_v1_convert (const GstTagList * list, const gchar * tag, - guint8 * dst, int maxlen) + guint8 * dst, int maxlen, gboolean * wrote_tag) { GDate *date; @@ -1102,6 +1103,7 @@ date_v1_convert (const GstTagList * list, const gchar * tag, if (year > 500 && year < 2100) { gchar str[5]; g_snprintf (str, 5, "%.4u", year); + *wrote_tag = TRUE; memcpy (dst, str, 4); } else { GST_WARNING ("invalid year %u, skipping", year); @@ -1113,14 +1115,14 @@ date_v1_convert (const GstTagList * list, const gchar * tag, static void genre_v1_convert (const GstTagList * list, const gchar * tag, - guint8 * dst, int maxlen) + guint8 * dst, int maxlen, gboolean * wrote_tag) { gchar *str; int genreidx = -1; guint i, max; /* We only support one genre */ - if (!gst_tag_list_get_string_index (list, tag, 0, &str)) + if (!gst_tag_list_get_string_index (list, tag, 0, &str) || str == NULL) return; max = gst_tag_id3_genre_count (); @@ -1133,15 +1135,17 @@ genre_v1_convert (const GstTagList * list, const gchar * tag, } } - if (genreidx >= 0 && genreidx <= 127) + if (genreidx >= 0 && genreidx <= 127) { *dst = (guint8) genreidx; + *wrote_tag = TRUE; + } g_free (str); } static void track_number_convert (const GstTagList * list, const gchar * tag, - guint8 * dst, int maxlen) + guint8 * dst, int maxlen, gboolean * wrote_tag) { guint tracknum; @@ -1149,8 +1153,10 @@ track_number_convert (const GstTagList * list, const gchar * tag, if (!gst_tag_list_get_uint_index (list, tag, 0, &tracknum)) return; - if (tracknum <= 127) + if (tracknum <= 127) { *dst = (guint8) tracknum; + *wrote_tag = TRUE; + } } static const struct @@ -1172,10 +1178,11 @@ static const struct }; GstBuffer * -gst_id3mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist) +id3_mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist) { GstBuffer *buf = gst_buffer_new_and_alloc (ID3_V1_TAG_SIZE); guint8 *data = GST_BUFFER_DATA (buf); + gboolean wrote_tag = FALSE; int i; memset (data, 0, ID3_V1_TAG_SIZE); @@ -1184,9 +1191,18 @@ gst_id3mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist) data[1] = 'A'; data[2] = 'G'; + /* Genre #0 stands for 'Blues', so init genre field to an invalid number */ + data[127] = 255; + for (i = 0; i < G_N_ELEMENTS (v1_funcs); i++) { v1_funcs[i].func (taglist, v1_funcs[i].gst_tag, data + v1_funcs[i].offset, - v1_funcs[i].length); + v1_funcs[i].length, &wrote_tag); + } + + if (!wrote_tag) { + GST_WARNING_OBJECT (mux, "no ID3v1 tag written (no suitable tags found)"); + gst_buffer_unref (buf); + return NULL; } gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad)); diff --git a/gst/id3tag/id3tag.h b/gst/id3tag/id3tag.h index 1fb59376..d5504969 100644 --- a/gst/id3tag/id3tag.h +++ b/gst/id3tag/id3tag.h @@ -24,9 +24,9 @@ G_BEGIN_DECLS #define ID3_VERSION_2_3 3 #define ID3_VERSION_2_4 4 -GstBuffer * gst_id3mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, +GstBuffer * id3_mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, int version); -GstBuffer * gst_id3mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist); +GstBuffer * id3_mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist); G_END_DECLS diff --git a/gst/liveadder/liveadder.c b/gst/liveadder/liveadder.c index d10f54f0..25fa4063 100644 --- a/gst/liveadder/liveadder.c +++ b/gst/liveadder/liveadder.c @@ -1349,9 +1349,8 @@ no_clock: pause: { - const gchar *reason = gst_flow_get_name (result); - - GST_DEBUG_OBJECT (adder, "pausing task, reason %s", reason); + GST_DEBUG_OBJECT (adder, "pausing task, reason %s", + gst_flow_get_name (result)); GST_OBJECT_LOCK (adder); diff --git a/gst/mpegdemux/gstmpegdefs.h b/gst/mpegdemux/gstmpegdefs.h index 7ad1e25c..375f1dc0 100644 --- a/gst/mpegdemux/gstmpegdefs.h +++ b/gst/mpegdemux/gstmpegdefs.h @@ -170,12 +170,17 @@ #define ST_PS_AUDIO_AC3 0x81 #define ST_PS_AUDIO_DTS 0x8a #define ST_PS_AUDIO_LPCM 0x8b -#define ST_HDV_PRIVATE_A0 0xa0 -#define ST_HDV_PRIVATE_A1 0xa1 #define ST_PS_DVD_SUBPICTURE 0xff /* Blu-ray PGS subpictures */ #define ST_BD_PGS_SUBPICTURE 0x90 +/* HDV AUX stream mapping + * 0xA0 ISO/IEC 61834-11 + * 0xA1 ISO/IEC 61834-11 + */ +#define ST_HDV_AUX_A 0xa0 +#define ST_HDV_AUX_V 0xa1 + /* Un-official time-code stream */ #define ST_PS_TIMECODE 0xd2 diff --git a/gst/mpegdemux/gstmpegdesc.c b/gst/mpegdemux/gstmpegdesc.c index 4c88e1fd..0a985897 100644 --- a/gst/mpegdemux/gstmpegdesc.c +++ b/gst/mpegdemux/gstmpegdesc.c @@ -65,7 +65,9 @@ gst_mpeg_descriptor_parse_1 (guint8 * data, guint size) if (length > size) return 0; - return length + 2;; + GST_MEMDUMP ("tag contents:", data, length); + + return length + 2; } GstMPEGDescriptor * diff --git a/gst/mpegdemux/gstmpegtsdemux.c b/gst/mpegdemux/gstmpegtsdemux.c index b75bfc6d..cb082a2b 100644 --- a/gst/mpegdemux/gstmpegtsdemux.c +++ b/gst/mpegdemux/gstmpegtsdemux.c @@ -634,15 +634,15 @@ gst_mpegts_demux_fill_stream (GstMpegTSStream * stream, guint8 id, caps = gst_caps_new_simple ("private/teletext", NULL); } break; - case ST_HDV_PRIVATE_A0: + case ST_HDV_AUX_V: template = klass->private_template; name = g_strdup_printf ("private_%04x", stream->PID); - caps = gst_caps_new_simple ("private/hdv-a0", NULL); + caps = gst_caps_new_simple ("hdv/aux-v", NULL); break; - case ST_HDV_PRIVATE_A1: + case ST_HDV_AUX_A: template = klass->private_template; name = g_strdup_printf ("private_%04x", stream->PID); - caps = gst_caps_new_simple ("private/hdv-a1", NULL); + caps = gst_caps_new_simple ("hdv/aux-a", NULL); break; case ST_PRIVATE_SECTIONS: case ST_MHEG: @@ -923,8 +923,8 @@ gst_mpegts_demux_data_cb (GstPESFilter * filter, gboolean first, * to drop. */ if (stream->PMT_pid <= MPEGTS_MAX_PID && demux->streams[stream->PMT_pid] && demux->streams[demux->streams[stream->PMT_pid]->PMT.PCR_PID] - && demux->streams[demux->streams[stream->PMT_pid]->PMT. - PCR_PID]->discont_PCR) { + && demux->streams[demux->streams[stream->PMT_pid]->PMT.PCR_PID]-> + discont_PCR) { GST_WARNING_OBJECT (demux, "middle of discont, dropping"); goto bad_timestamp; } @@ -946,8 +946,8 @@ gst_mpegts_demux_data_cb (GstPESFilter * filter, gboolean first, */ if (stream->PMT_pid <= MPEGTS_MAX_PID && demux->streams[stream->PMT_pid] && demux->streams[demux->streams[stream->PMT_pid]->PMT.PCR_PID] - && demux->streams[demux->streams[stream->PMT_pid]->PMT. - PCR_PID]->last_PCR > 0) { + && demux->streams[demux->streams[stream->PMT_pid]->PMT.PCR_PID]-> + last_PCR > 0) { GST_DEBUG_OBJECT (demux, "timestamps wrapped before noticed in PCR"); time = MPEGTIME_TO_GSTTIME (pts) + stream->base_time + MPEGTIME_TO_GSTTIME ((guint64) (1) << 33); @@ -2884,7 +2884,8 @@ gst_mpegts_demux_chain (GstPad * pad, GstBuffer * buffer) /* process all packets */ for (i = 0; i < sync_count; i++) { ret = gst_mpegts_demux_parse_transport_packet (demux, demux->sync_lut[i]); - if (G_UNLIKELY (ret == GST_FLOW_LOST_SYNC)) { + if (G_UNLIKELY (ret == GST_FLOW_LOST_SYNC + || ret == GST_FLOW_NEED_MORE_DATA)) { ret = GST_FLOW_OK; continue; } diff --git a/gst/mpegtsmux/mpegtsmux.c b/gst/mpegtsmux/mpegtsmux.c index f24c4a3e..edda2600 100644 --- a/gst/mpegtsmux/mpegtsmux.c +++ b/gst/mpegtsmux/mpegtsmux.c @@ -523,6 +523,8 @@ mpegtsmux_choose_best_stream (MpegTsMux * mux) return best; } +#define COLLECT_DATA_PAD(collect_data) (((GstCollectData *)(collect_data))->pad) + static GstFlowReturn mpegtsmux_collected (GstCollectPads * pads, MpegTsMux * mux) { @@ -540,10 +542,8 @@ mpegtsmux_collected (GstCollectPads * pads, MpegTsMux * mux) if (mux->pcr_stream == NULL) { if (best) { - GstCollectData *c_data = (GstCollectData *) best; /* Take the first data stream for the PCR */ - GST_DEBUG_OBJECT (mux, "Use stream from pad %" GST_PTR_FORMAT " as PCR", - c_data->pad); + GST_DEBUG_OBJECT (COLLECT_DATA_PAD (best), "Use stream as PCR"); mux->pcr_stream = best->stream; } } @@ -564,14 +564,12 @@ mpegtsmux_collected (GstCollectPads * pads, MpegTsMux * mux) if (best != NULL) { GstBuffer *buf = best->queued_buf; - GstCollectData *c_data = (GstCollectData *) best; gint64 pts = -1; g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR); - GST_DEBUG_OBJECT (mux, - "Chose stream from pad %" GST_PTR_FORMAT " for output (PID: 0x%04x)", - c_data->pad, best->pid); + GST_DEBUG_OBJECT (COLLECT_DATA_PAD (best), + "Chose stream for output (PID: 0x%04x)", best->pid); if (GST_CLOCK_TIME_IS_VALID (best->cur_ts)) { pts = GSTTIME_TO_MPEGTIME (best->cur_ts); diff --git a/gst/mpegvideoparse/mpegvideoparse.c b/gst/mpegvideoparse/mpegvideoparse.c index 2a7f9153..d0badbfc 100644 --- a/gst/mpegvideoparse/mpegvideoparse.c +++ b/gst/mpegvideoparse/mpegvideoparse.c @@ -278,6 +278,64 @@ mpegvideoparse_handle_sequence (MpegVideoParse * mpegvideoparse, return TRUE; } +#ifndef GST_DISABLE_GST_DEBUG +static const gchar * +picture_start_code_name (guint8 psc) +{ + guint i; + const struct + { + guint8 psc; + const gchar *name; + } psc_names[] = { + { + 0x00, "Picture Start"}, { + 0xb0, "Reserved"}, { + 0xb1, "Reserved"}, { + 0xb2, "User Data Start"}, { + 0xb3, "Sequence Header Start"}, { + 0xb4, "Sequence Error"}, { + 0xb5, "Extnsion Start"}, { + 0xb6, "Reserved"}, { + 0xb7, "Sequence End"}, { + 0xb8, "Group Start"}, { + 0xb9, "Program End"} + }; + if (psc < 0xB0 && psc > 0) + return "Slice Start"; + + for (i = 0; i < G_N_ELEMENTS (psc_names); i++) + if (psc_names[i].psc == psc) + return psc_names[i].name; + + return "UNKNOWN"; +}; + +static const gchar * +picture_type_name (guint8 pct) +{ + guint i; + const struct + { + guint8 pct; + const gchar *name; + } pct_names[] = { + { + 0, "Forbidden"}, { + 1, "I Frame"}, { + 2, "P Frame"}, { + 3, "B Frame"}, { + 4, "DC Intra Coded (Shall Not Be Used!)"} + }; + + for (i = 0; i < G_N_ELEMENTS (pct_names); i++) + if (pct_names[i].pct == pct) + return pct_names[i].name; + + return "Reserved/Unknown"; +} +#endif /* GST_DISABLE_GST_DEBUG */ + static gboolean mpegvideoparse_handle_picture (MpegVideoParse * mpegvideoparse, GstBuffer * buf) { @@ -289,6 +347,9 @@ mpegvideoparse_handle_picture (MpegVideoParse * mpegvideoparse, GstBuffer * buf) cur = mpeg_util_find_start_code (&sync_word, cur, end); while (cur != NULL) { + if (cur[0] == 0 || cur[0] > 0xaf) + GST_LOG_OBJECT (mpegvideoparse, "Picture Start Code : %s", + picture_start_code_name (cur[0])); /* Cur points at the last byte of the start code */ if (cur[0] == MPEG_PACKET_PICTURE) { guint8 *pic_data = cur - 3; @@ -301,7 +362,8 @@ mpegvideoparse_handle_picture (MpegVideoParse * mpegvideoparse, GstBuffer * buf) if (hdr.pic_type != MPEG_PICTURE_TYPE_I) GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); - GST_LOG_OBJECT (mpegvideoparse, "Picture type is %u", hdr.pic_type); + GST_LOG_OBJECT (mpegvideoparse, "Picture type is %s", + picture_type_name (hdr.pic_type)); /* FIXME: Can use the picture type and number of fields to track a * timestamp */ } @@ -359,8 +421,9 @@ mpegvideoparse_drain_avail (MpegVideoParse * mpegvideoparse) while ((cur != NULL) && (res == GST_FLOW_OK)) { /* Handle the block */ GST_LOG_OBJECT (mpegvideoparse, - "Have block of size %u with pack_type 0x%02x and flags 0x%02x", - cur->length, cur->first_pack_type, cur->flags); + "Have block of size %u with pack_type %s and flags 0x%02x", + cur->length, picture_start_code_name (cur->first_pack_type), + cur->flags); /* Don't start pushing out buffers until we've seen a sequence header */ if (mpegvideoparse->seq_hdr.mpeg_version == 0) { diff --git a/gst/mxf/mxfdemux.c b/gst/mxf/mxfdemux.c index c33650b5..5abd37ff 100644 --- a/gst/mxf/mxfdemux.c +++ b/gst/mxf/mxfdemux.c @@ -439,8 +439,8 @@ gst_mxf_demux_handle_partition_pack (GstMXFDemux * demux, const MXFUL * key, for (l = demux->partitions; l; l = l->next) { GstMXFDemuxPartition *a, *b; - if (l->next == NULL); - break; + if (l->next == NULL) + break; a = l->data; b = l->next->data; @@ -1921,8 +1921,8 @@ gst_mxf_demux_handle_random_index_pack (GstMXFDemux * demux, const MXFUL * key, for (l = demux->partitions; l; l = l->next) { GstMXFDemuxPartition *a, *b; - if (l->next == NULL); - break; + if (l->next == NULL) + break; a = l->data; b = l->next->data; diff --git a/gst/nsf/nes6502.c b/gst/nsf/nes6502.c index e2ed01f7..450350fc 100644 --- a/gst/nsf/nes6502.c +++ b/gst/nsf/nes6502.c @@ -944,7 +944,7 @@ ADD_CYCLES(cycles); \ } -#define SEC() \ +#define SEC_6502() \ { \ SET_FLAG(C_FLAG); \ ADD_CYCLES(2); \ @@ -1798,7 +1798,7 @@ nes6502_execute (int remaining_cycles) break; case 0x38: /* SEC */ - SEC (); + SEC_6502 (); break; case 0x39: /* AND $nnnn,Y */ diff --git a/gst/qtmux/atoms.c b/gst/qtmux/atoms.c index 47db40ce..29859d00 100644 --- a/gst/qtmux/atoms.c +++ b/gst/qtmux/atoms.c @@ -1036,7 +1036,8 @@ atom_meta_free (AtomMETA * meta) { atom_full_clear (&meta->header); atom_hdlr_clear (&meta->hdlr); - atom_ilst_free (meta->ilst); + if (meta->ilst) + atom_ilst_free (meta->ilst); meta->ilst = NULL; g_free (meta); } @@ -1061,8 +1062,11 @@ static void atom_udta_free (AtomUDTA * udta) { atom_clear (&udta->header); - atom_meta_free (udta->meta); + if (udta->meta) + atom_meta_free (udta->meta); udta->meta = NULL; + if (udta->entries) + atom_info_list_free (udta->entries); g_free (udta); } @@ -1149,6 +1153,7 @@ atom_moov_init (AtomMOOV * moov, AtomsContext * context) atom_mvhd_init (&(moov->mvhd)); moov->udta = NULL; moov->traks = NULL; + moov->context = *context; } AtomMOOV * @@ -1170,13 +1175,11 @@ atom_moov_free (AtomMOOV * moov) walker = moov->traks; while (walker) { - GList *aux = walker; - + atom_trak_free ((AtomTRAK *) walker->data); walker = g_list_next (walker); - moov->traks = g_list_remove_link (moov->traks, aux); - atom_trak_free ((AtomTRAK *) aux->data); - g_list_free (aux); } + g_list_free (moov->traks); + moov->traks = NULL; if (moov->udta) { atom_udta_free (moov->udta); @@ -1240,9 +1243,6 @@ atom_copy_data (Atom * atom, guint8 ** buffer, guint64 * size, guint64 * offset) * would be a problem for size (re)write code, not to mention memory */ g_return_val_if_fail (atom->type == FOURCC_mdat, 0); prop_copy_uint64 (atom->extended_size, buffer, size, offset); - } else { - /* just in case some trivially derived atom does not do so */ - atom_write_size (buffer, size, offset, original_offset); } return *offset - original_offset; @@ -2163,6 +2163,10 @@ atom_udta_copy_data (AtomUDTA * udta, guint8 ** buffer, guint64 * size, if (!atom_meta_copy_data (udta->meta, buffer, size, offset)) { return 0; } + } else if (udta->entries) { + /* extra atoms */ + if (!atom_info_list_copy_data (udta->entries, buffer, size, offset)) + return 0; } atom_write_size (buffer, size, offset, original_offset); @@ -2238,6 +2242,10 @@ stsc_entry_new (guint32 first_chunk, guint32 samples, guint32 desc_index) static void atom_stsc_add_new_entry (AtomSTSC * stsc, guint32 first_chunk, guint32 nsamples) { + if (stsc->entries && + ((STSCEntry *) stsc->entries->data)->samples_per_chunk == nsamples) + return; + stsc->entries = g_list_prepend (stsc->entries, stsc_entry_new (first_chunk, nsamples, 1)); stsc->n_entries++; @@ -2517,16 +2525,18 @@ atom_moov_chunks_add_offset (AtomMOOV * moov, guint32 offset) * Meta tags functions */ static void -atom_moov_init_metatags (AtomMOOV * moov) +atom_moov_init_metatags (AtomMOOV * moov, AtomsContext * context) { if (!moov->udta) { moov->udta = atom_udta_new (); } - if (!moov->udta->meta) { - moov->udta->meta = atom_meta_new (); - } - if (!moov->udta->meta->ilst) { - moov->udta->meta->ilst = atom_ilst_new (); + if (context->flavor != ATOMS_TREE_FLAVOR_3GP) { + if (!moov->udta->meta) { + moov->udta->meta = atom_meta_new (); + } + if (!moov->udta->meta->ilst) { + moov->udta->meta->ilst = atom_ilst_new (); + } } } @@ -2543,11 +2553,14 @@ atom_tag_data_alloc_data (AtomTagData * data, guint size) static void atom_moov_append_tag (AtomMOOV * moov, AtomInfo * tag) { - AtomILST *ilst; + GList **entries; - atom_moov_init_metatags (moov); - ilst = moov->udta->meta->ilst; - ilst->entries = g_list_append (ilst->entries, tag); + atom_moov_init_metatags (moov, &moov->context); + if (moov->udta->meta) + entries = &moov->udta->meta->ilst->entries; + else + entries = &moov->udta->entries; + *entries = g_list_append (*entries, tag); } void @@ -2621,6 +2634,87 @@ atom_moov_add_blob_tag (AtomMOOV * moov, guint8 * data, guint size) atom_data_free)); } +void +atom_moov_add_3gp_tag (AtomMOOV * moov, guint32 fourcc, guint8 * data, + guint size) +{ + AtomData *data_atom; + GstBuffer *buf; + guint8 *bdata; + + /* need full atom */ + buf = gst_buffer_new_and_alloc (size + 4); + bdata = GST_BUFFER_DATA (buf); + /* full atom: version and flags */ + GST_WRITE_UINT32_BE (bdata, 0); + memcpy (bdata + 4, data, size); + + data_atom = atom_data_new_from_gst_buffer (fourcc, buf); + gst_buffer_unref (buf); + + atom_moov_append_tag (moov, + build_atom_info_wrapper ((Atom *) data_atom, atom_data_copy_data, + atom_data_free)); +} + +guint16 +language_code (const char *lang) +{ + g_return_val_if_fail (lang != NULL, 0); + g_return_val_if_fail (strlen (lang) == 3, 0); + + return (((lang[0] - 0x60) & 0x1F) << 10) + (((lang[1] - 0x60) & 0x1F) << 5) + + ((lang[2] - 0x60) & 0x1F); +} + +void +atom_moov_add_3gp_str_int_tag (AtomMOOV * moov, guint32 fourcc, + const gchar * value, gint16 ivalue) +{ + gint len = 0, size = 0; + guint8 *data; + + if (value) { + len = strlen (value); + size = len + 3; + } + + if (ivalue >= 0) + size += 2; + + data = g_malloc (size + 3); + /* language tag and null-terminated UTF-8 string */ + if (value) { + GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE)); + /* include 0 terminator */ + memcpy (data + 2, value, len + 1); + } + /* 16-bit unsigned int if standalone, otherwise 8-bit */ + if (ivalue >= 0) { + if (size == 2) + GST_WRITE_UINT16_BE (data + size - 2, ivalue); + else { + GST_WRITE_UINT8 (data + size - 2, ivalue & 0xFF); + size--; + } + } + + atom_moov_add_3gp_tag (moov, fourcc, data, size); + g_free (data); +} + +void +atom_moov_add_3gp_str_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value) +{ + atom_moov_add_3gp_str_int_tag (moov, fourcc, value, -1); +} + +void +atom_moov_add_3gp_uint_tag (AtomMOOV * moov, guint32 fourcc, guint16 value) +{ + atom_moov_add_3gp_str_int_tag (moov, fourcc, NULL, value); +} + /* * Functions for specifying media types */ @@ -2804,14 +2898,56 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context, atom_trak_set_constant_size_samples (trak, sample_size); } +AtomInfo * +build_pasp_extension (AtomTRAK * trak, gint par_width, gint par_height) +{ + AtomData *atom_data; + GstBuffer *buf; + guint8 *data; + + buf = gst_buffer_new_and_alloc (8); + data = GST_BUFFER_DATA (buf); + + /* ihdr = image header box */ + GST_WRITE_UINT32_BE (data, par_width); + GST_WRITE_UINT32_BE (data + 4, par_height); + + atom_data = atom_data_new_from_gst_buffer (FOURCC_pasp, buf); + gst_buffer_unref (buf); + + return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data, + atom_data_free); +} + void atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context, VisualSampleEntry * entry, guint32 scale, AtomInfo * ext) { SampleTableEntryMP4V *ste; + gint dwidth, dheight; + gint par_n = 0, par_d = 0; + + if ((entry->par_n != 1 || entry->par_d != 1) && + (entry->par_n != entry->par_d)) { + par_n = entry->par_n; + par_d = entry->par_d; + } + + dwidth = entry->width; + dheight = entry->height; + /* ISO file spec says track header w/h indicates track's visual presentation + * (so this together with pixels w/h implicitly defines PAR) */ + if (par_n && (context->flavor != ATOMS_TREE_FLAVOR_MOV)) { + if (par_n > par_d) { + dwidth = entry->width * par_n / par_d; + dheight = entry->height; + } else { + dwidth = entry->width * par_n / par_d; + dheight = entry->height; + } + } - atom_trak_set_video_commons (trak, context, scale, entry->width, - entry->height); + atom_trak_set_video_commons (trak, context, scale, dwidth, dheight); ste = atom_trak_add_video_entry (trak, context, entry->fourcc); trak->is_video = TRUE; @@ -2825,6 +2961,12 @@ atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context, if (ext) ste->extension_atoms = g_list_prepend (ste->extension_atoms, ext); + + /* QT spec has a pasp extension atom in stsd that can hold PAR */ + if (par_n && (context->flavor == ATOMS_TREE_FLAVOR_MOV)) { + ste->extension_atoms = g_list_append (ste->extension_atoms, + build_pasp_extension (trak, par_n, par_d)); + } } /* some sample description construction helpers */ diff --git a/gst/qtmux/atoms.h b/gst/qtmux/atoms.h index 23bc19bb..4c94141b 100644 --- a/gst/qtmux/atoms.h +++ b/gst/qtmux/atoms.h @@ -55,7 +55,8 @@ typedef enum _AtomsTreeFlavor { ATOMS_TREE_FLAVOR_MOV, - ATOMS_TREE_FLAVOR_ISOM + ATOMS_TREE_FLAVOR_ISOM, + ATOMS_TREE_FLAVOR_3GP } AtomsTreeFlavor; typedef struct _AtomsContext @@ -509,6 +510,9 @@ typedef struct _AtomUDTA { Atom header; + /* list of AtomInfo */ + GList* entries; + /* or list is further down */ AtomMETA *meta; } AtomUDTA; @@ -526,6 +530,9 @@ typedef struct _AtomTRAK typedef struct _AtomMOOV { + /* style */ + AtomsContext context; + Atom header; AtomMVHD mvhd; @@ -602,6 +609,8 @@ typedef struct guint depth; guint frame_count; gint color_table_id; + guint par_n; + guint par_d; GstBuffer *codec_data; } VisualSampleEntry; @@ -649,4 +658,14 @@ void atom_moov_add_tag (AtomMOOV *moov, guint32 fourcc, guint32 flags, const guint8 * data, guint size); void atom_moov_add_blob_tag (AtomMOOV *moov, guint8 *data, guint size); +void atom_moov_add_3gp_str_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value); +void atom_moov_add_3gp_uint_tag (AtomMOOV * moov, guint32 fourcc, guint16 value); +void atom_moov_add_3gp_str_int_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value, + gint16 ivalue); +void atom_moov_add_3gp_tag (AtomMOOV * moov, guint32 fourcc, guint8 * data, + guint size); + +#define GST_QT_MUX_DEFAULT_TAG_LANGUAGE "eng" +guint16 language_code (const char * lang); + #endif /* __ATOMS_H__ */ diff --git a/gst/qtmux/fourcc.h b/gst/qtmux/fourcc.h index 3db60036..9b1fe65a 100644 --- a/gst/qtmux/fourcc.h +++ b/gst/qtmux/fourcc.h @@ -104,6 +104,7 @@ G_BEGIN_DECLS #define FOURCC_wave GST_MAKE_FOURCC('w','a','v','e') #define FOURCC_appl GST_MAKE_FOURCC('a','p','p','l') #define FOURCC_esds GST_MAKE_FOURCC('e','s','d','s') +#define FOURCC_pasp GST_MAKE_FOURCC('p','a','s','p') #define FOURCC_hnti GST_MAKE_FOURCC('h','n','t','i') #define FOURCC_rtp_ GST_MAKE_FOURCC('r','t','p',' ') #define FOURCC_sdp_ GST_MAKE_FOURCC('s','d','p',' ') @@ -181,6 +182,15 @@ G_BEGIN_DECLS #define FOURCC_titl GST_MAKE_FOURCC('t','i','t','l') #define FOURCC__cmt GST_MAKE_FOURCC(0xa9, 'c','m','t') +/* 3gp tags */ +#define FOURCC_dscp GST_MAKE_FOURCC('d','s','c','p') +#define FOURCC_perf GST_MAKE_FOURCC('p','e','r','f') +#define FOURCC_auth GST_MAKE_FOURCC('a','u','t','h') +#define FOURCC_yrrc GST_MAKE_FOURCC('y','r','r','c') +#define FOURCC_albm GST_MAKE_FOURCC('a','l','b','m') +#define FOURCC_loci GST_MAKE_FOURCC('l','o','c','i') +#define FOURCC_kywd GST_MAKE_FOURCC('k','y','w','d') + G_END_DECLS #endif /* __FOURCC_H__ */ diff --git a/gst/qtmux/gstqtmux.c b/gst/qtmux/gstqtmux.c index 03b0a1a6..8d811f41 100644 --- a/gst/qtmux/gstqtmux.c +++ b/gst/qtmux/gstqtmux.c @@ -111,9 +111,11 @@ enum PROP_FAST_START_TEMP_FILE }; -#define MDAT_ATOM_HEADER_SIZE 16 +/* some spare for header size as well */ +#define MDAT_LARGE_FILE_LIMIT ((guint64) 1024 * 1024 * 1024 * 2) + #define DEFAULT_LARGE_FILE FALSE -#define DEFAULT_MOVIE_TIMESCALE 600 +#define DEFAULT_MOVIE_TIMESCALE 1000 #define DEFAULT_DO_CTTS FALSE #define DEFAULT_FAST_START FALSE #define DEFAULT_FAST_START_TEMP_FILE NULL @@ -349,8 +351,294 @@ gst_qt_mux_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } -/* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific, - * and as such does not comply with e.g. 3GPP specs */ +static void +gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + switch (gst_tag_get_type (tag)) { + /* strings */ + case G_TYPE_STRING: + { + gchar *str = NULL; + + if (!gst_tag_list_get_string (list, tag, &str) || !str) + break; + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", + GST_FOURCC_ARGS (fourcc), str); + atom_moov_add_str_tag (qtmux->moov, fourcc, str); + g_free (str); + break; + } + /* double */ + case G_TYPE_DOUBLE: + { + gdouble value; + + if (!gst_tag_list_get_double (list, tag, &value)) + break; + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u", + GST_FOURCC_ARGS (fourcc), (gint) value); + atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value); + break; + } + /* paired unsigned integers */ + case G_TYPE_UINT: + { + guint value; + guint count; + + if (!gst_tag_list_get_uint (list, tag, &value) || + !gst_tag_list_get_uint (list, tag2, &count)) + break; + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u", + GST_FOURCC_ARGS (fourcc), value, count); + atom_moov_add_uint_tag (qtmux->moov, fourcc, 0, + value << 16 | (count & 0xFFFF)); + break; + } + default: + g_assert_not_reached (); + break; + } +} + +static void +gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + GDate *date = NULL; + GDateYear year; + GDateMonth month; + GDateDay day; + gchar *str; + + g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE); + + if (!gst_tag_list_get_date (list, tag, &date) || !date) + return; + + year = g_date_get_year (date); + month = g_date_get_month (date); + day = g_date_get_day (date); + + if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH && + day == G_DATE_BAD_DAY) { + GST_WARNING_OBJECT (qtmux, "invalid date in tag"); + return; + } + + str = g_strdup_printf ("%u-%u-%u", year, month, day); + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", + GST_FOURCC_ARGS (fourcc), str); + atom_moov_add_str_tag (qtmux->moov, fourcc, str); +} + +static void +gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + GValue value = { 0, }; + GstBuffer *buf; + GstCaps *caps; + GstStructure *structure; + gint flags = 0; + + g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER); + + if (!gst_tag_list_copy_value (&value, list, tag)) + return; + + buf = gst_value_get_buffer (&value); + if (!buf) + goto done; + + caps = gst_buffer_get_caps (buf); + if (!caps) { + GST_WARNING_OBJECT (qtmux, "preview image without caps"); + goto done; + } + + GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps); + + structure = gst_caps_get_structure (caps, 0); + if (gst_structure_has_name (structure, "image/jpeg")) + flags = 13; + else if (gst_structure_has_name (structure, "image/png")) + flags = 14; + gst_caps_unref (caps); + + if (!flags) { + GST_WARNING_OBJECT (qtmux, "preview image format not supported"); + goto done; + } + + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT + " -> image size %d", GST_FOURCC_ARGS (fourcc), GST_BUFFER_SIZE (buf)); + atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); +done: + g_value_unset (&value); +} + +static void +gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + gchar *str = NULL; + guint number; + + g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING); + g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT); + + if (!gst_tag_list_get_string (list, tag, &str) || !str) + return; + + if (tag2) + if (!gst_tag_list_get_uint (list, tag2, &number)) + tag2 = NULL; + + if (!tag2) { + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", + GST_FOURCC_ARGS (fourcc), str); + atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str); + } else { + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d", + GST_FOURCC_ARGS (fourcc), str, number); + atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number); + } + + g_free (str); +} + +static void +gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + GDate *date = NULL; + GDateYear year; + + g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE); + + if (!gst_tag_list_get_date (list, tag, &date) || !date) + return; + + year = g_date_get_year (date); + + if (year == G_DATE_BAD_YEAR) { + GST_WARNING_OBJECT (qtmux, "invalid date in tag"); + return; + } + + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d", year); + atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year); +} + +static void +gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + gdouble latitude = -360, longitude = -360, altitude = 0; + gchar *location = NULL; + guint8 *data, *ddata; + gint size = 0, len = 0; + gboolean ret = FALSE; + + g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0); + + ret = gst_tag_list_get_string (list, tag, &location); + ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE, + &longitude); + ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE, + &latitude); + ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION, + &altitude); + + if (!ret) + return; + + if (location) + len = strlen (location); + size += len + 1 + 2; + + /* role + (long, lat, alt) + body + notes */ + size += 1 + 3 * 4 + 1 + 1; + + data = ddata = g_malloc (size); + + /* language tag */ + GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE)); + /* location */ + if (location) + memcpy (data + 2, location, len); + GST_WRITE_UINT8 (data + 2 + len, 0); + data += len + 1 + 2; + /* role */ + GST_WRITE_UINT8 (data, 0); + /* long, lat, alt */ + GST_WRITE_UINT32_BE (data + 1, (guint32) (longitude * 65536.0)); + GST_WRITE_UINT32_BE (data + 5, (guint32) (latitude * 65536.0)); + GST_WRITE_UINT32_BE (data + 9, (guint32) (altitude * 65536.0)); + /* neither astronomical body nor notes */ + GST_WRITE_UINT16_BE (data + 13, 0); + + GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'"); + atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size); + g_free (ddata); +} + +static void +gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc) +{ + gchar *keywords = NULL; + guint8 *data, *ddata; + gint size = 0, i; + gchar **kwds; + + g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0); + + if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords) + return; + + kwds = g_strsplit (keywords, ",", 0); + + size = 0; + for (i = 0; kwds[i]; i++) { + /* size byte + null-terminator */ + size += strlen (kwds[i]) + 1 + 1; + } + + /* language tag + count + keywords */ + size += 2 + 1; + + data = ddata = g_malloc (size); + + /* language tag */ + GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE)); + /* count */ + GST_WRITE_UINT8 (data + 2, i); + data += 3; + /* keywords */ + for (i = 0; kwds[i]; ++i) { + gint len = strlen (kwds[i]); + + GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", + GST_FOURCC_ARGS (fourcc), kwds[i]); + /* size */ + GST_WRITE_UINT8 (data, len + 1); + memcpy (data + 1, kwds[i], len + 1); + data += len + 2; + } + + g_strfreev (kwds); + + atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size); + g_free (ddata); +} + + +typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list, + const char *tag, const char *tag2, guint32 fourcc); /* * Struct to record mappings from gstreamer tags to fourcc codes @@ -360,25 +648,42 @@ typedef struct _GstTagToFourcc guint32 fourcc; const gchar *gsttag; const gchar *gsttag2; + const GstQTMuxAddTagFunc func; } GstTagToFourcc; /* tag list tags to fourcc matching */ -static const GstTagToFourcc tag_matches[] = { - {FOURCC__alb, GST_TAG_ALBUM,}, - {FOURCC__ART, GST_TAG_ARTIST,}, - {FOURCC__cmt, GST_TAG_COMMENT,}, - {FOURCC__wrt, GST_TAG_COMPOSER,}, - {FOURCC__gen, GST_TAG_GENRE,}, - {FOURCC__nam, GST_TAG_TITLE,}, - {FOURCC__des, GST_TAG_DESCRIPTION,}, - {FOURCC__too, GST_TAG_ENCODER,}, - {FOURCC_cprt, GST_TAG_COPYRIGHT,}, - {FOURCC_keyw, GST_TAG_KEYWORDS,}, - {FOURCC__day, GST_TAG_DATE,}, - {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE,}, - {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT}, - {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT}, - {FOURCC_covr, GST_TAG_PREVIEW_IMAGE,}, +static const GstTagToFourcc tag_matches_mp4[] = { + {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date}, + {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag}, + {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, + gst_qt_mux_add_mp4_tag}, + {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, + gst_qt_mux_add_mp4_tag}, + {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover}, + {0, NULL,} +}; + +static const GstTagToFourcc tag_matches_3gp[] = { + {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str}, + {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords}, + {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date}, + {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str}, + {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location}, {0, NULL,} }; @@ -388,127 +693,35 @@ static const GstTagToFourcc tag_matches[] = { static void gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list) { + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); guint32 fourcc; gint i; const gchar *tag, *tag2; + const GstTagToFourcc *tag_matches; + + switch (qtmux_klass->format) { + case GST_QT_MUX_FORMAT_3GP: + tag_matches = tag_matches_3gp; + break; + case GST_QT_MUX_FORMAT_MJ2: + tag_matches = NULL; + break; + default: + /* sort of iTunes style for mp4 and QT (?) */ + tag_matches = tag_matches_mp4; + break; + } + + if (!tag_matches) + return; for (i = 0; tag_matches[i].fourcc; i++) { fourcc = tag_matches[i].fourcc; tag = tag_matches[i].gsttag; tag2 = tag_matches[i].gsttag2; - switch (gst_tag_get_type (tag)) { - /* strings */ - case G_TYPE_STRING: - { - gchar *str = NULL; - - if (!gst_tag_list_get_string (list, tag, &str) || !str) - break; - GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", - GST_FOURCC_ARGS (fourcc), str); - atom_moov_add_str_tag (qtmux->moov, fourcc, str); - g_free (str); - break; - } - /* double */ - case G_TYPE_DOUBLE: - { - gdouble value; - - if (!gst_tag_list_get_double (list, tag, &value)) - break; - GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u", - GST_FOURCC_ARGS (fourcc), (gint) value); - atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value); - break; - } - /* paired unsigned integers */ - case G_TYPE_UINT: - { - guint value; - guint count; - - if (!gst_tag_list_get_uint (list, tag, &value) || - !gst_tag_list_get_uint (list, tag2, &count)) - break; - GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u", - GST_FOURCC_ARGS (fourcc), value, count); - atom_moov_add_uint_tag (qtmux->moov, fourcc, 0, - value << 16 | (count & 0xFFFF)); - break; - } - default: - { - if (gst_tag_get_type (tag) == GST_TYPE_DATE) { - GDate *date = NULL; - GDateYear year; - GDateMonth month; - GDateDay day; - gchar *str; - - if (!gst_tag_list_get_date (list, tag, &date) || !date) - break; - year = g_date_get_year (date); - month = g_date_get_month (date); - day = g_date_get_day (date); - - if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH && - day == G_DATE_BAD_DAY) { - GST_WARNING_OBJECT (qtmux, "invalid date in tag"); - break; - } - - str = g_strdup_printf ("%u-%u-%u", year, month, day); - GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s", - GST_FOURCC_ARGS (fourcc), str); - atom_moov_add_str_tag (qtmux->moov, fourcc, str); - } else if (gst_tag_get_type (tag) == GST_TYPE_BUFFER) { - GValue value = { 0, }; - GstBuffer *buf; - GstCaps *caps; - GstStructure *structure; - gint flags = 0; - - if (!gst_tag_list_copy_value (&value, list, tag)) - break; - - buf = gst_value_get_buffer (&value); - if (!buf) - goto done; - - caps = gst_buffer_get_caps (buf); - if (!caps) { - GST_WARNING_OBJECT (qtmux, "preview image without caps"); - goto done; - } - - GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps); - - structure = gst_caps_get_structure (caps, 0); - if (gst_structure_has_name (structure, "image/jpeg")) - flags = 13; - else if (gst_structure_has_name (structure, "image/png")) - flags = 14; - gst_caps_unref (caps); - - if (!flags) { - GST_WARNING_OBJECT (qtmux, "preview image format not supported"); - goto done; - } - - GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT - " -> image size %d", GST_FOURCC_ARGS (fourcc), - GST_BUFFER_SIZE (buf)); - atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf), - GST_BUFFER_SIZE (buf)); - done: - g_value_unset (&value); - } else - g_assert_not_reached (); - break; - } - } + g_assert (tag_matches[i].func); + tag_matches[i].func (qtmux, list, tag, tag2, fourcc); } /* add unparsed blobs if present */ @@ -532,8 +745,12 @@ gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list) GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps); s = gst_caps_get_structure (caps, 0); if (s && (style = gst_structure_get_string (s, "style"))) { - /* FIXME make into a parameter */ - if (strcmp (style, "itunes") == 0) { + /* try to prevent some style tag ending up into another variant + * (todo: make into a list if more cases) */ + if ((strcmp (style, "itunes") == 0 && + qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) || + (strcmp (style, "iso") == 0 && + qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) { GST_DEBUG_OBJECT (qtmux, "Adding private tag"); atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); @@ -600,7 +817,7 @@ gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset, res = gst_pad_push (qtmux->srcpad, buf); } - if (offset) + if (G_LIKELY (offset)) *offset += size; return res; @@ -690,7 +907,8 @@ seek_failed: * seek back to it later and update when the streams have finished. */ static GstFlowReturn -gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size) +gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size, + gboolean extended) { Atom *node_header; GstBuffer *buf; @@ -702,11 +920,15 @@ gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size) node_header = g_malloc0 (sizeof (Atom)); node_header->type = FOURCC_mdat; - /* use extended size */ - node_header->size = 1; - node_header->extended_size = 0; - if (size) - node_header->extended_size = size; + if (extended) { + /* use extended size */ + node_header->size = 1; + node_header->extended_size = 0; + if (size) + node_header->extended_size = size + 16; + } else { + node_header->size = size + 8; + } size = offset = 0; if (atom_copy_data (node_header, &data, &size, &offset) == 0) @@ -740,14 +962,31 @@ gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos, { GstEvent *event; GstBuffer *buf; + gboolean large_file; + + large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT); + + if (large_file) + mdat_pos += 8; /* seek and rewrite the header */ event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, mdat_pos, GST_CLOCK_TIME_NONE, 0); gst_pad_push_event (qtmux->srcpad, event); - buf = gst_buffer_new_and_alloc (sizeof (guint64)); - GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size); + if (large_file) { + buf = gst_buffer_new_and_alloc (sizeof (guint64)); + GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size + 16); + } else { + guint8 *data; + + buf = gst_buffer_new_and_alloc (16); + data = GST_BUFFER_DATA (buf); + GST_WRITE_UINT32_BE (data, 8); + GST_WRITE_UINT32_LE (data + 4, FOURCC_free); + GST_WRITE_UINT32_BE (data + 8, mdat_size + 8); + GST_WRITE_UINT32_LE (data + 12, FOURCC_mdat); + } return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE); } @@ -798,6 +1037,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) /* tags into file metadata */ gst_qt_mux_setup_metadata (qtmux); + large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT); /* if faststart, update the offset of the atoms in the movie with the offset * that the movie headers before mdat will cause */ if (qtmux->fast_start_file) { @@ -807,7 +1047,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) goto serialize_error; GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT, size); - offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE; + offset += qtmux->header_size + (large_file ? 16 : 8); } else offset = qtmux->header_size; atom_moov_chunks_add_offset (qtmux->moov, offset); @@ -828,12 +1068,11 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms"); gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE); - /* total mdat size as of now also includes the atom header */ - qtmux->mdat_size += MDAT_ATOM_HEADER_SIZE; /* if needed, send mdat atom and move buffered data into it */ if (qtmux->fast_start_file) { /* mdat size = accumulated (buffered data) + mdat atom header */ - ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size); + ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size, + large_file); if (ret != GST_FLOW_OK) return ret; ret = gst_qt_mux_send_buffered_data (qtmux, NULL); @@ -927,9 +1166,9 @@ gst_qt_mux_start_file (GstQTMux * qtmux) if (!qtmux->fast_start_file) goto open_failed; } else { - ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0); - /* mdat size position = current header pos - extended header size */ - qtmux->mdat_pos = qtmux->header_size - sizeof (guint64); + /* extended to ensure some spare space */ + qtmux->mdat_pos = qtmux->header_size; + ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE); } GST_OBJECT_UNLOCK (qtmux); @@ -1464,7 +1703,6 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps) par_den = 1; gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num, &par_den); - /* FIXME: pixel-aspect-ratio */ qtpad->is_out_of_order = FALSE; @@ -1477,6 +1715,8 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps) /* set common properties */ entry.width = width; entry.height = height; + entry.par_n = par_num; + entry.par_d = par_den; /* should be OK according to qt and iso spec, override if really needed */ entry.color_table_id = -1; entry.frame_count = 1; diff --git a/gst/qtmux/gstqtmux.h b/gst/qtmux/gstqtmux.h index a4701cc1..567773a0 100644 --- a/gst/qtmux/gstqtmux.h +++ b/gst/qtmux/gstqtmux.h @@ -109,7 +109,7 @@ struct _GstQTMux guint64 header_size; /* accumulated size of raw media data (a priori not including mdat header) */ guint64 mdat_size; - /* position of mdat extended size field (for later updating) */ + /* position of mdat atom (for later updating) */ guint64 mdat_pos; /* atom helper objects */ diff --git a/gst/qtmux/gstqtmuxmap.c b/gst/qtmux/gstqtmuxmap.c index e0e4faed..808856a2 100644 --- a/gst/qtmux/gstqtmuxmap.c +++ b/gst/qtmux/gstqtmuxmap.c @@ -201,6 +201,8 @@ gst_qt_mux_map_format_to_flavor (GstQTMuxFormat format) { if (format == GST_QT_MUX_FORMAT_QT) return ATOMS_TREE_FLAVOR_MOV; + else if (format == GST_QT_MUX_FORMAT_3GP) + return ATOMS_TREE_FLAVOR_3GP; else return ATOMS_TREE_FLAVOR_ISOM; } diff --git a/gst/rtpmanager/gstrtpjitterbuffer.c b/gst/rtpmanager/gstrtpjitterbuffer.c index e023df10..55126054 100644 --- a/gst/rtpmanager/gstrtpjitterbuffer.c +++ b/gst/rtpmanager/gstrtpjitterbuffer.c @@ -1688,9 +1688,8 @@ flushing: } pause: { - const gchar *reason = gst_flow_get_name (result); - - GST_DEBUG_OBJECT (jitterbuffer, "pausing task, reason %s", reason); + GST_DEBUG_OBJECT (jitterbuffer, "pausing task, reason %s", + gst_flow_get_name (result)); JBUF_LOCK (priv); /* store result */ diff --git a/gst/rtpmanager/gstrtpsession.c b/gst/rtpmanager/gstrtpsession.c index c33fdfc6..9407ee52 100644 --- a/gst/rtpmanager/gstrtpsession.c +++ b/gst/rtpmanager/gstrtpsession.c @@ -259,7 +259,7 @@ struct _GstRtpSessionPrivate static GstFlowReturn gst_rtp_session_process_rtp (RTPSession * sess, RTPSource * src, GstBuffer * buffer, gpointer user_data); static GstFlowReturn gst_rtp_session_send_rtp (RTPSession * sess, - RTPSource * src, GstBuffer * buffer, gpointer user_data); + RTPSource * src, gpointer data, gpointer user_data); static GstFlowReturn gst_rtp_session_send_rtcp (RTPSession * sess, RTPSource * src, GstBuffer * buffer, gboolean eos, gpointer user_data); static GstFlowReturn gst_rtp_session_sync_rtcp (RTPSession * sess, @@ -1032,8 +1032,8 @@ gst_rtp_session_clear_pt_map (GstRtpSession * rtpsession) g_hash_table_foreach_remove (rtpsession->priv->ptmap, return_true, NULL); } -/* called when the session manager has an RTP packet ready for further - * processing */ +/* called when the session manager has an RTP packet or a list of packets + * ready for further processing */ static GstFlowReturn gst_rtp_session_process_rtp (RTPSession * sess, RTPSource * src, GstBuffer * buffer, gpointer user_data) @@ -1060,7 +1060,7 @@ gst_rtp_session_process_rtp (RTPSession * sess, RTPSource * src, * sending */ static GstFlowReturn gst_rtp_session_send_rtp (RTPSession * sess, RTPSource * src, - GstBuffer * buffer, gpointer user_data) + gpointer data, gpointer user_data) { GstFlowReturn result; GstRtpSession *rtpsession; @@ -1069,12 +1069,17 @@ gst_rtp_session_send_rtp (RTPSession * sess, RTPSource * src, rtpsession = GST_RTP_SESSION (user_data); priv = rtpsession->priv; - GST_LOG_OBJECT (rtpsession, "sending RTP packet"); - if (rtpsession->send_rtp_src) { - result = gst_pad_push (rtpsession->send_rtp_src, buffer); + if (GST_IS_BUFFER (data)) { + GST_LOG_OBJECT (rtpsession, "sending RTP packet"); + result = gst_pad_push (rtpsession->send_rtp_src, GST_BUFFER_CAST (data)); + } else { + GST_LOG_OBJECT (rtpsession, "sending RTP list"); + result = gst_pad_push_list (rtpsession->send_rtp_src, + GST_BUFFER_LIST_CAST (data)); + } } else { - gst_buffer_unref (buffer); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); result = GST_FLOW_OK; } return result; @@ -1642,11 +1647,12 @@ gst_rtp_session_setcaps_send_rtp (GstPad * pad, GstCaps * caps) return TRUE; } -/* Recieve an RTP packet to be send to the receivers, send to RTP session - * manager and forward to send_rtp_src. +/* Recieve an RTP packet or a list of packets to be send to the receivers, + * send to RTP session manager and forward to send_rtp_src. */ static GstFlowReturn -gst_rtp_session_chain_send_rtp (GstPad * pad, GstBuffer * buffer) +gst_rtp_session_chain_send_rtp_common (GstPad * pad, gpointer data, + gboolean is_list) { GstRtpSession *rtpsession; GstRtpSessionPrivate *priv; @@ -1658,10 +1664,22 @@ gst_rtp_session_chain_send_rtp (GstPad * pad, GstBuffer * buffer) rtpsession = GST_RTP_SESSION (gst_pad_get_parent (pad)); priv = rtpsession->priv; - GST_LOG_OBJECT (rtpsession, "received RTP packet"); + GST_LOG_OBJECT (rtpsession, "received RTP %s", is_list ? "list" : "packet"); /* get NTP time when this packet was captured, this depends on the timestamp. */ - timestamp = GST_BUFFER_TIMESTAMP (buffer); + if (is_list) { + GstBuffer *buffer = NULL; + + /* All groups in an list have the same timestamp. + * So, just take it from the first group. */ + buffer = gst_buffer_list_get (GST_BUFFER_LIST_CAST (data), 0, 0); + if (buffer) + timestamp = GST_BUFFER_TIMESTAMP (buffer); + else + timestamp = -1; + } else { + timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER_CAST (data)); + } if (GST_CLOCK_TIME_IS_VALID (timestamp)) { /* convert to running time using the segment start value. */ ntpnstime = @@ -1676,7 +1694,9 @@ gst_rtp_session_chain_send_rtp (GstPad * pad, GstBuffer * buffer) } current_time = gst_clock_get_time (priv->sysclock); - ret = rtp_session_send_rtp (priv->session, buffer, current_time, ntpnstime); + ret = + rtp_session_send_rtp (priv->session, data, is_list, current_time, + ntpnstime); if (ret != GST_FLOW_OK) goto push_error; @@ -1694,6 +1714,18 @@ push_error: } } +static GstFlowReturn +gst_rtp_session_chain_send_rtp (GstPad * pad, GstBuffer * buffer) +{ + return gst_rtp_session_chain_send_rtp_common (pad, buffer, FALSE); +} + +static GstFlowReturn +gst_rtp_session_chain_send_rtp_list (GstPad * pad, GstBufferList * list) +{ + return gst_rtp_session_chain_send_rtp_common (pad, list, TRUE); +} + /* Create sinkpad to receive RTP packets from senders. This will also create a * srcpad for the RTP packets. */ @@ -1817,6 +1849,8 @@ create_send_rtp_sink (GstRtpSession * rtpsession) "send_rtp_sink"); gst_pad_set_chain_function (rtpsession->send_rtp_sink, gst_rtp_session_chain_send_rtp); + gst_pad_set_chain_list_function (rtpsession->send_rtp_sink, + gst_rtp_session_chain_send_rtp_list); gst_pad_set_getcaps_function (rtpsession->send_rtp_sink, gst_rtp_session_getcaps_send_rtp); gst_pad_set_setcaps_function (rtpsession->send_rtp_sink, diff --git a/gst/rtpmanager/rtpsession.c b/gst/rtpmanager/rtpsession.c index 219aacf1..cda04182 100644 --- a/gst/rtpmanager/rtpsession.c +++ b/gst/rtpmanager/rtpsession.c @@ -958,7 +958,7 @@ rtp_session_get_sdes_string (RTPSession * sess, GstRTCPSDESType type) } static GstFlowReturn -source_push_rtp (RTPSource * source, GstBuffer * buffer, RTPSession * session) +source_push_rtp (RTPSource * source, gpointer data, RTPSession * session) { GstFlowReturn result = GST_FLOW_OK; @@ -969,21 +969,21 @@ source_push_rtp (RTPSource * source, GstBuffer * buffer, RTPSession * session) if (session->callbacks.send_rtp) result = - session->callbacks.send_rtp (session, source, buffer, + session->callbacks.send_rtp (session, source, data, session->send_rtp_user_data); - else - gst_buffer_unref (buffer); - + else { + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + } } else { GST_LOG ("source %08x pushed receiver RTP packet", source->ssrc); RTP_SESSION_UNLOCK (session); if (session->callbacks.process_rtp) result = - session->callbacks.process_rtp (session, source, buffer, - session->process_rtp_user_data); + session->callbacks.process_rtp (session, source, + GST_BUFFER_CAST (data), session->process_rtp_user_data); else - gst_buffer_unref (buffer); + gst_buffer_unref (GST_BUFFER_CAST (data)); } RTP_SESSION_LOCK (session); @@ -1962,7 +1962,7 @@ ignore: /** * rtp_session_send_rtp: * @sess: an #RTPSession - * @buffer: an RTP buffer + * @data: pointer to either an RTP buffer or a list of RTP buffers * @current_time: the current system time * @ntpnstime: the NTP time in nanoseconds of when this buffer was captured. * This is the buffer timestamp converted to NTP time. @@ -1973,20 +1973,27 @@ ignore: * Returns: a #GstFlowReturn. */ GstFlowReturn -rtp_session_send_rtp (RTPSession * sess, GstBuffer * buffer, +rtp_session_send_rtp (RTPSession * sess, gpointer data, gboolean is_list, GstClockTime current_time, guint64 ntpnstime) { GstFlowReturn result; RTPSource *source; gboolean prevsender; + gboolean valid_packet; g_return_val_if_fail (RTP_IS_SESSION (sess), GST_FLOW_ERROR); - g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + g_return_val_if_fail (is_list || GST_IS_BUFFER (data), GST_FLOW_ERROR); - if (!gst_rtp_buffer_validate (buffer)) + if (is_list) { + valid_packet = gst_rtp_buffer_list_validate (GST_BUFFER_LIST_CAST (data)); + } else { + valid_packet = gst_rtp_buffer_validate (GST_BUFFER_CAST (data)); + } + + if (!valid_packet) goto invalid_packet; - GST_LOG ("received RTP packet for sending"); + GST_LOG ("received RTP %s for sending", is_list ? "list" : "packet"); RTP_SESSION_LOCK (sess); source = sess->source; @@ -1997,7 +2004,7 @@ rtp_session_send_rtp (RTPSession * sess, GstBuffer * buffer, prevsender = RTP_SOURCE_IS_SENDER (source); /* we use our own source to send */ - result = rtp_source_send_rtp (source, buffer, ntpnstime); + result = rtp_source_send_rtp (source, data, is_list, ntpnstime); if (RTP_SOURCE_IS_SENDER (source) && !prevsender) sess->stats.sender_sources++; @@ -2008,7 +2015,7 @@ rtp_session_send_rtp (RTPSession * sess, GstBuffer * buffer, /* ERRORS */ invalid_packet: { - gst_buffer_unref (buffer); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); GST_DEBUG ("invalid RTP packet received"); return GST_FLOW_OK; } diff --git a/gst/rtpmanager/rtpsession.h b/gst/rtpmanager/rtpsession.h index 7e327a7d..6312f1c1 100644 --- a/gst/rtpmanager/rtpsession.h +++ b/gst/rtpmanager/rtpsession.h @@ -65,7 +65,7 @@ typedef GstFlowReturn (*RTPSessionProcessRTP) (RTPSession *sess, RTPSource *src, * * Returns: a #GstFlowReturn. */ -typedef GstFlowReturn (*RTPSessionSendRTP) (RTPSession *sess, RTPSource *src, GstBuffer *buffer, gpointer user_data); +typedef GstFlowReturn (*RTPSessionSendRTP) (RTPSession *sess, RTPSource *src, gpointer data, gpointer user_data); /** * RTPSessionSendRTCP: @@ -288,7 +288,7 @@ GstFlowReturn rtp_session_process_rtcp (RTPSession *sess, GstBuffer GstClockTime current_time); /* processing packets for sending */ -GstFlowReturn rtp_session_send_rtp (RTPSession *sess, GstBuffer *buffer, +GstFlowReturn rtp_session_send_rtp (RTPSession *sess, gpointer data, gboolean is_list, GstClockTime current_time, guint64 ntpnstime); /* stopping the session */ diff --git a/gst/rtpmanager/rtpsource.c b/gst/rtpmanager/rtpsource.c index ed080717..209c17b5 100644 --- a/gst/rtpmanager/rtpsource.c +++ b/gst/rtpmanager/rtpsource.c @@ -26,7 +26,7 @@ GST_DEBUG_CATEGORY_STATIC (rtp_source_debug); #define GST_CAT_DEFAULT rtp_source_debug -#define RTP_MAX_PROBATION_LEN 32 +#define RTP_MAX_PROBATION_LEN 32 /* signals and args */ enum @@ -1091,41 +1091,73 @@ rtp_source_process_bye (RTPSource * src, const gchar * reason) src->received_bye = TRUE; } +static GstBufferListItem +set_ssrc (GstBuffer ** buffer, guint group, guint idx, RTPSource * src) +{ + *buffer = gst_buffer_make_writable (*buffer); + gst_rtp_buffer_set_ssrc (*buffer, src->ssrc); + return GST_BUFFER_LIST_SKIP_GROUP; +} + /** * rtp_source_send_rtp: * @src: an #RTPSource - * @buffer: an RTP buffer + * @data: an RTP buffer or a list of RTP buffers + * @is_list: if @data is a buffer or list * @ntpnstime: the NTP time when this buffer was captured in nanoseconds. This * is the buffer timestamp converted to NTP time. * - * Send an RTP @buffer originating from @src. This will make @src a sender. - * This function takes ownership of @buffer and modifies the SSRC in the RTP - * packet to that of @src when needed. + * Send @data (an RTP buffer or list of buffers) originating from @src. + * This will make @src a sender. This function takes ownership of @data and + * modifies the SSRC in the RTP packet to that of @src when needed. * * Returns: a #GstFlowReturn. */ GstFlowReturn -rtp_source_send_rtp (RTPSource * src, GstBuffer * buffer, guint64 ntpnstime) +rtp_source_send_rtp (RTPSource * src, gpointer data, gboolean is_list, + guint64 ntpnstime) { - GstFlowReturn result = GST_FLOW_OK; + GstFlowReturn result; guint len; guint32 rtptime; guint64 ext_rtptime; guint64 ntp_diff, rtp_diff; guint64 elapsed; + GstBufferList *list = NULL; + GstBuffer *buffer = NULL; + guint packets; + guint32 ssrc; g_return_val_if_fail (RTP_IS_SOURCE (src), GST_FLOW_ERROR); - g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + g_return_val_if_fail (is_list || GST_IS_BUFFER (data), GST_FLOW_ERROR); - len = gst_rtp_buffer_get_payload_len (buffer); + if (is_list) { + list = GST_BUFFER_LIST_CAST (data); + /* We can grab the caps from the first group, since all + * groups of a buffer list have same caps. */ + buffer = gst_buffer_list_get (list, 0, 0); + if (!buffer) + goto no_buffer; + } else { + buffer = GST_BUFFER_CAST (data); + } rtp_source_update_caps (src, GST_BUFFER_CAPS (buffer)); /* we are a sender now */ src->is_sender = TRUE; + if (is_list) { + /* Each group makes up a network packet. */ + packets = gst_buffer_list_n_groups (list); + len = gst_rtp_buffer_list_get_payload_len (list); + } else { + packets = 1; + len = gst_rtp_buffer_get_payload_len (buffer); + } + /* update stats for the SR */ - src->stats.packets_sent++; + src->stats.packets_sent += packets; src->stats.octets_sent += len; src->bytes_sent += len; @@ -1156,7 +1188,11 @@ rtp_source_send_rtp (RTPSource * src, GstBuffer * buffer, guint64 ntpnstime) src->bitrate = 0; } - rtptime = gst_rtp_buffer_get_timestamp (buffer); + if (is_list) { + rtptime = gst_rtp_buffer_list_get_timestamp (list); + } else { + rtptime = gst_rtp_buffer_get_timestamp (buffer); + } ext_rtptime = src->last_rtptime; ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime); @@ -1180,31 +1216,53 @@ rtp_source_send_rtp (RTPSource * src, GstBuffer * buffer, guint64 ntpnstime) src->last_ntpnstime = ntpnstime; /* push packet */ - if (src->callbacks.push_rtp) { - guint32 ssrc; + if (!src->callbacks.push_rtp) + goto no_callback; + if (is_list) { + ssrc = gst_rtp_buffer_list_get_ssrc (list); + } else { ssrc = gst_rtp_buffer_get_ssrc (buffer); - if (ssrc != src->ssrc) { - /* the SSRC of the packet is not correct, make a writable buffer and - * update the SSRC. This could involve a complete copy of the packet when - * it is not writable. Usually the payloader will use caps negotiation to - * get the correct SSRC from the session manager before pushing anything. */ - buffer = gst_buffer_make_writable (buffer); - - /* FIXME, we don't want to warn yet because we can't inform any payloader - * of the changes SSRC yet because we don't implement pad-alloc. */ - GST_LOG ("updating SSRC from %08x to %08x, fix the payloader", ssrc, - src->ssrc); - gst_rtp_buffer_set_ssrc (buffer, src->ssrc); + } + + if (ssrc != src->ssrc) { + /* the SSRC of the packet is not correct, make a writable buffer and + * update the SSRC. This could involve a complete copy of the packet when + * it is not writable. Usually the payloader will use caps negotiation to + * get the correct SSRC from the session manager before pushing anything. */ + + /* FIXME, we don't want to warn yet because we can't inform any payloader + * of the changes SSRC yet because we don't implement pad-alloc. */ + GST_LOG ("updating SSRC from %08x to %08x, fix the payloader", ssrc, + src->ssrc); + + if (is_list) { + list = gst_buffer_list_make_writable (list); + gst_buffer_list_foreach (list, (GstBufferListFunc) set_ssrc, src); + } else { + set_ssrc (&buffer, 0, 0, src); } - GST_LOG ("pushing RTP packet %" G_GUINT64_FORMAT, src->stats.packets_sent); - result = src->callbacks.push_rtp (src, buffer, src->user_data); - } else { - GST_WARNING ("no callback installed, dropping packet"); - gst_buffer_unref (buffer); } + GST_LOG ("pushing RTP %s %" G_GUINT64_FORMAT, is_list ? "list" : "packet", + src->stats.packets_sent); + + result = src->callbacks.push_rtp (src, data, src->user_data); return result; + + /* ERRORS */ +no_buffer: + { + GST_WARNING ("no buffers in buffer list"); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + return GST_FLOW_OK; + } +no_callback: + { + GST_WARNING ("no callback installed, dropping packet"); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + return GST_FLOW_OK; + } } /** diff --git a/gst/rtpmanager/rtpsource.h b/gst/rtpmanager/rtpsource.h index a44ac1cd..8286f2ec 100644 --- a/gst/rtpmanager/rtpsource.h +++ b/gst/rtpmanager/rtpsource.h @@ -194,7 +194,7 @@ void rtp_source_set_rtcp_from (RTPSource *src, GstNetAddress *a /* handling RTP */ GstFlowReturn rtp_source_process_rtp (RTPSource *src, GstBuffer *buffer, RTPArrivalStats *arrival); -GstFlowReturn rtp_source_send_rtp (RTPSource *src, GstBuffer *buffer, guint64 ntpnstime); +GstFlowReturn rtp_source_send_rtp (RTPSource *src, gpointer data, gboolean is_list, guint64 ntpnstime); /* RTCP messages */ void rtp_source_process_bye (RTPSource *src, const gchar *reason); diff --git a/gst/scaletempo/gstscaletempo.c b/gst/scaletempo/gstscaletempo.c index ad3b949a..ed25d132 100644 --- a/gst/scaletempo/gstscaletempo.c +++ b/gst/scaletempo/gstscaletempo.c @@ -230,7 +230,7 @@ best_overlap_offset_s16 (GstScaletempo * scaletempo) ppc = p->buf_pre_corr; ppc += p->samples_overlap - p->samples_per_frame; ps += p->samples_overlap - p->samples_per_frame; - i = -(p->samples_overlap - p->samples_per_frame); + i = -((glong) p->samples_overlap - (glong) p->samples_per_frame); do { corr += ppc[i + 0] * ps[i + 0]; corr += ppc[i + 1] * ps[i + 1]; diff --git a/gst/selector/gstinputselector.c b/gst/selector/gstinputselector.c index 08d3d497..832707d0 100644 --- a/gst/selector/gstinputselector.c +++ b/gst/selector/gstinputselector.c @@ -420,6 +420,8 @@ gst_selector_pad_event (GstPad * pad, GstEvent * event) gst_tag_list_free (oldtags); GST_DEBUG_OBJECT (pad, "received tags %" GST_PTR_FORMAT, newtags); GST_OBJECT_UNLOCK (selpad); + + g_object_notify (G_OBJECT (selpad), "tags"); break; } case GST_EVENT_EOS: @@ -1415,6 +1417,7 @@ gst_input_selector_check_eos (GstElement * selector) if (!pad->eos) { done = TRUE; } + gst_object_unref (pad); break; case GST_ITERATOR_RESYNC: gst_iterator_resync (it); diff --git a/gst/selector/gstoutputselector.c b/gst/selector/gstoutputselector.c index bf354a74..15c0a726 100644 --- a/gst/selector/gstoutputselector.c +++ b/gst/selector/gstoutputselector.c @@ -196,7 +196,7 @@ gst_output_selector_set_property (GObject * object, guint prop_id, next_pad = g_value_get_object (value); - GST_LOG_OBJECT (sel, "Activating pad %s:%s", + GST_INFO_OBJECT (sel, "Activating pad %s:%s", GST_DEBUG_PAD_NAME (next_pad)); GST_OBJECT_LOCK (object); @@ -262,11 +262,11 @@ gst_output_selector_buffer_alloc (GstPad * pad, guint64 offset, guint size, GstPad *allocpad; sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad)); - res = GST_FLOW_NOT_LINKED; GST_OBJECT_LOCK (sel); - if ((allocpad = sel->active_srcpad)) { + allocpad = sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad; + if (allocpad) { /* if we had a previous pad we used for allocating a buffer, continue using * it. */ GST_DEBUG_OBJECT (sel, "using pad %s:%s for alloc", @@ -278,9 +278,16 @@ gst_output_selector_buffer_alloc (GstPad * pad, guint64 offset, guint size, gst_object_unref (allocpad); GST_OBJECT_LOCK (sel); + } else { + /* fallback case, allocate a buffer of our own, add pad caps. */ + GST_DEBUG_OBJECT (pad, "fallback buffer alloc"); + *buf = NULL; + res = GST_FLOW_OK; } GST_OBJECT_UNLOCK (sel); + GST_DEBUG_OBJECT (sel, "buffer alloc finished: %s", gst_flow_get_name (res)); + return res; } @@ -330,14 +337,24 @@ gst_output_selector_release_pad (GstElement * element, GstPad * pad) static gboolean gst_output_selector_switch (GstOutputSelector * osel) { - gboolean res = TRUE; + gboolean res = FALSE; GstEvent *ev = NULL; GstSegment *seg = NULL; gint64 start = 0, position = 0; + /* Switch */ + GST_OBJECT_LOCK (GST_OBJECT (osel)); GST_INFO ("switching to pad %" GST_PTR_FORMAT, osel->pending_srcpad); - if (gst_pad_is_linked (osel->pending_srcpad)) { + osel->active_srcpad = osel->pending_srcpad; + res = TRUE; + } + gst_object_unref (osel->pending_srcpad); + osel->pending_srcpad = NULL; + GST_OBJECT_UNLOCK (GST_OBJECT (osel)); + + /* Send NEWSEGMENT event and latest buffer if switching succeeded */ + if (res) { /* Send NEWSEGMENT to the pad we are going to switch to */ seg = &osel->segment; /* If resending then mark newsegment start and position accordingly */ @@ -349,29 +366,22 @@ gst_output_selector_switch (GstOutputSelector * osel) } ev = gst_event_new_new_segment (TRUE, seg->rate, seg->format, start, seg->stop, position); - if (!gst_pad_push_event (osel->pending_srcpad, ev)) { + if (!gst_pad_push_event (osel->active_srcpad, ev)) { GST_WARNING_OBJECT (osel, "newsegment handling failed in %" GST_PTR_FORMAT, - osel->pending_srcpad); + osel->active_srcpad); } /* Resend latest buffer to newly switched pad */ if (osel->resend_latest && osel->latest_buffer) { GST_INFO ("resending latest buffer"); - gst_pad_push (osel->pending_srcpad, osel->latest_buffer); + gst_pad_push (osel->active_srcpad, osel->latest_buffer); osel->latest_buffer = NULL; } - - /* Switch */ - osel->active_srcpad = osel->pending_srcpad; } else { GST_WARNING_OBJECT (osel, "switch failed, pad not linked"); - res = FALSE; } - gst_object_unref (osel->pending_srcpad); - osel->pending_srcpad = NULL; - return res; } @@ -389,10 +399,15 @@ gst_output_selector_chain (GstPad * pad, GstBuffer * buf) gst_output_selector_switch (osel); } - /* Keep reference to latest buffer to resend it after switch */ - if (osel->latest_buffer) + if (osel->latest_buffer) { gst_buffer_unref (osel->latest_buffer); - osel->latest_buffer = gst_buffer_ref (buf); + osel->latest_buffer = NULL; + } + + if (osel->resend_latest) { + /* Keep reference to latest buffer to resend it after switch */ + osel->latest_buffer = gst_buffer_ref (buf); + } /* Keep track of last stop and use it in NEWSEGMENT start after switching to a new src pad */ diff --git a/gst/shapewipe/gstshapewipe.c b/gst/shapewipe/gstshapewipe.c index c2c4ce2b..95218801 100644 --- a/gst/shapewipe/gstshapewipe.c +++ b/gst/shapewipe/gstshapewipe.c @@ -56,6 +56,11 @@ static void gst_shape_wipe_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_shape_wipe_reset (GstShapeWipe * self); +static void gst_shape_wipe_update_qos (GstShapeWipe * self, gdouble proportion, + GstClockTimeDiff diff, GstClockTime time); +static void gst_shape_wipe_reset_qos (GstShapeWipe * self); +static void gst_shape_wipe_read_qos (GstShapeWipe * self, gdouble * proportion, + GstClockTime * time); static GstStateChangeReturn gst_shape_wipe_change_state (GstElement * element, GstStateChange transition); @@ -67,6 +72,10 @@ static gboolean gst_shape_wipe_video_sink_event (GstPad * pad, static gboolean gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps); static GstCaps *gst_shape_wipe_video_sink_getcaps (GstPad * pad); +static GstFlowReturn gst_shape_wipe_video_sink_bufferalloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); +static gboolean gst_shape_wipe_video_sink_query (GstPad * pad, + GstQuery * query); static GstFlowReturn gst_shape_wipe_mask_sink_chain (GstPad * pad, GstBuffer * buffer); static gboolean gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event); @@ -74,6 +83,7 @@ static gboolean gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps); static GstCaps *gst_shape_wipe_mask_sink_getcaps (GstPad * pad); static gboolean gst_shape_wipe_src_event (GstPad * pad, GstEvent * event); static GstCaps *gst_shape_wipe_src_getcaps (GstPad * pad); +static gboolean gst_shape_wipe_src_query (GstPad * pad, GstQuery * query); enum { @@ -165,6 +175,10 @@ gst_shape_wipe_init (GstShapeWipe * self, GstShapeWipeClass * g_class) GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_setcaps)); gst_pad_set_getcaps_function (self->video_sinkpad, GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_getcaps)); + gst_pad_set_bufferalloc_function (self->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_bufferalloc)); + gst_pad_set_query_function (self->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_query)); gst_element_add_pad (GST_ELEMENT (self), self->video_sinkpad); self->mask_sinkpad = @@ -184,6 +198,8 @@ gst_shape_wipe_init (GstShapeWipe * self, GstShapeWipeClass * g_class) GST_DEBUG_FUNCPTR (gst_shape_wipe_src_event)); gst_pad_set_getcaps_function (self->srcpad, GST_DEBUG_FUNCPTR (gst_shape_wipe_src_getcaps)); + gst_pad_set_query_function (self->srcpad, + GST_DEBUG_FUNCPTR (gst_shape_wipe_src_query)); gst_element_add_pad (GST_ELEMENT (self), self->srcpad); self->mask_mutex = g_mutex_new (); @@ -263,6 +279,28 @@ gst_shape_wipe_reset (GstShapeWipe * self) self->mask_bpp = 0; gst_segment_init (&self->segment, GST_FORMAT_TIME); + + gst_shape_wipe_reset_qos (self); + self->frame_duration = 0; +} + +static GstFlowReturn +gst_shape_wipe_video_sink_bufferalloc (GstPad * pad, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad)); + GstFlowReturn ret = GST_FLOW_OK; + + GST_DEBUG_OBJECT (pad, "Allocating buffer with offset 0x%" G_GINT64_MODIFIER + "x and size %u with caps: %" GST_PTR_FORMAT, offset, size, caps); + + *buf = NULL; + + ret = gst_pad_alloc_buffer (self->srcpad, offset, size, caps, buf); + + gst_object_unref (self); + + return ret; } static gboolean @@ -272,13 +310,15 @@ gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps) gboolean ret = TRUE; GstStructure *s; gint width, height; + gint fps_n, fps_d; GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps); s = gst_caps_get_structure (caps, 0); if (!gst_structure_get_int (s, "width", &width) || - !gst_structure_get_int (s, "height", &height)) { + !gst_structure_get_int (s, "height", &height) || + !gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) { ret = FALSE; goto done; } @@ -294,6 +334,8 @@ gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps) g_mutex_unlock (self->mask_mutex); } + self->frame_duration = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n); + ret = gst_pad_set_caps (self->srcpad, caps); done: @@ -567,6 +609,123 @@ gst_shape_wipe_src_getcaps (GstPad * pad) return ret; } +static gboolean +gst_shape_wipe_video_sink_query (GstPad * pad, GstQuery * query) +{ + GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad)); + gboolean ret; + GstPad *peer = gst_pad_get_peer (self->srcpad); + + GST_DEBUG_OBJECT (pad, "Handling query of type '%s'", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + if (!peer) { + GST_INFO_OBJECT (pad, "No peer yet"); + ret = FALSE; + } else { + ret = gst_pad_query (peer, query); + gst_object_unref (peer); + } + + gst_object_unref (self); + return ret; +} + +static gboolean +gst_shape_wipe_src_query (GstPad * pad, GstQuery * query) +{ + GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad)); + gboolean ret; + GstPad *peer = gst_pad_get_peer (self->video_sinkpad); + + GST_DEBUG_OBJECT (pad, "Handling query of type '%s'", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + if (!peer) { + GST_INFO_OBJECT (pad, "No peer yet"); + ret = FALSE; + } else { + ret = gst_pad_query (peer, query); + gst_object_unref (peer); + } + + gst_object_unref (self); + return ret; +} + +static void +gst_shape_wipe_update_qos (GstShapeWipe * self, gdouble proportion, + GstClockTimeDiff diff, GstClockTime timestamp) +{ + GST_OBJECT_LOCK (self); + self->proportion = proportion; + if (G_LIKELY (timestamp != GST_CLOCK_TIME_NONE)) { + if (G_UNLIKELY (diff > 0)) + self->earliest_time = timestamp + 2 * diff + self->frame_duration; + else + self->earliest_time = timestamp + diff; + } else { + self->earliest_time = GST_CLOCK_TIME_NONE; + } + GST_OBJECT_UNLOCK (self); +} + +static void +gst_shape_wipe_reset_qos (GstShapeWipe * self) +{ + gst_shape_wipe_update_qos (self, 0.5, 0, GST_CLOCK_TIME_NONE); +} + +static void +gst_shape_wipe_read_qos (GstShapeWipe * self, gdouble * proportion, + GstClockTime * time) +{ + GST_OBJECT_LOCK (self); + *proportion = self->proportion; + *time = self->earliest_time; + GST_OBJECT_UNLOCK (self); +} + +/* Perform qos calculations before processing the next frame. Returns TRUE if + * the frame should be processed, FALSE if the frame can be dropped entirely */ +static gboolean +gst_shape_wipe_do_qos (GstShapeWipe * self, GstClockTime timestamp) +{ + GstClockTime qostime, earliest_time; + gdouble proportion; + + /* no timestamp, can't do QoS => process frame */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp))) { + GST_LOG_OBJECT (self, "invalid timestamp, can't do QoS, process frame"); + return TRUE; + } + + /* get latest QoS observation values */ + gst_shape_wipe_read_qos (self, &proportion, &earliest_time); + + /* skip qos if we have no observation (yet) => process frame */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (earliest_time))) { + GST_LOG_OBJECT (self, "no observation yet, process frame"); + return TRUE; + } + + /* qos is done on running time */ + qostime = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, + timestamp); + + /* see how our next timestamp relates to the latest qos timestamp */ + GST_LOG_OBJECT (self, "qostime %" GST_TIME_FORMAT ", earliest %" + GST_TIME_FORMAT, GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time)); + + if (qostime != GST_CLOCK_TIME_NONE && qostime <= earliest_time) { + GST_DEBUG_OBJECT (self, "we are late, drop frame"); + return FALSE; + } + + GST_LOG_OBJECT (self, "process frame"); + return TRUE; +} + static GstFlowReturn gst_shape_wipe_blend_16 (GstShapeWipe * self, GstBuffer * inbuf, GstBuffer * maskbuf, GstBuffer * outbuf) @@ -686,6 +845,7 @@ gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer) GstFlowReturn ret = GST_FLOW_OK; GstBuffer *mask = NULL, *outbuf = NULL; GstClockTime timestamp; + gboolean new_outbuf = FALSE; timestamp = GST_BUFFER_TIMESTAMP (buffer); timestamp = @@ -711,13 +871,28 @@ gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer) } g_mutex_unlock (self->mask_mutex); - ret = - gst_pad_alloc_buffer_and_set_caps (self->srcpad, GST_BUFFER_OFFSET_NONE, - GST_BUFFER_SIZE (buffer), GST_PAD_CAPS (self->srcpad), &outbuf); - if (G_UNLIKELY (ret != GST_FLOW_OK)) { + if (!gst_shape_wipe_do_qos (self, GST_BUFFER_TIMESTAMP (buffer))) { gst_buffer_unref (buffer); gst_buffer_unref (mask); - return ret; + return GST_FLOW_OK; + } + + /* Try to blend inplace, if it's not possible + * get a new buffer from downstream. + */ + if (!gst_buffer_is_writable (buffer)) { + ret = + gst_pad_alloc_buffer_and_set_caps (self->srcpad, GST_BUFFER_OFFSET_NONE, + GST_BUFFER_SIZE (buffer), GST_PAD_CAPS (self->srcpad), &outbuf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + gst_buffer_unref (buffer); + gst_buffer_unref (mask); + return ret; + } + gst_buffer_copy_metadata (outbuf, buffer, GST_BUFFER_COPY_ALL); + new_outbuf = TRUE; + } else { + outbuf = buffer; } if (self->mask_bpp == 16) @@ -726,7 +901,9 @@ gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer) ret = gst_shape_wipe_blend_8 (self, buffer, mask, outbuf); gst_buffer_unref (mask); - gst_buffer_unref (buffer); + if (new_outbuf) + gst_buffer_unref (buffer); + if (ret != GST_FLOW_OK) { gst_buffer_unref (outbuf); return ret; @@ -813,6 +990,9 @@ gst_shape_wipe_video_sink_event (GstPad * pad, GstEvent * event) } } /* fall through */ + case GST_EVENT_FLUSH_STOP: + gst_shape_wipe_reset_qos (self); + /* fall through */ default: ret = gst_pad_push_event (self->srcpad, event); break; @@ -839,6 +1019,16 @@ gst_shape_wipe_src_event (GstPad * pad, GstEvent * event) gboolean ret; switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_QOS:{ + GstClockTimeDiff diff; + GstClockTime timestamp; + gdouble proportion; + + gst_event_parse_qos (event, &proportion, &diff, ×tamp); + + gst_shape_wipe_update_qos (self, proportion, diff, timestamp); + } + /* fall through */ default: ret = gst_pad_push_event (self->video_sinkpad, event); break; diff --git a/gst/shapewipe/gstshapewipe.h b/gst/shapewipe/gstshapewipe.h index 00ed776e..2cc44831 100644 --- a/gst/shapewipe/gstshapewipe.h +++ b/gst/shapewipe/gstshapewipe.h @@ -60,6 +60,10 @@ struct _GstShapeWipe gint mask_bpp; gint width, height; + + gdouble proportion; + GstClockTime earliest_time; + GstClockTime frame_duration; }; struct _GstShapeWipeClass |