diff --git a/configure.ac b/configure.ac index 672fb6c186..e107b911ef 100644 --- a/configure.ac +++ b/configure.ac @@ -2602,6 +2602,30 @@ AG_GST_CHECK_FEATURE(GTK3, [Gtk+ elements], gtk, [ ]) AM_CONDITIONAL(USE_GTK3_GL, test "x$HAVE_GTK3_GL" = "xyes") +dnl *** Qt *** +translit(dnm, m, l) AM_CONDITIONAL(USE_QT, true) +AG_GST_CHECK_FEATURE(QT, [Qt elements], qt, [ + PKG_CHECK_MODULES(QT, Qt5Core Qt5Gui Qt5Quick, [ + AC_CHECK_PROGS(MOC, [moc-qt5 moc]) + AC_CHECK_PROGS(UIC, [uic-qt5 uic]) + AC_CHECK_PROGS(RCC, [rcc]) + if test "x$MOC" = "x" || test "x$UIC" = "x" || test "x$RCC" = "x"; then + AC_MSG_WARN([One of the required qt build programs was not found]) + HAVE_QT="no" + else + HAVE_QT="yes" + PKG_CHECK_MODULES(QT_X11, Qt5X11Extras, [ + QT_CFLAGS="$QT_CFLAGS $QT_X11_CFLAGS" + QT_LIBS="$QT_LIBS $QT_X11_LIBS" + AC_SUBST([QT_CFLAGS]) + AC_SUBST([QT_LIBS]) + ], []) + fi + ], [ + HAVE_QT="no" + ]) +]) + dnl *** libvisual *** translit(dnm, m, l) AM_CONDITIONAL(USE_LIBVISUAL, true) AG_GST_CHECK_FEATURE(LIBVISUAL, [libvisual visualization library], libvisual, [ @@ -3032,6 +3056,7 @@ AM_CONDITIONAL(USE_OPENJPEG, false) AM_CONDITIONAL(USE_OPENNI2, false) AM_CONDITIONAL(USE_OPUS, false) AM_CONDITIONAL(USE_PVR, false) +AM_CONDITIONAL(USE_QT, false) AM_CONDITIONAL(USE_LIBVISUAL, false) AM_CONDITIONAL(USE_TIMIDITY, false) AM_CONDITIONAL(USE_WILDMIDI, false) @@ -3339,6 +3364,7 @@ ext/openh264/Makefile ext/openjpeg/Makefile ext/openni2/Makefile ext/opus/Makefile +ext/qt/Makefile ext/rsvg/Makefile ext/resindvd/Makefile ext/rtmp/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 66dccd6b61..33ddba7ead 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -94,6 +94,12 @@ else GTK3_DIR= endif +if USE_QT +QT_DIR=qt +else +QT_DIR= +endif + if USE_RESINDVD RESINDVD_DIR = resindvd else @@ -443,6 +449,7 @@ SUBDIRS=\ $(RESINDVD_DIR) \ $(GL_DIR) \ $(GTK3_DIR) \ + $(QT_DIR) \ $(FAAC_DIR) \ $(FAAD_DIR) \ $(FLITE_DIR) \ @@ -524,6 +531,7 @@ DIST_SUBDIRS = \ dts \ gl \ gtk \ + qt \ modplug \ mimic \ mpeg2enc \ diff --git a/ext/qt/.gitignore b/ext/qt/.gitignore new file mode 100644 index 0000000000..f830c38fc1 --- /dev/null +++ b/ext/qt/.gitignore @@ -0,0 +1 @@ +moc_*.cc diff --git a/ext/qt/Makefile.am b/ext/qt/Makefile.am new file mode 100644 index 0000000000..5dc38dc085 --- /dev/null +++ b/ext/qt/Makefile.am @@ -0,0 +1,55 @@ +# preamble +NULL = + +moc_headers = \ + qtitem.h \ + gstqsgtexture.h \ + $(NULL) + +moc_generated = \ + moc_qtitem.cc \ + moc_gstqsgtexture.cc \ + $(NULL) + +#anything generated by the Qt tools... +BUILT_SOURCES = $(moc_generated) +CLEANFILES = $(moc_generated) + +sources = \ + gstqsgtexture.cc \ + qtitem.cc \ + gstqtsink.cc \ + gstqtsink.h \ + gstplugin.cc \ + $(moc_headers) \ + $(moc_generated) \ + $(NULL) + +libqtsink_la_CXXFLAGS = \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_CXXFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(QT_CFLAGS) +libqtsink_la_LIBADD = \ + $(GST_BASE_LIBS) \ + $(GST_PLUGINS_BASE_LIBS) \ + $(QT_LIBS) \ + $(top_builddir)/gst-libs/gst/gl/libgstgl-$(GST_API_VERSION).la \ + -lgstvideo-$(GST_API_VERSION) + +libqtsink_la_SOURCES = $(sources) +libqtsink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libqtsink_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +plugin_LTLIBRARIES = libqtsink.la + +$(moc_generated): moc_%.cc: %.h + @@MOC@ -o$@ $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(MOC_CPPFLAGS) $< + +ui-%.h: %.ui + @@UIC@ -o $@ $< + +qrc-%.cc: %.qrc + @@RCC@ -o $@ $< diff --git a/ext/qt/gstplugin.cc b/ext/qt/gstplugin.cc new file mode 100644 index 0000000000..70b6310f01 --- /dev/null +++ b/ext/qt/gstplugin.cc @@ -0,0 +1,46 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 "gstqtsink.h" +#include + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "qmlglsink", + GST_RANK_NONE, GST_TYPE_QT_SINK)) { + return FALSE; + } + /* this means the plugin must be loaded before the qml engine is loaded */ + qmlRegisterType ("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + qt, + "Qt sink", + plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/ext/qt/gstqsgtexture.cc b/ext/qt/gstqsgtexture.cc new file mode 100644 index 0000000000..1e5203832a --- /dev/null +++ b/ext/qt/gstqsgtexture.cc @@ -0,0 +1,142 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include "gstqsgtexture.h" + +#define GST_CAT_DEFAULT gst_qsg_texture_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +GstQSGTexture::GstQSGTexture () +{ + static volatile gsize _debug; + + initializeOpenGLFunctions(); + + 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; +} + +GstQSGTexture::~GstQSGTexture () +{ + gst_buffer_replace (&this->buffer_, NULL); +} + +/* only called from the streaming thread with scene graph thread blocked */ +void +GstQSGTexture::setCaps (GstCaps * caps) +{ + GST_LOG ("%p setCaps %" GST_PTR_FORMAT, this, caps); + + gst_video_info_from_caps (&this->v_info, caps); +} + +/* only called from the streaming thread with scene graph thread blocked */ +void +GstQSGTexture::setBuffer (GstBuffer * buffer) +{ + GST_LOG ("%p setBuffer %" GST_PTR_FORMAT, this, buffer); + /* FIXME: update more state here */ + gst_buffer_replace (&this->buffer_, buffer); +} + +/* only called from qt's scene graph render thread */ +void +GstQSGTexture::bind () +{ + guint tex_id; + + if (!this->buffer_) + return; + if (GST_VIDEO_INFO_FORMAT (&this->v_info) == GST_VIDEO_FORMAT_UNKNOWN) + return; + + /* 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 (); + return; + } + + tex_id = *(guint *) this->v_frame.data[0]; + GST_LOG ("%p binding Qt texture %u", this, tex_id); + + glBindTexture (GL_TEXTURE_2D, tex_id); + + gst_video_frame_unmap (&this->v_frame); +} + +/* can be called from any thread */ +int +GstQSGTexture::textureId () const +{ + int tex_id = 0; + + if (this->buffer_) { + GstMemory *mem = gst_buffer_peek_memory (this->buffer_, 0); + + tex_id = ((GstGLMemory *) mem)->tex_id; + } + + GST_LOG ("%p get texture id %u", this, tex_id); + + return tex_id; +} + +/* can be called from any thread */ +QSize +GstQSGTexture::textureSize () const +{ + if (GST_VIDEO_INFO_FORMAT (&this->v_info) == GST_VIDEO_FORMAT_UNKNOWN) + return QSize (0, 0); + + GST_TRACE ("%p get texture size %ux%u", this, this->v_info.width, + this->v_info.height); + + return QSize (this->v_info.width, this->v_info.height); +} + +/* can be called from any thread */ +bool +GstQSGTexture::hasAlphaChannel () const +{ + /* FIXME: support RGB textures */ + return true; +} + +/* can be called from any thread */ +bool +GstQSGTexture::hasMipmaps () const +{ + return false; +} diff --git a/ext/qt/gstqsgtexture.h b/ext/qt/gstqsgtexture.h new file mode 100644 index 0000000000..93fb60d063 --- /dev/null +++ b/ext/qt/gstqsgtexture.h @@ -0,0 +1,52 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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_QSG_TEXTURE_H__ +#define __GST_QSG_TEXTURE_H__ + +#include +#include +#include +#include + +class GstQSGTexture : public QSGTexture, protected QOpenGLFunctions +{ + Q_OBJECT +public: + GstQSGTexture (); + ~GstQSGTexture (); + + void setCaps (GstCaps * caps); + void setBuffer (GstBuffer * buffer); + + /* QSGTexture */ + void bind (); + int textureId () const; + QSize textureSize () const; + bool hasAlphaChannel () const; + bool hasMipmaps () const; + +private: + GstBuffer * buffer_; + GstVideoInfo v_info; + GstVideoFrame v_frame; +}; + +#endif /* __GST_QSG_TEXTURE_H__ */ diff --git a/ext/qt/gstqtsink.cc b/ext/qt/gstqtsink.cc new file mode 100644 index 0000000000..40e66d8d70 --- /dev/null +++ b/ext/qt/gstqtsink.cc @@ -0,0 +1,500 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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:gstqtsink + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstqtsink.h" +#include + +#define GST_CAT_DEFAULT gst_debug_qt_gl_sink +GST_DEBUG_CATEGORY (GST_CAT_DEFAULT); + +static void gst_qt_sink_finalize (GObject * object); +static void gst_qt_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * param_spec); +static void gst_qt_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * param_spec); + +static gboolean gst_qt_sink_stop (GstBaseSink * bsink); + +static gboolean gst_qt_sink_query (GstBaseSink * bsink, GstQuery * query); + +static GstStateChangeReturn +gst_qt_sink_change_state (GstElement * element, GstStateChange transition); + +static void gst_qt_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end); +static gboolean gst_qt_sink_set_caps (GstBaseSink * bsink, GstCaps * caps); +static GstFlowReturn gst_qt_sink_show_frame (GstVideoSink * bsink, + GstBuffer * buf); +static gboolean gst_qt_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 (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA"))); + +#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_qt_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstQtSink, gst_qt_sink, + GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, + "qtsink", 0, "Qt Video Sink")); + +static void +gst_qt_sink_class_init (GstQtSinkClass * 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_qt_sink_set_property; + gobject_class->get_property = gst_qt_sink_get_property; + + gst_element_class_set_metadata (gstelement_class, "Qt Video Sink", + "Sink/Video", "A video sink the renders to a QQuickItem", + "Matthew Waters "); + + g_object_class_install_property (gobject_class, PROP_WIDGET, + g_param_spec_pointer ("widget", "QQuickItem", + "The QQuickItem to place in the object heirachy", + (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_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_qt_sink_template)); + + gobject_class->finalize = gst_qt_sink_finalize; + + gstelement_class->change_state = gst_qt_sink_change_state; + gstbasesink_class->query = gst_qt_sink_query; + gstbasesink_class->set_caps = gst_qt_sink_set_caps; + gstbasesink_class->get_times = gst_qt_sink_get_times; + gstbasesink_class->propose_allocation = gst_qt_sink_propose_allocation; + gstbasesink_class->stop = gst_qt_sink_stop; + + gstvideosink_class->show_frame = gst_qt_sink_show_frame; +} + +static void +gst_qt_sink_init (GstQtSink * qt_sink) +{ +} + +static void +gst_qt_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstQtSink *qt_sink = GST_QT_SINK (object); + + switch (prop_id) { + case PROP_WIDGET: + qt_sink->widget = static_cast (g_value_get_pointer (value)); + 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 (GstQtSink * 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_qt_sink_finalize (GObject * object) +{ + GstQtSink *qt_sink = GST_QT_SINK (object); + + _reset (qt_sink); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_qt_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstQtSink *qt_sink = GST_QT_SINK (object); + + switch (prop_id) { + case PROP_WIDGET: + g_value_set_pointer (value, qt_sink->widget); + 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_qt_sink_query (GstBaseSink * bsink, GstQuery * query) +{ + GstQtSink *qt_sink = GST_QT_SINK (bsink); + gboolean res = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + { + const gchar *context_type; + GstContext *context, *old_context; + gboolean ret; + + ret = gst_gl_handle_context_query ((GstElement *) qt_sink, query, + &qt_sink->display, &qt_sink->qt_context); + + if (qt_sink->display) + gst_gl_display_filter_gl_api (qt_sink->display, gst_gl_context_get_gl_api (qt_sink->qt_context)); + + gst_query_parse_context_type (query, &context_type); + + if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) { + GstStructure *s; + + gst_query_parse_context (query, &old_context); + + if (old_context) + context = gst_context_copy (old_context); + else + context = gst_context_new ("gst.gl.local_context", FALSE); + + s = gst_context_writable_structure (context); + gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, qt_sink->context, + NULL); + gst_query_set_context (query, context); + gst_context_unref (context); + + ret = qt_sink->context != NULL; + } + GST_LOG_OBJECT (qt_sink, "context query of type %s %i", context_type, + ret); + + if (ret) + return ret; + } + default: + res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); + break; + } + + return res; +} + +static gboolean +gst_qt_sink_stop (GstBaseSink * bsink) +{ + return TRUE; +} + +static GstStateChangeReturn +gst_qt_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstQtSink *qt_sink = GST_QT_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 = dynamic_cast (QCoreApplication::instance ()); + if (!app) { + GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, + ("%s", "Failed to connect to Qt"), + ("%s", "Could not retreive 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_item_init_winsys (qt_sink->widget)) { + GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, + ("%s", "Could not initialize window system"), + (NULL)); + return GST_STATE_CHANGE_FAILURE; + } + + qt_sink->display = qt_item_get_display (qt_sink->widget); + qt_sink->context = qt_item_get_context (qt_sink->widget); + qt_sink->qt_context = qt_item_get_qt_context (qt_sink->widget); + + if (!qt_sink->display || !qt_sink->context || !qt_sink->qt_context) { + GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, + ("%s", "Could not retreive window system OpenGL configuration"), + (NULL)); + return GST_STATE_CHANGE_FAILURE; + } + 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: + qt_item_set_buffer (qt_sink->widget, NULL); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static void +gst_qt_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end) +{ + GstQtSink *qt_sink; + + qt_sink = GST_QT_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_qt_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) +{ + GstQtSink *qt_sink = GST_QT_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_item_set_caps (qt_sink->widget, caps)) + return FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_qt_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf) +{ + GstQtSink *qt_sink; + + GST_TRACE ("rendering buffer:%p", buf); + + qt_sink = GST_QT_SINK (vsink); + + qt_item_set_buffer (qt_sink->widget, buf); + + return GST_FLOW_OK; +} + +static gboolean +gst_qt_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) +{ + GstQtSink *qt_sink = GST_QT_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; + + 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); + } + + if (pool == NULL && need_pool) { + GstVideoInfo info; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + GST_DEBUG_OBJECT (qt_sink, "create new pool"); + pool = gst_gl_buffer_pool_new (qt_sink->context); + + /* the normal size of a frame */ + size = info.size; + + 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 */ + if (pool) { + gst_query_add_allocation_pool (query, pool, size, 2, 0); + 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; + } +} diff --git a/ext/qt/gstqtsink.h b/ext/qt/gstqtsink.h new file mode 100644 index 0000000000..9a1cfbece8 --- /dev/null +++ b/ext/qt/gstqtsink.h @@ -0,0 +1,81 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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_QT_SINK_H__ +#define __GST_QT_SINK_H__ + +#include +#include +#include +#include +#include "qtitem.h" + +typedef struct _GstQtSink GstQtSink; +typedef struct _GstQtSinkClass GstQtSinkClass; +typedef struct _GstQtSinkPrivate GstQtSinkPrivate; + +G_BEGIN_DECLS + +GType gst_qt_sink_get_type (void); +#define GST_TYPE_QT_SINK (gst_qt_sink_get_type()) +#define GST_QT_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QT_SINK,GstQtSink)) +#define GST_QT_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QT_SINK,GstQtSinkClass)) +#define GST_IS_QT_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QT_SINK)) +#define GST_IS_QT_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QT_SINK)) +#define GST_QT_SINK_CAST(obj) ((GstQtSink*)(obj)) + +/** + * GstQtSink: + * + * Opaque #GstQtSink object + */ +struct _GstQtSink +{ + /* */ + GstVideoSink parent; + + QtGLVideoItem *widget; + + GstVideoInfo v_info; + GstBufferPool *pool; + + GstGLDisplay *display; + GstGLContext *context; + GstGLContext *qt_context; + + GstQtSinkPrivate *priv; +}; + +/** + * GstQtSinkClass: + * + * The #GstQtSinkClass struct only contains private data + */ +struct _GstQtSinkClass +{ + /* */ + GstVideoSinkClass object_class; +}; + +GstQtSink * gst_qt_sink_new (void); + +G_END_DECLS + +#endif /* __GST_QT_SINK_H__ */ diff --git a/ext/qt/qtitem.cc b/ext/qt/qtitem.cc new file mode 100644 index 0000000000..5a7ac2e698 --- /dev/null +++ b/ext/qt/qtitem.cc @@ -0,0 +1,491 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include "qtitem.h" +#include "gstqsgtexture.h" + +#if GST_GL_HAVE_WINDOW_X11 +#include +#include +#include +#endif + +#if GST_GL_HAVE_WINDOW_WAYLAND +#include +#endif + +/** + * SECTION:gtkgstglwidget + * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers + * @see_also: #GtkGLArea, #GstBuffer + * + * #QtGLVideoItem is an #GtkWidget that renders GStreamer video buffers. + */ + +#define GST_CAT_DEFAULT qt_item_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define GTK_GST_GL_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + GTK_TYPE_GST_GL_WIDGET, QtGLVideoItemPrivate)) + +#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 _QtGLVideoItemPrivate +{ + GMutex lock; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + + gint display_width; + gint display_height; + + gboolean negotiated; + GstBuffer *buffer; + GstCaps *caps; + GstVideoInfo v_info; + + gboolean initted; + GstGLDisplay *display; + QOpenGLContext *qt_context; + GstGLContext *other_context; + GstGLContext *context; +}; + +QtGLVideoItem::QtGLVideoItem() +{ + QGuiApplication *app = dynamic_cast (QCoreApplication::instance ()); + static volatile gsize _debug; + + g_assert (app != NULL); + + 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 (QtGLVideoItemPrivate, 1); + + this->priv->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + this->priv->par_n = DEFAULT_PAR_N; + this->priv->par_d = DEFAULT_PAR_D; + + g_mutex_init (&this->priv->lock); + +#if GST_GL_HAVE_WINDOW_X11 + if (QString::fromUtf8 ("xcb") == app->platformName()) + this->priv->display = (GstGLDisplay *) + gst_gl_display_x11_new_with_display (QX11Info::display ()); +#endif + + if (!this->priv->display) + this->priv->display = gst_gl_display_new (); + + connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, + SLOT(handleWindowChanged(QQuickWindow*))); + + GST_DEBUG ("%p init Qt Video Item", this); +} + +QtGLVideoItem::~QtGLVideoItem() +{ + g_mutex_clear (&this->priv->lock); + + g_free (this->priv); + this->priv = NULL; +} + +void +QtGLVideoItem::setDAR(gint num, gint den) +{ + this->priv->par_n = num; + this->priv->par_d = den; +} + +void +QtGLVideoItem::getDAR(gint * num, gint * den) +{ + if (num) + *num = this->priv->par_n; + if (den) + *den = this->priv->par_d; +} + +void +QtGLVideoItem::setForceAspectRatio(bool far) +{ + this->priv->force_aspect_ratio = far; +} + +bool +QtGLVideoItem::getForceAspectRatio() +{ + return this->priv->force_aspect_ratio; +} + +QSGNode * +QtGLVideoItem::updatePaintNode(QSGNode * oldNode, + UpdatePaintNodeData * updatePaintNodeData) +{ + QSGSimpleTextureNode *texNode = static_cast (oldNode); + GstVideoRectangle src, dst, result; + GstQSGTexture *tex; + + g_mutex_lock (&this->priv->lock); + + GST_TRACE ("%p updatePaintNode", this); + + if (!this->priv->caps) { + g_mutex_unlock (&this->priv->lock); + return NULL; + } + + if (!texNode) { + texNode = new QSGSimpleTextureNode (); + tex = new GstQSGTexture (); + texNode->setTexture (tex); + } else { + tex = static_cast (texNode->texture()); + } + + tex->setCaps (this->priv->caps); + tex->setBuffer (this->priv->buffer); + + if (this->priv->force_aspect_ratio) { + 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; +} + +static void +_reset (QtGLVideoItem * qt_item) +{ + gst_buffer_replace (&qt_item->priv->buffer, NULL); + + gst_caps_replace (&qt_item->priv->caps, NULL); + + qt_item->priv->negotiated = FALSE; + qt_item->priv->initted = FALSE; +} + +void +qt_item_set_buffer (QtGLVideoItem * widget, GstBuffer * buffer) +{ + g_return_if_fail (widget != NULL); + g_return_if_fail (widget->priv->negotiated); + + g_mutex_lock (&widget->priv->lock); + + gst_buffer_replace (&widget->priv->buffer, buffer); + + QMetaObject::invokeMethod(widget, "update", Qt::QueuedConnection); + + g_mutex_unlock (&widget->priv->lock); +} + +void +QtGLVideoItem::onSceneGraphInitialized () +{ + GstGLPlatform platform; + GstGLAPI gl_api; + guintptr gl_handle; + + GST_DEBUG ("scene graph initialization with Qt GL context %p", + this->window()->openglContext ()); + + if (this->priv->qt_context == this->window()->openglContext ()) + return; + + this->priv->qt_context = this->window()->openglContext (); + if (this->priv->qt_context == NULL) { + g_assert_not_reached (); + return; + } + +#if GST_GL_HAVE_WINDOW_X11 + if (GST_IS_GL_DISPLAY_X11 (this->priv->display)) { + platform = GST_GL_PLATFORM_GLX; + gl_api = gst_gl_context_get_current_gl_api (NULL, NULL); + gl_handle = gst_gl_context_get_current_gl_context (platform); + if (gl_handle) + this->priv->other_context = + gst_gl_context_new_wrapped (this->priv->display, gl_handle, + platform, gl_api); + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND + if (GST_IS_GL_DISPLAY_WAYLAND (this->priv->display)) { + platform = GST_GL_PLATFORM_EGL; + gl_api = gst_gl_context_get_current_gl_api (NULL, NULL); + gl_handle = gst_gl_context_get_current_gl_context (platform); + if (gl_handle) + this->priv->other_context = + gst_gl_context_new_wrapped (this->priv->display, gl_handle, + platform, gl_api); + } +#endif + + (void) platform; + (void) gl_api; + (void) gl_handle; + + if (this->priv->other_context) { + GError *error = NULL; + + gst_gl_context_activate (this->priv->other_context, TRUE); + if (!gst_gl_context_fill_info (this->priv->other_context, &error)) { + GST_ERROR ("%p failed to retreive qt context info: %s", this, error->message); + g_object_unref (this->priv->other_context); + this->priv->other_context = NULL; + } else { + gst_gl_display_filter_gl_api (this->priv->display, gst_gl_context_get_gl_api (this->priv->other_context)); + gst_gl_context_activate (this->priv->other_context, FALSE); + } + } + + GST_DEBUG ("%p created wrapped GL context %" GST_PTR_FORMAT, this, + this->priv->other_context); +} + +void +QtGLVideoItem::onSceneGraphInvalidated () +{ + GST_FIXME ("%p scene graph invalidated", this); +} + +gboolean +qt_item_init_winsys (QtGLVideoItem * widget) +{ + g_return_val_if_fail (widget != NULL, FALSE); + + g_mutex_lock (&widget->priv->lock); + + if (widget->priv->display && widget->priv->qt_context + && widget->priv->other_context && widget->priv->context) { + /* already have the necessary state */ + g_mutex_unlock (&widget->priv->lock); + return TRUE; + } + + if (!GST_IS_GL_DISPLAY (widget->priv->display)) { + GST_ERROR ("%p failed to retreive display connection %" GST_PTR_FORMAT, + widget, widget->priv->display); + g_mutex_unlock (&widget->priv->lock); + return FALSE; + } + + if (!GST_GL_IS_CONTEXT (widget->priv->other_context)) { + GST_ERROR ("%p failed to retreive wrapped context %" GST_PTR_FORMAT, widget, + widget->priv->other_context); + g_mutex_unlock (&widget->priv->lock); + return FALSE; + } + + widget->priv->context = gst_gl_context_new (widget->priv->display); + + if (!widget->priv->context) { + g_mutex_unlock (&widget->priv->lock); + return FALSE; + } + + gst_gl_context_create (widget->priv->context, widget->priv->other_context, + NULL); + + g_mutex_unlock (&widget->priv->lock); + return TRUE; +} + +void +QtGLVideoItem::handleWindowChanged(QQuickWindow *win) +{ + if (win) { + connect(win, SIGNAL(sceneGraphInitialized()), this, SLOT(onSceneGraphInitialized()), Qt::DirectConnection); + connect(win, SIGNAL(sceneGraphInvalidated()), this, SLOT(onSceneGraphInvalidated()), Qt::DirectConnection); + } else { + this->priv->qt_context = NULL; + } +} + +static gboolean +_calculate_par (QtGLVideoItem * 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; + + GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n, display_par_d); + + if (height % display_ratio_den == 0) { + GST_DEBUG ("keeping video height"); + 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 ("keeping video width"); + 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 ("approximating while keeping video height"); + widget->priv->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->priv->display_height = height; + } + GST_DEBUG ("scaling to %dx%d", widget->priv->display_width, + widget->priv->display_height); + + return TRUE; +} + +gboolean +qt_item_set_caps (QtGLVideoItem * widget, GstCaps * caps) +{ + GstVideoInfo v_info; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + if (widget->priv->caps && gst_caps_is_equal_fixed (widget->priv->caps, caps)) + return TRUE; + + if (!gst_video_info_from_caps (&v_info, caps)) + return FALSE; + + g_mutex_lock (&widget->priv->lock); + + _reset (widget); + + gst_caps_replace (&widget->priv->caps, caps); + + if (!_calculate_par (widget, &v_info)) { + g_mutex_unlock (&widget->priv->lock); + return FALSE; + } + + widget->priv->v_info = v_info; + widget->priv->negotiated = TRUE; + + g_mutex_unlock (&widget->priv->lock); + + return TRUE; +} + +GstGLContext * +qt_item_get_qt_context (QtGLVideoItem * qt_item) +{ + g_return_val_if_fail (qt_item != NULL, NULL); + + if (!qt_item->priv->other_context) + return NULL; + + return (GstGLContext *) gst_object_ref (qt_item->priv->other_context); +} + +GstGLContext * +qt_item_get_context (QtGLVideoItem * qt_item) +{ + g_return_val_if_fail (qt_item != NULL, NULL); + + if (!qt_item->priv->context) + return NULL; + + return (GstGLContext *) gst_object_ref (qt_item->priv->context); +} + +GstGLDisplay * +qt_item_get_display (QtGLVideoItem * qt_item) +{ + g_return_val_if_fail (qt_item != NULL, NULL); + + if (!qt_item->priv->display) + return NULL; + + return (GstGLDisplay *) gst_object_ref (qt_item->priv->display); +} diff --git a/ext/qt/qtitem.h b/ext/qt/qtitem.h new file mode 100644 index 0000000000..afd2869d82 --- /dev/null +++ b/ext/qt/qtitem.h @@ -0,0 +1,72 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 __QT_ITEM_H__ +#define __QT_ITEM_H__ + +#include +#include +#include +#include +#include + +typedef struct _QtGLVideoItemPrivate QtGLVideoItemPrivate; + +class QtGLVideoItem : public QQuickItem, protected QOpenGLFunctions +{ + Q_OBJECT +public: + QtGLVideoItem(); + ~QtGLVideoItem(); + + void setDAR(gint, gint); + void getDAR(gint *, gint *); + void setForceAspectRatio(bool); + bool getForceAspectRatio(); + + /* private for C interface ... */ + QtGLVideoItemPrivate *priv; + +private Q_SLOTS: + void handleWindowChanged(QQuickWindow * win); + void onSceneGraphInitialized(); + void onSceneGraphInvalidated(); + +protected: + QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData); + +private: + void setViewportSize(const QSize &size); + void shareContext(); + + QSize m_viewportSize; +}; + +extern "C" +{ +void qt_item_set_buffer (QtGLVideoItem * widget, GstBuffer * buffer); +gboolean qt_item_set_caps (QtGLVideoItem * widget, GstCaps * caps); +gboolean qt_item_init_winsys (QtGLVideoItem * widget); +GstGLContext * qt_item_get_qt_context (QtGLVideoItem * qt_item); +GstGLContext * qt_item_get_context (QtGLVideoItem * qt_item); +GstGLDisplay * qt_item_get_display (QtGLVideoItem * qt_item); +} + +#endif /* __QT_ITEM_H__ */ diff --git a/tests/examples/qt/qml/.gitignore b/tests/examples/qt/qml/.gitignore new file mode 100644 index 0000000000..1f81518587 --- /dev/null +++ b/tests/examples/qt/qml/.gitignore @@ -0,0 +1,3 @@ +deployment.pri +play +qrc_qml.cpp diff --git a/tests/examples/qt/qml/main.cpp b/tests/examples/qt/qml/main.cpp new file mode 100644 index 0000000000..2317b05415 --- /dev/null +++ b/tests/examples/qt/qml/main.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include + +class SetPlaying : public QRunnable +{ +public: + SetPlaying(GstElement *); + ~SetPlaying(); + + void run (); + +private: + GstElement * pipeline_; +}; + +SetPlaying::SetPlaying (GstElement * pipeline) +{ + this->pipeline_ = pipeline ? static_cast (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; + + QGuiApplication app(argc, argv); + gst_init (&argc, &argv); + + 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 ("qmlglsink", 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 (engine.rootObjects().first()); + videoItem = rootObject->findChild ("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; +} diff --git a/tests/examples/qt/qml/main.qml b/tests/examples/qt/qml/main.qml new file mode 100644 index 0000000000..842e98f2eb --- /dev/null +++ b/tests/examples/qt/qml/main.qml @@ -0,0 +1,60 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Dialogs 1.2 +import QtQuick.Window 2.1 + +import org.freedesktop.gstreamer.GLVideoItem 1.0 + +ApplicationWindow { + id: window + visible: true + width: 640 + height: 480 + x: 30 + y: 30 + color: "black" + + Item { + anchors.fill: parent + + GstGLVideoItem { + 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 : childrenRect.width + 20 + height: childrenRect.height + 20 + 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() + } + } + } + } +} diff --git a/tests/examples/qt/qml/play.pro b/tests/examples/qt/qml/play.pro new file mode 100644 index 0000000000..374e40297a --- /dev/null +++ b/tests/examples/qt/qml/play.pro @@ -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 += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = diff --git a/tests/examples/qt/qml/qml.qrc b/tests/examples/qt/qml/qml.qrc new file mode 100644 index 0000000000..5f6483ac33 --- /dev/null +++ b/tests/examples/qt/qml/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + +