From 2b85e5f99c3cadfb3f7d2c1ab25322cb359884ec Mon Sep 17 00:00:00 2001 From: Vivienne Watermeier Date: Mon, 17 Jan 2022 16:10:37 +0100 Subject: [PATCH] glvideomixerelement: send translated navigation events to the relevant sink pads Part-of: --- .../gst-plugins-base/ext/gl/gstglvideomixer.c | 88 +++++++++ .../tests/check/libs/gstglvideomixerelement.c | 182 ++++++++++++++++++ .../gst-plugins-base/tests/check/meson.build | 1 + 3 files changed, 271 insertions(+) create mode 100644 subprojects/gst-plugins-base/tests/check/libs/gstglvideomixerelement.c diff --git a/subprojects/gst-plugins-base/ext/gl/gstglvideomixer.c b/subprojects/gst-plugins-base/ext/gl/gstglvideomixer.c index 14dabcbb2b..28f2ac9dfc 100644 --- a/subprojects/gst-plugins-base/ext/gl/gstglvideomixer.c +++ b/subprojects/gst-plugins-base/ext/gl/gstglvideomixer.c @@ -193,6 +193,8 @@ static void gst_gl_video_mixer_input_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_gl_video_mixer_input_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); +static gboolean gst_gl_video_mixer_src_event (GstAggregator * agg, + GstEvent * event); typedef struct _GstGLVideoMixerInput GstGLVideoMixerInput; typedef GstGhostPadClass GstGLVideoMixerInputClass; @@ -933,6 +935,7 @@ gst_gl_video_mixer_class_init (GstGLVideoMixerClass * klass) vagg_class->update_caps = _update_caps; + agg_class->src_event = gst_gl_video_mixer_src_event; agg_class->fixate_src_caps = _fixate_caps; agg_class->propose_allocation = gst_gl_video_mixer_propose_allocation; @@ -1193,6 +1196,91 @@ _reset_gl (GstGLContext * context, GstGLVideoMixer * video_mixer) gst_element_foreach_sink_pad (GST_ELEMENT (video_mixer), _reset_pad_gl, NULL); } +static gboolean +is_point_contained (const GstVideoRectangle rect, const gint px, const gint py) +{ + if ((px >= rect.x) && (px <= rect.x + rect.w) && + (py >= rect.y) && (py <= rect.y + rect.h)) + return TRUE; + return FALSE; +} + +static gboolean +src_pad_mouse_event (GstElement * element, GstPad * pad, gpointer user_data) +{ + GstGLVideoMixer *mix = GST_GL_VIDEO_MIXER (element); + GstGLVideoMixerPad *mix_pad = GST_GL_VIDEO_MIXER_PAD (pad); + GstCaps *caps = gst_pad_get_current_caps (pad); + GstStructure *event_st, *caps_st; + gint par_n, par_d; + gdouble event_x, event_y; + GstVideoRectangle rect; + + event_st = + gst_structure_copy (gst_event_get_structure (GST_EVENT_CAST (user_data))); + caps_st = gst_structure_copy (gst_caps_get_structure (caps, 0)); + + gst_structure_get (event_st, "pointer_x", G_TYPE_DOUBLE, &event_x, + "pointer_y", G_TYPE_DOUBLE, &event_y, NULL); + + /* Find output rectangle of this pad */ + gst_structure_get_fraction (caps_st, "pixel-aspect-ratio", &par_n, &par_d); + _mixer_pad_get_output_size (mix, mix_pad, par_n, par_d, &(rect.w), &(rect.h)); + rect.x = mix_pad->xpos; + rect.y = mix_pad->ypos; + + /* Translate coordinates and send event if it lies in this rectangle */ + if (is_point_contained (rect, event_x, event_y)) { + GstVideoAggregatorPad *vpad = GST_VIDEO_AGGREGATOR_PAD_CAST (mix_pad); + gdouble w, h, x, y; + + w = (gdouble) GST_VIDEO_INFO_WIDTH (&vpad->info); + h = (gdouble) GST_VIDEO_INFO_HEIGHT (&vpad->info); + x = (event_x - (gdouble) rect.x) * (w / (gdouble) rect.w); + y = (event_y - (gdouble) rect.y) * (h / (gdouble) rect.h); + + gst_structure_set (event_st, "pointer_x", G_TYPE_DOUBLE, x, + "pointer_y", G_TYPE_DOUBLE, y, NULL); + gst_pad_push_event (pad, gst_event_new_navigation (event_st)); + } else { + gst_structure_free (event_st); + } + + gst_structure_free (caps_st); + return TRUE; +} + +static gboolean +gst_gl_video_mixer_src_event (GstAggregator * agg, GstEvent * event) +{ + GstNavigationEventType event_type; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NAVIGATION: + { + event_type = gst_navigation_event_get_type (event); + switch (event_type) { + case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS: + case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE: + case GST_NAVIGATION_EVENT_MOUSE_MOVE: + case GST_NAVIGATION_EVENT_MOUSE_SCROLL: + gst_element_foreach_sink_pad (GST_ELEMENT_CAST (agg), + src_pad_mouse_event, event); + gst_event_unref (event); + return FALSE; + + default: + break; + } + } + + default: + break; + } + + return GST_AGGREGATOR_CLASS (parent_class)->src_event (agg, event); +} + static gboolean gst_gl_video_mixer_set_caps (GstGLMixer * mixer, GstCaps * outcaps) { diff --git a/subprojects/gst-plugins-base/tests/check/libs/gstglvideomixerelement.c b/subprojects/gst-plugins-base/tests/check/libs/gstglvideomixerelement.c new file mode 100644 index 0000000000..d71de15791 --- /dev/null +++ b/subprojects/gst-plugins-base/tests/check/libs/gstglvideomixerelement.c @@ -0,0 +1,182 @@ +/* GStreamer + * + * Copyright (C) 2022 Vivienne Watermeier + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include + +#include + +typedef struct _ProbeEvent +{ + gboolean received; + gdouble x_pos, y_pos; +} ProbeEvent; + +static GstPadProbeReturn +probe_nav_event (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstEvent *event = gst_pad_probe_info_get_event (info); + ProbeEvent *probe_ev = (ProbeEvent *) user_data; + + if (GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION) { + probe_ev->received = TRUE; + gst_navigation_event_parse_mouse_move_event (event, + &(probe_ev->x_pos), &(probe_ev->y_pos)); + } + + return GST_PAD_PROBE_OK; +} + +GST_START_TEST (test_navigation_events) +{ + GstElement *bin, *src1, *src2, *src3, *filter1, *filter2, *filter3; + GstElement *compositor, *sink; + GstPad *srcpad, *sinkpad; + GstCaps *caps; + gboolean res; + ProbeEvent probe_events[3]; + GstStateChangeReturn state_res; + GstEvent *event; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + src3 = gst_element_factory_make ("videotestsrc", "src3"); + filter1 = gst_element_factory_make ("capsfilter", "filter1"); + filter2 = gst_element_factory_make ("capsfilter", "filter2"); + filter3 = gst_element_factory_make ("capsfilter", "filter3"); + compositor = gst_element_factory_make ("compositor", "compositor"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, src2, src3, filter1, filter2, filter3, + compositor, sink, NULL); + + /* configure capsfilters */ + caps = gst_caps_from_string ("video/x-raw,width=800,height=400"); + g_object_set (filter1, "caps", caps, NULL); + gst_caps_unref (caps); + caps = gst_caps_from_string ("video/x-raw,width=400,height=200"); + g_object_set (filter2, "caps", caps, NULL); + gst_caps_unref (caps); + caps = gst_caps_from_string ("video/x-raw,width=200,height=50"); + g_object_set (filter3, "caps", caps, NULL); + gst_caps_unref (caps); + + res = gst_element_link_many (src1, filter1, compositor, NULL); + fail_unless (res == TRUE, NULL); + res = gst_element_link_many (src2, filter2, compositor, NULL); + fail_unless (res == TRUE, NULL); + res = gst_element_link_many (src3, filter3, compositor, NULL); + fail_unless (res == TRUE, NULL); + res = gst_element_link (compositor, sink); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (compositor, "src"); + gst_object_unref (srcpad); + + /* configure pads and add probes */ + srcpad = gst_element_get_static_pad (filter1, "src"); + sinkpad = gst_pad_get_peer (srcpad); + g_object_set (sinkpad, + "width", 400, "height", 300, "xpos", 200, "ypos", 100, NULL); + probe_events[0].received = FALSE; + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + probe_nav_event, (gpointer) probe_events, NULL); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (filter2, "src"); + sinkpad = gst_pad_get_peer (srcpad); + g_object_set (sinkpad, + "width", 400, "height", 200, "xpos", 20, "ypos", 0, NULL); + probe_events[1].received = FALSE; + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + probe_nav_event, (gpointer) (probe_events + 1), NULL); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (filter3, "src"); + sinkpad = gst_pad_get_peer (srcpad); + g_object_set (sinkpad, + "width", 200, "height", 50, "xpos", 0, "ypos", 0, NULL); + probe_events[2].received = FALSE; + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + probe_nav_event, (gpointer) (probe_events + 2), NULL); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + event = + gst_event_new_navigation (gst_structure_new + ("application/x-gst-navigation", "event", G_TYPE_STRING, "mouse-move", + "button", G_TYPE_INT, 0, "pointer_x", G_TYPE_DOUBLE, 350.0, + "pointer_y", G_TYPE_DOUBLE, 100.0, NULL)); + + GST_INFO ("starting test"); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* send event and validate */ + res = gst_element_send_event (sink, event); + fail_unless (res == TRUE, NULL); + + /* check received events */ + ck_assert_msg (probe_events[0].received); + ck_assert_msg (probe_events[1].received); + ck_assert_msg (!probe_events[2].received); + + ck_assert_int_eq ((gint) probe_events[0].x_pos, 300); + ck_assert_int_eq ((gint) probe_events[0].y_pos, 0); + ck_assert_int_eq ((gint) probe_events[1].x_pos, 330); + ck_assert_int_eq ((gint) probe_events[1].y_pos, 100); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + gst_object_unref (bin); +} + +GST_END_TEST; + +static Suite * +gst_gl_videomixer_element_suite (void) +{ + Suite *s = suite_create ("GstGLVideoMixerElement"); + TCase *tc_chain = tcase_create ("videomixerelement"); + + tcase_add_test (tc_chain, test_navigation_events); + + return s; +} + +GST_CHECK_MAIN (gst_gl_videomixer_element); diff --git a/subprojects/gst-plugins-base/tests/check/meson.build b/subprojects/gst-plugins-base/tests/check/meson.build index f135c1ffb9..e7dec6c56e 100644 --- a/subprojects/gst-plugins-base/tests/check/meson.build +++ b/subprojects/gst-plugins-base/tests/check/meson.build @@ -100,6 +100,7 @@ if build_gstgl and host_machine.system() != 'windows' [ 'libs/gstglsl.c', not build_gstgl, [gstgl_dep, gstglproto_dep]], [ 'libs/gstglslstage.c', not build_gstgl, [gstgl_dep, gstglproto_dep]], [ 'libs/gstglupload.c', not build_gstgl, [gstgl_dep, gstglproto_dep]], + [ 'libs/gstglvideomixerelement.c', not build_gstgl, [gstgl_dep, gstglproto_dep]], [ 'elements/glimagesink.c', not build_gstgl, [gstgl_dep, gstglproto_dep]], [ 'elements/glbin.c', not build_gstgl ], [ 'pipelines/gl-launch-lines.c', not build_gstgl ],