| /* GStreamer video frame cropping to aspect-ratio |
| * Copyright (C) 2009 Thijs Vermeir <thijsvermeir@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:element-aspectratiocrop |
| * @see_also: #GstVideoCrop |
| * |
| * This element crops video frames to a specified #GstAspectRatioCrop:aspect-ratio. |
| * |
| * If the aspect-ratio is already correct, the element will operate |
| * in pass-through mode. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch-1.0 -v videotestsrc ! video/x-raw,height=640,width=480 ! aspectratiocrop aspect-ratio=16/9 ! ximagesink |
| * ]| This pipeline generates a videostream in 4/3 and crops it to 16/9. |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <gst/gst.h> |
| #include <gst/video/video.h> |
| |
| #include "gstaspectratiocrop.h" |
| |
| #include "gst/glib-compat-private.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (aspect_ratio_crop_debug); |
| #define GST_CAT_DEFAULT aspect_ratio_crop_debug |
| |
| enum |
| { |
| ARG_0, |
| ARG_ASPECT_RATIO_CROP, |
| }; |
| |
| /* we support the same caps as videocrop (sync changes) */ |
| #define ASPECT_RATIO_CROP_CAPS \ |
| GST_VIDEO_CAPS_MAKE ("{ RGBx, xRGB, BGRx, xBGR, " \ |
| "RGBA, ARGB, BGRA, ABGR, RGB, BGR, AYUV, YUY2, " \ |
| "YVYU, UYVY, I420, YV12, RGB16, RGB15, GRAY8, " \ |
| "NV12, NV21, GRAY16_LE, GRAY16_BE }") |
| |
| static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (ASPECT_RATIO_CROP_CAPS) |
| ); |
| |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (ASPECT_RATIO_CROP_CAPS) |
| ); |
| |
| #define gst_aspect_ratio_crop_parent_class parent_class |
| G_DEFINE_TYPE (GstAspectRatioCrop, gst_aspect_ratio_crop, GST_TYPE_BIN); |
| |
| static void gst_aspect_ratio_crop_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_aspect_ratio_crop_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_aspect_ratio_crop_set_cropping (GstAspectRatioCrop * |
| aspect_ratio_crop, gint top, gint right, gint bottom, gint left); |
| static GstCaps *gst_aspect_ratio_crop_get_caps (GstPad * pad, GstCaps * filter); |
| static gboolean gst_aspect_ratio_crop_src_query (GstPad * pad, |
| GstObject * parent, GstQuery * query); |
| static gboolean gst_aspect_ratio_crop_set_caps (GstAspectRatioCrop * |
| aspect_ratio_crop, GstCaps * caps); |
| static gboolean gst_aspect_ratio_crop_sink_event (GstPad * pad, |
| GstObject * parent, GstEvent * evt); |
| static void gst_aspect_ratio_crop_finalize (GObject * object); |
| static void gst_aspect_ratio_transform_structure (GstAspectRatioCrop * |
| aspect_ratio_crop, GstStructure * structure, GstStructure ** new_structure, |
| gboolean set_videocrop); |
| |
| static void |
| gst_aspect_ratio_crop_set_cropping (GstAspectRatioCrop * aspect_ratio_crop, |
| gint top, gint right, gint bottom, gint left) |
| { |
| GValue value = { 0 }; |
| if (G_UNLIKELY (!aspect_ratio_crop->videocrop)) { |
| GST_WARNING_OBJECT (aspect_ratio_crop, |
| "Can't set the settings if there is no cropping element"); |
| return; |
| } |
| |
| g_value_init (&value, G_TYPE_INT); |
| g_value_set_int (&value, top); |
| GST_DEBUG_OBJECT (aspect_ratio_crop, "set top cropping to: %d", top); |
| g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "top", |
| &value); |
| g_value_set_int (&value, right); |
| GST_DEBUG_OBJECT (aspect_ratio_crop, "set right cropping to: %d", right); |
| g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "right", |
| &value); |
| g_value_set_int (&value, bottom); |
| GST_DEBUG_OBJECT (aspect_ratio_crop, "set bottom cropping to: %d", bottom); |
| g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "bottom", |
| &value); |
| g_value_set_int (&value, left); |
| GST_DEBUG_OBJECT (aspect_ratio_crop, "set left cropping to: %d", left); |
| g_object_set_property (G_OBJECT (aspect_ratio_crop->videocrop), "left", |
| &value); |
| |
| g_value_unset (&value); |
| } |
| |
| static gboolean |
| gst_aspect_ratio_crop_set_caps (GstAspectRatioCrop * aspect_ratio_crop, |
| GstCaps * caps) |
| { |
| GstPad *peer_pad; |
| GstStructure *structure; |
| gboolean ret; |
| |
| g_mutex_lock (&aspect_ratio_crop->crop_lock); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| gst_aspect_ratio_transform_structure (aspect_ratio_crop, structure, NULL, |
| TRUE); |
| peer_pad = |
| gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop), |
| "sink"); |
| ret = gst_pad_set_caps (peer_pad, caps); |
| gst_object_unref (peer_pad); |
| g_mutex_unlock (&aspect_ratio_crop->crop_lock); |
| return ret; |
| } |
| |
| static gboolean |
| gst_aspect_ratio_crop_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * evt) |
| { |
| gboolean ret; |
| GstAspectRatioCrop *aspect_ratio_crop = GST_ASPECT_RATIO_CROP (parent); |
| |
| ret = |
| aspect_ratio_crop->sinkpad_old_eventfunc (pad, parent, |
| gst_event_ref (evt)); |
| |
| switch (GST_EVENT_TYPE (evt)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (evt, &caps); |
| ret = gst_aspect_ratio_crop_set_caps (aspect_ratio_crop, caps); |
| break; |
| } |
| default: |
| break; |
| } |
| gst_event_unref (evt); |
| |
| return ret; |
| } |
| |
| static void |
| gst_aspect_ratio_crop_class_init (GstAspectRatioCropClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *element_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| element_class = (GstElementClass *) klass; |
| |
| gobject_class->set_property = gst_aspect_ratio_crop_set_property; |
| gobject_class->get_property = gst_aspect_ratio_crop_get_property; |
| gobject_class->finalize = gst_aspect_ratio_crop_finalize; |
| |
| g_object_class_install_property (gobject_class, ARG_ASPECT_RATIO_CROP, |
| gst_param_spec_fraction ("aspect-ratio", "aspect-ratio", |
| "Target aspect-ratio of video", 0, 1, G_MAXINT, 1, 0, 1, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_set_static_metadata (element_class, "aspectratiocrop", |
| "Filter/Effect/Video", |
| "Crops video into a user-defined aspect-ratio", |
| "Thijs Vermeir <thijsvermeir@gmail.com>"); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&sink_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&src_template)); |
| } |
| |
| static void |
| gst_aspect_ratio_crop_finalize (GObject * object) |
| { |
| GstAspectRatioCrop *aspect_ratio_crop; |
| |
| aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object); |
| |
| g_mutex_clear (&aspect_ratio_crop->crop_lock); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_aspect_ratio_crop_init (GstAspectRatioCrop * aspect_ratio_crop) |
| { |
| GstPad *link_pad; |
| GstPad *src_pad; |
| |
| GST_DEBUG_CATEGORY_INIT (aspect_ratio_crop_debug, "aspectratiocrop", 0, |
| "aspectratiocrop"); |
| |
| aspect_ratio_crop->ar_num = 0; |
| aspect_ratio_crop->ar_denom = 1; |
| |
| g_mutex_init (&aspect_ratio_crop->crop_lock); |
| |
| /* add the transform element */ |
| aspect_ratio_crop->videocrop = gst_element_factory_make ("videocrop", NULL); |
| gst_bin_add (GST_BIN (aspect_ratio_crop), aspect_ratio_crop->videocrop); |
| |
| /* create ghost pad src */ |
| link_pad = |
| gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop), |
| "src"); |
| src_pad = gst_ghost_pad_new ("src", link_pad); |
| gst_pad_set_query_function (src_pad, |
| GST_DEBUG_FUNCPTR (gst_aspect_ratio_crop_src_query)); |
| gst_element_add_pad (GST_ELEMENT (aspect_ratio_crop), src_pad); |
| gst_object_unref (link_pad); |
| /* create ghost pad sink */ |
| link_pad = |
| gst_element_get_static_pad (GST_ELEMENT (aspect_ratio_crop->videocrop), |
| "sink"); |
| aspect_ratio_crop->sink = gst_ghost_pad_new ("sink", link_pad); |
| gst_element_add_pad (GST_ELEMENT (aspect_ratio_crop), |
| aspect_ratio_crop->sink); |
| gst_object_unref (link_pad); |
| |
| aspect_ratio_crop->sinkpad_old_eventfunc = |
| GST_PAD_EVENTFUNC (aspect_ratio_crop->sink); |
| gst_pad_set_event_function (aspect_ratio_crop->sink, |
| GST_DEBUG_FUNCPTR (gst_aspect_ratio_crop_sink_event)); |
| } |
| |
| static void |
| gst_aspect_ratio_transform_structure (GstAspectRatioCrop * aspect_ratio_crop, |
| GstStructure * structure, GstStructure ** new_structure, |
| gboolean set_videocrop) |
| { |
| gdouble incoming_ar; |
| gdouble requested_ar; |
| gint width, height; |
| gint cropvalue; |
| gint par_d, par_n; |
| |
| /* Check if we need to change the aspect ratio */ |
| if (aspect_ratio_crop->ar_num < 1) { |
| GST_DEBUG_OBJECT (aspect_ratio_crop, "No cropping requested"); |
| goto beach; |
| } |
| |
| /* get the information from the caps */ |
| if (!gst_structure_get_int (structure, "width", &width) || |
| !gst_structure_get_int (structure, "height", &height)) |
| goto beach; |
| |
| if (!gst_structure_get_fraction (structure, "pixel-aspect-ratio", |
| &par_n, &par_d)) { |
| par_d = par_n = 1; |
| } |
| |
| incoming_ar = ((gdouble) (width * par_n)) / (height * par_d); |
| GST_LOG_OBJECT (aspect_ratio_crop, |
| "incoming caps width(%d), height(%d), par (%d/%d) : ar = %f", width, |
| height, par_n, par_d, incoming_ar); |
| |
| requested_ar = |
| (gdouble) aspect_ratio_crop->ar_num / aspect_ratio_crop->ar_denom; |
| |
| /* check if the original aspect-ratio is the aspect-ratio that we want */ |
| if (requested_ar == incoming_ar) { |
| GST_DEBUG_OBJECT (aspect_ratio_crop, |
| "Input video already has the correct aspect ratio (%.3f == %.3f)", |
| incoming_ar, requested_ar); |
| goto beach; |
| } else if (requested_ar > incoming_ar) { |
| /* fix aspect ratio with cropping on top and bottom */ |
| cropvalue = |
| ((((double) aspect_ratio_crop->ar_denom / |
| (double) (aspect_ratio_crop->ar_num)) * ((double) par_n / |
| (double) par_d) * width) - height) / 2; |
| if (cropvalue < 0) { |
| cropvalue *= -1; |
| } |
| if (cropvalue >= (height / 2)) |
| goto crop_failed; |
| if (set_videocrop) { |
| gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, cropvalue, 0, |
| cropvalue, 0); |
| } |
| if (new_structure) { |
| *new_structure = gst_structure_copy (structure); |
| gst_structure_set (*new_structure, |
| "height", G_TYPE_INT, (int) (height - (cropvalue * 2)), NULL); |
| } |
| } else { |
| /* fix aspect ratio with cropping on left and right */ |
| cropvalue = |
| ((((double) aspect_ratio_crop->ar_num / |
| (double) (aspect_ratio_crop->ar_denom)) * ((double) par_d / |
| (double) par_n) * height) - width) / 2; |
| if (cropvalue < 0) { |
| cropvalue *= -1; |
| } |
| if (cropvalue >= (width / 2)) |
| goto crop_failed; |
| if (set_videocrop) { |
| gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, 0, cropvalue, |
| 0, cropvalue); |
| } |
| if (new_structure) { |
| *new_structure = gst_structure_copy (structure); |
| gst_structure_set (*new_structure, |
| "width", G_TYPE_INT, (int) (width - (cropvalue * 2)), NULL); |
| } |
| } |
| |
| return; |
| |
| crop_failed: |
| GST_WARNING_OBJECT (aspect_ratio_crop, |
| "can't crop to aspect ratio requested"); |
| goto beach; |
| beach: |
| if (set_videocrop) { |
| gst_aspect_ratio_crop_set_cropping (aspect_ratio_crop, 0, 0, 0, 0); |
| } |
| |
| if (new_structure) { |
| *new_structure = gst_structure_copy (structure); |
| } |
| } |
| |
| static GstCaps * |
| gst_aspect_ratio_crop_transform_caps (GstAspectRatioCrop * aspect_ratio_crop, |
| GstCaps * caps) |
| { |
| GstCaps *transform; |
| gint size, i; |
| |
| transform = gst_caps_new_empty (); |
| |
| size = gst_caps_get_size (caps); |
| |
| for (i = 0; i < size; i++) { |
| GstStructure *s; |
| GstStructure *trans_s; |
| |
| s = gst_caps_get_structure (caps, i); |
| |
| gst_aspect_ratio_transform_structure (aspect_ratio_crop, s, &trans_s, |
| FALSE); |
| gst_caps_append_structure (transform, trans_s); |
| } |
| |
| return transform; |
| } |
| |
| static GstCaps * |
| gst_aspect_ratio_crop_get_caps (GstPad * pad, GstCaps * filter) |
| { |
| GstPad *peer; |
| GstAspectRatioCrop *aspect_ratio_crop; |
| GstCaps *return_caps; |
| |
| aspect_ratio_crop = GST_ASPECT_RATIO_CROP (gst_pad_get_parent (pad)); |
| |
| g_mutex_lock (&aspect_ratio_crop->crop_lock); |
| |
| peer = gst_pad_get_peer (aspect_ratio_crop->sink); |
| if (peer == NULL) { |
| return_caps = gst_static_pad_template_get_caps (&src_template); |
| } else { |
| GstCaps *peer_caps; |
| |
| peer_caps = gst_pad_query_caps (peer, filter); |
| return_caps = |
| gst_aspect_ratio_crop_transform_caps (aspect_ratio_crop, peer_caps); |
| gst_caps_unref (peer_caps); |
| gst_object_unref (peer); |
| } |
| |
| g_mutex_unlock (&aspect_ratio_crop->crop_lock); |
| gst_object_unref (aspect_ratio_crop); |
| |
| if (return_caps && filter) { |
| GstCaps *tmp = |
| gst_caps_intersect_full (filter, return_caps, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_replace (&return_caps, tmp); |
| gst_caps_unref (tmp); |
| } |
| |
| return return_caps; |
| } |
| |
| static gboolean |
| gst_aspect_ratio_crop_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| gboolean res = FALSE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter, *caps; |
| |
| gst_query_parse_caps (query, &filter); |
| caps = gst_aspect_ratio_crop_get_caps (pad, filter); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| res = TRUE; |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| return res; |
| } |
| |
| static void |
| gst_aspect_ratio_crop_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstAspectRatioCrop *aspect_ratio_crop; |
| gboolean recheck = FALSE; |
| |
| aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object); |
| |
| GST_OBJECT_LOCK (aspect_ratio_crop); |
| switch (prop_id) { |
| case ARG_ASPECT_RATIO_CROP: |
| if (GST_VALUE_HOLDS_FRACTION (value)) { |
| aspect_ratio_crop->ar_num = gst_value_get_fraction_numerator (value); |
| aspect_ratio_crop->ar_denom = |
| gst_value_get_fraction_denominator (value); |
| recheck = gst_pad_has_current_caps (aspect_ratio_crop->sink); |
| } |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| GST_OBJECT_UNLOCK (aspect_ratio_crop); |
| |
| if (recheck) { |
| GstCaps *caps = gst_pad_get_current_caps (aspect_ratio_crop->sink); |
| gst_aspect_ratio_crop_set_caps (aspect_ratio_crop, caps); |
| gst_caps_unref (caps); |
| } |
| } |
| |
| static void |
| gst_aspect_ratio_crop_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstAspectRatioCrop *aspect_ratio_crop; |
| |
| aspect_ratio_crop = GST_ASPECT_RATIO_CROP (object); |
| |
| GST_OBJECT_LOCK (aspect_ratio_crop); |
| switch (prop_id) { |
| case ARG_ASPECT_RATIO_CROP: |
| gst_value_set_fraction (value, aspect_ratio_crop->ar_num, |
| aspect_ratio_crop->ar_denom); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| GST_OBJECT_UNLOCK (aspect_ratio_crop); |
| } |