subdir('mpg123')
subdir('raw1394')
subdir('qt')
+subdir('qt6')
subdir('pulse')
subdir('shout2')
subdir('soup')
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstqt6elements.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ gboolean ret = FALSE;
+
+ ret |= GST_ELEMENT_REGISTER (qml6glsink, plugin);
+
+ return ret;
+}
+
+#ifndef GST_PACKAGE_NAME
+#define GST_PACKAGE_NAME "GStreamer Bad Plug-ins (qmake)"
+#define GST_PACKAGE_ORIGIN "Unknown package origin"
+#define GST_LICENSE "LGPL"
+#define PACKAGE "gst-plugins-bad (qmake)"
+#define PACKAGE_VERSION "1.21.0.1"
+#endif
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ qml6,
+ "Qt6 Qml plugin",
+ plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN)
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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:gstqml6glsink
+ *
+ * qml6glsink provides a way to render a video stream as a Qml object inside
+ * the Qml scene graph. This is achieved by providing the incoming OpenGL
+ * textures to Qt as a scene graph object.
+ *
+ * qml6glsink will attempt to retrieve the windowing system display connection
+ * that Qt is using (#GstGLDisplay). This may be different to any already
+ * existing window system display connection already in use in the pipeline for
+ * a number of reasons. A couple of examples of this are:
+ *
+ * 1. Adding qml6glsink to an already running pipeline
+ * 2. Not having any qml6glsink element start up before any
+ * other OpenGL-based element in the pipeline.
+ *
+ * If one of these scenarios occurs, then there will be multiple OpenGL contexts
+ * in use in the pipeline. This means that either the pipeline will fail to
+ * start up correctly, a downstream element may reject buffers, or a complete
+ * GPU->System memory->GPU transfer is performed for every buffer.
+ *
+ * The requirement to avoid this is that all elements share the same
+ * #GstGLDisplay object and as Qt cannot currently share an existing window
+ * system display connection, GStreamer must use the window system display
+ * connection provided by Qt. This window system display connection can be
+ * retrieved by either a qmlglsink element or a qmlgloverlay element. The
+ * recommended usage is to have either element (qmlglsink or qmlgloverlay)
+ * be the first to propagate the #GstGLDisplay for the entire pipeline to use by
+ * setting either element to the READY element state before any other OpenGL
+ * element in the pipeline.
+ *
+ * In a dynamically adding qmlglsink (or qmlgloverlay) to a pipeline case,
+ * there are some considerations for ensuring that the window system display
+ * and OpenGL contexts are compatible with Qt. When the qmlgloverlay (or
+ * qmlglsink) element is added and brought up to READY, it will propagate it's
+ * own #GstGLDisplay using the #GstContext mechanism regardless of any existing
+ * #GstGLDisplay used by the pipeline previously. In order for the new
+ * #GstGLDisplay to be used, the application must then set the provided
+ * #GstGLDisplay containing #GstContext on the pipeline. This may effectively
+ * cause each OpenGL element to replace the window system display and also the
+ * OpenGL context it is using. As such this process may take a significant
+ * amount of time and resources as objects are recreated in the new OpenGL
+ * context.
+ *
+ * All instances of qmlglsink and qmlgloverlay will return the exact same
+ * #GstGLDisplay object while the pipeline is running regardless of whether
+ * any qmlglsink or qmlgloverlay elements are added or removed from the
+ * pipeline.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstqt6elements.h"
+#include "gstqml6glsink.h"
+#include <QtGui/QGuiApplication>
+
+#include <gst/gl/gstglfuncs.h>
+
+#define GST_CAT_DEFAULT gst_debug_qml6_gl_sink
+GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
+
+static void gst_qml6_gl_sink_navigation_interface_init (GstNavigationInterface * iface);
+static void gst_qml6_gl_sink_finalize (GObject * object);
+static void gst_qml6_gl_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_qml6_gl_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static gboolean gst_qml6_gl_sink_stop (GstBaseSink * bsink);
+
+static gboolean gst_qml6_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
+
+static GstStateChangeReturn
+gst_qml6_gl_sink_change_state (GstElement * element, GstStateChange transition);
+
+static void gst_qml6_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end);
+static gboolean gst_qml6_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
+static GstFlowReturn gst_qml6_gl_sink_show_frame (GstVideoSink * bsink,
+ GstBuffer * buf);
+static gboolean gst_qml6_gl_sink_propose_allocation (GstBaseSink * bsink,
+ GstQuery * query);
+
+static GstStaticPadTemplate gst_qt_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) { RGB, RGBA }, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ", "
+ "texture-target = (string) 2D"));
+
+#define DEFAULT_FORCE_ASPECT_RATIO TRUE
+#define DEFAULT_PAR_N 0
+#define DEFAULT_PAR_D 1
+
+enum
+{
+ ARG_0,
+ PROP_WIDGET,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+};
+
+enum
+{
+ SIGNAL_0,
+ LAST_SIGNAL
+};
+
+#define gst_qml6_gl_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstQml6GLSink, gst_qml6_gl_sink,
+ GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT,
+ "qtsink", 0, "Qt Video Sink");
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_qml6_gl_sink_navigation_interface_init));
+GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qml6glsink, "qml6glsink",
+ GST_RANK_NONE, GST_TYPE_QML6_GL_SINK, qt6_element_init (plugin));
+
+static void
+gst_qml6_gl_sink_class_init (GstQml6GLSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+ GstVideoSinkClass *gstvideosink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+ gstvideosink_class = (GstVideoSinkClass *) klass;
+
+ gobject_class->set_property = gst_qml6_gl_sink_set_property;
+ gobject_class->get_property = gst_qml6_gl_sink_get_property;
+
+ gst_element_class_set_metadata (gstelement_class, "Qt6 Video Sink",
+ "Sink/Video", "A video sink that renders to a QQuickItem for Qt6",
+ "Matthew Waters <matthew@centricular.com>");
+
+ g_object_class_install_property (gobject_class, PROP_WIDGET,
+ g_param_spec_pointer ("widget", "QQuickItem",
+ "The QQuickItem to place in the object hierarchy",
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio",
+ DEFAULT_FORCE_ASPECT_RATIO,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
+ gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+ "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
+ G_MAXINT, 1, 1, 1,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ gst_element_class_add_static_pad_template (gstelement_class, &gst_qt_sink_template);
+
+ gobject_class->finalize = gst_qml6_gl_sink_finalize;
+
+ gstelement_class->change_state = gst_qml6_gl_sink_change_state;
+ gstbasesink_class->query = gst_qml6_gl_sink_query;
+ gstbasesink_class->set_caps = gst_qml6_gl_sink_set_caps;
+ gstbasesink_class->get_times = gst_qml6_gl_sink_get_times;
+ gstbasesink_class->propose_allocation = gst_qml6_gl_sink_propose_allocation;
+ gstbasesink_class->stop = gst_qml6_gl_sink_stop;
+
+ gstvideosink_class->show_frame = gst_qml6_gl_sink_show_frame;
+}
+
+static void
+gst_qml6_gl_sink_init (GstQml6GLSink * qt_sink)
+{
+ qt_sink->widget = QSharedPointer<Qt6GLVideoItemInterface>();
+ if (qt_sink->widget)
+ qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+}
+
+static void
+gst_qml6_gl_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+ switch (prop_id) {
+ case PROP_WIDGET: {
+ Qt6GLVideoItem *qt_item = static_cast<Qt6GLVideoItem *> (g_value_get_pointer (value));
+ if (qt_item) {
+ qt_sink->widget = qt_item->getInterface();
+ if (qt_sink->widget) {
+ qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+ }
+ } else {
+ qt_sink->widget.clear();
+ }
+ break;
+ }
+ case PROP_FORCE_ASPECT_RATIO:
+ g_return_if_fail (qt_sink->widget);
+ qt_sink->widget->setForceAspectRatio (g_value_get_boolean (value));
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ g_return_if_fail (qt_sink->widget);
+ qt_sink->widget->setDAR (gst_value_get_fraction_numerator (value),
+ gst_value_get_fraction_denominator (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_reset (GstQml6GLSink * qt_sink)
+{
+ if (qt_sink->display) {
+ gst_object_unref (qt_sink->display);
+ qt_sink->display = NULL;
+ }
+
+ if (qt_sink->context) {
+ gst_object_unref (qt_sink->context);
+ qt_sink->context = NULL;
+ }
+
+ if (qt_sink->qt_context) {
+ gst_object_unref (qt_sink->qt_context);
+ qt_sink->qt_context = NULL;
+ }
+}
+
+static void
+gst_qml6_gl_sink_finalize (GObject * object)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+ _reset (qt_sink);
+
+ qt_sink->widget.clear();
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_qml6_gl_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+ switch (prop_id) {
+ case PROP_WIDGET:
+ /* This is not really safe - the app needs to be
+ * sure the widget is going to be kept alive or
+ * this can crash */
+ if (qt_sink->widget)
+ g_value_set_pointer (value, qt_sink->widget->videoItem());
+ else
+ g_value_set_pointer (value, NULL);
+ break;
+ case PROP_FORCE_ASPECT_RATIO:
+ if (qt_sink->widget)
+ g_value_set_boolean (value, qt_sink->widget->getForceAspectRatio ());
+ else
+ g_value_set_boolean (value, DEFAULT_FORCE_ASPECT_RATIO);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ if (qt_sink->widget) {
+ gint num, den;
+ qt_sink->widget->getDAR (&num, &den);
+ gst_value_set_fraction (value, num, den);
+ } else {
+ gst_value_set_fraction (value, DEFAULT_PAR_N, DEFAULT_PAR_D);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_qml6_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) qt_sink, query,
+ qt_sink->display, qt_sink->context, qt_sink->qt_context))
+ return TRUE;
+
+ /* fallthrough */
+ }
+ default:
+ res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
+ break;
+ }
+
+ return res;
+}
+
+static gboolean
+gst_qml6_gl_sink_stop (GstBaseSink * bsink)
+{
+ return TRUE;
+}
+
+static GstStateChangeReturn
+gst_qml6_gl_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (element);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ QGuiApplication *app;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ app = static_cast<QGuiApplication *> (QCoreApplication::instance ());
+ if (!app) {
+ GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+ ("%s", "Failed to connect to Qt"),
+ ("%s", "Could not retrieve QGuiApplication instance"));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+
+ if (!qt_sink->widget) {
+ GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+ ("%s", "Required property \'widget\' not set"),
+ (NULL));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+
+ if (!qt_sink->widget->initWinSys()) {
+ GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+ ("%s", "Could not initialize window system"),
+ (NULL));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+
+ qt_sink->display = qt_sink->widget->getDisplay();
+ qt_sink->context = qt_sink->widget->getContext();
+ qt_sink->qt_context = qt_sink->widget->getQtContext();
+
+ if (!qt_sink->display || !qt_sink->context || !qt_sink->qt_context) {
+ GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+ ("%s", "Could not retrieve window system OpenGL configuration"),
+ (NULL));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+
+ GST_OBJECT_LOCK (qt_sink->display);
+ gst_gl_display_add_context (qt_sink->display, qt_sink->context);
+ GST_OBJECT_UNLOCK (qt_sink->display);
+
+ gst_gl_element_propagate_display_context (GST_ELEMENT (qt_sink), qt_sink->display);
+
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ if (qt_sink->widget)
+ qt_sink->widget->setBuffer(NULL);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_qml6_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+ *start = GST_BUFFER_TIMESTAMP (buf);
+ if (GST_BUFFER_DURATION_IS_VALID (buf))
+ *end = *start + GST_BUFFER_DURATION (buf);
+ else {
+ if (GST_VIDEO_INFO_FPS_N (&qt_sink->v_info) > 0) {
+ *end = *start +
+ gst_util_uint64_scale_int (GST_SECOND,
+ GST_VIDEO_INFO_FPS_D (&qt_sink->v_info),
+ GST_VIDEO_INFO_FPS_N (&qt_sink->v_info));
+ }
+ }
+ }
+}
+
+gboolean
+gst_qml6_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+
+ GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
+
+ if (!gst_video_info_from_caps (&qt_sink->v_info, caps))
+ return FALSE;
+
+ if (!qt_sink->widget)
+ return FALSE;
+
+ return qt_sink->widget->setCaps(caps);
+}
+
+static GstFlowReturn
+gst_qml6_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (vsink);
+
+ GST_TRACE ("rendering buffer:%p", buf);
+
+ if (qt_sink->widget)
+ qt_sink->widget->setBuffer(buf);
+
+ return GST_FLOW_OK;
+}
+
+static gboolean
+gst_qml6_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+ GstBufferPool *pool;
+ GstStructure *config;
+ GstCaps *caps;
+ guint size;
+ gboolean need_pool;
+
+ if (!qt_sink->display || !qt_sink->context)
+ return FALSE;
+
+ gst_query_parse_allocation (query, &caps, &need_pool);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ /* FIXME re-using buffer pool breaks renegotiation */
+ if ((pool = qt_sink->pool))
+ gst_object_ref (pool);
+
+ if (pool != NULL) {
+ GstCaps *pcaps;
+
+ /* we had a pool, check caps */
+ GST_DEBUG_OBJECT (qt_sink, "check existing pool caps");
+ config = gst_buffer_pool_get_config (pool);
+ gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
+
+ if (!gst_caps_is_equal (caps, pcaps)) {
+ GST_DEBUG_OBJECT (qt_sink, "pool has different caps");
+ /* different caps, we can't use this pool */
+ gst_object_unref (pool);
+ pool = NULL;
+ }
+ gst_structure_free (config);
+ } else {
+ GstVideoInfo info;
+
+ if (!gst_video_info_from_caps (&info, caps))
+ goto invalid_caps;
+
+ /* the normal size of a frame */
+ size = info.size;
+ }
+
+ if (pool == NULL && need_pool) {
+
+ GST_DEBUG_OBJECT (qt_sink, "create new pool");
+ pool = gst_gl_buffer_pool_new (qt_sink->context);
+
+ config = gst_buffer_pool_get_config (pool);
+ gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+ if (!gst_buffer_pool_set_config (pool, config))
+ goto config_failed;
+ }
+
+ /* we need at least 2 buffer because we hold on to the last one */
+ gst_query_add_allocation_pool (query, pool, size, 2, 0);
+ if (pool)
+ gst_object_unref (pool);
+
+ /* we also support various metadata */
+ gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
+
+ if (qt_sink->context->gl_vtable->FenceSync)
+ gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
+
+ return TRUE;
+
+ /* ERRORS */
+no_caps:
+ {
+ GST_DEBUG_OBJECT (bsink, "no caps specified");
+ return FALSE;
+ }
+invalid_caps:
+ {
+ GST_DEBUG_OBJECT (bsink, "invalid caps specified");
+ return FALSE;
+ }
+config_failed:
+ {
+ GST_DEBUG_OBJECT (bsink, "failed setting config");
+ return FALSE;
+ }
+}
+
+static void
+gst_qml6_gl_sink_navigation_send_event (GstNavigation * navigation,
+ GstEvent * event)
+{
+ GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (navigation);
+ GstPad *pad;
+
+ pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (qt_sink));
+
+ GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT,
+ gst_event_get_structure(event));
+
+ if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+ if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+ /* If upstream didn't handle the event we'll post a message with it
+ * for the application in case it wants to do something with it */
+ gst_element_post_message (GST_ELEMENT_CAST (qt_sink),
+ gst_navigation_message_new_event (GST_OBJECT_CAST (qt_sink), event));
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+}
+
+static void gst_qml6_gl_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+ iface->send_event_simple = gst_qml6_gl_sink_navigation_send_event;
+}
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+
+#ifndef __GST_QT6_SINK_H__
+#define __GST_QT6_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+#include "qt6glitem.h"
+
+typedef struct _GstQml6GLSinkPrivate GstQml6GLSinkPrivate;
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_QML6_GL_SINK (gst_qml6_gl_sink_get_type())
+G_DECLARE_FINAL_TYPE (GstQml6GLSink, gst_qml6_gl_sink, GST, QML6_GL_SINK, GstVideoSink)
+#define GST_QML6_GL_SINK_CAST(obj) ((GstQml6GLSink*)(obj))
+
+/**
+ * GstQml6GLSink:
+ *
+ * Opaque #GstQml6GLSink object
+ */
+struct _GstQml6GLSink
+{
+ /* <private> */
+ GstVideoSink parent;
+
+ GstVideoInfo v_info;
+ GstBufferPool *pool;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+ GstGLContext *qt_context;
+
+ QSharedPointer<Qt6GLVideoItemInterface> widget;
+};
+
+GstQml6GLSink * gst_qml6_gl_sink_new (void);
+
+G_END_DECLS
+
+#endif /* __GST_QT6_SINK_H__ */
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2022 Matthew Waters <matthew@centricular.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.
+ */
+
+#include "gstqsg6glnode.h"
+
+#include <QtQuick/QSGTextureProvider>
+#include <QtQuick/QSGSimpleTextureNode>
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/QSGTexture>
+
+#define GST_CAT_DEFAULT gst_qsg_texture_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+GstQSG6OpenGLNode::GstQSG6OpenGLNode(QQuickItem * item)
+{
+ static gsize _debug;
+
+ if (g_once_init_enter (&_debug)) {
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtqsgtexture", 0,
+ "Qt Scenegraph Texture");
+ g_once_init_leave (&_debug, 1);
+ }
+
+ gst_video_info_init (&this->v_info);
+
+ this->buffer_ = NULL;
+ this->sync_buffer_ = gst_buffer_new ();
+ this->dummy_tex_ = nullptr;
+ // TODO; handle windowChanged?
+ this->window_ = item->window();
+}
+
+GstQSG6OpenGLNode::~GstQSG6OpenGLNode()
+{
+ gst_buffer_replace (&this->buffer_, NULL);
+ gst_buffer_replace (&this->sync_buffer_, NULL);
+ this->buffer_was_bound = FALSE;
+ delete this->dummy_tex_;
+ this->dummy_tex_ = nullptr;
+}
+
+QSGTexture *
+GstQSG6OpenGLNode::texture() const
+{
+ return QSGSimpleTextureNode::texture();
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+void
+GstQSG6OpenGLNode::setCaps (GstCaps * caps)
+{
+ GST_LOG ("%p setCaps %" GST_PTR_FORMAT, this, caps);
+
+ if (caps)
+ gst_video_info_from_caps (&this->v_info, caps);
+ else
+ gst_video_info_init (&this->v_info);
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+GstBuffer *
+GstQSG6OpenGLNode::getBuffer ()
+{
+ GstBuffer *buffer = NULL;
+
+ if (this->buffer_)
+ buffer = gst_buffer_ref (this->buffer_);
+
+ return buffer;
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+void
+GstQSG6OpenGLNode::setBuffer (GstBuffer * buffer)
+{
+ GstGLContext *qt_context = NULL;
+ gboolean buffer_changed;
+
+ GST_LOG ("%p setBuffer %" GST_PTR_FORMAT, this, buffer);
+ /* FIXME: update more state here */
+ buffer_changed = gst_buffer_replace (&this->buffer_, buffer);
+
+ if (buffer_changed) {
+ GstGLContext *context;
+ GstGLSyncMeta *sync_meta;
+ GstMemory *mem;
+ guint tex_id;
+ QQuickWindow::CreateTextureOptions options = QQuickWindow::TextureHasAlphaChannel;
+ QSGTexture *texture = nullptr;
+ QSize texSize;
+
+ qt_context = gst_gl_context_get_current();
+ if (!qt_context)
+ goto use_dummy_tex;
+
+ if (!this->buffer_)
+ goto use_dummy_tex;
+ if (GST_VIDEO_INFO_FORMAT (&this->v_info) == GST_VIDEO_FORMAT_UNKNOWN)
+ goto use_dummy_tex;
+
+ this->mem_ = gst_buffer_peek_memory (this->buffer_, 0);
+ if (!this->mem_)
+ goto use_dummy_tex;
+
+ /* FIXME: should really lock the memory to prevent write access */
+ if (!gst_video_frame_map (&this->v_frame, &this->v_info, this->buffer_,
+ (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
+ g_assert_not_reached ();
+ goto use_dummy_tex;
+ }
+
+ mem = gst_buffer_peek_memory (this->buffer_, 0);
+ g_assert (gst_is_gl_memory (mem));
+
+ context = ((GstGLBaseMemory *)mem)->context;
+
+ sync_meta = gst_buffer_get_gl_sync_meta (this->sync_buffer_);
+ if (!sync_meta)
+ sync_meta = gst_buffer_add_gl_sync_meta (context, this->sync_buffer_);
+
+ gst_gl_sync_meta_set_sync_point (sync_meta, context);
+
+ gst_gl_sync_meta_wait (sync_meta, qt_context);
+
+ tex_id = *(guint *) this->v_frame.data[0];
+ GST_LOG ("%p binding Qt texture %u", this, tex_id);
+
+ texSize = QSize(GST_VIDEO_FRAME_WIDTH (&this->v_frame), GST_VIDEO_FRAME_HEIGHT (&this->v_frame));
+ // XXX: ideally, we would like to subclass the relevant texture object
+ // ourselves but this is good enough for now
+ texture = QNativeInterface::QSGOpenGLTexture::fromNative(tex_id, this->window_, texSize, options);
+
+ setTexture(texture);
+ setOwnsTexture(true);
+ markDirty(QSGNode::DirtyMaterial);
+
+ gst_video_frame_unmap (&this->v_frame);
+
+ /* Texture was successfully bound, so we do not need
+ * to use the dummy texture */
+ }
+
+ if (!texture()) {
+use_dummy_tex:
+ /* Create dummy texture if not already present. */
+ if (this->dummy_tex_ == nullptr) {
+ /* Make this a black 64x64 pixel RGBA texture.
+ * This size and format is supported pretty much everywhere, so these
+ * are a safe pick. (64 pixel sidelength must be supported according
+ * to the GLES2 spec, table 6.18.)
+ * Set min/mag filters to GL_LINEAR to make sure no mipmapping is used. */
+ const int tex_sidelength = 64;
+ QImage image(tex_sidelength, tex_sidelength, QImage::Format_ARGB32);
+ image.fill(QColor(0, 0, 0, 255));
+
+ this->dummy_tex_ = this->window_->createTextureFromImage(image);
+ }
+
+ g_assert (this->dummy_tex_ != nullptr);
+
+ if (texture() != this->dummy_tex_) {
+ setTexture(this->dummy_tex_);
+ setOwnsTexture(false);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ GST_LOG ("%p binding fallback dummy Qt texture %p", this, this->dummy_tex_);
+ }
+}
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2022 Matthew Waters <matthew@centricular.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.
+ */
+
+#pragma once
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include "gstqt6gl.h"
+#include <QtQuick/QQuickItem>
+#include <QtQuick/QSGTexture>
+#include <QtQuick/QSGTextureProvider>
+#include <QtQuick/QSGSimpleTextureNode>
+#include <QtGui/QOpenGLFunctions>
+
+class GstQSG6OpenGLNode : public QSGTextureProvider, public QSGSimpleTextureNode, protected QOpenGLFunctions
+{
+ Q_OBJECT
+
+public:
+ GstQSG6OpenGLNode(QQuickItem *item);
+ ~GstQSG6OpenGLNode();
+
+ QSGTexture *texture() const override;
+
+ void setCaps(GstCaps *caps);
+ void setBuffer(GstBuffer *buffer);
+ GstBuffer *getBuffer();
+
+ void updateQSGTexture();
+
+private:
+ QQuickWindow *window_;
+ GstBuffer * buffer_;
+ gboolean buffer_was_bound;
+ GstBuffer * sync_buffer_;
+ GstMemory * mem_;
+ QSGTexture *dummy_tex_;
+ GstVideoInfo v_info;
+ GstVideoFrame v_frame;
+};
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstqt6elements.h"
+#include "qt6glitem.h"
+#include <QtQml/QQmlApplicationEngine>
+
+void
+qt6_element_init (GstPlugin * plugin)
+{
+ static gsize res = FALSE;
+ if (g_once_init_enter (&res)) {
+ /* this means the plugin must be loaded before the qml engine is loaded */
+ qmlRegisterType<Qt6GLVideoItem> ("org.freedesktop.gstreamer.Qt6GLVideoItem", 1, 0, "GstGLQt6VideoItem");
+ g_once_init_leave (&res, TRUE);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2020 Huawei Technologies Co., Ltd.
+ * @Author: Julian Bouzas <julian.bouzas@collabora.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.
+ */
+
+#ifndef __GST_QT6_ELEMENTS_H__
+#define __GST_QT6_ELEMENTS_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+void qt6_element_init (GstPlugin * plugin);
+
+GST_ELEMENT_REGISTER_DECLARE (qml6glsink);
+
+G_END_DECLS
+
+#endif /* __GST_QT6_ELEMENTS_H__ */
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+#include <QtCore/qglobal.h>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
+#include <QtGui/qtgui-config.h>
+#endif
+
+#include <gst/gl/gstglconfig.h>
+
+/* The glext.h guard was renamed in 2018, but some software which
+ * includes their own copy of the GL headers (such as qt) might have
+ * older version which use the old guard. This would result in the
+ * header being included again (and symbols redefined).
+ *
+ * To avoid this, we define the "old" guard if the "new" guard is
+ * defined.*/
+#if GST_GL_HAVE_OPENGL
+#ifdef __gl_glext_h_
+#ifndef __glext_h_
+#define __glext_h_ 1
+#endif
+#endif
+#endif
+
+/* pulls in GLsync, see below */
+#include <QtGui/qopengl.h>
+
+/* qt uses the same trick as us to typedef GLsync on GLES2 but to a different
+ * type which confuses the preprocessor. Instead of trying to reconcile the
+ * two, we instead use the GLsync definition from Qt from above, and ensure
+ * that we don't typedef GLsync in gstglfuncs.h */
+#undef GST_GL_HAVE_GLSYNC
+#define GST_GL_HAVE_GLSYNC 1
+#include <gst/gl/gstglfuncs.h>
+
+#if defined(QT_OPENGL_ES_2)
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
+#endif /* defined(QT_OPENGL_ES_2) */
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2016 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * 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 "gstqt6glutility.h"
+#include <QtGui/QGuiApplication>
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+#include <gst/gl/x11/gstgldisplay_x11.h>
+//#include <QtPlatformHeaders/QGLXNativeContext>
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && (defined (HAVE_QT_WAYLAND) || defined (HAVE_QT_EGLFS) || defined (HAVE_QT_ANDROID))
+#include <gst/gl/egl/gstegl.h>
+#ifdef HAVE_QT_QPA_HEADER
+#include QT_QPA_HEADER
+#endif
+//#include <QtPlatformHeaders/QEGLNativeContext>
+#include <gst/gl/egl/gstgldisplay_egl.h>
+#endif
+
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+#include <gst/gl/wayland/gstgldisplay_wayland.h>
+#endif
+#if 0
+#if GST_GL_HAVE_WINDOW_VIV_FB
+#include <gst/gl/viv-fb/gstgldisplay_viv_fb.h>
+#endif
+
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+#include <windows.h>
+#include <QtPlatformHeaders/QWGLNativeContext>
+#endif
+#endif
+#include <gst/gl/gstglfuncs.h>
+
+#define GST_CAT_DEFAULT qml6_gl_utils_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+G_LOCK_DEFINE_STATIC (display_lock);
+static GWeakRef qt_display;
+static gboolean sink_retrieved = FALSE;
+
+GstGLDisplay *
+gst_qml6_get_gl_display (gboolean sink)
+{
+ GstGLDisplay *display = NULL;
+ QGuiApplication *app = static_cast<QGuiApplication *> (QCoreApplication::instance ());
+ static gsize _debug;
+
+ g_assert (app != NULL);
+
+ if (g_once_init_enter (&_debug)) {
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglutility", 0,
+ "Qt gl utility functions");
+ g_once_init_leave (&_debug, 1);
+ }
+
+ G_LOCK (display_lock);
+ /* XXX: this assumes that only one display will ever be created by Qt */
+ display = static_cast<GstGLDisplay *>(g_weak_ref_get (&qt_display));
+ if (display) {
+ if (sink_retrieved) {
+ GST_INFO ("returning previously created display");
+ G_UNLOCK (display_lock);
+ return display;
+ }
+ gst_clear_object (&display);
+ }
+ if (sink)
+ sink_retrieved = sink;
+
+ GST_INFO ("QGuiApplication::instance()->platformName() %s", app->platformName().toUtf8().data());
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+ if (QString::fromUtf8 ("xcb") == app->platformName()) {
+ auto x11_native = app->nativeInterface<QNativeInterface::QX11Application>();
+ if (x11_native) {
+ display = (GstGLDisplay *)
+ gst_gl_display_x11_new_with_display (x11_native->display());
+ }
+ }
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_WAYLAND)
+ if (QString::fromUtf8 ("wayland") == app->platformName()
+ || QString::fromUtf8 ("wayland-egl") == app->platformName()){
+ struct wl_display * wayland_display;
+ QPlatformNativeInterface *native =
+ QGuiApplication::platformNativeInterface();
+ wayland_display = (struct wl_display *)
+ native->nativeResourceForWindow("display", NULL);
+ display = (GstGLDisplay *)
+ gst_gl_display_wayland_new_with_display (wayland_display);
+ }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_WINDOW_ANDROID
+ if (QString::fromUtf8 ("android") == app->platformName()) {
+ EGLDisplay egl_display = (EGLDisplay) gst_gl_display_egl_get_from_native (GST_GL_DISPLAY_TYPE_ANY, 0);
+ display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+ }
+#elif GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_EGLFS)
+ if (QString::fromUtf8("eglfs") == app->platformName()) {
+#if GST_GL_HAVE_WINDOW_VIV_FB
+ /* FIXME: Could get the display directly from Qt like this
+ * QPlatformNativeInterface *native =
+ * QGuiApplication::platformNativeInterface();
+ * EGLDisplay egl_display = (EGLDisplay)
+ * native->nativeResourceForWindow("egldisplay", NULL);
+ *
+ * However we seem to have no way for getting the EGLNativeDisplayType, aka
+ * native_display, via public API. As such we have to assume that display 0
+ * is always used. Only way around that is parsing the index the same way as
+ * Qt does in QEGLDeviceIntegration::fbDeviceName(), so let's do that.
+ */
+ const gchar *fb_dev;
+ gint disp_idx = 0;
+
+ fb_dev = g_getenv ("QT_QPA_EGLFS_FB");
+ if (fb_dev) {
+ if (sscanf (fb_dev, "/dev/fb%d", &disp_idx) != 1)
+ disp_idx = 0;
+ }
+
+ display = (GstGLDisplay *) gst_gl_display_viv_fb_new (disp_idx);
+#elif defined(HAVE_QT_QPA_HEADER)
+ QPlatformNativeInterface *native =
+ QGuiApplication::platformNativeInterface();
+ EGLDisplay egl_display = (EGLDisplay)
+ native->nativeResourceForWindow("egldisplay", NULL);
+ if (egl_display != EGL_NO_DISPLAY)
+ display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+#else
+ EGLDisplay egl_display = (EGLDisplay) gst_gl_display_egl_get_from_native (GST_GL_DISPLAY_TYPE_ANY, 0);
+ display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+#endif
+ }
+#endif
+#if GST_GL_HAVE_WINDOW_COCOA && GST_GL_HAVE_PLATFORM_CGL && defined (HAVE_QT_MAC)
+ if (QString::fromUtf8 ("cocoa") == app->platformName())
+ display = (GstGLDisplay *) gst_gl_display_new ();
+#endif
+#if GST_GL_HAVE_WINDOW_EAGL && GST_GL_HAVE_PLATFORM_EAGL && defined (HAVE_QT_IOS)
+ if (QString::fromUtf8 ("ios") == app->platformName())
+ display = gst_gl_display_new ();
+#endif
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+ if (QString::fromUtf8 ("windows") == app->platformName())
+ display = gst_gl_display_new ();
+#endif
+
+ if (!display)
+ display = gst_gl_display_new ();
+
+ g_weak_ref_set (&qt_display, display);
+ G_UNLOCK (display_lock);
+
+ return display;
+}
+
+gboolean
+gst_qml6_get_gl_wrapcontext (GstGLDisplay * display,
+ GstGLContext **wrap_glcontext, GstGLContext **context)
+{
+ GstGLPlatform G_GNUC_UNUSED platform = (GstGLPlatform) 0;
+ GstGLAPI G_GNUC_UNUSED gl_api;
+ guintptr G_GNUC_UNUSED gl_handle;
+ GstGLContext *current;
+ GError *error = NULL;
+
+ g_return_val_if_fail (display != NULL && wrap_glcontext != NULL, FALSE);
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+ if (GST_IS_GL_DISPLAY_X11 (display)) {
+#if GST_GL_HAVE_PLATFORM_GLX
+ platform = GST_GL_PLATFORM_GLX;
+#elif GST_GL_HAVE_PLATFORM_EGL
+ platform = GST_GL_PLATFORM_EGL;
+#endif
+ }
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+ if (GST_IS_GL_DISPLAY_WAYLAND (display)) {
+ platform = GST_GL_PLATFORM_EGL;
+ }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_EGLFS)
+#if GST_GL_HAVE_WINDOW_VIV_FB
+ if (GST_IS_GL_DISPLAY_VIV_FB (display)) {
+#else
+ if (GST_IS_GL_DISPLAY_EGL (display)) {
+#endif
+ platform = GST_GL_PLATFORM_EGL;
+ }
+#endif
+ if (platform == 0) {
+#if GST_GL_HAVE_WINDOW_COCOA && GST_GL_HAVE_PLATFORM_CGL && defined (HAVE_QT_MAC)
+ platform = GST_GL_PLATFORM_CGL;
+#elif GST_GL_HAVE_WINDOW_EAGL && GST_GL_HAVE_PLATFORM_EAGL && defined (HAVE_QT_IOS)
+ platform = GST_GL_PLATFORM_EAGL;
+#elif GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+ platform = GST_GL_PLATFORM_WGL;
+#elif GST_GL_HAVE_WINDOW_ANDROID && GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_ANDROID)
+ platform = GST_GL_PLATFORM_EGL;
+#else
+ GST_ERROR ("Unknown platform");
+ return FALSE;
+#endif
+ }
+
+ gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
+ gl_handle = gst_gl_context_get_current_gl_context (platform);
+
+ /* see if we already have a current GL context in GStreamer for this thread */
+ current = gst_gl_context_get_current ();
+ if (current && current->display == display) {
+ /* just use current context we found */
+ *wrap_glcontext = static_cast<GstGLContext *> (gst_object_ref (current));
+ }
+ else {
+ if (gl_handle)
+ *wrap_glcontext =
+ gst_gl_context_new_wrapped (display, gl_handle,
+ platform, gl_api);
+
+ if (!*wrap_glcontext) {
+ GST_ERROR ("cannot wrap qt OpenGL context");
+ return FALSE;
+ }
+
+ gst_gl_context_activate(*wrap_glcontext, TRUE);
+ if (!gst_gl_context_fill_info (*wrap_glcontext, &error)) {
+ GST_ERROR ("failed to retrieve qt context info: %s", error->message);
+ gst_clear_object (wrap_glcontext);
+ return FALSE;
+ }
+
+ gst_gl_display_filter_gl_api (display, gst_gl_context_get_gl_api (*wrap_glcontext));
+ gst_gl_context_activate (*wrap_glcontext, FALSE);
+ }
+#if 0
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+ g_return_val_if_fail (context != NULL, FALSE);
+
+ G_STMT_START {
+ /* If there's no wglCreateContextAttribsARB() support, then we would fallback to
+ * wglShareLists() which will fail with ERROR_BUSY (0xaa) if either of the GL
+ * contexts are current in any other thread.
+ *
+ * The workaround here is to temporarily disable Qt's GL context while we
+ * set up our own.
+ *
+ * Sometimes wglCreateContextAttribsARB()
+ * exists, but isn't functional (some Intel drivers), so it's easiest to do this
+ * unconditionally.
+ */
+
+ /* retrieve Qt's GL device context as current device context */
+ HDC device = wglGetCurrentDC ();
+
+ *context = gst_gl_context_new (display);
+
+ wglMakeCurrent (NULL, NULL);
+ if (!gst_gl_context_create (*context, *wrap_glcontext, &error)) {
+ GST_ERROR ("failed to create shared GL context: %s", error->message);
+ gst_clear_object (wrap_glcontext);
+ gst_clear_object (context);
+ }
+ wglMakeCurrent (device, (HGLRC) gl_handle);
+
+ if (!*context)
+ return FALSE;
+
+ } G_STMT_END;
+#endif
+#endif
+ return TRUE;
+}
+#if 0
+QVariant
+qt_opengl_native_context_from_gst_gl_context (GstGLContext * context)
+{
+ guintptr handle;
+ GstGLPlatform platform;
+
+ handle = gst_gl_context_get_gl_context (context);
+ platform = gst_gl_context_get_gl_platform (context);
+
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+ if (platform == GST_GL_PLATFORM_GLX) {
+ GstGLDisplay *display = gst_gl_context_get_display (context);
+ GstGLWindow *window = gst_gl_context_get_window (context);
+ Display *xdisplay = (Display *) gst_gl_display_get_handle (display);
+ Window win = gst_gl_window_get_window_handle (window);
+ gst_object_unref (window);
+ gst_object_unref (display);
+ return QVariant::fromValue(QGLXNativeContext((GLXContext) handle, xdisplay, win));
+ }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && (defined (HAVE_QT_WAYLAND) || defined (HAVE_QT_EGLFS) || defined (HAVE_QT_ANDROID))
+ if (platform == GST_GL_PLATFORM_EGL) {
+ EGLDisplay egl_display = EGL_DEFAULT_DISPLAY;
+ GstGLDisplay *display = gst_gl_context_get_display (context);
+ GstGLDisplayEGL *display_egl = gst_gl_display_egl_from_gl_display (display);
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+ if (gst_gl_display_get_handle_type (display) == GST_GL_DISPLAY_TYPE_WAYLAND) {
+#if 1
+ g_warning ("Qt does not support wrapping native OpenGL contexts "
+ "on wayland. See https://bugreports.qt.io/browse/QTBUG-82528");
+ gst_object_unref (display_egl);
+ gst_object_unref (display);
+ return QVariant::fromValue(nullptr);
+#else
+ if (display_egl)
+ egl_display = (EGLDisplay) gst_gl_display_get_handle ((GstGLDisplay *) display_egl);
+#endif
+ }
+#endif
+ gst_object_unref (display_egl);
+ gst_object_unref (display);
+ return QVariant::fromValue(QEGLNativeContext((EGLContext) handle, egl_display));
+ }
+#endif
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+ if (platform == GST_GL_PLATFORM_WGL) {
+ GstGLWindow *window = gst_gl_context_get_window (context);
+ guintptr hwnd = gst_gl_window_get_window_handle (window);
+ gst_object_unref (window);
+ return QVariant::fromValue(QWGLNativeContext((HGLRC) handle, (HWND) hwnd));
+ }
+#endif
+ {
+ gchar *platform_s = gst_gl_platform_to_string (platform);
+ g_warning ("Unimplemented configuration! This means either:\n"
+ "1. The qmlgl plugin was built without support for your platform.\n"
+ "2. The necessary code to convert from a GstGLContext to Qt's "
+ "native context type for \'%s\' currently does not exist.",
+ platform_s);
+ g_free (platform_s);
+ }
+ return QVariant::fromValue(nullptr);
+}
+#endif
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2016 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef __QML6_GL_UTILS_H__
+#define __QML6_GL_UTILS_H__
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include <QVariant>
+#include <QRunnable>
+
+G_BEGIN_DECLS
+
+struct RenderJob : public QRunnable {
+ using Callable = std::function<void()>;
+
+ explicit RenderJob(Callable c) : _c(c) { }
+
+ void run() { _c(); }
+
+private:
+ Callable _c;
+};
+
+GstGLDisplay * gst_qml6_get_gl_display (gboolean sink);
+gboolean gst_qml6_get_gl_wrapcontext (GstGLDisplay * display,
+ GstGLContext **wrap_glcontext, GstGLContext **context);
+
+G_END_DECLS
+
+#if 0
+#if defined(__cplusplus)
+QVariant qt_opengl_native_context_from_gst_gl_context (GstGLContext * context);
+#endif
+#endif
+
+#endif /* __QML6_GL_UTILS_H__ */
--- /dev/null
+sources = [
+ 'gstplugin.cc',
+ 'gstqt6element.cc',
+ 'gstqsg6glnode.cc',
+ 'gstqt6glutility.cc',
+ 'gstqml6glsink.cc',
+ 'qt6glitem.cc',
+]
+
+moc_headers = [
+ 'qt6glitem.h',
+ 'gstqsg6glnode.h',
+]
+
+qt6qml_dep = dependency('', required: false)
+qt6_option = get_option('qt6')
+
+if qt6_option.disabled()
+ subdir_done()
+endif
+
+if not have_gstgl
+ if qt6_option.enabled()
+ error('qt6 qmlglsink plugin is enabled, but gstreamer-gl-1.0 was not found')
+ endif
+ subdir_done()
+endif
+
+if not add_languages('cpp', native: false, required: qt6_option)
+ subdir_done()
+endif
+
+qt6_mod = import('qt6')
+if not qt6_mod.has_tools()
+ if qt6_option.enabled()
+ error('qt6 qmlglsink plugin is enabled, but qt specific tools were not found')
+ endif
+ subdir_done()
+endif
+
+qt6qml_dep = dependency('qt6', modules : ['Core', 'Gui', 'Qml', 'Quick'],
+ required: qt6_option, static: host_machine.system() == 'ios')
+if not qt6qml_dep.found()
+ subdir_done()
+endif
+
+optional_deps = []
+qt_defines = []
+have_qpa_include = false
+have_qt_windowing = false
+
+# Look for the QPA platform native interface header
+qpa_header_path = join_paths(qt6qml_dep.version(), 'QtGui')
+qpa_header = join_paths(qpa_header_path, 'qpa/qplatformnativeinterface.h')
+if cxx.has_header(qpa_header, dependencies : qt6qml_dep)
+ qt_defines += '-DHAVE_QT_QPA_HEADER'
+ qt_defines += '-DQT_QPA_HEADER=' + '<@0@>'.format(qpa_header)
+ have_qpa_include = true
+ message('Found QtGui QPA header in ' + qpa_header_path)
+endif
+
+# Try to come up with all the platform/winsys combinations that will work
+
+if gst_gl_have_window_x11 and gst_gl_have_platform_glx
+ # FIXME: automagic
+ qt_defines += ['-DHAVE_QT_X11']
+ have_qt_windowing = true
+endif
+
+if gst_gl_have_platform_egl
+ # Embedded linux (e.g. i.MX6) with or without windowing support
+ qt_defines += ['-DHAVE_QT_EGLFS']
+ optional_deps += gstglegl_dep
+ have_qt_windowing = true
+ if have_qpa_include
+ # Wayland windowing
+ if gst_gl_have_window_wayland
+ # FIXME: automagic
+ qt6waylandextras = dependency('qt6', modules : ['WaylandClient'], required : false)
+ if qt6waylandextras.found()
+ optional_deps += [qt6waylandextras, gstglwayland_dep]
+ qt_defines += ['-DHAVE_QT_WAYLAND']
+ have_qt_windowing = true
+ endif
+ endif
+ # Android windowing
+# if gst_gl_have_window_android
+ # FIXME: automagic
+# qt5androidextras = dependency('qt5', modules : ['AndroidExtras'], required : false)
+ # for gl functions in QtGui/qopenglfunctions.h
+ # FIXME: automagic
+# glesv2_dep = cc.find_library('GLESv2', required : false)
+# if glesv2_dep.found() and qt5androidextras.found()
+# optional_deps += [qt5androidextras, glesv2_dep]
+# qt_defines += ['-DHAVE_QT_ANDROID']
+# have_qt_windowing = true
+ # Needed for C++11 support in Cerbero. People building with Android
+ # in some other way need to add the necessary bits themselves.
+# optional_deps += dependency('gnustl', required : false)
+# endif
+# endif
+ endif
+endif
+
+#if gst_gl_have_platform_wgl and gst_gl_have_window_win32
+ # for wglMakeCurrent()
+ # FIXME: automagic
+# opengl32_dep = cc.find_library('opengl32', required : false)
+# if opengl32_dep.found()
+# qt_defines += ['-DHAVE_QT_WIN32']
+# optional_deps += opengl32_dep
+# have_qt_windowing = true
+# endif
+#endif
+
+if gst_gl_have_window_cocoa and gst_gl_have_platform_cgl
+ # FIXME: automagic
+ if host_machine.system() == 'darwin'
+ qt_defines += ['-DHAVE_QT_MAC']
+ have_qt_windowing = true
+ endif
+endif
+
+if gst_gl_have_window_eagl and gst_gl_have_platform_eagl
+ if host_machine.system() == 'ios'
+ qt_defines += ['-DHAVE_QT_IOS']
+ have_qt_windowing = true
+ endif
+endif
+
+if have_qt_windowing
+ # Build it!
+ moc_files = qt6_mod.preprocess(moc_headers : moc_headers)
+ gstqml6gl = library('gstqml6', sources, moc_files,
+ cpp_args : gst_plugins_good_args + qt_defines,
+ link_args : noseh_link_args,
+ include_directories: [configinc, libsinc],
+ dependencies : [gst_dep, gstvideo_dep, gstgl_dep, gstglproto_dep, qt6qml_dep, optional_deps],
+ override_options : ['cpp_std=c++17'],
+ install: true,
+ install_dir : plugins_install_dir)
+ pkgconfig.generate(gstqml6gl, install_dir : plugins_pkgconfig_install_dir)
+ plugins += [gstqml6gl]
+endif
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include <gst/video/video.h>
+#include "qt6glitem.h"
+#include "gstqsg6glnode.h"
+#include "gstqt6glutility.h"
+
+#include <QtCore/QMutexLocker>
+#include <QtCore/QPointer>
+#include <QtGui/QGuiApplication>
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/QSGSimpleTextureNode>
+
+/**
+ * SECTION:Qt6GLVideoItem
+ * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers
+ *
+ * #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers.
+ */
+
+#define GST_CAT_DEFAULT qt_item_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEFAULT_FORCE_ASPECT_RATIO TRUE
+#define DEFAULT_PAR_N 0
+#define DEFAULT_PAR_D 1
+
+enum
+{
+ PROP_0,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+};
+
+struct _Qt6GLVideoItemPrivate
+{
+ GMutex lock;
+
+ /* properties */
+ gboolean force_aspect_ratio;
+ gint par_n, par_d;
+
+ GWeakRef sink;
+
+ gint display_width;
+ gint display_height;
+
+ GstBuffer *buffer;
+ GstCaps *new_caps;
+ GstCaps *caps;
+ GstVideoInfo new_v_info;
+ GstVideoInfo v_info;
+
+ gboolean initted;
+ GstGLDisplay *display;
+ QOpenGLContext *qt_context;
+ GstGLContext *other_context;
+ GstGLContext *context;
+
+ /* buffers with textures that were bound by QML */
+ GQueue bound_buffers;
+ /* buffers that were previously bound but in the meantime a new one was
+ * bound so this one is most likely not used anymore
+ * FIXME: Ideally we would use fences for this but there seems to be no
+ * way to reliably "try wait" on a fence */
+ GQueue potentially_unbound_buffers;
+
+ GstQSG6OpenGLNode *m_node;
+};
+
+Qt6GLVideoItem::Qt6GLVideoItem()
+{
+ static gsize _debug;
+
+ if (g_once_init_enter (&_debug)) {
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglwidget", 0, "Qt GL Widget");
+ g_once_init_leave (&_debug, 1);
+ }
+
+ this->setFlag (QQuickItem::ItemHasContents, true);
+
+ this->priv = g_new0 (Qt6GLVideoItemPrivate, 1);
+
+ this->priv->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
+ this->priv->par_n = DEFAULT_PAR_N;
+ this->priv->par_d = DEFAULT_PAR_D;
+
+ this->priv->initted = FALSE;
+
+ g_mutex_init (&this->priv->lock);
+
+ g_weak_ref_init (&priv->sink, NULL);
+
+ this->priv->display = gst_qml6_get_gl_display(TRUE);
+
+ connect(this, SIGNAL(windowChanged(QQuickWindow*)), this,
+ SLOT(handleWindowChanged(QQuickWindow*)));
+
+ this->proxy = QSharedPointer<Qt6GLVideoItemInterface>(new Qt6GLVideoItemInterface(this));
+
+ setFlag(ItemHasContents, true);
+ setAcceptedMouseButtons(Qt::AllButtons);
+ setAcceptHoverEvents(true);
+
+ setAcceptTouchEvents(true);
+
+ GST_DEBUG ("%p init Qt6 Video Item", this);
+}
+
+Qt6GLVideoItem::~Qt6GLVideoItem()
+{
+ GstBuffer *tmp_buffer;
+
+ /* Before destroying the priv info, make sure
+ * no qmlglsink's will call in again, and that
+ * any ongoing calls are done by invalidating the proxy
+ * pointer */
+ GST_INFO ("%p Destroying QtGLVideoItem and invalidating the proxy %p", this, proxy.data());
+ proxy->invalidateRef();
+ proxy.clear();
+
+ g_mutex_clear (&this->priv->lock);
+ if (this->priv->context)
+ gst_object_unref(this->priv->context);
+ if (this->priv->other_context)
+ gst_object_unref(this->priv->other_context);
+ if (this->priv->display)
+ gst_object_unref(this->priv->display);
+
+ while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) {
+ GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+ gst_buffer_unref (tmp_buffer);
+ }
+ while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) {
+ GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+ gst_buffer_unref (tmp_buffer);
+ }
+
+ gst_buffer_replace (&this->priv->buffer, NULL);
+
+ gst_caps_replace (&this->priv->caps, NULL);
+ gst_caps_replace (&this->priv->new_caps, NULL);
+
+ g_weak_ref_clear (&this->priv->sink);
+
+ g_free (this->priv);
+ this->priv = NULL;
+}
+
+void
+Qt6GLVideoItem::setDAR(gint num, gint den)
+{
+ this->priv->par_n = num;
+ this->priv->par_d = den;
+}
+
+void
+Qt6GLVideoItem::getDAR(gint * num, gint * den)
+{
+ if (num)
+ *num = this->priv->par_n;
+ if (den)
+ *den = this->priv->par_d;
+}
+
+void
+Qt6GLVideoItem::setForceAspectRatio(bool force_aspect_ratio)
+{
+ this->priv->force_aspect_ratio = !!force_aspect_ratio;
+
+ emit forceAspectRatioChanged(force_aspect_ratio);
+}
+
+bool
+Qt6GLVideoItem::getForceAspectRatio()
+{
+ return this->priv->force_aspect_ratio;
+}
+
+bool
+Qt6GLVideoItem::itemInitialized()
+{
+ return this->priv->initted;
+}
+
+static gboolean
+_calculate_par (Qt6GLVideoItem * widget, GstVideoInfo * info)
+{
+ gboolean ok;
+ gint width, height;
+ gint par_n, par_d;
+ gint display_par_n, display_par_d;
+ guint display_ratio_num, display_ratio_den;
+
+ width = GST_VIDEO_INFO_WIDTH (info);
+ height = GST_VIDEO_INFO_HEIGHT (info);
+
+ par_n = GST_VIDEO_INFO_PAR_N (info);
+ par_d = GST_VIDEO_INFO_PAR_D (info);
+
+ if (!par_n)
+ par_n = 1;
+
+ /* get display's PAR */
+ if (widget->priv->par_n != 0 && widget->priv->par_d != 0) {
+ display_par_n = widget->priv->par_n;
+ display_par_d = widget->priv->par_d;
+ } else {
+ display_par_n = 1;
+ display_par_d = 1;
+ }
+
+ ok = gst_video_calculate_display_ratio (&display_ratio_num,
+ &display_ratio_den, width, height, par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (!ok)
+ return FALSE;
+
+ widget->setImplicitWidth (width);
+ widget->setImplicitHeight (height);
+
+ GST_LOG ("%p PAR: %u/%u DAR:%u/%u", widget, par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (height % display_ratio_den == 0) {
+ GST_DEBUG ("%p keeping video height", widget);
+ widget->priv->display_width = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ widget->priv->display_height = height;
+ } else if (width % display_ratio_num == 0) {
+ GST_DEBUG ("%p keeping video width", widget);
+ widget->priv->display_width = width;
+ widget->priv->display_height = (guint)
+ gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+ } else {
+ GST_DEBUG ("%p approximating while keeping video height", widget);
+ widget->priv->display_width = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ widget->priv->display_height = height;
+ }
+ GST_DEBUG ("%p scaling to %dx%d", widget, widget->priv->display_width,
+ widget->priv->display_height);
+
+ return TRUE;
+}
+
+QSGNode *
+Qt6GLVideoItem::updatePaintNode(QSGNode * oldNode,
+ UpdatePaintNodeData * updatePaintNodeData)
+{
+ GstBuffer *old_buffer;
+
+ if (!this->priv->initted)
+ return oldNode;
+
+ GstQSG6OpenGLNode *texNode = static_cast<GstQSG6OpenGLNode *> (oldNode);
+ GstVideoRectangle src, dst, result;
+
+ g_mutex_lock (&this->priv->lock);
+
+ GST_TRACE ("%p updatePaintNode", this);
+
+ if (gst_gl_context_get_current() == NULL)
+ gst_gl_context_activate (this->priv->other_context, TRUE);
+
+ if (!texNode) {
+ texNode = new GstQSG6OpenGLNode (this);
+ this->priv->m_node = texNode;
+ }
+
+ if ((old_buffer = texNode->getBuffer())) {
+ if (old_buffer == this->priv->buffer) {
+ /* same buffer */
+ gst_buffer_unref (old_buffer);
+ } else {
+ GstBuffer *tmp_buffer;
+
+ GST_TRACE ("old buffer %p was bound, queueing up for later", old_buffer);
+ /* Unref all buffers that were previously not bound anymore. At least
+ * one more buffer was bound in the meantime so this one is most likely
+ * not in use anymore. */
+ while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) {
+ GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+ gst_buffer_unref (tmp_buffer);
+ }
+
+ /* Move previous bound buffers to the next queue. We now know that
+ * another buffer was bound in the meantime and will free them on
+ * the next iteration above. */
+ while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) {
+ GST_TRACE ("old buffer %p is potentially unbound now", tmp_buffer);
+ g_queue_push_tail (&this->priv->potentially_unbound_buffers, tmp_buffer);
+ }
+ g_queue_push_tail (&this->priv->bound_buffers, old_buffer);
+ }
+ old_buffer = NULL;
+ }
+
+ texNode->setCaps (this->priv->caps);
+ texNode->setBuffer (this->priv->buffer);
+
+ if (this->priv->force_aspect_ratio && this->priv->caps) {
+ src.w = this->priv->display_width;
+ src.h = this->priv->display_height;
+
+ dst.x = boundingRect().x();
+ dst.y = boundingRect().y();
+ dst.w = boundingRect().width();
+ dst.h = boundingRect().height();
+
+ gst_video_sink_center_rect (src, dst, &result, TRUE);
+ } else {
+ result.x = boundingRect().x();
+ result.y = boundingRect().y();
+ result.w = boundingRect().width();
+ result.h = boundingRect().height();
+ }
+
+ texNode->setRect (QRectF (result.x, result.y, result.w, result.h));
+
+ g_mutex_unlock (&this->priv->lock);
+
+ return texNode;
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+void
+Qt6GLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result)
+{
+ if (this->priv->force_aspect_ratio) {
+ GstVideoRectangle src, dst;
+
+ src.x = 0;
+ src.y = 0;
+ src.w = this->priv->display_width;
+ src.h = this->priv->display_height;
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = width();
+ dst.h = height();
+
+ gst_video_sink_center_rect (src, dst, result, TRUE);
+ } else {
+ result->x = 0;
+ result->y = 0;
+ result->w = width();
+ result->h = height();
+ }
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+QPointF
+Qt6GLVideoItem::mapPointToStreamSize(QPointF pos)
+{
+ gdouble stream_width, stream_height;
+ GstVideoRectangle result;
+ double stream_x, stream_y;
+ double x, y;
+
+ fitStreamToAllocatedSize(&result);
+
+ stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info);
+ stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info);
+ x = pos.x();
+ y = pos.y();
+
+ /* from display coordinates to stream coordinates */
+ if (result.w > 0)
+ stream_x = (x - result.x) / result.w * stream_width;
+ else
+ stream_x = 0.;
+
+ /* clip to stream size */
+ stream_x = CLAMP(stream_x, 0., stream_width);
+
+ /* same for y-axis */
+ if (result.h > 0)
+ stream_y = (y - result.y) / result.h * stream_height;
+ else
+ stream_y = 0.;
+
+ stream_y = CLAMP(stream_y, 0., stream_height);
+ GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y);
+
+ return QPointF(stream_x, stream_y);
+}
+
+static GstNavigationModifierType
+translateModifiers(Qt::KeyboardModifiers modifiers)
+{
+ return (GstNavigationModifierType)(
+ ((modifiers & Qt::KeyboardModifier::ShiftModifier) ? GST_NAVIGATION_MODIFIER_SHIFT_MASK : 0) |
+ ((modifiers & Qt::KeyboardModifier::ControlModifier) ? GST_NAVIGATION_MODIFIER_CONTROL_MASK : 0) |
+ ((modifiers & Qt::KeyboardModifier::AltModifier) ? GST_NAVIGATION_MODIFIER_ALT_MASK : 0) |
+ ((modifiers & Qt::KeyboardModifier::MetaModifier) ? GST_NAVIGATION_MODIFIER_META_MASK : 0));
+}
+
+static GstNavigationModifierType
+translateMouseButtons(Qt::MouseButtons buttons)
+{
+ return (GstNavigationModifierType)(
+ ((buttons & Qt::LeftButton) ? GST_NAVIGATION_MODIFIER_BUTTON1_MASK : 0) |
+ ((buttons & Qt::RightButton) ? GST_NAVIGATION_MODIFIER_BUTTON2_MASK : 0) |
+ ((buttons & Qt::MiddleButton) ? GST_NAVIGATION_MODIFIER_BUTTON3_MASK : 0) |
+ ((buttons & Qt::BackButton) ? GST_NAVIGATION_MODIFIER_BUTTON4_MASK : 0) |
+ ((buttons & Qt::ForwardButton) ? GST_NAVIGATION_MODIFIER_BUTTON5_MASK : 0));
+}
+
+void
+Qt6GLVideoItem::wheelEvent(QWheelEvent * event)
+{
+ g_mutex_lock (&this->priv->lock);
+ QPoint delta = event->angleDelta();
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+ auto position = event->position();
+ gst_navigation_send_event_simple (GST_NAVIGATION (element),
+ gst_navigation_event_new_mouse_scroll (position.x(), position.y(),
+ delta.x(), delta.y(),
+ (GstNavigationModifierType) (
+ translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))));
+ g_object_unref (element);
+ }
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::hoverEnterEvent(QHoverEvent *)
+{
+ mouseHovering = true;
+}
+
+void
+Qt6GLVideoItem::hoverLeaveEvent(QHoverEvent *)
+{
+ mouseHovering = false;
+}
+
+void
+Qt6GLVideoItem::hoverMoveEvent(QHoverEvent * event)
+{
+ if (!mouseHovering)
+ return;
+
+ g_mutex_lock (&this->priv->lock);
+
+ /* can't do anything when we don't have input format */
+ if (!this->priv->caps) {
+ g_mutex_unlock (&this->priv->lock);
+ return;
+ }
+
+ if (event->position() != event->oldPos()) {
+ QPointF pos = mapPointToStreamSize(event->position());
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+ gst_navigation_send_event_simple (GST_NAVIGATION (element),
+ gst_navigation_event_new_mouse_move (pos.x(), pos.y(),
+ translateModifiers(event->modifiers())));
+ g_object_unref (element);
+ }
+ }
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::touchEvent(QTouchEvent * event)
+{
+ g_mutex_lock (&this->priv->lock);
+
+ /* can't do anything when we don't have input format */
+ if (!this->priv->caps) {
+ g_mutex_unlock (&this->priv->lock);
+ return;
+ }
+
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+ if (element == NULL)
+ return;
+
+ if (event->type() == QEvent::TouchCancel) {
+ gst_navigation_send_event_simple (GST_NAVIGATION (element),
+ gst_navigation_event_new_touch_cancel (translateModifiers(event->modifiers())));
+ } else {
+ const QList<QTouchEvent::TouchPoint> points = event->points();
+ gboolean sent_event = FALSE;
+
+ for (int i = 0; i < points.count(); i++) {
+ GstEvent *nav_event;
+ QPointF pos = mapPointToStreamSize(points[i].position());
+
+ switch (points[i].state()) {
+ case QEventPoint::Pressed:
+ nav_event = gst_navigation_event_new_touch_down ((guint) points[i].id(),
+ pos.x(), pos.y(), (gdouble) points[i].pressure(), translateModifiers(event->modifiers()));
+ break;
+ case QEventPoint::Updated:
+ nav_event = gst_navigation_event_new_touch_motion ((guint) points[i].id(),
+ pos.x(), pos.y(), (gdouble) points[i].pressure(), translateModifiers(event->modifiers()));
+ break;
+ case QEventPoint::Released:
+ nav_event = gst_navigation_event_new_touch_up ((guint) points[i].id(),
+ pos.x(), pos.y(), translateModifiers(event->modifiers()));
+ break;
+ /* Don't send an event if the point did not change */
+ default:
+ nav_event = NULL;
+ break;
+ }
+
+ if (nav_event) {
+ gst_navigation_send_event_simple (GST_NAVIGATION (element), nav_event);
+ sent_event = TRUE;
+ }
+ }
+
+ /* Group simultaneos touch events with a frame event */
+ if (sent_event) {
+ gst_navigation_send_event_simple (GST_NAVIGATION (element),
+ gst_navigation_event_new_touch_frame (translateModifiers(event->modifiers())));
+ }
+ }
+
+ g_object_unref (element);
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::sendMouseEvent(QMouseEvent * event, gboolean is_press)
+{
+ quint32 button = 0;
+
+ switch (event->button()) {
+ case Qt::LeftButton:
+ button = 1;
+ break;
+ case Qt::RightButton:
+ button = 2;
+ break;
+ default:
+ break;
+ }
+
+ mousePressedButton = button;
+
+ g_mutex_lock (&this->priv->lock);
+
+ /* can't do anything when we don't have input format */
+ if (!this->priv->caps) {
+ g_mutex_unlock (&this->priv->lock);
+ return;
+ }
+
+ QPointF pos = mapPointToStreamSize(event->pos());
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+ gst_navigation_send_event_simple (GST_NAVIGATION (element),
+ (is_press) ? gst_navigation_event_new_mouse_button_press (button,
+ pos.x(), pos.y(),
+ (GstNavigationModifierType) (
+ translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))) :
+ gst_navigation_event_new_mouse_button_release (button, pos.x(),
+ pos.y(),
+ (GstNavigationModifierType) (
+ translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))));
+ g_object_unref (element);
+ }
+
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::mousePressEvent(QMouseEvent * event)
+{
+ forceActiveFocus();
+ sendMouseEvent(event, TRUE);
+}
+
+void
+Qt6GLVideoItem::mouseReleaseEvent(QMouseEvent * event)
+{
+ sendMouseEvent(event, FALSE);
+}
+
+void
+Qt6GLVideoItemInterface::setSink (GstElement * sink)
+{
+ QMutexLocker locker(&lock);
+ if (qt_item == NULL)
+ return;
+
+ g_mutex_lock (&qt_item->priv->lock);
+ g_weak_ref_set (&qt_item->priv->sink, sink);
+ g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
+Qt6GLVideoItemInterface::setBuffer (GstBuffer * buffer)
+{
+ QMutexLocker locker(&lock);
+
+ if (qt_item == NULL) {
+ GST_WARNING ("%p actual item is NULL. setBuffer call ignored", this);
+ return;
+ }
+
+ if (!qt_item->priv->caps && !qt_item->priv->new_caps) {
+ GST_WARNING ("%p Got buffer on unnegotiated QtGLVideoItem. Dropping", this);
+ return;
+ }
+
+ g_mutex_lock (&qt_item->priv->lock);
+
+ if (qt_item->priv->new_caps) {
+ GST_DEBUG ("%p caps change from %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT,
+ this, qt_item->priv->caps, qt_item->priv->new_caps);
+ gst_caps_take (&qt_item->priv->caps, qt_item->priv->new_caps);
+ qt_item->priv->new_caps = NULL;
+ qt_item->priv->v_info = qt_item->priv->new_v_info;
+
+ if (!_calculate_par (qt_item, &qt_item->priv->v_info)) {
+ g_mutex_unlock (&qt_item->priv->lock);
+ return;
+ }
+ }
+
+ gst_buffer_replace (&qt_item->priv->buffer, buffer);
+
+ QMetaObject::invokeMethod(qt_item, "update", Qt::QueuedConnection);
+
+ g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
+Qt6GLVideoItem::onSceneGraphInitialized ()
+{
+ QSGRendererInterface *renderer;
+ QOpenGLContext *gl_context;
+
+ if (this->window() == NULL)
+ return;
+
+ renderer = this->window()->rendererInterface();
+ if (!renderer)
+ return;
+
+ if (renderer->graphicsApi() != QSGRendererInterface::GraphicsApi::OpenGL) {
+ GST_WARNING ("%p scene graph initialized with a non-OpenGL renderer interface", this);
+ return;
+ }
+
+ gl_context =
+ static_cast<QOpenGLContext *> (
+ renderer->getResource(
+ this->window(),
+ QSGRendererInterface::Resource::OpenGLContextResource));
+
+ GST_DEBUG ("%p scene graph initialization with Qt GL context %p", this,
+ gl_context);
+
+ if (this->priv->qt_context == gl_context)
+ return;
+
+ this->priv->qt_context = gl_context;
+ if (this->priv->qt_context == NULL) {
+ g_assert_not_reached ();
+ return;
+ }
+
+ this->priv->initted = gst_qml6_get_gl_wrapcontext (this->priv->display,
+ &this->priv->other_context, &this->priv->context);
+
+ GST_DEBUG ("%p created wrapped GL context %" GST_PTR_FORMAT, this,
+ this->priv->other_context);
+
+ emit itemInitializedChanged();
+}
+
+void
+Qt6GLVideoItem::onSceneGraphInvalidated ()
+{
+ this->priv->m_node = nullptr;
+ GST_FIXME ("%p scene graph invalidated", this);
+}
+
+/**
+ * Retrieve and populate the GL context information from the current
+ * OpenGL context.
+ */
+gboolean
+Qt6GLVideoItemInterface::initWinSys ()
+{
+ QMutexLocker locker(&lock);
+
+ GError *error = NULL;
+
+ if (qt_item == NULL)
+ return FALSE;
+
+ g_mutex_lock (&qt_item->priv->lock);
+
+ if (qt_item->priv->display && qt_item->priv->qt_context
+ && qt_item->priv->other_context && qt_item->priv->context) {
+ /* already have the necessary state */
+ g_mutex_unlock (&qt_item->priv->lock);
+ return TRUE;
+ }
+
+ if (!GST_IS_GL_DISPLAY (qt_item->priv->display)) {
+ GST_ERROR ("%p failed to retrieve display connection %" GST_PTR_FORMAT,
+ qt_item, qt_item->priv->display);
+ g_mutex_unlock (&qt_item->priv->lock);
+ return FALSE;
+ }
+
+ if (!GST_IS_GL_CONTEXT (qt_item->priv->other_context)) {
+ GST_ERROR ("%p failed to retrieve wrapped context %" GST_PTR_FORMAT, qt_item,
+ qt_item->priv->other_context);
+ g_mutex_unlock (&qt_item->priv->lock);
+ return FALSE;
+ }
+
+ qt_item->priv->context = gst_gl_context_new (qt_item->priv->display);
+
+ if (!qt_item->priv->context) {
+ g_mutex_unlock (&qt_item->priv->lock);
+ return FALSE;
+ }
+
+ if (!gst_gl_context_create (qt_item->priv->context, qt_item->priv->other_context,
+ &error)) {
+ GST_ERROR ("%s", error->message);
+ g_mutex_unlock (&qt_item->priv->lock);
+ return FALSE;
+ }
+
+ g_mutex_unlock (&qt_item->priv->lock);
+ return TRUE;
+}
+
+void
+Qt6GLVideoItem::handleWindowChanged (QQuickWindow * win)
+{
+ if (win) {
+ if (win->isSceneGraphInitialized ())
+ win->scheduleRenderJob (new RenderJob (std::
+ bind (&Qt6GLVideoItem::onSceneGraphInitialized, this)),
+ QQuickWindow::BeforeSynchronizingStage);
+ else
+ connect (win, SIGNAL (sceneGraphInitialized ()), this,
+ SLOT (onSceneGraphInitialized ()), Qt::DirectConnection);
+
+ connect (win, SIGNAL (sceneGraphInvalidated ()), this,
+ SLOT (onSceneGraphInvalidated ()), Qt::DirectConnection);
+ } else {
+ this->priv->qt_context = NULL;
+ this->priv->initted = FALSE;
+ }
+ this->priv->m_node = nullptr;
+}
+
+void
+Qt6GLVideoItem::releaseResources()
+{
+ this->priv->m_node = nullptr;
+}
+
+gboolean
+Qt6GLVideoItemInterface::setCaps (GstCaps * caps)
+{
+ QMutexLocker locker(&lock);
+ GstVideoInfo v_info;
+
+ g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
+ g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
+
+ if (qt_item == NULL)
+ return FALSE;
+
+ if (qt_item->priv->caps && gst_caps_is_equal_fixed (qt_item->priv->caps, caps))
+ return TRUE;
+
+ if (!gst_video_info_from_caps (&v_info, caps))
+ return FALSE;
+
+ g_mutex_lock (&qt_item->priv->lock);
+
+ GST_DEBUG ("%p set caps %" GST_PTR_FORMAT, qt_item, caps);
+
+ gst_caps_replace (&qt_item->priv->new_caps, caps);
+
+ qt_item->priv->new_v_info = v_info;
+
+ g_mutex_unlock (&qt_item->priv->lock);
+
+ return TRUE;
+}
+
+GstGLContext *
+Qt6GLVideoItemInterface::getQtContext ()
+{
+ QMutexLocker locker(&lock);
+
+ if (!qt_item || !qt_item->priv->other_context)
+ return NULL;
+
+ return (GstGLContext *) gst_object_ref (qt_item->priv->other_context);
+}
+
+GstGLContext *
+Qt6GLVideoItemInterface::getContext ()
+{
+ QMutexLocker locker(&lock);
+
+ if (!qt_item || !qt_item->priv->context)
+ return NULL;
+
+ return (GstGLContext *) gst_object_ref (qt_item->priv->context);
+}
+
+GstGLDisplay *
+Qt6GLVideoItemInterface::getDisplay()
+{
+ QMutexLocker locker(&lock);
+
+ if (!qt_item || !qt_item->priv->display)
+ return NULL;
+
+ return (GstGLDisplay *) gst_object_ref (qt_item->priv->display);
+}
+
+void
+Qt6GLVideoItemInterface::setDAR(gint num, gint den)
+{
+ QMutexLocker locker(&lock);
+ if (!qt_item)
+ return;
+ qt_item->setDAR(num, den);
+}
+
+void
+Qt6GLVideoItemInterface::getDAR(gint * num, gint * den)
+{
+ QMutexLocker locker(&lock);
+ if (!qt_item)
+ return;
+ qt_item->getDAR (num, den);
+}
+
+void
+Qt6GLVideoItemInterface::setForceAspectRatio(bool force_aspect_ratio)
+{
+ QMutexLocker locker(&lock);
+ if (!qt_item)
+ return;
+ qt_item->setForceAspectRatio(force_aspect_ratio);
+}
+
+bool
+Qt6GLVideoItemInterface::getForceAspectRatio()
+{
+ QMutexLocker locker(&lock);
+ if (!qt_item)
+ return FALSE;
+ return qt_item->getForceAspectRatio();
+}
+
+void
+Qt6GLVideoItemInterface::invalidateRef()
+{
+ QMutexLocker locker(&lock);
+ qt_item = NULL;
+}
+
--- /dev/null
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
+ */
+
+#ifndef __QT6_GL_ITEM_H__
+#define __QT6_GL_ITEM_H__
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include "gstqt6gl.h"
+#include <QtCore/QMutex>
+#include <QtQuick/QQuickItem>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
+
+typedef struct _Qt6GLVideoItemPrivate Qt6GLVideoItemPrivate;
+
+class Qt6GLVideoItem;
+
+class Qt6GLVideoItemInterface : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+public:
+ Qt6GLVideoItemInterface (Qt6GLVideoItem *w) : qt_item (w), lock() {};
+
+ void invalidateRef();
+
+ void setSink (GstElement * sink);
+ void setBuffer (GstBuffer * buffer);
+ gboolean setCaps (GstCaps *caps);
+ gboolean initWinSys ();
+ GstGLContext *getQtContext();
+ GstGLContext *getContext();
+ GstGLDisplay *getDisplay();
+ Qt6GLVideoItem *videoItem () { return qt_item; };
+
+ void setDAR(gint, gint);
+ void getDAR(gint *, gint *);
+ void setForceAspectRatio(bool);
+ bool getForceAspectRatio();
+private:
+ Qt6GLVideoItem *qt_item;
+ QMutex lock;
+};
+
+class Qt6GLVideoItem : public QQuickItem, protected QOpenGLFunctions
+{
+ Q_OBJECT
+ QML_ELEMENT
+
+ Q_PROPERTY(bool itemInitialized
+ READ itemInitialized
+ NOTIFY itemInitializedChanged)
+ Q_PROPERTY(bool forceAspectRatio
+ READ getForceAspectRatio
+ WRITE setForceAspectRatio
+ NOTIFY forceAspectRatioChanged)
+
+public:
+ Qt6GLVideoItem();
+ ~Qt6GLVideoItem();
+
+ void setDAR(gint, gint);
+ void getDAR(gint *, gint *);
+ void setForceAspectRatio(bool);
+ bool getForceAspectRatio();
+ bool itemInitialized();
+
+ QSharedPointer<Qt6GLVideoItemInterface> getInterface() { return proxy; };
+ /* private for C interface ... */
+ Qt6GLVideoItemPrivate *priv;
+
+Q_SIGNALS:
+ void itemInitializedChanged();
+ void forceAspectRatioChanged(bool);
+
+private Q_SLOTS:
+ void handleWindowChanged(QQuickWindow * win);
+ void onSceneGraphInitialized();
+ void onSceneGraphInvalidated();
+
+protected:
+ QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData) override;
+ void releaseResources() override;
+ void wheelEvent(QWheelEvent *) override;
+ void hoverEnterEvent(QHoverEvent *) override;
+ void hoverLeaveEvent (QHoverEvent *) override;
+ void hoverMoveEvent (QHoverEvent *) override;
+ void mousePressEvent(QMouseEvent*) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+ void touchEvent(QTouchEvent*) override;
+
+private:
+
+ void setViewportSize(const QSize &size);
+ void shareContext();
+
+ void fitStreamToAllocatedSize(GstVideoRectangle * result);
+ QPointF mapPointToStreamSize(QPointF);
+
+ void sendMouseEvent(QMouseEvent * event, gboolean is_press);
+
+ quint32 mousePressedButton;
+ bool mouseHovering;
+
+ QSharedPointer<Qt6GLVideoItemInterface> proxy;
+};
+
+#endif /* __QT_GL_ITEM_H__ */
option('png', type : 'feature', value : 'auto', description : 'PNG image codec plugin')
option('pulse', type : 'feature', value : 'auto', description : 'Pulseaudio audio source/sink plugin')
option('qt5', type : 'feature', value : 'auto', yield : true, description : 'Qt5 QML video sink plugin')
+option('qt6', type : 'feature', value : 'auto', yield : true, description : 'Qt6 QML video sink plugin')
option('shout2', type : 'feature', value : 'auto', description : 'Shout-casting network sink plugin based on libshout2')
option('soup', type : 'feature', value : 'auto', description : 'libsoup HTTP client source/sink plugin')
option('speex', type : 'feature', value : 'auto', description : 'Speex audio codec plugin')
subdir('cairo')
subdir('level')
subdir('qt')
+subdir('qt6')
if is_variable('gstrpicamsrc')
subdir('rpicamsrc')
--- /dev/null
+if qt6_option.disabled()
+ subdir_done()
+endif
+
+# We already did all the checks when building the qt6 plugin
+if not qt6qml_dep.found()
+ subdir_done()
+endif
+
+qt6qml_example_deps = dependency('qt6', modules : ['Core', 'Gui', 'Widgets', 'Qml', 'Quick'],
+ required: get_option('examples'))
+
+if not qt6qml_example_deps.found()
+ subdir_done()
+endif
+
+subdir('qmlsink')
--- /dev/null
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QRunnable>
+#include <gst/gst.h>
+
+class SetPlaying : public QRunnable
+{
+public:
+ SetPlaying(GstElement *);
+ ~SetPlaying();
+
+ void run ();
+
+private:
+ GstElement * pipeline_;
+};
+
+SetPlaying::SetPlaying (GstElement * pipeline)
+{
+ this->pipeline_ = pipeline ? static_cast<GstElement *> (gst_object_ref (pipeline)) : NULL;
+}
+
+SetPlaying::~SetPlaying ()
+{
+ if (this->pipeline_)
+ gst_object_unref (this->pipeline_);
+}
+
+void
+SetPlaying::run ()
+{
+ if (this->pipeline_)
+ gst_element_set_state (this->pipeline_, GST_STATE_PLAYING);
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ gst_init (&argc, &argv);
+
+ {
+ QGuiApplication app(argc, argv);
+
+ QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
+
+ GstElement *pipeline = gst_pipeline_new (NULL);
+ GstElement *src = gst_element_factory_make ("videotestsrc", NULL);
+ GstElement *glupload = gst_element_factory_make ("glupload", NULL);
+ /* the plugin must be loaded before loading the qml file to register the
+ * GstGLVideoItem qml item */
+ GstElement *sink = gst_element_factory_make ("qml6glsink", NULL);
+
+ g_assert (src && glupload && sink);
+
+ gst_bin_add_many (GST_BIN (pipeline), src, glupload, sink, NULL);
+ gst_element_link_many (src, glupload, sink, NULL);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ QQuickItem *videoItem;
+ QQuickWindow *rootObject;
+
+ /* find and set the videoItem on the sink */
+ rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());
+ videoItem = rootObject->findChild<QQuickItem *> ("videoItem");
+ g_assert (videoItem);
+ g_object_set(sink, "widget", videoItem, NULL);
+
+ rootObject->scheduleRenderJob (new SetPlaying (pipeline),
+ QQuickWindow::BeforeSynchronizingStage);
+
+ ret = app.exec();
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_object_unref (pipeline);
+ }
+
+ gst_deinit ();
+
+ return ret;
+}
--- /dev/null
+import QtQuick 6.0
+import QtQuick.Controls 6.0
+import QtQuick.Dialogs 6.0
+import QtQuick.Window 6.0
+
+import org.freedesktop.gstreamer.Qt6GLVideoItem 1.0
+
+ApplicationWindow {
+ id: window
+ visible: true
+ width: 640
+ height: 480
+ x: 30
+ y: 30
+ color: "black"
+
+ Item {
+ anchors.fill: parent
+
+ GstGLQt6VideoItem {
+ id: video
+ objectName: "videoItem"
+ anchors.centerIn: parent
+ width: parent.width
+ height: parent.height
+ }
+
+ Rectangle {
+ color: Qt.rgba(1, 1, 1, 0.7)
+ border.width: 1
+ border.color: "white"
+ anchors.bottom: video.bottom
+ anchors.bottomMargin: 15
+ anchors.horizontalCenter: parent.horizontalCenter
+ width : parent.width - 30
+ height: parent.height - 30
+ radius: 8
+
+ MouseArea {
+ id: mousearea
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: {
+ parent.opacity = 1.0
+ hidetimer.start()
+ }
+ }
+
+ Timer {
+ id: hidetimer
+ interval: 5000
+ onTriggered: {
+ parent.opacity = 0.0
+ stop()
+ }
+ }
+ }
+ }
+}
--- /dev/null
+sources = [
+ 'main.cpp',
+]
+
+
+qt_preprocessed = qt6_mod.preprocess(qresources : 'qmlsink.qrc')
+executable('qml6sink', sources, qt_preprocessed,
+ dependencies : [gst_dep, qt6qml_example_deps],
+ override_options : ['cpp_std=c++17'],
+ c_args : gst_plugins_good_args,
+ include_directories : [configinc],
+ install: false)
--- /dev/null
+TEMPLATE = app
+
+QT += qml quick widgets
+
+QT_CONFIG -= no-pkg-config
+CONFIG += link_pkgconfig debug
+PKGCONFIG = \
+ gstreamer-1.0 \
+ gstreamer-video-1.0
+
+DEFINES += GST_USE_UNSTABLE_API
+
+INCLUDEPATH += ../lib
+
+SOURCES += main.cpp
+
+RESOURCES += qmlsink.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
--- /dev/null
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>