/* GStreamer * Copyright (C) <1999> Erik Walthinsen * Copyright (C) <2003> David Schleef * * 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:gstvideofilter * @title: GstVideoFilter * @short_description: Base class for video filters * * Provides useful functions and a base class for video filters. * * The videofilter will by default enable QoS on the parent GstBaseTransform * to implement frame dropping. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstvideofilter.h" #include #include #include GST_DEBUG_CATEGORY_STATIC (gst_video_filter_debug); #define GST_CAT_DEFAULT gst_video_filter_debug #define gst_video_filter_parent_class parent_class G_DEFINE_ABSTRACT_TYPE (GstVideoFilter, gst_video_filter, GST_TYPE_BASE_TRANSFORM); /* Answer the allocation query downstream. */ static gboolean gst_video_filter_propose_allocation (GstBaseTransform * trans, GstQuery * decide_query, GstQuery * query) { GstVideoFilter *filter = GST_VIDEO_FILTER_CAST (trans); GstVideoInfo info; GstBufferPool *pool; GstCaps *caps; guint size; if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans, decide_query, query)) return FALSE; /* passthrough, we're done */ if (decide_query == NULL) return TRUE; gst_query_parse_allocation (query, &caps, NULL); if (caps == NULL) return FALSE; if (!gst_video_info_from_caps (&info, caps)) return FALSE; size = GST_VIDEO_INFO_SIZE (&info); if (gst_query_get_n_allocation_pools (query) == 0) { GstStructure *structure; GstAllocator *allocator = NULL; GstAllocationParams params = { 0, 15, 0, 0, }; if (gst_query_get_n_allocation_params (query) > 0) gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); else gst_query_add_allocation_param (query, allocator, ¶ms); pool = gst_video_buffer_pool_new (); structure = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (structure, caps, size, 0, 0); gst_buffer_pool_config_set_allocator (structure, allocator, ¶ms); if (allocator) gst_object_unref (allocator); if (!gst_buffer_pool_set_config (pool, structure)) goto config_failed; gst_query_add_allocation_pool (query, pool, size, 0, 0); gst_object_unref (pool); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); } return TRUE; /* ERRORS */ config_failed: { GST_ERROR_OBJECT (filter, "failed to set config"); gst_object_unref (pool); return FALSE; } } /* configure the allocation query that was answered downstream, we can configure * some properties on it. Only called when not in passthrough mode. */ static gboolean gst_video_filter_decide_allocation (GstBaseTransform * trans, GstQuery * query) { GstBufferPool *pool = NULL; GstStructure *config; guint min, max, size; gboolean update_pool; GstCaps *outcaps = NULL; if (gst_query_get_n_allocation_pools (query) > 0) { gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); if (!pool) gst_query_parse_allocation (query, &outcaps, NULL); update_pool = TRUE; } else { GstVideoInfo vinfo; gst_query_parse_allocation (query, &outcaps, NULL); gst_video_info_init (&vinfo); gst_video_info_from_caps (&vinfo, outcaps); size = vinfo.size; min = max = 0; update_pool = FALSE; } if (!pool) pool = gst_video_buffer_pool_new (); config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); if (outcaps) gst_buffer_pool_config_set_params (config, outcaps, size, 0, 0); gst_buffer_pool_set_config (pool, config); if (update_pool) gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); else gst_query_add_allocation_pool (query, pool, size, min, max); gst_object_unref (pool); return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans, query); } /* our output size only depends on the caps, not on the input caps */ static gboolean gst_video_filter_transform_size (GstBaseTransform * btrans, GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize) { gboolean ret = TRUE; GstVideoInfo info; g_assert (size); ret = gst_video_info_from_caps (&info, othercaps); if (ret) *othersize = info.size; return ret; } static gboolean gst_video_filter_get_unit_size (GstBaseTransform * btrans, GstCaps * caps, gsize * size) { GstVideoInfo info; if (!gst_video_info_from_caps (&info, caps)) { GST_WARNING_OBJECT (btrans, "Failed to parse caps %" GST_PTR_FORMAT, caps); return FALSE; } *size = info.size; GST_DEBUG_OBJECT (btrans, "Returning size %" G_GSIZE_FORMAT " bytes" "for caps %" GST_PTR_FORMAT, *size, caps); return TRUE; } static gboolean gst_video_filter_set_caps (GstBaseTransform * trans, GstCaps * incaps, GstCaps * outcaps) { GstVideoFilter *filter = GST_VIDEO_FILTER_CAST (trans); GstVideoFilterClass *fclass; GstVideoInfo in_info, out_info; gboolean res; /* input caps */ if (!gst_video_info_from_caps (&in_info, incaps)) goto invalid_caps; /* output caps */ if (!gst_video_info_from_caps (&out_info, outcaps)) goto invalid_caps; fclass = GST_VIDEO_FILTER_GET_CLASS (filter); if (fclass->set_info) res = fclass->set_info (filter, incaps, &in_info, outcaps, &out_info); else res = TRUE; if (res) { filter->in_info = in_info; filter->out_info = out_info; if (fclass->transform_frame == NULL) gst_base_transform_set_in_place (trans, TRUE); if (fclass->transform_frame_ip == NULL) GST_BASE_TRANSFORM_CLASS (fclass)->transform_ip_on_passthrough = FALSE; } filter->negotiated = res; return res; /* ERRORS */ invalid_caps: { GST_ERROR_OBJECT (filter, "invalid caps"); filter->negotiated = FALSE; return FALSE; } } static GstFlowReturn gst_video_filter_transform (GstBaseTransform * trans, GstBuffer * inbuf, GstBuffer * outbuf) { GstFlowReturn res; GstVideoFilter *filter = GST_VIDEO_FILTER_CAST (trans); GstVideoFilterClass *fclass; if (G_UNLIKELY (!filter->negotiated)) goto unknown_format; fclass = GST_VIDEO_FILTER_GET_CLASS (filter); if (fclass->transform_frame) { GstVideoFrame in_frame, out_frame; if (!gst_video_frame_map (&in_frame, &filter->in_info, inbuf, GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF)) goto invalid_buffer; if (!gst_video_frame_map (&out_frame, &filter->out_info, outbuf, GST_MAP_WRITE | GST_VIDEO_FRAME_MAP_FLAG_NO_REF)) { gst_video_frame_unmap (&in_frame); goto invalid_buffer; } res = fclass->transform_frame (filter, &in_frame, &out_frame); gst_video_frame_unmap (&out_frame); gst_video_frame_unmap (&in_frame); } else { GST_DEBUG_OBJECT (trans, "no transform_frame vmethod"); res = GST_FLOW_OK; } return res; /* ERRORS */ unknown_format: { GST_ELEMENT_ERROR (filter, CORE, NOT_IMPLEMENTED, (NULL), ("unknown format")); return GST_FLOW_NOT_NEGOTIATED; } invalid_buffer: { GST_ELEMENT_WARNING (filter, CORE, NOT_IMPLEMENTED, (NULL), ("invalid video buffer received")); return GST_FLOW_OK; } } static GstFlowReturn gst_video_filter_transform_ip (GstBaseTransform * trans, GstBuffer * buf) { GstFlowReturn res; GstVideoFilter *filter = GST_VIDEO_FILTER_CAST (trans); GstVideoFilterClass *fclass; if (G_UNLIKELY (!filter->negotiated)) goto unknown_format; fclass = GST_VIDEO_FILTER_GET_CLASS (filter); if (fclass->transform_frame_ip) { GstVideoFrame frame; GstMapFlags flags; flags = GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF; if (!gst_base_transform_is_passthrough (trans)) flags |= GST_MAP_WRITE; if (!gst_video_frame_map (&frame, &filter->in_info, buf, flags)) goto invalid_buffer; res = fclass->transform_frame_ip (filter, &frame); gst_video_frame_unmap (&frame); } else { GST_DEBUG_OBJECT (trans, "no transform_frame_ip vmethod"); res = GST_FLOW_OK; } return res; /* ERRORS */ unknown_format: { GST_ELEMENT_ERROR (filter, CORE, NOT_IMPLEMENTED, (NULL), ("unknown format")); return GST_FLOW_NOT_NEGOTIATED; } invalid_buffer: { GST_ELEMENT_WARNING (filter, CORE, NOT_IMPLEMENTED, (NULL), ("invalid video buffer received")); return GST_FLOW_OK; } } static gboolean gst_video_filter_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 || (g_strv_length ((gchar **) tags) == 1 && gst_meta_api_type_has_tag (info->api, g_quark_from_string (GST_META_TAG_VIDEO_STR)))) return TRUE; return GST_BASE_TRANSFORM_CLASS (parent_class)->transform_meta (trans, inbuf, meta, outbuf); } static void gst_video_filter_class_init (GstVideoFilterClass * g_class) { GstBaseTransformClass *trans_class; GstVideoFilterClass *klass; klass = (GstVideoFilterClass *) g_class; trans_class = (GstBaseTransformClass *) klass; trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_filter_set_caps); trans_class->propose_allocation = GST_DEBUG_FUNCPTR (gst_video_filter_propose_allocation); trans_class->decide_allocation = GST_DEBUG_FUNCPTR (gst_video_filter_decide_allocation); trans_class->transform_size = GST_DEBUG_FUNCPTR (gst_video_filter_transform_size); trans_class->get_unit_size = GST_DEBUG_FUNCPTR (gst_video_filter_get_unit_size); trans_class->transform = GST_DEBUG_FUNCPTR (gst_video_filter_transform); trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_video_filter_transform_ip); trans_class->transform_meta = GST_DEBUG_FUNCPTR (gst_video_filter_transform_meta); GST_DEBUG_CATEGORY_INIT (gst_video_filter_debug, "videofilter", 0, "videofilter"); } static void gst_video_filter_init (GstVideoFilter * instance) { GstVideoFilter *videofilter = GST_VIDEO_FILTER (instance); GST_DEBUG_OBJECT (videofilter, "gst_video_filter_init"); videofilter->negotiated = FALSE; /* enable QoS */ gst_base_transform_set_qos_enabled (GST_BASE_TRANSFORM (videofilter), TRUE); }