mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 00:36:51 +00:00
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:
parent
47b8762774
commit
18972fc942
22 changed files with 2855 additions and 0 deletions
|
@ -13,6 +13,7 @@ subdir('libpng')
|
||||||
subdir('mpg123')
|
subdir('mpg123')
|
||||||
subdir('raw1394')
|
subdir('raw1394')
|
||||||
subdir('qt')
|
subdir('qt')
|
||||||
|
subdir('qt6')
|
||||||
subdir('pulse')
|
subdir('pulse')
|
||||||
subdir('shout2')
|
subdir('shout2')
|
||||||
subdir('soup')
|
subdir('soup')
|
||||||
|
|
50
subprojects/gst-plugins-good/ext/qt6/gstplugin.cc
Normal file
50
subprojects/gst-plugins-good/ext/qt6/gstplugin.cc
Normal 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)
|
582
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.cc
Normal file
582
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.cc
Normal 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;
|
||||||
|
}
|
62
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.h
Normal file
62
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.h
Normal 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__ */
|
186
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.cc
Normal file
186
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.cc
Normal 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_);
|
||||||
|
}
|
||||||
|
}
|
58
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.h
Normal file
58
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.h
Normal 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;
|
||||||
|
};
|
38
subprojects/gst-plugins-good/ext/qt6/gstqt6element.cc
Normal file
38
subprojects/gst-plugins-good/ext/qt6/gstqt6element.cc
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
33
subprojects/gst-plugins-good/ext/qt6/gstqt6elements.h
Normal file
33
subprojects/gst-plugins-good/ext/qt6/gstqt6elements.h
Normal 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__ */
|
56
subprojects/gst-plugins-good/ext/qt6/gstqt6gl.h
Normal file
56
subprojects/gst-plugins-good/ext/qt6/gstqt6gl.h
Normal 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) */
|
358
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.cc
Normal file
358
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.cc
Normal 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
|
55
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.h
Normal file
55
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.h
Normal 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__ */
|
144
subprojects/gst-plugins-good/ext/qt6/meson.build
Normal file
144
subprojects/gst-plugins-good/ext/qt6/meson.build
Normal 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
|
905
subprojects/gst-plugins-good/ext/qt6/qt6glitem.cc
Normal file
905
subprojects/gst-plugins-good/ext/qt6/qt6glitem.cc
Normal 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;
|
||||||
|
}
|
||||||
|
|
127
subprojects/gst-plugins-good/ext/qt6/qt6glitem.h
Normal file
127
subprojects/gst-plugins-good/ext/qt6/qt6glitem.h
Normal 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__ */
|
|
@ -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('png', type : 'feature', value : 'auto', description : 'PNG image codec plugin')
|
||||||
option('pulse', type : 'feature', value : 'auto', description : 'Pulseaudio audio source/sink 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('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('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('soup', type : 'feature', value : 'auto', description : 'libsoup HTTP client source/sink plugin')
|
||||||
option('speex', type : 'feature', value : 'auto', description : 'Speex audio codec plugin')
|
option('speex', type : 'feature', value : 'auto', description : 'Speex audio codec plugin')
|
||||||
|
|
|
@ -2,6 +2,7 @@ subdir('audiofx')
|
||||||
subdir('cairo')
|
subdir('cairo')
|
||||||
subdir('level')
|
subdir('level')
|
||||||
subdir('qt')
|
subdir('qt')
|
||||||
|
subdir('qt6')
|
||||||
|
|
||||||
if is_variable('gstrpicamsrc')
|
if is_variable('gstrpicamsrc')
|
||||||
subdir('rpicamsrc')
|
subdir('rpicamsrc')
|
||||||
|
|
17
subprojects/gst-plugins-good/tests/examples/qt6/meson.build
Normal file
17
subprojects/gst-plugins-good/tests/examples/qt6/meson.build
Normal 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')
|
|
@ -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;
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -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 =
|
|
@ -0,0 +1,5 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>main.qml</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
Loading…
Reference in a new issue