| /* GStreamer |
| * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> |
| * 2005 Wim Taymans <wim@fluendo.com> |
| * 2005 Andy Wingo <wingo@fluendo.com> |
| * 2005 Thomas Vander Stichele <thomas at apestaart dot org> |
| * 2008 Wim Taymans <wim.taymans@gmail.com> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:gstbasetransform |
| * @title: GstBaseTransform |
| * @short_description: Base class for simple transform filters |
| * @see_also: #GstBaseSrc, #GstBaseSink |
| * |
| * This base class is for filter elements that process data. Elements |
| * that are suitable for implementation using #GstBaseTransform are ones |
| * where the size and caps of the output is known entirely from the input |
| * caps and buffer sizes. These include elements that directly transform |
| * one buffer into another, modify the contents of a buffer in-place, as |
| * well as elements that collate multiple input buffers into one output buffer, |
| * or that expand one input buffer into multiple output buffers. See below |
| * for more concrete use cases. |
| * |
| * It provides for: |
| * |
| * * one sinkpad and one srcpad |
| * * Possible formats on sink and source pad implemented |
| * with custom transform_caps function. By default uses |
| * same format on sink and source. |
| * |
| * * Handles state changes |
| * * Does flushing |
| * * Push mode |
| * * Pull mode if the sub-class transform can operate on arbitrary data |
| * |
| * # Use Cases |
| * |
| * ## Passthrough mode |
| * |
| * * Element has no interest in modifying the buffer. It may want to inspect it, |
| * in which case the element should have a transform_ip function. If there |
| * is no transform_ip function in passthrough mode, the buffer is pushed |
| * intact. |
| * |
| * * The #GstBaseTransformClass.passthrough_on_same_caps variable |
| * will automatically set/unset passthrough based on whether the |
| * element negotiates the same caps on both pads. |
| * |
| * * #GstBaseTransformClass.passthrough_on_same_caps on an element that |
| * doesn't implement a transform_caps function is useful for elements that |
| * only inspect data (such as level) |
| * |
| * * Example elements |
| * |
| * * Level |
| * * Videoscale, audioconvert, videoconvert, audioresample in certain modes. |
| * |
| * ## Modifications in-place - input buffer and output buffer are the same thing. |
| * |
| * * The element must implement a transform_ip function. |
| * * Output buffer size must <= input buffer size |
| * * If the always_in_place flag is set, non-writable buffers will be copied |
| * and passed to the transform_ip function, otherwise a new buffer will be |
| * created and the transform function called. |
| * |
| * * Incoming writable buffers will be passed to the transform_ip function |
| * immediately. |
| * * only implementing transform_ip and not transform implies always_in_place = %TRUE |
| * |
| * * Example elements: |
| * * Volume |
| * * Audioconvert in certain modes (signed/unsigned conversion) |
| * * videoconvert in certain modes (endianness swapping) |
| * |
| * ## Modifications only to the caps/metadata of a buffer |
| * |
| * * The element does not require writable data, but non-writable buffers |
| * should be subbuffered so that the meta-information can be replaced. |
| * |
| * * Elements wishing to operate in this mode should replace the |
| * prepare_output_buffer method to create subbuffers of the input buffer |
| * and set always_in_place to %TRUE |
| * |
| * * Example elements |
| * * Capsfilter when setting caps on outgoing buffers that have |
| * none. |
| * * identity when it is going to re-timestamp buffers by |
| * datarate. |
| * |
| * ## Normal mode |
| * * always_in_place flag is not set, or there is no transform_ip function |
| * * Element will receive an input buffer and output buffer to operate on. |
| * * Output buffer is allocated by calling the prepare_output_buffer function. |
| * * Example elements: |
| * * Videoscale, videoconvert, audioconvert when doing |
| * scaling/conversions |
| * |
| * ## Special output buffer allocations |
| * * Elements which need to do special allocation of their output buffers |
| * beyond allocating output buffers via the negotiated allocator or |
| * buffer pool should implement the prepare_output_buffer method. |
| * |
| * * Example elements: |
| * * efence |
| * |
| * # Sub-class settable flags on GstBaseTransform |
| * |
| * * passthrough |
| * |
| * * Implies that in the current configuration, the sub-class is not interested in modifying the buffers. |
| * * Elements which are always in passthrough mode whenever the same caps has been negotiated on both pads can set the class variable passthrough_on_same_caps to have this behaviour automatically. |
| * |
| * * always_in_place |
| * * Determines whether a non-writable buffer will be copied before passing |
| * to the transform_ip function. |
| * |
| * * Implied %TRUE if no transform function is implemented. |
| * * Implied %FALSE if ONLY transform function is implemented. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "../../../gst/gst_private.h" |
| #include "../../../gst/gst-i18n-lib.h" |
| #include "../../../gst/glib-compat-private.h" |
| #include "gstbasetransform.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_base_transform_debug); |
| #define GST_CAT_DEFAULT gst_base_transform_debug |
| |
| /* BaseTransform signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| #define DEFAULT_PROP_QOS FALSE |
| |
| enum |
| { |
| PROP_0, |
| PROP_QOS |
| }; |
| |
| #define GST_BASE_TRANSFORM_GET_PRIVATE(obj) \ |
| (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_TRANSFORM, GstBaseTransformPrivate)) |
| |
| struct _GstBaseTransformPrivate |
| { |
| /* Set by sub-class */ |
| gboolean passthrough; |
| gboolean always_in_place; |
| |
| GstCaps *cache_caps1; |
| gsize cache_caps1_size; |
| GstCaps *cache_caps2; |
| gsize cache_caps2_size; |
| gboolean have_same_caps; |
| |
| gboolean negotiated; |
| |
| /* QoS *//* with LOCK */ |
| gboolean qos_enabled; |
| gdouble proportion; |
| GstClockTime earliest_time; |
| /* previous buffer had a discont */ |
| gboolean discont; |
| |
| GstPadMode pad_mode; |
| |
| gboolean gap_aware; |
| gboolean prefer_passthrough; |
| |
| /* QoS stats */ |
| guint64 processed; |
| guint64 dropped; |
| |
| GstClockTime position_out; |
| |
| GstBufferPool *pool; |
| gboolean pool_active; |
| GstAllocator *allocator; |
| GstAllocationParams params; |
| GstQuery *query; |
| }; |
| |
| |
| static GstElementClass *parent_class = NULL; |
| |
| static void gst_base_transform_class_init (GstBaseTransformClass * klass); |
| static void gst_base_transform_init (GstBaseTransform * trans, |
| GstBaseTransformClass * klass); |
| static GstFlowReturn default_submit_input_buffer (GstBaseTransform * trans, |
| gboolean is_discont, GstBuffer * input); |
| static GstFlowReturn default_generate_output (GstBaseTransform * trans, |
| GstBuffer ** outbuf); |
| |
| /* we can't use G_DEFINE_ABSTRACT_TYPE because we need the klass in the _init |
| * method to get to the padtemplates */ |
| GType |
| gst_base_transform_get_type (void) |
| { |
| static volatile gsize base_transform_type = 0; |
| |
| if (g_once_init_enter (&base_transform_type)) { |
| GType _type; |
| static const GTypeInfo base_transform_info = { |
| sizeof (GstBaseTransformClass), |
| NULL, |
| NULL, |
| (GClassInitFunc) gst_base_transform_class_init, |
| NULL, |
| NULL, |
| sizeof (GstBaseTransform), |
| 0, |
| (GInstanceInitFunc) gst_base_transform_init, |
| }; |
| |
| _type = g_type_register_static (GST_TYPE_ELEMENT, |
| "GstBaseTransform", &base_transform_info, G_TYPE_FLAG_ABSTRACT); |
| g_once_init_leave (&base_transform_type, _type); |
| } |
| return base_transform_type; |
| } |
| |
| static void gst_base_transform_finalize (GObject * object); |
| static void gst_base_transform_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_base_transform_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static gboolean gst_base_transform_src_activate_mode (GstPad * pad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static gboolean gst_base_transform_sink_activate_mode (GstPad * pad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static gboolean gst_base_transform_activate (GstBaseTransform * trans, |
| gboolean active); |
| static gboolean gst_base_transform_get_unit_size (GstBaseTransform * trans, |
| GstCaps * caps, gsize * size); |
| |
| static gboolean gst_base_transform_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_base_transform_src_eventfunc (GstBaseTransform * trans, |
| GstEvent * event); |
| static gboolean gst_base_transform_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_base_transform_sink_eventfunc (GstBaseTransform * trans, |
| GstEvent * event); |
| static GstFlowReturn gst_base_transform_getrange (GstPad * pad, |
| GstObject * parent, guint64 offset, guint length, GstBuffer ** buffer); |
| static GstFlowReturn gst_base_transform_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static GstCaps *gst_base_transform_default_transform_caps (GstBaseTransform * |
| trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter); |
| static GstCaps *gst_base_transform_default_fixate_caps (GstBaseTransform * |
| trans, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); |
| static GstCaps *gst_base_transform_query_caps (GstBaseTransform * trans, |
| GstPad * pad, GstCaps * filter); |
| static gboolean gst_base_transform_acceptcaps_default (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps); |
| static gboolean gst_base_transform_setcaps (GstBaseTransform * trans, |
| GstPad * pad, GstCaps * caps); |
| static gboolean gst_base_transform_default_decide_allocation (GstBaseTransform |
| * trans, GstQuery * query); |
| static gboolean gst_base_transform_default_propose_allocation (GstBaseTransform |
| * trans, GstQuery * decide_query, GstQuery * query); |
| static gboolean gst_base_transform_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_base_transform_default_query (GstBaseTransform * trans, |
| GstPadDirection direction, GstQuery * query); |
| static gboolean gst_base_transform_default_transform_size (GstBaseTransform * |
| trans, GstPadDirection direction, GstCaps * caps, gsize size, |
| GstCaps * othercaps, gsize * othersize); |
| |
| static GstFlowReturn default_prepare_output_buffer (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstBuffer ** outbuf); |
| static gboolean default_copy_metadata (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstBuffer * outbuf); |
| static gboolean |
| gst_base_transform_default_transform_meta (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstMeta * meta, GstBuffer * outbuf); |
| |
| /* static guint gst_base_transform_signals[LAST_SIGNAL] = { 0 }; */ |
| |
| |
| static void |
| gst_base_transform_finalize (GObject * object) |
| { |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_base_transform_class_init (GstBaseTransformClass * klass) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_base_transform_debug, "basetransform", 0, |
| "basetransform element"); |
| |
| GST_DEBUG ("gst_base_transform_class_init"); |
| |
| g_type_class_add_private (klass, sizeof (GstBaseTransformPrivate)); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->set_property = gst_base_transform_set_property; |
| gobject_class->get_property = gst_base_transform_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_QOS, |
| g_param_spec_boolean ("qos", "QoS", "Handle Quality-of-Service events", |
| DEFAULT_PROP_QOS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gobject_class->finalize = gst_base_transform_finalize; |
| |
| klass->passthrough_on_same_caps = FALSE; |
| klass->transform_ip_on_passthrough = TRUE; |
| |
| klass->transform_caps = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_transform_caps); |
| klass->fixate_caps = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_fixate_caps); |
| klass->accept_caps = |
| GST_DEBUG_FUNCPTR (gst_base_transform_acceptcaps_default); |
| klass->query = GST_DEBUG_FUNCPTR (gst_base_transform_default_query); |
| klass->decide_allocation = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_decide_allocation); |
| klass->propose_allocation = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_propose_allocation); |
| klass->transform_size = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_transform_size); |
| klass->transform_meta = |
| GST_DEBUG_FUNCPTR (gst_base_transform_default_transform_meta); |
| |
| klass->sink_event = GST_DEBUG_FUNCPTR (gst_base_transform_sink_eventfunc); |
| klass->src_event = GST_DEBUG_FUNCPTR (gst_base_transform_src_eventfunc); |
| klass->prepare_output_buffer = |
| GST_DEBUG_FUNCPTR (default_prepare_output_buffer); |
| klass->copy_metadata = GST_DEBUG_FUNCPTR (default_copy_metadata); |
| klass->submit_input_buffer = GST_DEBUG_FUNCPTR (default_submit_input_buffer); |
| klass->generate_output = GST_DEBUG_FUNCPTR (default_generate_output); |
| } |
| |
| static void |
| gst_base_transform_init (GstBaseTransform * trans, |
| GstBaseTransformClass * bclass) |
| { |
| GstPadTemplate *pad_template; |
| GstBaseTransformPrivate *priv; |
| |
| GST_DEBUG ("gst_base_transform_init"); |
| |
| priv = trans->priv = GST_BASE_TRANSFORM_GET_PRIVATE (trans); |
| |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink"); |
| g_return_if_fail (pad_template != NULL); |
| trans->sinkpad = gst_pad_new_from_template (pad_template, "sink"); |
| gst_pad_set_event_function (trans->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_sink_event)); |
| gst_pad_set_chain_function (trans->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_chain)); |
| gst_pad_set_activatemode_function (trans->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_sink_activate_mode)); |
| gst_pad_set_query_function (trans->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_query)); |
| gst_element_add_pad (GST_ELEMENT (trans), trans->sinkpad); |
| |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); |
| g_return_if_fail (pad_template != NULL); |
| trans->srcpad = gst_pad_new_from_template (pad_template, "src"); |
| gst_pad_set_event_function (trans->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_src_event)); |
| gst_pad_set_getrange_function (trans->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_getrange)); |
| gst_pad_set_activatemode_function (trans->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_src_activate_mode)); |
| gst_pad_set_query_function (trans->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_transform_query)); |
| gst_element_add_pad (GST_ELEMENT (trans), trans->srcpad); |
| |
| priv->qos_enabled = DEFAULT_PROP_QOS; |
| priv->cache_caps1 = NULL; |
| priv->cache_caps2 = NULL; |
| priv->pad_mode = GST_PAD_MODE_NONE; |
| priv->gap_aware = FALSE; |
| priv->prefer_passthrough = TRUE; |
| |
| priv->passthrough = FALSE; |
| if (bclass->transform == NULL) { |
| /* If no transform function, always_in_place is TRUE */ |
| GST_DEBUG_OBJECT (trans, "setting in_place TRUE"); |
| priv->always_in_place = TRUE; |
| |
| if (bclass->transform_ip == NULL) { |
| GST_DEBUG_OBJECT (trans, "setting passthrough TRUE"); |
| priv->passthrough = TRUE; |
| } |
| } |
| |
| priv->processed = 0; |
| priv->dropped = 0; |
| } |
| |
| static GstCaps * |
| gst_base_transform_default_transform_caps (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, GstCaps * filter) |
| { |
| GstCaps *ret; |
| |
| GST_DEBUG_OBJECT (trans, "identity from: %" GST_PTR_FORMAT, caps); |
| /* no transform function, use the identity transform */ |
| if (filter) { |
| ret = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); |
| } else { |
| ret = gst_caps_ref (caps); |
| } |
| return ret; |
| } |
| |
| /* given @caps on the src or sink pad (given by @direction) |
| * calculate the possible caps on the other pad. |
| * |
| * Returns new caps, unref after usage. |
| */ |
| static GstCaps * |
| gst_base_transform_transform_caps (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, GstCaps * filter) |
| { |
| GstCaps *ret = NULL; |
| GstBaseTransformClass *klass; |
| |
| if (caps == NULL) |
| return NULL; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| /* if there is a custom transform function, use this */ |
| if (klass->transform_caps) { |
| GST_DEBUG_OBJECT (trans, "transform caps (direction = %d)", direction); |
| |
| GST_LOG_OBJECT (trans, "from: %" GST_PTR_FORMAT, caps); |
| ret = klass->transform_caps (trans, direction, caps, filter); |
| GST_LOG_OBJECT (trans, " to: %" GST_PTR_FORMAT, ret); |
| |
| #ifdef GST_ENABLE_EXTRA_CHECKS |
| if (filter) { |
| if (!gst_caps_is_subset (ret, filter)) { |
| GstCaps *intersection; |
| |
| GST_ERROR_OBJECT (trans, |
| "transform_caps returned caps %" GST_PTR_FORMAT |
| " which are not a real subset of the filter caps %" |
| GST_PTR_FORMAT, ret, filter); |
| g_warning ("%s: transform_caps returned caps which are not a real " |
| "subset of the filter caps", GST_ELEMENT_NAME (trans)); |
| |
| intersection = |
| gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (ret); |
| ret = intersection; |
| } |
| } |
| #endif |
| } |
| |
| GST_DEBUG_OBJECT (trans, "to: %" GST_PTR_FORMAT, ret); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_default_transform_meta (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstMeta * meta, GstBuffer * outbuf) |
| { |
| const GstMetaInfo *info = meta->info; |
| const gchar *const *tags; |
| |
| tags = gst_meta_api_type_get_tags (info->api); |
| |
| if (!tags) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_base_transform_default_transform_size (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, gsize size, |
| GstCaps * othercaps, gsize * othersize) |
| { |
| gsize inunitsize, outunitsize, units; |
| GstBaseTransformClass *klass; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (klass->get_unit_size == NULL) { |
| /* if there is no transform_size and no unit_size, it means the |
| * element does not modify the size of a buffer */ |
| *othersize = size; |
| } else { |
| /* there is no transform_size function, we have to use the unit_size |
| * functions. This method assumes there is a fixed unit_size associated with |
| * each caps. We provide the same amount of units on both sides. */ |
| if (!gst_base_transform_get_unit_size (trans, caps, &inunitsize)) |
| goto no_in_size; |
| |
| GST_DEBUG_OBJECT (trans, |
| "input size %" G_GSIZE_FORMAT ", input unit size %" G_GSIZE_FORMAT, |
| size, inunitsize); |
| |
| /* input size must be a multiple of the unit_size of the input caps */ |
| if (inunitsize == 0 || (size % inunitsize != 0)) |
| goto no_multiple; |
| |
| /* get the amount of units */ |
| units = size / inunitsize; |
| |
| /* now get the unit size of the output */ |
| if (!gst_base_transform_get_unit_size (trans, othercaps, &outunitsize)) |
| goto no_out_size; |
| |
| /* the output size is the unit_size times the amount of units on the |
| * input */ |
| *othersize = units * outunitsize; |
| GST_DEBUG_OBJECT (trans, "transformed size to %" G_GSIZE_FORMAT, |
| *othersize); |
| } |
| return TRUE; |
| |
| /* ERRORS */ |
| no_in_size: |
| { |
| GST_DEBUG_OBJECT (trans, "could not get in_size"); |
| g_warning ("%s: could not get in_size", GST_ELEMENT_NAME (trans)); |
| return FALSE; |
| } |
| no_multiple: |
| { |
| GST_DEBUG_OBJECT (trans, "Size %" G_GSIZE_FORMAT " is not a multiple of" |
| "unit size %" G_GSIZE_FORMAT, size, inunitsize); |
| g_warning ("%s: size %" G_GSIZE_FORMAT " is not a multiple of unit size %" |
| G_GSIZE_FORMAT, GST_ELEMENT_NAME (trans), size, inunitsize); |
| return FALSE; |
| } |
| no_out_size: |
| { |
| GST_DEBUG_OBJECT (trans, "could not get out_size"); |
| g_warning ("%s: could not get out_size", GST_ELEMENT_NAME (trans)); |
| return FALSE; |
| } |
| } |
| |
| /* transform a buffer of @size with @caps on the pad with @direction to |
| * the size of a buffer with @othercaps and store the result in @othersize |
| * |
| * We have two ways of doing this: |
| * 1) use a custom transform size function, this is for complicated custom |
| * cases with no fixed unit_size. |
| * 2) use the unit_size functions where there is a relationship between the |
| * caps and the size of a buffer. |
| */ |
| static gboolean |
| gst_base_transform_transform_size (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, |
| gsize size, GstCaps * othercaps, gsize * othersize) |
| { |
| GstBaseTransformClass *klass; |
| gboolean ret = FALSE; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| GST_DEBUG_OBJECT (trans, |
| "asked to transform size %" G_GSIZE_FORMAT " for caps %" |
| GST_PTR_FORMAT " to size for caps %" GST_PTR_FORMAT " in direction %s", |
| size, caps, othercaps, direction == GST_PAD_SRC ? "SRC" : "SINK"); |
| |
| if (klass->transform_size) { |
| /* if there is a custom transform function, use this */ |
| ret = klass->transform_size (trans, direction, caps, size, othercaps, |
| othersize); |
| } |
| return ret; |
| } |
| |
| /* get the caps that can be handled by @pad. We perform: |
| * |
| * - take the caps of peer of otherpad, |
| * - filter against the padtemplate of otherpad, |
| * - calculate all transforms of remaining caps |
| * - filter against template of @pad |
| * |
| * If there is no peer, we simply return the caps of the padtemplate of pad. |
| */ |
| static GstCaps * |
| gst_base_transform_query_caps (GstBaseTransform * trans, GstPad * pad, |
| GstCaps * filter) |
| { |
| GstPad *otherpad; |
| GstCaps *peercaps = NULL, *caps, *temp, *peerfilter = NULL; |
| GstCaps *templ, *otempl; |
| |
| otherpad = (pad == trans->srcpad) ? trans->sinkpad : trans->srcpad; |
| |
| templ = gst_pad_get_pad_template_caps (pad); |
| otempl = gst_pad_get_pad_template_caps (otherpad); |
| |
| /* first prepare the filter to be send onwards. We need to filter and |
| * transform it to valid caps for the otherpad. */ |
| if (filter) { |
| GST_DEBUG_OBJECT (pad, "filter caps %" GST_PTR_FORMAT, filter); |
| |
| /* filtered against our padtemplate of this pad */ |
| GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ); |
| temp = gst_caps_intersect_full (filter, templ, GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); |
| |
| /* then see what we can transform this to */ |
| peerfilter = gst_base_transform_transform_caps (trans, |
| GST_PAD_DIRECTION (pad), temp, NULL); |
| GST_DEBUG_OBJECT (pad, "transformed %" GST_PTR_FORMAT, peerfilter); |
| gst_caps_unref (temp); |
| |
| if (peerfilter && !gst_caps_is_empty (peerfilter)) { |
| /* and filter against the template of the other pad */ |
| GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, otempl); |
| /* We keep the caps sorted like the returned caps */ |
| temp = |
| gst_caps_intersect_full (peerfilter, otempl, |
| GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); |
| gst_caps_unref (peerfilter); |
| peerfilter = temp; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (pad, "peer filter caps %" GST_PTR_FORMAT, peerfilter); |
| |
| if (peerfilter && gst_caps_is_empty (peerfilter)) { |
| GST_DEBUG_OBJECT (pad, "peer filter caps are empty"); |
| caps = peerfilter; |
| peerfilter = NULL; |
| goto done; |
| } |
| |
| /* query the peer with the transformed filter */ |
| peercaps = gst_pad_peer_query_caps (otherpad, peerfilter); |
| |
| if (peerfilter) |
| gst_caps_unref (peerfilter); |
| |
| if (peercaps) { |
| GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peercaps); |
| |
| /* filtered against our padtemplate on the other side */ |
| GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, otempl); |
| temp = gst_caps_intersect_full (peercaps, otempl, GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); |
| } else { |
| temp = gst_caps_ref (otempl); |
| } |
| |
| /* then see what we can transform this to */ |
| caps = gst_base_transform_transform_caps (trans, |
| GST_PAD_DIRECTION (otherpad), temp, filter); |
| GST_DEBUG_OBJECT (pad, "transformed %" GST_PTR_FORMAT, caps); |
| gst_caps_unref (temp); |
| if (caps == NULL || gst_caps_is_empty (caps)) |
| goto done; |
| |
| if (peercaps) { |
| /* and filter against the template of this pad */ |
| GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ); |
| /* We keep the caps sorted like the returned caps */ |
| temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); |
| gst_caps_unref (caps); |
| caps = temp; |
| |
| if (trans->priv->prefer_passthrough) { |
| /* Now try if we can put the untransformed downstream caps first */ |
| temp = gst_caps_intersect_full (peercaps, caps, GST_CAPS_INTERSECT_FIRST); |
| if (!gst_caps_is_empty (temp)) { |
| caps = gst_caps_merge (temp, caps); |
| } else { |
| gst_caps_unref (temp); |
| } |
| } |
| } else { |
| gst_caps_unref (caps); |
| /* no peer or the peer can do anything, our padtemplate is enough then */ |
| if (filter) { |
| caps = gst_caps_intersect_full (filter, templ, GST_CAPS_INTERSECT_FIRST); |
| } else { |
| caps = gst_caps_ref (templ); |
| } |
| } |
| |
| done: |
| GST_DEBUG_OBJECT (trans, "returning %" GST_PTR_FORMAT, caps); |
| |
| if (peercaps) |
| gst_caps_unref (peercaps); |
| |
| gst_caps_unref (templ); |
| gst_caps_unref (otempl); |
| |
| return caps; |
| } |
| |
| /* takes ownership of the pool, allocator and query */ |
| static gboolean |
| gst_base_transform_set_allocation (GstBaseTransform * trans, |
| GstBufferPool * pool, GstAllocator * allocator, |
| GstAllocationParams * params, GstQuery * query) |
| { |
| GstAllocator *oldalloc; |
| GstBufferPool *oldpool; |
| GstQuery *oldquery; |
| GstBaseTransformPrivate *priv = trans->priv; |
| |
| GST_OBJECT_LOCK (trans); |
| oldpool = priv->pool; |
| priv->pool = pool; |
| priv->pool_active = FALSE; |
| |
| oldalloc = priv->allocator; |
| priv->allocator = allocator; |
| |
| oldquery = priv->query; |
| priv->query = query; |
| |
| if (params) |
| priv->params = *params; |
| else |
| gst_allocation_params_init (&priv->params); |
| GST_OBJECT_UNLOCK (trans); |
| |
| if (oldpool) { |
| GST_DEBUG_OBJECT (trans, "deactivating old pool %p", oldpool); |
| gst_buffer_pool_set_active (oldpool, FALSE); |
| gst_object_unref (oldpool); |
| } |
| if (oldalloc) { |
| gst_object_unref (oldalloc); |
| } |
| if (oldquery) { |
| gst_query_unref (oldquery); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_base_transform_default_decide_allocation (GstBaseTransform * trans, |
| GstQuery * query) |
| { |
| guint i, n_metas; |
| GstBaseTransformClass *klass; |
| GstCaps *outcaps; |
| GstBufferPool *pool; |
| guint size, min, max; |
| GstAllocator *allocator; |
| GstAllocationParams params; |
| GstStructure *config; |
| gboolean update_allocator; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| n_metas = gst_query_get_n_allocation_metas (query); |
| for (i = 0; i < n_metas; i++) { |
| GType api; |
| const GstStructure *params; |
| gboolean remove; |
| |
| api = gst_query_parse_nth_allocation_meta (query, i, ¶ms); |
| |
| /* by default we remove all metadata, subclasses should implement a |
| * filter_meta function */ |
| if (gst_meta_api_type_has_tag (api, _gst_meta_tag_memory)) { |
| /* remove all memory dependent metadata because we are going to have to |
| * allocate different memory for input and output. */ |
| GST_LOG_OBJECT (trans, "removing memory specific metadata %s", |
| g_type_name (api)); |
| remove = TRUE; |
| } else if (G_LIKELY (klass->filter_meta)) { |
| /* remove if the subclass said so */ |
| remove = !klass->filter_meta (trans, query, api, params); |
| GST_LOG_OBJECT (trans, "filter_meta for api %s returned: %s", |
| g_type_name (api), (remove ? "remove" : "keep")); |
| } else { |
| GST_LOG_OBJECT (trans, "removing metadata %s", g_type_name (api)); |
| remove = TRUE; |
| } |
| |
| if (remove) { |
| gst_query_remove_nth_allocation_meta (query, i); |
| i--; |
| n_metas--; |
| } |
| } |
| |
| gst_query_parse_allocation (query, &outcaps, NULL); |
| |
| /* we got configuration from our peer or the decide_allocation method, |
| * parse them */ |
| if (gst_query_get_n_allocation_params (query) > 0) { |
| /* try the allocator */ |
| gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); |
| update_allocator = TRUE; |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| update_allocator = FALSE; |
| } |
| |
| if (gst_query_get_n_allocation_pools (query) > 0) { |
| gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); |
| |
| if (pool == NULL) { |
| /* no pool, we can make our own */ |
| GST_DEBUG_OBJECT (trans, "no pool, making new pool"); |
| pool = gst_buffer_pool_new (); |
| } |
| } else { |
| pool = NULL; |
| size = min = max = 0; |
| } |
| |
| /* now configure */ |
| if (pool) { |
| config = gst_buffer_pool_get_config (pool); |
| gst_buffer_pool_config_set_params (config, outcaps, size, min, max); |
| gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); |
| |
| /* buffer pool may have to do some changes */ |
| if (!gst_buffer_pool_set_config (pool, config)) { |
| config = gst_buffer_pool_get_config (pool); |
| |
| /* If change are not acceptable, fallback to generic pool */ |
| if (!gst_buffer_pool_config_validate_params (config, outcaps, size, min, |
| max)) { |
| GST_DEBUG_OBJECT (trans, "unsuported pool, making new pool"); |
| |
| gst_object_unref (pool); |
| pool = gst_buffer_pool_new (); |
| gst_buffer_pool_config_set_params (config, outcaps, size, min, max); |
| gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); |
| } |
| |
| if (!gst_buffer_pool_set_config (pool, config)) |
| goto config_failed; |
| } |
| } |
| |
| if (update_allocator) |
| gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); |
| else |
| gst_query_add_allocation_param (query, allocator, ¶ms); |
| if (allocator) |
| gst_object_unref (allocator); |
| |
| if (pool) { |
| gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); |
| gst_object_unref (pool); |
| } |
| |
| return TRUE; |
| |
| config_failed: |
| if (pool) |
| gst_object_unref (pool); |
| |
| GST_ELEMENT_ERROR (trans, RESOURCE, SETTINGS, |
| ("Failed to configure the buffer pool"), |
| ("Configuration is most likely invalid, please report this issue.")); |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_base_transform_do_bufferpool (GstBaseTransform * trans, GstCaps * outcaps) |
| { |
| GstQuery *query; |
| gboolean result = TRUE; |
| GstBufferPool *pool = NULL; |
| GstBaseTransformClass *klass; |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstAllocator *allocator; |
| GstAllocationParams params; |
| |
| /* there are these possibilities: |
| * |
| * 1) we negotiated passthrough, we can proxy the bufferpool directly and we |
| * will do that whenever some upstream does an allocation query. |
| * 2) we need to do a transform, we need to get a bufferpool from downstream |
| * and configure it. When upstream does the ALLOCATION query, the |
| * propose_allocation vmethod will be called and we will configure the |
| * upstream allocator with our proposed values then. |
| */ |
| if (priv->passthrough || priv->always_in_place) { |
| /* we are in passthrough, the input buffer is never copied and always passed |
| * along. We never allocate an output buffer on the srcpad. What we do is |
| * let the upstream element decide if it wants to use a bufferpool and |
| * then we will proxy the downstream pool */ |
| GST_DEBUG_OBJECT (trans, "we're passthough, delay bufferpool"); |
| gst_base_transform_set_allocation (trans, NULL, NULL, NULL, NULL); |
| return TRUE; |
| } |
| |
| /* not passthrough, we need to allocate */ |
| /* find a pool for the negotiated caps now */ |
| GST_DEBUG_OBJECT (trans, "doing allocation query"); |
| query = gst_query_new_allocation (outcaps, TRUE); |
| if (!gst_pad_peer_query (trans->srcpad, query)) { |
| /* not a problem, just debug a little */ |
| GST_DEBUG_OBJECT (trans, "peer ALLOCATION query failed"); |
| } |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| GST_DEBUG_OBJECT (trans, "calling decide_allocation"); |
| g_assert (klass->decide_allocation != NULL); |
| result = klass->decide_allocation (trans, query); |
| |
| GST_DEBUG_OBJECT (trans, "ALLOCATION (%d) params: %" GST_PTR_FORMAT, result, |
| query); |
| |
| if (!result) |
| goto no_decide_allocation; |
| |
| /* check again in case the sub-class have switch to passthrough/in-place |
| * after looking at the meta APIs */ |
| if (priv->passthrough || priv->always_in_place) { |
| GST_DEBUG_OBJECT (trans, "no doing passthrough, delay bufferpool"); |
| gst_base_transform_set_allocation (trans, NULL, NULL, NULL, NULL); |
| gst_query_unref (query); |
| return TRUE; |
| } |
| |
| /* we got configuration from our peer or the decide_allocation method, |
| * parse them */ |
| if (gst_query_get_n_allocation_params (query) > 0) { |
| gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| } |
| |
| if (gst_query_get_n_allocation_pools (query) > 0) |
| gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL); |
| |
| /* now store */ |
| result = |
| gst_base_transform_set_allocation (trans, pool, allocator, ¶ms, |
| query); |
| |
| return result; |
| |
| /* Errors */ |
| no_decide_allocation: |
| { |
| GST_WARNING_OBJECT (trans, "Subclass failed to decide allocation"); |
| gst_query_unref (query); |
| |
| return result; |
| } |
| } |
| |
| /* function triggered when the in and out caps are negotiated and need |
| * to be configured in the subclass. */ |
| static gboolean |
| gst_base_transform_configure_caps (GstBaseTransform * trans, GstCaps * in, |
| GstCaps * out) |
| { |
| gboolean ret = TRUE; |
| GstBaseTransformClass *klass; |
| GstBaseTransformPrivate *priv = trans->priv; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| GST_DEBUG_OBJECT (trans, "in caps: %" GST_PTR_FORMAT, in); |
| GST_DEBUG_OBJECT (trans, "out caps: %" GST_PTR_FORMAT, out); |
| |
| /* clear the cache */ |
| gst_caps_replace (&priv->cache_caps1, NULL); |
| gst_caps_replace (&priv->cache_caps2, NULL); |
| |
| /* figure out same caps state */ |
| priv->have_same_caps = gst_caps_is_equal (in, out); |
| GST_DEBUG_OBJECT (trans, "have_same_caps: %d", priv->have_same_caps); |
| |
| /* Set the passthrough if the class wants passthrough_on_same_caps |
| * and we have the same caps on each pad */ |
| if (klass->passthrough_on_same_caps) |
| gst_base_transform_set_passthrough (trans, priv->have_same_caps); |
| |
| /* now configure the element with the caps */ |
| if (klass->set_caps) { |
| GST_DEBUG_OBJECT (trans, "Calling set_caps method to setup caps"); |
| ret = klass->set_caps (trans, in, out); |
| } |
| |
| return ret; |
| } |
| |
| static GstCaps * |
| gst_base_transform_default_fixate_caps (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) |
| { |
| othercaps = gst_caps_fixate (othercaps); |
| GST_DEBUG_OBJECT (trans, "fixated to %" GST_PTR_FORMAT, othercaps); |
| |
| return othercaps; |
| } |
| |
| /* given a fixed @caps on @pad, create the best possible caps for the |
| * other pad. |
| * @caps must be fixed when calling this function. |
| * |
| * This function calls the transform caps vmethod of the basetransform to figure |
| * out the possible target formats. It then tries to select the best format from |
| * this list by: |
| * |
| * - attempt passthrough if the target caps is a superset of the input caps |
| * - fixating by using peer caps |
| * - fixating with transform fixate function |
| * - fixating with pad fixate functions. |
| * |
| * this function returns a caps that can be transformed into and is accepted by |
| * the peer element. |
| */ |
| static GstCaps * |
| gst_base_transform_find_transform (GstBaseTransform * trans, GstPad * pad, |
| GstCaps * caps) |
| { |
| GstBaseTransformClass *klass; |
| GstPad *otherpad, *otherpeer; |
| GstCaps *othercaps; |
| gboolean is_fixed; |
| |
| /* caps must be fixed here, this is a programming error if it's not */ |
| g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| otherpad = (pad == trans->srcpad) ? trans->sinkpad : trans->srcpad; |
| otherpeer = gst_pad_get_peer (otherpad); |
| |
| /* see how we can transform the input caps. We need to do this even for |
| * passthrough because it might be possible that this element cannot support |
| * passthrough at all. */ |
| othercaps = gst_base_transform_transform_caps (trans, |
| GST_PAD_DIRECTION (pad), caps, NULL); |
| |
| /* The caps we can actually output is the intersection of the transformed |
| * caps with the pad template for the pad */ |
| if (othercaps && !gst_caps_is_empty (othercaps)) { |
| GstCaps *intersect, *templ_caps; |
| |
| templ_caps = gst_pad_get_pad_template_caps (otherpad); |
| GST_DEBUG_OBJECT (trans, |
| "intersecting against padtemplate %" GST_PTR_FORMAT, templ_caps); |
| |
| intersect = |
| gst_caps_intersect_full (othercaps, templ_caps, |
| GST_CAPS_INTERSECT_FIRST); |
| |
| gst_caps_unref (othercaps); |
| gst_caps_unref (templ_caps); |
| othercaps = intersect; |
| } |
| |
| /* check if transform is empty */ |
| if (!othercaps || gst_caps_is_empty (othercaps)) |
| goto no_transform; |
| |
| /* if the othercaps are not fixed, we need to fixate them, first attempt |
| * is by attempting passthrough if the othercaps are a superset of caps. */ |
| /* FIXME. maybe the caps is not fixed because it has multiple structures of |
| * fixed caps */ |
| is_fixed = gst_caps_is_fixed (othercaps); |
| if (!is_fixed) { |
| GST_DEBUG_OBJECT (trans, |
| "transform returned non fixed %" GST_PTR_FORMAT, othercaps); |
| |
| /* Now let's see what the peer suggests based on our transformed caps */ |
| if (otherpeer) { |
| GstCaps *peercaps, *intersection, *templ_caps; |
| |
| GST_DEBUG_OBJECT (trans, |
| "Checking peer caps with filter %" GST_PTR_FORMAT, othercaps); |
| |
| peercaps = gst_pad_query_caps (otherpeer, othercaps); |
| GST_DEBUG_OBJECT (trans, "Resulted in %" GST_PTR_FORMAT, peercaps); |
| if (!gst_caps_is_empty (peercaps)) { |
| templ_caps = gst_pad_get_pad_template_caps (otherpad); |
| |
| GST_DEBUG_OBJECT (trans, |
| "Intersecting with template caps %" GST_PTR_FORMAT, templ_caps); |
| |
| intersection = |
| gst_caps_intersect_full (peercaps, templ_caps, |
| GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (trans, "Intersection: %" GST_PTR_FORMAT, |
| intersection); |
| gst_caps_unref (peercaps); |
| gst_caps_unref (templ_caps); |
| peercaps = intersection; |
| |
| GST_DEBUG_OBJECT (trans, |
| "Intersecting with transformed caps %" GST_PTR_FORMAT, othercaps); |
| intersection = |
| gst_caps_intersect_full (peercaps, othercaps, |
| GST_CAPS_INTERSECT_FIRST); |
| GST_DEBUG_OBJECT (trans, "Intersection: %" GST_PTR_FORMAT, |
| intersection); |
| gst_caps_unref (peercaps); |
| gst_caps_unref (othercaps); |
| othercaps = intersection; |
| } else { |
| gst_caps_unref (othercaps); |
| othercaps = peercaps; |
| } |
| |
| is_fixed = gst_caps_is_fixed (othercaps); |
| } else { |
| GST_DEBUG_OBJECT (trans, "no peer, doing passthrough"); |
| gst_caps_unref (othercaps); |
| othercaps = gst_caps_ref (caps); |
| is_fixed = TRUE; |
| } |
| } |
| if (gst_caps_is_empty (othercaps)) |
| goto no_transform_possible; |
| |
| GST_DEBUG ("have %sfixed caps %" GST_PTR_FORMAT, (is_fixed ? "" : "non-"), |
| othercaps); |
| |
| /* second attempt at fixation, call the fixate vmethod */ |
| /* caps could be fixed but the subclass may want to add fields */ |
| if (klass->fixate_caps) { |
| GST_DEBUG_OBJECT (trans, "calling fixate_caps for %" GST_PTR_FORMAT |
| " using caps %" GST_PTR_FORMAT " on pad %s:%s", othercaps, caps, |
| GST_DEBUG_PAD_NAME (otherpad)); |
| /* note that we pass the complete array of structures to the fixate |
| * function, it needs to truncate itself */ |
| othercaps = |
| klass->fixate_caps (trans, GST_PAD_DIRECTION (pad), caps, othercaps); |
| is_fixed = gst_caps_is_fixed (othercaps); |
| GST_DEBUG_OBJECT (trans, "after fixating %" GST_PTR_FORMAT, othercaps); |
| } |
| |
| /* caps should be fixed now, if not we have to fail. */ |
| if (!is_fixed) |
| goto could_not_fixate; |
| |
| /* and peer should accept */ |
| if (otherpeer && !gst_pad_query_accept_caps (otherpeer, othercaps)) |
| goto peer_no_accept; |
| |
| GST_DEBUG_OBJECT (trans, "Input caps were %" GST_PTR_FORMAT |
| ", and got final caps %" GST_PTR_FORMAT, caps, othercaps); |
| |
| if (otherpeer) |
| gst_object_unref (otherpeer); |
| |
| return othercaps; |
| |
| /* ERRORS */ |
| no_transform: |
| { |
| GST_DEBUG_OBJECT (trans, |
| "transform returned useless %" GST_PTR_FORMAT, othercaps); |
| goto error_cleanup; |
| } |
| no_transform_possible: |
| { |
| GST_DEBUG_OBJECT (trans, |
| "transform could not transform %" GST_PTR_FORMAT |
| " in anything we support", caps); |
| goto error_cleanup; |
| } |
| could_not_fixate: |
| { |
| GST_DEBUG_OBJECT (trans, "FAILED to fixate %" GST_PTR_FORMAT, othercaps); |
| goto error_cleanup; |
| } |
| peer_no_accept: |
| { |
| GST_DEBUG_OBJECT (trans, "FAILED to get peer of %" GST_PTR_FORMAT |
| " to accept %" GST_PTR_FORMAT, otherpad, othercaps); |
| goto error_cleanup; |
| } |
| error_cleanup: |
| { |
| if (otherpeer) |
| gst_object_unref (otherpeer); |
| if (othercaps) |
| gst_caps_unref (othercaps); |
| return NULL; |
| } |
| } |
| |
| static gboolean |
| gst_base_transform_acceptcaps_default (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps) |
| { |
| GstPad *pad, *otherpad; |
| GstCaps *templ, *otempl, *ocaps = NULL; |
| gboolean ret = TRUE; |
| |
| pad = |
| (direction == |
| GST_PAD_SINK) ? GST_BASE_TRANSFORM_SINK_PAD (trans) : |
| GST_BASE_TRANSFORM_SRC_PAD (trans); |
| otherpad = |
| (direction == |
| GST_PAD_SINK) ? GST_BASE_TRANSFORM_SRC_PAD (trans) : |
| GST_BASE_TRANSFORM_SINK_PAD (trans); |
| |
| GST_DEBUG_OBJECT (trans, "accept caps %" GST_PTR_FORMAT, caps); |
| |
| templ = gst_pad_get_pad_template_caps (pad); |
| otempl = gst_pad_get_pad_template_caps (otherpad); |
| |
| /* get all the formats we can handle on this pad */ |
| GST_DEBUG_OBJECT (trans, "intersect with pad template: %" GST_PTR_FORMAT, |
| templ); |
| if (!gst_caps_can_intersect (caps, templ)) |
| goto reject_caps; |
| |
| GST_DEBUG_OBJECT (trans, "trying to transform with filter: %" |
| GST_PTR_FORMAT " (the other pad template)", otempl); |
| ocaps = gst_base_transform_transform_caps (trans, direction, caps, otempl); |
| if (!ocaps || gst_caps_is_empty (ocaps)) |
| goto no_transform_possible; |
| |
| done: |
| GST_DEBUG_OBJECT (trans, "accept-caps result: %d", ret); |
| if (ocaps) |
| gst_caps_unref (ocaps); |
| gst_caps_unref (templ); |
| gst_caps_unref (otempl); |
| return ret; |
| |
| /* ERRORS */ |
| reject_caps: |
| { |
| GST_DEBUG_OBJECT (trans, "caps can't intersect with the template"); |
| ret = FALSE; |
| goto done; |
| } |
| no_transform_possible: |
| { |
| GST_DEBUG_OBJECT (trans, |
| "transform could not transform %" GST_PTR_FORMAT |
| " in anything we support", caps); |
| ret = FALSE; |
| goto done; |
| } |
| } |
| |
| /* called when new caps arrive on the sink pad, |
| * We try to find the best caps for the other side using our _find_transform() |
| * function. If there are caps, we configure the transform for this new |
| * transformation. |
| */ |
| static gboolean |
| gst_base_transform_setcaps (GstBaseTransform * trans, GstPad * pad, |
| GstCaps * incaps) |
| { |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstCaps *outcaps, *prev_incaps = NULL, *prev_outcaps = NULL; |
| gboolean ret = TRUE; |
| |
| GST_DEBUG_OBJECT (pad, "have new caps %p %" GST_PTR_FORMAT, incaps, incaps); |
| |
| /* find best possible caps for the other pad */ |
| outcaps = gst_base_transform_find_transform (trans, pad, incaps); |
| if (!outcaps || gst_caps_is_empty (outcaps)) |
| goto no_transform_possible; |
| |
| /* configure the element now */ |
| |
| /* if we have the same caps, we can optimize and reuse the input caps */ |
| if (gst_caps_is_equal (incaps, outcaps)) { |
| GST_INFO_OBJECT (trans, "reuse caps"); |
| gst_caps_unref (outcaps); |
| outcaps = gst_caps_ref (incaps); |
| } |
| |
| prev_incaps = gst_pad_get_current_caps (trans->sinkpad); |
| prev_outcaps = gst_pad_get_current_caps (trans->srcpad); |
| if (prev_incaps && prev_outcaps && gst_caps_is_equal (prev_incaps, incaps) |
| && gst_caps_is_equal (prev_outcaps, outcaps)) { |
| GST_DEBUG_OBJECT (trans, |
| "New caps equal to old ones: %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, |
| incaps, outcaps); |
| ret = TRUE; |
| } else { |
| /* call configure now */ |
| if (!(ret = gst_base_transform_configure_caps (trans, incaps, outcaps))) |
| goto failed_configure; |
| |
| if (!prev_outcaps || !gst_caps_is_equal (outcaps, prev_outcaps)) |
| /* let downstream know about our caps */ |
| ret = gst_pad_set_caps (trans->srcpad, outcaps); |
| } |
| |
| if (ret) { |
| /* try to get a pool when needed */ |
| ret = gst_base_transform_do_bufferpool (trans, outcaps); |
| } |
| |
| done: |
| if (outcaps) |
| gst_caps_unref (outcaps); |
| if (prev_incaps) |
| gst_caps_unref (prev_incaps); |
| if (prev_outcaps) |
| gst_caps_unref (prev_outcaps); |
| |
| GST_OBJECT_LOCK (trans); |
| priv->negotiated = ret; |
| GST_OBJECT_UNLOCK (trans); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_transform_possible: |
| { |
| GST_WARNING_OBJECT (trans, |
| "transform could not transform %" GST_PTR_FORMAT |
| " in anything we support", incaps); |
| ret = FALSE; |
| goto done; |
| } |
| failed_configure: |
| { |
| GST_WARNING_OBJECT (trans, "FAILED to configure incaps %" GST_PTR_FORMAT |
| " and outcaps %" GST_PTR_FORMAT, incaps, outcaps); |
| ret = FALSE; |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_base_transform_default_propose_allocation (GstBaseTransform * trans, |
| GstQuery * decide_query, GstQuery * query) |
| { |
| gboolean ret; |
| |
| if (decide_query == NULL) { |
| GST_DEBUG_OBJECT (trans, "doing passthrough query"); |
| ret = gst_pad_peer_query (trans->srcpad, query); |
| } else { |
| guint i, n_metas; |
| /* non-passthrough, copy all metadata, decide_query does not contain the |
| * metadata anymore that depends on the buffer memory */ |
| n_metas = gst_query_get_n_allocation_metas (decide_query); |
| for (i = 0; i < n_metas; i++) { |
| GType api; |
| const GstStructure *params; |
| |
| api = gst_query_parse_nth_allocation_meta (decide_query, i, ¶ms); |
| GST_DEBUG_OBJECT (trans, "proposing metadata %s", g_type_name (api)); |
| gst_query_add_allocation_meta (query, api, params); |
| } |
| ret = TRUE; |
| } |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_reconfigure (GstBaseTransform * trans) |
| { |
| gboolean reconfigure, ret = TRUE; |
| |
| reconfigure = gst_pad_check_reconfigure (trans->srcpad); |
| |
| if (G_UNLIKELY (reconfigure)) { |
| GstCaps *incaps; |
| |
| GST_DEBUG_OBJECT (trans, "we had a pending reconfigure"); |
| |
| incaps = gst_pad_get_current_caps (trans->sinkpad); |
| if (incaps == NULL) |
| goto done; |
| |
| /* if we need to reconfigure we pretend new caps arrived. This |
| * will reconfigure the transform with the new output format. */ |
| if (!gst_base_transform_setcaps (trans, trans->sinkpad, incaps)) { |
| GST_ELEMENT_WARNING (trans, STREAM, FORMAT, |
| ("not negotiated"), ("not negotiated")); |
| ret = FALSE; |
| } |
| |
| gst_caps_unref (incaps); |
| } |
| |
| done: |
| |
| if (!ret) |
| gst_pad_mark_reconfigure (trans->srcpad); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_default_query (GstBaseTransform * trans, |
| GstPadDirection direction, GstQuery * query) |
| { |
| gboolean ret = FALSE; |
| GstPad *pad, *otherpad; |
| GstBaseTransformClass *klass; |
| GstBaseTransformPrivate *priv = trans->priv; |
| |
| if (direction == GST_PAD_SRC) { |
| pad = trans->srcpad; |
| otherpad = trans->sinkpad; |
| } else { |
| pad = trans->sinkpad; |
| otherpad = trans->srcpad; |
| } |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_ALLOCATION: |
| { |
| GstQuery *decide_query = NULL; |
| |
| /* can only be done on the sinkpad */ |
| if (direction != GST_PAD_SINK) |
| goto done; |
| |
| ret = gst_base_transform_reconfigure (trans); |
| if (G_UNLIKELY (!ret)) |
| goto done; |
| |
| GST_OBJECT_LOCK (trans); |
| if (!priv->negotiated && !priv->passthrough && (klass->set_caps != NULL)) { |
| GST_DEBUG_OBJECT (trans, |
| "not negotiated yet but need negotiation, can't answer ALLOCATION query"); |
| GST_OBJECT_UNLOCK (trans); |
| goto done; |
| } |
| |
| decide_query = trans->priv->query; |
| trans->priv->query = NULL; |
| GST_OBJECT_UNLOCK (trans); |
| |
| GST_DEBUG_OBJECT (trans, |
| "calling propose allocation with query %" GST_PTR_FORMAT, |
| decide_query); |
| |
| /* pass the query to the propose_allocation vmethod if any */ |
| if (G_LIKELY (klass->propose_allocation)) |
| ret = klass->propose_allocation (trans, decide_query, query); |
| else |
| ret = FALSE; |
| |
| if (decide_query) { |
| GST_OBJECT_LOCK (trans); |
| |
| if (trans->priv->query == NULL) |
| trans->priv->query = decide_query; |
| else |
| gst_query_unref (decide_query); |
| |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| GST_DEBUG_OBJECT (trans, "ALLOCATION ret %d, %" GST_PTR_FORMAT, ret, |
| query); |
| break; |
| } |
| case GST_QUERY_POSITION: |
| { |
| GstFormat format; |
| |
| gst_query_parse_position (query, &format, NULL); |
| if (format == GST_FORMAT_TIME && trans->segment.format == GST_FORMAT_TIME) { |
| gint64 pos; |
| ret = TRUE; |
| |
| if ((direction == GST_PAD_SINK) |
| || (trans->priv->position_out == GST_CLOCK_TIME_NONE)) { |
| pos = |
| gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, |
| trans->segment.position); |
| } else { |
| pos = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, |
| trans->priv->position_out); |
| } |
| gst_query_set_position (query, format, pos); |
| } else { |
| ret = gst_pad_peer_query (otherpad, query); |
| } |
| break; |
| } |
| case GST_QUERY_ACCEPT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_query_parse_accept_caps (query, &caps); |
| if (klass->accept_caps) { |
| ret = klass->accept_caps (trans, direction, caps); |
| gst_query_set_accept_caps_result (query, ret); |
| /* return TRUE, we answered the query */ |
| ret = TRUE; |
| } |
| break; |
| } |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter, *caps; |
| |
| gst_query_parse_caps (query, &filter); |
| caps = gst_base_transform_query_caps (trans, pad, filter); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| ret = TRUE; |
| break; |
| } |
| default: |
| ret = gst_pad_peer_query (otherpad, query); |
| break; |
| } |
| |
| done: |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstBaseTransform *trans; |
| GstBaseTransformClass *bclass; |
| gboolean ret = FALSE; |
| |
| trans = GST_BASE_TRANSFORM_CAST (parent); |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (bclass->query) |
| ret = bclass->query (trans, GST_PAD_DIRECTION (pad), query); |
| |
| return ret; |
| } |
| |
| /* this function either returns the input buffer without incrementing the |
| * refcount or it allocates a new (writable) buffer */ |
| static GstFlowReturn |
| default_prepare_output_buffer (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstBuffer ** outbuf) |
| { |
| GstBaseTransformPrivate *priv; |
| GstFlowReturn ret; |
| GstBaseTransformClass *bclass; |
| GstCaps *incaps, *outcaps; |
| gsize insize, outsize; |
| gboolean res; |
| |
| priv = trans->priv; |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| /* figure out how to allocate an output buffer */ |
| if (priv->passthrough) { |
| /* passthrough, we will not modify the incoming buffer so we can just |
| * reuse it */ |
| GST_DEBUG_OBJECT (trans, "passthrough: reusing input buffer"); |
| *outbuf = inbuf; |
| goto done; |
| } |
| |
| /* we can't reuse the input buffer */ |
| if (priv->pool) { |
| if (!priv->pool_active) { |
| GST_DEBUG_OBJECT (trans, "setting pool %p active", priv->pool); |
| if (!gst_buffer_pool_set_active (priv->pool, TRUE)) |
| goto activate_failed; |
| priv->pool_active = TRUE; |
| } |
| GST_DEBUG_OBJECT (trans, "using pool alloc"); |
| ret = gst_buffer_pool_acquire_buffer (priv->pool, outbuf, NULL); |
| if (ret != GST_FLOW_OK) |
| goto alloc_failed; |
| |
| goto copy_meta; |
| } |
| |
| /* no pool, we need to figure out the size of the output buffer first */ |
| if ((bclass->transform_ip != NULL) && priv->always_in_place) { |
| /* we want to do an in-place alloc */ |
| if (gst_buffer_is_writable (inbuf)) { |
| GST_DEBUG_OBJECT (trans, "inplace reuse writable input buffer"); |
| *outbuf = inbuf; |
| } else { |
| GST_DEBUG_OBJECT (trans, "making writable buffer copy"); |
| /* we make a copy of the input buffer */ |
| *outbuf = gst_buffer_copy (inbuf); |
| } |
| goto done; |
| } |
| |
| /* else use the transform function to get the size */ |
| incaps = gst_pad_get_current_caps (trans->sinkpad); |
| outcaps = gst_pad_get_current_caps (trans->srcpad); |
| |
| /* srcpad might be flushing already if we're being shut down */ |
| if (outcaps == NULL) |
| goto no_outcaps; |
| |
| GST_DEBUG_OBJECT (trans, "getting output size for alloc"); |
| /* copy transform, figure out the output size */ |
| insize = gst_buffer_get_size (inbuf); |
| res = gst_base_transform_transform_size (trans, |
| GST_PAD_SINK, incaps, insize, outcaps, &outsize); |
| |
| gst_caps_unref (incaps); |
| gst_caps_unref (outcaps); |
| |
| if (!res) |
| goto unknown_size; |
| |
| GST_DEBUG_OBJECT (trans, "doing alloc of size %" G_GSIZE_FORMAT, outsize); |
| *outbuf = gst_buffer_new_allocate (priv->allocator, outsize, &priv->params); |
| if (!*outbuf) { |
| ret = GST_FLOW_ERROR; |
| goto alloc_failed; |
| } |
| |
| copy_meta: |
| /* copy the metadata */ |
| if (bclass->copy_metadata) |
| if (!bclass->copy_metadata (trans, inbuf, *outbuf)) { |
| /* something failed, post a warning */ |
| GST_ELEMENT_WARNING (trans, STREAM, NOT_IMPLEMENTED, |
| ("could not copy metadata"), (NULL)); |
| } |
| |
| done: |
| return GST_FLOW_OK; |
| |
| /* ERRORS */ |
| activate_failed: |
| { |
| GST_ELEMENT_ERROR (trans, RESOURCE, SETTINGS, |
| ("failed to activate bufferpool"), ("failed to activate bufferpool")); |
| return GST_FLOW_ERROR; |
| } |
| unknown_size: |
| { |
| GST_ERROR_OBJECT (trans, "unknown output size"); |
| return GST_FLOW_ERROR; |
| } |
| alloc_failed: |
| { |
| GST_DEBUG_OBJECT (trans, "could not allocate buffer from pool"); |
| return ret; |
| } |
| no_outcaps: |
| { |
| GST_DEBUG_OBJECT (trans, "no output caps, source pad has been deactivated"); |
| gst_caps_unref (incaps); |
| return GST_FLOW_FLUSHING; |
| } |
| } |
| |
| typedef struct |
| { |
| GstBaseTransform *trans; |
| GstBuffer *outbuf; |
| } CopyMetaData; |
| |
| static gboolean |
| foreach_metadata (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data) |
| { |
| CopyMetaData *data = user_data; |
| GstBaseTransform *trans = data->trans; |
| GstBaseTransformClass *klass; |
| const GstMetaInfo *info = (*meta)->info; |
| GstBuffer *outbuf = data->outbuf; |
| gboolean do_copy = FALSE; |
| |
| klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (gst_meta_api_type_has_tag (info->api, _gst_meta_tag_memory)) { |
| /* never call the transform_meta with memory specific metadata */ |
| GST_DEBUG_OBJECT (trans, "not copying memory specific metadata %s", |
| g_type_name (info->api)); |
| do_copy = FALSE; |
| } else if (klass->transform_meta) { |
| do_copy = klass->transform_meta (trans, outbuf, *meta, inbuf); |
| GST_DEBUG_OBJECT (trans, "transformed metadata %s: copy: %d", |
| g_type_name (info->api), do_copy); |
| } |
| |
| /* we only copy metadata when the subclass implemented a transform_meta |
| * function and when it returns %TRUE */ |
| if (do_copy) { |
| GstMetaTransformCopy copy_data = { FALSE, 0, -1 }; |
| /* simply copy then */ |
| if (info->transform_func) { |
| GST_DEBUG_OBJECT (trans, "copy metadata %s", g_type_name (info->api)); |
| info->transform_func (outbuf, *meta, inbuf, |
| _gst_meta_transform_copy, ©_data); |
| } else { |
| GST_DEBUG_OBJECT (trans, "couldn't copy metadata %s", |
| g_type_name (info->api)); |
| } |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| default_copy_metadata (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstBuffer * outbuf) |
| { |
| GstBaseTransformPrivate *priv = trans->priv; |
| CopyMetaData data; |
| |
| /* now copy the metadata */ |
| GST_DEBUG_OBJECT (trans, "copying metadata"); |
| |
| /* this should not happen, buffers allocated from a pool or with |
| * new_allocate should always be writable. */ |
| if (!gst_buffer_is_writable (outbuf)) |
| goto not_writable; |
| |
| /* when we get here, the metadata should be writable */ |
| gst_buffer_copy_into (outbuf, inbuf, |
| GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); |
| |
| /* clear the GAP flag when the subclass does not understand it */ |
| if (!priv->gap_aware) |
| GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_GAP); |
| |
| |
| data.trans = trans; |
| data.outbuf = outbuf; |
| |
| gst_buffer_foreach_meta (inbuf, foreach_metadata, &data); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| not_writable: |
| { |
| GST_WARNING_OBJECT (trans, "buffer %p not writable", outbuf); |
| return FALSE; |
| } |
| } |
| |
| /* Given @caps calcultate the size of one unit. |
| * |
| * For video caps, this is the size of one frame (and thus one buffer). |
| * For audio caps, this is the size of one sample. |
| * |
| * These values are cached since they do not change and the calculation |
| * potentially involves parsing caps and other expensive stuff. |
| * |
| * We have two cache locations to store the size, one for the source caps |
| * and one for the sink caps. |
| * |
| * this function returns %FALSE if no size could be calculated. |
| */ |
| static gboolean |
| gst_base_transform_get_unit_size (GstBaseTransform * trans, GstCaps * caps, |
| gsize * size) |
| { |
| gboolean res = FALSE; |
| GstBaseTransformClass *bclass; |
| GstBaseTransformPrivate *priv = trans->priv; |
| |
| /* see if we have the result cached */ |
| if (priv->cache_caps1 == caps) { |
| *size = priv->cache_caps1_size; |
| GST_DEBUG_OBJECT (trans, |
| "returned %" G_GSIZE_FORMAT " from first cache", *size); |
| return TRUE; |
| } |
| if (priv->cache_caps2 == caps) { |
| *size = priv->cache_caps2_size; |
| GST_DEBUG_OBJECT (trans, |
| "returned %" G_GSIZE_FORMAT " from second cached", *size); |
| return TRUE; |
| } |
| |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| res = bclass->get_unit_size (trans, caps, size); |
| GST_DEBUG_OBJECT (trans, |
| "caps %" GST_PTR_FORMAT " has unit size %" G_GSIZE_FORMAT ", res %s", |
| caps, *size, res ? "TRUE" : "FALSE"); |
| |
| if (res) { |
| /* and cache the values */ |
| if (priv->cache_caps1 == NULL) { |
| gst_caps_replace (&priv->cache_caps1, caps); |
| priv->cache_caps1_size = *size; |
| GST_DEBUG_OBJECT (trans, |
| "caching %" G_GSIZE_FORMAT " in first cache", *size); |
| } else if (priv->cache_caps2 == NULL) { |
| gst_caps_replace (&priv->cache_caps2, caps); |
| priv->cache_caps2_size = *size; |
| GST_DEBUG_OBJECT (trans, |
| "caching %" G_GSIZE_FORMAT " in second cache", *size); |
| } else { |
| GST_DEBUG_OBJECT (trans, "no free spot to cache unit_size"); |
| } |
| } |
| return res; |
| } |
| |
| static gboolean |
| gst_base_transform_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstBaseTransform *trans; |
| GstBaseTransformClass *bclass; |
| gboolean ret = TRUE; |
| |
| trans = GST_BASE_TRANSFORM_CAST (parent); |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (bclass->sink_event) |
| ret = bclass->sink_event (trans, event); |
| else |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_sink_eventfunc (GstBaseTransform * trans, GstEvent * event) |
| { |
| gboolean ret = TRUE, forward = TRUE; |
| GstBaseTransformPrivate *priv = trans->priv; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| GST_OBJECT_LOCK (trans); |
| /* reset QoS parameters */ |
| priv->proportion = 1.0; |
| priv->earliest_time = -1; |
| priv->discont = FALSE; |
| priv->processed = 0; |
| priv->dropped = 0; |
| GST_OBJECT_UNLOCK (trans); |
| /* we need new segment info after the flush. */ |
| trans->have_segment = FALSE; |
| gst_segment_init (&trans->segment, GST_FORMAT_UNDEFINED); |
| priv->position_out = GST_CLOCK_TIME_NONE; |
| break; |
| case GST_EVENT_EOS: |
| break; |
| case GST_EVENT_TAG: |
| break; |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| /* clear any pending reconfigure flag */ |
| gst_pad_check_reconfigure (trans->srcpad); |
| ret = gst_base_transform_setcaps (trans, trans->sinkpad, caps); |
| if (!ret) |
| gst_pad_mark_reconfigure (trans->srcpad); |
| |
| forward = FALSE; |
| break; |
| } |
| case GST_EVENT_SEGMENT: |
| { |
| gst_event_copy_segment (event, &trans->segment); |
| trans->have_segment = TRUE; |
| |
| GST_DEBUG_OBJECT (trans, "received SEGMENT %" GST_SEGMENT_FORMAT, |
| &trans->segment); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (ret && forward) |
| ret = gst_pad_push_event (trans->srcpad, event); |
| else |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstBaseTransform *trans; |
| GstBaseTransformClass *bclass; |
| gboolean ret = TRUE; |
| |
| trans = GST_BASE_TRANSFORM_CAST (parent); |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (bclass->src_event) |
| ret = bclass->src_event (trans, event); |
| else |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_transform_src_eventfunc (GstBaseTransform * trans, GstEvent * event) |
| { |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (trans, "handling event %p %" GST_PTR_FORMAT, event, event); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| break; |
| case GST_EVENT_NAVIGATION: |
| break; |
| case GST_EVENT_QOS: |
| { |
| gdouble proportion; |
| GstClockTimeDiff diff; |
| GstClockTime timestamp; |
| |
| gst_event_parse_qos (event, NULL, &proportion, &diff, ×tamp); |
| gst_base_transform_update_qos (trans, proportion, diff, timestamp); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| ret = gst_pad_push_event (trans->sinkpad, event); |
| |
| return ret; |
| } |
| |
| /* Takes the input buffer */ |
| static GstFlowReturn |
| default_submit_input_buffer (GstBaseTransform * trans, gboolean is_discont, |
| GstBuffer * inbuf) |
| { |
| GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstClockTime running_time; |
| GstClockTime timestamp; |
| |
| if (G_UNLIKELY (!gst_base_transform_reconfigure (trans))) |
| goto not_negotiated; |
| |
| if (GST_BUFFER_OFFSET_IS_VALID (inbuf)) |
| GST_DEBUG_OBJECT (trans, |
| "handling buffer %p of size %" G_GSIZE_FORMAT ", PTS %" GST_TIME_FORMAT |
| " and offset %" G_GUINT64_FORMAT, inbuf, gst_buffer_get_size (inbuf), |
| GST_TIME_ARGS (GST_BUFFER_PTS (inbuf)), GST_BUFFER_OFFSET (inbuf)); |
| else |
| GST_DEBUG_OBJECT (trans, |
| "handling buffer %p of size %" G_GSIZE_FORMAT ", PTS %" GST_TIME_FORMAT |
| " and offset NONE", inbuf, gst_buffer_get_size (inbuf), |
| GST_TIME_ARGS (GST_BUFFER_PTS (inbuf))); |
| |
| /* Don't allow buffer handling before negotiation, except in passthrough mode |
| * or if the class doesn't implement a set_caps function (in which case it doesn't |
| * care about caps) |
| */ |
| if (!priv->negotiated && !priv->passthrough && (bclass->set_caps != NULL)) |
| goto not_negotiated; |
| |
| /* can only do QoS if the segment is in TIME */ |
| if (trans->segment.format != GST_FORMAT_TIME) |
| goto no_qos; |
| |
| /* QOS is done on the running time of the buffer, get it now */ |
| timestamp = GST_BUFFER_TIMESTAMP (inbuf); |
| running_time = gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME, |
| timestamp); |
| |
| if (running_time != -1) { |
| gboolean need_skip; |
| GstClockTime earliest_time; |
| gdouble proportion; |
| |
| /* lock for getting the QoS parameters that are set (in a different thread) |
| * with the QOS events */ |
| GST_OBJECT_LOCK (trans); |
| earliest_time = priv->earliest_time; |
| proportion = priv->proportion; |
| /* check for QoS, don't perform conversion for buffers |
| * that are known to be late. */ |
| need_skip = priv->qos_enabled && |
| earliest_time != -1 && running_time <= earliest_time; |
| GST_OBJECT_UNLOCK (trans); |
| |
| if (need_skip) { |
| GstMessage *qos_msg; |
| GstClockTime duration; |
| guint64 stream_time; |
| gint64 jitter; |
| |
| GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, trans, "skipping transform: qostime %" |
| GST_TIME_FORMAT " <= %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (running_time), GST_TIME_ARGS (earliest_time)); |
| |
| priv->dropped++; |
| |
| duration = GST_BUFFER_DURATION (inbuf); |
| stream_time = |
| gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, |
| timestamp); |
| jitter = GST_CLOCK_DIFF (running_time, earliest_time); |
| |
| qos_msg = |
| gst_message_new_qos (GST_OBJECT_CAST (trans), FALSE, running_time, |
| stream_time, timestamp, duration); |
| gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000); |
| gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS, |
| priv->processed, priv->dropped); |
| gst_element_post_message (GST_ELEMENT_CAST (trans), qos_msg); |
| |
| /* mark discont for next buffer */ |
| priv->discont = TRUE; |
| ret = GST_BASE_TRANSFORM_FLOW_DROPPED; |
| goto skip; |
| } |
| } |
| |
| no_qos: |
| /* Stash input buffer where the default generate_output |
| * function can find it */ |
| if (trans->queued_buf) |
| gst_buffer_unref (trans->queued_buf); |
| trans->queued_buf = inbuf; |
| return ret; |
| skip: |
| gst_buffer_unref (inbuf); |
| return ret; |
| |
| not_negotiated: |
| { |
| gst_buffer_unref (inbuf); |
| if (GST_PAD_IS_FLUSHING (trans->srcpad)) |
| return GST_FLOW_FLUSHING; |
| return GST_FLOW_NOT_NEGOTIATED; |
| } |
| } |
| |
| static GstFlowReturn |
| default_generate_output (GstBaseTransform * trans, GstBuffer ** outbuf) |
| { |
| GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstBuffer *inbuf; |
| gboolean want_in_place; |
| |
| /* Retrieve stashed input buffer, if the default submit_input_buffer |
| * was run. Takes ownership back from there */ |
| inbuf = trans->queued_buf; |
| trans->queued_buf = NULL; |
| |
| /* This default processing method needs one input buffer to feed to |
| * the transform functions, we can't do anything without it */ |
| if (inbuf == NULL) |
| return GST_FLOW_OK; |
| |
| /* first try to allocate an output buffer based on the currently negotiated |
| * format. outbuf will contain a buffer suitable for doing the configured |
| * transform after this function. */ |
| if (bclass->prepare_output_buffer == NULL) |
| goto no_prepare; |
| |
| GST_DEBUG_OBJECT (trans, "calling prepare buffer"); |
| ret = bclass->prepare_output_buffer (trans, inbuf, outbuf); |
| |
| if (ret != GST_FLOW_OK || *outbuf == NULL) |
| goto no_buffer; |
| |
| GST_DEBUG_OBJECT (trans, "using allocated buffer in %p, out %p", inbuf, |
| *outbuf); |
| |
| /* now perform the needed transform */ |
| if (priv->passthrough) { |
| /* In passthrough mode, give transform_ip a look at the |
| * buffer, without making it writable, or just push the |
| * data through */ |
| if (bclass->transform_ip_on_passthrough && bclass->transform_ip) { |
| GST_DEBUG_OBJECT (trans, "doing passthrough transform_ip"); |
| ret = bclass->transform_ip (trans, *outbuf); |
| } else { |
| GST_DEBUG_OBJECT (trans, "element is in passthrough"); |
| } |
| } else { |
| want_in_place = (bclass->transform_ip != NULL) && priv->always_in_place; |
| |
| if (want_in_place) { |
| GST_DEBUG_OBJECT (trans, "doing inplace transform"); |
| ret = bclass->transform_ip (trans, *outbuf); |
| } else { |
| GST_DEBUG_OBJECT (trans, "doing non-inplace transform"); |
| |
| if (bclass->transform) |
| ret = bclass->transform (trans, inbuf, *outbuf); |
| else |
| ret = GST_FLOW_NOT_SUPPORTED; |
| } |
| } |
| |
| /* only unref input buffer if we allocated a new outbuf buffer. If we reused |
| * the input buffer, no refcount is changed to keep the input buffer writable |
| * when needed. */ |
| if (*outbuf != inbuf) |
| gst_buffer_unref (inbuf); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_prepare: |
| { |
| gst_buffer_unref (inbuf); |
| GST_ELEMENT_ERROR (trans, STREAM, NOT_IMPLEMENTED, |
| ("Sub-class has no prepare_output_buffer implementation"), (NULL)); |
| return GST_FLOW_NOT_SUPPORTED; |
| } |
| no_buffer: |
| { |
| gst_buffer_unref (inbuf); |
| *outbuf = NULL; |
| GST_WARNING_OBJECT (trans, "could not get buffer from pool: %s", |
| gst_flow_get_name (ret)); |
| return ret; |
| } |
| } |
| |
| /* FIXME, getrange is broken, need to pull range from the other |
| * end based on the transform_size result. |
| */ |
| static GstFlowReturn |
| gst_base_transform_getrange (GstPad * pad, GstObject * parent, guint64 offset, |
| guint length, GstBuffer ** buffer) |
| { |
| GstBaseTransformClass *klass = GST_BASE_TRANSFORM_GET_CLASS (parent); |
| GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (parent); |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstFlowReturn ret; |
| GstBuffer *inbuf = NULL; |
| GstBuffer *outbuf = NULL; |
| |
| /* Try and generate a buffer, if the sub-class wants more data, |
| * pull some and repeat until a buffer (or error) is produced */ |
| do { |
| ret = klass->generate_output (trans, &outbuf); |
| |
| /* Consume the DROPPED return value and go get more data */ |
| if (ret == GST_BASE_TRANSFORM_FLOW_DROPPED) |
| ret = GST_FLOW_OK; |
| |
| if (ret != GST_FLOW_OK || outbuf != NULL) |
| break; |
| |
| /* No buffer generated, try and pull data */ |
| ret = gst_pad_pull_range (trans->sinkpad, offset, length, &inbuf); |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) |
| goto pull_error; |
| |
| if (klass->before_transform) |
| klass->before_transform (trans, inbuf); |
| |
| /* Set discont flag so we can mark the next outgoing buffer */ |
| if (GST_BUFFER_IS_DISCONT (inbuf)) { |
| GST_DEBUG_OBJECT (trans, "got DISCONT buffer %p", inbuf); |
| priv->discont = TRUE; |
| } |
| |
| /* FIXME: Input offsets and lengths need to be translated, as per |
| * the FIXME above. For now, just advance somewhat */ |
| offset += gst_buffer_get_size (inbuf); |
| |
| ret = klass->submit_input_buffer (trans, priv->discont, inbuf); |
| if (ret != GST_FLOW_OK) { |
| if (ret == GST_BASE_TRANSFORM_FLOW_DROPPED) |
| ret = GST_FLOW_OK; |
| goto done; |
| } |
| } while (ret == GST_FLOW_OK && outbuf == NULL); |
| |
| *buffer = outbuf; |
| if (outbuf) { |
| /* apply DISCONT flag if the buffer is not yet marked as such */ |
| if (priv->discont) { |
| GST_DEBUG_OBJECT (trans, "we have a pending DISCONT"); |
| if (!GST_BUFFER_IS_DISCONT (outbuf)) { |
| GST_DEBUG_OBJECT (trans, "marking DISCONT on output buffer"); |
| outbuf = gst_buffer_make_writable (outbuf); |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); |
| } |
| priv->discont = FALSE; |
| } |
| priv->processed++; |
| } |
| done: |
| return ret; |
| |
| /* ERRORS */ |
| pull_error: |
| { |
| GST_DEBUG_OBJECT (trans, "failed to pull a buffer: %s", |
| gst_flow_get_name (ret)); |
| goto done; |
| } |
| } |
| |
| /* The flow of the chain function is the reverse of the |
| * getrange() function - we have data, feed it to the sub-class |
| * and then iterate, pushing buffers it generates until it either |
| * wants more data or returns an error */ |
| static GstFlowReturn |
| gst_base_transform_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (parent); |
| GstBaseTransformClass *klass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| GstBaseTransformPrivate *priv = trans->priv; |
| GstFlowReturn ret; |
| GstClockTime position = GST_CLOCK_TIME_NONE; |
| GstClockTime timestamp, duration; |
| GstBuffer *outbuf = NULL; |
| |
| timestamp = GST_BUFFER_TIMESTAMP (buffer); |
| duration = GST_BUFFER_DURATION (buffer); |
| |
| /* calculate end position of the incoming buffer */ |
| if (timestamp != GST_CLOCK_TIME_NONE) { |
| if (duration != GST_CLOCK_TIME_NONE) |
| position = timestamp + duration; |
| else |
| position = timestamp; |
| } |
| |
| if (klass->before_transform) |
| klass->before_transform (trans, buffer); |
| |
| /* Set discont flag so we can mark the outgoing buffer */ |
| if (GST_BUFFER_IS_DISCONT (buffer)) { |
| GST_DEBUG_OBJECT (trans, "got DISCONT buffer %p", buffer); |
| priv->discont = TRUE; |
| } |
| |
| /* Takes ownership of input buffer */ |
| ret = klass->submit_input_buffer (trans, priv->discont, buffer); |
| if (ret != GST_FLOW_OK) |
| goto done; |
| |
| do { |
| outbuf = NULL; |
| |
| ret = klass->generate_output (trans, &outbuf); |
| |
| /* outbuf can be NULL, this means a dropped buffer, if we have a buffer but |
| * GST_BASE_TRANSFORM_FLOW_DROPPED we will not push either. */ |
| if (outbuf != NULL) { |
| if (ret == GST_FLOW_OK) { |
| GstClockTime position_out = GST_CLOCK_TIME_NONE; |
| |
| /* Remember last stop position */ |
| if (position != GST_CLOCK_TIME_NONE && |
| trans->segment.format == GST_FORMAT_TIME) |
| trans->segment.position = position; |
| |
| if (GST_BUFFER_TIMESTAMP_IS_VALID (outbuf)) { |
| position_out = GST_BUFFER_TIMESTAMP (outbuf); |
| if (GST_BUFFER_DURATION_IS_VALID (outbuf)) |
| position_out += GST_BUFFER_DURATION (outbuf); |
| } else if (position != GST_CLOCK_TIME_NONE) { |
| position_out = position; |
| } |
| if (position_out != GST_CLOCK_TIME_NONE |
| && trans->segment.format == GST_FORMAT_TIME) |
| priv->position_out = position_out; |
| |
| /* apply DISCONT flag if the buffer is not yet marked as such */ |
| if (trans->priv->discont) { |
| GST_DEBUG_OBJECT (trans, "we have a pending DISCONT"); |
| if (!GST_BUFFER_IS_DISCONT (outbuf)) { |
| GST_DEBUG_OBJECT (trans, "marking DISCONT on output buffer"); |
| outbuf = gst_buffer_make_writable (outbuf); |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); |
| } |
| priv->discont = FALSE; |
| } |
| priv->processed++; |
| |
| ret = gst_pad_push (trans->srcpad, outbuf); |
| } else { |
| GST_DEBUG_OBJECT (trans, "we got return %s", gst_flow_get_name (ret)); |
| gst_buffer_unref (outbuf); |
| } |
| } |
| } while (ret == GST_FLOW_OK && outbuf != NULL); |
| |
| done: |
| /* convert internal flow to OK and mark discont for the next buffer. */ |
| if (ret == GST_BASE_TRANSFORM_FLOW_DROPPED) { |
| GST_DEBUG_OBJECT (trans, "dropped a buffer, marking DISCONT"); |
| priv->discont = TRUE; |
| ret = GST_FLOW_OK; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_base_transform_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstBaseTransform *trans; |
| |
| trans = GST_BASE_TRANSFORM_CAST (object); |
| |
| switch (prop_id) { |
| case PROP_QOS: |
| gst_base_transform_set_qos_enabled (trans, g_value_get_boolean (value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_base_transform_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstBaseTransform *trans; |
| |
| trans = GST_BASE_TRANSFORM_CAST (object); |
| |
| switch (prop_id) { |
| case PROP_QOS: |
| g_value_set_boolean (value, gst_base_transform_is_qos_enabled (trans)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| /* not a vmethod of anything, just an internal method */ |
| static gboolean |
| gst_base_transform_activate (GstBaseTransform * trans, gboolean active) |
| { |
| GstBaseTransformClass *bclass; |
| GstBaseTransformPrivate *priv = trans->priv; |
| gboolean result = TRUE; |
| |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| if (active) { |
| GstCaps *incaps, *outcaps; |
| |
| if (priv->pad_mode == GST_PAD_MODE_NONE && bclass->start) |
| result &= bclass->start (trans); |
| |
| incaps = gst_pad_get_current_caps (trans->sinkpad); |
| outcaps = gst_pad_get_current_caps (trans->srcpad); |
| |
| GST_OBJECT_LOCK (trans); |
| if (incaps && outcaps) |
| priv->have_same_caps = |
| gst_caps_is_equal (incaps, outcaps) || priv->passthrough; |
| else |
| priv->have_same_caps = priv->passthrough; |
| GST_DEBUG_OBJECT (trans, "have_same_caps %d", priv->have_same_caps); |
| priv->negotiated = FALSE; |
| trans->have_segment = FALSE; |
| gst_segment_init (&trans->segment, GST_FORMAT_UNDEFINED); |
| priv->position_out = GST_CLOCK_TIME_NONE; |
| priv->proportion = 1.0; |
| priv->earliest_time = -1; |
| priv->discont = FALSE; |
| priv->processed = 0; |
| priv->dropped = 0; |
| GST_OBJECT_UNLOCK (trans); |
| |
| if (incaps) |
| gst_caps_unref (incaps); |
| if (outcaps) |
| gst_caps_unref (outcaps); |
| } else { |
| /* We must make sure streaming has finished before resetting things |
| * and calling the ::stop vfunc */ |
| GST_PAD_STREAM_LOCK (trans->sinkpad); |
| GST_PAD_STREAM_UNLOCK (trans->sinkpad); |
| |
| priv->have_same_caps = FALSE; |
| /* We can only reset the passthrough mode if the instance told us to |
| handle it in configure_caps */ |
| if (bclass->passthrough_on_same_caps) { |
| gst_base_transform_set_passthrough (trans, FALSE); |
| } |
| gst_caps_replace (&priv->cache_caps1, NULL); |
| gst_caps_replace (&priv->cache_caps2, NULL); |
| |
| /* Make sure any left over buffer is freed */ |
| gst_buffer_replace (&trans->queued_buf, NULL); |
| |
| if (priv->pad_mode != GST_PAD_MODE_NONE && bclass->stop) |
| result &= bclass->stop (trans); |
| |
| gst_base_transform_set_allocation (trans, NULL, NULL, NULL, NULL); |
| } |
| |
| return result; |
| } |
| |
| static gboolean |
| gst_base_transform_sink_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean result = FALSE; |
| GstBaseTransform *trans; |
| |
| trans = GST_BASE_TRANSFORM_CAST (parent); |
| |
| switch (mode) { |
| case GST_PAD_MODE_PUSH: |
| { |
| result = gst_base_transform_activate (trans, active); |
| |
| if (result) |
| trans->priv->pad_mode = active ? GST_PAD_MODE_PUSH : GST_PAD_MODE_NONE; |
| |
| break; |
| } |
| default: |
| result = TRUE; |
| break; |
| } |
| return result; |
| } |
| |
| static gboolean |
| gst_base_transform_src_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean result = FALSE; |
| GstBaseTransform *trans; |
| |
| trans = GST_BASE_TRANSFORM_CAST (parent); |
| |
| switch (mode) { |
| case GST_PAD_MODE_PULL: |
| { |
| result = |
| gst_pad_activate_mode (trans->sinkpad, GST_PAD_MODE_PULL, active); |
| |
| if (result) |
| result &= gst_base_transform_activate (trans, active); |
| |
| if (result) |
| trans->priv->pad_mode = active ? mode : GST_PAD_MODE_NONE; |
| break; |
| } |
| default: |
| result = TRUE; |
| break; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * gst_base_transform_set_passthrough: |
| * @trans: the #GstBaseTransform to set |
| * @passthrough: boolean indicating passthrough mode. |
| * |
| * Set passthrough mode for this filter by default. This is mostly |
| * useful for filters that do not care about negotiation. |
| * |
| * Always %TRUE for filters which don't implement either a transform |
| * or transform_ip method. |
| * |
| * MT safe. |
| */ |
| void |
| gst_base_transform_set_passthrough (GstBaseTransform * trans, |
| gboolean passthrough) |
| { |
| GstBaseTransformClass *bclass; |
| |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| GST_OBJECT_LOCK (trans); |
| if (!passthrough) { |
| if (bclass->transform_ip || bclass->transform) |
| trans->priv->passthrough = FALSE; |
| } else { |
| trans->priv->passthrough = TRUE; |
| } |
| |
| GST_DEBUG_OBJECT (trans, "set passthrough %d", trans->priv->passthrough); |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_is_passthrough: |
| * @trans: the #GstBaseTransform to query |
| * |
| * See if @trans is configured as a passthrough transform. |
| * |
| * Returns: %TRUE is the transform is configured in passthrough mode. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_base_transform_is_passthrough (GstBaseTransform * trans) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_BASE_TRANSFORM (trans), FALSE); |
| |
| GST_OBJECT_LOCK (trans); |
| result = trans->priv->passthrough; |
| GST_OBJECT_UNLOCK (trans); |
| |
| return result; |
| } |
| |
| /** |
| * gst_base_transform_set_in_place: |
| * @trans: the #GstBaseTransform to modify |
| * @in_place: Boolean value indicating that we would like to operate |
| * on in_place buffers. |
| * |
| * Determines whether a non-writable buffer will be copied before passing |
| * to the transform_ip function. |
| * |
| * * Always %TRUE if no transform function is implemented. |
| * * Always %FALSE if ONLY transform function is implemented. |
| * |
| * MT safe. |
| */ |
| void |
| gst_base_transform_set_in_place (GstBaseTransform * trans, gboolean in_place) |
| { |
| GstBaseTransformClass *bclass; |
| |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| bclass = GST_BASE_TRANSFORM_GET_CLASS (trans); |
| |
| GST_OBJECT_LOCK (trans); |
| |
| if (in_place) { |
| if (bclass->transform_ip) { |
| GST_DEBUG_OBJECT (trans, "setting in_place TRUE"); |
| trans->priv->always_in_place = TRUE; |
| } |
| } else { |
| if (bclass->transform) { |
| GST_DEBUG_OBJECT (trans, "setting in_place FALSE"); |
| trans->priv->always_in_place = FALSE; |
| } |
| } |
| |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_is_in_place: |
| * @trans: the #GstBaseTransform to query |
| * |
| * See if @trans is configured as a in_place transform. |
| * |
| * Returns: %TRUE is the transform is configured in in_place mode. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_base_transform_is_in_place (GstBaseTransform * trans) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_BASE_TRANSFORM (trans), FALSE); |
| |
| GST_OBJECT_LOCK (trans); |
| result = trans->priv->always_in_place; |
| GST_OBJECT_UNLOCK (trans); |
| |
| return result; |
| } |
| |
| /** |
| * gst_base_transform_update_qos: |
| * @trans: a #GstBaseTransform |
| * @proportion: the proportion |
| * @diff: the diff against the clock |
| * @timestamp: the timestamp of the buffer generating the QoS expressed in |
| * running_time. |
| * |
| * Set the QoS parameters in the transform. This function is called internally |
| * when a QOS event is received but subclasses can provide custom information |
| * when needed. |
| * |
| * MT safe. |
| */ |
| void |
| gst_base_transform_update_qos (GstBaseTransform * trans, |
| gdouble proportion, GstClockTimeDiff diff, GstClockTime timestamp) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, trans, |
| "qos: proportion: %lf, diff %" G_GINT64_FORMAT ", timestamp %" |
| GST_TIME_FORMAT, proportion, diff, GST_TIME_ARGS (timestamp)); |
| |
| GST_OBJECT_LOCK (trans); |
| trans->priv->proportion = proportion; |
| trans->priv->earliest_time = timestamp + diff; |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_set_qos_enabled: |
| * @trans: a #GstBaseTransform |
| * @enabled: new state |
| * |
| * Enable or disable QoS handling in the transform. |
| * |
| * MT safe. |
| */ |
| void |
| gst_base_transform_set_qos_enabled (GstBaseTransform * trans, gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, trans, "enabled: %d", enabled); |
| |
| GST_OBJECT_LOCK (trans); |
| trans->priv->qos_enabled = enabled; |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_is_qos_enabled: |
| * @trans: a #GstBaseTransform |
| * |
| * Queries if the transform will handle QoS. |
| * |
| * Returns: %TRUE if QoS is enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_base_transform_is_qos_enabled (GstBaseTransform * trans) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_BASE_TRANSFORM (trans), FALSE); |
| |
| GST_OBJECT_LOCK (trans); |
| result = trans->priv->qos_enabled; |
| GST_OBJECT_UNLOCK (trans); |
| |
| return result; |
| } |
| |
| /** |
| * gst_base_transform_set_gap_aware: |
| * @trans: a #GstBaseTransform |
| * @gap_aware: New state |
| * |
| * If @gap_aware is %FALSE (the default), output buffers will have the |
| * %GST_BUFFER_FLAG_GAP flag unset. |
| * |
| * If set to %TRUE, the element must handle output buffers with this flag set |
| * correctly, i.e. it can assume that the buffer contains neutral data but must |
| * unset the flag if the output is no neutral data. |
| * |
| * MT safe. |
| */ |
| void |
| gst_base_transform_set_gap_aware (GstBaseTransform * trans, gboolean gap_aware) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| GST_OBJECT_LOCK (trans); |
| trans->priv->gap_aware = gap_aware; |
| GST_DEBUG_OBJECT (trans, "set gap aware %d", trans->priv->gap_aware); |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_set_prefer_passthrough: |
| * @trans: a #GstBaseTransform |
| * @prefer_passthrough: New state |
| * |
| * If @prefer_passthrough is %TRUE (the default), @trans will check and |
| * prefer passthrough caps from the list of caps returned by the |
| * transform_caps vmethod. |
| * |
| * If set to %FALSE, the element must order the caps returned from the |
| * transform_caps function in such a way that the preferred format is |
| * first in the list. This can be interesting for transforms that can do |
| * passthrough transforms but prefer to do something else, like a |
| * capsfilter. |
| * |
| * MT safe. |
| * |
| * Since: 1.0.1 |
| */ |
| void |
| gst_base_transform_set_prefer_passthrough (GstBaseTransform * trans, |
| gboolean prefer_passthrough) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| GST_OBJECT_LOCK (trans); |
| trans->priv->prefer_passthrough = prefer_passthrough; |
| GST_DEBUG_OBJECT (trans, "prefer passthrough %d", prefer_passthrough); |
| GST_OBJECT_UNLOCK (trans); |
| } |
| |
| /** |
| * gst_base_transform_reconfigure_sink: |
| * @trans: a #GstBaseTransform |
| * |
| * Instructs @trans to request renegotiation upstream. This function is |
| * typically called after properties on the transform were set that |
| * influence the input format. |
| */ |
| void |
| gst_base_transform_reconfigure_sink (GstBaseTransform * trans) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| /* push the renegotiate event */ |
| if (!gst_pad_push_event (GST_BASE_TRANSFORM_SINK_PAD (trans), |
| gst_event_new_reconfigure ())) |
| GST_DEBUG_OBJECT (trans, "Renegotiate event wasn't handled"); |
| } |
| |
| /** |
| * gst_base_transform_reconfigure_src: |
| * @trans: a #GstBaseTransform |
| * |
| * Instructs @trans to renegotiate a new downstream transform on the next |
| * buffer. This function is typically called after properties on the transform |
| * were set that influence the output format. |
| */ |
| void |
| gst_base_transform_reconfigure_src (GstBaseTransform * trans) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| gst_pad_mark_reconfigure (trans->srcpad); |
| } |
| |
| /** |
| * gst_base_transform_get_buffer_pool: |
| * @trans: a #GstBaseTransform |
| * |
| * Returns: (transfer full): the instance of the #GstBufferPool used |
| * by @trans; free it after use it |
| */ |
| GstBufferPool * |
| gst_base_transform_get_buffer_pool (GstBaseTransform * trans) |
| { |
| g_return_val_if_fail (GST_IS_BASE_TRANSFORM (trans), NULL); |
| |
| if (trans->priv->pool) |
| return gst_object_ref (trans->priv->pool); |
| |
| return NULL; |
| } |
| |
| /** |
| * gst_base_transform_get_allocator: |
| * @trans: a #GstBaseTransform |
| * @allocator: (out) (allow-none) (transfer full): the #GstAllocator |
| * used |
| * @params: (out) (allow-none) (transfer full): the |
| * #GstAllocationParams of @allocator |
| * |
| * Lets #GstBaseTransform sub-classes to know the memory @allocator |
| * used by the base class and its @params. |
| * |
| * Unref the @allocator after use it. |
| */ |
| void |
| gst_base_transform_get_allocator (GstBaseTransform * trans, |
| GstAllocator ** allocator, GstAllocationParams * params) |
| { |
| g_return_if_fail (GST_IS_BASE_TRANSFORM (trans)); |
| |
| if (allocator) |
| *allocator = trans->priv->allocator ? |
| gst_object_ref (trans->priv->allocator) : NULL; |
| |
| if (params) |
| *params = trans->priv->params; |
| } |
| |
| /** |
| * gst_base_transform_update_src_caps: |
| * @trans: a #GstBaseTransform |
| * @updated_caps: An updated version of the srcpad caps to be pushed |
| * downstream |
| * |
| * Updates the srcpad caps and send the caps downstream. This function |
| * can be used by subclasses when they have already negotiated their caps |
| * but found a change in them (or computed new informations). This way, |
| * they can notify downstream about that change without loosing any |
| * buffer. |
| * |
| * Returns: %TRUE if the caps could be send downstream %FALSE otherwise |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_base_transform_update_src_caps (GstBaseTransform * trans, |
| GstCaps * updated_caps) |
| { |
| g_return_val_if_fail (GST_IS_BASE_TRANSFORM (trans), FALSE); |
| |
| if (gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (trans), |
| gst_event_new_caps (updated_caps))) { |
| gst_pad_mark_reconfigure (trans->srcpad); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |