/* GStreamer * Copyright 2023 Igalia S.L. * @author: Thibault Saunier * * 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-autovideoflip * @title: autovideoflip * * The #autovideoflip element is used to flip the video plugging the right * element depending on caps and underlying buffer memory. * * Since: 1.24 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gstautovideo.h" #include "gstautovideoflip.h" GST_DEBUG_CATEGORY (autovideoflip_debug); #define GST_CAT_DEFAULT (autovideoflip_debug) #define PROP_DIRECTION_DEFAULT GST_VIDEO_ORIENTATION_IDENTITY /* GstVideoFlip properties */ enum { PROP_0, PROP_VIDEO_DIRECTION }; struct _GstAutoVideoFlip { GstBaseAutoConvert parent; GstVideoOrientationMethod direction; GList *bindings; }; G_DEFINE_TYPE (GstAutoVideoFlip, gst_auto_video_flip, GST_TYPE_BASE_AUTO_CONVERT); GST_ELEMENT_REGISTER_DEFINE (autovideoflip, "autovideoflip", GST_RANK_NONE, gst_auto_video_flip_get_type ()); static void gst_auto_video_flip_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAutoVideoFlip *self = GST_AUTO_VIDEO_FLIP (object); switch (prop_id) { case PROP_VIDEO_DIRECTION: self->direction = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_auto_video_flip_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstAutoVideoFlip *self = GST_AUTO_VIDEO_FLIP (object); switch (prop_id) { case PROP_VIDEO_DIRECTION: g_value_set_enum (value, self->direction); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } #if !GLIB_CHECK_VERSION(2, 68, 0) static GObject * g_binding_dup_target (GBinding * binding) { return g_object_ref (g_binding_get_target (binding)); } #endif static gboolean element_is_handled_video_flip (GstElement * element) { GstElementFactory *factory = gst_element_get_factory (element); return !g_strcmp0 (GST_OBJECT_NAME (factory), "glvideoflip") || !g_strcmp0 (GST_OBJECT_NAME (factory), "videoflip"); } static gboolean gst_auto_video_flip_transform_to (GBinding * binding, const GValue * from, GValue * to_value, gpointer _udata) { g_value_set_enum (to_value, g_value_get_enum (from)); return TRUE; } static void gst_auto_video_flip_deep_element_added (GstBin * bin, GstBin * sub_bin, GstElement * element) { GstAutoVideoFlip *self = GST_AUTO_VIDEO_FLIP (bin); GList *new_bindings = NULL; if (!element_is_handled_video_flip (element)) goto done; GST_OBJECT_LOCK (bin); for (GList * tmp = self->bindings; tmp; tmp = tmp->next) { GBinding *binding = tmp->data; GObject *target = g_binding_dup_target (binding); if (GST_ELEMENT (target) == element) { GST_INFO_OBJECT (self, "Newly added element %s already bound", GST_OBJECT_NAME (gst_element_get_factory (element))); GST_OBJECT_UNLOCK (bin); gst_object_unref (target); goto done; } gst_object_unref (target); } GST_OBJECT_UNLOCK (bin); new_bindings = g_list_prepend (new_bindings, g_object_bind_property_full (bin, "video-direction", element, "video-direction", G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT, gst_auto_video_flip_transform_to, NULL, NULL, NULL) ); GST_OBJECT_LOCK (bin); self->bindings = g_list_concat (self->bindings, new_bindings); GST_OBJECT_UNLOCK (bin); done: GST_BIN_CLASS (gst_auto_video_flip_parent_class)->deep_element_added (bin, sub_bin, element); } static void gst_auto_video_flip_deep_element_removed (GstBin * bin, GstBin * sub_bin, GstElement * element) { GList *bindings = NULL; GstAutoVideoFlip *self = GST_AUTO_VIDEO_FLIP (bin); if (!element_is_handled_video_flip (element)) goto done; GST_OBJECT_LOCK (bin); for (GList * tmp = self->bindings; tmp; tmp = tmp->next) { GBinding *binding = tmp->data; GObject *target = g_binding_dup_target (binding); if (GST_ELEMENT (target) == element) { GList *node = tmp; bindings = g_list_prepend (bindings, binding); tmp = tmp->prev; self->bindings = g_list_delete_link (self->bindings, node); if (!tmp) break; } gst_object_unref (target); } GST_OBJECT_UNLOCK (bin); done: GST_BIN_CLASS (gst_auto_video_flip_parent_class)->deep_element_removed (bin, sub_bin, element); } static void gst_auto_video_flip_class_init (GstAutoVideoFlipClass * klass) { GstElementClass *gstelement_class = (GstElementClass *) klass; GObjectClass *gobject_class = (GObjectClass *) klass; GstBinClass *gstbin_class = (GstBinClass *) klass; GST_DEBUG_CATEGORY_INIT (autovideoflip_debug, "autovideoflip", 0, "Auto video flipper"); gobject_class->set_property = gst_auto_video_flip_set_property; gobject_class->get_property = gst_auto_video_flip_get_property; g_object_class_install_property (gobject_class, PROP_VIDEO_DIRECTION, g_param_spec_enum ("video-direction", "video-direction", "Video direction: rotation and flipping", GST_TYPE_VIDEO_ORIENTATION_METHOD, PROP_DIRECTION_DEFAULT, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); gst_element_class_set_static_metadata (gstelement_class, "Flip the video plugging the right element depending on caps", "Bin/Filter/Effect/Video", "Selects the right video flip element based on the caps", "Thibault Saunier "); gstbin_class->deep_element_added = gst_auto_video_flip_deep_element_added; gstbin_class->deep_element_removed = gst_auto_video_flip_deep_element_removed; } static void gst_auto_video_flip_init (GstAutoVideoFlip * self) { self->direction = PROP_DIRECTION_DEFAULT; /* *INDENT-OFF* */ static const GstAutoVideoFilterGenerator gen[] = { { .first_elements = { "bayer2rgb", NULL}, .colorspace_converters = { "videoconvertscale", NULL }, .last_elements = { NULL } , .filters = { "videoflip", NULL }, .rank = GST_RANK_MARGINAL, }, { .first_elements = { NULL, }, .colorspace_converters = { "videoconvertscale", NULL }, .last_elements = { NULL }, .filters = { "videoflip" }, .rank = GST_RANK_SECONDARY, }, { .first_elements = { NULL, }, .colorspace_converters = { "videoconvertscale", NULL }, .last_elements = { "rgb2bayer", NULL }, .filters = { "videoflip" }, .rank = GST_RANK_MARGINAL, }, { .first_elements = { "glupload" }, .colorspace_converters = { "glcolorconvert", NULL }, .last_elements = { NULL, }, .filters = { "glvideoflip" }, .rank = GST_RANK_PRIMARY, }, { .first_elements = { NULL }, .colorspace_converters = { "glcolorconvert", NULL }, .last_elements = { NULL, }, .filters = { "glvideoflip" }, .rank = GST_RANK_PRIMARY, }, { .first_elements = { "videoconvertscale", "glupload", NULL }, .colorspace_converters = { NULL }, .last_elements = { NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_MARGINAL + 1, }, { .first_elements = { NULL }, .colorspace_converters = { NULL }, .last_elements = { "gldownload", NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_MARGINAL + 1, }, { /* CUDA -> GL */ .first_elements = { "capsfilter caps=video/x-raw(memory:CUDAMemory)", "cudadownload", NULL }, .colorspace_converters = { "glcolorconvert", "glcolorscale", "glcolorconvert", NULL }, .last_elements = { NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_PRIMARY - 1, }, { /* CUDA -> CUDA */ .first_elements = { "capsfilter caps=video/x-raw(memory:CUDAMemory)", "cudadownload", NULL }, .colorspace_converters = { "glcolorconvert", "glcolorscale", "glcolorconvert", NULL }, .last_elements = { "cudaupload", "capsfilter caps=video/x-raw(memory:CUDAMemory)", NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_SECONDARY - 1, }, { /* Software -> CUDA (uploading as soon as possible) */ .first_elements = { "glupload", NULL }, .colorspace_converters = { "glcoloconvert", NULL }, .last_elements = { "cudaupload", "capsfilter caps=video/x-raw(memory:CUDAMemory)", NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_MARGINAL, }, { /* CUDA -> Software */ .first_elements = { "capsfilter caps=video/x-raw(memory:CUDAMemory)", "cudadownload", NULL }, .colorspace_converters = { "glcolorconvert", "glcolorscale", "glcolorconvert", NULL }, .last_elements = { "gldownload", NULL }, .filters = { "glvideoflip", NULL }, .rank = GST_RANK_MARGINAL, }, { /* Worst case we upload/download as required */ .first_elements = { NULL}, .colorspace_converters = { NULL }, .last_elements = { NULL }, .filters = { NULL }, .rank = 0, }, }; /* *INDENT-ON* */ gst_auto_video_register_well_known_bins (GST_BASE_AUTO_CONVERT (self), gen); }