From 16ff5f963cdd12f3e6f2001799eb355562d0c6a7 Mon Sep 17 00:00:00 2001
From: Silas Della Contrada <s.develop@4-dc.de>
Date: Wed, 27 Jan 2021 10:14:11 +0100
Subject: [PATCH] Feature complete: Automatic encoder selection based on gpu

 - Don't use the nVidia GPU on optimus devices for the application if
you have an GeForce MX GPU, because they don't have any video de-/
encoders and the application selects the encoder based on the rendering
GPU
---
 VanadiumCast/VanadiumCast.pro                 |  3 +-
 VanadiumCast/res/gui/fragments/PageMedia.qml  |  2 +-
 VanadiumCast/src/main.cpp                     |  5 +-
 backend/backend.pro                           |  3 +
 backend/src/MediaProcessing/OGLUtil.cpp       | 61 +++++++++++++++++++
 backend/src/MediaProcessing/OGLUtil.h         | 31 ++++++++++
 .../src/MediaProcessing/VideoTranscoder.cpp   | 10 ++-
 backend/src/MediaProcessing/VideoTranscoder.h | 50 +++++++++++++--
 8 files changed, 156 insertions(+), 9 deletions(-)
 create mode 100644 backend/src/MediaProcessing/OGLUtil.cpp
 create mode 100644 backend/src/MediaProcessing/OGLUtil.h

diff --git a/VanadiumCast/VanadiumCast.pro b/VanadiumCast/VanadiumCast.pro
index 9be353e..02657b9 100644
--- a/VanadiumCast/VanadiumCast.pro
+++ b/VanadiumCast/VanadiumCast.pro
@@ -1,4 +1,4 @@
-QT += core quick quickcontrols2 gui widgets multimedia network concurrent
+QT += core quick quickcontrols2 gui widgets multimedia network concurrent opengl
 
 CONFIG += qtquickcompiler
 
@@ -23,6 +23,7 @@ defineTest(copyToDestDir) {
 
 win32 {
     QMAKE_CXXFLAGS_RELEASE += /O2 /Oy
+    LIBS += -lOpenGL32
     CONFIG(release, debug|release) {
         message("release")
 #        LIBS += -LE:/Dev/QtAV/build-release/lib_win_x86_64 -lQtAV1 -lQtAVWidgets1
diff --git a/VanadiumCast/res/gui/fragments/PageMedia.qml b/VanadiumCast/res/gui/fragments/PageMedia.qml
index 317075e..81326ac 100644
--- a/VanadiumCast/res/gui/fragments/PageMedia.qml
+++ b/VanadiumCast/res/gui/fragments/PageMedia.qml
@@ -114,7 +114,7 @@ Page {
             anchors.topMargin: 8
             height: 1280
             width: 720
-            videoCodecPriority: ["QSV", "DXVA", "MMAL", "CUDA", "FFMPEG"]
+            videoCodecPriority: ["DXVA", "QSV", "MMAL", "CUDA", "VAAPI", "FFMPEG"]
             audioBackends: ["OpenAL", "XAudio2", "null"]
             Component.onCompleted: console.log(previewVideo.audioBackends)
             smooth: true
diff --git a/VanadiumCast/src/main.cpp b/VanadiumCast/src/main.cpp
index cd7b1cb..a05602c 100644
--- a/VanadiumCast/src/main.cpp
+++ b/VanadiumCast/src/main.cpp
@@ -7,6 +7,8 @@
 #include <QtNetwork>
 #include <csignal>
 #include "API/NetworkAPI.h"
+#include <QtOpenGL>
+#include <QtOpenGL/QGLContext>
 
 
 QApplication *app;
@@ -25,8 +27,9 @@ int main(int argc, char *argv[])
     app = new QApplication(argc, argv);
 
     NetworkAPI api;
-    api.init();
     QtAV::Widgets::registerRenderers();
+    qDebug() << QtAV::VideoEncoder::supportedCodecs();
+    api.init();
 
     QQmlApplicationEngine engine;
     engine.rootContext()->setContextProperty("deviceDirectory", api.getDeviceDirectory());
diff --git a/backend/backend.pro b/backend/backend.pro
index ca40f0b..65f2a7a 100644
--- a/backend/backend.pro
+++ b/backend/backend.pro
@@ -4,6 +4,7 @@ QT += core gui widgets network multimedia quick concurrent
 
 win32 {
     QMAKE_CXXFLAGS_RELEASE += /O2 /Oy
+    LIBS += -lOpenGL32
     CONFIG(release, debug|release) {
         message("release")
         message("$$QT.core.libs")
@@ -33,6 +34,7 @@ SOURCES += src/util.cpp \
            src/GUI/WindowCloseEventFilter.cpp \
            src/MediaProcessing/CachedLocalStream.cpp \
            src/MediaProcessing/InputFile.cpp \
+           src/MediaProcessing/OGLUtil.cpp \
            src/MediaProcessing/VideoTranscoder.cpp \
            src/Networking/NetworkDevice.cpp \
            src/Networking/NetworkDeviceDirectory.cpp \
@@ -48,6 +50,7 @@ HEADERS += src/util.h \
            src/GUI/WindowCloseEventFilter.h \
            src/MediaProcessing/CachedLocalStream.h \
            src/MediaProcessing/InputFile.h \
+           src/MediaProcessing/OGLUtil.h \
            src/MediaProcessing/VideoTranscoder.h \
            src/Networking/NetworkDevice.h \
            src/Networking/NetworkDeviceDirectory.h \
diff --git a/backend/src/MediaProcessing/OGLUtil.cpp b/backend/src/MediaProcessing/OGLUtil.cpp
new file mode 100644
index 0000000..8fb7735
--- /dev/null
+++ b/backend/src/MediaProcessing/OGLUtil.cpp
@@ -0,0 +1,61 @@
+#include "OGLUtil.h"
+#include <QtOpenGL/QtOpenGL>
+
+OGLUtil::OGLUtil(QObject *parent) : QObject(parent)
+{
+
+}
+
+QVariant OGLUtil::getResult() {
+    if (done) {
+        return result;
+    } else {
+        return QVariant();
+    }
+}
+
+bool OGLUtil::waitForFinished(qint64 msecs)
+{
+    auto t1 = std::chrono::system_clock::now();
+    while (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - t1).count() <= msecs && !done) {
+        QCoreApplication::processEvents();
+    }
+    return done;
+}
+
+bool OGLUtil::event(QEvent *event)
+{
+    if (event->type() == QEvent::User) {
+        switch (action) {
+            case Action::GET_OGL_VENDOR: {
+                QOpenGLContext *context = new QOpenGLContext();
+                QOffscreenSurface *surface = new QOffscreenSurface();
+                context->create();
+                surface->create();
+                context->makeCurrent(surface);
+                QMutexLocker locker(&resultLock);
+                result.setValue(QString::fromLocal8Bit(reinterpret_cast<const char*>(glGetString(GL_VENDOR))));
+                locker.unlock();
+                surface->destroy();
+                delete context;
+                delete surface;
+                done = true;
+                break;
+            }
+        }
+        return true;
+    }
+    return QObject::event(event);
+}
+
+bool OGLUtil::triggerAction(OGLUtil::Action action)
+{
+    if (done) {
+        done = false;
+        this->action = action;
+        QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/backend/src/MediaProcessing/OGLUtil.h b/backend/src/MediaProcessing/OGLUtil.h
new file mode 100644
index 0000000..b800412
--- /dev/null
+++ b/backend/src/MediaProcessing/OGLUtil.h
@@ -0,0 +1,31 @@
+#ifndef OGLUTIL_H
+#define OGLUTIL_H
+
+#include <QtCore>
+
+class OGLUtil : public QObject
+{
+    Q_OBJECT
+public:
+    enum class Action {
+        GET_OGL_VENDOR
+    };
+    explicit OGLUtil(QObject *parent = nullptr);
+
+    bool waitForFinished(qint64 msecs);
+
+    QVariant getResult();
+
+public slots:
+    bool event(QEvent *event) override;
+
+    bool triggerAction(Action action);
+
+private:
+    QMutex resultLock;
+    QVariant result;
+    Action action;
+    bool done = true;
+};
+
+#endif // OGLUTIL_H
diff --git a/backend/src/MediaProcessing/VideoTranscoder.cpp b/backend/src/MediaProcessing/VideoTranscoder.cpp
index 299d697..306e096 100644
--- a/backend/src/MediaProcessing/VideoTranscoder.cpp
+++ b/backend/src/MediaProcessing/VideoTranscoder.cpp
@@ -22,7 +22,15 @@ VideoTranscoder::VideoTranscoder(std::string inputFilePath, End *outputDevice, E
     avPlayer->setAudioStream(-1);
 //    }
     avPlayer->setFrameRate(10000.0);
-    avPlayer->setVideoDecoderPriority(QStringList() << "QSV" << "DXVA" << "VAAPI" << "MMAL" << "VideoToolbox" << "CUDA" << "FFmpeg");
+#ifdef _WIN32
+    avPlayer->setVideoDecoderPriority(QStringList() << "DXVA" << "QSV" << "CUDA" << "FFmpeg");
+#endif
+#ifdef __linux__
+    avPlayer->setVideoDecoderPriority(QStringList() << "QSV" << "CUDA" << "VAAPI" << "FFmpeg");
+#endif
+#ifdef __APPLE__
+    avPlayer->setVideoDecoderPriority(QStringList() << "VideoToolbox" << "FFmpeg");
+#endif
     bufferCon1 = connect(outputDevice, &End::outputUnderrun, [&]() {
         if (!isPausedByUser && avPlayer->isPaused()) {
 //            qDebug() << "[VideoTranscoder] Resuming";
diff --git a/backend/src/MediaProcessing/VideoTranscoder.h b/backend/src/MediaProcessing/VideoTranscoder.h
index 2b3589d..91e1934 100644
--- a/backend/src/MediaProcessing/VideoTranscoder.h
+++ b/backend/src/MediaProcessing/VideoTranscoder.h
@@ -2,12 +2,15 @@
 #define VIDEOTRANSCODER_H
 
 #include <QObject>
+#include <QOffscreenSurface>
 #include <QtAV/QtAV>
 #include <QtAV/AVTranscoder.h>
 #include <QtAVWidgets/QtAVWidgets>
 #include "PlayerStateSlots.h"
 #include "EncodingProfile.h"
 #include "CachedLocalStream.h"
+#include <gl/GL.h>
+#include "OGLUtil.h"
 
 class VideoTranscoder : public QObject {
 Q_OBJECT
@@ -47,9 +50,45 @@ private:
     static void initializeProfiles() {
         static bool encodingProfilesInitialized = false;
         if (!encodingProfilesInitialized) {
+            OGLUtil *oglutil = new OGLUtil;
+            oglutil->moveToThread(qApp->thread());
+            oglutil->triggerAction(OGLUtil::Action::GET_OGL_VENDOR);
+            oglutil->waitForFinished(10000);
+            QString vendor = oglutil->getResult().toString();
+            QString videoCodecSQ = "";
+            QString videoCodecHQ = "";
+//            qDebug() << "[VideoTranscoder] Video card vendor:";
+
+#ifdef __APPLE__
+            videoCodecSQ = "h264_videotoolbox";
+            videoCodecHQ = "hevc_videotoolbox";
+#else
+            qDebug() << "[VideoTranscoder] OpenGL Renderer:" << vendor;
+            if (vendor.compare("Intel", Qt::CaseInsensitive) == 0) {
+                qDebug() << "[VideoTranscoder] Intel QSV encoder selected";
+                videoCodecSQ = "h264_qsv";
+                videoCodecHQ = "h264_qsv";
+            } else if (vendor.compare("NVIDIA Corporation", Qt::CaseInsensitive) == 0) {
+                qDebug() << "[VideoTranscoder] nVidia NVENC encoder selected";
+                videoCodecSQ = "h264_nvenc";
+                videoCodecHQ = "hevc_nvenc";
+            } else if (vendor.compare("AMD", Qt::CaseInsensitive) == 0) {
+#ifdef _WIN32
+                qDebug() << "[VideoTranscoder] AMD AMF encoder selected";
+                videoCodecSQ = "h264_amf";
+                videoCodecHQ = "hevc_amf";
+#endif
+#ifdef __linux__
+                qDebug() << "[VideoTranscoder] AMD VAAPI encoder selected";
+                videoCodec1 = "h264_vaapi";
+                videoCodec2 = "hevc_vaapi";
+#endif
+            }
+#endif
+
             // Low profile
             LOW.audioCodecName = "aac";
-            LOW.videoCodecName = "h264_qsv";
+            LOW.videoCodecName = videoCodecSQ;
             LOW.width = 1280;
             LOW.height = 720;
             LOW.rate = 1000000;
@@ -58,7 +97,7 @@ private:
 
             // Standard profile
             MEDIUM.audioCodecName = "aac";
-            MEDIUM.videoCodecName = "h264_qsv";
+            MEDIUM.videoCodecName = videoCodecSQ;
             MEDIUM.width = 1920;
             MEDIUM.height = 1080;
             MEDIUM.rate = 5000000;
@@ -67,7 +106,7 @@ private:
 
             // High profile
             HIGH.audioCodecName = "aac";
-            HIGH.videoCodecName = "h264_qsv";
+            HIGH.videoCodecName = videoCodecSQ;
             HIGH.width = 1920;
             HIGH.height = 1080;
             HIGH.rate = 10000000;
@@ -76,7 +115,7 @@ private:
 
             // Ultra profile
             ULTRA.audioCodecName = "aac";
-            ULTRA.videoCodecName = "hevc_qsv";
+            ULTRA.videoCodecName = videoCodecHQ;
             ULTRA.width = 2560;
             ULTRA.height = 1440;
             ULTRA.rate = 15000000;
@@ -85,7 +124,7 @@ private:
 
             // Extreme profile
             EXTREME.audioCodecName = "aac";
-            EXTREME.videoCodecName = "hevc_qsv";
+            EXTREME.videoCodecName = videoCodecHQ;
             EXTREME.width = 3840;
             EXTREME.height = 2160;
             EXTREME.rate = 30000000;
@@ -103,6 +142,7 @@ private:
     QMetaObject::Connection bufferCon1, bufferCon2, posCon1, posCon2;
     bool isPausedByUser = false;
     qint64 duration = 0;
+    OGLUtil *oglutil;
 };
 
 #endif // VIDEOTRANSCODER_H
-- 
GitLab