add new plugin for Qt 6 rendering inside a QML scene

- Based heavily on the existing Qt5 integration however:
  - The sharing of OpenGL resources is slightly different
  - The integration with the scengraph is a bit different
- Wayland, XCB and KMS have been smoke tested.  Android, MacOS/iOS,
  Windows may or may not work.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3281>
This commit is contained in:
Matthew Waters 2022-10-06 17:08:54 +11:00
parent 47b8762774
commit 18972fc942
22 changed files with 2855 additions and 0 deletions

View file

@ -13,6 +13,7 @@ subdir('libpng')
subdir('mpg123')
subdir('raw1394')
subdir('qt')
subdir('qt6')
subdir('pulse')
subdir('shout2')
subdir('soup')

View file

@ -0,0 +1,50 @@
/*
* 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)

View file

@ -0,0 +1,582 @@
/*
* 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;
}

View file

@ -0,0 +1,62 @@
/*
* 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__ */

View file

@ -0,0 +1,186 @@
/*
* 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_);
}
}

View file

@ -0,0 +1,58 @@
/*
* 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;
};

View file

@ -0,0 +1,38 @@
/*
* 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);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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__ */

View file

@ -0,0 +1,56 @@
/*
* 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) */

View file

@ -0,0 +1,358 @@
/*
* 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

View file

@ -0,0 +1,55 @@
/*
* 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__ */

View file

@ -0,0 +1,144 @@
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

View file

@ -0,0 +1,905 @@
/*
* 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;
}

View file

@ -0,0 +1,127 @@
/*
* 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__ */

View file

@ -67,6 +67,7 @@ option('osxvideo', type : 'feature', value : 'auto', description : 'macOS Cocoa
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')

View file

@ -2,6 +2,7 @@ subdir('audiofx')
subdir('cairo')
subdir('level')
subdir('qt')
subdir('qt6')
if is_variable('gstrpicamsrc')
subdir('rpicamsrc')

View file

@ -0,0 +1,17 @@
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')

View file

@ -0,0 +1,85 @@
#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;
}

View file

@ -0,0 +1,59 @@
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()
}
}
}
}
}

View file

@ -0,0 +1,12 @@
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)

View file

@ -0,0 +1,20 @@
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 =

View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>