diff --git a/.gitmodules b/.gitmodules
index fe2788c33d76ea294a02f0fbfba5d5273588fda4..52c11033a1bc913014d480ba9fba9ec0720a0bfe 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,5 +8,5 @@
path = libs/OpenSSL/android_openssl
url = https://github.com/Auterion/android_openssl
[submodule "libs/gst-plugins-good"]
- path = libs/gst-plugins-good
+ path = libs/qmlglsink/gst-plugins-good
url = https://github.com/mavlink/gst-plugins-good.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e2f00e718c229f91827475b1616416eaae0efd15..74ccd3640ab04d46fc0e1f06424f5517e744bc2d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,8 +102,6 @@ if (GST_FOUND)
-DQGC_GST_TAISYNC_ENABLED
-DQGC_GST_MICROHARD_ENABLED
)
-
- include(qmlglsink.cmake)
endif()
add_definitions(
diff --git a/VideoReceiverApp/CMakeLists.txt b/VideoReceiverApp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2630a49f0ca593314de54aed84feb312a4476687
--- /dev/null
+++ b/VideoReceiverApp/CMakeLists.txt
@@ -0,0 +1,153 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(VideoReceiverApp LANGUAGES C CXX)
+
+set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel;Coverage")
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+include(FeatureSummary)
+
+if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ add_compile_options(-Wall -Wextra -Wno-address-of-packed-member)
+endif()
+
+# CMake build type
+# Debug Release RelWithDebInfo MinSizeRel Coverage
+if (NOT CMAKE_BUILD_TYPE)
+ # default to release with debug symbols
+ set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE)
+endif()
+
+set(QGC_ROOT ${CMAKE_SOURCE_DIR}/..)
+
+# Add folder where are supportive functions
+list(APPEND CMAKE_MODULE_PATH ${QGC_ROOT}/cmake)
+
+# Configure Qt5 to get necessary variables
+include(Qt5QGCConfiguration)
+message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
+message(STATUS "Qt version: ${QT_VERSION}")
+message(STATUS "Qt spec: ${QT_MKSPEC}")
+
+set(COMPANY "Auterion")
+set(COPYRIGHT "Copyright (c) 2020 VideoReceiverApp. All rights reserved.")
+set(IDENTIFIER "labs.auterion.VideoReceiverApp")
+
+include(Git)
+message(STATUS "VideoReceiverApp version: ${GIT_VERSION}")
+
+#=============================================================================
+# ccache
+#
+option(CCACHE "Use ccache if available" ON)
+find_program(CCACHE_PROGRAM ccache)
+if (CCACHE AND CCACHE_PROGRAM AND NOT DEFINED ENV{CCACHE_DISABLE})
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
+endif()
+
+#=============================================================================
+# Compile QML
+#
+option(COMPILE_QML "Pre-compile QML files using the Qt Quick compiler." FALSE)
+add_feature_info(COMPILE_QML COMPILE_QML "Pre-compile QML files using the Qt Quick compiler.")
+if(COMPILE_QML)
+ find_package(Qt5QuickCompiler)
+
+ set_package_properties(Qt5QuickCompiler PROPERTIES
+ DESCRIPTION "Pre-compile QML files using the Qt Quick compiler."
+ TYPE OPTIONAL
+ )
+endif()
+
+#=============================================================================
+# Debug QML
+#
+option(DEBUG_QML "Build VideoReceiverApp with QML debugging/profiling support." FALSE)
+add_feature_info(DEBUG_QML DEBUG_QML "Build VideoReceiverApp with QML debugging/profiling support.")
+if(DEBUG_QML)
+ message(STATUS "To enable the QML debugger/profiler, run with: '-qmljsdebugger=port:1234'")
+ add_definitions(-DQMLJSDEBUGGER)
+ add_definitions(-DQT_DECLARATIVE_DEBUG)
+ add_definitions(-DQT_QML_DEBUG)
+endif()
+
+#=============================================================================
+# GStreamer
+#
+find_package(PkgConfig)
+pkg_check_modules(GST
+ gstreamer-1.0>=1.14
+ gstreamer-video-1.0>=1.14
+ gstreamer-gl-1.0>=1.14
+ egl
+ )
+
+if (GST_FOUND)
+ include_directories(
+ ${GST_INCLUDE_DIRS}
+ )
+endif()
+
+#=============================================================================
+# Qt5
+#
+find_package(Qt5 ${QT_VERSION}
+ COMPONENTS
+ Bluetooth
+ Charts
+ Concurrent
+ Core
+ Location
+ Multimedia
+ Network
+ Positioning
+ Quick
+ QuickWidgets
+ OpenGL
+ Sql
+ Svg
+ Test
+ TextToSpeech
+ Widgets
+ Xml
+ REQUIRED
+ HINTS
+ ${QT_LIBRARY_HINTS}
+ )
+
+# Sets the default flags for compilation and linking.
+include(CompileOptions)
+
+include_directories(
+ ${QGC_ROOT}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${Qt5Location_PRIVATE_INCLUDE_DIRS}
+ VideoReceiver
+ )
+
+add_subdirectory(${QGC_ROOT}/libs/qmlglsink qmlglsink.build)
+add_subdirectory(${QGC_ROOT}/src/VideoReceiver VideoReceiver.build)
+
+set(VIDEORECIVERAPP_SOURCES main.cpp ${QGC_ROOT}/src/QGCLoggingCategory.cc)
+set(VIDEORECIVERAPP_RESOURCES qml.qrc)
+
+if(ANDROID)
+ add_library(VideoReceiverApp SHARED ${VIDEORECIVERAPP_SOURCES} ${VIDEORECIVERAPP_RESOURCES})
+else()
+ add_executable(VideoReceiverApp ${VIDEORECIVERAPP_SOURCES} ${VIDEORECIVERAPP_RESOURCES})
+endif()
+
+target_link_libraries(VideoReceiverApp
+ PRIVATE
+ VideoReceiver
+ Qt5::Core
+ Qt5::Multimedia
+ Qt5::OpenGL
+ Qt5::Quick
+ Qt5::QuickWidgets
+)
diff --git a/VideoReceiverApp/Info.plist b/VideoReceiverApp/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..29e73ca474d1a9eb1e7f7bcbcb24dde32010dfed
--- /dev/null
+++ b/VideoReceiverApp/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ CFBundleDisplayName
+ QQmlGlSinkTest
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ NSHumanReadableCopyright
+ Open Source Flight Systems GmbH - Internal Build
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ labs.auterion.VideoReceiverApp
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 0.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIFileSharingEnabled
+
+
+
diff --git a/VideoReceiverApp/android/AndroidManifest.xml b/VideoReceiverApp/android/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dee6fa75d9356b7b1680d85a152d7ef79e61287e
--- /dev/null
+++ b/VideoReceiverApp/android/AndroidManifest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VideoReceiverApp/android/AndroidManifest.xml.in b/VideoReceiverApp/android/AndroidManifest.xml.in
new file mode 100644
index 0000000000000000000000000000000000000000..2d9c543f253fb8a44b98dfd324e7be18d897efe2
--- /dev/null
+++ b/VideoReceiverApp/android/AndroidManifest.xml.in
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VideoReceiverApp/android/GooglePlayDailyIcon.png b/VideoReceiverApp/android/GooglePlayDailyIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..087667afdbbf7b49aa7029ea92a19ab3ee6e944e
Binary files /dev/null and b/VideoReceiverApp/android/GooglePlayDailyIcon.png differ
diff --git a/VideoReceiverApp/android/GooglePlayFeatureGraphic.png b/VideoReceiverApp/android/GooglePlayFeatureGraphic.png
new file mode 100644
index 0000000000000000000000000000000000000000..e49b9949cc3940a037e1a47782606e3a5603f641
Binary files /dev/null and b/VideoReceiverApp/android/GooglePlayFeatureGraphic.png differ
diff --git a/VideoReceiverApp/android/GooglePlayScreenShot1.jpg b/VideoReceiverApp/android/GooglePlayScreenShot1.jpg
new file mode 100755
index 0000000000000000000000000000000000000000..4adfd44fbdd87891ff6b823e51472c5ac0cc9bf0
Binary files /dev/null and b/VideoReceiverApp/android/GooglePlayScreenShot1.jpg differ
diff --git a/VideoReceiverApp/android/GooglePlayScreenShot2.jpg b/VideoReceiverApp/android/GooglePlayScreenShot2.jpg
new file mode 100755
index 0000000000000000000000000000000000000000..04f2ad672e859f6eeb3d0b49ef3594de5254485c
Binary files /dev/null and b/VideoReceiverApp/android/GooglePlayScreenShot2.jpg differ
diff --git a/VideoReceiverApp/android/Google_Play_Android_Developer-4432a3c4f5d1.json.enc b/VideoReceiverApp/android/Google_Play_Android_Developer-4432a3c4f5d1.json.enc
new file mode 100644
index 0000000000000000000000000000000000000000..dae400d87ac5e44816df2b3ffecfdba57d09dd12
Binary files /dev/null and b/VideoReceiverApp/android/Google_Play_Android_Developer-4432a3c4f5d1.json.enc differ
diff --git a/VideoReceiverApp/android/android_release.keystore b/VideoReceiverApp/android/android_release.keystore
new file mode 100644
index 0000000000000000000000000000000000000000..a349ecf790ccfab95c7a62fed13a4f10584573f7
Binary files /dev/null and b/VideoReceiverApp/android/android_release.keystore differ
diff --git a/VideoReceiverApp/android/build.gradle b/VideoReceiverApp/android/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..dbe261805e2779d80f1481aa19148ba67a7e4817
--- /dev/null
+++ b/VideoReceiverApp/android/build.gradle
@@ -0,0 +1,64 @@
+buildscript {
+
+ repositories {
+ maven {
+ url "http://repo1.maven.org/maven2"
+ }
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.0'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+android {
+ /*******************************************************
+ * The following variables:
+ * - androidBuildToolsVersion,
+ * - androidCompileSdkVersion
+ * - qt5AndroidDir - holds the path to qt android files
+ * needed to build any Qt application
+ * on Android.
+ *
+ * are defined in gradle.properties file. This file is
+ * updated by QtCreator and androiddeployqt tools.
+ * Changing them manually might break the compilation!
+ *******************************************************/
+
+ compileSdkVersion androidCompileSdkVersion.toInteger()
+
+ buildToolsVersion androidBuildToolsVersion
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
+ aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
+ res.srcDirs = [qt5AndroidDir + '/res', 'res']
+ resources.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ assets.srcDirs = ['assets']
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+
+ aaptOptions {
+ cruncherEnabled = false
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+}
diff --git a/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.jar b/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd
Binary files /dev/null and b/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.properties b/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0c71e760dc93830dd3411fe50d6f5c86bf0a8f4d
--- /dev/null
+++ b/VideoReceiverApp/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/VideoReceiverApp/android/gradlew b/VideoReceiverApp/android/gradlew
new file mode 100755
index 0000000000000000000000000000000000000000..91a7e269e19dfc62e27137a0b57ef3e430cee4fd
--- /dev/null
+++ b/VideoReceiverApp/android/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/VideoReceiverApp/android/gradlew.bat b/VideoReceiverApp/android/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..8a0b282aa6885fb573c106b3551f7275c5f17e8e
--- /dev/null
+++ b/VideoReceiverApp/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/VideoReceiverApp/android/res/drawable-hdpi/icon.png b/VideoReceiverApp/android/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6323cd90f2f6323161df8f449a9f811ff5a071f
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-hdpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/drawable-ldpi/icon.png b/VideoReceiverApp/android/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..8676e6502c749cf0015a93c47c76c42d43b251ae
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-ldpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/drawable-mdpi/icon.png b/VideoReceiverApp/android/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c7603491eec475c6f6d6f4ad902f4b6adabffc
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-mdpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/drawable-xhdpi/icon.png b/VideoReceiverApp/android/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..58a1454d6a8c74b6edade18acc4d0e5f4fc4ce7e
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-xhdpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/drawable-xxhdpi/icon.png b/VideoReceiverApp/android/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f74d76f41f81bf844e0b2875f36e94003acc6474
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-xxhdpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/drawable-xxxhdpi/icon.png b/VideoReceiverApp/android/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f74d76f41f81bf844e0b2875f36e94003acc6474
Binary files /dev/null and b/VideoReceiverApp/android/res/drawable-xxxhdpi/icon.png differ
diff --git a/VideoReceiverApp/android/res/values/libs.xml b/VideoReceiverApp/android/res/values/libs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4d68673cb0673489a670dcfa8cf4cb2cd2e27f7a
--- /dev/null
+++ b/VideoReceiverApp/android/res/values/libs.xml
@@ -0,0 +1,25 @@
+
+
+
+ - https://download.qt-project.org/ministro/android/qt5/qt-5.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VideoReceiverApp/android/src/labs/mavlink/VideoReceiverApp/QGLSinkActivity.java b/VideoReceiverApp/android/src/labs/mavlink/VideoReceiverApp/QGLSinkActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..216da17900b4883b2a008cdb518c915ab033f83f
--- /dev/null
+++ b/VideoReceiverApp/android/src/labs/mavlink/VideoReceiverApp/QGLSinkActivity.java
@@ -0,0 +1,94 @@
+package labs.mavlink.VideoReceiverApp;
+
+/* Copyright 2013 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: http://code.google.com/p/usb-serial-for-android/
+ */
+///////////////////////////////////////////////////////////////////////////////////////////
+// Written by: Mike Goza April 2014
+//
+// These routines interface with the Android USB Host devices for serial port communication.
+// The code uses the usb-serial-for-android software library. The QGCActivity class is the
+// interface to the C++ routines through jni calls. Do not change the functions without also
+// changing the corresponding calls in the C++ routines or you will break the interface.
+//
+////////////////////////////////////////////////////////////////////////////////////////////
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.io.IOException;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.widget.Toast;
+import android.util.Log;
+import android.os.PowerManager;
+import android.os.Bundle;
+import android.app.PendingIntent;
+import android.view.WindowManager;
+import android.os.Bundle;
+import android.bluetooth.BluetoothDevice;
+
+import org.qtproject.qt5.android.bindings.QtActivity;
+import org.qtproject.qt5.android.bindings.QtApplication;
+
+public class QGLSinkActivity extends QtActivity
+{
+ public native void nativeInit();
+
+ // QGLSinkActivity singleton
+ public QGLSinkActivity() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ nativeInit();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ public void onInit(int status) {
+ }
+
+ public void jniOnLoad() {
+ nativeInit();
+ }
+}
diff --git a/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhcCallback.java b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhcCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..53811a9d33b5a206e989489553ab075d46223a6b
--- /dev/null
+++ b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhcCallback.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012, Collabora Ltd.
+ * Author: Youness Alaoui
+ *
+ * Copyright (C) 2015, Collabora Ltd.
+ * Author: Justin Kim
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+package org.freedesktop.gstreamer.androidmedia;
+
+import android.hardware.Camera;
+
+public class GstAhcCallback implements Camera.PreviewCallback,
+ Camera.ErrorCallback,
+ Camera.AutoFocusCallback {
+ public long mUserData;
+ public long mCallback;
+
+ public static native void gst_ah_camera_on_preview_frame(byte[] data, Camera camera,
+ long callback, long user_data);
+ public static native void gst_ah_camera_on_error(int error, Camera camera,
+ long callback, long user_data);
+ public static native void gst_ah_camera_on_auto_focus(boolean success, Camera camera,
+ long callback, long user_data);
+
+ public GstAhcCallback(long callback, long user_data) {
+ mCallback = callback;
+ mUserData = user_data;
+ }
+
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ gst_ah_camera_on_preview_frame(data, camera, mCallback, mUserData);
+ }
+
+ @Override
+ public void onError(int error, Camera camera) {
+ gst_ah_camera_on_error(error, camera, mCallback, mUserData);
+ }
+
+ @Override
+ public void onAutoFocus(boolean success, Camera camera) {
+ gst_ah_camera_on_auto_focus(success, camera, mCallback, mUserData);
+ }
+}
diff --git a/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6fb0158cd5191ecc58a0cbe7f487147ed684ff2
--- /dev/null
+++ b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 SurroundIO
+ * Author: Martin Kelly
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.androidmedia;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+
+public class GstAhsCallback implements SensorEventListener {
+ public long mUserData;
+ public long mSensorCallback;
+ public long mAccuracyCallback;
+
+ public static native void gst_ah_sensor_on_sensor_changed(SensorEvent event,
+ long callback, long user_data);
+ public static native void gst_ah_sensor_on_accuracy_changed(Sensor sensor, int accuracy,
+ long callback, long user_data);
+
+ public GstAhsCallback(long sensor_callback,
+ long accuracy_callback, long user_data) {
+ mSensorCallback = sensor_callback;
+ mAccuracyCallback = accuracy_callback;
+ mUserData = user_data;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ gst_ah_sensor_on_sensor_changed(event, mSensorCallback, mUserData);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ gst_ah_sensor_on_accuracy_changed(sensor, accuracy,
+ mAccuracyCallback, mUserData);
+ }
+}
diff --git a/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAmcOnFrameAvailableListener.java b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAmcOnFrameAvailableListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..f34bcf7ae2b4303cd0127c78b864fc048edb8554
--- /dev/null
+++ b/VideoReceiverApp/android/src/org/freedesktop/gstreamer/androidmedia/GstAmcOnFrameAvailableListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015, Collabora Ltd.
+ * Author: Matthieu Bouron
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+package org.freedesktop.gstreamer.androidmedia;
+
+import android.graphics.SurfaceTexture;
+import android.graphics.SurfaceTexture.OnFrameAvailableListener;
+
+public class GstAmcOnFrameAvailableListener implements OnFrameAvailableListener
+{
+ private long context = 0;
+
+ public synchronized void onFrameAvailable (SurfaceTexture surfaceTexture) {
+ native_onFrameAvailable(context, surfaceTexture);
+ }
+
+ public synchronized long getContext () {
+ return context;
+ }
+
+ public synchronized void setContext (long c) {
+ context = c;
+ }
+
+ private native void native_onFrameAvailable (long context, SurfaceTexture surfaceTexture);
+}
diff --git a/VideoReceiverApp/main.cpp b/VideoReceiverApp/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a0b864afd418122cb5dcf66db160d4c0288e93db
--- /dev/null
+++ b/VideoReceiverApp/main.cpp
@@ -0,0 +1,479 @@
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "QGCLoggingCategory.h"
+
+QGC_LOGGING_CATEGORY(AppLog, "VideoReceiverApp")
+
+#if defined(__android__)
+#include
+
+#include
+
+#include
+
+static jobject _class_loader = nullptr;
+static jobject _context = nullptr;
+
+extern "C" {
+ void gst_amc_jni_set_java_vm(JavaVM *java_vm);
+
+ jobject gst_android_get_application_class_loader(void) {
+ return _class_loader;
+ }
+}
+
+static void
+gst_android_init(JNIEnv* env, jobject context)
+{
+ jobject class_loader = nullptr;
+
+ jclass context_cls = env->GetObjectClass(context);
+
+ if (!context_cls) {
+ return;
+ }
+
+ jmethodID get_class_loader_id = env->GetMethodID(context_cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return;
+ }
+
+ class_loader = env->CallObjectMethod(context, get_class_loader_id);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return;
+ }
+
+ _context = env->NewGlobalRef(context);
+ _class_loader = env->NewGlobalRef(class_loader);
+}
+
+static const char kJniClassName[] {"labs/mavlink/VideoReceiverApp/QGLSinkActivity"};
+
+static void setNativeMethods(void)
+{
+ JNINativeMethod javaMethods[] {
+ {"nativeInit", "()V", reinterpret_cast(gst_android_init)}
+ };
+
+ QAndroidJniEnvironment jniEnv;
+
+ if (jniEnv->ExceptionCheck()) {
+ jniEnv->ExceptionDescribe();
+ jniEnv->ExceptionClear();
+ }
+
+ jclass objectClass = jniEnv->FindClass(kJniClassName);
+
+ if (!objectClass) {
+ qWarning() << "Couldn't find class:" << kJniClassName;
+ return;
+ }
+
+ jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
+
+ if (val < 0) {
+ qWarning() << "Error registering methods: " << val;
+ } else {
+ qDebug() << "Main Native Functions Registered";
+ }
+
+ if (jniEnv->ExceptionCheck()) {
+ jniEnv->ExceptionDescribe();
+ jniEnv->ExceptionClear();
+ }
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ Q_UNUSED(reserved);
+
+ JNIEnv* env;
+
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return -1;
+ }
+
+ setNativeMethods();
+
+ gst_amc_jni_set_java_vm(vm);
+
+ return JNI_VERSION_1_6;
+}
+#endif
+
+#include
+#include
+
+class VideoReceiverApp : public QRunnable
+{
+public:
+ VideoReceiverApp(QCoreApplication& app, bool qmlAllowed)
+ : _app(app)
+ , _qmlAllowed(qmlAllowed)
+ {}
+
+ void run();
+
+ int exec();
+
+ void startStreaming();
+ void startDecoding();
+ void startRecording();
+
+protected:
+ void _dispatch(std::function code);
+
+private:
+ QCoreApplication& _app;
+ bool _qmlAllowed;
+ VideoReceiver* _receiver = nullptr;
+ QQuickWindow* _window = nullptr;
+ QQuickItem* _widget = nullptr;
+ void* _videoSink = nullptr;
+ QString _url;
+ unsigned _timeout = 5;
+ unsigned _connect = 1;
+ bool _decode = true;
+ unsigned _stopDecodingAfter = 0;
+ bool _record = false;
+ QString _videoFile;
+ unsigned int _fileFormat = VideoReceiver::FILE_FORMAT_MIN;
+ unsigned _stopRecordingAfter = 15;
+ bool _useFakeSink = false;
+};
+
+void
+VideoReceiverApp::run()
+{
+ if((_videoSink = GStreamer::createVideoSink(nullptr, _widget)) == nullptr) {
+ qCDebug(AppLog) << "createVideoSink failed";
+ return;
+ }
+
+ _receiver->startDecoding(_videoSink);
+}
+
+int
+VideoReceiverApp::exec()
+{
+ QCommandLineParser parser;
+
+ parser.addHelpOption();
+
+ parser.addPositionalArgument("url",
+ QCoreApplication::translate("main", "Source URL."));
+
+ QCommandLineOption timeoutOption(QStringList() << "t" << "timeout",
+ QCoreApplication::translate("main", "Source timeout."),
+ QCoreApplication::translate("main", "seconds"));
+
+ parser.addOption(timeoutOption);
+
+ QCommandLineOption connectOption(QStringList() << "c" << "connect",
+ QCoreApplication::translate("main", "Number of connection attempts."),
+ QCoreApplication::translate("main", "attempts"));
+
+ parser.addOption(connectOption);
+
+ QCommandLineOption decodeOption(QStringList() << "d" << "decode",
+ QCoreApplication::translate("main", "Decode and render video."));
+
+ parser.addOption(decodeOption);
+
+ QCommandLineOption noDecodeOption("no-decode",
+ QCoreApplication::translate("main", "Don't decode and render video."));
+
+ parser.addOption(noDecodeOption);
+
+ QCommandLineOption stopDecodingOption("stop-decoding",
+ QCoreApplication::translate("main", "Stop decoding after time."),
+ QCoreApplication::translate("main", "seconds"));
+
+ parser.addOption(stopDecodingOption);
+
+ QCommandLineOption recordOption(QStringList() << "r" << "record",
+ QCoreApplication::translate("main", "Record video."),
+ QGuiApplication::translate("main", "file"));
+
+ parser.addOption(recordOption);
+
+ QCommandLineOption formatOption(QStringList() << "f" << "format",
+ QCoreApplication::translate("main", "File format."),
+ QCoreApplication::translate("main", "format"));
+
+ parser.addOption(formatOption);
+
+ QCommandLineOption stopRecordingOption("stop-recording",
+ QCoreApplication::translate("main", "Stop recording after time."),
+ QCoreApplication::translate("main", "seconds"));
+
+ parser.addOption(stopRecordingOption);
+
+ QCommandLineOption videoSinkOption("video-sink",
+ QCoreApplication::translate("main", "Use video sink: 0 - autovideosink, 1 - fakesink"),
+ QCoreApplication::translate("main", "sink"));
+
+ if (!_qmlAllowed) {
+ parser.addOption(videoSinkOption);
+ }
+
+ parser.process(_app);
+
+ const QStringList args = parser.positionalArguments();
+
+ if (args.size() != 1) {
+ parser.showHelp(0);
+ }
+
+ _url = args.at(0);
+
+ if (parser.isSet(timeoutOption)) {
+ _timeout = parser.value(timeoutOption).toUInt();
+ }
+
+ if (parser.isSet(connectOption)) {
+ _connect = parser.value(connectOption).toUInt();
+ }
+
+ if (parser.isSet(decodeOption) && parser.isSet(noDecodeOption)) {
+ parser.showHelp(0);
+ }
+
+ if (parser.isSet(decodeOption)) {
+ _decode = true;
+ }
+
+ if (parser.isSet(noDecodeOption)) {
+ _decode = false;
+ }
+
+ if (_decode && parser.isSet(stopDecodingOption)) {
+ _stopDecodingAfter = parser.value(stopDecodingOption).toUInt();
+ }
+
+ if (parser.isSet(recordOption)) {
+ _record = true;
+ _videoFile = parser.value(recordOption);
+ }
+
+ if (parser.isSet(formatOption)) {
+ _fileFormat += parser.value(formatOption).toUInt();
+ }
+
+ if (_record && parser.isSet(stopRecordingOption)) {
+ _stopRecordingAfter = parser.value(stopRecordingOption).toUInt();
+ }
+
+ if (parser.isSet(videoSinkOption)) {
+ _useFakeSink = parser.value(videoSinkOption).toUInt() > 0;
+ }
+
+ _receiver = GStreamer::createVideoReceiver(nullptr);
+
+ QQmlApplicationEngine engine;
+
+ if (_decode && _qmlAllowed) {
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ _window = static_cast(engine.rootObjects().first());
+ Q_ASSERT(_window != nullptr);
+
+ _widget = _window->findChild("videoItem");
+ Q_ASSERT(_widget != nullptr);
+ }
+
+ startStreaming();
+
+ QObject::connect(_receiver, &VideoReceiver::timeout, [this](){
+ qCDebug(AppLog) << "Streaming timeout";
+
+ _dispatch([this](){
+ if (_receiver->streaming()) {
+ _receiver->stop();
+ } else {
+ if (--_connect > 0) {
+ qCDebug(AppLog) << "Restarting streaming";
+ _dispatch([this](){
+ startStreaming();
+ });
+ } else {
+ qCDebug(AppLog) << "Closing...";
+ delete _receiver;
+ _app.exit();
+ }
+ }
+ });
+ });
+
+ QObject::connect(_receiver, &VideoReceiver::streamingChanged, [this](){
+ if (_receiver->streaming()) {
+ qCDebug(AppLog) << "Streaming started";
+ } else {
+ qCDebug(AppLog) << "Streaming stopped";
+ _dispatch([this](){
+ if (--_connect > 0) {
+ qCDebug(AppLog) << "Restarting streaming";
+ startStreaming();
+ } else {
+ qCDebug(AppLog) << "Closing...";
+ delete _receiver;
+ _app.exit();
+ }
+ });
+ }
+ });
+
+ QObject::connect(_receiver, &VideoReceiver::decodingChanged, [this](){
+ if (_receiver->decoding()) {
+ qCDebug(AppLog) << "Decoding started";
+ } else {
+ qCDebug(AppLog) << "Decoding stopped";
+ if (_receiver->streaming()) {
+ if (!_receiver->recording()) {
+ _dispatch([this](){
+ _receiver->stop();
+ });
+ }
+ }
+ }
+ });
+
+ QObject::connect(_receiver, &VideoReceiver::recordingChanged, [this](){
+ if (_receiver->recording()) {
+ qCDebug(AppLog) << "Recording started";
+ } else {
+ qCDebug(AppLog) << "Recording stopped";
+ if (_receiver->streaming()) {
+ if (!_receiver->decoding()) {
+ _dispatch([this](){
+ _receiver->stop();
+ });
+ }
+ }
+ }
+ });
+
+ return _app.exec();
+}
+
+void
+VideoReceiverApp::startStreaming()
+{
+ _receiver->start(_url, _timeout);
+
+ if (_decode) {
+ startDecoding();
+ }
+
+ if (_record) {
+ startRecording();
+ }
+}
+
+void
+VideoReceiverApp::startDecoding()
+{
+ if (_qmlAllowed) {
+ _window->scheduleRenderJob(this, QQuickWindow::BeforeSynchronizingStage);
+ } else {
+ if (_videoSink == nullptr) {
+ if ((_videoSink = gst_element_factory_make(_useFakeSink ? "fakesink" : "autovideosink", nullptr)) == nullptr) {
+ qCDebug(AppLog) << "Failed to create video sink";
+ return;
+ }
+ }
+
+ _receiver->startDecoding(_videoSink);
+ }
+
+ if (_stopDecodingAfter > 0) {
+ unsigned connect = _connect;
+ QTimer::singleShot(_stopDecodingAfter * 1000, Qt::PreciseTimer, [this, connect](){
+ if (connect != _connect) {
+ return;
+ }
+ _receiver->stopDecoding();
+ });
+ }
+}
+
+void
+VideoReceiverApp::startRecording()
+{
+ _receiver->startRecording(_videoFile, static_cast(_fileFormat));
+
+ if (_stopRecordingAfter > 0) {
+ unsigned connect = _connect;
+ QTimer::singleShot(_stopRecordingAfter * 1000, [this, connect](){
+ if (connect != _connect) {
+ return;
+ }
+ _receiver->stopRecording();
+ });
+ }
+}
+
+void
+VideoReceiverApp::_dispatch(std::function code)
+{
+ QTimer* timer = new QTimer();
+ timer->moveToThread(qApp->thread());
+ timer->setSingleShot(true);
+ QObject::connect(timer, &QTimer::timeout, [=](){
+ code();
+ timer->deleteLater();
+ });
+ QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
+}
+
+
+static bool isQtApp(const char* app)
+{
+ const char* s;
+
+#if defined(Q_OS_WIN)
+ if ((s = strrchr(app, '\\')) != nullptr) {
+#else
+ if ((s = strrchr(app, '/')) != nullptr) {
+#endif
+ s += 1;
+ } else {
+ s = app;
+ }
+
+ return s[0] == 'Q' || s[0] == 'q';
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 1) {
+ return 0;
+ }
+
+ GStreamer::initialize(argc, argv, 3);
+
+ if (isQtApp(argv[0])) {
+ QGuiApplication app(argc, argv);
+ VideoReceiverApp videoApp(app, true);
+ return videoApp.exec();
+ } else {
+ QCoreApplication app(argc, argv);
+ VideoReceiverApp videoApp(app, false);
+ return videoApp.exec();
+ }
+}
diff --git a/VideoReceiverApp/main.qml b/VideoReceiverApp/main.qml
new file mode 100644
index 0000000000000000000000000000000000000000..b231811803c85106caa89e6937f9ac9b5a1ad4b5
--- /dev/null
+++ b/VideoReceiverApp/main.qml
@@ -0,0 +1,23 @@
+import QtQuick 2.12
+import QtQuick.Window 2.12
+import QtQuick.Layouts 1.3
+import org.freedesktop.gstreamer.GLVideoItem 1.0
+
+Window {
+ visible: true
+ width: 640
+ height: 480
+ title: qsTr("VideoReceiverApp")
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ GstGLVideoItem {
+ id: video
+ objectName: "videoItem"
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/VideoReceiverApp/qml.qrc b/VideoReceiverApp/qml.qrc
new file mode 100644
index 0000000000000000000000000000000000000000..5f6483ac33f1881cc59e080e69bb033ebe2a7829
--- /dev/null
+++ b/VideoReceiverApp/qml.qrc
@@ -0,0 +1,5 @@
+
+
+ main.qml
+
+
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index 8d66aec7ba951b1e9b715347e908958021332604..512c682df1937e217bbe7438743f81cdc1d31cd7 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,2 +1,5 @@
add_subdirectory(qtandroidserialport)
add_subdirectory(shapelib)
+if (GST_FOUND)
+ add_subdirectory(qmlglsink)
+endif()
diff --git a/libs/qmlglsink/CMakeLists.txt b/libs/qmlglsink/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..66e187523087216c3439037af9f2616990b5aa46
--- /dev/null
+++ b/libs/qmlglsink/CMakeLists.txt
@@ -0,0 +1,64 @@
+
+find_package(Qt5Gui ${QT_VERSION} CONFIG REQUIRED Private)
+
+add_library(qmlglsink
+ gst-plugins-good/ext/qt/gstplugin.cc
+ gst-plugins-good/ext/qt/gstqtglutility.cc
+ gst-plugins-good/ext/qt/gstqsgtexture.cc
+ gst-plugins-good/ext/qt/gstqtsink.cc
+ gst-plugins-good/ext/qt/gstqtsrc.cc
+ gst-plugins-good/ext/qt/qtwindow.cc
+ gst-plugins-good/ext/qt/qtitem.cc
+
+ gst-plugins-good/ext/qt/gstqsgtexture.h
+ gst-plugins-good/ext/qt/gstqtgl.h
+ gst-plugins-good/ext/qt/gstqtglutility.h
+ gst-plugins-good/ext/qt/gstqtsink.h
+ gst-plugins-good/ext/qt/gstqtsrc.h
+ gst-plugins-good/ext/qt/qtwindow.h
+ gst-plugins-good/ext/qt/qtitem.h
+)
+
+if(LINUX)
+ target_compile_definitions(qmlglsink PUBLIC HAVE_QT_X11 HAVE_QT_EGLFS HAVE_QT_WAYLAND)
+
+
+ find_package(Qt5 ${QT_VERSION}
+ COMPONENTS
+ X11Extras
+ REQUIRED
+ HINTS
+ ${QT_LIBRARY_HINTS}
+ )
+
+ target_link_libraries(qmlglsink
+ PUBLIC
+ Qt5::X11Extras
+ )
+
+elseif(APPLE)
+ target_compile_definitions(qmlglsink PUBLIC HAVE_QT_MAC)
+elseif(IOS)
+ target_compile_definitions(qmlglsink PUBLIC HAVE_QT_MAC)
+elseif(WIN32)
+ target_compile_definitions(qmlglsink PUBLIC HAVE_QT_WIN32 HAVE_QT_QPA_HEADER)
+
+ # TODO: use FindOpenGL?
+ target_link_libraries(qmlglsink PUBLIC opengl32.lib user32.lib)
+ # LIBS += opengl32.lib user32.lib
+elseif(ANDROID)
+ target_compile_definitions(qmlglsink PUBLIC HAVE_QT_ANDROID)
+endif()
+
+target_link_libraries(qmlglsink
+ PUBLIC
+ Qt5::Core
+ Qt5::OpenGL
+ Qt5::GuiPrivate
+)
+
+target_compile_options(qmlglsink
+ PRIVATE
+ -Wno-unused-parameter
+ -Wno-implicit-fallthrough
+)
diff --git a/libs/gst-plugins-good b/libs/qmlglsink/gst-plugins-good
similarity index 100%
rename from libs/gst-plugins-good
rename to libs/qmlglsink/gst-plugins-good
diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index c6c424382c8229ebbfefc59c430a6fcbfb61979a..eeb4682cbedd09c9da808d7a3a9de04891f06621 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -1315,19 +1315,15 @@ contains (DEFINES, QGC_AIRMAP_ENABLED) {
# Video Streaming
INCLUDEPATH += \
- src/VideoStreaming
+ src/VideoManager
HEADERS += \
- src/VideoStreaming/VideoReceiver.h \
- src/VideoStreaming/VideoStreaming.h \
- src/VideoStreaming/SubtitleWriter.h \
- src/VideoStreaming/VideoManager.h
+ src/VideoManager/SubtitleWriter.h \
+ src/VideoManager/VideoManager.h
SOURCES += \
- src/VideoStreaming/VideoReceiver.cc \
- src/VideoStreaming/VideoStreaming.cc \
- src/VideoStreaming/SubtitleWriter.cc \
- src/VideoStreaming/VideoManager.cc
+ src/VideoManager/SubtitleWriter.cc \
+ src/VideoManager/VideoManager.cc
contains (CONFIG, DISABLE_VIDEOSTREAMING) {
message("Skipping support for video streaming (manual override from command line)")
@@ -1335,14 +1331,19 @@ contains (CONFIG, DISABLE_VIDEOSTREAMING) {
} else:exists(user_config.pri):infile(user_config.pri, DEFINES, DISABLE_VIDEOSTREAMING) {
message("Skipping support for video streaming (manual override from user_config.pri)")
} else {
- include(src/VideoStreaming/VideoStreaming.pri)
+ include(src/VideoReceiver/VideoReceiver.pri)
}
!VideoEnabled {
+ INCLUDEPATH += \
+ src/VideoReceiver
+
HEADERS += \
- src/VideoStreaming/GLVideoItemStub.h
+ src/VideoManager/GLVideoItemStub.h \
+ src/VideoReceiver/VideoReceiver.h
+
SOURCES += \
- src/VideoStreaming/GLVideoItemStub.cc
+ src/VideoManager/GLVideoItemStub.cc
}
#-------------------------------------------------------------------------------------
diff --git a/qmlglsink.cmake b/qmlglsink.cmake
deleted file mode 100644
index 4ed0c49a4c5725f5053bb6732570654e2ae013c5..0000000000000000000000000000000000000000
--- a/qmlglsink.cmake
+++ /dev/null
@@ -1,65 +0,0 @@
-
-find_package(Qt5Gui ${QT_VERSION} CONFIG REQUIRED Private)
-
-add_library(gst_plugins_good
- libs/gst-plugins-good/ext/qt/gstplugin.cc
- libs/gst-plugins-good/ext/qt/gstqtglutility.cc
- libs/gst-plugins-good/ext/qt/gstqsgtexture.cc
- libs/gst-plugins-good/ext/qt/gstqtsink.cc
- libs/gst-plugins-good/ext/qt/gstqtsrc.cc
- libs/gst-plugins-good/ext/qt/qtwindow.cc
- libs/gst-plugins-good/ext/qt/qtitem.cc
-
- libs/gst-plugins-good/ext/qt/gstqsgtexture.h
- libs/gst-plugins-good/ext/qt/gstqtgl.h
- libs/gst-plugins-good/ext/qt/gstqtglutility.h
- libs/gst-plugins-good/ext/qt/gstqtsink.h
- libs/gst-plugins-good/ext/qt/gstqtsrc.h
- libs/gst-plugins-good/ext/qt/qtwindow.h
- libs/gst-plugins-good/ext/qt/qtitem.h
-)
-
-if(LINUX)
- target_compile_definitions(gst_plugins_good PUBLIC HAVE_QT_X11 HAVE_QT_EGLFS HAVE_QT_WAYLAND)
-
-
- find_package(Qt5 ${QT_VERSION}
- COMPONENTS
- X11Extras
- REQUIRED
- HINTS
- ${QT_LIBRARY_HINTS}
- )
-
- target_link_libraries(gst_plugins_good
- PUBLIC
- Qt5::X11Extras
- )
-
-elseif(APPLE)
- target_compile_definitions(gst_plugins_good PUBLIC HAVE_QT_MAC)
-elseif(IOS)
- target_compile_definitions(gst_plugins_good PUBLIC HAVE_QT_MAC)
-elseif(WIN32)
- target_compile_definitions(gst_plugins_good PUBLIC HAVE_QT_WIN32 HAVE_QT_QPA_HEADER)
-
- # TODO: use FindOpenGL?
- target_link_libraries(gst_plugins_good PUBLIC opengl32.lib user32.lib)
- # LIBS += opengl32.lib user32.lib
-elseif(ANDROID)
- target_compile_definitions(gst_plugins_good PUBLIC HAVE_QT_ANDROID)
-endif()
-
-target_link_libraries(gst_plugins_good
- PUBLIC
- Qt5::Core
- Qt5::OpenGL
- Qt5::GuiPrivate
-)
-
-target_compile_options(gst_plugins_good
- PRIVATE
- -Wno-unused-parameter
- -Wno-implicit-fallthrough
-)
-
diff --git a/qmlglsink.pri b/qmlglsink.pri
index 29bb9d27c394e3a9e18813ff80b5704993feae90..a6a07df519fadf5b646d24de244b310657f8703c 100644
--- a/qmlglsink.pri
+++ b/qmlglsink.pri
@@ -15,19 +15,19 @@ LinuxBuild {
}
SOURCES += \
- libs/gst-plugins-good/ext/qt/gstplugin.cc \
- libs/gst-plugins-good/ext/qt/gstqtglutility.cc \
- libs/gst-plugins-good/ext/qt/gstqsgtexture.cc \
- libs/gst-plugins-good/ext/qt/gstqtsink.cc \
- libs/gst-plugins-good/ext/qt/gstqtsrc.cc \
- libs/gst-plugins-good/ext/qt/qtwindow.cc \
- libs/gst-plugins-good/ext/qt/qtitem.cc
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstplugin.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtglutility.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqsgtexture.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtsink.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtsrc.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/qtwindow.cc \
+ libs/qmlglsink/gst-plugins-good/ext/qt/qtitem.cc
HEADERS += \
- libs/gst-plugins-good/ext/qt/gstqsgtexture.h \
- libs/gst-plugins-good/ext/qt/gstqtgl.h \
- libs/gst-plugins-good/ext/qt/gstqtglutility.h \
- libs/gst-plugins-good/ext/qt/gstqtsink.h \
- libs/gst-plugins-good/ext/qt/gstqtsrc.h \
- libs/gst-plugins-good/ext/qt/qtwindow.h \
- libs/gst-plugins-good/ext/qt/qtitem.h
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqsgtexture.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtgl.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtglutility.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtsink.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/gstqtsrc.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/qtwindow.h \
+ libs/qmlglsink/gst-plugins-good/ext/qt/qtitem.h
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6be6af89e178158a353033b8ebb6eb10608a698e..7b31525630270a92e94c70cf5a383532497ef1c5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,7 +3,7 @@ include_directories(
.
${CMAKE_CURRENT_BINARY_DIR}
${Qt5Location_PRIVATE_INCLUDE_DIRS}
- VideoStreaming
+ VideoManager
)
set(EXTRA_SRC)
@@ -136,7 +136,8 @@ add_subdirectory(Terrain)
add_subdirectory(uas)
add_subdirectory(Vehicle)
add_subdirectory(VehicleSetup)
-add_subdirectory(VideoStreaming)
+add_subdirectory(VideoManager)
+add_subdirectory(VideoReceiver)
add_subdirectory(ViewWidgets)
target_link_libraries(qgc
@@ -172,7 +173,7 @@ target_link_libraries(qgc
ui
Vehicle
VehicleSetup
- VideoStreaming
+ VideoManager
ViewWidgets
)
diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index 88b6e74e8b910ea258f11543458723f2c25af5a9..4d0614164e7661e3f5b09aafe47dba2e1488def5 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -34,7 +34,9 @@
#include
-#include "VideoStreaming.h"
+#if defined(QGC_GST_STREAMING)
+#include "GStreamer.h"
+#endif
#include "QGC.h"
#include "QGCApplication.h"
@@ -323,8 +325,13 @@ QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting)
if (settings.contains(AppSettings::gstDebugLevelName)) {
gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt();
}
- // Initialize Video Streaming
- initializeVideoStreaming(argc, argv, gstDebugLevel);
+
+#if defined(QGC_GST_STREAMING)
+ // Initialize Video Receiver
+ GStreamer::initialize(argc, argv, gstDebugLevel);
+#else
+ Q_UNUSED(gstDebugLevel)
+#endif
_toolbox = new QGCToolbox(this);
_toolbox->setChildToolboxes();
diff --git a/src/VideoManager/CMakeLists.txt b/src/VideoManager/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4ffc81714dee03106f236d76ed4c7754b5c5c07b
--- /dev/null
+++ b/src/VideoManager/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_library(VideoManager
+ GLVideoItemStub.cc
+ GLVideoItemStub.h
+ SubtitleWriter.cc
+ SubtitleWriter.h
+ VideoManager.cc
+ VideoManager.h
+)
+
+target_link_libraries(VideoManager
+ PUBLIC
+ qgc
+ Qt5::Multimedia
+ Qt5::OpenGL
+ VideoReceiver
+)
+
+target_include_directories(VideoManager INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/VideoStreaming/GLVideoItemStub.cc b/src/VideoManager/GLVideoItemStub.cc
similarity index 100%
rename from src/VideoStreaming/GLVideoItemStub.cc
rename to src/VideoManager/GLVideoItemStub.cc
diff --git a/src/VideoStreaming/GLVideoItemStub.h b/src/VideoManager/GLVideoItemStub.h
similarity index 100%
rename from src/VideoStreaming/GLVideoItemStub.h
rename to src/VideoManager/GLVideoItemStub.h
diff --git a/src/VideoStreaming/SubtitleWriter.cc b/src/VideoManager/SubtitleWriter.cc
similarity index 99%
rename from src/VideoStreaming/SubtitleWriter.cc
rename to src/VideoManager/SubtitleWriter.cc
index 4e64e147fe494a29a24e644bd8f1d1edcb2423c4..0ab1c8d18f83ea978474791979cb5b56419be351 100644
--- a/src/VideoStreaming/SubtitleWriter.cc
+++ b/src/VideoManager/SubtitleWriter.cc
@@ -80,11 +80,9 @@ void SubtitleWriter::startCapturingTelemetry(const QString& videoFile)
void SubtitleWriter::stopCapturingTelemetry()
{
-#if defined(QGC_GST_STREAMING)
qCDebug(SubtitleWriterLog) << "Stopping writing";
_timer.stop();
_file.close();
-#endif
}
void SubtitleWriter::_captureTelemetry()
diff --git a/src/VideoStreaming/SubtitleWriter.h b/src/VideoManager/SubtitleWriter.h
similarity index 100%
rename from src/VideoStreaming/SubtitleWriter.h
rename to src/VideoManager/SubtitleWriter.h
diff --git a/src/VideoStreaming/VideoManager.cc b/src/VideoManager/VideoManager.cc
similarity index 94%
rename from src/VideoStreaming/VideoManager.cc
rename to src/VideoManager/VideoManager.cc
index 49d934d6f5209d7e39bbd6160a68dcd4c2fa4128..1305bde709e2cd5a4f123b9deedb4b948d059e09 100644
--- a/src/VideoStreaming/VideoManager.cc
+++ b/src/VideoManager/VideoManager.cc
@@ -28,18 +28,33 @@
#include "Vehicle.h"
#include "QGCCameraManager.h"
+#if defined(QGC_GST_STREAMING)
+#include "GStreamer.h"
+#else
+#include "GLVideoItemStub.h"
+#endif
+
QGC_LOGGING_CATEGORY(VideoManagerLog, "VideoManagerLog")
+#if defined(QGC_GST_STREAMING)
static const char* kFileExtension[VideoReceiver::FILE_FORMAT_MAX - VideoReceiver::FILE_FORMAT_MIN] = {
"mkv",
"mov",
"mp4"
};
+#endif
//-----------------------------------------------------------------------------
VideoManager::VideoManager(QGCApplication* app, QGCToolbox* toolbox)
: QGCTool(app, toolbox)
{
+#if !defined(QGC_GST_STREAMING)
+ static bool once = false;
+ if (!once) {
+ qmlRegisterType("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem");
+ once = true;
+ }
+#endif
}
//-----------------------------------------------------------------------------
@@ -50,15 +65,10 @@ VideoManager::~VideoManager()
delete _thermalVideoReceiver;
_thermalVideoReceiver = nullptr;
#if defined(QGC_GST_STREAMING)
- if (_thermalVideoSink != nullptr) {
- gst_object_unref(_thermalVideoSink);
- _thermalVideoSink = nullptr;
- }
-
- if (_videoSink != nullptr) {
- gst_object_unref(_videoSink);
- _videoSink = nullptr;
- }
+ GStreamer::releaseVideoSink(_thermalVideoSink);
+ _thermalVideoSink = nullptr;
+ GStreamer::releaseVideoSink(_videoSink);
+ _videoSink = nullptr;
#endif
}
@@ -90,6 +100,7 @@ VideoManager::setToolbox(QGCToolbox *toolbox)
emit isGStreamerChanged();
qCDebug(VideoManagerLog) << "New Video Source:" << videoSource;
+#if defined(QGC_GST_STREAMING)
_videoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
_thermalVideoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
@@ -103,7 +114,7 @@ VideoManager::setToolbox(QGCToolbox *toolbox)
// and I expect that it will be changed during multiple video stream activity
connect(_thermalVideoReceiver, &VideoReceiver::timeout, this, &VideoManager::_restartVideo);
connect(_thermalVideoReceiver, &VideoReceiver::streamingChanged, this, &VideoManager::_streamingChanged);
-
+#endif
_updateSettings();
if(isGStreamer()) {
startVideo();
@@ -146,7 +157,7 @@ void VideoManager::_cleanupOldVideos()
//-- Remove old movies until max size is satisfied.
while(total >= maxSize && !vidList.isEmpty()) {
total -= vidList.last().size();
- qCDebug(VideoReceiverLog) << "Removing old video file:" << vidList.last().filePath();
+ qCDebug(VideoManagerLog) << "Removing old video file:" << vidList.last().filePath();
QFile file (vidList.last().filePath());
file.remove();
vidList.removeLast();
@@ -164,7 +175,7 @@ VideoManager::startVideo()
}
if(!_videoSettings->streamEnabled()->rawValue().toBool() || !_videoSettings->streamConfigured()) {
- qCDebug(VideoReceiverLog) << "Stream not enabled/configured";
+ qCDebug(VideoManagerLog) << "Stream not enabled/configured";
return;
}
@@ -194,9 +205,10 @@ VideoManager::stopVideo()
if (qgcApp()->runningUnitTests()) {
return;
}
-
+#if defined(QGC_GST_STREAMING)
if(_videoReceiver) _videoReceiver->stop();
if(_thermalVideoReceiver) _thermalVideoReceiver->stop();
+#endif
}
void
@@ -205,7 +217,7 @@ VideoManager::startRecording(const QString& videoFile)
if (qgcApp()->runningUnitTests()) {
return;
}
-
+#if defined(QGC_GST_STREAMING)
if (!_videoReceiver) {
qgcApp()->showMessage(tr("Video receiver is not ready."));
return;
@@ -233,6 +245,9 @@ VideoManager::startRecording(const QString& videoFile)
+ "." + kFileExtension[fileFormat - VideoReceiver::FILE_FORMAT_MIN];
_videoReceiver->startRecording(_videoFile, fileFormat);
+#else
+ Q_UNUSED(videoFile)
+#endif
}
void
@@ -241,12 +256,13 @@ VideoManager::stopRecording()
if (qgcApp()->runningUnitTests()) {
return;
}
-
+#if defined(QGC_GST_STREAMING)
if (!_videoReceiver) {
return;
}
_videoReceiver->stopRecording();
+#endif
}
void
@@ -255,7 +271,7 @@ VideoManager::grabImage(const QString& imageFile)
if (qgcApp()->runningUnitTests()) {
return;
}
-
+#if defined(QGC_GST_STREAMING)
if (!_videoReceiver) {
return;
}
@@ -265,6 +281,9 @@ VideoManager::grabImage(const QString& imageFile)
emit imageFileChanged();
_videoReceiver->takeScreenshot(_imageFile);
+#else
+ Q_UNUSED(imageFile)
+#endif
}
//-----------------------------------------------------------------------------
@@ -455,26 +474,6 @@ VideoManager::setfullScreen(bool f)
emit fullScreenChanged();
}
-//-----------------------------------------------------------------------------
-#if defined(QGC_GST_STREAMING)
-GstElement*
-VideoManager::_makeVideoSink(gpointer widget)
-{
- GstElement* sink;
-
- if ((sink = gst_element_factory_make("qgcvideosinkbin", nullptr)) != nullptr) {
- g_object_set(sink, "widget", widget, NULL);
- // FIXME: AV: temporally disable sync due to MPEG2-TS sync issues
- g_object_set(sink, "sync", FALSE, NULL);
- } else {
- qCritical() << "gst_element_factory_make('qgcvideosinkbin') failed";
- }
-
- return sink;
-}
-#endif
-
-//-----------------------------------------------------------------------------
void
VideoManager::_initVideo()
{
@@ -489,10 +488,10 @@ VideoManager::_initVideo()
QQuickItem* widget = root->findChild("videoContent");
if (widget != nullptr && _videoReceiver != nullptr) {
- if ((_videoSink = _makeVideoSink(widget)) != nullptr) {
+ if ((_videoSink = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget)) != nullptr) {
_videoReceiver->startDecoding(_videoSink);
} else {
- qCDebug(VideoManagerLog) << "_makeVideoSink() failed";
+ qCDebug(VideoManagerLog) << "createVideoSink() failed";
}
} else {
qCDebug(VideoManagerLog) << "video receiver disabled";
@@ -501,10 +500,10 @@ VideoManager::_initVideo()
widget = root->findChild("thermalVideo");
if (widget != nullptr && _thermalVideoReceiver != nullptr) {
- if ((_thermalVideoSink = _makeVideoSink(widget)) != nullptr) {
+ if ((_thermalVideoSink = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget)) != nullptr) {
_thermalVideoReceiver->startDecoding(_thermalVideoSink);
} else {
- qCDebug(VideoManagerLog) << "_makeVideoSink() failed";
+ qCDebug(VideoManagerLog) << "createVideoSink() failed";
}
} else {
qCDebug(VideoManagerLog) << "thermal video receiver disabled";
@@ -516,7 +515,7 @@ VideoManager::_initVideo()
void
VideoManager::_updateSettings()
{
- if(!_videoSettings || !_videoReceiver)
+ if(!_videoSettings)
return;
//-- Auto discovery
if(_activeVehicle && _activeVehicle->dynamicCameras()) {
@@ -646,9 +645,11 @@ VideoManager::_recordingStarted()
void
VideoManager::_recordingChanged()
{
+#if defined(QGC_GST_STREAMING)
if (_videoReceiver && !_videoReceiver->recording()) {
_subtitleWriter.stopCapturingTelemetry();
}
+#endif
}
//----------------------------------------------------------------------------------------
diff --git a/src/VideoStreaming/VideoManager.h b/src/VideoManager/VideoManager.h
similarity index 94%
rename from src/VideoStreaming/VideoManager.h
rename to src/VideoManager/VideoManager.h
index 70c5d8a79d1dbc9f37117aa56e5130b3f98787c5..f5c3192918d866d9f44c60552c62b7b2bee76c36 100644
--- a/src/VideoStreaming/VideoManager.h
+++ b/src/VideoManager/VideoManager.h
@@ -112,10 +112,7 @@ protected slots:
protected:
friend class FinishVideoInitialization;
-#if defined(QGC_GST_STREAMING)
- static gboolean _videoSinkQuery (GstPad* pad, GstObject* parent, GstQuery* query);
- GstElement* _makeVideoSink (gpointer widget);
-#endif
+
void _initVideo ();
void _updateSettings ();
void _setVideoUri (const QString& uri);
@@ -134,10 +131,8 @@ protected:
bool _isTaisync = false;
VideoReceiver* _videoReceiver = nullptr;
VideoReceiver* _thermalVideoReceiver = nullptr;
-#if defined(QGC_GST_STREAMING)
- GstElement* _videoSink = nullptr;
- GstElement* _thermalVideoSink = nullptr;
-#endif
+ void* _videoSink = nullptr;
+ void* _thermalVideoSink = nullptr;
VideoSettings* _videoSettings = nullptr;
QString _videoUri;
QString _thermalVideoUri;
diff --git a/src/VideoReceiver/CMakeLists.txt b/src/VideoReceiver/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3653a683eaec131a054a19c480a7072bdd366db9
--- /dev/null
+++ b/src/VideoReceiver/CMakeLists.txt
@@ -0,0 +1,22 @@
+set(EXTRA_SOURCES)
+set(EXTRA_LIBRARIES)
+
+if (GST_FOUND)
+ set(EXTRA_SOURCES gstqgc.c gstqgcvideosinkbin.c GStreamer.cc GStreamer.h GstVideoReceiver.cc GstVideoReceiver.h)
+ set(EXTRA_LIBRARIES qmlglsink ${GST_LIBRARIES})
+endif()
+
+add_library(VideoReceiver
+ ${EXTRA_SOURCES}
+ VideoReceiver.h
+)
+
+target_link_libraries(VideoReceiver
+ PUBLIC
+ Qt5::Multimedia
+ Qt5::OpenGL
+ Qt5::Quick
+ ${EXTRA_LIBRARIES}
+)
+
+target_include_directories(VideoReceiver INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/VideoStreaming/VideoStreaming.cc b/src/VideoReceiver/GStreamer.cc
similarity index 66%
rename from src/VideoStreaming/VideoStreaming.cc
rename to src/VideoReceiver/GStreamer.cc
index a8a4184cc8bd145cd0b17fbf68fe69f180d690d1..8198b20b1f7f4985bc68f4d58ec39ce6c267827d 100644
--- a/src/VideoStreaming/VideoStreaming.cc
+++ b/src/VideoReceiver/GStreamer.cc
@@ -14,17 +14,12 @@
* @author Gus Grubba
*/
-#include
#include
-#include "VideoReceiver.h"
-
-#if defined(QGC_GST_STREAMING)
-#include
+#include "GStreamer.h"
+#include "GstVideoReceiver.h"
-#include "QGCLoggingCategory.h"
-
-QGC_LOGGING_CATEGORY(GstreamerLog, "GstreamerLog")
+QGC_LOGGING_CATEGORY(GStreamerLog, "GStreamerLog")
static void qt_gst_log(GstDebugCategory * category,
GstDebugLevel level,
@@ -35,6 +30,8 @@ static void qt_gst_log(GstDebugCategory * category,
GstDebugMessage * message,
gpointer data)
{
+ Q_UNUSED(data);
+
if (level > gst_debug_category_get_threshold(category)) {
return;
}
@@ -46,20 +43,20 @@ static void qt_gst_log(GstDebugCategory * category,
switch (level) {
default:
case GST_LEVEL_ERROR:
- log.critical(GstreamerLog, "%s %s", object_info, gst_debug_message_get(message));
+ log.critical(GStreamerLog, "%s %s", object_info, gst_debug_message_get(message));
break;
case GST_LEVEL_WARNING:
- log.warning(GstreamerLog, "%s %s", object_info, gst_debug_message_get(message));
+ log.warning(GStreamerLog, "%s %s", object_info, gst_debug_message_get(message));
break;
case GST_LEVEL_FIXME:
case GST_LEVEL_INFO:
- log.info(GstreamerLog, "%s %s", object_info, gst_debug_message_get(message));
+ log.info(GStreamerLog, "%s %s", object_info, gst_debug_message_get(message));
break;
case GST_LEVEL_DEBUG:
case GST_LEVEL_LOG:
case GST_LEVEL_TRACE:
case GST_LEVEL_MEMDUMP:
- log.debug(GstreamerLog, "%s %s", object_info, gst_debug_message_get(message));
+ log.debug(GStreamerLog, "%s %s", object_info, gst_debug_message_get(message));
break;
}
@@ -70,15 +67,11 @@ static void qt_gst_log(GstDebugCategory * category,
#if defined(__ios__)
#include "gst_ios_init.h"
#endif
-#else
-#include "GLVideoItemStub.h"
-#endif
-#include "VideoStreaming.h"
+#include "VideoReceiver.h"
-#if defined(QGC_GST_STREAMING)
- G_BEGIN_DECLS
- // The static plugins we use
+G_BEGIN_DECLS
+// The static plugins we use
#if defined(__android__) || defined(__ios__)
GST_PLUGIN_STATIC_DECLARE(coreelements);
GST_PLUGIN_STATIC_DECLARE(playback);
@@ -102,10 +95,8 @@ static void qt_gst_log(GstDebugCategory * category,
#endif
GST_PLUGIN_STATIC_DECLARE(qmlgl);
GST_PLUGIN_STATIC_DECLARE(qgc);
- G_END_DECLS
-#endif
+G_END_DECLS
-#if defined(QGC_GST_STREAMING)
#if (defined(Q_OS_MAC) && defined(QGC_INSTALL_RELEASE)) || defined(Q_OS_WIN)
static void qgcputenv(const QString& key, const QString& root, const QString& path)
{
@@ -113,30 +104,29 @@ static void qgcputenv(const QString& key, const QString& root, const QString& pa
qputenv(key.toStdString().c_str(), QByteArray(value.toStdString().c_str()));
}
#endif
-#endif
-void initializeVideoStreaming(int &argc, char* argv[], int gstDebuglevel)
+void
+GStreamer::initialize(int argc, char* argv[], int debuglevel)
{
-#if defined(QGC_GST_STREAMING)
- #ifdef Q_OS_MAC
- #ifdef QGC_INSTALL_RELEASE
- QString currentDir = QCoreApplication::applicationDirPath();
- qgcputenv("GST_PLUGIN_SCANNER", currentDir, "/../Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0/gst-plugin-scanner");
- qgcputenv("GTK_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current");
- qgcputenv("GIO_EXTRA_MODULES", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gio/modules");
- qgcputenv("GST_PLUGIN_SYSTEM_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
- qgcputenv("GST_PLUGIN_SYSTEM_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
- qgcputenv("GST_PLUGIN_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
- qgcputenv("GST_PLUGIN_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
- #endif
- #elif defined(Q_OS_WIN)
+#ifdef Q_OS_MAC
+ #ifdef QGC_INSTALL_RELEASE
QString currentDir = QCoreApplication::applicationDirPath();
- qgcputenv("GST_PLUGIN_PATH", currentDir, "/gstreamer-plugins");
+ qgcputenv("GST_PLUGIN_SCANNER", currentDir, "/../Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0/gst-plugin-scanner");
+ qgcputenv("GTK_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current");
+ qgcputenv("GIO_EXTRA_MODULES", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gio/modules");
+ qgcputenv("GST_PLUGIN_SYSTEM_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
+ qgcputenv("GST_PLUGIN_SYSTEM_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
+ qgcputenv("GST_PLUGIN_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
+ qgcputenv("GST_PLUGIN_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0");
#endif
+#elif defined(Q_OS_WIN)
+ QString currentDir = QCoreApplication::applicationDirPath();
+ qgcputenv("GST_PLUGIN_PATH", currentDir, "/gstreamer-plugins");
+#endif
//-- If gstreamer debugging is not configured via environment then use internal QT logging
if (qgetenv("GST_DEBUG").isEmpty()) {
- gst_debug_set_default_threshold(static_cast(gstDebuglevel));
+ gst_debug_set_default_threshold(static_cast(debuglevel));
gst_debug_remove_log_function(gst_debug_log_default);
gst_debug_add_log_function(qt_gst_log, nullptr, nullptr);
}
@@ -149,7 +139,7 @@ void initializeVideoStreaming(int &argc, char* argv[], int gstDebuglevel)
GError* error = nullptr;
if (!gst_init_check(&argc, &argv, &error)) {
- qCCritical(VideoReceiverLog) << "gst_init_check() failed: " << error->message;
+ qCCritical(GStreamerLog) << "gst_init_check() failed: " << error->message;
g_error_free(error);
}
@@ -196,14 +186,39 @@ void initializeVideoStreaming(int &argc, char* argv[], int gstDebuglevel)
gst_object_unref(sink);
sink = nullptr;
} else {
- qCCritical(VideoReceiverLog) << "unable to find qmlglsink - you need to build it yourself and add to GST_PLUGIN_PATH";
+ qCCritical(GStreamerLog) << "unable to find qmlglsink - you need to build it yourself and add to GST_PLUGIN_PATH";
}
GST_PLUGIN_STATIC_REGISTER(qgc);
-#else
- qmlRegisterType("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem");
- Q_UNUSED(argc)
- Q_UNUSED(argv)
- Q_UNUSED(gstDebuglevel)
-#endif
+}
+
+void*
+GStreamer::createVideoSink(QObject* parent, QQuickItem* widget)
+{
+ Q_UNUSED(parent)
+
+ GstElement* sink;
+
+ if ((sink = gst_element_factory_make("qgcvideosinkbin", nullptr)) != nullptr) {
+ g_object_set(sink, "widget", widget, NULL);
+ } else {
+ qCritical() << "gst_element_factory_make('qgcvideosinkbin') failed";
+ }
+
+ return sink;
+}
+
+void
+GStreamer::releaseVideoSink(void* sink)
+{
+ if (sink != nullptr) {
+ gst_object_unref(GST_ELEMENT(sink));
+ }
+}
+
+VideoReceiver*
+GStreamer::createVideoReceiver(QObject* parent)
+{
+ Q_UNUSED(parent)
+ return new GstVideoReceiver(nullptr);
}
diff --git a/src/VideoReceiver/GStreamer.h b/src/VideoReceiver/GStreamer.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d96e5d15ba09d9149ee12eeeddf2982e5c24722
--- /dev/null
+++ b/src/VideoReceiver/GStreamer.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+#include
+
+#include "VideoReceiver.h"
+
+class GStreamer {
+public:
+ static void initialize(int argc, char* argv[], int debuglevel);
+ static void* createVideoSink(QObject* parent, QQuickItem* widget);
+ static void releaseVideoSink(void* sink);
+ static VideoReceiver* createVideoReceiver(QObject* parent);
+};
diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoReceiver/GstVideoReceiver.cc
similarity index 87%
rename from src/VideoStreaming/VideoReceiver.cc
rename to src/VideoReceiver/GstVideoReceiver.cc
index da89ec18008c8770c78ba465c7575f221a55f31c..e8e14023f73c02aab59f15f9adb3a2d9e15a3240 100644
--- a/src/VideoStreaming/VideoReceiver.cc
+++ b/src/VideoReceiver/GstVideoReceiver.cc
@@ -14,7 +14,7 @@
* @author Gus Grubba
*/
-#include "VideoReceiver.h"
+#include "GstVideoReceiver.h"
#include
#include
@@ -33,9 +33,8 @@ QGC_LOGGING_CATEGORY(VideoReceiverLog, "VideoReceiverLog")
// +-->queue-->_recorderValve[-->_fileSink]
//
-VideoReceiver::VideoReceiver(QObject* parent)
- : QThread(parent)
-#if defined(QGC_GST_STREAMING)
+GstVideoReceiver::GstVideoReceiver(QObject* parent)
+ : VideoReceiver(parent)
, _removingDecoder(false)
, _removingRecorder(false)
, _source(nullptr)
@@ -51,36 +50,26 @@ VideoReceiver::VideoReceiver(QObject* parent)
, _resetVideoSink(true)
, _videoSinkProbeId(0)
, _udpReconnect_us(5000000)
- , _shutdown(false)
-#endif
- , _streaming(false)
- , _decoding(false)
- , _recording(false)
+ , _endOfStream(false)
{
-#if defined(QGC_GST_STREAMING)
- QThread::start();
- connect(&_watchdogTimer, &QTimer::timeout, this, &VideoReceiver::_watchdog);
+ _apiHandler.start();
+ _notificationHandler.start();
+ connect(&_watchdogTimer, &QTimer::timeout, this, &GstVideoReceiver::_watchdog);
_watchdogTimer.start(1000);
-#endif
}
-VideoReceiver::~VideoReceiver(void)
+GstVideoReceiver::~GstVideoReceiver(void)
{
-#if defined(QGC_GST_STREAMING)
stop();
- _post([this](){
- _shutdown = true;
- });
- QThread::wait();
-#endif
+ _notificationHandler.shutdown();
+ _apiHandler.shutdown();
}
void
-VideoReceiver::start(const QString& uri, unsigned timeout)
+GstVideoReceiver::start(const QString& uri, unsigned timeout)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this, uri, timeout]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this, uri, timeout]() {
start(uri, timeout);
});
return;
@@ -98,6 +87,8 @@ VideoReceiver::start(const QString& uri, unsigned timeout)
qCDebug(VideoReceiverLog) << "Starting";
+ _endOfStream = false;
+
_timeout = timeout;
bool running = false;
@@ -236,18 +227,13 @@ VideoReceiver::start(const QString& uri, unsigned timeout)
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-started");
qCDebug(VideoReceiverLog) << "Started";
}
-#else
- Q_UNUSED(uri);
- Q_UNUSED(timeout);
-#endif
}
void
-VideoReceiver::stop(void)
+GstVideoReceiver::stop(void)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this]() {
stop();
});
return;
@@ -309,22 +295,28 @@ VideoReceiver::stop(void)
if (_streaming) {
_streaming = false;
- emit streamingChanged();
qCDebug(VideoReceiverLog) << "Streaming stopped";
+ _notificationHandler.dispatch([this](){
+ emit streamingChanged();
+ });
}
}
qCDebug(VideoReceiverLog) << "Stopped";
-#endif
}
void
-VideoReceiver::startDecoding(VideoSink* videoSink)
+GstVideoReceiver::startDecoding(void* sink)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
+ if (sink == nullptr) {
+ qCCritical(VideoReceiverLog) << "VideoSink is NULL";
+ return;
+ }
+
+ if (_apiHandler.needDispatch()) {
+ GstElement* videoSink = GST_ELEMENT(sink);
gst_object_ref(videoSink);
- _post([this, videoSink]() {
+ _apiHandler.dispatch([this, videoSink]() mutable {
startDecoding(videoSink);
gst_object_unref(videoSink);
});
@@ -340,6 +332,8 @@ VideoReceiver::startDecoding(VideoSink* videoSink)
}
}
+ GstElement* videoSink = GST_ELEMENT(sink);
+
if(_videoSink != nullptr || _decoding) {
qCDebug(VideoReceiverLog) << "Already decoding!";
return;
@@ -376,17 +370,13 @@ VideoReceiver::startDecoding(VideoSink* videoSink)
g_object_set(_decoderValve, "drop", FALSE, nullptr);
qCDebug(VideoReceiverLog) << "Decoding started";
-#else
- Q_UNUSED(videoSink)
-#endif
}
void
-VideoReceiver::stopDecoding(void)
+GstVideoReceiver::stopDecoding(void)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this]() {
stopDecoding();
});
return;
@@ -405,15 +395,13 @@ VideoReceiver::stopDecoding(void)
_removingDecoder = true;
_unlinkBranch(_decoderValve);
-#endif
}
void
-VideoReceiver::startRecording(const QString& videoFile, FILE_FORMAT format)
+GstVideoReceiver::startRecording(const QString& videoFile, FILE_FORMAT format)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this, videoFile, format]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this, videoFile, format]() {
startRecording(videoFile, format);
});
return;
@@ -421,8 +409,12 @@ VideoReceiver::startRecording(const QString& videoFile, FILE_FORMAT format)
qCDebug(VideoReceiverLog) << "Starting recording";
- // exit immediately if we are already recording
- if (_pipeline == nullptr || _recording) {
+ if (_pipeline == nullptr) {
+ qCDebug(VideoReceiverLog) << "Streaming is not active!";
+ return;
+ }
+
+ if (_recording) {
qCDebug(VideoReceiverLog) << "Already recording!";
return;
}
@@ -466,23 +458,18 @@ VideoReceiver::startRecording(const QString& videoFile, FILE_FORMAT format)
g_object_set(_recorderValve, "drop", FALSE, nullptr);
_recording = true;
-
- emit recordingChanged();
-
qCDebug(VideoReceiverLog) << "Recording started";
-#else
- Q_UNUSED(videoFile)
- Q_UNUSED(format)
-#endif
+ _notificationHandler.dispatch([this](){
+ emit recordingChanged();
+ });
}
//-----------------------------------------------------------------------------
void
-VideoReceiver::stopRecording(void)
+GstVideoReceiver::stopRecording(void)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this]() {
stopRecording();
});
return;
@@ -501,38 +488,34 @@ VideoReceiver::stopRecording(void)
_removingRecorder = true;
_unlinkBranch(_recorderValve);
-#endif
}
void
-VideoReceiver::takeScreenshot(const QString& imageFile)
+GstVideoReceiver::takeScreenshot(const QString& imageFile)
{
-#if defined(QGC_GST_STREAMING)
- if (!_isOurThread()) {
- _post([this, imageFile]() {
+ if (_apiHandler.needDispatch()) {
+ _apiHandler.dispatch([this, imageFile]() {
takeScreenshot(imageFile);
});
return;
}
// FIXME: AV: record screenshot here
- emit screenshotComplete();
-#else
- Q_UNUSED(imageFile);
-#endif
+ _notificationHandler.dispatch([this](){
+ emit screenshotComplete();
+ });
}
-#if defined(QGC_GST_STREAMING)
-const char* VideoReceiver::_kFileMux[FILE_FORMAT_MAX - FILE_FORMAT_MIN] = {
+const char* GstVideoReceiver::_kFileMux[FILE_FORMAT_MAX - FILE_FORMAT_MIN] = {
"matroskamux",
"qtmux",
"mp4mux"
};
void
-VideoReceiver::_watchdog(void)
+GstVideoReceiver::_watchdog(void)
{
- _post([this](){
+ _apiHandler.dispatch([this](){
if(_pipeline == nullptr) {
return;
}
@@ -544,7 +527,10 @@ VideoReceiver::_watchdog(void)
}
if (now - _lastSourceFrameTime > _timeout) {
- emit timeout();
+ qCDebug(VideoReceiverLog) << "Stream timeout, no frames for " << now - _lastSourceFrameTime;
+ _notificationHandler.dispatch([this](){
+ emit timeout();
+ });
}
if (_decoding && !_removingDecoder) {
@@ -553,36 +539,39 @@ VideoReceiver::_watchdog(void)
}
if (now - _lastVideoFrameTime > _timeout * 2) {
- emit timeout();
+ qCDebug(VideoReceiverLog) << "Video decoder timeout, no frames for " << now - _lastVideoFrameTime;
+ _notificationHandler.dispatch([this](){
+ emit timeout();
+ });
}
}
});
}
void
-VideoReceiver::_handleEOS(void)
+GstVideoReceiver::_handleEOS(void)
{
if(_pipeline == nullptr) {
qCWarning(VideoReceiverLog) << "We should not be here";
return;
}
- if (!_streaming) {
+ if (_endOfStream) {
stop();
} else {
if(_decoding && _removingDecoder) {
_shutdownDecodingBranch();
} else if(_recording && _removingRecorder) {
_shutdownRecordingBranch();
- } else {
+ } /*else {
qCWarning(VideoReceiverLog) << "Unexpected EOS!";
stop();
- }
+ }*/
}
}
GstElement*
-VideoReceiver::_makeSource(const QString& uri)
+GstVideoReceiver::_makeSource(const QString& uri)
{
if (uri.isEmpty()) {
qCCritical(VideoReceiverLog) << "Failed because URI is not specified";
@@ -742,8 +731,10 @@ VideoReceiver::_makeSource(const QString& uri)
}
GstElement*
-VideoReceiver::_makeDecoder(GstCaps* caps, GstElement* videoSink)
+GstVideoReceiver::_makeDecoder(GstCaps* caps, GstElement* videoSink)
{
+ Q_UNUSED(caps);
+
GstElement* decoder = nullptr;
do {
@@ -759,7 +750,7 @@ VideoReceiver::_makeDecoder(GstCaps* caps, GstElement* videoSink)
}
GstElement*
-VideoReceiver::_makeFileSink(const QString& videoFile, FILE_FORMAT format)
+GstVideoReceiver::_makeFileSink(const QString& videoFile, FILE_FORMAT format)
{
GstElement* fileSink = nullptr;
GstElement* mux = nullptr;
@@ -846,7 +837,7 @@ VideoReceiver::_makeFileSink(const QString& videoFile, FILE_FORMAT format)
}
void
-VideoReceiver::_onNewSourcePad(GstPad* pad)
+GstVideoReceiver::_onNewSourcePad(GstPad* pad)
{
// FIXME: check for caps - if this is not video stream (and preferably - one of these which we have to support) then simply skip it
if(!gst_element_link(_source, _tee)) {
@@ -857,7 +848,9 @@ VideoReceiver::_onNewSourcePad(GstPad* pad)
if (!_streaming) {
_streaming = true;
qCDebug(VideoReceiverLog) << "Streaming started";
- emit streamingChanged();
+ _notificationHandler.dispatch([this](){
+ emit streamingChanged();
+ });
}
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, _eosProbe, this, nullptr);
@@ -879,7 +872,7 @@ VideoReceiver::_onNewSourcePad(GstPad* pad)
}
void
-VideoReceiver::_onNewDecoderPad(GstPad* pad)
+GstVideoReceiver::_onNewDecoderPad(GstPad* pad)
{
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-with-new-decoder-pad");
@@ -889,7 +882,7 @@ VideoReceiver::_onNewDecoderPad(GstPad* pad)
}
bool
-VideoReceiver::_addDecoder(GstElement* src)
+GstVideoReceiver::_addDecoder(GstElement* src)
{
GstPad* srcpad;
@@ -942,7 +935,7 @@ VideoReceiver::_addDecoder(GstElement* src)
}
bool
-VideoReceiver::_addVideoSink(GstPad* pad)
+GstVideoReceiver::_addVideoSink(GstPad* pad)
{
GstCaps* caps = gst_pad_query_caps(pad, nullptr);
@@ -981,35 +974,36 @@ VideoReceiver::_addVideoSink(GstPad* pad)
}
_decoding = true;
- emit decodingChanged();
+ qCDebug(VideoReceiverLog) << "Decoding started";
+ _notificationHandler.dispatch([this](){
+ emit decodingChanged();
+ });
return true;
}
void
-VideoReceiver::_noteTeeFrame(void)
+GstVideoReceiver::_noteTeeFrame(void)
{
_lastSourceFrameTime = QDateTime::currentSecsSinceEpoch();
}
void
-VideoReceiver::_noteVideoSinkFrame(void)
+GstVideoReceiver::_noteVideoSinkFrame(void)
{
_lastVideoFrameTime = QDateTime::currentSecsSinceEpoch();
}
void
-VideoReceiver::_noteEndOfStream(void)
+GstVideoReceiver::_noteEndOfStream(void)
{
- if (_streaming) {
- _streaming = false;
- }
+ _endOfStream = true;
}
// -Unlink the branch from the src pad
// -Send an EOS event at the beginning of that branch
void
-VideoReceiver::_unlinkBranch(GstElement* from)
+GstVideoReceiver::_unlinkBranch(GstElement* from)
{
GstPad* src;
@@ -1053,7 +1047,7 @@ VideoReceiver::_unlinkBranch(GstElement* from)
}
void
-VideoReceiver::_shutdownDecodingBranch(void)
+GstVideoReceiver::_shutdownDecodingBranch(void)
{
if (_decoder != nullptr) {
GstObject* parent;
@@ -1097,15 +1091,17 @@ VideoReceiver::_shutdownDecodingBranch(void)
if (_decoding) {
_decoding = false;
- emit decodingChanged();
qCDebug(VideoReceiverLog) << "Decoding stopped";
+ _notificationHandler.dispatch([this](){
+ emit decodingChanged();
+ });
}
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-decoding-stopped");
}
void
-VideoReceiver::_shutdownRecordingBranch(void)
+GstVideoReceiver::_shutdownRecordingBranch(void)
{
gst_bin_remove(GST_BIN(_pipeline), _fileSink);
gst_element_set_state(_fileSink, GST_STATE_NULL);
@@ -1116,51 +1112,21 @@ VideoReceiver::_shutdownRecordingBranch(void)
if (_recording) {
_recording = false;
- emit recordingChanged();
qCDebug(VideoReceiverLog) << "Recording stopped";
+ _notificationHandler.dispatch([this](){
+ emit recordingChanged();
+ });
}
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-recording-stopped");
}
-bool
-VideoReceiver::_isOurThread(void)
-{
- return QThread::currentThread() == (QThread*)this;
-}
-
-void
-VideoReceiver::_post(Task t)
-{
- QMutexLocker lock(&_taskQueueSync);
- _taskQueue.enqueue(t);
- _taskQueueUpdate.wakeOne();
-}
-
-void
-VideoReceiver::run(void)
-{
- while(!_shutdown) {
- _taskQueueSync.lock();
-
- while (_taskQueue.isEmpty()) {
- _taskQueueUpdate.wait(&_taskQueueSync);
- }
-
- Task t = _taskQueue.dequeue();
-
- _taskQueueSync.unlock();
-
- t();
- }
-}
-
gboolean
-VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer data)
+GstVideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer data)
{
Q_UNUSED(bus)
Q_ASSERT(msg != nullptr && data != nullptr);
- VideoReceiver* pThis = (VideoReceiver*)data;
+ GstVideoReceiver* pThis = (GstVideoReceiver*)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
@@ -1181,13 +1147,13 @@ VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer data)
error = nullptr;
}
- pThis->_post([pThis](){
+ pThis->_apiHandler.dispatch([pThis](){
pThis->stop();
});
} while(0);
break;
case GST_MESSAGE_EOS:
- pThis->_post([pThis](){
+ pThis->_apiHandler.dispatch([pThis](){
pThis->_handleEOS();
});
break;
@@ -1208,7 +1174,7 @@ VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer data)
}
if (GST_MESSAGE_TYPE(forward_msg) == GST_MESSAGE_EOS) {
- pThis->_post([pThis](){
+ pThis->_apiHandler.dispatch([pThis](){
pThis->_handleEOS();
});
}
@@ -1225,9 +1191,9 @@ VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer data)
}
void
-VideoReceiver::_onNewPad(GstElement* element, GstPad* pad, gpointer data)
+GstVideoReceiver::_onNewPad(GstElement* element, GstPad* pad, gpointer data)
{
- VideoReceiver* self = static_cast(data);
+ GstVideoReceiver* self = static_cast(data);
if (element == self->_source) {
self->_onNewSourcePad(pad);
@@ -1239,8 +1205,10 @@ VideoReceiver::_onNewPad(GstElement* element, GstPad* pad, gpointer data)
}
void
-VideoReceiver::_wrapWithGhostPad(GstElement* element, GstPad* pad, gpointer data)
+GstVideoReceiver::_wrapWithGhostPad(GstElement* element, GstPad* pad, gpointer data)
{
+ Q_UNUSED(data)
+
gchar* name;
if ((name = gst_pad_get_name(pad)) == nullptr) {
@@ -1268,7 +1236,7 @@ VideoReceiver::_wrapWithGhostPad(GstElement* element, GstPad* pad, gpointer data
}
void
-VideoReceiver::_linkPadWithOptionalBuffer(GstElement* element, GstPad* pad, gpointer data)
+GstVideoReceiver::_linkPadWithOptionalBuffer(GstElement* element, GstPad* pad, gpointer data)
{
bool isRtpPad = false;
@@ -1334,8 +1302,10 @@ VideoReceiver::_linkPadWithOptionalBuffer(GstElement* element, GstPad* pad, gpoi
}
gboolean
-VideoReceiver::_padProbe(GstElement* element, GstPad* pad, gpointer user_data)
+GstVideoReceiver::_padProbe(GstElement* element, GstPad* pad, gpointer user_data)
{
+ Q_UNUSED(element)
+
int* probeRes = (int*)user_data;
*probeRes |= 1;
@@ -1362,8 +1332,12 @@ VideoReceiver::_padProbe(GstElement* element, GstPad* pad, gpointer user_data)
}
gboolean
-VideoReceiver::_autoplugQueryCaps(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
+GstVideoReceiver::_autoplugQueryCaps(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
{
+ Q_UNUSED(bin)
+ Q_UNUSED(pad)
+ Q_UNUSED(element)
+
GstElement* glupload = (GstElement* )data;
GstPad* sinkpad;
@@ -1393,8 +1367,12 @@ VideoReceiver::_autoplugQueryCaps(GstElement* bin, GstPad* pad, GstElement* elem
}
gboolean
-VideoReceiver::_autoplugQueryContext(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
+GstVideoReceiver::_autoplugQueryContext(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
{
+ Q_UNUSED(bin)
+ Q_UNUSED(pad)
+ Q_UNUSED(element)
+
GstElement* glsink = (GstElement* )data;
GstPad* sinkpad;
@@ -1413,7 +1391,7 @@ VideoReceiver::_autoplugQueryContext(GstElement* bin, GstPad* pad, GstElement* e
}
gboolean
-VideoReceiver::_autoplugQuery(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
+GstVideoReceiver::_autoplugQuery(GstElement* bin, GstPad* pad, GstElement* element, GstQuery* query, gpointer data)
{
gboolean ret;
@@ -1433,13 +1411,13 @@ VideoReceiver::_autoplugQuery(GstElement* bin, GstPad* pad, GstElement* element,
}
GstPadProbeReturn
-VideoReceiver::_teeProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
+GstVideoReceiver::_teeProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
{
- Q_UNUSED(pad);
+ Q_UNUSED(pad)
Q_UNUSED(info)
if(user_data != nullptr) {
- VideoReceiver* pThis = static_cast(user_data);
+ GstVideoReceiver* pThis = static_cast(user_data);
pThis->_noteTeeFrame();
}
@@ -1447,10 +1425,13 @@ VideoReceiver::_teeProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
}
GstPadProbeReturn
-VideoReceiver::_videoSinkProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
+GstVideoReceiver::_videoSinkProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
{
+ Q_UNUSED(pad)
+ Q_UNUSED(info)
+
if(user_data != nullptr) {
- VideoReceiver* pThis = static_cast(user_data);
+ GstVideoReceiver* pThis = static_cast(user_data);
if (pThis->_resetVideoSink) {
pThis->_resetVideoSink = false;
@@ -1486,7 +1467,7 @@ VideoReceiver::_videoSinkProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user
}
GstPadProbeReturn
-VideoReceiver::_eosProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
+GstVideoReceiver::_eosProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
{
Q_UNUSED(pad);
Q_ASSERT(user_data != nullptr);
@@ -1495,7 +1476,7 @@ VideoReceiver::_eosProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
GstEvent* event = gst_pad_probe_info_get_event(info);
if (GST_EVENT_TYPE(event) == GST_EVENT_EOS) {
- VideoReceiver* pThis = static_cast(user_data);
+ GstVideoReceiver* pThis = static_cast(user_data);
pThis->_noteEndOfStream();
}
}
@@ -1504,7 +1485,7 @@ VideoReceiver::_eosProbe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
}
GstPadProbeReturn
-VideoReceiver::_keyframeWatch(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
+GstVideoReceiver::_keyframeWatch(GstPad* pad, GstPadProbeInfo* info, gpointer user_data)
{
if (info == nullptr || user_data == nullptr) {
qCCritical(VideoReceiverLog) << "Invalid arguments";
@@ -1520,7 +1501,7 @@ VideoReceiver::_keyframeWatch(GstPad* pad, GstPadProbeInfo* info, gpointer user_
// set media file '0' offset to current timeline position - we don't want to touch other elements in the graph, except these which are downstream!
gst_pad_set_offset(pad, -static_cast(buf->pts));
- VideoReceiver* pThis = static_cast(user_data);
+ GstVideoReceiver* pThis = static_cast(user_data);
qCDebug(VideoReceiverLog) << "Got keyframe, stop dropping buffers";
@@ -1528,4 +1509,3 @@ VideoReceiver::_keyframeWatch(GstPad* pad, GstPadProbeInfo* info, gpointer user_
return GST_PAD_PROBE_REMOVE;
}
-#endif
diff --git a/src/VideoStreaming/VideoReceiver.h b/src/VideoReceiver/GstVideoReceiver.h
similarity index 70%
rename from src/VideoStreaming/VideoReceiver.h
rename to src/VideoReceiver/GstVideoReceiver.h
index d30e902bdacaa8f5fda5bc08e58eaabd5264c5a7..87700bc8e7067b04b80bde1fda2bfcceac67e699 100644
--- a/src/VideoStreaming/VideoReceiver.h
+++ b/src/VideoReceiver/GstVideoReceiver.h
@@ -16,81 +16,87 @@
#pragma once
#include "QGCLoggingCategory.h"
-#include
-#include
#include
-#include
#include
#include
#include
#include
+#include
+
+#include "VideoReceiver.h"
-#if defined(QGC_GST_STREAMING)
#include
-typedef GstElement VideoSink;
-#else
-typedef void VideoSink;
-#endif
Q_DECLARE_LOGGING_CATEGORY(VideoReceiverLog)
-class VideoReceiver : public QThread
+class Worker : public QThread
{
Q_OBJECT
public:
- explicit VideoReceiver(QObject* parent = nullptr);
- ~VideoReceiver(void);
-
- typedef enum {
- FILE_FORMAT_MIN = 0,
- FILE_FORMAT_MKV = FILE_FORMAT_MIN,
- FILE_FORMAT_MOV,
- FILE_FORMAT_MP4,
- FILE_FORMAT_MAX
- } FILE_FORMAT;
-
- Q_PROPERTY(bool streaming READ streaming NOTIFY streamingChanged)
- Q_PROPERTY(bool decoding READ decoding NOTIFY decodingChanged)
- Q_PROPERTY(bool recording READ recording NOTIFY recordingChanged)
- Q_PROPERTY(QSize videoSize READ videoSize NOTIFY videoSizeChanged)
-
- bool streaming(void) {
- return _streaming;
+ bool needDispatch() {
+ return QThread::currentThread() != this;
}
- bool decoding(void) {
- return _decoding;
+ void dispatch(std::function t) {
+ QMutexLocker lock(&_taskQueueSync);
+ _taskQueue.enqueue(t);
+ _taskQueueUpdate.wakeOne();
}
- bool recording(void) {
- return _recording;
+ void shutdown() {
+ if (needDispatch()) {
+ dispatch([this](){
+ _shutdown = true;
+ });
+ QThread::wait();
+ } else {
+ QThread::terminate();
+ }
}
- QSize videoSize(void) {
- const quint32 size = _videoSize;
- return QSize((size >> 16) & 0xFFFF, size & 0xFFFF);
+protected:
+ void run() {
+ while(!_shutdown) {
+ _taskQueueSync.lock();
+
+ while (_taskQueue.isEmpty()) {
+ _taskQueueUpdate.wait(&_taskQueueSync);
+ }
+
+ Task t = _taskQueue.dequeue();
+
+ _taskQueueSync.unlock();
+
+ t();
+ }
}
-signals:
- void timeout(void);
- void streamingChanged(void);
- void decodingChanged(void);
- void recordingChanged(void);
- void recordingStarted(void);
- void videoSizeChanged(void);
- void screenshotComplete(void);
+private:
+ typedef std::function Task;
+ QWaitCondition _taskQueueUpdate;
+ QMutex _taskQueueSync;
+ QQueue _taskQueue;
+ bool _shutdown = false;
+};
+
+class GstVideoReceiver : public VideoReceiver
+{
+ Q_OBJECT
+
+public:
+ explicit GstVideoReceiver(QObject* parent = nullptr);
+ ~GstVideoReceiver(void);
public slots:
virtual void start(const QString& uri, unsigned timeout);
virtual void stop(void);
- virtual void startDecoding(VideoSink* videoSink);
+ virtual void startDecoding(void* sink);
virtual void stopDecoding(void);
virtual void startRecording(const QString& videoFile, FILE_FORMAT format);
virtual void stopRecording(void);
virtual void takeScreenshot(const QString& imageFile);
-#if defined(QGC_GST_STREAMING)
protected slots:
virtual void _watchdog(void);
virtual void _handleEOS(void);
@@ -116,11 +122,6 @@ protected:
virtual void _shutdownDecodingBranch (void);
virtual void _shutdownRecordingBranch(void);
- typedef std::function Task;
- bool _isOurThread(void);
- void _post(Task t);
- void run(void);
-
private:
static gboolean _onBusMessage(GstBus* bus, GstMessage* message, gpointer user_data);
static void _onNewPad(GstElement* element, GstPad* pad, gpointer data);
@@ -158,18 +159,14 @@ private:
unsigned _timeout;
- QWaitCondition _taskQueueUpdate;
- QMutex _taskQueueSync;
- QQueue _taskQueue;
- bool _shutdown;
+ Worker _apiHandler;
+ Worker _notificationHandler;
- static const char* _kFileMux[FILE_FORMAT_MAX - FILE_FORMAT_MIN];
-#else
-private:
-#endif
+ bool _endOfStream;
- std::atomic _streaming;
- std::atomic _decoding;
- std::atomic _recording;
- std::atomic_videoSize;
+ static const char* _kFileMux[FILE_FORMAT_MAX - FILE_FORMAT_MIN];
};
+
+void* createVideoSink(void* widget);
+
+void initializeVideoReceiver(int argc, char* argv[], int debuglevel);
diff --git a/src/VideoStreaming/README.md b/src/VideoReceiver/README.md
similarity index 100%
rename from src/VideoStreaming/README.md
rename to src/VideoReceiver/README.md
diff --git a/src/VideoReceiver/VideoReceiver.h b/src/VideoReceiver/VideoReceiver.h
new file mode 100644
index 0000000000000000000000000000000000000000..9df1bd9cb8a385aed921cc8d0cf1d79c2fa8c826
--- /dev/null
+++ b/src/VideoReceiver/VideoReceiver.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+ *
+ * (c) 2009-2020 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+/**
+ * @file
+ * @brief QGC Video Receiver
+ * @author Gus Grubba
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+class VideoReceiver : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit VideoReceiver(QObject* parent = nullptr)
+ : QObject(parent)
+ , _streaming(false)
+ , _decoding(false)
+ , _recording(false)
+ , _videoSize(0)
+ {}
+
+ virtual ~VideoReceiver(void) {}
+
+ typedef enum {
+ FILE_FORMAT_MIN = 0,
+ FILE_FORMAT_MKV = FILE_FORMAT_MIN,
+ FILE_FORMAT_MOV,
+ FILE_FORMAT_MP4,
+ FILE_FORMAT_MAX
+ } FILE_FORMAT;
+
+ Q_PROPERTY(bool streaming READ streaming NOTIFY streamingChanged)
+ Q_PROPERTY(bool decoding READ decoding NOTIFY decodingChanged)
+ Q_PROPERTY(bool recording READ recording NOTIFY recordingChanged)
+ Q_PROPERTY(QSize videoSize READ videoSize NOTIFY videoSizeChanged)
+
+ bool streaming(void) {
+ return _streaming;
+ }
+
+ bool decoding(void) {
+ return _decoding;
+ }
+
+ bool recording(void) {
+ return _recording;
+ }
+
+ QSize videoSize(void) {
+ const quint32 size = _videoSize;
+ return QSize((size >> 16) & 0xFFFF, size & 0xFFFF);
+ }
+
+signals:
+ void timeout(void);
+ void streamingChanged(void);
+ void decodingChanged(void);
+ void recordingChanged(void);
+ void recordingStarted(void);
+ void videoSizeChanged(void);
+ void screenshotComplete(void);
+
+public slots:
+ virtual void start(const QString& uri, unsigned timeout) = 0;
+ virtual void stop(void) = 0;
+ virtual void startDecoding(void* sink) = 0;
+ virtual void stopDecoding(void) = 0;
+ virtual void startRecording(const QString& videoFile, FILE_FORMAT format) = 0;
+ virtual void stopRecording(void) = 0;
+ virtual void takeScreenshot(const QString& imageFile) = 0;
+
+protected:
+ std::atomic _streaming;
+ std::atomic _decoding;
+ std::atomic _recording;
+ std::atomic_videoSize;
+};
+
diff --git a/src/VideoStreaming/VideoStreaming.pri b/src/VideoReceiver/VideoReceiver.pri
similarity index 94%
rename from src/VideoStreaming/VideoStreaming.pri
rename to src/VideoReceiver/VideoReceiver.pri
index b1af89f0c03373700cc9cad127d916f04122135b..7b93e2582ceaca73d7461aa25facd881230bb2a8 100644
--- a/src/VideoStreaming/VideoStreaming.pri
+++ b/src/VideoReceiver/VideoReceiver.pri
@@ -122,22 +122,30 @@ VideoEnabled {
DEFINES += \
QGC_GST_STREAMING
+ INCLUDEPATH += \
+ $$PWD
+
iOSBuild {
OBJECTIVE_SOURCES += \
- $$PWD/iOS/gst_ios_init.m
- INCLUDEPATH += \
- $$PWD/iOS
+ $$PWD/gst_ios_init.m
}
+ HEADERS += \
+ $$PWD/GStreamer.h \
+ $$PWD/GstVideoReceiver.h \
+ $$PWD/VideoReceiver.h
+
SOURCES += \
$$PWD/gstqgcvideosinkbin.c \
- $$PWD/gstqgc.c
+ $$PWD/gstqgc.c \
+ $$PWD/GStreamer.cc \
+ $$PWD/GstVideoReceiver.cc
include($$PWD/../../qmlglsink.pri)
} else {
LinuxBuild|MacBuild|iOSBuild|WindowsBuild|AndroidBuild {
message("Skipping support for video streaming (GStreamer libraries not installed)")
- message("Installation instructions here: https://github.com/mavlink/qgroundcontrol/blob/master/src/VideoStreaming/README.md")
+ message("Installation instructions here: https://github.com/mavlink/qgroundcontrol/blob/master/src/VideoReceiver/README.md")
} else {
message("Skipping support for video streaming (Unsupported platform)")
}
diff --git a/src/VideoStreaming/iOS/gst_ios_init.h b/src/VideoReceiver/gst_ios_init.h
similarity index 100%
rename from src/VideoStreaming/iOS/gst_ios_init.h
rename to src/VideoReceiver/gst_ios_init.h
diff --git a/src/VideoStreaming/iOS/gst_ios_init.m b/src/VideoReceiver/gst_ios_init.m
similarity index 100%
rename from src/VideoStreaming/iOS/gst_ios_init.m
rename to src/VideoReceiver/gst_ios_init.m
diff --git a/src/VideoStreaming/gstqgc.c b/src/VideoReceiver/gstqgc.c
similarity index 100%
rename from src/VideoStreaming/gstqgc.c
rename to src/VideoReceiver/gstqgc.c
diff --git a/src/VideoStreaming/gstqgcvideosinkbin.c b/src/VideoReceiver/gstqgcvideosinkbin.c
similarity index 100%
rename from src/VideoStreaming/gstqgcvideosinkbin.c
rename to src/VideoReceiver/gstqgcvideosinkbin.c
diff --git a/src/VideoStreaming/CMakeLists.txt b/src/VideoStreaming/CMakeLists.txt
deleted file mode 100644
index 13861ae3cdbf1195c8742187333521ded65beae5..0000000000000000000000000000000000000000
--- a/src/VideoStreaming/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-set(EXTRA_LIBRARIES)
-
-if (GST_FOUND)
- set(EXTRA_LIBRARIES ${GST_LIBRARIES})
-endif()
-
-add_library(VideoStreaming
- GLVideoItemStub.cc
- GLVideoItemStub.h
- gstqgc.c
- gstqgcvideosinkbin.c
- SubtitleWriter.cc
- SubtitleWriter.h
- VideoReceiver.cc
- VideoReceiver.h
- VideoStreaming.cc
- VideoStreaming.h
- VideoManager.cc
- VideoManager.h
-)
-
-target_link_libraries(VideoStreaming
- PRIVATE
- gst_plugins_good
- PUBLIC
- qgc
- Qt5::Multimedia
- Qt5::OpenGL
- ${EXTRA_LIBRARIES}
-)
-
-target_include_directories(VideoStreaming INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/VideoStreaming/VideoStreaming.h b/src/VideoStreaming/VideoStreaming.h
deleted file mode 100644
index 4551271bce079aa5cfc657b86b6eaac1aaebb5f0..0000000000000000000000000000000000000000
--- a/src/VideoStreaming/VideoStreaming.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/****************************************************************************
- *
- * (c) 2009-2020 QGROUNDCONTROL PROJECT
- *
- * QGroundControl is licensed according to the terms in the file
- * COPYING.md in the root of the source code directory.
- *
- ****************************************************************************/
-
-
-/**
- * @file
- * @brief QGC Video Streaming Initialization
- * @author Gus Grubba
- */
-
-#pragma once
-
-extern void initializeVideoStreaming (int &argc, char *argv[], int gstDebuglevel);
diff --git a/src/api/QGCCorePlugin.cc b/src/api/QGCCorePlugin.cc
index e9d5a9eef97ac9dd4dfa847acc9e6a051a1442e2..48be2c132ab8cafee7cd52e8ec913372c6a7ce53 100644
--- a/src/api/QGCCorePlugin.cc
+++ b/src/api/QGCCorePlugin.cc
@@ -16,7 +16,10 @@
#include "AppMessages.h"
#include "QmlObjectListModel.h"
#include "VideoManager.h"
+#if defined(QGC_GST_STREAMING)
+#include "GStreamer.h"
#include "VideoReceiver.h"
+#endif
#include "QGCLoggingCategory.h"
#include "QGCCameraManager.h"
@@ -440,7 +443,23 @@ VideoManager* QGCCorePlugin::createVideoManager(QGCApplication *app, QGCToolbox
VideoReceiver* QGCCorePlugin::createVideoReceiver(QObject* parent)
{
- return new VideoReceiver(parent);
+#if defined(QGC_GST_STREAMING)
+ return GStreamer::createVideoReceiver(parent);
+#else
+ Q_UNUSED(parent)
+ return nullptr;
+#endif
+}
+
+void* QGCCorePlugin::createVideoSink(QObject* parent, QQuickItem* widget)
+{
+#if defined(QGC_GST_STREAMING)
+ return GStreamer::createVideoSink(parent, widget);
+#else
+ Q_UNUSED(parent)
+ Q_UNUSED(widget)
+ return nullptr;
+#endif
}
bool QGCCorePlugin::guidedActionsControllerLogging() const
diff --git a/src/api/QGCCorePlugin.h b/src/api/QGCCorePlugin.h
index 122fc8846eb4ef832908aab5750f9e5ccc4f9b08..865bbf68b029c71501e493c604b5a2385ccd24f4 100644
--- a/src/api/QGCCorePlugin.h
+++ b/src/api/QGCCorePlugin.h
@@ -32,9 +32,11 @@ class Vehicle;
class LinkInterface;
class QmlObjectListModel;
class VideoReceiver;
+class VideoSink;
class PlanMasterController;
class QGCCameraManager;
class QGCCameraControl;
+class QQuickItem;
class QGCCorePlugin : public QGCTool
{
@@ -113,6 +115,8 @@ public:
virtual VideoManager* createVideoManager(QGCApplication* app, QGCToolbox* toolbox);
/// Allows the plugin to override the creation of VideoReceiver.
virtual VideoReceiver* createVideoReceiver(QObject* parent);
+ /// Allows the plugin to override the creation of VideoSink.
+ virtual void* createVideoSink(QObject* parent, QQuickItem* widget);
/// Allows the plugin to see all mavlink traffic to a vehicle
/// @return true: Allow vehicle to continue processing, false: Vehicle should not process message