diff --git a/.github/workflows/macos_debug.yml b/.github/workflows/macos_debug.yml index 73db165f984..76a79b321a3 100644 --- a/.github/workflows/macos_debug.yml +++ b/.github/workflows/macos_debug.yml @@ -62,17 +62,12 @@ jobs: cache: true - name: Install Dependencies - run: | - brew install ninja - brew install SDL2 + run: brew install ninja SDL2 - - name: Install Gstreamer - run: | - wget https://gstreamer.freedesktop.org/data/pkg/osx/1.18.6/gstreamer-1.0-devel-1.18.6-x86_64.pkg - wget https://gstreamer.freedesktop.org/data/pkg/osx/1.18.6/gstreamer-1.0-1.18.6-x86_64.pkg - for package in *.pkg ; - do sudo installer -verbose -pkg "$package" -target / - done + - name: Setup GStreamer + uses: blinemedical/setup-gstreamer@v1 + with: + version: 1.18.6 - name: Create build directory run: mkdir ${{ runner.temp }}/shadow_build_dir diff --git a/.github/workflows/windows_debug.yml b/.github/workflows/windows_debug.yml index ad8fdb9c4d7..64dc747c196 100644 --- a/.github/workflows/windows_debug.yml +++ b/.github/workflows/windows_debug.yml @@ -63,26 +63,13 @@ jobs: setup-python: true cache: true - - name: Download Gstreamer - uses: suisei-cn/actions-download-file@v1.6.0 + - name: Setup GStreamer + uses: blinemedical/setup-gstreamer@v1 with: - url: https://s3-us-west-2.amazonaws.com/qgroundcontrol/dependencies/gstreamer-1.0-msvc-x86_64-1.18.1.msi - target: ${{ runner.temp }}\ - - - name: Download Gstreamer dev - uses: suisei-cn/actions-download-file@v1.6.0 - with: - url: https://s3-us-west-2.amazonaws.com/qgroundcontrol/dependencies/gstreamer-1.0-devel-msvc-x86_64-1.18.1.msi - target: ${{ runner.temp }}\ - - - name: Install Gstreamer - run: | - cmd /c start /wait msiexec /package ${{ runner.temp }}\gstreamer-1.0-msvc-x86_64-1.18.1.msi /passive ADDLOCAL=ALL - cmd /c start /wait msiexec /package ${{ runner.temp }}\gstreamer-1.0-devel-msvc-x86_64-1.18.1.msi /passive ADDLOCAL=ALL + version: 1.18.6 - name: Install Dependencies - run: | - python -m pip install ninja + run: python -m pip install ninja - name: Set up Visual Studio shell uses: ilammy/msvc-dev-cmd@v1 diff --git a/.github/workflows/windows_release.yml b/.github/workflows/windows_release.yml index 25dcac9f9fd..ba3f1ebb467 100644 --- a/.github/workflows/windows_release.yml +++ b/.github/workflows/windows_release.yml @@ -75,22 +75,8 @@ jobs: run: | 7z x jom.zip -ojom - - name: Download Gstreamer - uses: suisei-cn/actions-download-file@v1.6.0 - with: - url: https://s3-us-west-2.amazonaws.com/qgroundcontrol/dependencies/gstreamer-1.0-msvc-x86_64-1.18.1.msi - target: ${{ runner.temp }}\ - - - name: Download Gstreamer dev - uses: suisei-cn/actions-download-file@v1.6.0 - with: - url: https://s3-us-west-2.amazonaws.com/qgroundcontrol/dependencies/gstreamer-1.0-devel-msvc-x86_64-1.18.1.msi - target: ${{ runner.temp }}\ - - name: Install Gstreamer - run: | - cmd /c start /wait msiexec /package ${{ runner.temp }}\gstreamer-1.0-msvc-x86_64-1.18.1.msi /passive ADDLOCAL=ALL - cmd /c start /wait msiexec /package ${{ runner.temp }}\gstreamer-1.0-devel-msvc-x86_64-1.18.1.msi /passive ADDLOCAL=ALL + run: choco install --no-progress gstreamer gstreamer-devel --version=1.18.6 - name: Set up Visual Studio shell uses: ilammy/msvc-dev-cmd@v1 diff --git a/.gitmodules b/.gitmodules index 6f07a406e6c..acfcf388646 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "libs/OpenSSL/android_openssl"] path = libs/OpenSSL/android_openssl url = https://github.com/KDAB/android_openssl -[submodule "libs/qmlglsink/gst-plugins-good"] - path = libs/qmlglsink/gst-plugins-good - url = https://github.com/mavlink/gst-plugins-good.git [submodule "libs/xz-embedded"] path = libs/xz-embedded url = https://github.com/Auterion/xz-embedded.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e32216c1fff..72bef135fd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21.1 FATAL_ERROR) +cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) ####################################################### # Project Info diff --git a/libs/qmlglsink/CMakeLists.txt b/libs/qmlglsink/CMakeLists.txt index 4a0235f3e26..cd938a118cb 100644 --- a/libs/qmlglsink/CMakeLists.txt +++ b/libs/qmlglsink/CMakeLists.txt @@ -1,111 +1,256 @@ option(QGC_ENABLE_VIDEOSTREAMING "Enable video streaming" ON) if(QGC_ENABLE_VIDEOSTREAMING) - message(STATUS "Enabling video streaming support") + message(STATUS "Enabling video streaming support") - find_package(PkgConfig) + if(ANDROID) + set(GST_STATIC_BUILD ON) + else() + set(GST_STATIC_BUILD OFF) + endif() - set(GST_DEPENDENCIES - gstreamer-1.0>=1.16 - gstreamer-video-1.0>=1.16 - gstreamer-gl-1.0>=1.16 + if(LINUX) + set(GST_TARGET_VERSION 1.16) + elseif(ANDROID) + set(GST_TARGET_VERSION 1.18.5) + else() + set(GST_TARGET_VERSION 1.18) + endif() + + set(GST_TARGET_PLUGINS + gstcoreelements + gstplayback + gstudp + gstrtp + gstrtsp + gstx264 + gstlibav + gstsdpelem + gstvideoparsersbad + gstrtpmanager + gstisomp4 + gstmatroska + gstmpegtsdemux + gstopengl + gsttcp ) + if(ANDROID) + list(APPEND GST_TARGET_PLUGINS gstandroidmedia) + elseif(IOS) + list(APPEND GST_TARGET_PLUGINS gstapplemedia) + endif() - if(LINUX OR ANDROID) - list(APPEND GST_DEPENDENCIES egl) + set(GST_TARGET_MODULES + gstreamer-1.0 + gstreamer-gl-1.0 + gstreamer-video-1.0 + ) + if(LINUX) + list(APPEND GST_TARGET_MODULES egl) endif() - if(NOT ANDROID) - pkg_check_modules(GST - ${GST_DEPENDENCIES} - ) - endif() + set(GSTREAMER_ROOT) + if(WIN32) + if(EXISTS $ENV{GSTREAMER_ROOT_X86_64}) + set(GSTREAMER_ROOT $ENV{GSTREAMER_ROOT_X86_64}) + elseif(EXISTS $ENV{GSTREAMER_1_0_ROOT_MSVC_X86_64}) + set(GSTREAMER_ROOT $ENV{GSTREAMER_1_0_ROOT_MSVC_X86_64}) + endif() + elseif(MACOS) + set(GSTREAMER_ROOT "/Library/Frameworks/GStreamer.framework") + elseif(ANDROID) + if(${ANDROID_ABI} STREQUAL armeabi-v7a) + set(GSTREAMER_ROOT ${CMAKE_SOURCE_DIR}/gstreamer-1.0-android-universal-${GST_TARGET_VERSION}/armv7) + elseif(${ANDROID_ABI} STREQUAL arm64-v8a) + set(GSTREAMER_ROOT ${CMAKE_SOURCE_DIR}/gstreamer-1.0-android-universal-${GST_TARGET_VERSION}/arm64) + endif() + endif() - message(STATUS "GStreamer libs: ${GST_LIBRARIES}") - message(STATUS "GStreamer include dirs: ${GST_INCLUDE_DIRS}") - message(STATUS "GStreamer link dirs: ${GST_LIBRARY_DIRS}") - message(STATUS "GStreamer cflags: ${GST_CFLAGS}") - message(STATUS "GStreamer ldflags: ${GST_LDFLAGS}") - message(STATUS "GStreamer libs: ${GST_LIBS}") - - if(LINUX OR ANDROID) - message(STATUS "GStreamer egl libs: ${GST_EGL_LIBRARIES}") - message(STATUS "GStreamer egl include dirs: ${GST_EGL_INCLUDE_DIRS}") - message(STATUS "GStreamer egl link dirs: ${GST_EGL_LIBRARY_DIRS}") - message(STATUS "GStreamer egl cflags: ${GST_EGL_CFLAGS}") - message(STATUS "GStreamer egl ldflags: ${GST_EGL_LDFLAGS}") - message(STATUS "GStreamer egl libs: ${GST_EGL_LIBS}") + find_package(PkgConfig) + if(NOT EXISTS ${GSTREAMER_ROOT}) + pkg_check_modules(GSTREAMER gstreamer-1.0>=${GST_TARGET_VERSION}) + if(GSTREAMER_FOUND) + set(GSTREAMER_ROOT ${GSTREAMER_PREFIX}) + endif() endif() - message(STATUS "gst found ${GST_FOUND}") + if(EXISTS ${GSTREAMER_ROOT}) + message(STATUS "Gstreamer found") + cmake_print_variables(GSTREAMER_ROOT) - if(GST_FOUND) - message(STATUS "Building qmlglsink") - - find_package(Qt6 REQUIRED COMPONENTS Gui OpenGL) - find_package(OpenGL) - - qt_add_library(qmlglsink STATIC - gst-plugins-good/ext/qt6/gstplugin.cc - gst-plugins-good/ext/qt6/gstqml6glsink.cc - gst-plugins-good/ext/qt6/gstqml6glsink.h - gst-plugins-good/ext/qt6/gstqsg6glnode.cc - gst-plugins-good/ext/qt6/gstqsg6glnode.h - gst-plugins-good/ext/qt6/gstqt6element.cc - gst-plugins-good/ext/qt6/gstqt6elements.h - gst-plugins-good/ext/qt6/gstqt6gl.h - gst-plugins-good/ext/qt6/gstqt6glutility.cc - gst-plugins-good/ext/qt6/gstqt6glutility.h - gst-plugins-good/ext/qt6/qt6glitem.cc - gst-plugins-good/ext/qt6/qt6glitem.h - ) + find_package(Qt6 REQUIRED COMPONENTS Core) + qt_add_library(qmlglsink STATIC) - target_compile_definitions(qmlglsink PUBLIC QGC_GST_STREAMING) + if(GST_STATIC_BUILD) + list(APPEND PKG_CONFIG_ARGN --static) + endif() - if(LINUX) - option(USE_WAYLAND "Use Wayland instead of X11 for building GST" ON) - if(USE_WAYLAND) - message(STATUS "Using wayland for qmlglsink") - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_WAYLAND) - else() - message(STATUS "Using x11 for qmlglsink") - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_X11) - endif() - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_EGLFS HAVE_QT_QPA_HEADER) + list(PREPEND CMAKE_PREFIX_PATH ${GSTREAMER_ROOT}) + if(ANDROID) + set(ENV{PKG_CONFIG_PATH} "${GSTREAMER_ROOT}/lib/pkgconfig:${GSTREAMER_ROOT}/lib/gstreamer-1.0/pkgconfig:$ENV{PKG_CONFIG_PATH}") + list(APPEND PKG_CONFIG_ARGN + --define-prefix + --define-variable=prefix=${GSTREAMER_ROOT} + --define-variable=libdir=${GSTREAMER_ROOT}/lib + --define-variable=includedir=${GSTREAMER_ROOT}/include + ) + pkg_check_modules(GST + IMPORTED_TARGET + NO_CMAKE_ENVIRONMENT_PATH + ${GST_TARGET_MODULES} + ${GST_TARGET_PLUGINS} + ) + elseif(LINUX) + set(ENV{PKG_CONFIG_PATH} "${GSTREAMER_ROOT}/lib/pkgconfig:${GSTREAMER_ROOT}/x86_64-linux-gnu/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + pkg_check_modules(GST IMPORTED_TARGET ${GST_TARGET_MODULES}) elseif(MACOS) - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_MAC) - elseif(IOS) - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_IOS) - elseif(WIN32) - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_WIN32 HAVE_QT_QPA_HEADER) + set(ENV{PKG_CONFIG_PATH} "${GSTREAMER_ROOT}/Versions/Current/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + pkg_check_modules(GST IMPORTED_TARGET ${GST_TARGET_MODULES}) + else() + pkg_check_modules(GST IMPORTED_TARGET ${GST_TARGET_MODULES}) + endif() - target_link_libraries(qmlglsink - PUBLIC - OpenGL::GL - user32.lib - ) - elseif(ANDROID) - target_compile_definitions(qmlglsink PUBLIC HAVE_QT_ANDROID) + if(TARGET PkgConfig::GST) + target_link_libraries(qmlglsink PUBLIC PkgConfig::GST) + target_include_directories(qmlglsink PUBLIC ${GSTREAMER_ROOT}/include/gstreamer-1.0) + if(GST_STATIC_BUILD) + target_link_libraries(qmlglsink PUBLIC ${GST_STATIC_LINK_LIBRARIES}) + target_link_directories(qmlglsink PUBLIC ${GST_STATIC_LIBRARY_DIRS}) + target_link_options(qmlglsink PUBLIC ${GST_STATIC_LDFLAGS} ${GST_STATIC_LDFLAGS_OTHER}) + target_compile_options(qmlglsink PUBLIC ${GST_STATIC_CFLAGS} ${GST_STATIC_CFLAGS_OTHER}) + target_include_directories(qmlglsink PUBLIC ${GST_STATIC_INCLUDE_DIRS}) + if(ANDROID) + target_link_options(PkgConfig::GST INTERFACE "-Wl,-Bsymbolic") + endif() + else() + target_link_libraries(qmlglsink PUBLIC ${GST_LINK_LIBRARIES}) + target_link_directories(qmlglsink PUBLIC ${GST_LIBRARY_DIRS}) + target_link_options(qmlglsink PUBLIC ${GST_LDFLAGS} ${GST_LDFLAGS_OTHER}) + target_compile_options(qmlglsink PUBLIC ${GST_CFLAGS} ${GST_CFLAGS_OTHER}) + target_include_directories(qmlglsink PUBLIC ${GST_INCLUDE_DIRS}) + endif() endif() + else() + message(WARNING "Gstreamer Not Found") + endif() + + if(GST_FOUND) + message(STATUS "GST Modules Found") + target_sources(qmlglsink + PRIVATE + qt6/gstplugin.cc + qt6/gstqml6glsink.cc + qt6/gstqml6glsink.h + qt6/gstqsg6glnode.cc + qt6/gstqsg6glnode.h + qt6/gstqt6element.cc + qt6/gstqt6elements.h + qt6/gstqt6gl.h + qt6/gstqt6glutility.cc + qt6/gstqt6glutility.h + qt6/qt6glitem.cc + qt6/qt6glitem.h + ) - target_link_libraries(qmlglsink - PUBLIC + find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL Qml Quick) + target_link_libraries(qmlglsink + PUBLIC Qt6::Core - Qt6::OpenGL + Qt6::Gui Qt6::GuiPrivate - ${GST_LINK_LIBRARIES} + Qt6::OpenGL + Qt6::Qml + Qt6::Quick ) + if(WIN32) + find_package(OpenGL) + target_link_libraries(qmlglsink PUBLIC OpenGL::GL) + elseif(LINUX) + find_package(Qt6 COMPONENTS WaylandClient) + if(Qt6WaylandClient_FOUND) + target_link_libraries(qmlglsink PRIVATE Qt6::WaylandClient) + endif() + endif() - target_include_directories(qmlglsink PUBLIC ${GST_INCLUDE_DIRS}) - if (MSVC) - target_include_directories(qmlglsink PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/windows) + target_include_directories(qmlglsink PUBLIC qt6) + + target_compile_definitions(qmlglsink + PRIVATE + HAVE_QT_QPA_HEADER + QT_QPA_HEADER= + PUBLIC + QGC_GST_STREAMING + QGC_CMAKE_GST + ) + if(LINUX) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_X11) + if(EGL_FOUND) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_EGLFS) + endif() + if(Qt6WaylandClient_FOUND) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_WAYLAND) + endif() + elseif(ANDROID) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_ANDROID) + elseif(WIN32) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_WIN32) + elseif(MACOS) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_MAC) + elseif(IOS) + target_compile_definitions(qmlglsink PRIVATE HAVE_QT_IOS) + message(WARNING "qmlglsink not supported for IOS") + endif() + + if(UNIX) + target_compile_options(qmlglsink + PRIVATE + -Wno-unused-parameter + -Wno-implicit-fallthrough + -Wno-unused-private-field + ) + endif() + + message(STATUS "GStreamer version: ${GST_gstreamer-1.0_VERSION}") + message(STATUS "GStreamer prefix: ${GST_gstreamer-1.0_PREFIX}") + message(STATUS "GStreamer include dir: ${GST_gstreamer-1.0_INCLUDEDIR}") + message(STATUS "GStreamer libdir: ${GST_gstreamer-1.0_LIBDIR}") + if(GST_STATIC_BUILD) + list(REMOVE_DUPLICATES GST_STATIC_LIBRARIES) + list(REMOVE_DUPLICATES GST_STATIC_LINK_LIBRARIES) + list(REMOVE_DUPLICATES GST_STATIC_LIBRARY_DIRS) + list(REMOVE_DUPLICATES GST_STATIC_INCLUDE_DIRS) + list(REMOVE_DUPLICATES GST_STATIC_LDFLAGS) + list(REMOVE_DUPLICATES GST_STATIC_LDFLAGS_OTHER) + list(REMOVE_DUPLICATES GST_STATIC_CFLAGS) + list(REMOVE_DUPLICATES GST_STATIC_CFLAGS_OTHER) + message(VERBOSE "GStreamer static libs: ${GST_STATIC_LIBRARIES}") + message(VERBOSE "GStreamer static link libs: ${GST_STATIC_LINK_LIBRARIES}") + message(VERBOSE "GStreamer static link dirs: ${GST_STATIC_LIBRARY_DIRS}") + message(VERBOSE "GStreamer static include dirs: ${GST_STATIC_INCLUDE_DIRS}") + message(VERBOSE "GStreamer static ldflags: ${GST_STATIC_LDFLAGS}") + message(VERBOSE "GStreamer static ldflags other: ${GST_STATIC_LDFLAGS_OTHER}") + message(VERBOSE "GStreamer static cflags: ${GST_STATIC_CFLAGS}") + message(VERBOSE "GStreamer static cflags other: ${GST_STATIC_CFLAGS_OTHER}") else() - target_compile_options(qmlglsink - PRIVATE - -Wno-unused-parameter - -Wno-implicit-fallthrough - ) + list(REMOVE_DUPLICATES GST_LIBRARIES) + list(REMOVE_DUPLICATES GST_LINK_LIBRARIES) + list(REMOVE_DUPLICATES GST_LIBRARY_DIRS) + list(REMOVE_DUPLICATES GST_LDFLAGS) + list(REMOVE_DUPLICATES GST_LDFLAGS_OTHER) + list(REMOVE_DUPLICATES GST_INCLUDE_DIRS) + list(REMOVE_DUPLICATES GST_CFLAGS) + list(REMOVE_DUPLICATES GST_CFLAGS_OTHER) + message(VERBOSE "GStreamer libs: ${GST_LIBRARIES}") + message(VERBOSE "GStreamer link libs: ${GST_LINK_LIBRARIES}") + message(VERBOSE "GStreamer link dirs: ${GST_LIBRARY_DIRS}") + message(VERBOSE "GStreamer ldflags: ${GST_LDFLAGS}") + message(VERBOSE "GStreamer ldflags other: ${GST_LDFLAGS_OTHER}") + message(VERBOSE "GStreamer include dirs: ${GST_INCLUDE_DIRS}") + message(VERBOSE "GStreamer cflags: ${GST_CFLAGS}") + message(VERBOSE "GStreamer cflags other: ${GST_CFLAGS_OTHER}") endif() - endif() + else() + message(WARNING "GST Modules Not Found") + endif() else() message(STATUS "Video streaming disabled") endif() diff --git a/libs/qmlglsink/gst-plugins-good b/libs/qmlglsink/gst-plugins-good deleted file mode 160000 index 5ff0a6af0e1..00000000000 --- a/libs/qmlglsink/gst-plugins-good +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5ff0a6af0e1fd29bafc9f6644e728d24349d0ce7 diff --git a/libs/qmlglsink/qt6/gstplugin.cc b/libs/qmlglsink/qt6/gstplugin.cc new file mode 100644 index 00000000000..eb24046dc5e --- /dev/null +++ b/libs/qmlglsink/qt6/gstplugin.cc @@ -0,0 +1,62 @@ +/* + * 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 "gstqt6elements.h" +#include "qt6glitem.h" + +#include + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret = FALSE; +// TODO(zdanek) fix after switching to gstreamer 1.20.0+ +// original code from 1.20.0 +// ret |= GST_ELEMENT_REGISTER (qml6glsink, plugin); + ret |= gst_element_register_qml6glsink (plugin); + + return ret; +} + +static void registerMetatypes() +{ + qmlRegisterType ("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem"); +} + +Q_CONSTRUCTOR_FUNCTION(registerMetatypes) + +#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) diff --git a/libs/qmlglsink/qt6/gstqml6glsink.cc b/libs/qmlglsink/qt6/gstqml6glsink.cc new file mode 100644 index 00000000000..9c9ad386ef0 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqml6glsink.cc @@ -0,0 +1,576 @@ +/* + * 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: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 + +#include + +#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)); +G_BEGIN_DECLS gboolean G_PASTE(gst_element_register_, qml6glsink)(GstPlugin *plugin) +{ + { + { + qt6_element_init(plugin); + } + } + return gst_element_register(plugin, "qml6glsink", GST_RANK_NONE, (gst_qml6_gl_sink_get_type())); +} +G_END_DECLS; + +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 "); + + 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(); + 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 (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 (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, + GstStructure *structure) +{ + GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (navigation); + GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT, + structure); + // TODO(zdanek) this will be available from gstreamer 1.22 +} + +static void gst_qml6_gl_sink_navigation_interface_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_qml6_gl_sink_navigation_send_event; +} diff --git a/libs/qmlglsink/qt6/gstqml6glsink.h b/libs/qmlglsink/qt6/gstqml6glsink.h new file mode 100644 index 00000000000..4eeeddbced1 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqml6glsink.h @@ -0,0 +1,62 @@ +/* + * 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_QT6_SINK_H__ +#define __GST_QT6_SINK_H__ + +#include +#include +#include +#include +#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 +{ + /* */ + GstVideoSink parent; + + GstVideoInfo v_info; + GstBufferPool *pool; + + GstGLDisplay *display; + GstGLContext *context; + GstGLContext *qt_context; + + QSharedPointer widget; +}; + +GstQml6GLSink * gst_qml6_gl_sink_new (void); + +G_END_DECLS + +#endif /* __GST_QT6_SINK_H__ */ diff --git a/libs/qmlglsink/qt6/gstqsg6glnode.cc b/libs/qmlglsink/qt6/gstqsg6glnode.cc new file mode 100644 index 00000000000..cf9689984de --- /dev/null +++ b/libs/qmlglsink/qt6/gstqsg6glnode.cc @@ -0,0 +1,188 @@ +/* + * GStreamer + * Copyright (C) 2022 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. + */ + +#include "gstqsg6glnode.h" + +#include +#include +#include +#include + +#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))) { + // TODO(zdanek) - this happens when format of the video changes + //g_assert_not_reached (); + GST_ERROR ("Failed to map video frame"); + 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_); + } +} diff --git a/libs/qmlglsink/qt6/gstqsg6glnode.h b/libs/qmlglsink/qt6/gstqsg6glnode.h new file mode 100644 index 00000000000..0428fa5dea1 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqsg6glnode.h @@ -0,0 +1,58 @@ +/* + * GStreamer + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include + +#include "gstqt6gl.h" +#include +#include +#include +#include +#include + +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; +}; diff --git a/libs/qmlglsink/qt6/gstqt6element.cc b/libs/qmlglsink/qt6/gstqt6element.cc new file mode 100644 index 00000000000..aa934710312 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqt6element.cc @@ -0,0 +1,38 @@ +/* + * 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 "gstqt6elements.h" +#include "qt6glitem.h" +#include + +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 ("org.freedesktop.gstreamer.Qt6GLVideoItem", 1, 0, "GstGLQt6VideoItem"); + g_once_init_leave (&res, TRUE); + } +} diff --git a/libs/qmlglsink/qt6/gstqt6elements.h b/libs/qmlglsink/qt6/gstqt6elements.h new file mode 100644 index 00000000000..c22984216f9 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqt6elements.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Huawei Technologies Co., Ltd. + * @Author: Julian Bouzas + * + * 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 + +G_BEGIN_DECLS + +void qt6_element_init (GstPlugin * plugin); + +// TODO(zdanek) fix after switching to gstreamer 1.20.0+ +// original code from 1.20.0 +// GST_ELEMENT_REGISTER_DECLARE (qml6glsink); +// backported to: +extern "C" { gboolean gst_element_register_qml6glsink (GstPlugin * plugin); }; + +G_END_DECLS + +#endif /* __GST_QT6_ELEMENTS_H__ */ diff --git a/libs/qmlglsink/qt6/gstqt6gl.h b/libs/qmlglsink/qt6/gstqt6gl.h new file mode 100644 index 00000000000..a1babaad18c --- /dev/null +++ b/libs/qmlglsink/qt6/gstqt6gl.h @@ -0,0 +1,56 @@ +/* + * 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. + */ +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) +#include +#endif + +#include + +/* 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 + +/* 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 + +#if defined(QT_OPENGL_ES_2) +#include +#include +#endif /* defined(QT_OPENGL_ES_2) */ diff --git a/libs/qmlglsink/qt6/gstqt6glutility.cc b/libs/qmlglsink/qt6/gstqt6glutility.cc new file mode 100644 index 00000000000..013ded4bfef --- /dev/null +++ b/libs/qmlglsink/qt6/gstqt6glutility.cc @@ -0,0 +1,359 @@ +/* + * 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 +#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11) +#include +//#include +#endif +#if GST_GL_HAVE_PLATFORM_EGL && (defined (HAVE_QT_WAYLAND) || defined (HAVE_QT_EGLFS) || defined (HAVE_QT_ANDROID)) +#include +#ifdef HAVE_QT_QPA_HEADER +#include +#endif +//#include +#include +#endif + +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND) +#include +#endif +#if 0 +#if GST_GL_HAVE_WINDOW_VIV_FB +#include +#endif + +#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32) +#include +#include +#endif +#endif +#include + +#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 (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(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(); + 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 (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_gl_context_activate(*wrap_glcontext, FALSE); + 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 diff --git a/libs/qmlglsink/qt6/gstqt6glutility.h b/libs/qmlglsink/qt6/gstqt6glutility.h new file mode 100644 index 00000000000..ba436230da8 --- /dev/null +++ b/libs/qmlglsink/qt6/gstqt6glutility.h @@ -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 +#include + +#include +#include + +G_BEGIN_DECLS + +struct RenderJob : public QRunnable { + using Callable = std::function; + + 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__ */ diff --git a/libs/qmlglsink/qt6/meson.build b/libs/qmlglsink/qt6/meson.build new file mode 100644 index 00000000000..150b0b9f222 --- /dev/null +++ b/libs/qmlglsink/qt6/meson.build @@ -0,0 +1,145 @@ +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') +qt6_method = get_option('qt-method') + +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'], + method: qt6_method, required: qt6_option, static: host_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'], method: qt6_method, 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'], method: qt6_method, 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 diff --git a/libs/qmlglsink/qt6/qt6glitem.cc b/libs/qmlglsink/qt6/qt6glitem.cc new file mode 100644 index 00000000000..dfb012a05cd --- /dev/null +++ b/libs/qmlglsink/qt6/qt6glitem.cc @@ -0,0 +1,755 @@ +/* + * 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 "qt6glitem.h" +#include "gstqsg6glnode.h" +#include "gstqt6glutility.h" + +#include +#include +#include +#include +#include + +/** + * 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(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 (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); +} + +void +Qt6GLVideoItem::wheelEvent(QWheelEvent * event) +{ + // noop +} + +void +Qt6GLVideoItem::hoverEnterEvent(QHoverEvent *) +{ + mouseHovering = true; +} + +void +Qt6GLVideoItem::hoverLeaveEvent(QHoverEvent *) +{ + mouseHovering = false; +} + +void +Qt6GLVideoItem::hoverMoveEvent(QHoverEvent * event) +{ + // noop +} + +void +Qt6GLVideoItem::touchEvent(QTouchEvent * event) +{ + // noop +} + +void +Qt6GLVideoItem::sendMouseEvent(QMouseEvent * event, gboolean is_press) +{ + // noop +} + +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 ( + 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) { + GST_ERROR ("%p failed to retrieve Qt GL context", this); + 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; +} + diff --git a/libs/qmlglsink/qt6/qt6glitem.h b/libs/qmlglsink/qt6/qt6glitem.h new file mode 100644 index 00000000000..c5c5efcfcfd --- /dev/null +++ b/libs/qmlglsink/qt6/qt6glitem.h @@ -0,0 +1,128 @@ +/* + * 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 __QT6_GL_ITEM_H__ +#define __QT6_GL_ITEM_H__ + +#include +#include + +#include "gstqt6gl.h" +#include +#include +#include +#include +#include + +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 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 proxy; +}; + +#endif /* __QT_GL_ITEM_H__ */ diff --git a/qmlglsink.pri b/qmlglsink.pri index 30d226b77e3..0d9af09a5b9 100644 --- a/qmlglsink.pri +++ b/qmlglsink.pri @@ -17,17 +17,17 @@ LinuxBuild { } SOURCES += \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstplugin.cc \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqml6glsink.cc \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqsg6glnode.cc \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqt6element.cc \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqt6glutility.cc \ - libs/qmlglsink/gst-plugins-good/ext/qt6/qt6glitem.cc + libs/qmlglsink/qt6/gstplugin.cc \ + libs/qmlglsink/qt6/gstqml6glsink.cc \ + libs/qmlglsink/qt6/gstqsg6glnode.cc \ + libs/qmlglsink/qt6/gstqt6element.cc \ + libs/qmlglsink/qt6/gstqt6glutility.cc \ + libs/qmlglsink/qt6/qt6glitem.cc HEADERS += \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqml6glsink.h \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqsg6glnode.h \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqt6elements.h \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqt6gl.h \ - libs/qmlglsink/gst-plugins-good/ext/qt6/gstqt6glutility.h \ - libs/qmlglsink/gst-plugins-good/ext/qt6/qt6glitem.h + libs/qmlglsink/qt6/gstqml6glsink.h \ + libs/qmlglsink/qt6/gstqsg6glnode.h \ + libs/qmlglsink/qt6/gstqt6elements.h \ + libs/qmlglsink/qt6/gstqt6gl.h \ + libs/qmlglsink/qt6/gstqt6glutility.h \ + libs/qmlglsink/qt6/qt6glitem.h diff --git a/tools/setup/ubuntu.sh b/tools/setup/ubuntu.sh index 3c03d86e4b7..742ab7495b8 100644 --- a/tools/setup/ubuntu.sh +++ b/tools/setup/ubuntu.sh @@ -25,4 +25,5 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get -y --quiet --no-install-recommends i patchelf \ libxcb-xinerama0 \ libxkbcommon-x11-0 \ - libxcb-cursor0 \ \ No newline at end of file + libxcb-cursor0 \ + libdrm-dev