├── .gitignore ├── LICENSE ├── README.md ├── external └── dosis-font │ ├── Dosis-SemiBold.ttf │ ├── OFL.txt │ └── readme.txt ├── qtglviddemo.pro └── src ├── base ├── FifoWatch.cpp ├── FifoWatch.hpp ├── ScopeGuard.hpp ├── SystemStats.cpp ├── SystemStats.hpp ├── Utility.cpp ├── Utility.hpp ├── VideoInputDevicesModel.cpp └── VideoInputDevicesModel.hpp ├── main ├── Application.cpp ├── Application.hpp ├── Resources.qrc ├── UserInterface.qml └── main.cpp ├── mesh ├── CubeMesh.cpp ├── CubeMesh.hpp ├── Mesh.cpp ├── Mesh.hpp ├── QuadMesh.cpp ├── QuadMesh.hpp ├── SphereMesh.cpp ├── SphereMesh.hpp ├── TeapotMesh.cpp ├── TeapotMesh.hpp ├── TorusMesh.cpp └── TorusMesh.hpp ├── player ├── GStreamerCommon.hpp ├── GStreamerMediaSample.cpp ├── GStreamerMediaSample.hpp ├── GStreamerPlayer.cpp ├── GStreamerPlayer.hpp ├── GStreamerSignalDispatcher.cpp ├── GStreamerSignalDispatcher.hpp ├── GStreamerVideoRenderer.cpp └── GStreamerVideoRenderer.hpp ├── scene ├── Arcball.cpp ├── Arcball.hpp ├── Camera.cpp ├── Camera.hpp ├── GLResources.cpp ├── GLResources.hpp ├── Transform.cpp ├── Transform.hpp ├── VideoObjectItem.cpp ├── VideoObjectItem.hpp ├── VideoObjectModel.cpp └── VideoObjectModel.hpp └── videomaterial ├── GLVIVDirectTextureExtension.cpp ├── GLVIVDirectTextureExtension.hpp ├── VideoMaterial.cpp ├── VideoMaterial.hpp ├── VideoMaterialProviderGeneric.cpp ├── VideoMaterialProviderGeneric.hpp ├── VideoMaterialProviderVivante.cpp └── VideoMaterialProviderVivante.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.qmlc 3 | /build 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Qt 5 demo application for rendering videos on 3D objects integrated in QtQuick 2. 2 | =================================================================================== 3 | 4 | 5 | Overview 6 | -------- 7 | 8 | The qtglviddemo demo application shows how to render video frames on 3D objects, 9 | which in turn are integrated in a QML and QtQuick 2 based user interface. 10 | 11 | The video frames are produced by the GStreamer GstPlayer library. They are uploaded 12 | into OpenGL textures, which are then used on 3D meshes. These meshes are rendered 13 | in [QQuickFramebufferObject QtQuick 2 items](https://doc.qt.io/qt-5/qquickframebufferobject.html), 14 | and the items are composed by a PathView on screen. The 3D objects can be rotated 15 | with the mouse or with touch events. UI controls allow for adjusting several parameters 16 | such as opacity, scale, mesh type, etc. and for adding/removing objects. The video 17 | frames can come from local video files, network streams, or Video4Linux2 based 18 | video capture devices. 19 | 20 | Subtitles can be shown on screen. The subtitles can come either from the playing media 21 | itself, or from a FIFO if one is enabled in the configuration file. 22 | 23 | There is also special support built in for Vivante GPUs, specifically their direct 24 | texture extensions. This makes it possible to render high resolution videos smoothly 25 | on i.MX6 machines for example. 26 | 27 | 28 | License 29 | ------- 30 | 31 | This demo application is licensed under the [GNU General Public License, version 3](https://www.gnu.org/licenses/gpl-3.0.html). 32 | 33 | 34 | Dependencies 35 | ------------ 36 | 37 | * Qt 5.7 or newer 38 | * GStreamer 1.10 or newer 39 | * libudev 40 | 41 | 42 | Building and installing 43 | ----------------------- 44 | 45 | qtglviddemo uses qmake for building. qmake can do out-of-tree builds, and it is 46 | generally recommended to do so. 47 | 48 | First, create a build directory and go into it: 49 | 50 | mkdir build 51 | cd build 52 | 53 | Next, run qmake: 54 | 55 | qmake .. 56 | 57 | It is possible to enable additional features via qmake by appending to the CONFIG 58 | variable, like this: 59 | 60 | qmake .. CONFIG+="[features]" PREFIX="[install prefix]" 61 | 62 | Where `[features]` is a whitespace separated list of feature names and 63 | `[install prefix` is path that will be used as prefix when installing files. 64 | PREFIX is optional; if not specified, it will default to `/usr/local`. 65 | 66 | The following features are available: 67 | 68 | * vivante : Build the additional Vivante GPU support. 69 | * useImxV4L2 : When adding video streams from capture devices, use `imxv4l2://` 70 | URLs instead of `v4l2://` ones to make use of gstreamer-imx' imxv4l2videosrc 71 | element. 72 | 73 | Example on i.MX6 with [gstreamer-imx](https://github.com/Freescale/gstreamer-imx) installed: 74 | 75 | qmake .. CONFIG+="vivante useImxV4L2" 76 | 77 | Then, run: 78 | 79 | make 80 | 81 | Parallel make is supported by qmake generate Makefiles. 82 | 83 | The build can then be installed by running: 84 | 85 | make install 86 | 87 | This will copy the built binary to PREFIX/bin. So, if for example the prefix 88 | was configured in the qmake call to be `/opt/local`, then the binary will be 89 | copied to `/opt/local/bin`. 90 | 91 | 92 | Running the demo application 93 | ---------------------------- 94 | 95 | The application's command line arguments can be shown by running `qtglviddemo -h`: 96 | 97 | Usage: ./qtglviddemo [options] 98 | Qt5 OpenGL video demo 99 | 100 | Options: 101 | -h, --help Displays this help. 102 | -v, --version Displays version information. 103 | -w, --write-config-at-end Write configuration when program is ended 104 | -c, --config-file Configuration file to use 105 | -s, --splashscreen Filename of splashscreen to use 106 | 107 | The splashscreen must be in a format supported by Qt. JPEG and PNG are a good pick. 108 | 109 | Simply running qtglviddemo without any switches will run the application with 110 | a default configuration. 111 | 112 | Several Unix signals are caught for facilitating a graceful shutdown. These are 113 | SIGINT, SIGTERM, SIGQUIT. This makes it possible to for example end the program 114 | by pressing Ctrl+C on the console without an abrupt stop. 115 | 116 | 117 | Configuration 118 | ------------- 119 | 120 | qtglviddemo accepts configuration files in JSON format. At the top level, 121 | the following fields are supported: 122 | 123 | * fifoPath: This configures the path to the FIFO mentioned in the introduction 124 | If set to a valid not yet existing absolute filename, the user can then pipe 125 | data into this FIFO, and it will show up on screen if the 3D objects' subtitle 126 | source is set to "FIFO". If fifoPath refers to an existing file, no FIFO will 127 | be active, so make sure the path is valid and the filename is not already in use. 128 | 129 | * deviceNodeNameMap: Some devices, such as `mxc_v4l2` based i.MX6 video capture 130 | devices, do not have any model name. Since these are usually fixed-mounted, 131 | they will always have the same Linux device node. This makes it possible to 132 | define a name for them, which will be shown in the UI, making it easier for 133 | the user to know what device this is. That's the purpose of deviceNodeNameMap: 134 | it is an array of JSON objects, each object containing a "name" and a "node" 135 | value. Devices with nodes listed in this field will use the configured name 136 | instead of the `ID_MODEL` V4L2 string value. 137 | 138 | * items: The items/objects shown on screen. 139 | 140 | The items are configured through the user interface. The other two fields are 141 | configured manually. 142 | 143 | Here is an example of a configuration with 1 item, a FIFO path, and a device 144 | node name map that sets the name of `/dev/video0` to be "Built-in camera": 145 | 146 | { 147 | "items": [ 148 | { 149 | "cropRectangle": [ 150 | 0, 151 | 0, 152 | 100, 153 | 100 154 | ], 155 | "meshType": "cube", 156 | "opacity": 0.7, 157 | "rotation": [ 158 | -0.633301317691803, 159 | -0.16265153884887695, 160 | -0.755376398563385, 161 | -0.04336431622505188 162 | ], 163 | "scale": 1, 164 | "subtitleSource": "media", 165 | "url": "file:///home/root/test123.mkv" 166 | } 167 | ], 168 | "fifoPath": "/tmp/qtglviddemo-fifo", 169 | "deviceNodeNameMap" : [ 170 | { "name": "Built-in camera", "node": "/dev/video0" } 171 | ] 172 | } 173 | 174 | Configuration files are specified with the `-c` command line switch. If the 175 | `-w` switch is also present, then the configuration file will be updated 176 | when the demo application exits. 177 | 178 | 179 | How it works 180 | ------------ 181 | 182 | This is a high level explanation of how the demo application operates. 183 | 184 | The essential components are the `VideoObjectItem` QtQuick 2 item and the 185 | `VideoObjectModel` Qt list data model. The latter contains "descriptions", 186 | which describe an object, its parameters (mesh type, URL, opacity etc.). 187 | The former can render such an object. 188 | 189 | They are linked together in the QML UI through a [PathView](http://doc.qt.io/qt-5/qml-qtquick-pathview.html). 190 | Using the MVC pattern, the list of objects is implemented as the list of 191 | descriptions in the VideoObjectModel. There is one such model instantiated 192 | in the application, and made globally accessible in the QML script. 193 | The PathView's model property is set to this data model, meaning that 194 | PathView uses the descriptions as the data to render. PathView uses 195 | VideoObjectItem as a delegate; in QtQuick 2, delegates specify *how* 196 | an item in a data model is rendered. So, PathView creates VideoObjectItem 197 | instances for each description in the VideoObjectModel. If descriptions 198 | are added or removed, then VideoObjectItem instances are automatically 199 | created or destroyed. 200 | 201 | The video playback is part of VideoObjectItem. It is automatically started 202 | once the VideoObjectItem is created. The playback is done by the 203 | GStreamerPlayer C++ class, which hands out decoded video frames through 204 | the [GStreamer appsink element](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-appsink.html). 205 | The video frames are then fed into a "video material", which consists of 206 | a texture (which gets the video frame pixels uploaded into) and a set 207 | of parameters. 208 | 209 | Video capture devices are discovered by using libudev. Any devices that 210 | are hotplugged are also detected. 211 | 212 | 213 | Limitations 214 | ----------- 215 | 216 | * qtglviddemo does not support media without a video track. 217 | * Adding too many objects can worsen behavior and performance of the 218 | demo, depending on the platform. For example, trying to play multiple 219 | 1080p videos on an i.MX6 machine may not work properly, because this 220 | exceeds the bandwidth limitations of such machines. Also, since objects 221 | are rendered into FBOs, the GPU allocates memory for each FBO. With 222 | too many objects, the GPU may run out of memory (depending on how it 223 | is configured). 224 | * Only simple subtitles with [Pango markup](https://developer.gnome.org/pango/stable/PangoMarkupFormat.html) 225 | are supported. More advances formats, such as TTML or WebVTT, would require 226 | substantially more complex code, which is beyond the scope of this demo. 227 | -------------------------------------------------------------------------------- /external/dosis-font/Dosis-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dv1/qtglviddemo/751fa144814e8d61f1ddc18953e5f2388897c159/external/dosis-font/Dosis-SemiBold.ttf -------------------------------------------------------------------------------- /external/dosis-font/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Edgar Tolentino and Pablo Impallari (www.impallari.com|impallari@gmail.com), 2 | Copyright (c) 2011, Igino Marini. (www.ikern.com|mail@iginomarini.com), 3 | with Reserved Font Names "Dosis". 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: 7 | http://scripts.sil.org/OFL 8 | 9 | 10 | ----------------------------------------------------------- 11 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 12 | ----------------------------------------------------------- 13 | 14 | PREAMBLE 15 | The goals of the Open Font License (OFL) are to stimulate worldwide 16 | development of collaborative font projects, to support the font creation 17 | efforts of academic and linguistic communities, and to provide a free and 18 | open framework in which fonts may be shared and improved in partnership 19 | with others. 20 | 21 | The OFL allows the licensed fonts to be used, studied, modified and 22 | redistributed freely as long as they are not sold by themselves. The 23 | fonts, including any derivative works, can be bundled, embedded, 24 | redistributed and/or sold with any software provided that any reserved 25 | names are not used by derivative works. The fonts and derivatives, 26 | however, cannot be released under any other type of license. The 27 | requirement for fonts to remain under this license does not apply 28 | to any document created using the fonts or their derivatives. 29 | 30 | DEFINITIONS 31 | "Font Software" refers to the set of files released by the Copyright 32 | Holder(s) under this license and clearly marked as such. This may 33 | include source files, build scripts and documentation. 34 | 35 | "Reserved Font Name" refers to any names specified as such after the 36 | copyright statement(s). 37 | 38 | "Original Version" refers to the collection of Font Software components as 39 | distributed by the Copyright Holder(s). 40 | 41 | "Modified Version" refers to any derivative made by adding to, deleting, 42 | or substituting -- in part or in whole -- any of the components of the 43 | Original Version, by changing formats or by porting the Font Software to a 44 | new environment. 45 | 46 | "Author" refers to any designer, engineer, programmer, technical 47 | writer or other person who contributed to the Font Software. 48 | 49 | PERMISSION & CONDITIONS 50 | Permission is hereby granted, free of charge, to any person obtaining 51 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 52 | redistribute, and sell modified and unmodified copies of the Font 53 | Software, subject to the following conditions: 54 | 55 | 1) Neither the Font Software nor any of its individual components, 56 | in Original or Modified Versions, may be sold by itself. 57 | 58 | 2) Original or Modified Versions of the Font Software may be bundled, 59 | redistributed and/or sold with any software, provided that each copy 60 | contains the above copyright notice and this license. These can be 61 | included either as stand-alone text files, human-readable headers or 62 | in the appropriate machine-readable metadata fields within text or 63 | binary files as long as those fields can be easily viewed by the user. 64 | 65 | 3) No Modified Version of the Font Software may use the Reserved Font 66 | Name(s) unless explicit written permission is granted by the corresponding 67 | Copyright Holder. This restriction only applies to the primary font name as 68 | presented to the users. 69 | 70 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 71 | Software shall not be used to promote, endorse or advertise any 72 | Modified Version, except to acknowledge the contribution(s) of the 73 | Copyright Holder(s) and the Author(s) or with their explicit written 74 | permission. 75 | 76 | 5) The Font Software, modified or unmodified, in part or in whole, 77 | must be distributed entirely under this license, and must not be 78 | distributed under any other license. The requirement for fonts to 79 | remain under this license does not apply to any document created 80 | using the Font Software. 81 | 82 | TERMINATION 83 | This license becomes null and void if any of the above conditions are 84 | not met. 85 | 86 | DISCLAIMER 87 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 89 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 90 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 91 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 92 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 93 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 94 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 95 | OTHER DEALINGS IN THE FONT SOFTWARE. 96 | -------------------------------------------------------------------------------- /external/dosis-font/readme.txt: -------------------------------------------------------------------------------- 1 | Dosis font obtained from: 2 | https://fonts.google.com/specimen/Dosis 3 | -------------------------------------------------------------------------------- /qtglviddemo.pro: -------------------------------------------------------------------------------- 1 | PKGCONFIG += gstreamer-1.0 gstreamer-base-1.0 gstreamer-video-1.0 gstreamer-app-1.0 gstreamer-player-1.0 libudev 2 | CONFIG += qt c++11 link_pkgconfig moc 3 | QT += core qml quick quickcontrols2 widgets 4 | 5 | 6 | TARGET = qtglviddemo 7 | 8 | SOURCES += \ 9 | src/base/SystemStats.cpp \ 10 | src/base/Utility.cpp \ 11 | src/base/FifoWatch.cpp \ 12 | src/base/VideoInputDevicesModel.cpp \ 13 | src/mesh/QuadMesh.cpp \ 14 | src/mesh/CubeMesh.cpp \ 15 | src/mesh/TeapotMesh.cpp \ 16 | src/mesh/SphereMesh.cpp \ 17 | src/mesh/TorusMesh.cpp \ 18 | src/mesh/Mesh.cpp \ 19 | src/scene/GLResources.cpp \ 20 | src/scene/Transform.cpp \ 21 | src/scene/Camera.cpp \ 22 | src/scene/Arcball.cpp \ 23 | src/scene/VideoObjectModel.cpp \ 24 | src/scene/VideoObjectItem.cpp \ 25 | src/player/GStreamerPlayer.cpp \ 26 | src/player/GStreamerMediaSample.cpp \ 27 | src/player/GStreamerVideoRenderer.cpp \ 28 | src/player/GStreamerSignalDispatcher.cpp \ 29 | src/main/Application.cpp \ 30 | src/main/main.cpp \ 31 | src/videomaterial/VideoMaterial.cpp \ 32 | src/videomaterial/VideoMaterialProviderGeneric.cpp 33 | 34 | HEADERS += \ 35 | src/base/ScopeGuard.hpp \ 36 | src/base/VideoInputDevicesModel.hpp \ 37 | src/base/SystemStats.hpp \ 38 | src/base/FifoWatch.hpp \ 39 | src/base/Utility.hpp \ 40 | src/mesh/TeapotMesh.hpp \ 41 | src/mesh/SphereMesh.hpp \ 42 | src/mesh/TorusMesh.hpp \ 43 | src/mesh/Mesh.hpp \ 44 | src/mesh/CubeMesh.hpp \ 45 | src/mesh/QuadMesh.hpp \ 46 | src/scene/Arcball.hpp \ 47 | src/scene/GLResources.hpp \ 48 | src/scene/VideoObjectItem.hpp \ 49 | src/scene/Camera.hpp \ 50 | src/scene/Transform.hpp \ 51 | src/scene/VideoObjectModel.hpp \ 52 | src/player/GStreamerVideoRenderer.hpp \ 53 | src/player/GStreamerPlayer.hpp \ 54 | src/player/GStreamerMediaSample.hpp \ 55 | src/player/GStreamerSignalDispatcher.hpp \ 56 | src/player/GStreamerCommon.hpp \ 57 | src/main/Application.hpp \ 58 | src/videomaterial/VideoMaterial.hpp \ 59 | src/videomaterial/VideoMaterialProviderGeneric.hpp 60 | 61 | 62 | OTHER_FILES += src/main/UserInterface.qml 63 | 64 | RESOURCES += src/main/Resources.qrc 65 | 66 | INCLUDEPATH += src 67 | 68 | isEmpty(PREFIX) { 69 | PREFIX = /usr/local 70 | } 71 | target.path = $$PREFIX/bin 72 | INSTALLS += target 73 | 74 | useImxV4L2 { 75 | DEFINES += USE_IMX_V4L2 76 | } 77 | 78 | vivante { 79 | DEFINES += WITH_VIV_GPU 80 | SOURCES += src/videomaterial/GLVIVDirectTextureExtension.cpp src/videomaterial/VideoMaterialProviderVivante.cpp 81 | HEADERS += src/videomaterial/GLVIVDirectTextureExtension.hpp src/videomaterial/VideoMaterialProviderVivante.hpp 82 | } 83 | 84 | QMAKE_CXXFLAGS += -Wextra -Wall -std=c++11 -pedantic -fPIC -DPIC -O0 -g3 -ggdb 85 | QMAKE_LFLAGS += -fPIC -DPIC 86 | -------------------------------------------------------------------------------- /src/base/FifoWatch.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "FifoWatch.hpp" 30 | 31 | 32 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 33 | 34 | 35 | namespace qtglviddemo 36 | { 37 | 38 | 39 | FifoWatch::FifoWatch(QObject *p_parent) 40 | : QObject(p_parent) 41 | , m_fifoNotifier(nullptr) 42 | , m_fifoFd(-1) 43 | { 44 | } 45 | 46 | 47 | FifoWatch::~FifoWatch() 48 | { 49 | stop(); 50 | } 51 | 52 | 53 | QString FifoWatch::getPath() const 54 | { 55 | return m_fifoPath; 56 | } 57 | 58 | 59 | void FifoWatch::start(QString p_fifoPath, bool p_unlinkAtStop) 60 | { 61 | // Stop first to not collide with any ongoing watch. 62 | stop(); 63 | 64 | // Get the FIFO path as UTF-8 string that we can pass to mkfifo(). 65 | m_fifoPathAsUtf8 = p_fifoPath.toUtf8(); 66 | 67 | // Attempt to create and open the FIFO. If this fails, 68 | // the FIFO watch remains in the stopped state. 69 | if (mkfifo(m_fifoPathAsUtf8.constData(), S_IRUSR | S_IWUSR) == 0) 70 | { 71 | // Creating the FIFO succeeded. 72 | qCDebug(lcQtGLVidDemo) << "Successfully created FIFO" << p_fifoPath; 73 | 74 | m_unlinkAtStop = p_unlinkAtStop; 75 | 76 | // Open the newly created FIFO. 77 | // NOTE: We use the O_RDWR mode, not O_RDONLY. For the reason 78 | // why, see https://stackoverflow.com/a/580057/560774 79 | // We open the FIFO in nonblocking mode, which is necessary 80 | // for being able to watch the FIFO's file descriptor. 81 | m_fifoFd = open(m_fifoPathAsUtf8.constData(), O_RDWR | O_NONBLOCK); 82 | if (m_fifoFd > 0) 83 | { 84 | qCDebug(lcQtGLVidDemo) << "Creating socket notifier to listen to incoming data from FIFO" << p_fifoPath; 85 | m_fifoNotifier = new QSocketNotifier(m_fifoFd, QSocketNotifier::Read); 86 | 87 | // Store the FIFO path. Do this here, _after_ the watch 88 | // has been set up successfully, since a non-empty path 89 | // member is OK only if a watch is currently ongoing. 90 | // (See the getPath() documentation.) 91 | m_fifoPath = p_fifoPath; 92 | 93 | connect(m_fifoNotifier, &QSocketNotifier::activated, this, &FifoWatch::readFromFifo); 94 | } 95 | else 96 | { 97 | qCWarning(lcQtGLVidDemo) << "Could not open FIFO" << p_fifoPath << ":" << std::strerror(errno) << "- removing FIFO"; 98 | // Unlink the FIFO since it proved to be unusable. 99 | unlinkFifo(); 100 | } 101 | } 102 | else 103 | { 104 | // Creating the FIFO failed. This can happen for example 105 | // if a file already exist at the given location. 106 | qCWarning(lcQtGLVidDemo) << "Could not create FIFO" << p_fifoPath << ":" << std::strerror(errno); 107 | } 108 | } 109 | 110 | 111 | void FifoWatch::stop() 112 | { 113 | if (m_fifoNotifier != nullptr) 114 | { 115 | delete m_fifoNotifier; 116 | m_fifoNotifier = nullptr; 117 | } 118 | 119 | if (m_fifoFd > 0) 120 | { 121 | ::close(m_fifoFd); 122 | unlinkFifo(); 123 | m_fifoFd = -1; 124 | } 125 | 126 | m_fifoPath = ""; 127 | } 128 | 129 | 130 | void FifoWatch::readFromFifo(int) 131 | { 132 | assert(m_fifoFd > 0); 133 | 134 | char buf[1024]; 135 | ssize_t numBytesRead; 136 | 137 | QString line = ""; 138 | 139 | // Read out all currently available bytes from the FIFO. 140 | while (true) 141 | { 142 | numBytesRead = read(m_fifoFd, buf, sizeof(buf)); 143 | if (numBytesRead < 0) 144 | { 145 | // EAGAIN signals the end of the available data and is 146 | // therefore not considered an error. 147 | // (Standard behavior with non-blocking file descriptors.) 148 | if (errno != EAGAIN) 149 | { 150 | qCWarning(lcQtGLVidDemo) << "Could not read from FIFO:" << std::strerror(errno); 151 | return; 152 | } 153 | else 154 | break; 155 | } 156 | else if (numBytesRead > 0) 157 | line += QString::fromUtf8(buf, numBytesRead); 158 | } 159 | 160 | // Remove whitespace from the start and end of the received line. Here, 161 | // whitespace includes CR/LF line delimiters. We do not want to pass 162 | // these on. CR/LF in the middle of the line is OK, just not not at 163 | // the ends, because often, such a delimiter is added when pushing data 164 | // into the FIFO via a shell. Example: 165 | // 166 | // echo Hello > /tmp/my-fifo 167 | // 168 | // This would produce "Hello\n" without trimming. 169 | line = line.trimmed(); 170 | 171 | qCDebug(lcQtGLVidDemo) << "New line from FIFO:" << line; 172 | 173 | emit newFifoLine(line); 174 | } 175 | 176 | 177 | void FifoWatch::unlinkFifo() 178 | { 179 | if (m_unlinkAtStop) 180 | unlink(m_fifoPathAsUtf8.constData()); 181 | } 182 | 183 | 184 | } // namespace qtglviddemo end 185 | -------------------------------------------------------------------------------- /src/base/FifoWatch.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_FIFO_WATCH_HPP 21 | #define QTGLVIDDEMO_FIFO_WATCH_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | namespace qtglviddemo 29 | { 30 | 31 | 32 | /** 33 | * FIFO input watcher class. 34 | * 35 | * This class creates a named pipe (a FIFO) and observes it, 36 | * looking for any incoming text lines. If a line is received, 37 | * newFifoLine is emitted. 38 | * 39 | * If the FIFO already exists, this class does nothing. 40 | */ 41 | class FifoWatch 42 | : public QObject 43 | { 44 | Q_OBJECT 45 | public: 46 | /** 47 | * Constructor. 48 | * 49 | * This does not start the FIFO watch. It just sets 50 | * up internal states. Use start() for starting the 51 | * observations. 52 | * 53 | * @param p_parent Parent QObject 54 | */ 55 | FifoWatch(QObject *p_parent = nullptr); 56 | /** 57 | * Destructor 58 | * 59 | * Internally calls stop() automatically. 60 | */ 61 | ~FifoWatch(); 62 | 63 | /*** 64 | * Returns the path of the currently watched FIFO. 65 | * 66 | * If no FIFO watch is currently ongoing, this 67 | * returns an empty string. 68 | */ 69 | QString getPath() const; 70 | 71 | 72 | public slots: 73 | /** 74 | * Creates a FIFO at the given path and begins watching it for I/O activity. 75 | * 76 | * Internally calls stop() first, so any ongoing watch 77 | * will be ceased (and if in the prior start() call, 78 | * p_unlinkAtStop was set to true, the watched FIFO 79 | * will be deleted). 80 | * 81 | * Typically, FIFOs are created in the temporary 82 | * directory /tmp/ . So one valid path would be 83 | * /tmp/myfifo for example. 84 | * 85 | * If the given path already exists, stop() is called, 86 | * but nothing else happens. 87 | * 88 | * @param p_fifoPath Path of the new FIFO to create 89 | * and watch. 90 | * @param p_unlinkAtStop If true, the created FIFO is 91 | * unlinked (= deleted). 92 | */ 93 | void start(QString p_fifoPath, bool p_unlinkAtStop); 94 | /** 95 | * Stops any ongoing FIFO watch. 96 | * 97 | * If in the prior start() call, p_unlinkAtStop was set to 98 | * true, the watched FIFO will be deleted by stop(). 99 | * 100 | * If no FIFO watch is currently active, this function 101 | * does nothing. 102 | */ 103 | void stop(); 104 | 105 | 106 | signals: 107 | /** 108 | * This signal is emitted whenever a new line has been 109 | * received through the FIFO. (This implies that this 110 | * signal is only ever emitted if a FIFO watch is 111 | * currently active.) 112 | * 113 | * @param line Newly received line. 114 | */ 115 | void newFifoLine(QString line); 116 | 117 | 118 | private slots: 119 | void readFromFifo(int p_socket); 120 | 121 | private: 122 | void unlinkFifo(); 123 | 124 | QSocketNotifier *m_fifoNotifier; 125 | QString m_fifoPath; 126 | QByteArray m_fifoPathAsUtf8; 127 | int m_fifoFd; 128 | bool m_unlinkAtStop; 129 | }; 130 | 131 | 132 | } // namespace qtglviddemo end 133 | 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /src/base/ScopeGuard.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_SCOPE_GUARD_HPP 21 | #define QTGLVIDDEMO_SCOPE_GUARD_HPP 22 | 23 | #include 24 | #include 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | namespace detail 32 | { 33 | 34 | 35 | class ScopeGuardImpl 36 | { 37 | public: 38 | template < typename Func > 39 | explicit ScopeGuardImpl(Func &&p_func) 40 | : m_func(std::forward < Func > (p_func)) 41 | , m_dismissed(false) 42 | { 43 | } 44 | 45 | ~ScopeGuardImpl() 46 | { 47 | if (!m_dismissed) 48 | { 49 | #ifdef QT_NO_EXCEPTIONS 50 | m_func(); 51 | #else 52 | // Make sure exceptions never exit the destructor, otherwise 53 | // undefined behavior occurs. For details about this, see 54 | // http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM 55 | try 56 | { 57 | m_func(); 58 | } 59 | catch (...) 60 | { 61 | } 62 | #endif 63 | } 64 | } 65 | 66 | ScopeGuardImpl(ScopeGuardImpl &&p_other) 67 | : m_func(std::move(p_other.m_func)) 68 | , m_dismissed(p_other.m_dismissed) 69 | { 70 | p_other.m_dismissed = true; 71 | } 72 | 73 | /// Dismisses the scope guard, which will do nothing after this was called. 74 | void dismiss() const throw() 75 | { 76 | m_dismissed = true; 77 | } 78 | 79 | 80 | private: 81 | ScopeGuardImpl(ScopeGuardImpl const &) = delete; 82 | ScopeGuardImpl& operator = (ScopeGuardImpl const &) = delete; 83 | 84 | std::function < void() > m_func; 85 | mutable bool m_dismissed; 86 | }; 87 | 88 | 89 | } // namespace detail end 90 | 91 | 92 | template < typename Func > 93 | detail::ScopeGuardImpl makeScopeGuard(Func &&p_func) 94 | { 95 | return detail::ScopeGuardImpl(std::forward < Func > (p_func)); 96 | } 97 | 98 | 99 | } // namespace qtglviddemo end 100 | 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /src/base/SystemStats.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "SystemStats.hpp" 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | SystemStats::SystemStats() 32 | : m_normCpuUsage(0) 33 | , m_normMemoryUsage(0) 34 | , m_memoryUsage(0) 35 | , m_lastStatIdle(0) 36 | , m_lastStatTotal(0) 37 | { 38 | } 39 | 40 | 41 | void SystemStats::update() 42 | { 43 | { 44 | // Get CPU usage from /proc/stat. For understanding the fields, 45 | // see https://www.idnt.net/en-GB/kb/941772 46 | 47 | std::ifstream statFile("/proc/stat"); 48 | 49 | std::string line; 50 | std::getline(statFile, line); 51 | std::istringstream sstr(line); 52 | 53 | std::string label; 54 | int statUser, statNice, statSystem, statIdle, statIoWait, statIrq, statSoftIrq; 55 | sstr >> label >> statUser >> statNice >> statSystem >> statIdle >> statIoWait >> statIrq >> statSoftIrq; 56 | 57 | int total = statUser + statNice + statSystem + statIdle + statIoWait + statIrq + statSoftIrq; 58 | int totalDelta = total - m_lastStatTotal; 59 | 60 | int idleDelta = statIdle - m_lastStatIdle; 61 | 62 | m_lastStatTotal = total; 63 | m_lastStatIdle = statIdle; 64 | 65 | m_normCpuUsage = 1.0f - float(idleDelta) / float(totalDelta); 66 | } 67 | 68 | { 69 | long pagesize = sysconf(_SC_PAGESIZE); 70 | long totalMemory = sysconf(_SC_PHYS_PAGES) * pagesize; 71 | long freeMemory = sysconf(_SC_AVPHYS_PAGES) * pagesize; 72 | long usedMemory = totalMemory - freeMemory; 73 | 74 | m_memoryUsage = usedMemory; 75 | m_normMemoryUsage = double(usedMemory) / double(totalMemory); 76 | } 77 | } 78 | 79 | 80 | float SystemStats::getNormalizedCpuUsage() const 81 | { 82 | return m_normCpuUsage; 83 | } 84 | 85 | 86 | float SystemStats::getNormalizedMemoryUsage() const 87 | { 88 | return m_normMemoryUsage; 89 | } 90 | 91 | 92 | std::uint64_t SystemStats::getMemoryUsageInBytes() const 93 | { 94 | return m_memoryUsage; 95 | } 96 | 97 | 98 | } // namespace qtglviddemo end 99 | -------------------------------------------------------------------------------- /src/base/SystemStats.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_SYSTEMSTATS_HPP 21 | #define QTGLVIDDEMO_SYSTEMSTATS_HPP 22 | 23 | #include 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | /** 31 | * System stats measurement class. 32 | * 33 | * This class is used for getting system stats (CPU usage etc.). 34 | */ 35 | class SystemStats 36 | { 37 | public: 38 | /** 39 | * Constructor. 40 | * 41 | * Sets initial usage values to 0. 42 | */ 43 | SystemStats(); 44 | 45 | /** 46 | * Update measurements. 47 | * 48 | * Call this regularly to get the current measurements. 49 | */ 50 | void update(); 51 | 52 | /// Returns the current CPU usage in the 0..1 range (1 = CPU fully used). 53 | float getNormalizedCpuUsage() const; 54 | /// Returns the current memory usage in the 0..1 range (1 = memory full). 55 | float getNormalizedMemoryUsage() const; 56 | /// Returns the current memory usage in bytes. 57 | std::uint64_t getMemoryUsageInBytes() const; 58 | 59 | private: 60 | float m_normCpuUsage, m_normMemoryUsage; 61 | std::uint64_t m_memoryUsage; 62 | int m_lastStatIdle, m_lastStatTotal; 63 | }; 64 | 65 | 66 | } // namespace qtglviddemo end 67 | 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/base/Utility.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "Utility.hpp" 33 | 34 | 35 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 36 | 37 | 38 | namespace qtglviddemo 39 | { 40 | 41 | 42 | namespace 43 | { 44 | 45 | 46 | volatile sig_atomic_t signalFd = -1; 47 | 48 | void sigHandler(int) 49 | { 50 | if (signalFd != -1) 51 | { 52 | auto ret = write(signalFd, "1", 1); 53 | assert(ret >= 1); 54 | } 55 | } 56 | 57 | 58 | } // unnamed namespace end 59 | 60 | 61 | 62 | 63 | ScopedGstDeinit::~ScopedGstDeinit() 64 | { 65 | gst_deinit(); 66 | } 67 | 68 | 69 | 70 | 71 | ScopedSignalPipe::ScopedSignalPipe(QWindow *p_window) 72 | : m_notifier(nullptr) 73 | { 74 | assert(p_window != nullptr); 75 | 76 | m_pipeFds[0] = m_pipeFds[1] = -1; 77 | if (pipe(m_pipeFds) == -1) 78 | { 79 | qCCritical(lcQtGLVidDemo) << "Could not create signal pipe: " << std::strerror(errno); 80 | return; 81 | } 82 | signalFd = m_pipeFds[1]; 83 | 84 | m_notifier = new QSocketNotifier(m_pipeFds[0], QSocketNotifier::Read, nullptr); 85 | QObject::connect(m_notifier, &QSocketNotifier::activated, [this, p_window]() { 86 | if (signalFd < 0) 87 | return; 88 | 89 | char c; 90 | auto ret = read(m_pipeFds[0], &c, 1); 91 | if (ret >= 1) 92 | { 93 | qCDebug(lcQtGLVidDemo) << "Signal caught, quitting"; 94 | p_window->close(); 95 | } 96 | else if (ret < 0) 97 | { 98 | qCCritical(lcQtGLVidDemo) << "Error reading from signal pipe:" << std::strerror(errno) << " " << signalFd; 99 | p_window->close(); 100 | } 101 | }); 102 | } 103 | 104 | 105 | ScopedSignalPipe::~ScopedSignalPipe() 106 | { 107 | delete m_notifier; 108 | if (m_pipeFds[0] != -1) 109 | close(m_pipeFds[0]); 110 | if (m_pipeFds[1] != -1) 111 | close(m_pipeFds[1]); 112 | signalFd = -1; 113 | } 114 | 115 | 116 | 117 | 118 | struct ScopedSighandler::Priv 119 | { 120 | int m_signal; 121 | struct sigaction m_oldSigaction; 122 | bool m_initialized; 123 | 124 | Priv() 125 | : m_initialized(false) 126 | { 127 | } 128 | }; 129 | 130 | 131 | ScopedSighandler::ScopedSighandler(int p_signal) 132 | : m_priv(new Priv) 133 | { 134 | m_priv->m_signal = p_signal; 135 | 136 | // Retain the old signal handler so we can restore 137 | // it in the destructor. 138 | sigaction(p_signal, nullptr, &(m_priv->m_oldSigaction)); 139 | 140 | // Only set up a signal handler if this signal wasn't 141 | // marked as to be ignored. 142 | if (m_priv->m_oldSigaction.sa_handler != SIG_IGN) 143 | { 144 | struct sigaction newSigaction; 145 | sigemptyset(&newSigaction.sa_mask); 146 | newSigaction.sa_handler = sigHandler; 147 | newSigaction.sa_flags = SA_RESTART; 148 | sigfillset(&(newSigaction.sa_mask)); 149 | 150 | if (sigaction(p_signal, &newSigaction, nullptr) < 0) 151 | { 152 | qCCritical(lcQtGLVidDemo) << "Could not set up signal handler:" << std::strerror(errno); 153 | } 154 | else 155 | m_priv->m_initialized = true; 156 | } 157 | } 158 | 159 | 160 | ScopedSighandler::~ScopedSighandler() 161 | { 162 | // Restore the previous signal handler. 163 | if (m_priv->m_initialized) 164 | sigaction(m_priv->m_signal, &(m_priv->m_oldSigaction), nullptr); 165 | 166 | delete m_priv; 167 | } 168 | 169 | 170 | 171 | } // namespace qtglviddemo end 172 | -------------------------------------------------------------------------------- /src/base/Utility.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_UTILITY_HPP 21 | #define QTGLVIDDEMO_UTILITY_HPP 22 | 23 | class QWindow; 24 | class QSocketNotifier; 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | /** 32 | * Class for RAII-based GStreamer deinitialization. 33 | * 34 | * This is useful for making sure gst_deinit() is called even if an 35 | * exception is thrown for some reason. 36 | */ 37 | struct ScopedGstDeinit 38 | { 39 | /// Destructor. Calls gst_deinit(). 40 | ~ScopedGstDeinit(); 41 | }; 42 | 43 | 44 | /** 45 | * Sets up an unnamed pipe for the scoped signal handlers below. 46 | * 47 | * This is used together with ScopedSighandler. First, a ScopedSignalPipe 48 | * instance is created. Then, ScopedSighandler instances are set up. This 49 | * way, RAII-based Unix signal handler setup is possible. 50 | */ 51 | class ScopedSignalPipe 52 | { 53 | public: 54 | /** 55 | * Constructor. 56 | * 57 | * Creates an unnamed pipe and calls p_window's close() function 58 | * if the pipe receives a message from a signal handler. 59 | * 60 | * @param p_window Window to close in case of a Unix signal 61 | * emission. Must not be null. 62 | */ 63 | explicit ScopedSignalPipe(QWindow *p_window); 64 | /// Destructor. Cleans up the unnamed pipe. 65 | ~ScopedSignalPipe(); 66 | 67 | private: 68 | int m_pipeFds[2]; 69 | QSocketNotifier *m_notifier; 70 | }; 71 | 72 | 73 | /** 74 | * Sets up a signal handler that emits a message through the ScopedSignalPipe. 75 | */ 76 | class ScopedSighandler 77 | { 78 | public: 79 | /** 80 | * Constructor. Sets up a Unix signal handler for the given 81 | * signal. This handler emits a message through the ScopedSignalPipe 82 | * if it is triggered. 83 | * 84 | * If the given signal was previously marked as to be ignored 85 | * (via SIG_IGN), then neither the constructor nor the destructor 86 | * do anything. 87 | * 88 | * @param p_signal Signal to install. One example is SIGINT. 89 | */ 90 | explicit ScopedSighandler(int p_signal); 91 | /// Destructor. Removes the signal handler. 92 | ~ScopedSighandler(); 93 | 94 | 95 | private: 96 | struct Priv; 97 | Priv *m_priv; 98 | }; 99 | 100 | 101 | } // namespace qtglviddemo end 102 | 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /src/base/VideoInputDevicesModel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_VIDEO_INPUT_DEVICES_MODEL_HPP 21 | #define QTGLVIDDEMO_VIDEO_INPUT_DEVICES_MODEL_HPP 22 | 23 | #include 24 | #include 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | /** 32 | * Qt list data model containing a list of video input devices. 33 | * 34 | * The list contains entries with two roles: one for device node strings, 35 | * one for the user-readable name of the device. 36 | * 37 | * This list updates itself by listening to udev events. 38 | */ 39 | class VideoInputDevicesModel 40 | : public QAbstractListModel 41 | { 42 | Q_OBJECT 43 | 44 | public: 45 | typedef std::map < QString, QString > DeviceNodeNameMap; 46 | 47 | /** 48 | * Constructor. 49 | * 50 | * Sets up and starts udev based device monitoring. 51 | * 52 | * @param p_parent Parent QObject 53 | */ 54 | explicit VideoInputDevicesModel(QObject *p_parent = nullptr); 55 | /** 56 | * Destructor. 57 | * 58 | * Stops any ongoing udev based device monitoring. 59 | */ 60 | ~VideoInputDevicesModel(); 61 | 62 | enum Roles 63 | { 64 | DeviceNodeRole = Qt::UserRole + 1, 65 | DeviceNameRole 66 | }; 67 | 68 | /** 69 | * Retrieves all data roles for the given list entry. 70 | * 71 | * The returned value is a QVariantMap containing one entry for each 72 | * of the data roles of the entry at the given row (= the item index). 73 | */ 74 | Q_INVOKABLE QVariantMap get(int p_row) const; 75 | 76 | void setDeviceNodeNameMap(DeviceNodeNameMap p_deviceNodeNameMap); 77 | DeviceNodeNameMap const & getDeviceNodeNameMap() const; 78 | 79 | // QAbstractListModel overloads 80 | virtual QVariant data(QModelIndex const &p_index, int p_role = Qt::DisplayRole) const override; 81 | virtual Qt::ItemFlags flags(QModelIndex const &index) const override; 82 | virtual QVariant headerData(int p_section, Qt::Orientation p_orientation, int p_role = Qt::DisplayRole) const override; 83 | virtual QModelIndex parent(QModelIndex const &p_index) const override; 84 | virtual QHash < int, QByteArray > roleNames() const override; 85 | virtual int rowCount(QModelIndex const &p_parent = QModelIndex()) const override; 86 | 87 | 88 | private slots: 89 | void handleUDevNotification(); 90 | 91 | 92 | private: 93 | void enumerateDevices(); 94 | 95 | struct Priv; 96 | Priv *m_priv; 97 | }; 98 | 99 | 100 | } // namespace qtglviddemo end 101 | 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/main/Application.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_APPLICATION_HPP 21 | #define QTGLVIDDEMO_APPLICATION_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "base/SystemStats.hpp" 32 | #include "base/FifoWatch.hpp" 33 | #include "base/VideoInputDevicesModel.hpp" 34 | #include "scene/VideoObjectModel.hpp" 35 | 36 | 37 | namespace qtglviddemo 38 | { 39 | 40 | 41 | /** 42 | * Main application class. 43 | * 44 | * This inherits from QApplication instead of QCoreApplication or 45 | * QGuiApplication because in this demo we make use of QtQuick Controls 2 46 | * (which require QGuiApplication) and standard dialog windows such as the 47 | * QFileDialog (which require QApplication). 48 | * 49 | * In this class, QML context properties are installed, command line 50 | * arguments are parsed, configuration files are loaded/saved, the 51 | * FIFO watch is set up, and the data model that listens for V4L2 52 | * capture devices is created. 53 | */ 54 | class Application 55 | : public QApplication 56 | { 57 | Q_OBJECT 58 | Q_PROPERTY(VideoObjectModel *videoObjectModel READ getVideoObjectModel CONSTANT) 59 | Q_PROPERTY(VideoInputDevicesModel *videoInputDevicesModel READ getVideoInputDevicesModel CONSTANT) 60 | Q_PROPERTY(FifoWatch *fifoWatch READ getFifoWatch CONSTANT) 61 | Q_PROPERTY(QUrl splashscreenUrl READ getSplashscreenUrl CONSTANT) 62 | Q_PROPERTY(bool keepSplashscreen READ getKeepSplashscreen CONSTANT) 63 | 64 | public: 65 | /** 66 | * Constructor. 67 | * 68 | * Sets up the FIFO watch, the video input devices model, the video 69 | * object model, the QML engine, and the main window is shown. 70 | * 71 | * The configuration is NOT loaded here. Neither is the QML UI 72 | * loaded. These steps are done in prepare(). 73 | */ 74 | Application(int &argc, char **argv); 75 | /** 76 | * Destructor. 77 | * 78 | * If the FIFO watch is running, it is stopped here. 79 | * Also, if the command line arguments specified that the 80 | * configuration file needs to be updated when the program ends, 81 | * the current configuration is saved to file here. 82 | */ 83 | virtual ~Application(); 84 | 85 | /** 86 | * Prepares resources, states, data structures, and QML UI. 87 | * 88 | * This is called right before exec(). 89 | * 90 | * The configuration is loaded here if a config file is specified 91 | * in the arguments. This may also start the FIFO watch if a FIFO 92 | * path is specified in the configuration. 93 | * 94 | * The QML user interface is also loaded here, _after_ the 95 | * configuration file is loaded. This is important, because the 96 | * QML UI code may want to access settings that were specified 97 | * in the command line (splashcreen filename for example): 98 | * 99 | * Returns true if preparation finished successfully, false 100 | * otherwise. If this returns false, the program should exit. 101 | */ 102 | bool prepare(); 103 | 104 | /** 105 | * Parses the command line arguments that were forwarded to the constructor. 106 | * 107 | * The return value is an STL pair. The first value is true if 108 | * parsing finished successfully, false otherwise. The second 109 | * value is set to the return code that main() should return in 110 | * case parsing fails. 111 | * (The second value is not used if parsing succeeded.) 112 | */ 113 | std::pair < bool, int > parseCommandLineArgs(); 114 | 115 | Q_INVOKABLE void saveConfiguration(); 116 | 117 | /** 118 | * Measure system stats and get them formatted as a string. 119 | * 120 | * This contains CPU usage, memory usage, and framerate. 121 | */ 122 | Q_INVOKABLE QString getSystemStats() const; 123 | 124 | /// Retrieve a reference to the main application window. 125 | QQuickWindow & getMainWindow(); 126 | 127 | 128 | private slots: 129 | void onBeforeRendering(); 130 | void onAfterRendering(); 131 | 132 | 133 | private: 134 | // These getters are to be used by the properties only, 135 | // so they are made private. 136 | VideoObjectModel* getVideoObjectModel(); 137 | VideoInputDevicesModel* getVideoInputDevicesModel(); 138 | FifoWatch* getFifoWatch(); 139 | QUrl getSplashscreenUrl(); 140 | bool getKeepSplashscreen(); 141 | 142 | void loadConfiguration(); 143 | 144 | QString m_configFilename; 145 | bool m_saveConfigAtEnd; 146 | 147 | QString m_splashScreenFilename; 148 | bool m_keepSplashscreen; 149 | 150 | QQmlApplicationEngine m_engine; 151 | QQuickWindow *m_mainWindow; 152 | bool m_fullscreen; 153 | 154 | // Keeping the FIFO path separately because the FifoWatch getPath() 155 | // function returns an empty string when the FIFO watch is stopped. 156 | QString m_fifoPath; 157 | FifoWatch m_fifoWatch; 158 | 159 | VideoObjectModel m_videoObjectModel; 160 | VideoInputDevicesModel m_videoInputDevicesModel; 161 | 162 | mutable std::mutex m_sysStatsMutex; 163 | mutable SystemStats m_systemStats; 164 | GstClockTime m_beginRenderingTimestamp; 165 | GstClockTimeDiff m_renderingDuration; 166 | }; 167 | 168 | 169 | } // namespace qtglviddemo end 170 | 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/main/Resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | UserInterface.qml 4 | ../../external/dosis-font/Dosis-SemiBold.ttf 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | 23 | // NOTE: Even though the qmlRegisterType() documentation lists QQmlEngine 24 | // as the header, it alone is not enough, since the QML registration 25 | // functions are defined elsewhere. Including QtQml makes sure the necessary 26 | // heades are included. 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include "base/Utility.hpp" 33 | #include "player/GStreamerPlayer.hpp" 34 | #include "scene/VideoObjectItem.hpp" 35 | #include "scene/VideoObjectModel.hpp" 36 | #include "Application.hpp" 37 | 38 | 39 | Q_LOGGING_CATEGORY(lcQtGLVidDemo, "qtglviddemo", QtInfoMsg) 40 | 41 | 42 | 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | // Initialize GStreamer. 47 | if (!gst_init_check(&argc, &argv, nullptr)) 48 | return -1; 49 | 50 | // Set up scoped GStreamer deinitialization. We do this because 51 | // some GStreamer functionality such as its tracing subsystem 52 | // rely on the gst_deinit() function being called at the end 53 | // of the program's execution. 54 | qtglviddemo::ScopedGstDeinit gstdeinit; 55 | 56 | // Enforce the Material style for the user interface controls. 57 | QQuickStyle::setStyle("Material"); 58 | 59 | // Register some of our data types with QML so they can be used 60 | // in QML scripts. 61 | qmlRegisterUncreatableType < qtglviddemo::GStreamerPlayer > ("qtglviddemo", 1, 0, "GStreamerPlayer", "cannot create player object"); 62 | qmlRegisterUncreatableType < qtglviddemo::VideoObjectModel > ("qtglviddemo", 1, 0, "VideoObjectModel", "cannot create video object model"); 63 | qmlRegisterType < qtglviddemo::VideoObjectItem > ("qtglviddemo", 1, 0, "VideoObject"); 64 | 65 | // Set up Qt application object. 66 | qtglviddemo::Application app(argc, argv); 67 | 68 | // Add a font from the resources so we can use it in Qt Quick 2. 69 | QFontDatabase::addApplicationFont(":/Dosis-SemiBold.ttf"); 70 | 71 | // Parse command line arguments. If parsing failed (determined 72 | // by the boolean in retval.first), exit here with the given 73 | // return code (stored in retval.second). 74 | auto retval = app.parseCommandLineArgs(); 75 | if (!retval.first) 76 | return retval.second; 77 | 78 | // Prepare the application. This means: the loading configuration 79 | // file, loading the QML UI script etc. 80 | if (!app.prepare()) 81 | return -1; 82 | 83 | // Setup signal handlers and the corresponding unnamed pipe 84 | // so we can catch signals and gracefully exit. 85 | // (The signal handlers cause the main application window 86 | // to be closed, which in turn causes the application's 87 | // mainloop to stop and exit.) 88 | 89 | qtglviddemo::ScopedSignalPipe signalPipe(&(app.getMainWindow())); 90 | 91 | qtglviddemo::ScopedSighandler sigintHandler(SIGINT); 92 | qtglviddemo::ScopedSighandler sigtermHandler(SIGTERM); 93 | qtglviddemo::ScopedSighandler sigquitHandler(SIGQUIT); 94 | qtglviddemo::ScopedSighandler sighupHandler(SIGHUP); 95 | 96 | // Start the application's mainloop. 97 | return app.exec(); 98 | } 99 | -------------------------------------------------------------------------------- /src/mesh/CubeMesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "CubeMesh.hpp" 21 | 22 | 23 | namespace qtglviddemo 24 | { 25 | 26 | 27 | namespace 28 | { 29 | 30 | Mesh::Vertex const cubeVertices[4*6] = { 31 | { { 1, -1, -1 }, { 0, 0, -1 }, { 0, 1 } }, 32 | { { -1, -1, -1 }, { 0, 0, -1 }, { 1, 1 } }, 33 | { { 1, 1, -1 }, { 0, 0, -1 }, { 0, 0 } }, 34 | { { -1, 1, -1 }, { 0, 0, -1 }, { 1, 0 } }, 35 | 36 | { { -1, -1, 1 }, { 0, 0, 1 }, { 0, 1 } }, 37 | { { 1, -1, 1 }, { 0, 0, 1 }, { 1, 1 } }, 38 | { { -1, 1, 1 }, { 0, 0, 1 }, { 0, 0 } }, 39 | { { 1, 1, 1 }, { 0, 0, 1 }, { 1, 0 } }, 40 | 41 | { { -1, -1, -1 }, { 0, -1, 0 }, { 1, 0 } }, 42 | { { 1, -1, -1 }, { 0, -1, 0 }, { 0, 0 } }, 43 | { { -1, -1, 1 }, { 0, -1, 0 }, { 1, 1 } }, 44 | { { 1, -1, 1 }, { 0, -1, 0 }, { 0, 1 } }, 45 | 46 | { { 1, 1, -1 }, { 0, 1, 0 }, { 1, 0 } }, 47 | { { -1, 1, -1 }, { 0, 1, 0 }, { 0, 0 } }, 48 | { { 1, 1, 1 }, { 0, 1, 0 }, { 1, 1 } }, 49 | { { -1, 1, 1 }, { 0, 1, 0 }, { 0, 1 } }, 50 | 51 | { { -1, -1, 1 }, { -1, 0, 0 }, { 1, 1 } }, 52 | { { -1, 1, 1 }, { -1, 0, 0 }, { 1, 0 } }, 53 | { { -1, -1, -1 }, { -1, 0, 0 }, { 0, 1 } }, 54 | { { -1, 1, -1 }, { -1, 0, 0 }, { 0, 0 } }, 55 | 56 | { { 1, -1, -1 }, { 1, 0, 0 }, { 1, 1 } }, 57 | { { 1, 1, -1 }, { 1, 0, 0 }, { 1, 0 } }, 58 | { { 1, -1, 1 }, { 1, 0, 0 }, { 0, 1 } }, 59 | { { 1, 1, 1 }, { 1, 0, 0 }, { 0, 0 } } 60 | }; 61 | 62 | Mesh::Index const cubeIndices[3*2*6] = { 63 | 0, 1, 2, 2, 1, 3, 64 | 4, 5, 6, 6, 5, 7, 65 | 8, 9, 10, 10, 9, 11, 66 | 12, 13, 14, 14, 13, 15, 67 | 16, 17, 18, 18, 17, 19, 68 | 20, 21, 22, 22, 21, 23 69 | }; 70 | 71 | Mesh::MeshData const cubeData = { 72 | { cubeVertices, cubeVertices + sizeof(cubeVertices)/sizeof(Mesh::Vertex) }, 73 | { cubeIndices, cubeIndices + sizeof(cubeIndices)/sizeof(Mesh::Index) } 74 | }; 75 | 76 | } // unnamed namespace end 77 | 78 | 79 | Mesh::MeshData const & getCubeMeshData() 80 | { 81 | return cubeData; 82 | } 83 | 84 | 85 | } // namespace qtglviddemo end 86 | -------------------------------------------------------------------------------- /src/mesh/CubeMesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_CUBE_MESH_HPP 21 | #define QTGLVIDDEMO_CUBE_MESH_HPP 22 | 23 | #include "Mesh.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | Mesh::MeshData const & getCubeMeshData(); 31 | 32 | 33 | } // namespace qtglviddemo end 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/mesh/Mesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "Mesh.hpp" 21 | 22 | 23 | namespace qtglviddemo 24 | { 25 | 26 | 27 | Mesh::Mesh(QString p_type) 28 | : m_type(std::move(p_type)) 29 | , m_vertexBuffer(QOpenGLBuffer::VertexBuffer) 30 | , m_indexBuffer(QOpenGLBuffer::IndexBuffer) 31 | { 32 | } 33 | 34 | 35 | Mesh::~Mesh() 36 | { 37 | } 38 | 39 | 40 | QString const & Mesh::getType() const 41 | { 42 | return m_type; 43 | } 44 | 45 | 46 | void Mesh::setContents(Vertices const &p_vertices, Indices const &p_indices) 47 | { 48 | m_numVertices = p_vertices.size(); 49 | m_numIndices = p_indices.size(); 50 | 51 | // Fill the OpenGL buffer objects. Set their usage pattern to StaticDraw, 52 | // since we'll fill them rarely (usually only once), but will use them 53 | // for rendering very often. 54 | 55 | m_vertexBuffer.create(); 56 | m_vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw); 57 | m_vertexBuffer.bind(); 58 | m_vertexBuffer.allocate(&(p_vertices[0]), p_vertices.size() * sizeof(Vertices::value_type)); 59 | m_vertexBuffer.release(); 60 | 61 | m_indexBuffer.create(); 62 | m_indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw); 63 | m_indexBuffer.bind(); 64 | m_indexBuffer.allocate(&(p_indices[0]), p_indices.size() * sizeof(Indices::value_type)); 65 | m_indexBuffer.release(); 66 | } 67 | 68 | 69 | void Mesh::setContents(MeshData const &p_meshData) 70 | { 71 | setContents(p_meshData.m_vertices, p_meshData.m_indices); 72 | } 73 | 74 | 75 | void Mesh::clearContents() 76 | { 77 | m_vertexBuffer.destroy(); 78 | m_indexBuffer.destroy(); 79 | } 80 | 81 | 82 | bool Mesh::hasContents() const 83 | { 84 | return m_vertexBuffer.isCreated() && m_indexBuffer.isCreated(); 85 | } 86 | 87 | 88 | void Mesh::bindBuffers() 89 | { 90 | m_vertexBuffer.bind(); 91 | m_indexBuffer.bind(); 92 | } 93 | 94 | 95 | void Mesh::releaseBuffers() 96 | { 97 | m_vertexBuffer.release(); 98 | m_indexBuffer.release(); 99 | } 100 | 101 | 102 | std::size_t Mesh::getNumVertices() const 103 | { 104 | return m_numVertices; 105 | } 106 | 107 | 108 | std::size_t Mesh::getNumIndices() const 109 | { 110 | return m_numIndices; 111 | } 112 | 113 | 114 | } // namespace qtglviddemo end 115 | -------------------------------------------------------------------------------- /src/mesh/Mesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_MESH_HPP 21 | #define QTGLVIDDEMO_MESH_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | 30 | namespace qtglviddemo 31 | { 32 | 33 | 34 | /** 35 | * Class for 3D mesh data stored in OpenGL buffers. 36 | * 37 | * This is a 3D mesh that is used for OpenGL rendering. It is meant for modern 38 | * OpenGL, and therefore contains only triangles (no quads, polygons etc.). 39 | * One triangle = 3 indices. 40 | * 41 | * Meshes have a "type". This is a string that is used to identify the type 42 | * of the mesh contents. This type must be associated with any contents that 43 | * are used for this mesh. For example, of cube mesh data is provided in the 44 | * setContents() call, the mesh type must be "cube". The mesh type is used 45 | * for looking up the correct mesh data when creating meshes from config files. 46 | */ 47 | class Mesh 48 | { 49 | public: 50 | struct Vertex 51 | { 52 | float position[3]; 53 | float normal[3]; 54 | float uv[2]; 55 | }; 56 | 57 | typedef std::uint16_t Index; 58 | 59 | typedef std::vector < Vertex > Vertices; 60 | typedef std::vector < Index > Indices; 61 | 62 | struct MeshData 63 | { 64 | Vertices m_vertices; 65 | Indices m_indices; 66 | }; 67 | 68 | /** 69 | * Constructor. 70 | * 71 | * This creates the OpenGL buffers, but does not fill them with data. 72 | * 73 | * Note that a valid OpenGL context must be present when this is called. 74 | * 75 | * @param p_type String denoting the type of the mesh. 76 | */ 77 | explicit Mesh(QString p_type); 78 | /** 79 | * Destructor. 80 | * 81 | * Destroys the OpenGL buffer objects. 82 | * 83 | * Note that a valid OpenGL context must be present when this is called. 84 | */ 85 | ~Mesh(); 86 | 87 | // Mesh instances may not be copied. 88 | Mesh(Mesh const &) = delete; 89 | Mesh& operator = (Mesh const &) = delete; 90 | 91 | /// Returns the mesh type string. 92 | QString const & getType() const; 93 | 94 | /** 95 | * Sets the contents of this mesh. 96 | * 97 | * This fills the OpenGL vertex and index buffers that were created by 98 | * the constructor. Neither p_vertices nor p_indices may be empty. 99 | * If the OpenGL buffers are already filled, their old content is 100 | * discarded, and the new one filled in. 101 | * 102 | * The number of indices must be an integer multiple of 3, since three 103 | * indices make up one triangle. 104 | * 105 | * Note that a valid OpenGL context must be present when this is called. 106 | * 107 | * @param p_vertices STL vector containing vertex data. 108 | * @param p_indices STL vector containing index data. 109 | */ 110 | void setContents(Vertices const &p_vertices, Indices const &p_indices); 111 | /** 112 | * setContents() convenience overload. 113 | * 114 | * This sets vertex and index data from a MeshData instance. 115 | * 116 | * Note that a valid OpenGL context must be present when this is called. 117 | * 118 | * @param p_meshData MeshData instance containg the vertex and index data. 119 | */ 120 | void setContents(MeshData const &p_meshData); 121 | /** 122 | * Clears the contents of the OpenGL buffer objects. 123 | * 124 | * Note that a valid OpenGL context must be present when this is called. 125 | */ 126 | void clearContents(); 127 | /// Returns true if the OpenGL buffers are filled with vertex/index data. 128 | bool hasContents() const; 129 | 130 | /** 131 | * Binds the OpenGL vertex and index buffers to the current OpenGL context. 132 | * 133 | * This must be called prior to issuing drawing calls so the GPU reads 134 | * vertex data from the right place. 135 | * 136 | * Note that a valid OpenGL context must be present when this is called. 137 | */ 138 | void bindBuffers(); 139 | /** 140 | * Releases (= unbinds) the OpenGL vertex and index buffers from the current 141 | * OpenGL context. 142 | * 143 | * If the buffers weren't bound earlier, this does nothing. 144 | */ 145 | void releaseBuffers(); 146 | 147 | /// Return the number of vertices that were set in the setContents() call. 148 | std::size_t getNumVertices() const; 149 | /// Return the number of indices that were set in the setContents() call. 150 | std::size_t getNumIndices() const; 151 | 152 | 153 | private: 154 | QString const m_type; 155 | QOpenGLBuffer m_vertexBuffer, m_indexBuffer; 156 | std::size_t m_numVertices, m_numIndices; 157 | }; 158 | 159 | // Define a unique_ptr for the mesh for ownership management. 160 | typedef std::unique_ptr < Mesh > MeshUPtr; 161 | 162 | 163 | } // namespace qtglviddemo end 164 | 165 | 166 | #endif 167 | 168 | -------------------------------------------------------------------------------- /src/mesh/QuadMesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "QuadMesh.hpp" 21 | 22 | 23 | namespace qtglviddemo 24 | { 25 | 26 | 27 | namespace 28 | { 29 | 30 | Mesh::Vertex const quadVertices[4*2] = { 31 | { { -1, -1, 0 }, { 0, 0, 1 }, { 0, 1 } }, 32 | { { 1, -1, 0 }, { 0, 0, 1 }, { 1, 1 } }, 33 | { { -1, 1, 0 }, { 0, 0, 1 }, { 0, 0 } }, 34 | { { 1, 1, 0 }, { 0, 0, 1 }, { 1, 0 } }, 35 | { { -1, -1, 0 }, { 0, 0, -1 }, { 1, 1 } }, 36 | { { 1, -1, 0 }, { 0, 0, -1 }, { 0, 1 } }, 37 | { { -1, 1, 0 }, { 0, 0, -1 }, { 1, 0 } }, 38 | { { 1, 1, 0 }, { 0, 0, -1 }, { 0, 0 } } 39 | }; 40 | 41 | Mesh::Index const quadIndices[6*2] = { 42 | 0, 1, 2, 43 | 2, 1, 3, 44 | 4, 6, 5, 45 | 5, 6, 7 46 | }; 47 | 48 | Mesh::MeshData const quadData = { 49 | { quadVertices, quadVertices + sizeof(quadVertices)/sizeof(Mesh::Vertex) }, 50 | { quadIndices, quadIndices + sizeof(quadIndices)/sizeof(Mesh::Index) } 51 | }; 52 | 53 | } // unnamed namespace end 54 | 55 | 56 | Mesh::MeshData const & getQuadMeshData() 57 | { 58 | return quadData; 59 | } 60 | 61 | 62 | } // namespace qtglviddemo end 63 | -------------------------------------------------------------------------------- /src/mesh/QuadMesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_QUAD_MESH_HPP 21 | #define QTGLVIDDEMO_QUAD_MESH_HPP 22 | 23 | #include "Mesh.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | Mesh::MeshData const & getQuadMeshData(); 31 | 32 | 33 | } // namespace qtglviddemo end 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/mesh/SphereMesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include "SphereMesh.hpp" 23 | 24 | 25 | namespace qtglviddemo 26 | { 27 | 28 | 29 | Mesh::MeshData calculateSphereMeshData(float const p_radius, unsigned int const p_latitudeTesselation, unsigned int const p_longitudeTesselation) 30 | { 31 | /* Calculate a UV sphere. p_longitudeTesselation specifies how many 32 | * vertical ring segments shall be calculated. p_latitudeTesselation 33 | * does the same for the horizontal segments. 34 | * 35 | * The vertices of the very first vertical segment are duplicated 36 | * and used for the last segment. This is because at the first longitude, 37 | * the first and last triangles of the sphere mesh meet, and while 38 | * these vertices share the same position and normal vector, they 39 | * have different UV coordinates. 40 | * 41 | * Three indices per triangle are calculated. Triangles are pairwise 42 | * grouped to form a quad. So, for each quad, there are 2*3 = 6 indices. 43 | * Also, the number of quads in horizontal and vertical direction is 44 | * one less than the number of horizontal and vertical ring segments. 45 | * This is because a triangle is bounded by segments, like this: 46 | * 47 | * [vertex] [triangle] [vertex] [triangle] [vertex] 48 | */ 49 | 50 | // A sphere mesh with less tesselation makes no sense. 51 | assert(p_latitudeTesselation >= 3); 52 | assert(p_longitudeTesselation >= 3); 53 | 54 | // Calculate number of vertices and indices. 55 | // Use p_longitudeTesselation+1 instead of p_longitudeTesselation to 56 | // make room for the extra vertical segment. 57 | // And, as said about the indices above, the number of quads in 58 | // horizontal and vertical direction would be p_latitudeTesselation-1 59 | // and p_longitudeTesselation-1 , but since we add an extra segment, 60 | // we use p_longitudeTesselation-1+1 -> p_longitudeTesselation instead. 61 | Mesh::MeshData meshData; 62 | meshData.m_vertices.resize(p_latitudeTesselation * (p_longitudeTesselation + 1)); 63 | meshData.m_indices.resize((p_latitudeTesselation - 1) * p_longitudeTesselation * 2 * 3); 64 | 65 | // Calculate vertices by generating latitude rings 66 | for (unsigned int latitude = 0; latitude < p_latitudeTesselation; ++latitude) 67 | { 68 | float latitudeF = float(latitude) / float(p_latitudeTesselation - 1); 69 | float latAngle = latitudeF * M_PI; 70 | float y = std::cos(latAngle); 71 | float latitudeRadius = std::sin(latAngle); 72 | 73 | // Go through all the longitudes, producing one vertex per each. 74 | // These make up the latitude ring segment. 75 | for (unsigned int longitude = 0; longitude < (p_longitudeTesselation + 1); ++longitude) 76 | { 77 | Mesh::Vertex & vtx = meshData.m_vertices[latitude * (p_longitudeTesselation + 1) + longitude]; 78 | 79 | float longitudeF = float(longitude) / float(p_longitudeTesselation); 80 | float longAngle = longitudeF * 2.0 * M_PI; 81 | float x = std::cos(longAngle); 82 | float z = std::sin(longAngle); 83 | vtx.position[0] = x * latitudeRadius; 84 | vtx.position[1] = y * p_radius; 85 | vtx.position[2] = z * latitudeRadius; 86 | vtx.normal[0] = x; 87 | vtx.normal[1] = y; 88 | vtx.normal[2] = z; 89 | vtx.uv[0] = 1.0f - longitudeF; // Necessary because otherwise the texture is flipped in X direction. 90 | vtx.uv[1] = latitudeF; 91 | } 92 | } 93 | 94 | for (unsigned int latitude = 0; latitude < (p_latitudeTesselation - 1); ++latitude) 95 | { 96 | unsigned int latAVtxOfs = (p_longitudeTesselation + 1) * latitude; 97 | unsigned int latBVtxOfs = (p_longitudeTesselation + 1) * (latitude + 1); 98 | 99 | for (unsigned int longitude = 0; longitude < p_longitudeTesselation; ++longitude) 100 | { 101 | Mesh::Index * idx = &(meshData.m_indices[(latitude * p_longitudeTesselation + longitude) * 2 * 3]); 102 | 103 | idx[0] = latAVtxOfs + longitude; 104 | idx[1] = latAVtxOfs + longitude + 1; 105 | idx[2] = latBVtxOfs + longitude; 106 | 107 | idx[3] = latBVtxOfs + longitude; 108 | idx[4] = latAVtxOfs + longitude + 1; 109 | idx[5] = latBVtxOfs + longitude + 1; 110 | } 111 | } 112 | 113 | return meshData; 114 | } 115 | 116 | 117 | } // namespace qtglviddemo end 118 | -------------------------------------------------------------------------------- /src/mesh/SphereMesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_SPHERE_MESH_HPP 21 | #define QTGLVIDDEMO_SPHERE_MESH_HPP 22 | 23 | #include "Mesh.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | Mesh::MeshData calculateSphereMeshData(float const p_radius, unsigned int const p_latitudeTesselation, unsigned int const p_longitudeTesselation); 31 | 32 | 33 | } // namespace qtglviddemo end 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/mesh/TeapotMesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_TEAPOT_MESH_HPP 21 | #define QTGLVIDDEMO_TEAPOT_MESH_HPP 22 | 23 | #include "Mesh.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | // Mesh data for the "Utah teapot" 3D object. 31 | Mesh::MeshData const & getTeapotMeshData(); 32 | 33 | 34 | } // namespace qtglviddemo end 35 | 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/mesh/TorusMesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include "TorusMesh.hpp" 23 | 24 | 25 | namespace qtglviddemo 26 | { 27 | 28 | 29 | Mesh::MeshData calculateTorusMeshData(float const p_majorRadius, float const p_minorRadius, unsigned int const p_majorTesselation, unsigned int const p_minorTesselation) 30 | { 31 | /* Calculate a torus. p_majorRadius is the overall radius of the torus, 32 | * while p_minorRadius is the radius of the torus' tube. Likewise, 33 | * p_majorTesselation and p_minorTesselation specify the level of 34 | * tesselation across the torus and the torus tube, respectively. 35 | * 36 | * The torus mesh is made of ring segments that make up the tube 37 | * sections. 38 | * 39 | * The vertices of the very first section are duplicated and used 40 | * for the last section. This is because at the first section, 41 | * the first and last triangles of the sphere mesh meet, and while 42 | * these vertices share the same position and normal vector, they 43 | * have different UV coordinates. 44 | * 45 | * Three indices per triangle are calculated. Triangles are pairwise 46 | * grouped to form a quad. So, for each quad, there are 2*3 = 6 indices. 47 | * Quads are inserted between torus sections. 48 | */ 49 | 50 | // A torus mesh with less tesselation makes no sense. 51 | assert(p_majorTesselation >= 4); 52 | assert(p_minorTesselation >= 3); 53 | 54 | Mesh::MeshData meshData; 55 | meshData.m_vertices.resize((p_majorTesselation + 1) * p_minorTesselation); 56 | meshData.m_indices.resize(p_majorTesselation * p_minorTesselation * 2 * 3); 57 | 58 | // Calculate vertices by generating sections 59 | for (unsigned int majorI = 0; majorI < (p_majorTesselation + 1); ++majorI) 60 | { 61 | unsigned baseOfs = majorI * p_minorTesselation; 62 | 63 | float majorF = float(majorI) / float(p_majorTesselation); 64 | float majorAngle = majorF * 2.0f * M_PI; 65 | float majorX = std::cos(majorAngle); 66 | float majorZ = std::sin(majorAngle); 67 | 68 | // Calculate the sections of the current section 69 | for (unsigned int minorI = 0; minorI < p_minorTesselation; ++minorI) 70 | { 71 | Mesh::Vertex & vtx = meshData.m_vertices[baseOfs + minorI]; 72 | 73 | float minorF = float(minorI) / float(p_minorTesselation); 74 | float minorAngle = minorF * 2.0f * M_PI; 75 | 76 | // Reverse X direction for correct backface culling 77 | // and V texture coordinate direction. 78 | float minorX = -std::cos(minorAngle); 79 | float minorY = std::sin(minorAngle); 80 | 81 | // The torus tube section ring is oriented along the 82 | // normal vector. Compute the X and Z position coordinates 83 | // by applying this "section radius" to the overall torus 84 | // radius. 85 | vtx.position[0] = majorX * (p_majorRadius + minorX * p_minorRadius); 86 | vtx.position[1] = minorY * p_minorRadius; 87 | vtx.position[2] = majorZ * (p_majorRadius + minorX * p_minorRadius); 88 | vtx.normal[0] = majorX * minorX; 89 | vtx.normal[1] = minorY; 90 | vtx.normal[2] = majorZ * minorX; 91 | // Make the texture repeat itself 4 times, otherwise it 92 | // looks too "stretched". Also flip the coordinate 93 | // direction, otherwise the texture looks flipped in 94 | // the X direction. 95 | vtx.uv[0] = (1.0f - majorF) * 4.0f; 96 | vtx.uv[1] = minorF; 97 | } 98 | } 99 | 100 | for (unsigned int majorI = 0; majorI < p_majorTesselation; ++majorI) 101 | { 102 | unsigned int ringAVtxOfs = p_minorTesselation * majorI; 103 | unsigned int ringBVtxOfs = p_minorTesselation * (majorI + 1); 104 | 105 | for (unsigned int minorI = 0; minorI < p_minorTesselation; ++minorI) 106 | { 107 | Mesh::Index * idx = &(meshData.m_indices[(majorI * p_minorTesselation + minorI) * 2 * 3]); 108 | unsigned int minorI2 = (minorI + 1) % p_minorTesselation; 109 | 110 | idx[0] = ringAVtxOfs + minorI; 111 | idx[1] = ringBVtxOfs + minorI; 112 | idx[2] = ringAVtxOfs + minorI2; 113 | 114 | idx[3] = ringAVtxOfs + minorI2; 115 | idx[4] = ringBVtxOfs + minorI; 116 | idx[5] = ringBVtxOfs + minorI2; 117 | } 118 | } 119 | 120 | return meshData; 121 | } 122 | 123 | 124 | } // namespace qtglviddemo end 125 | -------------------------------------------------------------------------------- /src/mesh/TorusMesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_TORUS_MESH_HPP 21 | #define QTGLVIDDEMO_TORUS_MESH_HPP 22 | 23 | #include "Mesh.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | Mesh::MeshData calculateTorusMeshData(float const p_majorRadius, float const p_minorRadius, unsigned int const p_majorTesselation, unsigned int const p_minorTesselation); 31 | 32 | 33 | } // namespace qtglviddemo end 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/player/GStreamerCommon.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GSTREAMER_COMMON_HPP 21 | #define QTGLVIDDEMO_GSTREAMER_COMMON_HPP 22 | 23 | #include 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | typedef std::function < void() > NewVideoFrameAvailableCB; 31 | 32 | 33 | } // namespace qtglviddemo end 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/player/GStreamerMediaSample.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "GStreamerMediaSample.hpp" 21 | 22 | 23 | namespace qtglviddemo 24 | { 25 | 26 | 27 | GStreamerMediaSample::GStreamerMediaSample(GstSample *p_sample, bool p_sampleHasNewCaps) 28 | : m_sample(p_sample) 29 | , m_sampleHasNewCaps(p_sampleHasNewCaps) 30 | { 31 | } 32 | 33 | 34 | GStreamerMediaSample::GStreamerMediaSample(GStreamerMediaSample && p_other) 35 | : m_sample(p_other.m_sample) 36 | , m_sampleHasNewCaps(p_other.m_sampleHasNewCaps) 37 | { 38 | // Mark the other instance as moved 39 | p_other.m_sample = nullptr; 40 | } 41 | 42 | 43 | GStreamerMediaSample::~GStreamerMediaSample() 44 | { 45 | if (m_sample != nullptr) 46 | gst_sample_unref(m_sample); 47 | } 48 | 49 | 50 | GStreamerMediaSample& GStreamerMediaSample::operator = (GStreamerMediaSample && p_other) 51 | { 52 | // If there is currently a media sample present, get rid of it first 53 | if (m_sample != nullptr) 54 | gst_sample_unref(m_sample); 55 | 56 | m_sample = p_other.m_sample; 57 | m_sampleHasNewCaps = p_other.m_sampleHasNewCaps; 58 | 59 | // Mark the other instance as moved 60 | p_other.m_sample = nullptr; 61 | 62 | return *this; 63 | } 64 | 65 | 66 | GstSample* GStreamerMediaSample::getSample() 67 | { 68 | return m_sample; 69 | } 70 | 71 | 72 | bool GStreamerMediaSample::sampleHasNewCaps() const 73 | { 74 | return m_sampleHasNewCaps; 75 | } 76 | 77 | 78 | } // namespace qtglviddemo end 79 | -------------------------------------------------------------------------------- /src/player/GStreamerMediaSample.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GSTREAMER_MEDIA_SAMPLE_HPP 21 | #define QTGLVIDDEMO_GSTREAMER_MEDIA_SAMPLE_HPP 22 | 23 | #include 24 | #include 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | /** 32 | * Class containing a GstSample. 33 | * 34 | * A GstSample is returned by GStreamer appsinks. It contains a GstBuffer and 35 | * extra metadata such as caps and segment information. For this demo program, 36 | * the GstBuffer and the caps are of interest. The caps describe the format of 37 | * the data in the GstBuffer. 38 | * 39 | * The reason why this class exists is to encapsulate GstSamples in a lightweight 40 | * object that unrefs the GstSample in the destructor, adhering to the RAII 41 | * principle. It also has a flag that denotes if this sample's caps are new, or 42 | * if these are the same caps a previous GstSample had. This is useful to check 43 | * if something has to be reconfigured (OpenGL textures for example if the 44 | * GstSample contains a video frame and the width and height changed). 45 | */ 46 | class GStreamerMediaSample 47 | { 48 | public: 49 | /** 50 | * Constructor. 51 | * 52 | * @param p_sample Sample pointer to store in this object. 53 | * @param p_sampleHasNewCaps If true, then the sample's caps are new 54 | * (= they are different compared to a previous sample's caps). 55 | */ 56 | explicit GStreamerMediaSample(GstSample *p_sample, bool p_sampleHasNewCaps); 57 | /// Move constructor. 58 | GStreamerMediaSample(GStreamerMediaSample && p_other); 59 | /** 60 | * Destructor. 61 | * 62 | * Unrefs the GstSample set by the constructor (if the pointer wasn't null). 63 | */ 64 | ~GStreamerMediaSample(); 65 | 66 | /// Move assignment operator. 67 | GStreamerMediaSample& operator = (GStreamerMediaSample && p_other); 68 | 69 | // This class is movable, but not copyable. 70 | GStreamerMediaSample(GStreamerMediaSample const & p_other) = delete; 71 | GStreamerMediaSample& operator = (GStreamerMediaSample const & p_other) = delete; 72 | 73 | /** 74 | * Returns the GstSample pointer from this object. 75 | * 76 | * The return value may be null. This can happen if an attempt was made 77 | * to pull a sample and there was none. 78 | * 79 | * This function does not call gst_sample_ref(). 80 | */ 81 | GstSample* getSample(); 82 | /** 83 | * Returns true if the sample's caps are new (= they are different 84 | * compared to a previous sample's caps). 85 | */ 86 | bool sampleHasNewCaps() const; 87 | 88 | private: 89 | GstSample *m_sample; 90 | bool m_sampleHasNewCaps; 91 | }; 92 | 93 | 94 | } // namespace qtglviddemo end 95 | 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/player/GStreamerPlayer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "base/ScopeGuard.hpp" 28 | #include "GStreamerPlayer.hpp" 29 | #include "GStreamerVideoRenderer.hpp" 30 | #include "GStreamerSignalDispatcher.hpp" 31 | 32 | 33 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 34 | 35 | 36 | namespace qtglviddemo 37 | { 38 | 39 | 40 | GStreamerPlayer::GStreamerPlayer(NewVideoFrameAvailableCB p_newVideoFrameAvailableCB, QObject *p_parent) 41 | : QObject(p_parent) 42 | , m_gstplayer(nullptr) 43 | , m_gstdispatcher(nullptr) 44 | , m_gstvidrenderer(nullptr) 45 | , m_subtitleAppsink(nullptr) 46 | , m_state(State::Stopped) 47 | , m_lastSampleCaps(nullptr) 48 | { 49 | // Set up the core GstPlayer instance. Create the associated signal 50 | // dispatcher and video renderer and pass them to the GstPlayer. 51 | m_gstdispatcher = createGStreamerSignalDispatcher(this); 52 | m_gstvidrenderer = createGStreamerVideoRenderer(std::move(p_newVideoFrameAvailableCB)); 53 | m_gstplayer = gst_player_new(m_gstvidrenderer, m_gstdispatcher); 54 | 55 | // Set up the subtitle appsink. 56 | m_subtitleAppsink = gst_element_factory_make("appsink", "subtitleAppsink"); 57 | // Create and connect the GLib signal callback for new subtitles. 58 | GstFlowReturn (*newSubtitleSampleCB)(GstElement *, gpointer) = [](GstElement *, gpointer p_userData) -> GstFlowReturn { 59 | return reinterpret_cast < GStreamerPlayer* > (p_userData)->onNewSubtitleSample(); 60 | }; 61 | g_signal_connect(G_OBJECT(m_subtitleAppsink), "new-sample", G_CALLBACK(newSubtitleSampleCB), this); 62 | // appsink does not emit signals by default, so we need to enable it. 63 | gst_app_sink_set_emit_signals(GST_APP_SINK(m_subtitleAppsink), TRUE); 64 | 65 | // There is currently no GstPlayer API to set the subtitle sink, so we 66 | // have to manually do that by acquiring a reference to the GstPlayer's 67 | // playbin and setting its text-sink property. playbin takes ownership 68 | // over the subtitle appsink; we don't have to worry about unref'ing it. 69 | GstElement *playbin = gst_player_get_pipeline(m_gstplayer); 70 | g_object_set(G_OBJECT(playbin), "text-sink", m_subtitleAppsink, "flags", gint(0x55), nullptr); 71 | gst_object_unref(GST_OBJECT(playbin)); 72 | 73 | // Connect the GstPlayer signals. These are emitted from the main Qt 74 | // thread (the signal dispatcher takes care of that). 75 | g_object_connect( 76 | m_gstplayer, 77 | "swapped-signal::end-of-stream", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerEndOfStream), this, 78 | "swapped-signal::state-changed", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerStateChanged), this, 79 | "swapped-signal::duration-changed", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerDurationChanged), this, 80 | "swapped-signal::position-updated", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerPositionUpdated), this, 81 | "swapped-signal::buffering", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerBufferingChanged), this, 82 | "swapped-signal::media-info-updated", G_CALLBACK(GStreamerPlayer::staticOnGstPlayerMediaInfoUpdated), this, 83 | nullptr 84 | ); 85 | 86 | // Enable video and subtitle tracks, but disable audio, since at this 87 | // moment we do not care for audio output. 88 | gst_player_set_video_track_enabled(m_gstplayer, true); 89 | gst_player_set_audio_track_enabled(m_gstplayer, false); 90 | gst_player_set_subtitle_track_enabled(m_gstplayer, true); 91 | } 92 | 93 | 94 | GStreamerPlayer::~GStreamerPlayer() 95 | { 96 | // Stop the GstPlayer to make sure no new GLib signal emissions 97 | // are dispatched. Also disconnect all of its signals to make sure 98 | // they don't try to invoke callbacks related to this GStreamerPlayer 99 | // instance. 100 | // Stopping the GstPlayer also sets an internal flag that makes sure 101 | // any signals that were queued by the dispatcher and might still be 102 | // in the Qt event loop won't do anything. 103 | if (m_gstplayer != nullptr) 104 | { 105 | qCDebug(lcQtGLVidDemo) << "Stopping gstplayer and disconnecting GLib signals"; 106 | gst_player_stop(m_gstplayer); 107 | g_signal_handlers_disconnect_by_data(m_gstplayer, this); 108 | } 109 | 110 | // Unref the GstPlayer. 111 | // Note that this may not always destroy the gstplayer instance right 112 | // away. If there is an unemitted signal in the event loop remaining, 113 | // then the gstplayer is destroyed once this signal is torn down, 114 | // because these signals hold references to the gstplayer. (The signals 115 | // are marshaled into the event loop by GStreamerSignalDispatcher.) 116 | // But this is okay, since there are no adverse effects of a lingering 117 | // gstplayer instance. 118 | if (m_gstplayer != nullptr) 119 | { 120 | qCDebug(lcQtGLVidDemo) << "Unref'ing gstplayer"; 121 | gst_object_unref(GST_OBJECT(m_gstplayer)); 122 | } 123 | 124 | // Unref any lingering last sample caps. 125 | if (m_lastSampleCaps != nullptr) 126 | gst_caps_unref(m_lastSampleCaps); 127 | } 128 | 129 | 130 | void GStreamerPlayer::setUrl(QUrl p_url) 131 | { 132 | if (m_url != p_url) 133 | { 134 | m_url = std::move(p_url); 135 | 136 | QByteArray urlCStr = m_url.toString().toUtf8(); 137 | gst_player_set_uri(m_gstplayer, urlCStr.data()); 138 | 139 | emit urlChanged(); 140 | } 141 | } 142 | 143 | 144 | QUrl GStreamerPlayer::getUrl() const 145 | { 146 | return m_url; 147 | } 148 | 149 | 150 | GStreamerPlayer::State GStreamerPlayer::getState() const 151 | { 152 | return m_state; 153 | } 154 | 155 | 156 | int GStreamerPlayer::getPosition() const 157 | { 158 | GstClockTime pos = gst_player_get_position(m_gstplayer); 159 | return GST_CLOCK_TIME_IS_VALID(pos) ? int(pos / GST_MSECOND) : int(-1); 160 | } 161 | 162 | 163 | int GStreamerPlayer::getDuration() const 164 | { 165 | GstClockTime dur = gst_player_get_duration(m_gstplayer); 166 | // Use std::max() to avoid fringe cases where a duration of than 1 ms length is reported. 167 | return GST_CLOCK_TIME_IS_VALID(dur) ? std::max(int(dur / GST_MSECOND), 1) : int(-1); 168 | } 169 | 170 | 171 | bool GStreamerPlayer::isSeekable() const 172 | { 173 | if (m_gstplayer == nullptr) 174 | return false; 175 | 176 | GstPlayerMediaInfo *mediaInfo = gst_player_get_media_info(m_gstplayer); 177 | if (mediaInfo == nullptr) 178 | return false; 179 | 180 | bool seekable = gst_player_media_info_is_seekable(mediaInfo); 181 | g_object_unref(G_OBJECT(mediaInfo)); 182 | 183 | return seekable; 184 | } 185 | 186 | 187 | QString GStreamerPlayer::getSubtitle() const 188 | { 189 | return m_subtitle; 190 | } 191 | 192 | 193 | void GStreamerPlayer::setSinkCaps(GstCaps *p_sinkCaps) 194 | { 195 | setGStreamerVideoRendererSinkCaps(m_gstvidrenderer, p_sinkCaps); 196 | } 197 | 198 | 199 | void GStreamerPlayer::setSinkCapsFromVideoFormats(std::vector < GstVideoFormat > const &p_videoFormats) 200 | { 201 | // Produce caps with unrestricted width/height/framerate and a list of format strings. 202 | // Example: if p_videoFormats contains GST_VIDEO_FORMAT_RGBA and GST_VIDEO_FORMAT_I420, 203 | // this produces: "video/x-raw; width: [ 1, 2147483647 ], height: [ 1, 2147483647 ], 204 | // framerate: [ 0/1, 2147483647/1 ], format: { RGBA, I420 }". 205 | 206 | assert(!p_videoFormats.empty()); 207 | 208 | GstCaps *caps = gst_caps_new_simple( 209 | "video/x-raw", 210 | "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, 211 | "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, 212 | "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, 213 | nullptr 214 | ); 215 | 216 | GValue format = G_VALUE_INIT; 217 | GValue formats = G_VALUE_INIT; 218 | g_value_init(&format, G_TYPE_STRING); 219 | g_value_init(&formats, GST_TYPE_LIST); 220 | for (GstVideoFormat fmt : p_videoFormats) 221 | { 222 | g_value_set_static_string(&format, gst_video_format_to_string(fmt)); 223 | gst_value_list_append_value(&formats, &format); 224 | } 225 | gst_caps_set_value(caps, "format", &formats); 226 | g_value_unset(&format); 227 | g_value_unset(&formats); 228 | 229 | setSinkCaps(caps); 230 | 231 | gst_caps_unref(caps); 232 | } 233 | 234 | 235 | void GStreamerPlayer::play() 236 | { 237 | gst_player_play(m_gstplayer); 238 | } 239 | 240 | 241 | void GStreamerPlayer::pause() 242 | { 243 | gst_player_pause(m_gstplayer); 244 | } 245 | 246 | 247 | void GStreamerPlayer::stop() 248 | { 249 | gst_player_stop(m_gstplayer); 250 | } 251 | 252 | 253 | void GStreamerPlayer::seek(int p_position) 254 | { 255 | gst_player_seek(m_gstplayer, GstClockTime(p_position) * GST_MSECOND); 256 | } 257 | 258 | 259 | GStreamerMediaSample GStreamerPlayer::pullVideoSample() 260 | { 261 | GstSample *sample; 262 | bool hasNewCaps = false; 263 | 264 | sample = gst_app_sink_try_pull_sample(GST_APP_SINK_CAST(getGStreamerVideoRendererVideoAppsink(m_gstvidrenderer)), 0); 265 | 266 | if (sample != nullptr) 267 | { 268 | // Check if the caps changed, and if so, record it. This 269 | // information is then passed to the new media sample below. 270 | GstCaps *caps = gst_sample_get_caps(sample); 271 | hasNewCaps = (m_lastSampleCaps == nullptr) || !gst_caps_is_equal(m_lastSampleCaps, caps); 272 | // Remember the current caps so we can compare them against 273 | // future caps to detect caps changes. 274 | gst_caps_replace(&m_lastSampleCaps, caps); 275 | } 276 | 277 | return GStreamerMediaSample(sample, hasNewCaps); 278 | } 279 | 280 | 281 | GstFlowReturn GStreamerPlayer::onNewSubtitleSample() 282 | { 283 | GstSample *subtitleSample = gst_app_sink_pull_sample(GST_APP_SINK(m_subtitleAppsink)); 284 | if (subtitleSample == nullptr) 285 | return GST_FLOW_OK; 286 | 287 | auto cleanup = makeScopeGuard([&]() { gst_sample_unref(subtitleSample); }); 288 | 289 | GstBuffer *buffer = gst_sample_get_buffer(subtitleSample); 290 | if (buffer == nullptr) 291 | return GST_FLOW_OK; 292 | 293 | gsize data_size = gst_buffer_get_size(buffer); 294 | if (data_size == 0) 295 | return GST_FLOW_OK; 296 | 297 | GstMapInfo map_info; 298 | gst_buffer_map(buffer, &map_info, GST_MAP_READ); 299 | 300 | // Subtitle data is provided as UTF-8 text. 301 | QString htmlSubtitle = QString::fromUtf8(reinterpret_cast < char const * > (map_info.data), map_info.size); 302 | 303 | gst_buffer_unmap(buffer, &map_info); 304 | 305 | // The incoming data is provided typically in the Pango text attribute 306 | // markup format. This format contains a subset of HTML, including 307 | // HTML entities like ä . For more details about the markup, go to: 308 | // https://developer.gnome.org/pango/stable/PangoMarkupFormat.html) 309 | // 310 | // Qt Quick 2 items such as Text do have a "StyledText" format support, 311 | // but this does not cover the Pango markup properly. In particular, it 312 | // does not support HTML entities. 313 | // 314 | // To fix this, we convert newline characters to the HTML
tag, and 315 | // then pass the subtitle string to the fromHtml() function. This decodes 316 | // HTML entities and converts
back to newline. 317 | // 318 | // However, StyledText does not support newline characters, so we have 319 | // to convert newline to
again afterwards. 320 | // 321 | // Note that this is only a minimal subtitle format support. There are 322 | // other subtitle formats such as WebVTT, TTML etc. that would need 323 | // extra consideration. To keep things simple, we do not handle these. 324 | htmlSubtitle.replace("\r\n", "
"); 325 | htmlSubtitle.replace("\n", "
"); 326 | m_subtitle = QTextDocumentFragment::fromHtml(htmlSubtitle).toPlainText(); 327 | m_subtitle.replace("\n", "
"); 328 | 329 | // We have a new subtitle, inform the slots. 330 | emit subtitleChanged(); 331 | 332 | return GST_FLOW_OK; 333 | } 334 | 335 | 336 | void GStreamerPlayer::staticOnGstPlayerEndOfStream(GStreamerPlayer *self) 337 | { 338 | emit self->endOfStream(); 339 | } 340 | 341 | 342 | void GStreamerPlayer::staticOnGstPlayerStateChanged(GStreamerPlayer *self, GstPlayerState p_state) 343 | { 344 | State newState = static_cast < State > (p_state); 345 | self->m_state = newState; 346 | 347 | emit self->stateChanged(); 348 | } 349 | 350 | 351 | void GStreamerPlayer::staticOnGstPlayerDurationChanged(GStreamerPlayer *self, guint64 p_duration) 352 | { 353 | // Use std::max() to avoid fringe cases where a duration of than 1 ms length is reported. 354 | emit self->durationChanged(std::max(int(p_duration / GST_MSECOND), 1)); 355 | } 356 | 357 | 358 | void GStreamerPlayer::staticOnGstPlayerPositionUpdated(GStreamerPlayer *self, guint64 p_position) 359 | { 360 | emit self->positionUpdated(p_position / GST_MSECOND); 361 | } 362 | 363 | 364 | void GStreamerPlayer::staticOnGstPlayerBufferingChanged(GStreamerPlayer *self, gint p_percentage) 365 | { 366 | emit self->buffering(p_percentage); 367 | } 368 | 369 | 370 | void GStreamerPlayer::staticOnGstPlayerMediaInfoUpdated(GStreamerPlayer *self, GstPlayerMediaInfo *) 371 | { 372 | emit self->isSeekableChanged(); 373 | } 374 | 375 | 376 | } // namespace qtglviddemo end 377 | -------------------------------------------------------------------------------- /src/player/GStreamerPlayer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GSTREAMER_PLAYER_HPP 21 | #define QTGLVIDDEMO_GSTREAMER_PLAYER_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "GStreamerCommon.hpp" 31 | #include "GStreamerMediaSample.hpp" 32 | 33 | 34 | namespace qtglviddemo 35 | { 36 | 37 | 38 | /** 39 | * Main GStreamer based media player class. 40 | * 41 | * This implements a media player using GStreamer and the GstPlayer library. 42 | * GstPlayer takes care of several nontrivial features such as seeking or 43 | * buffering. This reduces code complexity and potential for errors. 44 | * 45 | * Decoded video frames are sent to an appsink element. This element makes 46 | * it possible for the main application to pull video frames from it. We 47 | * use this in Qt Quick item code to pull decoded video frames during 48 | * rendering. If a video frame isn't pulled fast enough, and a new frame 49 | * is produced, then the current frame is discarded. In other words, 50 | * frame dropping is done if necessary. 51 | * 52 | * For subtitles, an appsink is also used, except that in this case, 53 | * the subtitles aren't pulled - instead, the subtitleChanged signal 54 | * is emitted. 55 | * 56 | * The player class is designed to be usable in QML. 57 | * 58 | * Playback is started by first setting the url property and then calling 59 | * play(). Calling play() during playback does nothing unless playback is 60 | * currently paused, in which cause it is resumed. 61 | * 62 | * Note that typically, whatever outputs decoded frames supports only a 63 | * certain subset of formats. For this reason, make sure setSinkCaps() 64 | * or setSinkCapsFromVideoFormats() is called before starting playback. 65 | * 66 | * GstPlayer requires two other components to be implemented and instantiated: 67 | * a signal dispatcher and a video renderer. See the corresponding source 68 | * files for details. 69 | */ 70 | class GStreamerPlayer 71 | : public QObject 72 | { 73 | Q_OBJECT 74 | /** 75 | * The next url to play. 76 | * 77 | * If this is changed, it will take effect only after playback is 78 | * restarted by calling stop() and play() again. 79 | */ 80 | Q_PROPERTY(QUrl url READ getUrl WRITE setUrl NOTIFY urlChanged) 81 | /// Current playback state. 82 | Q_PROPERTY(State state READ getState NOTIFY stateChanged) 83 | /** 84 | * Current playback position, in milliseconds. 85 | * 86 | * If the position cannot be currently determined, the position is -1. 87 | */ 88 | Q_PROPERTY(int position READ getPosition) 89 | /** 90 | * Current playback duration, in milliseconds. 91 | * 92 | * If the duration cannot be currently determined, the position is -1. 93 | */ 94 | Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged) 95 | /** 96 | * If this is true, then seek() is supported. 97 | * 98 | * Note that there can be valid position values even if this 99 | * is set to false. 100 | */ 101 | Q_PROPERTY(bool isSeekable READ isSeekable NOTIFY isSeekableChanged) 102 | /// The current subtitle. 103 | Q_PROPERTY(QString subtitle READ getSubtitle NOTIFY subtitleChanged) 104 | 105 | 106 | public: 107 | enum class State 108 | { 109 | /// Player is currently stopped (= idle). 110 | Stopped = GST_PLAYER_STATE_STOPPED, 111 | /// Player is currently buffering data. Playback is paused. 112 | Buffering = GST_PLAYER_STATE_BUFFERING, 113 | /// Player is paused because the user requested it to be paused. 114 | Paused = GST_PLAYER_STATE_PAUSED, 115 | /// Player is playing. 116 | Playing = GST_PLAYER_STATE_PLAYING 117 | }; 118 | Q_ENUM(State) 119 | 120 | /** 121 | * Constructor. 122 | * 123 | * This sets up the GstPlayer, the appsinks, etc. but does not start 124 | * playback. Use the url property and play() for this purpose. 125 | * 126 | * @param newVideoFrameAvailableCB Callback function object that shall 127 | * be invoked whenever a new video frame is available. If this 128 | * is not a valid function object, no notification is done. 129 | * Note that this is called from a GStreamer streaming thread. 130 | * @param p_parent Parent QObject 131 | */ 132 | explicit GStreamerPlayer(NewVideoFrameAvailableCB p_newVideoFrameAvailableCB = NewVideoFrameAvailableCB(), QObject *p_parent = nullptr); 133 | ~GStreamerPlayer(); 134 | 135 | 136 | // Property accessors 137 | 138 | void setUrl(QUrl p_url); 139 | QUrl getUrl() const; 140 | 141 | State getState() const; 142 | 143 | int getPosition() const; 144 | int getDuration() const; 145 | 146 | bool isSeekable() const; 147 | 148 | QString getSubtitle() const; 149 | 150 | 151 | /** 152 | * Sets the allowed video caps. 153 | * 154 | * This limits the possible formatting of the frames the player 155 | * can produce. Internally, if necessary, frames are converted 156 | * prior to passing them to the video appsink. 157 | * 158 | * Make sure this is called before playback is started, otherwise 159 | * frames are produced with incorrect formats. 160 | * 161 | * @param p_sinkCaps Sink caps to use. This does not take 162 | * ownership over the caps. 163 | */ 164 | void setSinkCaps(GstCaps *p_sinkCaps); 165 | /** 166 | * Sets the list of allowed video formats. 167 | * 168 | * This is a variant of setSinkCaps() that limits only the 169 | * set of pixel formats frames can use. Other capabilities such 170 | * as width, height, framerate remain unrestricted. 171 | * 172 | * @param p_videoFormats The set of allowed video formats. 173 | * Must not be empty. 174 | */ 175 | void setSinkCapsFromVideoFormats(std::vector < GstVideoFormat > const &p_videoFormats); 176 | 177 | /** 178 | * Starts playback if not playing yet, or resumes if paused. 179 | * 180 | * This function is used in two cases: 181 | * 182 | * * If the current playback state is Stopped, this starts 183 | * playback. The uri property must be set prior to the 184 | * playback start. If the allowed video formats need to 185 | * be restricted (for example to make sure only frames that 186 | * can be consumed by a GPU are produced), also make sure 187 | * that setSinkCaps() or setSinkCapsFromVideoFormats() 188 | * is called prior to playback start. 189 | * 190 | * * If the current playback state is Paused, this resumes 191 | * (= unpauses) playback. 192 | * 193 | * Either way, the state change to Playing happens 194 | * asynchronously, so do not expect the state to be Playing 195 | * when this function ends. Observe the stateChanged signal 196 | * instead. 197 | * 198 | * This function can be called from QML. 199 | */ 200 | Q_INVOKABLE void play(); 201 | /** 202 | * Pauses playing playback. 203 | * 204 | * If the current state is Playing, this initiates a 205 | * state change to Paused. Otherwise it does nothing. 206 | * 207 | * This function can be called from QML. 208 | */ 209 | Q_INVOKABLE void pause(); 210 | /** 211 | * Stops playback. 212 | * 213 | * This changes the state to Stopped. If the current 214 | * state is already Stopped, this does nothing. 215 | * 216 | * Unlike other calls, this blocks until the Stopped 217 | * state is reached. 218 | * 219 | * This function can be called from QML. 220 | */ 221 | Q_INVOKABLE void stop(); 222 | /** 223 | * 224 | * This function can be called from QML. 225 | */ 226 | Q_INVOKABLE void seek(int p_position); 227 | 228 | /** 229 | * Pulls the current video sample from the video appsink. 230 | * 231 | * If the appsink currently has no sample, the media sample's 232 | * getSample() function will return a null pointer. 233 | * 234 | * Note that the returned media sample holds a reference to 235 | * the underlying GstSample, so make sure the media sample 236 | * is discarded once it is no longer needed. 237 | */ 238 | GStreamerMediaSample pullVideoSample(); 239 | 240 | 241 | signals: 242 | /** 243 | * This signal is emitted during buffering, for example when 244 | * playing from a HTTP source and the HTTP network buffer is 245 | * being filled. When buffering starts, the playback state is 246 | * switched to Buffering, which is similar to Paused, except 247 | * that it can't be "resumed". 248 | * 249 | * p_percent is the percentage of the buffer fill level. Once 250 | * the value reaches 100, the playback state is switched back 251 | * to Paused or Playing (depending on whatever the state was 252 | * before buffering started). 253 | * 254 | * For applications, this signal is mainly useful for displaying 255 | * some sort of progress indicator in the user interface. Do 256 | * not rely on the values to decide if the indicator shall be 257 | * shown; observe the stateChanged signal for this purpose. 258 | * 259 | * @param p_percent Current buffer fill level percentage. Valid 260 | * range is 0-100. 261 | */ 262 | void buffering(int p_percent); 263 | /** 264 | * This signal is emitted when the end of the playback stream 265 | * is reached. The playback state is set to Stopped when this 266 | * happens. 267 | * 268 | * One use for this signal is looping. When it is emitted, 269 | * calling play() will restart playback. 270 | */ 271 | void endOfStream(); 272 | /// This signal is emitted when the URL changed. 273 | void urlChanged(); 274 | /// This signal is emitted when the current playback state changed. 275 | void stateChanged(); 276 | /** 277 | * This signal is emitted when the current playback duration changed. 278 | * 279 | * The current duration is passed as an argument here, because 280 | * it is supplied by GstPlayer, and calling getDuration() would 281 | * be an alternative with slightly more overhead (because it calls 282 | * gst_player_get_duration()). 283 | * 284 | * @param newDuration New playback duration, in milliseconds. 285 | * If no duration is known, this is set to -1. 286 | */ 287 | void durationChanged(int newDuration); 288 | /** 289 | * This signal is emitted when the current playback position changed. 290 | * 291 | * The current position is passed as an argument here, because 292 | * it is supplied by GstPlayer, and calling getPosition() would 293 | * be an alternative with slightly more overhead (because it calls 294 | * gst_player_get_position()). 295 | * 296 | * @param newPosition New playback position, in milliseconds 297 | * If no position is known, this is set to -1. 298 | */ 299 | void positionUpdated(int newPosition); 300 | /// This signal is emitted whenever the seekable property changes. 301 | void isSeekableChanged(); 302 | /** 303 | * This signal is emitted when a new subtitle is available. 304 | * 305 | * Note that if a slot is connected to this with a direct connection, 306 | * said slot takes a long time time to finish, and in the meantime, 307 | * new subtitles are available, then these subtitles will be dropped. 308 | */ 309 | void subtitleChanged(); 310 | 311 | 312 | private: 313 | GstFlowReturn onNewSubtitleSample(); 314 | 315 | static void staticOnGstPlayerEndOfStream(GStreamerPlayer *self); 316 | static void staticOnGstPlayerStateChanged(GStreamerPlayer *self, GstPlayerState p_state); 317 | static void staticOnGstPlayerDurationChanged(GStreamerPlayer *self, guint64 p_duration); 318 | static void staticOnGstPlayerPositionUpdated(GStreamerPlayer *self, guint64 p_position); 319 | static void staticOnGstPlayerBufferingChanged(GStreamerPlayer *self, gint p_percentage); 320 | static void staticOnGstPlayerMediaInfoUpdated(GStreamerPlayer *self, GstPlayerMediaInfo *p_mediaInfo); 321 | 322 | GstPlayer *m_gstplayer; 323 | GstPlayerSignalDispatcher *m_gstdispatcher; 324 | GstPlayerVideoRenderer *m_gstvidrenderer; 325 | GstElement *m_subtitleAppsink; 326 | 327 | QUrl m_url; 328 | State m_state; 329 | 330 | QString m_subtitle; 331 | 332 | GstCaps *m_lastSampleCaps; 333 | }; 334 | 335 | typedef std::unique_ptr < GStreamerPlayer > GStreamerPlayerUPtr; 336 | 337 | 338 | } // namespace qtglviddemo end 339 | 340 | 341 | #endif 342 | -------------------------------------------------------------------------------- /src/player/GStreamerSignalDispatcher.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "GStreamerSignalDispatcher.hpp" 27 | #include "GStreamerPlayer.hpp" 28 | 29 | 30 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 31 | 32 | 33 | namespace 34 | { 35 | 36 | 37 | // Adapted from http://stackoverflow.com/a/21653558 . This is utility code 38 | // to make sure a given function object is executed in the Qt event loop. 39 | template < typename F > 40 | void postFunctionToThread(QObject *p_receiver, F && p_function) 41 | { 42 | class FuncEvent 43 | : public QEvent 44 | { 45 | public: 46 | using Func = typename std::decay < F > ::type; 47 | 48 | explicit FuncEvent(Func && p_func) 49 | : QEvent(QEvent::None) 50 | , m_func(std::move(p_func)) 51 | { 52 | } 53 | 54 | explicit FuncEvent(Func const &p_func) 55 | : QEvent(QEvent::None) 56 | , m_func(p_func) 57 | { 58 | } 59 | 60 | ~FuncEvent() 61 | { 62 | #ifdef QT_NO_EXCEPTIONS 63 | m_func(); 64 | #else 65 | try 66 | { 67 | m_func(); 68 | } 69 | catch (...) 70 | { 71 | // Can't let exceptions out in the destructor 72 | } 73 | #endif 74 | } 75 | 76 | private: 77 | Func m_func; 78 | }; 79 | 80 | QCoreApplication::postEvent(p_receiver, new FuncEvent(std::forward < F > (p_function))); 81 | } 82 | 83 | 84 | } // unnamed namespace end 85 | 86 | 87 | 88 | 89 | struct GStreamerSignalDispatcher 90 | { 91 | GObject parent; 92 | qtglviddemo::GStreamerPlayer *player; 93 | }; 94 | 95 | 96 | struct GStreamerSignalDispatcherClass 97 | { 98 | GObjectClass parent_class; 99 | }; 100 | 101 | 102 | namespace 103 | { 104 | 105 | 106 | void dispatch(GstPlayerSignalDispatcher *p_iface, GstPlayer *p_gstplayer, void (*p_emitter)(gpointer data), gpointer p_emitter_data, GDestroyNotify p_emitter_destroy); 107 | void initDispatcherInterface(GstPlayerSignalDispatcherInterface *p_iface); 108 | 109 | 110 | } // unnamed namespace end 111 | 112 | 113 | // The GstPlayer signal dispatcher is not a GLib class, it is a GLib interface. 114 | // We implement the dispatcher by subclassing GObject and implementing the 115 | // dispatcher interface. 116 | 117 | G_DEFINE_TYPE_WITH_CODE( 118 | GStreamerSignalDispatcher, gstreamer_signal_dispatcher, G_TYPE_OBJECT, 119 | G_IMPLEMENT_INTERFACE(GST_TYPE_PLAYER_SIGNAL_DISPATCHER, initDispatcherInterface) 120 | ) 121 | 122 | 123 | // Init function definitions necessary for the GObject boilerplate. We do not 124 | // need to initialize anything, so they are empty, but they must still exist. 125 | // (See G_DEFINE_TYPE_WITH_CODE documentation for details.) 126 | 127 | static void gstreamer_signal_dispatcher_class_init(GStreamerSignalDispatcherClass *) 128 | { 129 | } 130 | 131 | static void gstreamer_signal_dispatcher_init(GStreamerSignalDispatcher *) 132 | { 133 | } 134 | 135 | 136 | namespace 137 | { 138 | 139 | 140 | void dispatch(GstPlayerSignalDispatcher *p_iface, GstPlayer *, void (*p_emitter)(gpointer data), gpointer p_emitter_data, GDestroyNotify p_emitter_destroy) 141 | { 142 | GStreamerSignalDispatcher *self = (GStreamerSignalDispatcher *)p_iface; 143 | 144 | QObject *receiver = static_cast < QObject* > (self->player); 145 | 146 | qCDebug(lcQtGLVidDemo) << "Dispatching GstPlayer signal; emitter data" << p_emitter_data; 147 | 148 | // Make sure the signal emission is handled in the main Qt thread. 149 | postFunctionToThread(receiver, [=]() { 150 | qCDebug(lcQtGLVidDemo) << "Handling dispatched GstPlayer signal; emitter data" << p_emitter_data; 151 | p_emitter(p_emitter_data); 152 | if (p_emitter_destroy != nullptr) 153 | p_emitter_destroy(p_emitter_data); 154 | }); 155 | } 156 | 157 | 158 | void initDispatcherInterface(GstPlayerSignalDispatcherInterface *p_iface) 159 | { 160 | p_iface->dispatch = GST_DEBUG_FUNCPTR(dispatch); 161 | } 162 | 163 | 164 | } // unnamed namespace end 165 | 166 | 167 | namespace qtglviddemo 168 | { 169 | 170 | 171 | GstPlayerSignalDispatcher* createGStreamerSignalDispatcher(GStreamerPlayer *player) 172 | { 173 | gpointer dispatcher = g_object_new(gstreamer_signal_dispatcher_get_type(), nullptr); 174 | static_cast < GStreamerSignalDispatcher* > (dispatcher)->player = player; 175 | return static_cast < GstPlayerSignalDispatcher* > (dispatcher); 176 | } 177 | 178 | 179 | } // namespace qtglviddemo end 180 | -------------------------------------------------------------------------------- /src/player/GStreamerSignalDispatcher.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GSTREAMER_SIGNAL_DISPATCHER_HPP 21 | #define QTGLVIDDEMO_GSTREAMER_SIGNAL_DISPATCHER_HPP 22 | 23 | #include 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | class GStreamerPlayer; 31 | 32 | 33 | /** 34 | * Creates an implementation of GstPlayerSignalDispatcherInterface. 35 | * 36 | * GstPlayer runs its own GLib mainloop in a separate thread. Its GLib signals 37 | * would be emitted from this separate thread. GLib signal connections are more 38 | * limited compared to Qt ones; they essentially behave like Qt connections of the 39 | * DirectConnection type. This means that signals that are emitted in a separate 40 | * thread also invoke the connected callbacks in that thread. Therefore, any 41 | * application that uses GstPlayer would have to use mutexes etc. to make sure 42 | * no race conditions occur. 43 | * 44 | * To avoid this, GstPlayer has a feature called a "signal dispatcher". It allows 45 | * for integrating signal emissions into existing mainloops so the emissions 46 | * happen in the correct thread. In the Qt case, it means that a function pointer 47 | * and a data pointer are passed to the signal dispatcher, and the dispatcher 48 | * wraps these two in a QEvent and pushes it to the Qt event queue. This way, 49 | * that function pointer is invoked in the main Qt thread, the GstPlayer GLib 50 | * signals are emitted from the main Qt thread, and no race conditions can occur. 51 | * 52 | * Signal emissions hold a reference to the gstplayer that owns the dispatcher. 53 | * If the gstplayer is stopped, it raises an internal flag that makes sure any 54 | * associated signal emission doesn't actually do anything other than releasing 55 | * its reference to the gstplayer. This is important during shutdown, since 56 | * signal emissions may still linger in the Qt event queue. So, during shutdown, 57 | * gst_player_stop() is called, which raises this flag. If the lingering emissions 58 | * are dispatched later when the Qt event loop processes them, this dispatch only 59 | * releases the gstplayer reference and does nothing else. 60 | */ 61 | GstPlayerSignalDispatcher* createGStreamerSignalDispatcher(GStreamerPlayer *player); 62 | 63 | 64 | } // namespace qtglviddemo end 65 | 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/player/GStreamerVideoRenderer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "GStreamerVideoRenderer.hpp" 25 | 26 | 27 | struct GStreamerVideoRenderer 28 | { 29 | GObject parent; 30 | GstElement *videoBin; 31 | GstElement *videoAppsink; 32 | qtglviddemo::NewVideoFrameAvailableCB newVideoFrameAvailableCB; 33 | }; 34 | 35 | 36 | struct GStreamerVideoRendererClass 37 | { 38 | GObjectClass parent_class; 39 | }; 40 | 41 | 42 | namespace 43 | { 44 | 45 | GstElement* createVideoSink(GstPlayerVideoRenderer *p_iface, GstPlayer *p_gstplayer); 46 | void initVideoRenderInterface(GstPlayerVideoRendererInterface *p_iface); 47 | void disposeVideoRenderer(GObject *p_object); 48 | 49 | } // unnamed namespace end 50 | 51 | 52 | // The GstPlayer video renderer is not a GLib class, it is a GLib interface. 53 | // We implement the renderer by subclassing GObject and implementing the 54 | // renderer interface. 55 | 56 | G_DEFINE_TYPE_WITH_CODE( 57 | GStreamerVideoRenderer, gstreamer_video_renderer, G_TYPE_OBJECT, 58 | G_IMPLEMENT_INTERFACE(GST_TYPE_PLAYER_VIDEO_RENDERER, initVideoRenderInterface) 59 | ) 60 | 61 | 62 | // These _class_init and _init functions are declared by the 63 | // G_DEFINE_TYPE_WITH_CODE() boilerplate. 64 | static void gstreamer_video_renderer_class_init(GStreamerVideoRendererClass *klass) 65 | { 66 | GObjectClass *gobject_class = G_OBJECT_CLASS(klass); 67 | gobject_class->dispose = GST_DEBUG_FUNCPTR(disposeVideoRenderer); 68 | } 69 | 70 | 71 | static void gstreamer_video_renderer_init(GStreamerVideoRenderer *renderer) 72 | { 73 | // Create a bin containing all elements necessary for the video output. 74 | // We output frames to an appsink. Format conversions may be necessary, 75 | // so we insert some conversion elements in front of the appsink. Since 76 | // the GstPlayer video renderer interface is supposed to return only 77 | // one element, we put all of these converter elements and the appsink 78 | // into one bin, so that from the outside, they look like one element. 79 | renderer->videoBin = gst_bin_new("videoBin"); 80 | GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr); 81 | renderer->videoAppsink = gst_element_factory_make("appsink", "videoAppsink"); 82 | 83 | // Configure the video appsink to drop the current frame is a new frame 84 | // is produced and the application didn't pull the current frame yet. 85 | // This is essential to make sure the appsink doesn't block if its 86 | // queue is full. 87 | g_object_set(G_OBJECT(renderer->videoAppsink), "sync", gboolean(TRUE), "max-buffers", guint(1), nullptr); 88 | gst_app_sink_set_drop(GST_APP_SINK(renderer->videoAppsink), TRUE); 89 | 90 | // Add the converter and appsink elements to the bin and link them. 91 | gst_bin_add_many(GST_BIN(renderer->videoBin), videoconvert, renderer->videoAppsink, nullptr); 92 | gst_element_link(videoconvert, renderer->videoAppsink); 93 | 94 | // Set up a ghost pad. This ghost pad is added to the bin. Its job is 95 | // to forward incoming data to the inner elements. 96 | GstPad *pad = gst_element_get_static_pad(videoconvert, "sink"); 97 | gst_element_add_pad(renderer->videoBin, gst_ghost_pad_new("sink", pad)); 98 | gst_object_unref(GST_OBJECT(pad)); 99 | 100 | // Sink-ref the element. The video renderer's create_video_sink function 101 | // is used by GstPlayer to get the video renderer element and pass it 102 | // to the internal playbin. This playbin takes ownership over the video 103 | // output element, which means it sink-refs it. 104 | // 105 | // GStreamer elements are GObjects which implement the GInitiallyUnowned 106 | // interface. This means that newly created elements are flagged as a 107 | // "floating" reference. This affects the behavior of gst_object_ref_sink(). 108 | // If it is called on an element with a floating reference, it does _not_ 109 | // increase the refcount; instead, it just clears the floating flag. If 110 | // however the reference isn't floating, it _does_ increase the refcount. 111 | // 112 | // Details can be found at: 113 | // https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#gobject-The-Base-Object-Type.description 114 | // 115 | // Floating references are useful if GObjects are passed to some container 116 | // that can take ownership over these GObjects. However, in our case, 117 | // the GStreamerVideoRenderer subclass is the one that "owns" the video bin. 118 | // So, call gst_object_ref_sink() to make sure the floating flag is cleared. 119 | // (We do explicitely unref the video bin in the dispose function.) 120 | gst_object_ref_sink(GST_OBJECT(renderer->videoBin)); 121 | } 122 | 123 | 124 | 125 | 126 | namespace 127 | { 128 | 129 | 130 | GstElement* createVideoSink(GstPlayerVideoRenderer *p_iface, GstPlayer *) 131 | { 132 | GStreamerVideoRenderer *self = reinterpret_cast < GStreamerVideoRenderer* > (p_iface); 133 | return self->videoBin; 134 | } 135 | 136 | 137 | void initVideoRenderInterface(GstPlayerVideoRendererInterface *p_iface) 138 | { 139 | p_iface->create_video_sink = GST_DEBUG_FUNCPTR(createVideoSink); 140 | } 141 | 142 | 143 | void disposeVideoRenderer(GObject *p_object) 144 | { 145 | GStreamerVideoRenderer *self = reinterpret_cast < GStreamerVideoRenderer* > (p_object); 146 | gst_object_unref(GST_OBJECT(self->videoBin)); 147 | G_OBJECT_CLASS(gstreamer_video_renderer_parent_class)->dispose(p_object); 148 | } 149 | 150 | 151 | void setNewVideoFrameAvailableCB(GStreamerVideoRenderer &p_videoRenderer, qtglviddemo::NewVideoFrameAvailableCB p_newVideoFrameAvailableCB) 152 | { 153 | p_videoRenderer.newVideoFrameAvailableCB = std::move(p_newVideoFrameAvailableCB); 154 | 155 | if (p_videoRenderer.newVideoFrameAvailableCB) 156 | { 157 | // Install new_sample callback function that invokes the 158 | // newVideoFrameAvailableCB function object when a new 159 | // frame is available. 160 | GstFlowReturn (*newSampleCB)(GstAppSink *, gpointer) = [](GstAppSink *, gpointer p_user_data) -> GstFlowReturn { 161 | GStreamerVideoRenderer *renderer = reinterpret_cast < GStreamerVideoRenderer* > (p_user_data); 162 | renderer->newVideoFrameAvailableCB(); 163 | return GST_FLOW_OK; 164 | }; 165 | 166 | GstAppSinkCallbacks callbacks; 167 | std::memset(&callbacks, 0, sizeof(callbacks)); 168 | callbacks.new_sample = newSampleCB; 169 | gst_app_sink_set_callbacks(GST_APP_SINK_CAST(p_videoRenderer.videoAppsink), &callbacks, gpointer(&p_videoRenderer), nullptr); 170 | } 171 | } 172 | 173 | 174 | } // unnamed namespace end 175 | 176 | 177 | namespace qtglviddemo 178 | { 179 | 180 | 181 | GstPlayerVideoRenderer* createGStreamerVideoRenderer(NewVideoFrameAvailableCB newVideoFrameAvailableCB) 182 | { 183 | // Create the video renderer instance. 184 | gpointer renderer = g_object_new(gstreamer_video_renderer_get_type(), nullptr); 185 | // Pass the frame available callback to the new renderer. 186 | setNewVideoFrameAvailableCB(*(static_cast < GStreamerVideoRenderer* > (renderer)), std::move(newVideoFrameAvailableCB)); 187 | // We are done, return the new renderer. 188 | return static_cast < GstPlayerVideoRenderer* > (renderer); 189 | } 190 | 191 | 192 | GstElement* getGStreamerVideoRendererVideoAppsink(GstPlayerVideoRenderer *renderer) 193 | { 194 | GStreamerVideoRenderer *self = (GStreamerVideoRenderer *)renderer; 195 | return self->videoAppsink; 196 | } 197 | 198 | 199 | void setGStreamerVideoRendererSinkCaps(GstPlayerVideoRenderer *renderer, GstCaps *sinkCaps) 200 | { 201 | GStreamerVideoRenderer *self = (GStreamerVideoRenderer *)renderer; 202 | gst_app_sink_set_caps(GST_APP_SINK_CAST(self->videoAppsink), sinkCaps); 203 | } 204 | 205 | 206 | } // namespace qtglviddemo end 207 | -------------------------------------------------------------------------------- /src/player/GStreamerVideoRenderer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GSTREAMER_VIDEO_RENDERER_HPP 21 | #define QTGLVIDDEMO_GSTREAMER_VIDEO_RENDERER_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include "GStreamerCommon.hpp" 27 | 28 | 29 | namespace qtglviddemo 30 | { 31 | 32 | 33 | /** 34 | * Creates an implementation of GstPlayerVideoRenderer. 35 | * 36 | * GstPlayer uses the GstPlayerVideoRenderer interface to create a video 37 | * sink element. This function instantiates an implementation of this interface 38 | * with an appsink as the video sink element. This appsink is capable of 39 | * housing the current video frame so the application can pull it. In addition, 40 | * the appsink can notify about a newly received frame if newVideoFrameAvailableCB 41 | * is a valid function object. 42 | * 43 | * The GStreamerPlayer pullVideoSample() function pulls video samples from 44 | * this renderer's appsink. 45 | * 46 | * @param newVideoFrameAvailableCB Callback function object that shall be 47 | * invoked whenever a new video frame is available in the appsink. 48 | * If this is not a valid function object, no notification is done. 49 | * Note that this is called from a GStreamer streaming thread. 50 | */ 51 | GstPlayerVideoRenderer* createGStreamerVideoRenderer(NewVideoFrameAvailableCB newVideoFrameAvailableCB = NewVideoFrameAvailableCB()); 52 | /** 53 | * Retrieves the video renderer's appsink. 54 | * 55 | * The returned element reference is valid for as long as the renderer exists. 56 | * 57 | * @param renderer Video renderer instance to get the appsink from. 58 | */ 59 | GstElement* getGStreamerVideoRendererVideoAppsink(GstPlayerVideoRenderer *renderer); 60 | /** 61 | * Sets the allowed output sink caps. 62 | * 63 | * This is necessary if only certain subset of output video formats are allowed. 64 | * If for example the output only supports the I420 pixel format, then this must 65 | * be called with the format caps set to I420. 66 | * 67 | * If the sink caps are null, then the formats are unrestricted. 68 | * 69 | * @param renderer Video renderer instance whose sink caps shall be set. 70 | * @param sinkCaps Sink caps to set. 71 | */ 72 | void setGStreamerVideoRendererSinkCaps(GstPlayerVideoRenderer *renderer, GstCaps *sinkCaps); 73 | 74 | 75 | } // namespace qtglviddemo end 76 | 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/scene/Arcball.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "Arcball.hpp" 27 | 28 | 29 | namespace qtglviddemo 30 | { 31 | 32 | 33 | Arcball::Arcball(Transform *p_transform) 34 | : m_transform(p_transform) 35 | { 36 | } 37 | 38 | 39 | void Arcball::setTransform(Transform *p_transform) 40 | { 41 | m_transform = p_transform; 42 | } 43 | 44 | 45 | void Arcball::setViewport(unsigned int const p_width, unsigned int const p_height) 46 | { 47 | m_viewport[0] = p_width; 48 | m_viewport[1] = p_height; 49 | } 50 | 51 | 52 | void Arcball::press(unsigned int const p_x, unsigned int const p_y) 53 | { 54 | if (m_transform == nullptr) 55 | return; 56 | 57 | m_lastRotationAngle = 0.0f; 58 | 59 | // Project a ray starting at the 2D coordinates 60 | // from the screen on the sphere. 61 | m_startVector = projectOnSphere(p_x, p_y); 62 | // Use the existing transform rotation as the base. 63 | m_startRotation = m_transform->getRotation(); 64 | } 65 | 66 | 67 | void Arcball::drag(unsigned int const p_x, unsigned int const p_y) 68 | { 69 | if (m_transform == nullptr) 70 | return; 71 | 72 | QQuaternion newRot; 73 | 74 | // Project a ray starting at the 2D coordinates 75 | // from the screen on the sphere. 76 | QVector3D endVector = projectOnSphere(int(p_x), int(p_y)); 77 | 78 | // Calculate the axis out of the start and end vector. 79 | // Also get the axis length to catch fringe cases where 80 | // the vector is so short that it would cause numerical 81 | // problems. 82 | QVector3D axis = QVector3D::crossProduct(m_startVector, endVector); 83 | float axisLength = std::sqrt(QVector3D::dotProduct(axis, axis)); 84 | 85 | // Calculate the angle using the dot product between start 86 | // and end vector. Since both start and end vector are 87 | // of unit length, the dot product is directly the cosine 88 | // of the angle between them. 89 | float angle = std::acos(QVector3D::dotProduct(m_startVector, endVector)); 90 | // Convert the angle from radians to degrees, since this is 91 | // what QQuaternion expects. 92 | angle = angle * 180.0f / M_PI; 93 | 94 | if (axisLength > std::numeric_limits < float > ::epsilon()) 95 | { 96 | // Produce the rotation quaternion. 97 | newRot = QQuaternion::fromAxisAndAngle(axis, angle); 98 | } 99 | else 100 | { 101 | // We cannot produce a rotation quaternion out of 102 | // the calculations above for numerical reasons. 103 | // In this case, use the unit quaternion instead. 104 | newRot = QQuaternion(); 105 | } 106 | 107 | m_lastRotationAxis = axis; 108 | m_lastRotationAngle = angle; 109 | 110 | // Combine the new rotation quaternion with the base rotation 111 | // that was saved in press(). 112 | newRot = newRot * m_startRotation; 113 | newRot.normalize(); 114 | 115 | // Update the rotation quaternion of the associated transform. 116 | m_transform->setRotation(std::move(newRot)); 117 | } 118 | 119 | 120 | QVector3D Arcball::getLastRotationAxis() const 121 | { 122 | return m_lastRotationAxis; 123 | } 124 | 125 | 126 | float Arcball::getLastRotationAngle() const 127 | { 128 | return m_lastRotationAngle; 129 | } 130 | 131 | 132 | QVector3D Arcball::projectOnSphere(unsigned int const p_x, unsigned int const p_y) const 133 | { 134 | // Translate the coordinates from the 0..viewport scales to 135 | // -1..+1. Also flip the Y coordinate, since the Y axis 136 | // of the screen and the Y axis in the 3D scene are reversed. 137 | float fx = +(float(p_x) / float(m_viewport[0]) * 2.0f - 1.0f); 138 | float fy = -(float(p_y) / float(m_viewport[1]) * 2.0f - 1.0f); 139 | 140 | float length = fx*fx + fy*fy; 141 | 142 | if (length > 1.0f) 143 | { 144 | // The projected ray will miss the sphere, because the 145 | // user didn't actually click on the sphere. In this 146 | // case, use the 2D coordinates to perform a rotation 147 | // around the Z axis instead by projecting them on 148 | // the unit circle on the XY plane. Also normalize 149 | // the length to make sure the produced vector has 150 | // a length of 1. 151 | 152 | float norm = 1.0f / std::sqrt(length); 153 | return QVector3D(fx * norm, fy * norm, 0.0f); 154 | } 155 | else 156 | { 157 | // The projected ray will hit the sphere. Calculate 158 | // the hit point on the hemisphere that is facing us. 159 | // The produced vector has a length of 1. 160 | 161 | return QVector3D(fx, fy, std::sqrt(1.0f - length)); 162 | } 163 | } 164 | 165 | 166 | } // namespace qtglviddemo end 167 | -------------------------------------------------------------------------------- /src/scene/Arcball.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_ARCBALL_HPP 21 | #define QTGLVIDDEMO_ARCBALL_HPP 22 | 23 | #include 24 | #include "Transform.hpp" 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | /** 32 | * Class for arcball-based rotation with a mouse pointer or touch event. 33 | * 34 | * This class allows for producing rotation quaternions from user interface 35 | * interactions, typically a mouse pointer or a touch event. The user presses 36 | * on a unit sphere, and rotates the sphere by dragging that point. 37 | * Rotation is implemented by projecting the 2D event coordinates on this 38 | * sphere when the user presses on it. When the user drags, the 2D drag event 39 | * coordinates are also projected on the sphere. Using these projected 40 | * coordinates, an axis and an angle are calculated, and with these, a 41 | * rotation quaternion is produced. 42 | * 43 | * Using this class requires associating it with a Transform object. The 44 | * transform object's rotation quaternion is automatically adjusted when 45 | * the user drags the arcball. 46 | * 47 | * Also, before using the arcball, make sure the viewport is set by calling 48 | * setViewport. This defines the valid area for 2D event coordinates. 49 | * 50 | * There is no release event or anything like that, since the rotation 51 | * is always calculated using the point defined in the press event as 52 | * the starting point. So, once the user lets go of the touchscreen or 53 | * mouse button, the last rotation calculated during the drag event is 54 | * simply retained. 55 | */ 56 | class Arcball 57 | { 58 | public: 59 | /** 60 | * Constructor. 61 | * 62 | * @param p_transform Transform to associate this arcball with. 63 | * If null, no arcball calculations will be done. 64 | */ 65 | explicit Arcball(Transform *p_transform = nullptr); 66 | 67 | /** 68 | * Associates the given transform object with this arcball. 69 | * 70 | * If p_transform is null, then no transform object is associated, 71 | * and no arcball calculations are done, meaning that press() 72 | * and drag() will do nothing. 73 | * 74 | * @param p_transform Transform to associate this arcball with. 75 | */ 76 | void setTransform(Transform *p_transform); 77 | 78 | /** 79 | * Sets the viewport (the valid area) for 2D event coordinates. 80 | * 81 | * Using press() and drag() before this was called results in 82 | * undefined behavior. 83 | * 84 | * @param p_width Width of the viewport. 85 | * @param p_height Height of the viewport. 86 | */ 87 | void setViewport(unsigned int const p_width, unsigned int const p_height); 88 | 89 | /** 90 | * Press event that starts the arcball rotation. 91 | * 92 | * The rotation begins when the user "presses" on the arcball. 93 | * The mouse button is held down. 94 | * 95 | * On touchscreens, "pressing" is actually be the first touch 96 | * event report. The user still has the finger on the touchscreen 97 | * at this point. 98 | * 99 | * This does not rotate the arcball. It just defines a 100 | * starting point for rotation calculations. 101 | * 102 | * @param p_x X coordinate of the event. Must be within the 103 | * viewport defined by setViewport(). 104 | * @param p_y Y coordinate of the event. Must be within the 105 | * viewport defined by setViewport(). 106 | */ 107 | void press(unsigned int const p_x, unsigned int const p_y); 108 | /** 109 | * Drag event that actually rotates the arcball. 110 | * 111 | * The rotation is calculated when the user moves the mouse 112 | * while still keeping the mouse button pressed. 113 | * 114 | * On touchscreens, the user is still pressing the finger on 115 | * the touchscreen and moving it across the screen. 116 | * 117 | * @param p_x X coordinate of the event. Must be within the 118 | * viewport defined by setViewport(). 119 | * @param p_y Y coordinate of the event. Must be within the 120 | * viewport defined by setViewport(). 121 | */ 122 | void drag(unsigned int const p_x, unsigned int const p_y); 123 | 124 | /** 125 | * Returns the last rotation axis that was computed in the drag() function. 126 | */ 127 | QVector3D getLastRotationAxis() const; 128 | /** 129 | * Returns the last rotation angle that was computed in the drag() function. 130 | */ 131 | float getLastRotationAngle() const; 132 | 133 | 134 | private: 135 | QVector3D projectOnSphere(unsigned int const p_x, unsigned int const p_y) const; 136 | 137 | Transform *m_transform; 138 | 139 | QVector3D m_lastRotationAxis; 140 | float m_lastRotationAngle; 141 | 142 | QQuaternion m_startRotation; 143 | QVector3D m_startVector; 144 | unsigned int m_viewport[2]; 145 | }; 146 | 147 | 148 | } // namespace qtglviddemo end 149 | 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /src/scene/Camera.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include "Camera.hpp" 23 | 24 | 25 | namespace qtglviddemo 26 | { 27 | 28 | 29 | Camera::Camera() 30 | : m_fov(90.0f) 31 | , m_aspect(1.0f) 32 | , m_znear(1.0f) 33 | , m_zfar(100.0f) 34 | , m_projectionMatrixValid(false) 35 | , m_viewMatrixValid(false) 36 | { 37 | } 38 | 39 | 40 | void Camera::setFov(float const p_fov) 41 | { 42 | assert(p_fov > 0.0f); 43 | 44 | m_fov = p_fov; 45 | m_projectionMatrixValid = false; 46 | } 47 | 48 | 49 | void Camera::setAspect(float const p_aspect) 50 | { 51 | assert(p_aspect > 0.0f); 52 | 53 | m_aspect = p_aspect; 54 | m_projectionMatrixValid = false; 55 | } 56 | 57 | 58 | void Camera::setZrange(float const p_znear, float const p_zfar) 59 | { 60 | assert(p_znear > 0.0f); 61 | assert(p_zfar > 0.0f); 62 | assert(p_znear < p_zfar); 63 | 64 | m_znear = p_znear; 65 | m_zfar = p_zfar; 66 | m_projectionMatrixValid = false; 67 | } 68 | 69 | 70 | void Camera::setPosition(QVector3D p_position) 71 | { 72 | m_transform.setPosition(std::move(p_position)); 73 | m_viewMatrixValid = false; 74 | } 75 | 76 | 77 | void Camera::setRotation(QQuaternion p_rotation) 78 | { 79 | m_transform.setRotation(std::move(p_rotation)); 80 | m_viewMatrixValid = false; 81 | } 82 | 83 | 84 | QMatrix4x4 const & Camera::getProjectionMatrix() const 85 | { 86 | if (!m_projectionMatrixValid) 87 | { 88 | m_projectionMatrix = QMatrix4x4(); 89 | m_projectionMatrix.perspective(m_fov, m_aspect, m_znear, m_zfar); 90 | m_projectionMatrixValid = true; 91 | } 92 | 93 | return m_projectionMatrix; 94 | } 95 | 96 | 97 | QMatrix4x4 const & Camera::getViewMatrix() const 98 | { 99 | if (!m_viewMatrixValid) 100 | { 101 | m_viewMatrix = m_transform.getMatrix().inverted(); 102 | m_viewMatrixValid = true; 103 | } 104 | 105 | return m_viewMatrix; 106 | } 107 | 108 | 109 | } // namespace qtglviddemo end 110 | -------------------------------------------------------------------------------- /src/scene/Camera.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_CAMERA_HPP 21 | #define QTGLVIDDEMO_CAMERA_HPP 22 | 23 | #include "Transform.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | /** 31 | * 3D representation of a camera. 32 | * 33 | * This class allows for calculating view and projection 3D 4x4 matrices 34 | * out of the given parameters. This is useful for modeling the behavior 35 | * of a virtual camera in 3D rendering. 36 | * 37 | * Mathematically, the view area the camera can "see" is called the 38 | * view frustum. Since we render the 3D scene into a rectangular area, 39 | * the frustum looks like a pyramid with a cut off tip. 40 | * 41 | * Internally, the camera has a Transform instance, which describes the 42 | * position and orientation of the camera in the 3D world. (Scaling is 43 | * not done and left at 1.) The view matrix is actually the exact inverse 44 | * of that transform's matrix. The view matrix transforms vertex coordinates 45 | * from world space (also called model space) to view space using the view 46 | * matrix. In other words, this transformation warps the coordinates 47 | * to make it look as if the camera's position were (0,0,0). 48 | * 49 | * After that, perspective transformation is applied by producing the 50 | * projection matrix. 51 | * 52 | * Both view and projection matrix are (re)calculated on-demand if any 53 | * of their parameters changed. 54 | */ 55 | class Camera 56 | { 57 | public: 58 | /** 59 | * Constructor. 60 | * 61 | * Creates a camera that is placed at (0,0,0), has a field of 62 | * view angle of 90 degrees, an aspect ratio of 1.0 and a Z 63 | * range of 1..100. 64 | */ 65 | Camera(); 66 | 67 | /** 68 | * Sets the field of view angle of the view frustum. 69 | * 70 | * The FOV angle defines how wide the view frustum is. 71 | * 72 | * @param p_fov Field of view angle to use. Must be positive. 73 | * and nonzero. 74 | */ 75 | void setFov(float const p_fov); 76 | /** 77 | * Sets the aspect ratio of the view frustum. 78 | * 79 | * This is necessary to make sure the output is not stretched 80 | * in horizontal or vertical direction in case the rectangular 81 | * area we render into has different width and height sizes. 82 | * 83 | * The aspect ratio is in fact the ratio between the width and 84 | * height of that area. So, if the render region is an area 85 | * with 320x240 pixels, the aspect ratio is 320/240 ~ 1.333 . 86 | * 87 | * @param p_aspect Aspect ratio to use. Must be positive 88 | * and nonzero. 89 | */ 90 | void setAspect(float const p_aspect); 91 | /** 92 | * Sets the visible range in Z direction. 93 | * 94 | * Vertices with Z coordinates outside of this range will not 95 | * be visible. The range defines the position of the near and 96 | * far plane, or the start and end of the view frustum in 97 | * Z direction. 98 | * 99 | * p_znear must always be smaller than p_zfar. Both values 100 | * must be positive and nonzero. 101 | * 102 | * @param p_znear Near plane value; the start of the Z range. 103 | * @param p_zfar Far plane value; the end of the Z range. 104 | */ 105 | void setZrange(float const p_znear, float const p_zfar); 106 | 107 | /** 108 | * Sets the camera's position in world space. 109 | * 110 | * @param p_position New 3D position vector to use. 111 | */ 112 | void setPosition(QVector3D p_position); 113 | /** 114 | * Sets the camera's rotation in world space. 115 | * 116 | * @param p_rotation New 3D rotation quaternion to use. 117 | */ 118 | void setRotation(QQuaternion p_rotation); 119 | 120 | /** 121 | * Returns the projection matrix. 122 | * 123 | * If FOV, aspect ratio, or the Z range were modified, 124 | * then this matrix will internally be recalculated 125 | * before returning it. 126 | */ 127 | QMatrix4x4 const & getProjectionMatrix() const; 128 | /** 129 | * Returns the view matrix. 130 | * 131 | * This is the exact inverse of the matrix of the 132 | * internal transform object. 133 | * 134 | * If position or rotation were modified, 135 | * then this matrix will internally be recalculated 136 | * before returning it. 137 | */ 138 | QMatrix4x4 const & getViewMatrix() const; 139 | 140 | 141 | private: 142 | float m_fov, m_aspect, m_znear, m_zfar; 143 | 144 | Transform m_transform; 145 | 146 | mutable QMatrix4x4 m_projectionMatrix; 147 | mutable bool m_projectionMatrixValid; 148 | 149 | mutable QMatrix4x4 m_viewMatrix; 150 | mutable bool m_viewMatrixValid; 151 | }; 152 | 153 | 154 | } // namespace qtglviddemo end 155 | 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /src/scene/GLResources.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "GLResources.hpp" 25 | #include "videomaterial/VideoMaterialProviderGeneric.hpp" 26 | #include "videomaterial/VideoMaterialProviderVivante.hpp" 27 | #include "videomaterial/GLVIVDirectTextureExtension.hpp" 28 | #include "mesh/CubeMesh.hpp" 29 | #include "mesh/QuadMesh.hpp" 30 | #include "mesh/TeapotMesh.hpp" 31 | #include "mesh/SphereMesh.hpp" 32 | #include "mesh/TorusMesh.hpp" 33 | 34 | 35 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 36 | 37 | 38 | namespace qtglviddemo 39 | { 40 | 41 | 42 | static GLResources *inst = nullptr; 43 | 44 | 45 | GLResources::GLResources(QOpenGLContext *p_glcontext) 46 | { 47 | // This constructor is called when the singleton instance is created. 48 | 49 | // Create the video material provider. 50 | #ifdef WITH_VIV_GPU 51 | // On platforms with a Vivante GPU, try to create a Vivante video 52 | // material provider first. 53 | if (isVivDirectTextureSupported(p_glcontext)) 54 | { 55 | qCDebug(lcQtGLVidDemo) << "Vivante direct textures supported - using Vivante video material provider"; 56 | m_videoMaterialProvider = VideoMaterialProviderUPtr(new VideoMaterialProviderVivante(p_glcontext)); 57 | } 58 | else 59 | #endif 60 | { 61 | qCDebug(lcQtGLVidDemo) << "using generic video material provider"; 62 | m_videoMaterialProvider = VideoMaterialProviderUPtr(new VideoMaterialProviderGeneric(p_glcontext)); 63 | } 64 | } 65 | 66 | 67 | GLResources::~GLResources() 68 | { 69 | // Destroy all allocated meshes by clearing the map. 70 | m_meshMap.clear(); 71 | // Destroy the video material provider. 72 | m_videoMaterialProvider.reset(); 73 | } 74 | 75 | 76 | QOpenGLVertexArrayObject & GLResources::getVAO() 77 | { 78 | return m_vao; 79 | } 80 | 81 | 82 | VideoMaterialProvider & GLResources::getVideoMaterialProvider() 83 | { 84 | assert(m_videoMaterialProvider); 85 | return *m_videoMaterialProvider; 86 | } 87 | 88 | 89 | Mesh & GLResources::getMesh(QString const &p_meshType) 90 | { 91 | auto iter = m_meshMap.find(p_meshType); 92 | if (iter == m_meshMap.end()) 93 | { 94 | MeshUPtr newMesh(new Mesh(p_meshType)); 95 | 96 | if (p_meshType == "quad") 97 | newMesh->setContents(getQuadMeshData()); 98 | else if (p_meshType == "cube") 99 | newMesh->setContents(getCubeMeshData()); 100 | else if (p_meshType == "teapot") 101 | newMesh->setContents(getTeapotMeshData()); 102 | else if (p_meshType == "sphere") 103 | newMesh->setContents(calculateSphereMeshData(1.0f, 16, 32)); 104 | else if (p_meshType == "torus") 105 | newMesh->setContents(calculateTorusMeshData(1.0f, 0.4f, 32, 16)); 106 | 107 | auto retval = m_meshMap.emplace(p_meshType, std::move(newMesh)); 108 | iter = retval.first; 109 | } 110 | 111 | return *(iter->second); 112 | } 113 | 114 | 115 | GLResources& GLResources::instance() 116 | { 117 | if (inst == nullptr) 118 | { 119 | QOpenGLContext *glctx = QOpenGLContext::currentContext(); 120 | 121 | qCDebug(lcQtGLVidDemo) << "Setting up shared OpenGL resources"; 122 | inst = new GLResources(glctx); 123 | 124 | // Connect the aboutToBeDestroyed() signal. This is the only 125 | // reliable way to detect when to destroy resources, since 126 | // the QQuickWindow's sceneGraphInvalidated() may not be 127 | // emitted on all platforms. 128 | connect(glctx, &QOpenGLContext::aboutToBeDestroyed, inst, &GLResources::teardownSingletonInstance, Qt::DirectConnection); 129 | 130 | } 131 | return *inst; 132 | } 133 | 134 | 135 | void GLResources::teardownSingletonInstance() 136 | { 137 | if (inst != nullptr) 138 | { 139 | qCDebug(lcQtGLVidDemo) << "Tearing down shared OpenGL resources"; 140 | delete inst; 141 | inst = nullptr; 142 | } 143 | } 144 | 145 | 146 | } // namespace qtglviddemo end 147 | -------------------------------------------------------------------------------- /src/scene/GLResources.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_GLRESOURCES_HPP 21 | #define QTGLVIDDEMO_GLRESOURCES_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include "mesh/Mesh.hpp" 27 | #include "videomaterial/VideoMaterial.hpp" 28 | 29 | 30 | class QOpenGLContext; 31 | 32 | 33 | namespace qtglviddemo 34 | { 35 | 36 | 37 | /** 38 | * Class for common OpenGL resources used by all Qt Quick video object items. 39 | * 40 | * There are some OpenGL resources that do not have to be created more than 41 | * once. In fact, doing so would probably waste resources. For example, the 42 | * shader for rendering video materials only needs to be instantiated once. 43 | * 44 | * This class contains the common resources, which are: 45 | * - Video material provider 46 | * - Vertex array object 47 | * - Map containing Mesh instances (with OpenGL index/vertex buffer objects) 48 | * 49 | * Since it is not possible to pass arguments to the constructor of a custom 50 | * Qt Quick 2 item, this class is set up as a singleton. The instance() 51 | * function returns the global class instance, and creates it if it does 52 | * not yet exist. 53 | * 54 | * The reason why this singleton class is initialized this way is that there 55 | * is no designated moment when custom shared OpenGL resources can be initialized 56 | * in Qt. On desktop machines, QQuickWindow's sceneGraphInitialized() signal 57 | * can be used for this purpose, but this signal isn't emitted on embedded 58 | * devices with the eglfs platform. As it turns out, the only reliable way 59 | * of initializing the common resources is on-demand, that is, whenever the 60 | * global instance is first accessed with instance(). 61 | * 62 | * When the global instance is created in instance(), the constructor in 63 | * turn initializes the common OpenGL resources. For this purpose, it uses 64 | * the current OpenGL context. It also establishes a connection to said 65 | * context's aboutToBeDestroyed() signal. The connected slot destroys the 66 | * global class instance. This way, it is guaranteed that when the 67 | * destructor runs, the OpenGL context is still valid. 68 | */ 69 | class GLResources 70 | : public QObject 71 | { 72 | Q_OBJECT 73 | 74 | public: 75 | /** 76 | * Returns the vertex array object (VAO). 77 | * 78 | * On OpenGL 3.3 and later, having a VAO is a must. On version 79 | * 3.2 and older, and on OpenGL ES 2.x, a VAO may not be required, 80 | * or VAOs may not even exist. To remain compatible with both 81 | * kinds of platforms, GLResources contains a Qt VAO instance. 82 | * This instance internally does or doesn't create a VAO, 83 | * depending on the OpenGL type. If the instance's isCreated() 84 | * function returns true, the VAO was created, otherwise it wasn't. 85 | */ 86 | QOpenGLVertexArrayObject & getVAO(); 87 | 88 | /** 89 | * Returns the video material provider. 90 | * 91 | * Which provider is used depends on the platform. 92 | */ 93 | VideoMaterialProvider & getVideoMaterialProvider(); 94 | 95 | /** 96 | * Returns a mesh of the given type. 97 | * 98 | * If such a mesh doesn't exist yet, it is created and stored 99 | * in an internal STL map. This way, multiple cube objects can 100 | * exist for example, and the mesh has to be created only once. 101 | * 102 | * The provider's OpenGL context must be valid when this is run. 103 | * 104 | * @param p_meshType Mesh type string. Valid values are "cube", 105 | * "quad", "teapot". 106 | */ 107 | Mesh & getMesh(QString const &p_meshType); 108 | 109 | /** 110 | * Returns the singleton instance. If it doesn't exist yet, it 111 | * is created. 112 | * 113 | * The provider's OpenGL context must be valid when this is run. 114 | */ 115 | static GLResources& instance(); 116 | 117 | private slots: 118 | void teardownSingletonInstance(); 119 | 120 | private: 121 | explicit GLResources(QOpenGLContext *p_glcontext); 122 | ~GLResources(); 123 | 124 | QOpenGLVertexArrayObject m_vao; 125 | 126 | typedef std::unique_ptr < VideoMaterialProvider > VideoMaterialProviderUPtr; 127 | VideoMaterialProviderUPtr m_videoMaterialProvider; 128 | 129 | typedef std::map < QString, MeshUPtr > MeshMap; 130 | MeshMap m_meshMap; 131 | }; 132 | 133 | 134 | } // namespace qtglviddemo end 135 | 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /src/scene/Transform.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include "Transform.hpp" 22 | 23 | 24 | namespace qtglviddemo 25 | { 26 | 27 | 28 | Transform::Transform() 29 | : m_matrixValid(false) 30 | , m_position(0, 0, 0) 31 | , m_scale(1) 32 | , m_rotation(1, 0, 0, 0) 33 | { 34 | } 35 | 36 | 37 | QMatrix4x4 const & Transform::getMatrix() const 38 | { 39 | // Lazy evaluation: recalculate the matrix on-demand, 40 | // that is, if current matrix is marked as invalid. 41 | if (!m_matrixValid) 42 | { 43 | m_matrix = QMatrix4x4(); 44 | m_matrix.translate(m_position); 45 | m_matrix.rotate(m_rotation); 46 | m_matrix.scale(m_scale); 47 | 48 | m_matrixValid = true; 49 | } 50 | 51 | return m_matrix; 52 | } 53 | 54 | 55 | void Transform::setPosition(QVector3D p_position) 56 | { 57 | m_position = std::move(p_position); 58 | m_matrixValid = false; 59 | } 60 | 61 | 62 | QVector3D const & Transform::getPosition() const 63 | { 64 | return m_position; 65 | } 66 | 67 | 68 | void Transform::setScale(float const p_scale) 69 | { 70 | m_scale = p_scale; 71 | m_matrixValid = false; 72 | } 73 | 74 | 75 | float Transform::getScale() const 76 | { 77 | return m_scale; 78 | } 79 | 80 | 81 | void Transform::setRotation(QQuaternion p_rotation) 82 | { 83 | m_rotation = std::move(p_rotation); 84 | m_matrixValid = false; 85 | } 86 | 87 | 88 | QQuaternion const & Transform::getRotation() const 89 | { 90 | return m_rotation; 91 | } 92 | 93 | 94 | } // namespace qtglviddemo end 95 | -------------------------------------------------------------------------------- /src/scene/Transform.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_TRANSFORM_HPP 21 | #define QTGLVIDDEMO_TRANSFORM_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | namespace qtglviddemo 29 | { 30 | 31 | 32 | /** 33 | * 3D transformation class. 34 | * 35 | * This allows for calculating 3D transformations and producing 4x4 matrices 36 | * containing these transformations. 37 | * 38 | * Supported transformations are rotation, translation, and scaling. 39 | * The scaling is uniform, meaning that scaling in X,Y,Z direction is done 40 | * with equal magnitude. 41 | * 42 | * The advantage of using this class over using matrix multiplications is 43 | * that the position vector, scale factor, and rotation quaternion can be 44 | * adjusted independently, and the order of the individual transformations 45 | * is maintained. 46 | * 47 | * Rotation is performed using the position vector as the origin. Scaling 48 | * also uses the position vector as the origin. First, scaling is done. 49 | * Then, the scaled version is rotated. Finally, the scaled and rotated 50 | * version is moved from (0,0,0) to the position vector. 51 | */ 52 | class Transform 53 | { 54 | public: 55 | /** 56 | * Constructor. 57 | * 58 | * Sets up an identity transform: position vector (0,0,0), 59 | * scale factor 1, identity quaternion as rotation. 60 | */ 61 | Transform(); 62 | 63 | /** 64 | * Returns the transform in matrix form. 65 | * 66 | * This matrix is calculated on-demand. If for example the position 67 | * is changed, then an internal flag is set to denote that the 68 | * matrix needs to be updated. Later, when getMatrix() is called, 69 | * the matrix is recalculated. 70 | */ 71 | QMatrix4x4 const & getMatrix() const; 72 | 73 | /** 74 | * Sets the position vector. 75 | * 76 | * The position vector is also used as the origin for the 77 | * rotation and scale transformations. 78 | * 79 | * @param p_position New position vector to use. 80 | */ 81 | void setPosition(QVector3D p_position); 82 | /// Returns the current position vector. 83 | QVector3D const & getPosition() const; 84 | 85 | /** 86 | * Sets the scale factor. 87 | * 88 | * While a factor of 0 may work, it is untested. 89 | * 90 | * The position vector is used as the origin for this transformation. 91 | * 92 | * @param p_scale New scale factor to use. 93 | */ 94 | void setScale(float const p_scale); 95 | /// Returns the current scale factor. 96 | float getScale() const; 97 | 98 | /** 99 | * Sets the rotation quaternion. 100 | * 101 | * The position vector is used as the origin for this transformation. 102 | * 103 | * @param p_scale New rotation quaternion to use. 104 | */ 105 | void setRotation(QQuaternion p_rotation); 106 | /// Returns the current rotation quaternion. 107 | QQuaternion const & getRotation() const; 108 | 109 | 110 | private: 111 | // These are defined as mutable to be able to perform lazy evaluation 112 | // in getMatrix(). 113 | mutable bool m_matrixValid; 114 | mutable QMatrix4x4 m_matrix; 115 | 116 | QVector3D m_position; 117 | float m_scale; 118 | QQuaternion m_rotation; 119 | }; 120 | 121 | 122 | } // namespace qtglviddemo end 123 | 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /src/scene/VideoObjectItem.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_VIDEO_OBJECT_ITEM_HPP 21 | #define QTGLVIDDEMO_VIDEO_OBJECT_ITEM_HPP 22 | 23 | #include 24 | #include 25 | #include "player/GStreamerPlayer.hpp" 26 | #include "Arcball.hpp" 27 | #include "Camera.hpp" 28 | #include "Transform.hpp" 29 | 30 | 31 | class QOpenGLContext; 32 | 33 | 34 | namespace qtglviddemo 35 | { 36 | 37 | 38 | /** 39 | * QtQuick 2 item for rendering video objects. 40 | * 41 | * A "video object" is a 3D mesh with a video material applied to it. For 42 | * example, it could be a cube with the video shown on all of its faces. 43 | * 44 | * The VideoObjectItem renders video objects using parameters like opacity 45 | * or mesh type, and produces the video frames using GStreamerPlayer. 46 | * It is a QtQuick 2 item that can be used as the delegate of a QtQuick 2 47 | * view. 48 | * 49 | * The properties (meshType, rotation etc.) may be set manually in C++ 50 | * or QML, but typically they are defined by using VideoObjectItem as a 51 | * QtQuick 2 view delegate, and using an instance of VideoObjectModel 52 | * as the data model for that QtQuick 2 view. 53 | * 54 | * For rendering it uses custom OpenGL commands. Internally, the object 55 | * is actually rendered to an OpenGL framebuffer object (which is why 56 | * this item inherits from QQuickFramebufferObject and not directly 57 | * from QQuickItem). This is the recommended way of integrating custom 58 | * 3D rendering into the QtQuick 2 scenegraph. 59 | */ 60 | class VideoObjectItem 61 | : public QQuickFramebufferObject 62 | { 63 | Q_OBJECT 64 | 65 | Q_PROPERTY(qtglviddemo::GStreamerPlayer* player READ getPlayer) 66 | 67 | /// Rotation quaternion to use for rotating the 3D object. 68 | Q_PROPERTY(QQuaternion rotation READ getRotation WRITE setRotation NOTIFY rotationChanged) 69 | /// Crop rectangle to use in the video material. 70 | Q_PROPERTY(QRect cropRectangle READ getCropRectangle WRITE setCropRectangle NOTIFY cropRectangleChanged) 71 | /// Type of the mesh to render. 72 | Q_PROPERTY(QString meshType READ getMeshType WRITE setMeshType NOTIFY meshTypeChanged) 73 | /// Texture rotation angle to use in the video material. 74 | Q_PROPERTY(int textureRotation READ getTextureRotation WRITE setTextureRotation NOTIFY textureRotationChanged) 75 | 76 | class Renderer; 77 | 78 | public: 79 | /** 80 | * Constructor. 81 | * 82 | * Creates an item with crop rectangle (0,0,100,100), identity rotation 83 | * quaternion, and texture rotation angle 0. 84 | * 85 | * This item will not be rendered until a valid mesh type is set. 86 | * 87 | * @param p_parent Parent QtQuick 2 item 88 | */ 89 | explicit VideoObjectItem(QQuickItem *p_parent = nullptr); 90 | ~VideoObjectItem(); 91 | 92 | // QQuickFramebufferObject::createRenderer() override. 93 | virtual QQuickFramebufferObject::Renderer* createRenderer() const override; 94 | 95 | 96 | // Property accessors 97 | 98 | GStreamerPlayer* getPlayer(); 99 | 100 | void setRotation(QQuaternion p_rotation); 101 | QQuaternion const & getRotation() const; 102 | 103 | void setCropRectangle(QRect p_cropRectangle); 104 | QRect getCropRectangle() const; 105 | 106 | void setMeshType(QString const p_meshType); 107 | QString getMeshType() const; 108 | 109 | void setTextureRotation(int const p_rotation); 110 | int getTextureRotation() const; 111 | 112 | 113 | signals: 114 | /** 115 | * This signal is emitted when it is OK to start playback. 116 | * 117 | * In a QML script, this is useful for autostarting playback. Calling 118 | * the player's setUrl() and play() functions in the Component.onComplete() 119 | * signal is not an option, since the renderer might not be set up 120 | * at that point yet. So, instead, by listening to this signal, the 121 | * setUrl() and play() functions can be done at the right moment. 122 | */ 123 | void canStartPlayback(); 124 | 125 | /// This signal is emitted when the rotation quaternion changes. 126 | void rotationChanged(); 127 | /// This signal is emitted when the crop rectangle changes. 128 | void cropRectangleChanged(); 129 | /// This signal is emitted when the mesh type string changes. 130 | void meshTypeChanged(); 131 | /// This signal is emitted when the texture rotation angle changes. 132 | void textureRotationChanged(); 133 | 134 | // Internal signal for when the FBO needs to be updated. Typically 135 | // this is emitted when the player has a new video frame. 136 | void fboNeedsChange(); 137 | 138 | 139 | protected: 140 | virtual QSGNode* updatePaintNode(QSGNode *p_oldNode, UpdatePaintNodeData *p_updatePaintNodeData) override; 141 | 142 | 143 | private: 144 | virtual void mousePressEvent(QMouseEvent *p_event); 145 | virtual void mouseMoveEvent(QMouseEvent *p_event); 146 | virtual void mouseReleaseEvent(QMouseEvent *p_event); 147 | 148 | void onNewFrameAvailable(); 149 | 150 | Arcball m_arcball; 151 | bool m_mouseButtonPressed; 152 | Camera m_camera; 153 | Transform m_transform; 154 | 155 | QRect m_cropRectangle; 156 | QString m_meshType; 157 | int m_textureRotation; 158 | 159 | QVector3D m_lastRotationAxis; 160 | float m_lastRotationAngle; 161 | GstClockTime m_lastMovementTimestamp, m_lastMovementDuration; 162 | float m_rotAttenuation; 163 | GstClockTime m_lastUpdateTimestamp; 164 | 165 | mutable GStreamerPlayer m_player; 166 | }; 167 | 168 | 169 | } // namespace qtglviddemo end 170 | 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/scene/VideoObjectModel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include "VideoObjectModel.hpp" 22 | 23 | 24 | namespace qtglviddemo 25 | { 26 | 27 | 28 | namespace 29 | { 30 | 31 | 32 | // Helper function to set data and to set a flag if the data really changed. 33 | // This is necessary to avoid endless loops ("binding data loops" in QML jargon) 34 | // which can happen in two-way data bindings. Example: a QtQuick 2 item modifies 35 | // the opacity value. The data model's setData() function is called. Since the 36 | // opacity value was overwritten, the dataChanged() signal is emitted. In the 37 | // QML script, this causes the item's value to be modified. And this in turn 38 | // causes the item to modifies the opacity value again ... 39 | // 40 | // By checking if the value actually changed, this can be prevented. If the 41 | // "new" value is in fact the same as the current one, dataChanged() is not 42 | // emitted. To determine if that signal needs to be emitted, p_valueChangedFlag 43 | // is set to true only if the value actually changed. 44 | template < typename T > 45 | void setDataHelper(T & p_destValue, QVariant const &p_variant, bool &p_valueChangedFlag) 46 | { 47 | T value = p_variant.value < T > (); 48 | if (p_destValue != value) 49 | { 50 | p_destValue = value; 51 | p_valueChangedFlag = true; 52 | } 53 | } 54 | 55 | 56 | } // unnamed namespace end 57 | 58 | 59 | VideoObjectModel::Description::Description() 60 | : m_meshType("cube") 61 | , m_scale(1.0f) 62 | , m_opacity(1.0f) 63 | , m_cropRectangle(0, 0, 100, 100) 64 | , m_textureRotation(0) 65 | , m_subtitleSource(SubtitleSource::MediaSubtitles) 66 | { 67 | } 68 | 69 | 70 | VideoObjectModel::VideoObjectModel(QObject *p_parent) 71 | : QAbstractListModel(p_parent) 72 | { 73 | } 74 | 75 | 76 | int VideoObjectModel::addFromURL(QUrl p_url) 77 | { 78 | if (!p_url.isValid()) 79 | return -1; 80 | 81 | // Construct default description and set its URL. 82 | Description newDesc; 83 | newDesc.m_url = std::move(p_url); 84 | // Add the new description. 85 | addDescription(std::move(newDesc)); 86 | 87 | // The description is always appended, meaning that its index is always 88 | // the new number of descriptions, minus one. 89 | return m_descriptions.size() - 1; 90 | } 91 | 92 | 93 | int VideoObjectModel::addV4L2DeviceNode(QString const &p_deviceNode) 94 | { 95 | // Produce URL that can be used with GStreamer to receive rames from 96 | // the camera. If gstreamer-imx is used, we must use the imxv4l2:// 97 | // URL scheme to instruct the GStreamer URI handler subsystem to 98 | // choose imxv4l2videosrc. This is essential, because v4l2src does 99 | // not work with mxc_v4l2 devices, and imxv4l2videosrc produces 100 | // frames that are stored in DMA buffers, thereby allowing for 101 | // zerocopy-enabled video rendering. 102 | QUrl url( 103 | QString( 104 | #ifdef USE_IMX_V4L2 105 | "imxv4l2://" 106 | #else 107 | "v4l2://" 108 | #endif 109 | ) + p_deviceNode 110 | ); 111 | 112 | return addFromURL(std::move(url)); 113 | } 114 | 115 | 116 | void VideoObjectModel::remove(int const p_index) 117 | { 118 | removeDescription(p_index); 119 | } 120 | 121 | 122 | void VideoObjectModel::addDescription(Description p_description) 123 | { 124 | // Let the base class know that we are adding a new entry (= a new row). 125 | beginInsertRows(QModelIndex(), m_descriptions.size(), m_descriptions.size()); 126 | 127 | // Perform the actuall add operation. 128 | m_descriptions.emplace_back(p_description); 129 | 130 | // We are done modifying the model. 131 | endInsertRows(); 132 | 133 | // Notify listeners that the count property changed (since there's now one 134 | // more description in the list). 135 | emit countChanged(); 136 | } 137 | 138 | 139 | VideoObjectModel::Description const & VideoObjectModel::getDescription(std::size_t const p_index) const 140 | { 141 | return m_descriptions[p_index]; 142 | } 143 | 144 | 145 | std::size_t VideoObjectModel::getNumDescriptions() const 146 | { 147 | return m_descriptions.size(); 148 | } 149 | 150 | 151 | void VideoObjectModel::removeDescription(std::size_t const p_index) 152 | { 153 | // Do nothing if the index is invalid. 154 | if (p_index >= std::size_t(m_descriptions.size())) 155 | return; 156 | 157 | // Let the base class know that we are remove an entry (= a row). 158 | beginRemoveRows(QModelIndex(), p_index, p_index); 159 | 160 | // Perform the actual remove operation. 161 | m_descriptions.erase(m_descriptions.begin() + p_index); 162 | 163 | // We are done modifying the model. 164 | endRemoveRows(); 165 | 166 | // Notify listeners that the count property changed (since there's now one 167 | // less description in the list). 168 | emit countChanged(); 169 | } 170 | 171 | 172 | QVariant VideoObjectModel::data(QModelIndex const &p_index, int p_role) const 173 | { 174 | int idx = p_index.row(); 175 | 176 | // If the index is invalid, return an empty value. 177 | if ((idx < 0) || (idx >= int(m_descriptions.size()))) 178 | return QVariant(); 179 | 180 | Description const & desc = m_descriptions[idx]; 181 | 182 | switch (p_role) 183 | { 184 | case UrlRole: return QVariant::fromValue(desc.m_url); 185 | case MeshTypeRole: return QVariant::fromValue(desc.m_meshType); 186 | case ScaleRole: return QVariant::fromValue(desc.m_scale); 187 | case RotationRole: return QVariant::fromValue(desc.m_rotation); 188 | case OpacityRole: return QVariant::fromValue(desc.m_opacity); 189 | case CropRectangleRole: return QVariant::fromValue(desc.m_cropRectangle); 190 | case TextureRotationRole: return QVariant::fromValue(desc.m_textureRotation); 191 | case SubtitleSourceRole: return QVariant::fromValue(desc.m_subtitleSource); 192 | default: return QVariant(); 193 | } 194 | } 195 | 196 | 197 | bool VideoObjectModel::setData(QModelIndex const &p_index, const QVariant &p_value, int p_role) 198 | { 199 | int idx = p_index.row(); 200 | 201 | // If the index is invalid, return an empty value. 202 | if ((idx < 0) || (idx >= int(m_descriptions.size()))) 203 | return false; 204 | 205 | Description & desc = m_descriptions[idx]; 206 | bool valueGotChanged = false; 207 | 208 | #define ROLE_VALUE_TO_DESC(VALNAME, VALTYPE) \ 209 | setDataHelper < VALTYPE > (desc.m_ ##VALNAME, p_value, valueGotChanged) 210 | 211 | switch (p_role) 212 | { 213 | case UrlRole: ROLE_VALUE_TO_DESC(url, QUrl); break; 214 | case MeshTypeRole: ROLE_VALUE_TO_DESC(meshType, QString); break; 215 | case ScaleRole: ROLE_VALUE_TO_DESC(scale, float); break; 216 | case RotationRole: ROLE_VALUE_TO_DESC(rotation, QQuaternion); break; 217 | case OpacityRole: ROLE_VALUE_TO_DESC(opacity, float); break; 218 | case CropRectangleRole: ROLE_VALUE_TO_DESC(cropRectangle, QRect); break; 219 | case TextureRotationRole: ROLE_VALUE_TO_DESC(textureRotation, int); break; 220 | case SubtitleSourceRole: ROLE_VALUE_TO_DESC(subtitleSource, SubtitleSource); break; 221 | default: return false; 222 | } 223 | 224 | if (valueGotChanged) 225 | emit dataChanged(p_index, p_index, { p_role }); 226 | 227 | return true; 228 | } 229 | 230 | 231 | Qt::ItemFlags VideoObjectModel::flags(QModelIndex const &) const 232 | { 233 | return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; 234 | } 235 | 236 | 237 | QVariant VideoObjectModel::headerData(int, Qt::Orientation, int) const 238 | { 239 | return QVariant(); 240 | } 241 | 242 | 243 | QModelIndex VideoObjectModel::parent(QModelIndex const &) const 244 | { 245 | return QModelIndex(); 246 | } 247 | 248 | 249 | QHash < int, QByteArray > VideoObjectModel::roleNames() const 250 | { 251 | // The role names have an "obj" prefix to make sure there is no name 252 | // collision with QtQuick property names ("opacity" is one example). 253 | // It also makes QML code a bit more readable, since if there's an 254 | // "obj" prefix, it is immediately clear that this is an item data 255 | // role value. 256 | QHash < int, QByteArray > names; 257 | names[UrlRole] = "objUrl"; 258 | names[MeshTypeRole] = "objMeshType"; 259 | names[ScaleRole] = "objScale"; 260 | names[RotationRole] = "objRotation"; 261 | names[OpacityRole] = "objOpacity"; 262 | names[CropRectangleRole] = "objCropRectangle"; 263 | names[TextureRotationRole] = "objTextureRotation"; 264 | names[SubtitleSourceRole] = "objSubtitleSource"; 265 | return names; 266 | } 267 | 268 | 269 | int VideoObjectModel::rowCount(QModelIndex const &) const 270 | { 271 | return m_descriptions.size(); 272 | } 273 | 274 | 275 | int VideoObjectModel::getCount() const 276 | { 277 | return getNumDescriptions(); 278 | } 279 | 280 | 281 | } // namespace qtglviddemo end 282 | -------------------------------------------------------------------------------- /src/scene/VideoObjectModel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_VIDEO_OBJECT_MODEL_HPP 21 | #define QTGLVIDDEMO_VIDEO_OBJECT_MODEL_HPP 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | namespace qtglviddemo 32 | { 33 | 34 | 35 | /** 36 | * Qt list data model containing a list of video object descriptions. 37 | * 38 | * The entries in this data model only describe the video objects. 39 | * They do not contain any 3D mesh data or OpenGL resources. The 40 | * descriptions instead contain parameters such as 3D position, 41 | * video URL, mesh type string, etc. 42 | * 43 | * This model is designed to be usable in QML together with Qt Quick 2 44 | * views and VideoObjectItem as the delegate. That is, Qt Quick 2 45 | * views are connected to this model, and they use VideoObjectItem 46 | * to display the entries of this model. 47 | * 48 | * This model also allows for modifying the parameters of a description. 49 | * If for example the user rotates a video object on screen, the 50 | * rotation quaternion of the corresponding description in this model 51 | * is updated. 52 | * 53 | * Since this is derived from QAbstractListModel, entries are modeled 54 | * as "rows". So, 3 entries mean 3 rows for example. When QModelIndex 55 | * is used, only its row index is of any interest. (The list has only 56 | * one "column".) The terms "entry", "description", "row", "item" are 57 | * used interchangeably here. ("item" is not to be confused with 58 | * VideoObjectItem.) 59 | */ 60 | class VideoObjectModel 61 | : public QAbstractListModel 62 | { 63 | Q_OBJECT 64 | /** 65 | * How many descriptions are stored in the model. 66 | * 67 | * This is always equivalent to the return value of 68 | * getNumDescriptions(). 69 | */ 70 | Q_PROPERTY(int count READ getCount NOTIFY countChanged) 71 | 72 | public: 73 | enum class SubtitleSource 74 | { 75 | /** 76 | * Subtitles for the video object shall come from the FIFO. 77 | * (See FifoWatch class for details.) 78 | */ 79 | FIFOSubtitles, 80 | /** 81 | * Subtitles for the video object shall come from the 82 | * video object's associated media player. 83 | */ 84 | MediaSubtitles, 85 | /** 86 | * Subtitles for the video object shall come from system 87 | * stat measurements (CPU usage, memory usage, framerate). 88 | */ 89 | SystemStatsSubtitles 90 | }; 91 | Q_ENUM(SubtitleSource) 92 | 93 | /** 94 | * Video object description. 95 | */ 96 | struct Description 97 | { 98 | /// URL of the media the video object shall play. 99 | QUrl m_url; 100 | 101 | /** 102 | * String containing the type of the mesh the video object 103 | * shall render. See the GLResources::getMesh() documenation 104 | * for the list of valid mesh types. 105 | */ 106 | QString m_meshType; 107 | 108 | /// Scale factor the video object's transform shall use. 109 | float m_scale; 110 | /// Rotation quaternion the video object's transform shall use. 111 | QQuaternion m_rotation; 112 | /// Opacity that shall be used for rendering the VideoObjectItem. 113 | float m_opacity; 114 | /// Crop rectangle the video object's video material shall use. 115 | QRect m_cropRectangle; 116 | /// Texture rotation angle the video object's video material shall use. 117 | int m_textureRotation; 118 | 119 | /// Where the video object's subtitles shall come from. 120 | SubtitleSource m_subtitleSource; 121 | 122 | /** 123 | * Constructor. 124 | * 125 | * Creates a description with mesh type "cube", scale factor 126 | * 1, opacity 1, crop rectangle (0,0,100,100), a texture 127 | * rotation angle of 0 degrees, and MediaSubtitles as the 128 | * subtitle source. 129 | */ 130 | Description(); 131 | }; 132 | 133 | /** 134 | * Model item data roles. 135 | * 136 | * A description has several parameters. These parameters are exposed 137 | * to QML through item data roles. For each field in Description 138 | * there is one user item data role defined here. 139 | */ 140 | enum DescriptionRoles 141 | { 142 | UrlRole = Qt::UserRole + 1, 143 | 144 | MeshTypeRole, 145 | 146 | ScaleRole, 147 | RotationRole, 148 | 149 | OpacityRole, 150 | CropRectangleRole, 151 | 152 | TextureRotationRole, 153 | 154 | SubtitleSourceRole 155 | }; 156 | 157 | /** 158 | * Constructor. 159 | * 160 | * @param p_parent Parent QObject 161 | */ 162 | explicit VideoObjectModel(QObject *p_parent = nullptr); 163 | 164 | /** 165 | * Adds a description and sets its URL to the given URL. 166 | * 167 | * This produces a description with default values, except for the 168 | * URL, which is set to p_url. Internally, it calls addDescription() 169 | * for adding the new description. 170 | * 171 | * This returns an integer which can be used as list index in QML scripts. 172 | * 173 | * @param p_url URL to set in the new description. 174 | */ 175 | Q_INVOKABLE int addFromURL(QUrl p_url); 176 | /** 177 | * Adds a description for a V4L2 device node. 178 | * 179 | * This is a convenience function to add descriptions with URLs 180 | * that refer to video capture devices. Depending on the platform, 181 | * these URLs may start with v4l2:// , or with something else. 182 | * 183 | * @param p_deviceNode Device node to use in the new description. 184 | */ 185 | Q_INVOKABLE int addV4L2DeviceNode(QString const &p_deviceNode); 186 | /** 187 | * Removes the description at the given index. 188 | * 189 | * Internally, this calls removeDescription() for removing 190 | * the description at the given index. 191 | * 192 | * @param p_index Index of the description to remove. Must be >= 0 193 | * and less than the value of the count property. 194 | */ 195 | Q_INVOKABLE void remove(int const p_index); 196 | 197 | /** 198 | * Adds a description to the list. 199 | * 200 | * Since this effectively adds rows to the list (rows = entries), 201 | * this will cause rowsAboutToBeInserted() and rowsInserted() signals 202 | * to be emitted. Qt Quick 2 views use this to update themselves. 203 | */ 204 | void addDescription(Description p_description); 205 | /** 206 | * Retrieves a const reference to the description at the given index. 207 | * 208 | * @param p_index Index of the description to remove. Must be >= 0 209 | * and less than what getNumDescriptions() returns. 210 | */ 211 | Description const & getDescription(std::size_t const p_index) const; 212 | /** 213 | * Returns the number of description in this list model. 214 | */ 215 | std::size_t getNumDescriptions() const; 216 | /** 217 | * Removes a description at the given index. 218 | * 219 | * Since this effectively adds rows to the list (rows = entries), 220 | * this will cause rowsAboutToBeRemoved() and rowsRemoved() signals 221 | * to be emitted. Qt Quick 2 views use this to update themselves. 222 | * 223 | * @param p_index Index of the description to remove. Must be >= 0 224 | * and less than what getNumDescriptions() returns. 225 | */ 226 | void removeDescription(std::size_t const p_index); 227 | 228 | // QAbstractItemModel and QAbstractListModel overrides. 229 | virtual QVariant data(QModelIndex const &p_index, int p_role = Qt::DisplayRole) const override; 230 | virtual bool setData(QModelIndex const &p_index, const QVariant &p_value, int p_role = Qt::EditRole) override; 231 | virtual Qt::ItemFlags flags(QModelIndex const &index) const override; 232 | virtual QVariant headerData(int p_section, Qt::Orientation p_orientation, int p_role = Qt::DisplayRole) const override; 233 | virtual QModelIndex parent(QModelIndex const &p_index) const override; 234 | virtual QHash < int, QByteArray > roleNames() const override; 235 | virtual int rowCount(QModelIndex const &p_parent = QModelIndex()) const override; 236 | 237 | 238 | signals: 239 | void countChanged(); 240 | 241 | 242 | private: 243 | // Returns the current count of descriptions. This function exists only 244 | // because getNumDescriptions() can't be directly used in the Q_PROPERTY() 245 | // definition above (because of the type of the return value). 246 | int getCount() const; 247 | 248 | typedef std::vector < Description > Descriptions; 249 | Descriptions m_descriptions; 250 | }; 251 | 252 | 253 | } // namespace qtglviddemo end 254 | 255 | 256 | #endif 257 | -------------------------------------------------------------------------------- /src/videomaterial/GLVIVDirectTextureExtension.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "GLVIVDirectTextureExtension.hpp" 26 | 27 | 28 | Q_DECLARE_LOGGING_CATEGORY(lcQtGLVidDemo) 29 | 30 | 31 | namespace qtglviddemo 32 | { 33 | 34 | 35 | VivDirectTextureFuncs::VivDirectTextureFuncs(QOpenGLContext *p_context) 36 | { 37 | assert(p_context != nullptr); 38 | 39 | glTexDirectVIV = reinterpret_cast < PFNGLTEXDIRECTVIVPROC > (p_context->getProcAddress(QByteArray("glTexDirectVIV"))); 40 | glTexDirectVIVMap = reinterpret_cast < PFNGLTEXDIRECTVIVMAPPROC > (p_context->getProcAddress(QByteArray("glTexDirectVIVMap"))); 41 | glTexDirectTiledMapVIV = reinterpret_cast < PFNGLTEXDIRECTTILEDMAPVIVPROC > (p_context->getProcAddress(QByteArray("glTexDirectTiledMapVIV"))); 42 | glTexDirectInvalidateVIV = reinterpret_cast < PFNGLTEXDIRECTINVALIDATEVIVPROC > (p_context->getProcAddress(QByteArray("glTexDirectInvalidateVIV"))); 43 | } 44 | 45 | 46 | bool isVivDirectTextureSupported(QOpenGLContext *p_context) 47 | { 48 | assert(p_context != nullptr); 49 | 50 | // Newer Vivante drivers call the extension GL_VIV_tex_direct instead 51 | // of GL_VIV_direct_texture, even though it is the same extension. 52 | if (p_context->hasExtension(QByteArray("GL_VIV_direct_texture"))) 53 | { 54 | qCDebug(lcQtGLVidDemo) << "GL_VIV_direct_texture supported"; 55 | return true; 56 | } 57 | else if (p_context->hasExtension(QByteArray("GL_VIV_tex_direct"))) 58 | { 59 | qCDebug(lcQtGLVidDemo) << "GL_VIV_tex_direct supported"; 60 | return true; 61 | } 62 | else 63 | { 64 | qCDebug(lcQtGLVidDemo) << "Neither GL_VIV_direct_texture nor GL_VIV_tex_direct supported"; 65 | return false; 66 | } 67 | } 68 | 69 | 70 | } // namespace qtglviddemo end 71 | -------------------------------------------------------------------------------- /src/videomaterial/GLVIVDirectTextureExtension.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef IMX_GL_VIV_DIRECT_TEXTURE_H 21 | #define IMX_GL_VIV_DIRECT_TEXTURE_H 22 | 23 | // Use our own definitions to make sure they are the 24 | // same no matter what Vivante driver version is used 25 | #define GL_VIV_direct_texture 1 26 | 27 | #include 28 | #include 29 | 30 | 31 | #define GL_VIV_YV12 0x8FC0 32 | #define GL_VIV_NV12 0x8FC1 33 | #define GL_VIV_YUY2 0x8FC2 34 | #define GL_VIV_UYVY 0x8FC3 35 | #define GL_VIV_NV21 0x8FC4 36 | #define GL_VIV_I420 0x8FC5 37 | 38 | 39 | #ifndef GL_APICALL 40 | #define GL_APICALL KHRONOS_APICALL 41 | #endif 42 | 43 | #ifndef GL_APIENTRY 44 | #define GL_APIENTRY KHRONOS_APIENTRY 45 | #endif 46 | 47 | #ifndef GL_APIENTRYP 48 | #define GL_APIENTRYP GL_APIENTRY* 49 | #endif 50 | 51 | 52 | typedef void (GL_APIENTRYP PFNGLTEXDIRECTVIVPROC) (GLenum Target, GLsizei Width, GLsizei Height, GLenum Format, GLvoid ** Pixels); 53 | typedef void (GL_APIENTRYP PFNGLTEXDIRECTVIVMAPPROC) (GLenum Target, GLsizei Width, GLsizei Height, GLenum Format, GLvoid ** Logical, const GLuint * Physical); 54 | typedef void (GL_APIENTRYP PFNGLTEXDIRECTTILEDMAPVIVPROC) (GLenum Target, GLsizei Width, GLsizei Height, GLenum Format, GLvoid ** Logical, const GLuint * Physical); 55 | typedef void (GL_APIENTRYP PFNGLTEXDIRECTINVALIDATEVIVPROC) (GLenum Target); 56 | 57 | 58 | class QOpenGLContext; 59 | 60 | 61 | namespace qtglviddemo 62 | { 63 | 64 | 65 | /** 66 | * Vivante direct texture extension functions. 67 | * 68 | * Do not attempt to create an instance of this class if isVivDirectTextureSupported() 69 | * returns false. Otherwise, the function pointer values are undefined. 70 | */ 71 | struct VivDirectTextureFuncs 72 | { 73 | PFNGLTEXDIRECTVIVPROC glTexDirectVIV; 74 | PFNGLTEXDIRECTVIVMAPPROC glTexDirectVIVMap; 75 | PFNGLTEXDIRECTTILEDMAPVIVPROC glTexDirectTiledMapVIV; 76 | PFNGLTEXDIRECTINVALIDATEVIVPROC glTexDirectInvalidateVIV; 77 | 78 | /** 79 | * Constructor. 80 | * 81 | * Sets the pointers to the Vivante direct texture extension functions. 82 | * The specified OpenGL context must be valid when this constructor runs. 83 | * 84 | * @param p_context OpenGL context to use for getting the functions. 85 | * Must not be null. 86 | */ 87 | explicit VivDirectTextureFuncs(QOpenGLContext *p_context); 88 | }; 89 | 90 | 91 | /** 92 | * Checks if the Vivante direct texture extension is supported. 93 | * 94 | * This returns true if the extension is supported, false otherwise. 95 | * 96 | * The specified OpenGL context must be valid when this function is called. 97 | * 98 | * @param p_context OpenGL context to use for checking. Must not be null. 99 | */ 100 | bool isVivDirectTextureSupported(QOpenGLContext *p_context); 101 | 102 | 103 | } // namespace qtglviddemo end 104 | 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/videomaterial/VideoMaterialProviderGeneric.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include "VideoMaterialProviderGeneric.hpp" 23 | 24 | 25 | namespace qtglviddemo 26 | { 27 | 28 | 29 | VideoMaterialProviderGeneric::VideoMaterialProviderGeneric(QOpenGLContext *p_glcontext) 30 | : VideoMaterialProvider(p_glcontext, { GST_VIDEO_FORMAT_RGBx }) 31 | , m_videoInfoChanged(true) 32 | { 33 | } 34 | 35 | 36 | void VideoMaterialProviderGeneric::setVideoInfoChangedFlag(bool const p_flag) 37 | { 38 | m_videoInfoChanged = p_flag; 39 | } 40 | 41 | 42 | void VideoMaterialProviderGeneric::uploadGstFrame(VideoMaterial &p_videoMaterial, GstVideoFrame &p_vframe) 43 | { 44 | // Call glTexImage2D() if the video info changed or if this the 45 | // first upload call. Otherwise, call glTexSubImage2D(); which 46 | // is faster because it does not have to reallocate the texture. 47 | 48 | if (m_videoInfoChanged) 49 | { 50 | m_glcontext->functions()->glTexImage2D( 51 | GL_TEXTURE_2D, 52 | 0, 53 | GL_RGBA, 54 | p_videoMaterial.getTotalWidth(), p_videoMaterial.getTotalHeight(), 55 | 0, 56 | GL_RGBA, 57 | GL_UNSIGNED_BYTE, 58 | p_vframe.data[0] 59 | ); 60 | 61 | m_videoInfoChanged = false; 62 | } 63 | else 64 | { 65 | m_glcontext->functions()->glTexSubImage2D( 66 | GL_TEXTURE_2D, 67 | 0, 68 | 0, 0, 69 | p_videoMaterial.getTotalWidth(), p_videoMaterial.getTotalHeight(), 70 | GL_RGBA, 71 | GL_UNSIGNED_BYTE, 72 | p_vframe.data[0] 73 | ); 74 | } 75 | } 76 | 77 | 78 | } // namespace qtglviddemo end 79 | -------------------------------------------------------------------------------- /src/videomaterial/VideoMaterialProviderGeneric.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_VIDEO_MATERIAL_PROVIDER_GENERIC_HPP 21 | #define QTGLVIDDEMO_VIDEO_MATERIAL_PROVIDER_GENERIC_HPP 22 | 23 | #include "VideoMaterial.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | /** 31 | * Video material provider subclass which uploads video frames with glTex(Sub)Image2D. 32 | * 33 | * This is considered a "generic" provider because all OpenGL implementations support 34 | * the glTexImage2D() and glTexSubImage2D() functions. So, if the implementation has 35 | * no specialized video frame upload functions, this can be used as a fallback. 36 | * However, specialized providers should always be preferred, since (a) glTexImage2D() 37 | * and glTexSubImage2D() only support RGB formats, forcing pixel format conversions 38 | * prior to uploading, and (b) they copy the video frame pixels, which requires CPU work. 39 | * 40 | * Since, as said, these functions only support RGB(A) data, the list of supported 41 | * formats contains only one entry, GST_VIDEO_FORMAT_RGBx . 42 | */ 43 | class VideoMaterialProviderGeneric 44 | : public VideoMaterialProvider 45 | { 46 | public: 47 | explicit VideoMaterialProviderGeneric(QOpenGLContext *p_glcontext); 48 | 49 | private: 50 | virtual void setVideoInfoChangedFlag(bool const p_flag) override; 51 | virtual void uploadGstFrame(VideoMaterial &p_videoMaterial, GstVideoFrame &p_vframe) override; 52 | 53 | bool m_videoInfoChanged; 54 | }; 55 | 56 | 57 | } // namespace qtglviddemo end 58 | 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/videomaterial/VideoMaterialProviderVivante.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include "GLVIVDirectTextureExtension.hpp" 24 | #include "VideoMaterialProviderVivante.hpp" 25 | 26 | 27 | namespace qtglviddemo 28 | { 29 | 30 | 31 | namespace 32 | { 33 | 34 | 35 | GLenum toVivPixelFormat(GstVideoFormat p_gstformat) 36 | { 37 | switch (p_gstformat) 38 | { 39 | case GST_VIDEO_FORMAT_I420: return GL_VIV_I420; 40 | case GST_VIDEO_FORMAT_YV12: return GL_VIV_YV12; 41 | case GST_VIDEO_FORMAT_NV12: return GL_VIV_NV12; 42 | case GST_VIDEO_FORMAT_NV21: return GL_VIV_NV21; 43 | case GST_VIDEO_FORMAT_YUY2: return GL_VIV_YUY2; 44 | case GST_VIDEO_FORMAT_UYVY: return GL_VIV_UYVY; 45 | case GST_VIDEO_FORMAT_RGB16: return GL_RGB565; 46 | case GST_VIDEO_FORMAT_RGBA: return GL_RGBA; 47 | case GST_VIDEO_FORMAT_BGRA: return GL_BGRA_EXT; 48 | case GST_VIDEO_FORMAT_RGBx: return GL_RGBA; 49 | case GST_VIDEO_FORMAT_BGRx: return GL_BGRA_EXT; 50 | default: assert(false); 51 | } 52 | } 53 | 54 | 55 | } // unnamed namespace end 56 | 57 | 58 | VideoMaterialProviderVivante::VideoMaterialProviderVivante(QOpenGLContext *p_glcontext) 59 | : VideoMaterialProvider(p_glcontext, { 60 | GST_VIDEO_FORMAT_I420, 61 | GST_VIDEO_FORMAT_YV12, 62 | GST_VIDEO_FORMAT_NV12, 63 | GST_VIDEO_FORMAT_NV21, 64 | GST_VIDEO_FORMAT_YUY2, 65 | GST_VIDEO_FORMAT_UYVY, 66 | GST_VIDEO_FORMAT_RGB16, 67 | GST_VIDEO_FORMAT_RGBA, 68 | GST_VIDEO_FORMAT_BGRA, 69 | GST_VIDEO_FORMAT_RGBx, 70 | GST_VIDEO_FORMAT_BGRx 71 | }) 72 | { 73 | m_vivFuncs = new VivDirectTextureFuncs(p_glcontext); 74 | } 75 | 76 | 77 | void VideoMaterialProviderVivante::uploadGstFrame(VideoMaterial &p_videoMaterial, GstVideoFrame &p_vframe) 78 | { 79 | // Pass on the virtual address, and 0 as the physical address. If 80 | // we could get the address to the video frame's physically contiguous 81 | // memory block, we'd pass it on, but the new GstPhysMemory structure 82 | // has been introduced in 1.12, and it was in -bad there, so it is 83 | // unstable in 1.12. 1.14 is not out yet. So we do not use it for now. 84 | // Plus, we do not really need it. It would slightly improve performance 85 | // if the physical address were set, but the extension is capable of 86 | // figuring it out from the virtual address. 87 | 88 | GLvoid *virtualAddr = p_vframe.data[0]; 89 | GLuint physicalAddr = ~0U; 90 | 91 | // Map the GstBuffer memory to the texture. 92 | m_vivFuncs->glTexDirectVIVMap( 93 | GL_TEXTURE_2D, 94 | p_videoMaterial.getTotalWidth(), p_videoMaterial.getTotalHeight(), 95 | toVivPixelFormat(GST_VIDEO_INFO_FORMAT(&(p_vframe.info))), 96 | &virtualAddr, 97 | &physicalAddr 98 | ); 99 | // Invalidate the texture. This is necessary to flush any GPU or CPU 100 | // cache lines filled with texture data that is now invalid since 101 | // we changed/created the mapping above. 102 | m_vivFuncs->glTexDirectInvalidateVIV(GL_TEXTURE_2D); 103 | } 104 | 105 | 106 | } // namespace qtglviddemo end 107 | -------------------------------------------------------------------------------- /src/videomaterial/VideoMaterialProviderVivante.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Qt5 OpenGL video demo application 3 | * Copyright (C) 2018 Carlos Rafael Giani < dv AT pseudoterminal DOT org > 4 | * 5 | * qtglviddemo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef QTGLVIDDEMO_VIDEO_MATERIAL_PROVIDER_VIVANTE_HPP 21 | #define QTGLVIDDEMO_VIDEO_MATERIAL_PROVIDER_VIVANTE_HPP 22 | 23 | #include "VideoMaterial.hpp" 24 | 25 | 26 | namespace qtglviddemo 27 | { 28 | 29 | 30 | class VivDirectTextureFuncs; 31 | 32 | 33 | /** 34 | * Video material provider subclass which uploads video frames with Vivante direct textures. 35 | * 36 | * This provider can only be used if the isVivDirectTextureSupported() function 37 | * (declared in GLVIVDirectTextureExtension.hpp) returns true. Otherwise, the 38 | * Vivante direct texture extension is not present, and this provider won't work. 39 | * 40 | * This provider does not actually upload frame pixels into the texture. Instead, 41 | * it associates the video frame pixels stored in the GstBuffer with the texture, 42 | * meaning that during rendering, texels are directly fetched from the GstBuffer's 43 | * memory block. If the GstBuffer's memory block is physically contiguous, this 44 | * mapping will cause the GPU to fetch the texels via DMA. In addition, the 45 | * Vivante direct textures support transparent YUV->RGB conversions. In sum, they 46 | * are an efficient way to display videos as textures, since there is little CPU 47 | * work done (no pixels are copied, no color space conversion is done by the CPU). 48 | * If the hardware has a Vivante GPU, and the closed-source Vivante drivers are 49 | * installed, use this provider. Do NOT use this provider if the system uses the 50 | * open-source etnaviv driver instead of the closed-source Vivante ones, since 51 | * etnaviv does not expose the Vivante direct texture extension. 52 | */ 53 | class VideoMaterialProviderVivante 54 | : public VideoMaterialProvider 55 | { 56 | public: 57 | explicit VideoMaterialProviderVivante(QOpenGLContext *p_glcontext); 58 | 59 | private: 60 | virtual void uploadGstFrame(VideoMaterial &p_videoMaterial, GstVideoFrame &p_vframe) override; 61 | 62 | VivDirectTextureFuncs *m_vivFuncs; 63 | }; 64 | 65 | 66 | } // namespace qtglviddemo end 67 | 68 | 69 | #endif 70 | --------------------------------------------------------------------------------