mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 16:26:39 +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('raw1394')
|
||||
subdir('qt')
|
||||
subdir('qt6')
|
||||
subdir('pulse')
|
||||
subdir('shout2')
|
||||
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('pulse', type : 'feature', value : 'auto', description : 'Pulseaudio audio source/sink plugin')
|
||||
option('qt5', type : 'feature', value : 'auto', yield : true, description : 'Qt5 QML video sink plugin')
|
||||
option('qt6', type : 'feature', value : 'auto', yield : true, description : 'Qt6 QML video sink plugin')
|
||||
option('shout2', type : 'feature', value : 'auto', description : 'Shout-casting network sink plugin based on libshout2')
|
||||
option('soup', type : 'feature', value : 'auto', description : 'libsoup HTTP client source/sink plugin')
|
||||
option('speex', type : 'feature', value : 'auto', description : 'Speex audio codec plugin')
|
||||
|
|
|
@ -2,6 +2,7 @@ subdir('audiofx')
|
|||
subdir('cairo')
|
||||
subdir('level')
|
||||
subdir('qt')
|
||||
subdir('qt6')
|
||||
|
||||
if is_variable('gstrpicamsrc')
|
||||
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