Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57137412b5 |
No files matched your search
@@ -1,5 +1 @@
|
|||||||
*pro.user
|
*pro.user
|
||||||
|
|
||||||
|
|
||||||
**/.vs/
|
|
||||||
build/
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
[submodule "moonlight-common-c/moonlight-common-c"]
|
[submodule "moonlight-common-c/moonlight-common-c"]
|
||||||
path = moonlight-common-c/moonlight-common-c
|
path = moonlight-common-c/moonlight-common-c
|
||||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||||
|
[submodule "opus/opus"]
|
||||||
|
path = opus/opus
|
||||||
|
url = https://github.com/xiph/opus.git
|
||||||
[submodule "qmdnsengine/qmdnsengine"]
|
[submodule "qmdnsengine/qmdnsengine"]
|
||||||
path = qmdnsengine/qmdnsengine
|
path = qmdnsengine/qmdnsengine
|
||||||
url = https://github.com/cgutman/qmdnsengine.git
|
url = https://github.com/cgutman/qmdnsengine.git
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
language: cpp
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: osx
|
|
||||||
compiler: clang
|
|
||||||
- os: linux
|
|
||||||
sudo: required
|
|
||||||
dist: xenial
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- sourceline: 'ppa:beineri/opt-qt596-xenial'
|
|
||||||
- sourceline: 'ppa:jonathonf/ffmpeg-4'
|
|
||||||
packages:
|
|
||||||
- qt59base
|
|
||||||
- qt59quickcontrols2
|
|
||||||
- qt59svg
|
|
||||||
- libesd0-dev
|
|
||||||
- libgl-dev
|
|
||||||
- libopus-dev
|
|
||||||
- libavcodec-dev
|
|
||||||
- libavutil-dev
|
|
||||||
- libva-dev
|
|
||||||
- libvdpau-dev
|
|
||||||
|
|
||||||
install:
|
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew install qt5'
|
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew link --force qt5'
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_VER=2.0.8 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make && sudo make install && cd ..; fi
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- '[ "$TRAVIS_OS_NAME" != "linux" ] || source /opt/qt59/bin/qt59-env.sh'
|
|
||||||
- qmake -v
|
|
||||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
|
|
||||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
|
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || export QMAKESPEC=macx-clang'
|
|
||||||
|
|
||||||
script:
|
|
||||||
- qmake moonlight-qt.pro -spec $QMAKESPEC
|
|
||||||
- make debug
|
|
||||||
- make release
|
|
||||||
@@ -1,31 +1,20 @@
|
|||||||
# Moonlight PC
|
# Moonlight PC
|
||||||
|
|
||||||
[Moonlight PC](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
|
[Moonlight PC](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux.
|
||||||
|
|
||||||
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
|
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
|
||||||
|
|
||||||
See the [releases page](https://github.com/moonlight-stream/moonlight-qt/releases) for download links for Windows and Mac. For Linux, we're on [Flathub](https://flathub.org/apps/details/com.moonlight_stream.Moonlight).
|
This is a work-in-progress port and is only suitable for development use at this point.
|
||||||
|
|
||||||
You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
|
||||||
[](https://travis-ci.org/moonlight-stream/moonlight-qt)
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
|
||||||
- Supports streaming at up to 120 FPS on high refresh rate monitors
|
|
||||||
- Supports streaming at 720p, 1080p, 1440p, or 4K
|
|
||||||
- 5.1 surround sound audio
|
|
||||||
- HEVC support for better image quality at reduced bandwidth
|
|
||||||
- Keyboard and mouse support
|
|
||||||
- Gamepad support with SDL gamepad mappings
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download (select MSVC toolchain on Windows)
|
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
3. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||||
3. Open the project in Qt Creator or build from qmake on the command line
|
4. Open the project in Qt Creator or build from qmake on the command line
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
|
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||||
|
|
||||||
1. Fork us
|
1. Fork us
|
||||||
2. Write code
|
2. Write code
|
||||||
3. Send Pull Requests
|
3. Send Pull Requests
|
||||||
|
|||||||
@@ -3,28 +3,24 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Moonlight</string>
|
<string>moonlight-qt</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>Stream games from your NVIDIA GameStream-enabled PC</string>
|
<string>Created by Qt/QMake</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>moonlight</string>
|
<string>macos</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.moonlight-stream.Moonlight</string>
|
<string>com.yourcompany.moonlight-qt</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.10</string>
|
<string>10.10</string>
|
||||||
|
<key>NOTE</key>
|
||||||
|
<string>This file was generated by Qt/QMake.</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0.1.0</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Moonlight</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
QT += core quick network quickcontrols2 svg
|
QT += core quick network quickcontrols2
|
||||||
CONFIG += c++11
|
CONFIG += c++11
|
||||||
|
|
||||||
unix:!macx {
|
TARGET = moonlight-qt
|
||||||
TARGET = moonlight
|
|
||||||
} else {
|
|
||||||
# On macOS, this is the name displayed in the global menu bar
|
|
||||||
TARGET = Moonlight
|
|
||||||
}
|
|
||||||
|
|
||||||
# Support debug and release builds from command line for CI
|
|
||||||
CONFIG += debug_and_release
|
|
||||||
|
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
@@ -37,36 +28,20 @@ win32 {
|
|||||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib
|
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
INCLUDEPATH += $$PWD/../libs/mac/include
|
||||||
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
LIBS += -L$$PWD/../libs/mac/lib
|
||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += openssl sdl2 opus
|
PKGCONFIG += openssl sdl2 libavcodec libavdevice libavformat libavutil
|
||||||
LIBS += -ldl
|
|
||||||
|
|
||||||
packagesExist(libavcodec) {
|
|
||||||
PKGCONFIG += libavcodec libavutil
|
|
||||||
CONFIG += ffmpeg
|
|
||||||
|
|
||||||
packagesExist(libva) {
|
|
||||||
CONFIG += libva
|
|
||||||
}
|
|
||||||
|
|
||||||
packagesExist(vdpau) {
|
|
||||||
CONFIG += libvdpau
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
win32 {
|
win32 {
|
||||||
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavutil -lopus
|
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavdevice -lavformat -lavutil
|
||||||
CONFIG += ffmpeg
|
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2
|
LIBS += -lssl -lcrypto -lSDL2 -lavcodec.58 -lavdevice.58 -lavformat.58 -lavutil.56
|
||||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
|
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework AppKit
|
||||||
CONFIG += ffmpeg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@@ -80,9 +55,17 @@ SOURCES += \
|
|||||||
streaming/input.cpp \
|
streaming/input.cpp \
|
||||||
streaming/session.cpp \
|
streaming/session.cpp \
|
||||||
streaming/audio.cpp \
|
streaming/audio.cpp \
|
||||||
|
streaming/video/ffmpeg.cpp \
|
||||||
gui/computermodel.cpp \
|
gui/computermodel.cpp \
|
||||||
gui/appmodel.cpp \
|
gui/appmodel.cpp \
|
||||||
streaming/streamutils.cpp
|
streaming/video/ffmpeg-renderers/sdl.cpp
|
||||||
|
|
||||||
|
win32 {
|
||||||
|
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
||||||
|
}
|
||||||
|
macx {
|
||||||
|
SOURCES += streaming/video/avfoundation.mm
|
||||||
|
}
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
@@ -97,53 +80,14 @@ HEADERS += \
|
|||||||
gui/computermodel.h \
|
gui/computermodel.h \
|
||||||
gui/appmodel.h \
|
gui/appmodel.h \
|
||||||
streaming/video/decoder.h \
|
streaming/video/decoder.h \
|
||||||
streaming/streamutils.h
|
|
||||||
|
|
||||||
# Platform-specific renderers and decoders
|
|
||||||
ffmpeg {
|
|
||||||
message(FFmpeg decoder selected)
|
|
||||||
|
|
||||||
DEFINES += HAVE_FFMPEG
|
|
||||||
SOURCES += \
|
|
||||||
streaming/video/ffmpeg.cpp \
|
|
||||||
streaming/video/ffmpeg-renderers/sdl.cpp
|
|
||||||
HEADERS += \
|
|
||||||
streaming/video/ffmpeg.h \
|
streaming/video/ffmpeg.h \
|
||||||
streaming/video/ffmpeg-renderers/renderer.h
|
streaming/video/ffmpeg-renderers/renderer.h
|
||||||
}
|
|
||||||
libva {
|
|
||||||
message(VAAPI renderer selected)
|
|
||||||
|
|
||||||
DEFINES += HAVE_LIBVA
|
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/vaapi.cpp
|
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/vaapi.h
|
|
||||||
}
|
|
||||||
libvdpau {
|
|
||||||
message(VDPAU renderer selected)
|
|
||||||
|
|
||||||
DEFINES += HAVE_LIBVDPAU
|
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/vdpau.cpp
|
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/vdpau.h
|
|
||||||
}
|
|
||||||
config_SLVideo {
|
|
||||||
message(SLVideo decoder/renderer selected)
|
|
||||||
|
|
||||||
DEFINES += HAVE_SLVIDEO
|
|
||||||
LIBS += -lSLVideo
|
|
||||||
SOURCES += streaming/video/sl.cpp
|
|
||||||
HEADERS += streaming/video/sl.h
|
|
||||||
}
|
|
||||||
win32 {
|
win32 {
|
||||||
message(DXVA2 renderer selected)
|
|
||||||
|
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/dxva2.h
|
HEADERS += streaming/video/ffmpeg-renderers/dxva2.h
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
message(VideoToolbox renderer selected)
|
HEADERS += streaming/video/avfoundation.h
|
||||||
|
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/vt.mm
|
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/vt.h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
@@ -163,6 +107,13 @@ else:unix: LIBS += -L$$OUT_PWD/../moonlight-common-c/ -lmoonlight-common-c
|
|||||||
INCLUDEPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
INCLUDEPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
||||||
DEPENDPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
DEPENDPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
||||||
|
|
||||||
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../opus/release/ -lopus
|
||||||
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../opus/debug/ -lopus
|
||||||
|
else:unix: LIBS += -L$$OUT_PWD/../opus/ -lopus
|
||||||
|
|
||||||
|
INCLUDEPATH += $$PWD/../opus/opus/include
|
||||||
|
DEPENDPATH += $$PWD/../opus/opus/include
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/release/ -lqmdnsengine
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/release/ -lqmdnsengine
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/debug/ -lqmdnsengine
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/debug/ -lqmdnsengine
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
||||||
@@ -170,36 +121,14 @@ else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
|||||||
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
|
|
||||||
unix:!macx: {
|
# Default rules for deployment.
|
||||||
isEmpty(PREFIX) {
|
qnx: target.path = /tmp/$${TARGET}/bin
|
||||||
PREFIX = /usr/local
|
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||||
}
|
!isEmpty(target.path): INSTALLS += target
|
||||||
isEmpty(BINDIR) {
|
|
||||||
BINDIR = bin
|
|
||||||
}
|
|
||||||
|
|
||||||
target.path = $$PREFIX/$$BINDIR/
|
|
||||||
|
|
||||||
desktop.files = deploy/linux/com.moonlight_stream.Moonlight.desktop
|
|
||||||
desktop.path = $$PREFIX/share/applications/
|
|
||||||
|
|
||||||
icons.files = res/moonlight.svg
|
|
||||||
icons.path = $$PREFIX/share/icons/hicolor/scalable/apps/
|
|
||||||
|
|
||||||
appdata.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
|
||||||
appdata.path = $$PREFIX/share/metainfo/
|
|
||||||
|
|
||||||
INSTALLS += target desktop icons appdata
|
|
||||||
}
|
|
||||||
win32 {
|
|
||||||
RC_ICONS = moonlight.ico
|
|
||||||
}
|
|
||||||
macx {
|
macx {
|
||||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||||
|
APP_QML_FILES.files = res/macos.icns
|
||||||
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
APP_QML_FILES.path = Contents/Resources
|
||||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
QMAKE_BUNDLE_DATA += APP_QML_FILES
|
||||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VERSION = 0.1.0
|
|
||||||
@@ -51,8 +51,6 @@ NvComputer::NvComputer(QSettings& settings)
|
|||||||
this->appVersion = nullptr;
|
this->appVersion = nullptr;
|
||||||
this->maxLumaPixelsHEVC = 0;
|
this->maxLumaPixelsHEVC = 0;
|
||||||
this->serverCodecModeSupport = 0;
|
this->serverCodecModeSupport = 0;
|
||||||
this->pendingQuit = false;
|
|
||||||
this->gpuModel = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -135,21 +133,19 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
|||||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||||
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||||
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||||
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
|
|
||||||
this->activeAddress = address;
|
this->activeAddress = address;
|
||||||
this->state = NvComputer::CS_ONLINE;
|
this->state = NvComputer::CS_ONLINE;
|
||||||
this->pendingQuit = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NvComputer::wake()
|
bool NvComputer::wake()
|
||||||
{
|
{
|
||||||
if (state == NvComputer::CS_ONLINE) {
|
if (state == NvComputer::CS_ONLINE) {
|
||||||
qWarning() << name << "is already online";
|
qWarning() << name << " is already online";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (macAddress.isNull()) {
|
if (macAddress.isNull()) {
|
||||||
qWarning() << name << "has no MAC address stored";
|
qWarning() << name << " has no MAC address stored";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +184,7 @@ bool NvComputer::wake()
|
|||||||
// Send to all ports
|
// Send to all ports
|
||||||
for (quint16 port : WOL_PORTS) {
|
for (quint16 port : WOL_PORTS) {
|
||||||
if (sock.writeDatagram(wolPayload, address, port)) {
|
if (sock.writeDatagram(wolPayload, address, port)) {
|
||||||
qInfo().nospace().noquote() << "Send WoL packet to " << name << " via " << address.toString() << ":" << port;
|
qDebug() << "Send WoL packet to " << name << " via " << address.toString() << ":" << port;
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +264,6 @@ bool NvComputer::update(NvComputer& that)
|
|||||||
ASSIGN_IF_CHANGED(gfeVersion);
|
ASSIGN_IF_CHANGED(gfeVersion);
|
||||||
ASSIGN_IF_CHANGED(appVersion);
|
ASSIGN_IF_CHANGED(appVersion);
|
||||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||||
ASSIGN_IF_CHANGED(gpuModel);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
||||||
return changed;
|
return changed;
|
||||||
@@ -291,56 +286,6 @@ ComputerManager::ComputerManager(QObject *parent)
|
|||||||
settings.endArray();
|
settings.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
ComputerManager::~ComputerManager()
|
|
||||||
{
|
|
||||||
QWriteLocker lock(&m_Lock);
|
|
||||||
|
|
||||||
// Delete machines that haven't been resolved yet
|
|
||||||
while (!m_PendingResolution.isEmpty()) {
|
|
||||||
MdnsPendingComputer* computer = m_PendingResolution.first();
|
|
||||||
delete computer;
|
|
||||||
m_PendingResolution.removeFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the browser to stop discovery
|
|
||||||
delete m_MdnsBrowser;
|
|
||||||
m_MdnsBrowser = nullptr;
|
|
||||||
|
|
||||||
{
|
|
||||||
QMutableMapIterator<QString, QThread*> i(m_PollThreads);
|
|
||||||
|
|
||||||
// Interrupt all polling threads
|
|
||||||
while (i.hasNext()) {
|
|
||||||
i.next();
|
|
||||||
i.value()->requestInterruption();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all threads to terminate before destroying
|
|
||||||
// the NvComputer objects.
|
|
||||||
i.toFront();
|
|
||||||
while (i.hasNext()) {
|
|
||||||
i.next();
|
|
||||||
|
|
||||||
QThread* thread = i.value();
|
|
||||||
thread->wait();
|
|
||||||
delete thread;
|
|
||||||
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QMutableMapIterator<QString, NvComputer*> i(m_KnownHosts);
|
|
||||||
|
|
||||||
// Destroy all NvComputer objects
|
|
||||||
while (i.hasNext()) {
|
|
||||||
i.next();
|
|
||||||
delete i.value();
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ComputerManager::saveHosts()
|
void ComputerManager::saveHosts()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@@ -367,7 +312,7 @@ void ComputerManager::startPolling()
|
|||||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||||
this, [this](const QMdnsEngine::Service& service) {
|
this, [this](const QMdnsEngine::Service& service) {
|
||||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
qDebug() << "Discovered mDNS host: " << service.hostname();
|
||||||
|
|
||||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
||||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||||
@@ -386,7 +331,7 @@ void ComputerManager::startPolling()
|
|||||||
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||||
const QHostAddress& address)
|
const QHostAddress& address)
|
||||||
{
|
{
|
||||||
qInfo() << "Resolved" << computer->hostname() << "to" << address.toString();
|
qDebug() << "Resolved " << computer->hostname() << " to " << address.toString();
|
||||||
|
|
||||||
addNewHost(address.toString(), true);
|
addNewHost(address.toString(), true);
|
||||||
|
|
||||||
@@ -451,15 +396,6 @@ void ComputerManager::pairHost(NvComputer* computer, QString pin)
|
|||||||
QThreadPool::globalInstance()->start(pairing);
|
QThreadPool::globalInstance()->start(pairing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputerManager::quitRunningApp(NvComputer* computer)
|
|
||||||
{
|
|
||||||
QWriteLocker lock(&computer->lock);
|
|
||||||
computer->pendingQuit = true;
|
|
||||||
|
|
||||||
PendingQuitTask* quit = new PendingQuitTask(this, computer);
|
|
||||||
QThreadPool::globalInstance()->start(quit);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ComputerManager::stopPollingAsync()
|
void ComputerManager::stopPollingAsync()
|
||||||
{
|
{
|
||||||
QWriteLocker lock(&m_Lock);
|
QWriteLocker lock(&m_Lock);
|
||||||
@@ -511,11 +447,6 @@ ComputerManager::handleComputerStateChanged(NvComputer* computer)
|
|||||||
{
|
{
|
||||||
emit computerStateChanged(computer);
|
emit computerStateChanged(computer);
|
||||||
|
|
||||||
if (computer->pendingQuit && computer->currentGameId == 0) {
|
|
||||||
computer->pendingQuit = false;
|
|
||||||
emit quitAppCompleted(QVariant());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save updated hosts to QSettings
|
// Save updated hosts to QSettings
|
||||||
saveHosts();
|
saveHosts();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,10 @@
|
|||||||
class NvComputer
|
class NvComputer
|
||||||
{
|
{
|
||||||
friend class PcMonitorThread;
|
friend class PcMonitorThread;
|
||||||
friend class ComputerManager;
|
|
||||||
friend class PendingQuitTask;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sortAppList();
|
void sortAppList();
|
||||||
|
|
||||||
bool pendingQuit;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NvComputer(QString address, QString serverInfo);
|
explicit NvComputer(QString address, QString serverInfo);
|
||||||
|
|
||||||
@@ -65,7 +61,6 @@ public:
|
|||||||
QVector<NvDisplayMode> displayModes;
|
QVector<NvDisplayMode> displayModes;
|
||||||
int maxLumaPixelsHEVC;
|
int maxLumaPixelsHEVC;
|
||||||
int serverCodecModeSupport;
|
int serverCodecModeSupport;
|
||||||
QString gpuModel;
|
|
||||||
|
|
||||||
// Persisted traits
|
// Persisted traits
|
||||||
QString localAddress;
|
QString localAddress;
|
||||||
@@ -153,27 +148,32 @@ private:
|
|||||||
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
||||||
while (!isInterruptionRequested()) {
|
while (!isInterruptionRequested()) {
|
||||||
bool stateChanged = false;
|
bool stateChanged = false;
|
||||||
bool online = false;
|
for (int i = 0; i < TRIES_BEFORE_OFFLINING; i++) {
|
||||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
|
||||||
for (auto& address : m_Computer->uniqueAddresses()) {
|
for (auto& address : m_Computer->uniqueAddresses()) {
|
||||||
if (isInterruptionRequested()) {
|
if (isInterruptionRequested()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tryPollComputer(address, stateChanged)) {
|
if (tryPollComputer(address, stateChanged)) {
|
||||||
online = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No need to continue retrying if we're online
|
||||||
|
if (m_Computer->state == NvComputer::CS_ONLINE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we failed after all retry attempts
|
// Check if we failed after all retry attempts
|
||||||
// Note: we don't need to acquire the read lock here,
|
// Note: we don't need to acquire the read lock here,
|
||||||
// because we're on the writing thread.
|
// because we're on the writing thread.
|
||||||
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
if (m_Computer->state != NvComputer::CS_ONLINE) {
|
||||||
|
if (m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
||||||
pollsSinceLastAppListFetch++;
|
pollsSinceLastAppListFetch++;
|
||||||
@@ -249,8 +249,6 @@ class ComputerManager : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit ComputerManager(QObject *parent = nullptr);
|
explicit ComputerManager(QObject *parent = nullptr);
|
||||||
|
|
||||||
virtual ~ComputerManager();
|
|
||||||
|
|
||||||
Q_INVOKABLE void startPolling();
|
Q_INVOKABLE void startPolling();
|
||||||
|
|
||||||
Q_INVOKABLE void stopPollingAsync();
|
Q_INVOKABLE void stopPollingAsync();
|
||||||
@@ -259,8 +257,6 @@ public:
|
|||||||
|
|
||||||
void pairHost(NvComputer* computer, QString pin);
|
void pairHost(NvComputer* computer, QString pin);
|
||||||
|
|
||||||
void quitRunningApp(NvComputer* computer);
|
|
||||||
|
|
||||||
QVector<NvComputer*> getComputers();
|
QVector<NvComputer*> getComputers();
|
||||||
|
|
||||||
// computer is deleted inside this call
|
// computer is deleted inside this call
|
||||||
@@ -273,8 +269,6 @@ signals:
|
|||||||
|
|
||||||
void computerAddCompleted(QVariant success);
|
void computerAddCompleted(QVariant success);
|
||||||
|
|
||||||
void quitAppCompleted(QVariant error);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleComputerStateChanged(NvComputer* computer);
|
void handleComputerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
@@ -342,49 +336,6 @@ private:
|
|||||||
QString m_Pin;
|
QString m_Pin;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PendingQuitTask : public QObject, public QRunnable
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PendingQuitTask(ComputerManager* computerManager, NvComputer* computer)
|
|
||||||
: m_Computer(computer)
|
|
||||||
{
|
|
||||||
connect(this, &PendingQuitTask::quitAppFailed,
|
|
||||||
computerManager, &ComputerManager::quitAppCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void quitAppFailed(QString error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
NvHTTP http(m_Computer->activeAddress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (m_Computer->currentGameId != 0) {
|
|
||||||
http.quitApp();
|
|
||||||
}
|
|
||||||
} catch (const GfeHttpResponseException& e) {
|
|
||||||
{
|
|
||||||
QWriteLocker lock(&m_Computer->lock);
|
|
||||||
m_Computer->pendingQuit = false;
|
|
||||||
}
|
|
||||||
if (e.getStatusCode() == 599) {
|
|
||||||
// 599 is a special code we make a custom message for
|
|
||||||
emit quitAppFailed("The running game wasn't started by this PC. "
|
|
||||||
"You must quit the game on the host PC manually or use the device that originally started the game.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
emit quitAppFailed(e.toQString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer* m_Computer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PendingAddTask : public QObject, public QRunnable
|
class PendingAddTask : public QObject, public QRunnable
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QRandomGenerator64>
|
||||||
|
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/bn.h>
|
#include <openssl/bn.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
#define SER_UNIQUEID "uniqueid"
|
#define SER_UNIQUEID "uniqueid"
|
||||||
#define SER_CERT "certificate"
|
#define SER_CERT "certificate"
|
||||||
@@ -101,7 +101,7 @@ void IdentityManager::createCredentials(QSettings& settings)
|
|||||||
settings.setValue(SER_CERT, m_CachedPemCert);
|
settings.setValue(SER_CERT, m_CachedPemCert);
|
||||||
settings.setValue(SER_KEY, m_CachedPrivateKey);
|
settings.setValue(SER_KEY, m_CachedPrivateKey);
|
||||||
|
|
||||||
qInfo() << "Wrote new identity credentials to settings";
|
qDebug() << "Wrote new identity credentials to settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityManager::IdentityManager()
|
IdentityManager::IdentityManager()
|
||||||
@@ -112,7 +112,7 @@ IdentityManager::IdentityManager()
|
|||||||
m_CachedPrivateKey = settings.value(SER_KEY).toByteArray();
|
m_CachedPrivateKey = settings.value(SER_KEY).toByteArray();
|
||||||
|
|
||||||
if (m_CachedPemCert.isEmpty() || m_CachedPrivateKey.isEmpty()) {
|
if (m_CachedPemCert.isEmpty() || m_CachedPrivateKey.isEmpty()) {
|
||||||
qInfo() << "No existing credentials found";
|
qDebug() << "No existing credentials found";
|
||||||
createCredentials(settings);
|
createCredentials(settings);
|
||||||
}
|
}
|
||||||
else if (getSslCertificate().isNull()) {
|
else if (getSslCertificate().isNull()) {
|
||||||
@@ -157,14 +157,10 @@ IdentityManager::getSslKey()
|
|||||||
|
|
||||||
// We must write out our PEM in the old PKCS1 format for SecureTransport
|
// We must write out our PEM in the old PKCS1 format for SecureTransport
|
||||||
// on macOS/iOS to be able to read it.
|
// on macOS/iOS to be able to read it.
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
PEM_write_bio_PrivateKey_traditional(bio, pk, nullptr, nullptr, 0, nullptr, 0);
|
|
||||||
#else
|
|
||||||
PEM_write_bio_PrivateKey(bio, pk, nullptr, nullptr, 0, nullptr, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BUF_MEM* mem;
|
BUF_MEM* mem;
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
PEM_write_bio_PrivateKey_traditional(bio, pk, nullptr, nullptr, 0, nullptr, 0);
|
||||||
|
|
||||||
m_CachedSslKey = QSslKey(QByteArray::fromRawData(mem->data, (int)mem->length), QSsl::Rsa);
|
m_CachedSslKey = QSslKey(QByteArray::fromRawData(mem->data, (int)mem->length), QSsl::Rsa);
|
||||||
|
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
@@ -191,15 +187,14 @@ IdentityManager::getUniqueId()
|
|||||||
// Load the unique ID from settings
|
// Load the unique ID from settings
|
||||||
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
||||||
if (!m_CachedUniqueId.isEmpty() && !m_CachedUniqueId.isNull()) {
|
if (!m_CachedUniqueId.isEmpty() && !m_CachedUniqueId.isNull()) {
|
||||||
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
qDebug() << "Loaded unique ID from settings: " << m_CachedUniqueId;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Generate a new unique ID in base 16
|
// Generate a new unique ID in base 16
|
||||||
uint64_t uid;
|
m_CachedUniqueId = QString::number(
|
||||||
RAND_bytes(reinterpret_cast<unsigned char*>(&uid), sizeof(uid));
|
QRandomGenerator64::securelySeeded().generate64(), 16);
|
||||||
m_CachedUniqueId = QString::number(uid, 16);
|
|
||||||
|
|
||||||
qInfo() << "Generated new unique ID:" << m_CachedUniqueId;
|
qDebug() << "Generated new unique ID: " << m_CachedUniqueId;
|
||||||
|
|
||||||
settings.setValue(SER_UNIQUEID, m_CachedUniqueId);
|
settings.setValue(SER_UNIQUEID, m_CachedUniqueId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ NvHTTP::getServerInfo()
|
|||||||
serverInfo = openConnectionToString(m_BaseUrlHttps,
|
serverInfo = openConnectionToString(m_BaseUrlHttps,
|
||||||
"serverinfo",
|
"serverinfo",
|
||||||
nullptr,
|
nullptr,
|
||||||
true,
|
|
||||||
true);
|
true);
|
||||||
// Throws if the request failed
|
// Throws if the request failed
|
||||||
verifyResponseStatus(serverInfo);
|
verifyResponseStatus(serverInfo);
|
||||||
@@ -80,7 +79,6 @@ NvHTTP::getServerInfo()
|
|||||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||||
"serverinfo",
|
"serverinfo",
|
||||||
nullptr,
|
nullptr,
|
||||||
true,
|
|
||||||
true);
|
true);
|
||||||
verifyResponseStatus(serverInfo);
|
verifyResponseStatus(serverInfo);
|
||||||
}
|
}
|
||||||
@@ -125,11 +123,6 @@ NvHTTP::launchApp(int appId,
|
|||||||
bool localAudio,
|
bool localAudio,
|
||||||
int gamepadMask)
|
int gamepadMask)
|
||||||
{
|
{
|
||||||
int riKeyId;
|
|
||||||
|
|
||||||
memcpy(&riKeyId, streamConfig->remoteInputAesIv, sizeof(riKeyId));
|
|
||||||
riKeyId = qFromBigEndian(riKeyId);
|
|
||||||
|
|
||||||
QString response =
|
QString response =
|
||||||
openConnectionToString(m_BaseUrlHttps,
|
openConnectionToString(m_BaseUrlHttps,
|
||||||
"launch",
|
"launch",
|
||||||
@@ -139,7 +132,7 @@ NvHTTP::launchApp(int appId,
|
|||||||
QString::number(streamConfig->fps)+
|
QString::number(streamConfig->fps)+
|
||||||
"&additionalStates=1&sops="+QString::number(sops ? 1 : 0)+
|
"&additionalStates=1&sops="+QString::number(sops ? 1 : 0)+
|
||||||
"&rikey="+QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex()+
|
"&rikey="+QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex()+
|
||||||
"&rikeyid="+QString::number(riKeyId)+
|
"&rikeyid="+QString::number(qFromBigEndian(*(int*)streamConfig->remoteInputAesIv))+
|
||||||
(streamConfig->enableHdr ?
|
(streamConfig->enableHdr ?
|
||||||
"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0" :
|
"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0" :
|
||||||
"")+
|
"")+
|
||||||
@@ -156,16 +149,11 @@ NvHTTP::launchApp(int appId,
|
|||||||
void
|
void
|
||||||
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
||||||
{
|
{
|
||||||
int riKeyId;
|
|
||||||
|
|
||||||
memcpy(&riKeyId, streamConfig->remoteInputAesIv, sizeof(riKeyId));
|
|
||||||
riKeyId = qFromBigEndian(riKeyId);
|
|
||||||
|
|
||||||
QString response =
|
QString response =
|
||||||
openConnectionToString(m_BaseUrlHttps,
|
openConnectionToString(m_BaseUrlHttps,
|
||||||
"resume",
|
"resume",
|
||||||
"rikey="+QString(QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex())+
|
"rikey="+QString(QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex())+
|
||||||
"&rikeyid="+QString::number(riKeyId)+
|
"&rikeyid="+QString::number(qFromBigEndian(*(int*)streamConfig->remoteInputAesIv))+
|
||||||
"&surroundAudioInfo="+getSurroundAudioInfoString(streamConfig->audioConfiguration),
|
"&surroundAudioInfo="+getSurroundAudioInfoString(streamConfig->audioConfiguration),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
@@ -227,7 +215,6 @@ NvHTTP::getAppList()
|
|||||||
QString appxml = openConnectionToString(m_BaseUrlHttps,
|
QString appxml = openConnectionToString(m_BaseUrlHttps,
|
||||||
"applist",
|
"applist",
|
||||||
nullptr,
|
nullptr,
|
||||||
true,
|
|
||||||
true);
|
true);
|
||||||
verifyResponseStatus(appxml);
|
verifyResponseStatus(appxml);
|
||||||
|
|
||||||
@@ -278,10 +265,7 @@ NvHTTP::verifyResponseStatus(QString xml)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
QString statusMessage = xmlReader.attributes().value("status_message").toString();
|
QString statusMessage = xmlReader.attributes().value("status_message").toString();
|
||||||
if (statusCode != 401) {
|
qDebug() << "Request failed: " << statusCode << " " << statusMessage;
|
||||||
// 401 is expected for unpaired PCs when we fetch serverinfo over HTTPS
|
|
||||||
qWarning() << "Request failed:" << statusCode << statusMessage;
|
|
||||||
}
|
|
||||||
throw GfeHttpResponseException(statusCode, statusMessage);
|
throw GfeHttpResponseException(statusCode, statusMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,15 +325,12 @@ QString
|
|||||||
NvHTTP::openConnectionToString(QUrl baseUrl,
|
NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
QString arguments,
|
QString arguments,
|
||||||
bool enableTimeout,
|
bool enableTimeout)
|
||||||
bool suppressLogging)
|
|
||||||
{
|
{
|
||||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout, suppressLogging);
|
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout);
|
||||||
QString ret;
|
QString ret;
|
||||||
|
|
||||||
QTextStream stream(reply);
|
ret = QTextStream(reply).readAll();
|
||||||
stream.setCodec("UTF-8");
|
|
||||||
ret = stream.readAll();
|
|
||||||
delete reply;
|
delete reply;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -359,8 +340,7 @@ QNetworkReply*
|
|||||||
NvHTTP::openConnection(QUrl baseUrl,
|
NvHTTP::openConnection(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
QString arguments,
|
QString arguments,
|
||||||
bool enableTimeout,
|
bool enableTimeout)
|
||||||
bool suppressLogging)
|
|
||||||
{
|
{
|
||||||
// Build a URL for the request
|
// Build a URL for the request
|
||||||
QUrl url(baseUrl);
|
QUrl url(baseUrl);
|
||||||
@@ -386,17 +366,13 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
{
|
{
|
||||||
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
||||||
}
|
}
|
||||||
if (!suppressLogging) {
|
qDebug() << "Executing request: " << url.toString();
|
||||||
qInfo() << "Executing request:" << url.toString();
|
|
||||||
}
|
|
||||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
// Abort the request if it timed out
|
// Abort the request if it timed out
|
||||||
if (!reply->isFinished())
|
if (!reply->isFinished())
|
||||||
{
|
{
|
||||||
if (!suppressLogging) {
|
qDebug() << "Aborting timed out request for " << url.toString();
|
||||||
qWarning() << "Aborting timed out request for" << url.toString();
|
|
||||||
}
|
|
||||||
reply->abort();
|
reply->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,9 +383,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
// Handle error
|
// Handle error
|
||||||
if (reply->error() != QNetworkReply::NoError)
|
if (reply->error() != QNetworkReply::NoError)
|
||||||
{
|
{
|
||||||
if (!suppressLogging) {
|
qDebug() << command << " request failed with error " << reply->error();
|
||||||
qWarning() << command << " request failed with error " << reply->error();
|
|
||||||
}
|
|
||||||
GfeHttpResponseException exception(reply->error(), reply->errorString());
|
GfeHttpResponseException exception(reply->error(), reply->errorString());
|
||||||
delete reply;
|
delete reply;
|
||||||
throw exception;
|
throw exception;
|
||||||
|
|||||||
@@ -107,8 +107,7 @@ public:
|
|||||||
openConnectionToString(QUrl baseUrl,
|
openConnectionToString(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
QString arguments,
|
QString arguments,
|
||||||
bool enableTimeout,
|
bool enableTimeout);
|
||||||
bool suppressLogging = false);
|
|
||||||
|
|
||||||
static
|
static
|
||||||
QVector<int>
|
QVector<int>
|
||||||
@@ -144,8 +143,7 @@ private:
|
|||||||
openConnection(QUrl baseUrl,
|
openConnection(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
QString arguments,
|
QString arguments,
|
||||||
bool enableTimeout,
|
bool enableTimeout);
|
||||||
bool suppressLogging = false);
|
|
||||||
|
|
||||||
QString m_Address;
|
QString m_Address;
|
||||||
QNetworkAccessManager m_Nam;
|
QNetworkAccessManager m_Nam;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "nvpairingmanager.h"
|
#include "nvpairingmanager.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/aes.h>
|
#include <openssl/aes.h>
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
@@ -19,7 +21,7 @@ NvPairingManager::NvPairingManager(QString address) :
|
|||||||
BIO_free_all(bio);
|
BIO_free_all(bio);
|
||||||
if (m_Cert == nullptr)
|
if (m_Cert == nullptr)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Unable to load certificate");
|
throw new std::runtime_error("Unable to load certificate");
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray pk = IdentityManager::get()->getPrivateKey();
|
QByteArray pk = IdentityManager::get()->getPrivateKey();
|
||||||
@@ -30,7 +32,7 @@ NvPairingManager::NvPairingManager(QString address) :
|
|||||||
BIO_free_all(bio);
|
BIO_free_all(bio);
|
||||||
if (m_PrivateKey == nullptr)
|
if (m_PrivateKey == nullptr)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Unable to load private key");
|
throw new std::runtime_error("Unable to load private key");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,16 +89,14 @@ NvPairingManager::getSignatureFromPemCert(QByteArray certificate)
|
|||||||
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||||
BIO_free_all(bio);
|
BIO_free_all(bio);
|
||||||
|
|
||||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||||
ASN1_BIT_STRING *asnSignature = cert->signature;
|
|
||||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
|
||||||
ASN1_BIT_STRING *asnSignature;
|
ASN1_BIT_STRING *asnSignature;
|
||||||
X509_get0_signature(&asnSignature, NULL, cert);
|
|
||||||
#else
|
#else
|
||||||
const ASN1_BIT_STRING *asnSignature;
|
const ASN1_BIT_STRING *asnSignature;
|
||||||
X509_get0_signature(&asnSignature, NULL, cert);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
X509_get0_signature(&asnSignature, NULL, cert);
|
||||||
|
|
||||||
QByteArray signature(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
QByteArray signature(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
||||||
|
|
||||||
X509_free(cert);
|
X509_free(cert);
|
||||||
@@ -164,7 +164,7 @@ NvPairingManager::PairState
|
|||||||
NvPairingManager::pair(QString appVersion, QString pin)
|
NvPairingManager::pair(QString appVersion, QString pin)
|
||||||
{
|
{
|
||||||
int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
|
int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
|
||||||
qInfo() << "Pairing with server generation:" << serverMajorVersion;
|
qDebug() << "Pairing with server generation: " << serverMajorVersion;
|
||||||
|
|
||||||
QCryptographicHash::Algorithm hashAlgo;
|
QCryptographicHash::Algorithm hashAlgo;
|
||||||
int hashLength;
|
int hashLength;
|
||||||
@@ -196,14 +196,14 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
NvHTTP::verifyResponseStatus(getCert);
|
NvHTTP::verifyResponseStatus(getCert);
|
||||||
if (NvHTTP::getXmlString(getCert, "paired") != "1")
|
if (NvHTTP::getXmlString(getCert, "paired") != "1")
|
||||||
{
|
{
|
||||||
qCritical() << "Failed pairing at stage #1";
|
qDebug() << "Failed pairing at stage #1";
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray serverCert = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
QByteArray serverCert = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
||||||
if (serverCert == nullptr)
|
if (serverCert == nullptr)
|
||||||
{
|
{
|
||||||
qCritical() << "Server likely already pairing";
|
qDebug() << "Server likely already pairing";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::ALREADY_IN_PROGRESS;
|
return PairState::ALREADY_IN_PROGRESS;
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
NvHTTP::verifyResponseStatus(challengeXml);
|
NvHTTP::verifyResponseStatus(challengeXml);
|
||||||
if (NvHTTP::getXmlString(challengeXml, "paired") != "1")
|
if (NvHTTP::getXmlString(challengeXml, "paired") != "1")
|
||||||
{
|
{
|
||||||
qCritical() << "Failed pairing at stage #2";
|
qDebug() << "Failed pairing at stage #2";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
@@ -228,15 +228,13 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
QByteArray challengeResponse;
|
QByteArray challengeResponse;
|
||||||
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
||||||
|
|
||||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||||
ASN1_BIT_STRING *asnSignature = m_Cert->signature;
|
|
||||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
|
||||||
ASN1_BIT_STRING *asnSignature;
|
ASN1_BIT_STRING *asnSignature;
|
||||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
#else
|
||||||
#else
|
|
||||||
const ASN1_BIT_STRING *asnSignature;
|
const ASN1_BIT_STRING *asnSignature;
|
||||||
|
#endif
|
||||||
|
|
||||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
||||||
#endif
|
|
||||||
|
|
||||||
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
|
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
|
||||||
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
||||||
@@ -251,7 +249,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
NvHTTP::verifyResponseStatus(respXml);
|
NvHTTP::verifyResponseStatus(respXml);
|
||||||
if (NvHTTP::getXmlString(respXml, "paired") != "1")
|
if (NvHTTP::getXmlString(respXml, "paired") != "1")
|
||||||
{
|
{
|
||||||
qCritical() << "Failed pairing at stage #3";
|
qDebug() << "Failed pairing at stage #3";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
@@ -264,7 +262,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
serverSignature,
|
serverSignature,
|
||||||
serverCert))
|
serverCert))
|
||||||
{
|
{
|
||||||
qCritical() << "MITM detected";
|
qDebug() << "MITM detected";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
@@ -275,7 +273,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
expectedResponseData.append(serverSecret);
|
expectedResponseData.append(serverSecret);
|
||||||
if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse)
|
if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse)
|
||||||
{
|
{
|
||||||
qCritical() << "Incorrect PIN";
|
qDebug() << "Incorrect PIN";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::PIN_WRONG;
|
return PairState::PIN_WRONG;
|
||||||
}
|
}
|
||||||
@@ -292,7 +290,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
NvHTTP::verifyResponseStatus(secretRespXml);
|
NvHTTP::verifyResponseStatus(secretRespXml);
|
||||||
if (NvHTTP::getXmlString(secretRespXml, "paired") != "1")
|
if (NvHTTP::getXmlString(secretRespXml, "paired") != "1")
|
||||||
{
|
{
|
||||||
qCritical() << "Failed pairing at stage #4";
|
qDebug() << "Failed pairing at stage #4";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
@@ -304,7 +302,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
|||||||
NvHTTP::verifyResponseStatus(pairChallengeXml);
|
NvHTTP::verifyResponseStatus(pairChallengeXml);
|
||||||
if (NvHTTP::getXmlString(pairChallengeXml, "paired") != "1")
|
if (NvHTTP::getXmlString(pairChallengeXml, "paired") != "1")
|
||||||
{
|
{
|
||||||
qCritical() << "Failed pairing at stage #5";
|
qDebug() << "Failed pairing at stage #5";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||||
return PairState::FAILED;
|
return PairState::FAILED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<component type="desktop">
|
|
||||||
<id>com.moonlight_stream.Moonlight</id>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
|
||||||
<project_license>GPL-3.0+</project_license>
|
|
||||||
<name>Moonlight</name>
|
|
||||||
<summary>Stream games from your NVIDIA GameStream-enabled PC</summary>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
|
||||||
<p>Features include:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Streaming at up to 4K resolution</li>
|
|
||||||
<li>Support for up to 120 FPS streaming on high refresh rate monitors</li>
|
|
||||||
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
|
||||||
<li>5.1 surround sound audio support</li>
|
|
||||||
<li>HEVC support for better video compression efficiency</li>
|
|
||||||
<li>Gamepad support with SDL mapping compatibility</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<url type="homepage">https://moonlight-stream.com</url>
|
|
||||||
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
|
||||||
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
|
||||||
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_1.png</image>
|
|
||||||
</screenshot>
|
|
||||||
<screenshot>
|
|
||||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_2.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
|
|
||||||
<releases>
|
|
||||||
<release version="0.1.0" date="2018-08-05">
|
|
||||||
<description>
|
|
||||||
<p>New features:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Allow streaming at the client's native resolution</li>
|
|
||||||
<li>VDPAU hardware decoding support for NVIDIA GPUs</li>
|
|
||||||
<li>Updated UI to replace status text with icons</li>
|
|
||||||
<li>Stream window is now resizable</li>
|
|
||||||
<li>Display warnings during game launch for configuration issues</li>
|
|
||||||
</ul>
|
|
||||||
<p>Bugfixes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Gamepad input now works even if the app doesn't have focus</li>
|
|
||||||
<li>Fixed streaming black screen if the host supported HEVC but the client didn't</li>
|
|
||||||
<li>Fixed broken video scaling causing extra stretching and off-center video</li>
|
|
||||||
<li>Fixed gamepad input issue causing some Y axis inputs to be reversed</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="0.0.5" date="2018-08-01">
|
|
||||||
<description>
|
|
||||||
<p>Changes from 0.0.4:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed a bug causing increased frame drops and input lag</li>
|
|
||||||
<li>Added support for quitting running games</li>
|
|
||||||
<li>Added support for waking sleeping PCs with Wake-on-LAN</li>
|
|
||||||
<li>Increased the FPS limit to 120 FPS</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="0.0.4a" date="2018-07-30">
|
|
||||||
<description>
|
|
||||||
<p>Initial pre-alpha release for Linux. Please file bugs!</p>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
</releases>
|
|
||||||
|
|
||||||
<categories>
|
|
||||||
<category>Game</category>
|
|
||||||
<category>Qt</category>
|
|
||||||
</categories>
|
|
||||||
|
|
||||||
<developer_name>Moonlight Game Streaming Team</developer_name>
|
|
||||||
<update_contact>cameron@moonlight-stream.com</update_contact>
|
|
||||||
|
|
||||||
<launchable type="desktop-id">com.moonlight_stream.Moonlight.desktop</launchable>
|
|
||||||
|
|
||||||
<provides>
|
|
||||||
<binary>moonlight</binary>
|
|
||||||
</provides>
|
|
||||||
|
|
||||||
<content_rating type="oars-1.0" />
|
|
||||||
</component>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=Moonlight
|
|
||||||
Comment=Stream games from your NVIDIA GameStream-enabled PC
|
|
||||||
Exec=moonlight
|
|
||||||
Icon=moonlight
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Qt;Game;
|
|
||||||
Keywords=nvidia;gamestream;stream;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Dialogs 1.3
|
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
import AppModel 1.0
|
import AppModel 1.0
|
||||||
@@ -13,7 +12,7 @@ GridView {
|
|||||||
id: appGrid
|
id: appGrid
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 5
|
||||||
anchors.rightMargin: 5
|
anchors.rightMargin: 5
|
||||||
anchors.bottomMargin: 5
|
anchors.bottomMargin: 5
|
||||||
cellWidth: 225; cellHeight: 350;
|
cellWidth: 225; cellHeight: 350;
|
||||||
@@ -51,6 +50,12 @@ GridView {
|
|||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: 200; height: 300;
|
width: 200; height: 300;
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (model.running) {
|
||||||
|
appNameText.text = "<font color=\"green\">Running</font><font color=\"white\"> - </font>" + appNameText.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: appIcon
|
id: appIcon
|
||||||
anchors.horizontalCenter: parent.horizontalCenter;
|
anchors.horizontalCenter: parent.horizontalCenter;
|
||||||
@@ -61,110 +66,29 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
|
||||||
id: runningIcon
|
|
||||||
anchors.centerIn: appIcon
|
|
||||||
visible: model.running
|
|
||||||
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
|
|
||||||
sourceSize {
|
|
||||||
width: 75
|
|
||||||
height: 75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: appNameText
|
id: appNameText
|
||||||
text: model.name
|
text: model.name
|
||||||
color: "white"
|
color: "white"
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 125
|
height: 100
|
||||||
anchors.top: appIcon.bottom
|
anchors.top: appIcon.bottom
|
||||||
font.pointSize: 22
|
font.pointSize: 26
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
function launchOrResumeSelectedApp()
|
|
||||||
{
|
|
||||||
var runningIndex = appModel.getRunningAppIndex()
|
|
||||||
if (runningIndex >= 0 && runningIndex !== index) {
|
|
||||||
quitAppDialog.appName = appModel.getRunningAppName()
|
|
||||||
quitAppDialog.segueToStream = true
|
|
||||||
quitAppDialog.open()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var component = Qt.createComponent("StreamSegue.qml")
|
|
||||||
var segue = component.createObject(stackView)
|
|
||||||
segue.appName = model.name
|
|
||||||
segue.session = appModel.createSessionForApp(index)
|
|
||||||
stackView.push(segue)
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: quitAppDialog
|
|
||||||
modality:Qt.WindowModal
|
|
||||||
property string appName : "";
|
|
||||||
property bool segueToStream : false
|
|
||||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
|
||||||
onYes: {
|
|
||||||
var component = Qt.createComponent("QuitSegue.qml")
|
|
||||||
var segue = component.createObject(stackView)
|
|
||||||
segue.appName = appName
|
|
||||||
if (segueToStream) {
|
|
||||||
// Store the session and app name if we're going to stream after
|
|
||||||
// successfully quitting the old app.
|
|
||||||
segue.nextAppName = model.name
|
|
||||||
segue.nextSession = appModel.createSessionForApp(index)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
segue.nextAppName = null
|
|
||||||
segue.nextSession = null
|
|
||||||
}
|
|
||||||
|
|
||||||
stackView.push(segue)
|
|
||||||
|
|
||||||
// Trigger the quit after pushing the quit segue on screen
|
|
||||||
appModel.quitRunningApp()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (mouse.button === Qt.LeftButton) {
|
// TODO: Check if a different game is running
|
||||||
// Nothing is running or this app is running
|
|
||||||
launchOrResumeSelectedApp()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Right click
|
|
||||||
appContextMenu.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
var component = Qt.createComponent("StreamSegue.qml")
|
||||||
id: appContextMenu
|
var segue = component.createObject(stackView)
|
||||||
MenuItem {
|
segue.appname = model.name
|
||||||
text: model.running ? "Resume Game" : "Launch Game"
|
segue.session = appModel.createSessionForApp(index)
|
||||||
onTriggered: {
|
stackView.push(segue)
|
||||||
appContextMenu.close()
|
|
||||||
launchOrResumeSelectedApp()
|
|
||||||
}
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Quit Game"
|
|
||||||
onTriggered: {
|
|
||||||
quitAppDialog.appName = appModel.getRunningAppName()
|
|
||||||
quitAppDialog.segueToStream = false
|
|
||||||
quitAppDialog.open()
|
|
||||||
}
|
|
||||||
visible: model.running
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import QtQuick.Layouts 1.3
|
|||||||
import ComputerModel 1.0
|
import ComputerModel 1.0
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import StreamingPreferences 1.0
|
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
property ComputerModel computerModel : createModel()
|
property ComputerModel computerModel : createModel()
|
||||||
@@ -14,7 +13,7 @@ GridView {
|
|||||||
id: pcGrid
|
id: pcGrid
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 5
|
||||||
anchors.rightMargin: 5
|
anchors.rightMargin: 5
|
||||||
anchors.bottomMargin: 5
|
anchors.bottomMargin: 5
|
||||||
cellWidth: 350; cellHeight: 350;
|
cellWidth: 350; cellHeight: 350;
|
||||||
@@ -26,17 +25,9 @@ GridView {
|
|||||||
// routine to run, but only if we start as invisible
|
// routine to run, but only if we start as invisible
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
StreamingPreferences {
|
|
||||||
id: prefs
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Setup signals on CM
|
// Setup signals on CM
|
||||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||||
|
|
||||||
if (!prefs.hasAnyHardwareAcceleration()) {
|
|
||||||
noHwDecoderDialog.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
@@ -81,14 +72,14 @@ GridView {
|
|||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
model: computerModel
|
model: createModel()
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: 300; height: 300;
|
width: 300; height: 300;
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: pcIcon
|
id: pcIcon
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter;
|
||||||
source: {
|
source: {
|
||||||
model.addPc ? "qrc:/res/ic_add_to_queue_white_48px.svg" : "qrc:/res/ic_tv_white_48px.svg"
|
model.addPc ? "qrc:/res/ic_add_to_queue_white_48px.svg" : "qrc:/res/ic_tv_white_48px.svg"
|
||||||
}
|
}
|
||||||
@@ -98,30 +89,6 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
|
||||||
// TODO: Tooltip
|
|
||||||
id: stateIcon
|
|
||||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
|
||||||
anchors.verticalCenter: pcIcon.verticalCenter
|
|
||||||
anchors.verticalCenterOffset: -10
|
|
||||||
visible: !model.addPc && !model.statusUnknown && (!model.online || !model.paired)
|
|
||||||
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
|
||||||
sourceSize {
|
|
||||||
width: 75
|
|
||||||
height: 75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
id: statusUnknownSpinner
|
|
||||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
|
||||||
anchors.verticalCenter: pcIcon.verticalCenter
|
|
||||||
anchors.verticalCenterOffset: -10
|
|
||||||
width: 75
|
|
||||||
height: 75
|
|
||||||
visible: !model.addPc && model.statusUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: pcNameText
|
id: pcNameText
|
||||||
text: model.name
|
text: model.name
|
||||||
@@ -134,29 +101,41 @@ GridView {
|
|||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
Text {
|
||||||
id: pcContextMenu
|
function getStatusText(model)
|
||||||
MenuItem {
|
{
|
||||||
text: "Wake PC"
|
if (model.online) {
|
||||||
onTriggered: computerModel.wakeComputer(index)
|
var text = "<font color=\"green\">Online</font>"
|
||||||
visible: !model.addPc && !model.online && model.wakeable
|
text += "<font color=\"white\"> - </font>"
|
||||||
height: visible ? implicitHeight : 0
|
if (model.paired) {
|
||||||
|
text += "<font color=\"skyblue\">Paired</font>"
|
||||||
}
|
}
|
||||||
MenuItem {
|
else if (model.busy) {
|
||||||
text: "Delete PC"
|
text += "<font color=\"red\">Busy</font>"
|
||||||
onTriggered: {
|
}
|
||||||
deletePcDialog.pcIndex = index
|
else {
|
||||||
// get confirmation first, actual closing is called from the dialog
|
text += "<font color=\"red\">Not Paired</font>"
|
||||||
deletePcDialog.open()
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "<font color=\"red\">Offline</font>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id: pcPairedText
|
||||||
|
text: getStatusText(model)
|
||||||
|
visible: !model.addPc
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: pcNameText.bottom
|
||||||
|
font.pointSize: 24
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton;
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if(mouse.button === Qt.LeftButton) {
|
|
||||||
if (model.addPc) {
|
if (model.addPc) {
|
||||||
addPcDialog.open()
|
addPcDialog.open()
|
||||||
}
|
}
|
||||||
@@ -189,15 +168,9 @@ GridView {
|
|||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(!model.online) {
|
|
||||||
pcContextMenu.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else { // right click
|
|
||||||
if(!model.addPc) { // but only for actual PCs, not the add-pc option
|
|
||||||
pcContextMenu.open()
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Wake on LAN and delete PC options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,19 +184,6 @@ GridView {
|
|||||||
standardButtons: StandardButton.Ok
|
standardButtons: StandardButton.Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: noHwDecoderDialog
|
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Warning
|
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
|
||||||
text: "No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
|
|
||||||
"Your streaming performance may be severely degraded in this configuration. " +
|
|
||||||
"Click the Help button for more information on solving this problem."
|
|
||||||
onHelp: {
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
id: pairDialog
|
id: pairDialog
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
@@ -236,19 +196,6 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: deletePcDialog
|
|
||||||
// don't allow edits to the rest of the window while open
|
|
||||||
modality:Qt.WindowModal
|
|
||||||
property int pcIndex : -1;
|
|
||||||
text:"Are you sure you want to remove this PC?"
|
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
|
||||||
onYes: {
|
|
||||||
console.log("deleting PC pairing for PC at index: " + pcIndex)
|
|
||||||
computerModel.deleteComputer(pcIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
id: addPcDialog
|
id: addPcDialog
|
||||||
property string label: "Enter the IP address of your GameStream PC"
|
property string label: "Enter the IP address of your GameStream PC"
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import QtQuick 2.0
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Dialogs 1.3
|
|
||||||
|
|
||||||
import ComputerManager 1.0
|
|
||||||
import Session 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
property string appName
|
|
||||||
property Session nextSession : null
|
|
||||||
property string nextAppName : ""
|
|
||||||
|
|
||||||
property string stageText : "Quitting " + appName + "..."
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
function quitAppCompleted(error)
|
|
||||||
{
|
|
||||||
// Display a failed dialog if we got an error
|
|
||||||
if (error !== undefined) {
|
|
||||||
errorDialog.text = error
|
|
||||||
errorDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit this view
|
|
||||||
stackView.pop()
|
|
||||||
|
|
||||||
// If we're supposed to launch another game after this, do so now
|
|
||||||
if (error === undefined && nextSession !== null) {
|
|
||||||
var component = Qt.createComponent("StreamSegue.qml")
|
|
||||||
var segue = component.createObject(stackView)
|
|
||||||
segue.appName = nextAppName
|
|
||||||
segue.session = nextSession
|
|
||||||
stackView.push(segue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
// Connect the quit completion signal
|
|
||||||
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
|
|
||||||
|
|
||||||
// We must be polling or we won't get the quitAppCompleted() callback
|
|
||||||
ComputerManager.startPolling()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Disconnect the signal
|
|
||||||
ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
|
|
||||||
|
|
||||||
// Stop polling when this view is not on top
|
|
||||||
ComputerManager.stopPollingAsync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
id: stageSpinner
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: stageLabel
|
|
||||||
height: stageSpinner.height
|
|
||||||
text: stageText
|
|
||||||
font.pointSize: 20
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: errorDialog
|
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Critical
|
|
||||||
standardButtons: StandardButton.Ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,13 +16,14 @@ ScrollView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
padding: 10
|
x: 10
|
||||||
id: settingsColumn1
|
y: 10
|
||||||
width: settingsPage.width / 2 - padding
|
width: settingsPage.width
|
||||||
|
height: 400
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
id: basicSettingsGroupBox
|
id: basicSettingsGroupBox
|
||||||
width: (parent.width - 2 * parent.padding)
|
width: (parent.width - 20)
|
||||||
padding: 12
|
padding: 12
|
||||||
title: "<font color=\"skyblue\">Basic Settings</font>"
|
title: "<font color=\"skyblue\">Basic Settings</font>"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
@@ -34,7 +35,7 @@ ScrollView {
|
|||||||
Label {
|
Label {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
id: resFPStitle
|
id: resFPStitle
|
||||||
text: qsTr("Resolution and FPS")
|
text: qsTr("Resolution and FPS target")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
color: "white"
|
||||||
@@ -43,156 +44,87 @@ ScrollView {
|
|||||||
Label {
|
Label {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
id: resFPSdesc
|
id: resFPSdesc
|
||||||
text: qsTr("Setting values too high for your PC may cause lag, stuttering, or errors.")
|
text: qsTr("Setting values too high for your device may cause lag or crashes")
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Add native resolutions for all attached displays
|
// load the saved width/height/fps, and iterate through the ComboBox until a match is found
|
||||||
for (var displayIndex = 0;; displayIndex++) {
|
// set it to that index.
|
||||||
var screenRect = prefs.getDisplayResolution(displayIndex)
|
|
||||||
if (screenRect.width === 0) {
|
|
||||||
// Exceeded max count of displays
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexToAdd = 0
|
|
||||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
|
||||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
|
||||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
|
||||||
|
|
||||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
|
||||||
// Duplicate entry, skip
|
|
||||||
indexToAdd = -1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
|
||||||
// Candidate entrypoint after this entry
|
|
||||||
indexToAdd = j + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert this display's resolution if it's not a duplicate
|
|
||||||
if (indexToAdd >= 0) {
|
|
||||||
resolutionListModel.insert(indexToAdd,
|
|
||||||
{
|
|
||||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
|
||||||
"video_width": ""+screenRect.width,
|
|
||||||
"video_height": ""+screenRect.height
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the saved width/height, and iterate through the ComboBox until a match is found
|
|
||||||
// and set it to that index.
|
|
||||||
var saved_width = prefs.width
|
var saved_width = prefs.width
|
||||||
var saved_height = prefs.height
|
var saved_height = prefs.height
|
||||||
|
var saved_fps = prefs.fps
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < resolutionComboBox.count; i++) {
|
for(var i = 0; i < resolutionComboBox.count; i++) {
|
||||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||||
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
||||||
|
var el_fps = parseInt(resolutionListModel.get(i).video_fps);
|
||||||
// Pick the highest value lesser or equal to the saved resolution
|
if(saved_width === el_width &&
|
||||||
if (saved_width * saved_height >= el_width * el_height) {
|
saved_height === el_height &&
|
||||||
|
saved_fps === el_fps) {
|
||||||
currentIndex = i
|
currentIndex = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id: resolutionComboBox
|
id: resolutionComboBox
|
||||||
width: 200
|
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||||
|
font.pointSize: 9
|
||||||
textRole: "text"
|
textRole: "text"
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
id: resolutionListModel
|
id: resolutionListModel
|
||||||
// Other elements may be added at runtime
|
|
||||||
// based on attached display resolution
|
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "720p"
|
text: "720p 30 FPS"
|
||||||
video_width: "1280"
|
video_width: "1280"
|
||||||
video_height: "720"
|
video_height: "720"
|
||||||
|
video_fps: "30"
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "1080p"
|
text: "720p 60 FPS"
|
||||||
|
video_width: "1280"
|
||||||
|
video_height: "720"
|
||||||
|
video_fps: "60"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
text: "1080p 30 FPS"
|
||||||
video_width: "1920"
|
video_width: "1920"
|
||||||
video_height: "1080"
|
video_height: "1080"
|
||||||
|
video_fps: "30"
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "1440p"
|
text: "1080p 60 FPS"
|
||||||
video_width: "2560"
|
video_width: "1920"
|
||||||
video_height: "1440"
|
video_height: "1080"
|
||||||
|
video_fps: "60"
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "4K"
|
text: "4K 30 FPS"
|
||||||
video_width: "3840"
|
video_width: "3840"
|
||||||
video_height: "2160"
|
video_height: "2160"
|
||||||
|
video_fps: "30"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
text: "4K 60 FPS"
|
||||||
|
video_width: "3840"
|
||||||
|
video_height: "2160"
|
||||||
|
video_fps: "60"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||||
prefs.height = parseInt(resolutionListModel.get(currentIndex).video_height)
|
prefs.height = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||||
|
prefs.fps = parseInt(resolutionListModel.get(currentIndex).video_fps)
|
||||||
|
|
||||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||||
slider.value = prefs.bitrateKbps
|
slider.value = prefs.bitrateKbps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
|
||||||
Component.onCompleted: {
|
|
||||||
// Get the max supported FPS on this system
|
|
||||||
var max_fps = prefs.getMaximumStreamingFrameRate();
|
|
||||||
|
|
||||||
// Use 64 as the cutoff for adding a separate option to
|
|
||||||
// handle wonky displays that report just over 60 Hz.
|
|
||||||
if (max_fps > 64) {
|
|
||||||
fpsListModel.append({"text": max_fps+" FPS", "video_fps": ""+max_fps})
|
|
||||||
}
|
|
||||||
|
|
||||||
var saved_fps = prefs.fps
|
|
||||||
currentIndex = 0
|
|
||||||
for (var i = 0; i < fpsComboBox.count; i++) {
|
|
||||||
var el_fps = parseInt(fpsListModel.get(i).video_fps);
|
|
||||||
|
|
||||||
// Pick the highest value lesser or equal to the saved FPS
|
|
||||||
if (saved_fps >= el_fps) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id: fpsComboBox
|
|
||||||
textRole: "text"
|
|
||||||
model: ListModel {
|
|
||||||
id: fpsListModel
|
|
||||||
ListElement {
|
|
||||||
text: "30 FPS"
|
|
||||||
video_fps: "30"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "60 FPS"
|
|
||||||
video_fps: "60"
|
|
||||||
}
|
|
||||||
// A higher value may be added at runtime
|
|
||||||
// based on the attached display refresh rate
|
|
||||||
}
|
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
|
||||||
onActivated : {
|
|
||||||
prefs.fps = parseInt(fpsListModel.get(currentIndex).video_fps)
|
|
||||||
|
|
||||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
|
||||||
slider.value = prefs.bitrateKbps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
id: bitrateTitle
|
id: bitrateTitle
|
||||||
@@ -219,7 +151,7 @@ ScrollView {
|
|||||||
|
|
||||||
stepSize: 500
|
stepSize: 500
|
||||||
from : 500
|
from : 500
|
||||||
to: 150000
|
to: 100000
|
||||||
|
|
||||||
snapMode: "SnapOnRelease"
|
snapMode: "SnapOnRelease"
|
||||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||||
@@ -245,7 +177,7 @@ ScrollView {
|
|||||||
GroupBox {
|
GroupBox {
|
||||||
|
|
||||||
id: audioSettingsGroupBox
|
id: audioSettingsGroupBox
|
||||||
width: (parent.width - 2 * parent.padding)
|
width: (parent.width - 20)
|
||||||
padding: 12
|
padding: 12
|
||||||
title: "<font color=\"skyblue\">Audio Settings</font>"
|
title: "<font color=\"skyblue\">Audio Settings</font>"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
@@ -254,65 +186,24 @@ ScrollView {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
Label {
|
CheckBox {
|
||||||
width: parent.width
|
id: surroundSoundCheck
|
||||||
id: resAudioTitle
|
text: "<font color=\"white\">Enable 5.1 surround sound</font>"
|
||||||
text: qsTr("Audio configuration")
|
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
// the backend actually supports auto/stereo/5.1. We'll expose stereo/5.1
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
checked: prefs.audioConfig === StreamingPreferences.AC_FORCE_SURROUND
|
||||||
Component.onCompleted: {
|
|
||||||
var saved_audio = prefs.audioConfig
|
|
||||||
currentIndex = 0
|
|
||||||
for(var i = 0; i < audioListModel.count; i++) {
|
|
||||||
var el_audio= audioListModel.get(i).val;
|
|
||||||
if(saved_audio === el_audio) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id: audioComboBox
|
|
||||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
|
||||||
textRole: "text"
|
|
||||||
model: ListModel {
|
|
||||||
id: audioListModel
|
|
||||||
ListElement {
|
|
||||||
text: "Autodetect"
|
|
||||||
val: StreamingPreferences.AC_AUTO
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Stereo"
|
|
||||||
val: StreamingPreferences.AC_FORCE_STEREO
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "5.1 surround sound"
|
|
||||||
val: StreamingPreferences.AC_FORCE_SURROUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
|
||||||
onActivated : {
|
|
||||||
prefs.audioConfig = audioListModel.get(currentIndex).val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
prefs.audioConfig = checked ? StreamingPreferences.AC_FORCE_SURROUND : StreamingPreferences.AC_FORCE_STEREO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
|
||||||
padding: 10
|
|
||||||
anchors.left: settingsColumn1.right
|
|
||||||
id: settingsColumn2
|
|
||||||
width: settingsPage.width / 2 - padding
|
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
id: gamepadSettingsGroupBox
|
id: gamepadSettingsGroupBox
|
||||||
width: (parent.width - parent.padding)
|
width: (parent.width - 20)
|
||||||
padding: 12
|
padding: 12
|
||||||
title: "<font color=\"skyblue\">Gamepad Settings</font>"
|
title: "<font color=\"skyblue\">Gamepad Settings</font>"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
@@ -330,12 +221,38 @@ ScrollView {
|
|||||||
prefs.multiController = checked
|
prefs.multiController = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CheckBox {
|
||||||
|
id: mouseEmulationCheck
|
||||||
|
text: "<font color=\"white\">Mouse emulation via gamepad</font>"
|
||||||
|
font.pointSize: 12
|
||||||
|
// TODO: make this actually do anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
id: onScreenControlsGroupBox
|
||||||
|
width: (parent.width - 20)
|
||||||
|
padding: 12
|
||||||
|
title: "<font color=\"skyblue\">On-screen Controls Settings</font>"
|
||||||
|
font.pointSize: 12
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: onScreenControlsCheck
|
||||||
|
text: "<font color=\"white\">Show on-screen controls</font>"
|
||||||
|
font.pointSize: 12
|
||||||
|
// TODO: make this actually do anything
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
id: hostSettingsGroupBox
|
id: hostSettingsGroupBox
|
||||||
width: (parent.width - parent.padding)
|
width: (parent.width - 20)
|
||||||
padding: 12
|
padding: 12
|
||||||
title: "<font color=\"skyblue\">Host Settings</font>"
|
title: "<font color=\"skyblue\">Host Settings</font>"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
@@ -368,7 +285,7 @@ ScrollView {
|
|||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
id: advancedSettingsGroupBox
|
id: advancedSettingsGroupBox
|
||||||
width: (parent.width - parent.padding)
|
width: (parent.width - 20)
|
||||||
padding: 12
|
padding: 12
|
||||||
title: "<font color=\"skyblue\">Advanced Settings</font>"
|
title: "<font color=\"skyblue\">Advanced Settings</font>"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
@@ -377,105 +294,11 @@ ScrollView {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
Label {
|
CheckBox {
|
||||||
width: parent.width
|
id: neverDropFramesCheck
|
||||||
id: resVDSTitle
|
text: "<font color=\"white\">Never drop frames</font>"
|
||||||
text: qsTr("Video decoder")
|
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
|
||||||
Component.onCompleted: {
|
|
||||||
var saved_vds = prefs.videoDecoderSelection
|
|
||||||
currentIndex = 0
|
|
||||||
for(var i = 0; i < decoderListModel.count; i++) {
|
|
||||||
var el_vds = decoderListModel.get(i).val;
|
|
||||||
if(saved_vds === el_vds) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id: decoderComboBox
|
|
||||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
|
||||||
textRole: "text"
|
|
||||||
model: ListModel {
|
|
||||||
id: decoderListModel
|
|
||||||
ListElement {
|
|
||||||
text: "Automatic (Recommended)"
|
|
||||||
val: StreamingPreferences.VDS_AUTO
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Force software decoding"
|
|
||||||
val: StreamingPreferences.VDS_FORCE_SOFTWARE
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Force hardware decoding"
|
|
||||||
val: StreamingPreferences.VDS_FORCE_HARDWARE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
|
||||||
onActivated : {
|
|
||||||
prefs.videoDecoderSelection = decoderListModel.get(currentIndex).val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
width: parent.width
|
|
||||||
id: resVCCTitle
|
|
||||||
text: qsTr("Video codec")
|
|
||||||
font.pointSize: 12
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
|
||||||
Component.onCompleted: {
|
|
||||||
var saved_vcc = prefs.videoCodecConfig
|
|
||||||
currentIndex = 0
|
|
||||||
for(var i = 0; i < codecListModel.count; i++) {
|
|
||||||
var el_vcc = codecListModel.get(i).val;
|
|
||||||
if(saved_vcc === el_vcc) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id: codecComboBox
|
|
||||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
|
||||||
textRole: "text"
|
|
||||||
model: ListModel {
|
|
||||||
id: codecListModel
|
|
||||||
ListElement {
|
|
||||||
text: "Automatic (Recommended)"
|
|
||||||
val: StreamingPreferences.VCC_AUTO
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Force H.264"
|
|
||||||
val: StreamingPreferences.VCC_FORCE_H264
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Force HEVC"
|
|
||||||
val: StreamingPreferences.VCC_FORCE_HEVC
|
|
||||||
}
|
|
||||||
// HDR seems to be broken in GFE 3.14.1, and even when that's fixed
|
|
||||||
// we'll probably need to gate this feature on OS support in our
|
|
||||||
// renderers.
|
|
||||||
/* ListElement {
|
|
||||||
text: "Force HEVC HDR"
|
|
||||||
val: StreamingPreferences.VCC_FORCE_HEVC_HDR
|
|
||||||
} */
|
|
||||||
}
|
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
|
||||||
onActivated : {
|
|
||||||
prefs.videoCodecConfig = codecListModel.get(currentIndex).val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Session 1.0
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
property Session session
|
property Session session
|
||||||
property string appName
|
property string appname
|
||||||
property string stageText : "Starting " + appName + "..."
|
property string stageText : "Starting " + appname + "..."
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
@@ -47,13 +47,7 @@ Item {
|
|||||||
|
|
||||||
function displayLaunchWarning(text)
|
function displayLaunchWarning(text)
|
||||||
{
|
{
|
||||||
// This toast appears for 3 seconds, just shorter than how long
|
// TODO: toast
|
||||||
// Session will wait for it to be displayed. This gives it time
|
|
||||||
// to transition to invisible before continuing.
|
|
||||||
var toast = Qt.createQmlObject('import QtQuick.Controls 2.3; ToolTip {}', parent, '')
|
|
||||||
toast.text = text
|
|
||||||
toast.timeout = 3000
|
|
||||||
toast.visible = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
|
|||||||
@@ -19,32 +19,6 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
|
|||||||
m_CurrentGameId = m_Computer->currentGameId;
|
m_CurrentGameId = m_Computer->currentGameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AppModel::getRunningAppIndex()
|
|
||||||
{
|
|
||||||
if (m_CurrentGameId != 0) {
|
|
||||||
for (int i = 0; i < m_Apps.count(); i++) {
|
|
||||||
if (m_Apps[i].id == m_CurrentGameId) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AppModel::getRunningAppName()
|
|
||||||
{
|
|
||||||
if (m_CurrentGameId != 0) {
|
|
||||||
for (int i = 0; i < m_Apps.count(); i++) {
|
|
||||||
if (m_Apps[i].id == m_CurrentGameId) {
|
|
||||||
return m_Apps[i].name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Session* AppModel::createSessionForApp(int appIndex)
|
Session* AppModel::createSessionForApp(int appIndex)
|
||||||
{
|
{
|
||||||
Q_ASSERT(appIndex < m_Apps.count());
|
Q_ASSERT(appIndex < m_Apps.count());
|
||||||
@@ -96,11 +70,6 @@ QHash<int, QByteArray> AppModel::roleNames() const
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppModel::quitRunningApp()
|
|
||||||
{
|
|
||||||
m_ComputerManager->quitRunningApp(m_Computer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppModel::handleComputerStateChanged(NvComputer* computer)
|
void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||||
{
|
{
|
||||||
// Ignore updates for computers that aren't ours
|
// Ignore updates for computers that aren't ours
|
||||||
@@ -154,15 +123,10 @@ void AppModel::handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl /* image
|
|||||||
Q_ASSERT(computer == m_Computer);
|
Q_ASSERT(computer == m_Computer);
|
||||||
|
|
||||||
int index = m_Apps.indexOf(app);
|
int index = m_Apps.indexOf(app);
|
||||||
|
Q_ASSERT(index >= 0);
|
||||||
|
|
||||||
// Make sure we're not delivering a callback to an app that's already been removed
|
|
||||||
if (index >= 0) {
|
|
||||||
// Let our view know the box art data has changed for this app
|
// Let our view know the box art data has changed for this app
|
||||||
emit dataChanged(createIndex(index, 0),
|
emit dataChanged(createIndex(index, 0),
|
||||||
createIndex(index, 0),
|
createIndex(index, 0),
|
||||||
QVector<int>() << BoxArtRole);
|
QVector<int>() << BoxArtRole);
|
||||||
}
|
|
||||||
else {
|
|
||||||
qWarning() << "App not found for box art callback:" << app.name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -25,12 +25,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE Session* createSessionForApp(int appIndex);
|
Q_INVOKABLE Session* createSessionForApp(int appIndex);
|
||||||
|
|
||||||
Q_INVOKABLE int getRunningAppIndex();
|
|
||||||
|
|
||||||
Q_INVOKABLE QString getRunningAppName();
|
|
||||||
|
|
||||||
Q_INVOKABLE void quitRunningApp();
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent) const override;
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
|||||||
@@ -46,12 +46,8 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
|||||||
return computer->pairState == NvComputer::PS_PAIRED;
|
return computer->pairState == NvComputer::PS_PAIRED;
|
||||||
case BusyRole:
|
case BusyRole:
|
||||||
return computer->currentGameId != 0;
|
return computer->currentGameId != 0;
|
||||||
case WakeableRole:
|
|
||||||
return !computer->macAddress.isEmpty();
|
|
||||||
case AddPcRole:
|
case AddPcRole:
|
||||||
return false;
|
return false;
|
||||||
case StatusUnknownRole:
|
|
||||||
return computer->state == NvComputer::CS_UNKNOWN;
|
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@@ -78,8 +74,6 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
|
|||||||
names[PairedRole] = "paired";
|
names[PairedRole] = "paired";
|
||||||
names[BusyRole] = "busy";
|
names[BusyRole] = "busy";
|
||||||
names[AddPcRole] = "addPc";
|
names[AddPcRole] = "addPc";
|
||||||
names[WakeableRole] = "wakeable";
|
|
||||||
names[StatusUnknownRole] = "statusUnknown";
|
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ class ComputerModel : public QAbstractListModel
|
|||||||
OnlineRole,
|
OnlineRole,
|
||||||
PairedRole,
|
PairedRole,
|
||||||
BusyRole,
|
BusyRole,
|
||||||
WakeableRole,
|
|
||||||
StatusUnknownRole,
|
|
||||||
AddPcRole
|
AddPcRole
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
// TODO: Implement gamepad mapping then unhide this button
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QMutex>
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
// Don't let SDL hook our main function, since Qt is already
|
// Don't let SDL hook our main function, since Qt is already
|
||||||
// doing the same thing. This needs to be before any headers
|
// doing the same thing. This needs to be before any headers
|
||||||
@@ -16,134 +14,8 @@
|
|||||||
#include "streaming/session.hpp"
|
#include "streaming/session.hpp"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
#if !defined(QT_DEBUG) && defined(Q_OS_WIN32)
|
|
||||||
// Log to file for release Windows builds
|
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
#define LOG_TO_FILE
|
|
||||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
|
|
||||||
// Use stdout logger on all Linux/BSD builds
|
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
#elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN)
|
|
||||||
// Log to file for release Mac builds
|
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
#define LOG_TO_FILE
|
|
||||||
#else
|
|
||||||
// For debug Windows and Mac builds, use default logger
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
|
||||||
static QTime s_LoggerTime;
|
|
||||||
static QTextStream s_LoggerStream(stdout);
|
|
||||||
static QMutex s_LoggerLock;
|
|
||||||
#ifdef LOG_TO_FILE
|
|
||||||
#define MAX_LOG_LINES 10000
|
|
||||||
static int s_LogLinesWritten = 0;
|
|
||||||
static bool s_LogLimitReached = false;
|
|
||||||
static QFile* s_LoggerFile;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void logToLoggerStream(QString& message)
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&s_LoggerLock);
|
|
||||||
|
|
||||||
#ifdef LOG_TO_FILE
|
|
||||||
if (s_LogLimitReached) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (s_LogLinesWritten == MAX_LOG_LINES) {
|
|
||||||
s_LoggerStream << "Log size limit reached!" << endl;
|
|
||||||
s_LogLimitReached = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s_LogLinesWritten++;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
s_LoggerStream << message << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const char* message)
|
|
||||||
{
|
|
||||||
QString priorityTxt;
|
|
||||||
|
|
||||||
switch (priority) {
|
|
||||||
case SDL_LOG_PRIORITY_VERBOSE:
|
|
||||||
priorityTxt = "Verbose";
|
|
||||||
break;
|
|
||||||
case SDL_LOG_PRIORITY_DEBUG:
|
|
||||||
priorityTxt = "Debug";
|
|
||||||
break;
|
|
||||||
case SDL_LOG_PRIORITY_INFO:
|
|
||||||
priorityTxt = "Info";
|
|
||||||
break;
|
|
||||||
case SDL_LOG_PRIORITY_WARN:
|
|
||||||
priorityTxt = "Warn";
|
|
||||||
break;
|
|
||||||
case SDL_LOG_PRIORITY_ERROR:
|
|
||||||
priorityTxt = "Error";
|
|
||||||
break;
|
|
||||||
case SDL_LOG_PRIORITY_CRITICAL:
|
|
||||||
priorityTxt = "Critical";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
priorityTxt = "Unknown";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
|
||||||
QString txt = QString("%1 - SDL %2 (%3): %4").arg(logTime.toString()).arg(priorityTxt).arg(category).arg(message);
|
|
||||||
|
|
||||||
logToLoggerStream(txt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
|
|
||||||
{
|
|
||||||
QString typeTxt;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case QtDebugMsg:
|
|
||||||
typeTxt = "Debug";
|
|
||||||
break;
|
|
||||||
case QtInfoMsg:
|
|
||||||
typeTxt = "Info";
|
|
||||||
break;
|
|
||||||
case QtWarningMsg:
|
|
||||||
typeTxt = "Warning";
|
|
||||||
break;
|
|
||||||
case QtCriticalMsg:
|
|
||||||
typeTxt = "Critical";
|
|
||||||
break;
|
|
||||||
case QtFatalMsg:
|
|
||||||
typeTxt = "Fatal";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
|
||||||
QString txt = QString("%1 - Qt %2: %3").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
|
||||||
|
|
||||||
logToLoggerStream(txt);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
|
||||||
#ifdef LOG_TO_FILE
|
|
||||||
QDir tempDir(QDir::tempPath());
|
|
||||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
|
||||||
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
|
||||||
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
|
||||||
s_LoggerStream.setDevice(s_LoggerFile);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
s_LoggerTime.start();
|
|
||||||
qInstallMessageHandler(qtLogToDiskHandler);
|
|
||||||
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
|
||||||
// This avoids using the default keychain for SSL, which may cause
|
// This avoids using the default keychain for SSL, which may cause
|
||||||
@@ -181,8 +53,12 @@ int main(int argc, char *argv[])
|
|||||||
if (engine.rootObjects().isEmpty())
|
if (engine.rootObjects().isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
// Ensure that SDL is always initialized since we may need to use it
|
||||||
|
// for non-streaming purposes (like checking on audio devices)
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
if (SDL_Init(0) != 0) {
|
if (SDL_Init(SDL_INIT_VIDEO |
|
||||||
|
SDL_INIT_AUDIO |
|
||||||
|
SDL_INIT_GAMECONTROLLER) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_Init() failed: %s",
|
"SDL_Init() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -6,6 +6,5 @@
|
|||||||
<file>gui/SettingsView.qml</file>
|
<file>gui/SettingsView.qml</file>
|
||||||
<file>gui/StreamSegue.qml</file>
|
<file>gui/StreamSegue.qml</file>
|
||||||
<file>gui/GamepadMapper.qml</file>
|
<file>gui/GamepadMapper.qml</file>
|
||||||
<file>gui/QuitSegue.qml</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="24" height="24" viewBox="0 0 24 24">
|
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
|
||||||
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 390 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="#fff">
|
|
||||||
<path d="M0 0h48v48H0z" fill="none"/>
|
|
||||||
<path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm-4 29V15l12 9-12 9z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 250 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="24" height="24" viewBox="0 0 24 24">
|
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
|
||||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 215 B |
@@ -1,69 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.0">
|
||||||
<svg
|
<defs>
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<filter id="f1" y="-40%" width="120%" height="220%">
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
<feOffset xmlns="http://www.w3.org/2000/svg" result="offOut" in="SourceAlpha" dy="-100"/>
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="200"/>
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</filter>
|
||||||
id="svg3761"
|
</defs>
|
||||||
viewBox="0 0 680 680"
|
<!-- -105, 521 are within 0.5 pixel-perfect -->
|
||||||
height="680"
|
<!-- offsetting more to show shadow -->
|
||||||
width="680"
|
<g transform="translate(-95,530) scale(0.100000,-0.100000)" filter="url(#f1)">
|
||||||
version="1.0">
|
<circle cx="4755" cy="1505" r="3700" fill="#565C64">
|
||||||
<metadata
|
</circle>
|
||||||
id="metadata3767">
|
<path d="M4550 4230 c-582 -48 -1123 -271 -1568 -646 l-84 -70 869 -869 c477 -477 871 -868 876 -868 4 0 6 552 5 1228 l-3 1227 -25 1 c-14 0 -45 -1 -70 -3z" style="fill:#ffffff"/>
|
||||||
<rdf:RDF>
|
<path xmlns="http://www.w3.org/2000/svg" d="M4867 4230 c-10 -9 -9 -2453 1 -2453 4 0 398 391 875 868 l869 869 -84 70 c-344 290 -749 492 -1178 587 -166 36 -469 74 -483 59z" style="fill:#ffffff"/>
|
||||||
<cc:Work
|
<path d="M2673 3275 c-241 -285 -412 -595 -526 -953 -63 -196 -111 -458 -119 -640 l-3 -70 1228 -3 c675 -1 1227 1 1227 5 0 5 -391 399 -868 876 l-869 869 -70 -84z" style="fill:#ffffff"/>
|
||||||
rdf:about="">
|
<path d="M5898 2490 c-478 -477 -868 -871 -868 -876 0 -4 552 -6 1228 -5 l1227 3 -3 70 c-14 326 -124 729 -287 1055 -91 182 -259 430 -387 573 l-43 48 -867 -868z" style="fill:#ffffff"/>
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<path d="M2026 1388 c-7 -11 10 -198 30 -321 76 -486 291 -952 617 -1338 l70 -84 869 869 c477 477 868 871 868 875 0 11 -2448 10 -2454 -1z" style="fill:#ffffff"/>
|
||||||
<dc:type
|
<path d="M5030 1390 c0 -5 391 -399 868 -876 l869 -869 70 84 c241 285 412 595 526 953 63 196 111 458 119 640 l3 70 -1227 3 c-676 1 -1228 -1 -1228 -5z" style="fill:#ffffff"/>
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<path d="M3767 359 l-869 -869 84 -70 c285 -241 595 -412 953 -526 196 -63 458 -111 640 -119 l70 -3 3 1228 c1 675 -1 1227 -5 1227 -5 0 -399 -391 -876 -868z" style="fill:#ffffff"/>
|
||||||
<dc:title></dc:title>
|
<path d="M4862 0 l3 -1228 70 3 c182 8 444 56 640 119 358 114 668 285 953 526 l84 70 -869 869 c-477 477 -871 868 -876 868 -4 0 -6 -552 -5 -1227z" style="fill:#ffffff"/>
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<defs
|
|
||||||
id="defs3765" />
|
|
||||||
<g
|
|
||||||
id="g3759"
|
|
||||||
transform="translate(-135,491) scale(0.100000,-0.100000)">
|
|
||||||
<circle
|
|
||||||
id="circle3741"
|
|
||||||
fill="#565C64"
|
|
||||||
r="3400"
|
|
||||||
cy="1505"
|
|
||||||
cx="4755" />
|
|
||||||
<path
|
|
||||||
id="path3743"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M4550 4230 c-582 -48 -1123 -271 -1568 -646 l-84 -70 869 -869 c477 -477 871 -868 876 -868 4 0 6 552 5 1228 l-3 1227 -25 1 c-14 0 -45 -1 -70 -3z" />
|
|
||||||
<path
|
|
||||||
id="path3745"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M4867 4230 c-10 -9 -9 -2453 1 -2453 4 0 398 391 875 868 l869 869 -84 70 c-344 290 -749 492 -1178 587 -166 36 -469 74 -483 59z" />
|
|
||||||
<path
|
|
||||||
id="path3747"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M2673 3275 c-241 -285 -412 -595 -526 -953 -63 -196 -111 -458 -119 -640 l-3 -70 1228 -3 c675 -1 1227 1 1227 5 0 5 -391 399 -868 876 l-869 869 -70 -84z" />
|
|
||||||
<path
|
|
||||||
id="path3749"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M5898 2490 c-478 -477 -868 -871 -868 -876 0 -4 552 -6 1228 -5 l1227 3 -3 70 c-14 326 -124 729 -287 1055 -91 182 -259 430 -387 573 l-43 48 -867 -868z" />
|
|
||||||
<path
|
|
||||||
id="path3751"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M2026 1388 c-7 -11 10 -198 30 -321 76 -486 291 -952 617 -1338 l70 -84 869 869 c477 477 868 871 868 875 0 11 -2448 10 -2454 -1z" />
|
|
||||||
<path
|
|
||||||
id="path3753"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M5030 1390 c0 -5 391 -399 868 -876 l869 -869 70 84 c241 285 412 595 526 953 63 196 111 458 119 640 l3 70 -1227 3 c-676 1 -1228 -1 -1228 -5z" />
|
|
||||||
<path
|
|
||||||
id="path3755"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M3767 359 l-869 -869 84 -70 c285 -241 595 -412 953 -526 196 -63 458 -111 640 -119 l70 -3 3 1228 c1 675 -1 1227 -5 1227 -5 0 -399 -391 -876 -868z" />
|
|
||||||
<path
|
|
||||||
id="path3757"
|
|
||||||
style="fill:#ffffff"
|
|
||||||
d="M4862 0 l3 -1228 70 3 c182 8 444 56 640 119 358 114 668 285 953 526 l84 70 -869 869 c-477 477 -871 868 -876 868 -4 0 -6 -552 -5 -1227z" />
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -5,9 +5,6 @@
|
|||||||
<file>res/ic_videogame_asset_white_48px.svg</file>
|
<file>res/ic_videogame_asset_white_48px.svg</file>
|
||||||
<file>res/ic_tv_white_48px.svg</file>
|
<file>res/ic_tv_white_48px.svg</file>
|
||||||
<file>res/ic_add_to_queue_white_48px.svg</file>
|
<file>res/ic_add_to_queue_white_48px.svg</file>
|
||||||
<file>res/baseline-lock-24px.svg</file>
|
|
||||||
<file>res/baseline-play_circle_filled_white-48px.svg</file>
|
|
||||||
<file>res/baseline-warning-24px.svg</file>
|
|
||||||
<file>res/no_app_image.png</file>
|
<file>res/no_app_image.png</file>
|
||||||
<file>res/settings.svg</file>
|
<file>res/settings.svg</file>
|
||||||
<file>res/arrow_left.svg</file>
|
<file>res/arrow_left.svg</file>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "streamingpreferences.h"
|
#include "streamingpreferences.h"
|
||||||
#include "streaming/session.hpp"
|
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
@@ -58,88 +57,24 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StreamingPreferences::hasAnyHardwareAcceleration()
|
|
||||||
{
|
|
||||||
// Always use VDS_AUTO to avoid spamming the user with warnings
|
|
||||||
// if they've forced software decoding.
|
|
||||||
return Session::isHardwareDecodeAvailable(VDS_AUTO,
|
|
||||||
VIDEO_FORMAT_H264,
|
|
||||||
1920, 1080, 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamingPreferences::getMaximumStreamingFrameRate()
|
|
||||||
{
|
|
||||||
// Never let the maximum drop below 60 FPS
|
|
||||||
int maxFrameRate = 60;
|
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return maxFrameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0) {
|
|
||||||
// Cap the frame rate at 120 FPS. Past this, the encoders start
|
|
||||||
// to max out and drop frames.
|
|
||||||
maxFrameRate = qMax(maxFrameRate, qMin(120, mode.refresh_rate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
return maxFrameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect StreamingPreferences::getDisplayResolution(int displayIndex)
|
|
||||||
{
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
int err = SDL_GetCurrentDisplayMode(displayIndex, &mode);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
if (err == 0) {
|
|
||||||
return QRect(0, 0, mode.w, mode.h);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetCurrentDisplayMode() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||||
{
|
{
|
||||||
// This table prefers 16:10 resolutions because they are
|
if (width * height * fps <= 1280 * 720 * 30) {
|
||||||
// only slightly more pixels than the 16:9 equivalents, so
|
return 5000;
|
||||||
// we don't want to bump those 16:10 resolutions up to the
|
|
||||||
// next 16:9 slot.
|
|
||||||
|
|
||||||
// This covers 1280x720 and 1280x800 too
|
|
||||||
if (width * height <= 1366 * 768) {
|
|
||||||
return static_cast<int>(5000 * (fps / 30.0));
|
|
||||||
}
|
}
|
||||||
else if (width * height <= 1920 * 1200) {
|
else if (width * height * fps <= 1280 * 720 * 60) {
|
||||||
return static_cast<int>(10000 * (fps / 30.0));
|
return 10000;
|
||||||
}
|
}
|
||||||
else if (width * height <= 2560 * 1600) {
|
else if (width * height * fps <= 1920 * 1080 * 30) {
|
||||||
return static_cast<int>(20000 * (fps / 30.0));
|
return 10000;
|
||||||
}
|
}
|
||||||
else /* if (width * height <= 3840 * 2160) */ {
|
else if (width * height * fps <= 1920 * 1080 * 60) {
|
||||||
return static_cast<int>(40000 * (fps / 30.0));
|
return 20000;
|
||||||
|
}
|
||||||
|
else if (width * height * fps <= 3840 * 2160 * 30) {
|
||||||
|
return 40000;
|
||||||
|
}
|
||||||
|
else /* if (width * height * fps <= 3840 * 2160 * 60) */ {
|
||||||
|
return 80000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QRect>
|
|
||||||
|
|
||||||
class StreamingPreferences : public QObject
|
class StreamingPreferences : public QObject
|
||||||
{
|
{
|
||||||
@@ -15,12 +14,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void save();
|
Q_INVOKABLE void save();
|
||||||
|
|
||||||
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
|
||||||
|
|
||||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
|
||||||
|
|
||||||
Q_INVOKABLE QRect getDisplayResolution(int displayIndex);
|
|
||||||
|
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
enum AudioConfig
|
enum AudioConfig
|
||||||
|
|||||||
@@ -23,14 +23,6 @@ int Session::sdlDetermineAudioConfiguration()
|
|||||||
{
|
{
|
||||||
SDL_AudioSpec want, have;
|
SDL_AudioSpec want, have;
|
||||||
SDL_AudioDeviceID dev;
|
SDL_AudioDeviceID dev;
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_zero(want);
|
SDL_zero(want);
|
||||||
want.freq = 48000;
|
want.freq = 48000;
|
||||||
@@ -46,8 +38,7 @@ int Session::sdlDetermineAudioConfiguration()
|
|||||||
"Failed to open audio device");
|
"Failed to open audio device");
|
||||||
// We'll probably have issues during audio stream init, but we'll
|
// We'll probably have issues during audio stream init, but we'll
|
||||||
// try anyway
|
// try anyway
|
||||||
ret = AUDIO_CONFIGURATION_STEREO;
|
return AUDIO_CONFIGURATION_STEREO;
|
||||||
goto Exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_CloseAudioDevice(dev);
|
SDL_CloseAudioDevice(dev);
|
||||||
@@ -59,15 +50,11 @@ int Session::sdlDetermineAudioConfiguration()
|
|||||||
// We don't support quadraphonic or 7.1 surround, but SDL
|
// We don't support quadraphonic or 7.1 surround, but SDL
|
||||||
// should be able to downmix or upmix better from 5.1 than
|
// should be able to downmix or upmix better from 5.1 than
|
||||||
// from stereo, so use 5.1 in non-stereo cases.
|
// from stereo, so use 5.1 in non-stereo cases.
|
||||||
ret = AUDIO_CONFIGURATION_51_SURROUND;
|
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ret = AUDIO_CONFIGURATION_STEREO;
|
return AUDIO_CONFIGURATION_STEREO;
|
||||||
}
|
}
|
||||||
|
|
||||||
Exit:
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::sdlAudioInit(int /* audioConfiguration */,
|
int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||||
@@ -77,14 +64,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
|||||||
SDL_AudioSpec want, have;
|
SDL_AudioSpec want, have;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_zero(want);
|
SDL_zero(want);
|
||||||
want.freq = opusConfig->sampleRate;
|
want.freq = opusConfig->sampleRate;
|
||||||
want.format = AUDIO_S16;
|
want.format = AUDIO_S16;
|
||||||
@@ -101,7 +80,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to open audio device: %s",
|
"Failed to open audio device: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +95,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
|||||||
error);
|
error);
|
||||||
SDL_CloseAudioDevice(s_AudioDevice);
|
SDL_CloseAudioDevice(s_AudioDevice);
|
||||||
s_AudioDevice = 0;
|
s_AudioDevice = 0;
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +104,7 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
|||||||
// pending data count. Get a baseline so we
|
// pending data count. Get a baseline so we
|
||||||
// can exclude that data.
|
// can exclude that data.
|
||||||
s_BaselinePendingData = 0;
|
s_BaselinePendingData = 0;
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef _WIN32
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
s_BaselinePendingData = qMax(s_BaselinePendingData, SDL_GetQueuedAudioSize(s_AudioDevice));
|
s_BaselinePendingData = qMax(s_BaselinePendingData, SDL_GetQueuedAudioSize(s_AudioDevice));
|
||||||
SDL_Delay(10);
|
SDL_Delay(10);
|
||||||
@@ -142,10 +119,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
|||||||
s_SampleIndex = 0;
|
s_SampleIndex = 0;
|
||||||
s_PendingDrops = s_PendingHardDrops = 0;
|
s_PendingDrops = s_PendingHardDrops = 0;
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Audio stream has %d channels",
|
|
||||||
opusConfig->channelCount);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,10 +140,7 @@ void Session::sdlAudioCleanup()
|
|||||||
s_AudioDevice = 0;
|
s_AudioDevice = 0;
|
||||||
|
|
||||||
opus_multistream_decoder_destroy(s_OpusDecoder);
|
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||||
s_OpusDecoder = nullptr;
|
s_OpusDecoder = NULL;
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include "streaming/session.hpp"
|
#include "streaming/session.hpp"
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#define VK_0 0x30
|
#define VK_0 0x30
|
||||||
#define VK_A 0x41
|
#define VK_A 0x41
|
||||||
|
|
||||||
@@ -11,7 +9,7 @@
|
|||||||
#ifndef VK_F1
|
#ifndef VK_F1
|
||||||
#define VK_F1 0x70
|
#define VK_F1 0x70
|
||||||
#define VK_F13 0x7C
|
#define VK_F13 0x7C
|
||||||
#define VK_NUMPAD0 0x60
|
#define VK_NUMPAD1 0x61
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const int SdlInputHandler::k_ButtonMap[] = {
|
const int SdlInputHandler::k_ButtonMap[] = {
|
||||||
@@ -25,18 +23,6 @@ const int SdlInputHandler::k_ButtonMap[] = {
|
|||||||
SdlInputHandler::SdlInputHandler(bool multiController)
|
SdlInputHandler::SdlInputHandler(bool multiController)
|
||||||
: m_MultiController(multiController)
|
: m_MultiController(multiController)
|
||||||
{
|
{
|
||||||
// Allow gamepad input when the app doesn't have focus
|
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
|
||||||
|
|
||||||
// We need to reinit this each time, since you only get
|
|
||||||
// an initial set of gamepad arrival events once per init.
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_MultiController) {
|
if (!m_MultiController) {
|
||||||
// Player 1 is always present in non-MC mode
|
// Player 1 is always present in non-MC mode
|
||||||
m_GamepadMask = 0x1;
|
m_GamepadMask = 0x1;
|
||||||
@@ -52,9 +38,6 @@ SdlInputHandler::~SdlInputHandler()
|
|||||||
SDL_GameControllerClose(m_GamepadState[i].controller);
|
SDL_GameControllerClose(m_GamepadState[i].controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||||
@@ -68,6 +51,11 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
(event->keysym.mod & KMOD_ALT) &&
|
(event->keysym.mod & KMOD_ALT) &&
|
||||||
(event->keysym.mod & KMOD_SHIFT)) {
|
(event->keysym.mod & KMOD_SHIFT)) {
|
||||||
|
|
||||||
|
// Check for quit combo (Ctrl+Alt+Shift+Q)
|
||||||
|
if (event->keysym.sym == SDLK_q) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Detected quit key combo");
|
||||||
|
|
||||||
// Force raise all keys in the combo to avoid
|
// Force raise all keys in the combo to avoid
|
||||||
// leaving them down after disconnecting
|
// leaving them down after disconnecting
|
||||||
LiSendKeyboardEvent(0xA0, KEY_ACTION_UP, 0);
|
LiSendKeyboardEvent(0xA0, KEY_ACTION_UP, 0);
|
||||||
@@ -77,17 +65,12 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
LiSendKeyboardEvent(0xA4, KEY_ACTION_UP, 0);
|
LiSendKeyboardEvent(0xA4, KEY_ACTION_UP, 0);
|
||||||
LiSendKeyboardEvent(0xA5, KEY_ACTION_UP, 0);
|
LiSendKeyboardEvent(0xA5, KEY_ACTION_UP, 0);
|
||||||
|
|
||||||
// Check for quit combo (Ctrl+Alt+Shift+Q)
|
|
||||||
if (event->keysym.sym == SDLK_q) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Detected quit key combo");
|
|
||||||
|
|
||||||
// Uncapture the mouse to avoid processing any
|
|
||||||
// further keyboard input
|
|
||||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
|
||||||
|
// Drain the event queue of any additional input
|
||||||
|
// that might be processed before our quit message.
|
||||||
|
while (SDL_PollEvent(&event));
|
||||||
|
|
||||||
// Push a quit event to the main loop
|
// Push a quit event to the main loop
|
||||||
event.type = SDL_QUIT;
|
event.type = SDL_QUIT;
|
||||||
event.quit.timestamp = SDL_GetTicks();
|
event.quit.timestamp = SDL_GetTicks();
|
||||||
@@ -105,7 +88,12 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
else if (event->keysym.sym == SDLK_x) {
|
else if (event->keysym.sym == SDLK_x) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Detected full-screen toggle combo");
|
"Detected full-screen toggle combo");
|
||||||
Session::s_ActiveSession->toggleFullscreen();
|
if (SDL_GetWindowFlags(Session::s_ActiveSession->m_Window) & SDL_WINDOW_FULLSCREEN) {
|
||||||
|
SDL_SetWindowFullscreen(Session::s_ActiveSession->m_Window, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_SetWindowFullscreen(Session::s_ActiveSession->m_Window, SDL_WINDOW_FULLSCREEN);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,199 +115,195 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
modifiers |= MODIFIER_SHIFT;
|
modifiers |= MODIFIER_SHIFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set keycode. We explicitly use scancode here because GFE will try to correct
|
// Set keycode
|
||||||
// for AZERTY layouts on the host but it depends on receiving VK_ values matching
|
if (event->keysym.sym >= SDLK_0 && event->keysym.sym <= SDLK_9) {
|
||||||
// a QWERTY layout to work.
|
keyCode = (event->keysym.sym - SDLK_0) + VK_0;
|
||||||
if (event->keysym.scancode >= SDL_SCANCODE_1 && event->keysym.scancode <= SDL_SCANCODE_9) {
|
|
||||||
// SDL defines SDL_SCANCODE_0 > SDL_SCANCODE_9, so we need to handle that manually
|
|
||||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_1) + VK_0 + 1;
|
|
||||||
}
|
}
|
||||||
else if (event->keysym.scancode >= SDL_SCANCODE_A && event->keysym.scancode <= SDL_SCANCODE_Z) {
|
else if (event->keysym.sym >= SDLK_a && event->keysym.sym <= SDLK_z) {
|
||||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_A) + VK_A;
|
keyCode = (event->keysym.sym - SDLK_a) + VK_A;
|
||||||
}
|
}
|
||||||
else if (event->keysym.scancode >= SDL_SCANCODE_F1 && event->keysym.scancode <= SDL_SCANCODE_F12) {
|
else if (event->keysym.sym >= SDLK_F1 && event->keysym.sym <= SDLK_F12) {
|
||||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_F1) + VK_F1;
|
keyCode = (event->keysym.sym - SDLK_F1) + VK_F1;
|
||||||
}
|
}
|
||||||
else if (event->keysym.scancode >= SDL_SCANCODE_F13 && event->keysym.scancode <= SDL_SCANCODE_F24) {
|
else if (event->keysym.sym >= SDLK_F13 && event->keysym.sym <= SDLK_F24) {
|
||||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_F13) + VK_F13;
|
keyCode = (event->keysym.sym - SDLK_F13) + VK_F13;
|
||||||
}
|
}
|
||||||
else if (event->keysym.scancode >= SDL_SCANCODE_KP_1 && event->keysym.scancode <= SDL_SCANCODE_KP_9) {
|
else if (event->keysym.sym >= SDLK_KP_1 && event->keysym.sym <= SDLK_KP_9) {
|
||||||
// SDL defines SDL_SCANCODE_KP_0 > SDL_SCANCODE_KP_9, so we need to handle that manually
|
// SDL defines SDLK_KP_0 > SDLK_KP_9, so we need to handle that manually
|
||||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_KP_1) + VK_NUMPAD0 + 1;
|
keyCode = (event->keysym.sym - SDLK_KP_1) + VK_NUMPAD1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (event->keysym.scancode) {
|
switch (event->keysym.sym) {
|
||||||
case SDL_SCANCODE_BACKSPACE:
|
case SDLK_BACKSPACE:
|
||||||
keyCode = 0x08;
|
keyCode = 0x08;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_TAB:
|
case SDLK_TAB:
|
||||||
keyCode = 0x09;
|
keyCode = 0x09;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_CLEAR:
|
case SDLK_CLEAR:
|
||||||
keyCode = 0x0C;
|
keyCode = 0x0C;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RETURN:
|
case SDLK_RETURN:
|
||||||
keyCode = 0x0D;
|
keyCode = 0x0D;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_PAUSE:
|
case SDLK_PAUSE:
|
||||||
keyCode = 0x13;
|
keyCode = 0x13;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_CAPSLOCK:
|
case SDLK_CAPSLOCK:
|
||||||
keyCode = 0x14;
|
keyCode = 0x14;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_ESCAPE:
|
case SDLK_ESCAPE:
|
||||||
keyCode = 0x1B;
|
keyCode = 0x1B;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_SPACE:
|
case SDLK_SPACE:
|
||||||
keyCode = 0x20;
|
keyCode = 0x20;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_PAGEUP:
|
case SDLK_PAGEUP:
|
||||||
keyCode = 0x21;
|
keyCode = 0x21;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_PAGEDOWN:
|
case SDLK_PAGEDOWN:
|
||||||
keyCode = 0x22;
|
keyCode = 0x22;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_END:
|
case SDLK_END:
|
||||||
keyCode = 0x23;
|
keyCode = 0x23;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_HOME:
|
case SDLK_HOME:
|
||||||
keyCode = 0x24;
|
keyCode = 0x24;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LEFT:
|
case SDLK_LEFT:
|
||||||
keyCode = 0x25;
|
keyCode = 0x25;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_UP:
|
case SDLK_UP:
|
||||||
keyCode = 0x26;
|
keyCode = 0x26;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RIGHT:
|
case SDLK_RIGHT:
|
||||||
keyCode = 0x27;
|
keyCode = 0x27;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_DOWN:
|
case SDLK_DOWN:
|
||||||
keyCode = 0x28;
|
keyCode = 0x28;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_SELECT:
|
case SDLK_SELECT:
|
||||||
keyCode = 0x29;
|
keyCode = 0x29;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_EXECUTE:
|
case SDLK_EXECUTE:
|
||||||
keyCode = 0x2B;
|
keyCode = 0x2B;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_PRINTSCREEN:
|
case SDLK_PRINTSCREEN:
|
||||||
keyCode = 0x2C;
|
keyCode = 0x2C;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_INSERT:
|
case SDLK_INSERT:
|
||||||
keyCode = 0x2D;
|
keyCode = 0x2D;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_DELETE:
|
case SDLK_DELETE:
|
||||||
keyCode = 0x2E;
|
keyCode = 0x2E;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_HELP:
|
case SDLK_HELP:
|
||||||
keyCode = 0x2F;
|
keyCode = 0x2F;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_0:
|
case SDLK_KP_0:
|
||||||
// See comment above about why we only handle SDL_SCANCODE_KP_0 here
|
// See comment above about why we only handle KP_0 here
|
||||||
keyCode = VK_NUMPAD0;
|
keyCode = 0x60;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_0:
|
case SDLK_KP_MULTIPLY:
|
||||||
// See comment above about why we only handle SDL_SCANCODE_0 here
|
|
||||||
keyCode = VK_0;
|
|
||||||
break;
|
|
||||||
case SDL_SCANCODE_KP_MULTIPLY:
|
|
||||||
keyCode = 0x6A;
|
keyCode = 0x6A;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_PLUS:
|
case SDLK_KP_PLUS:
|
||||||
keyCode = 0x6B;
|
keyCode = 0x6B;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_COMMA:
|
case SDLK_KP_COMMA:
|
||||||
keyCode = 0x6C;
|
keyCode = 0x6C;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_MINUS:
|
case SDLK_KP_MINUS:
|
||||||
keyCode = 0x6D;
|
keyCode = 0x6D;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_DECIMAL:
|
case SDLK_KP_DECIMAL:
|
||||||
keyCode = 0x6E;
|
keyCode = 0x6E;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_DIVIDE:
|
case SDLK_KP_DIVIDE:
|
||||||
keyCode = 0x6F;
|
keyCode = 0x6F;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_NUMLOCKCLEAR:
|
case SDLK_NUMLOCKCLEAR:
|
||||||
keyCode = 0x90;
|
keyCode = 0x90;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_SCROLLLOCK:
|
case SDLK_SCROLLLOCK:
|
||||||
keyCode = 0x91;
|
keyCode = 0x91;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LSHIFT:
|
case SDLK_LSHIFT:
|
||||||
keyCode = 0xA0;
|
keyCode = 0xA0;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RSHIFT:
|
case SDLK_RSHIFT:
|
||||||
keyCode = 0xA1;
|
keyCode = 0xA1;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LCTRL:
|
case SDLK_LCTRL:
|
||||||
keyCode = 0xA2;
|
keyCode = 0xA2;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RCTRL:
|
case SDLK_RCTRL:
|
||||||
keyCode = 0xA3;
|
keyCode = 0xA3;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LALT:
|
case SDLK_LALT:
|
||||||
keyCode = 0xA4;
|
keyCode = 0xA4;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RALT:
|
case SDLK_RALT:
|
||||||
keyCode = 0xA5;
|
keyCode = 0xA5;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_BACK:
|
case SDLK_AC_BACK:
|
||||||
keyCode = 0xA6;
|
keyCode = 0xA6;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_FORWARD:
|
case SDLK_AC_FORWARD:
|
||||||
keyCode = 0xA7;
|
keyCode = 0xA7;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_REFRESH:
|
case SDLK_AC_REFRESH:
|
||||||
keyCode = 0xA8;
|
keyCode = 0xA8;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_STOP:
|
case SDLK_AC_STOP:
|
||||||
keyCode = 0xA9;
|
keyCode = 0xA9;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_SEARCH:
|
case SDLK_AC_SEARCH:
|
||||||
keyCode = 0xAA;
|
keyCode = 0xAA;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_BOOKMARKS:
|
case SDLK_AC_BOOKMARKS:
|
||||||
keyCode = 0xAB;
|
keyCode = 0xAB;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_AC_HOME:
|
case SDLK_AC_HOME:
|
||||||
keyCode = 0xAC;
|
keyCode = 0xAC;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_SEMICOLON:
|
case SDLK_SEMICOLON:
|
||||||
keyCode = 0xBA;
|
keyCode = 0xBA;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_COMMA:
|
case SDLK_PLUS:
|
||||||
|
keyCode = 0xBB;
|
||||||
|
break;
|
||||||
|
case SDLK_COMMA:
|
||||||
keyCode = 0xBC;
|
keyCode = 0xBC;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_MINUS:
|
case SDLK_MINUS:
|
||||||
keyCode = 0xBD;
|
keyCode = 0xBD;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_PERIOD:
|
case SDLK_PERIOD:
|
||||||
keyCode = 0xBE;
|
keyCode = 0xBE;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_SLASH:
|
case SDLK_SLASH:
|
||||||
keyCode = 0xBF;
|
keyCode = 0xBF;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_GRAVE:
|
case SDLK_BACKQUOTE:
|
||||||
keyCode = 0xC0;
|
keyCode = 0xC0;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LEFTBRACKET:
|
case SDLK_LEFTBRACKET:
|
||||||
keyCode = 0xDB;
|
keyCode = 0xDB;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_BACKSLASH:
|
case SDLK_BACKSLASH:
|
||||||
keyCode = 0xDC;
|
keyCode = 0xDC;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RIGHTBRACKET:
|
case SDLK_RIGHTBRACKET:
|
||||||
keyCode = 0xDD;
|
keyCode = 0xDD;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_APOSTROPHE:
|
case SDLK_QUOTE:
|
||||||
keyCode = 0xDE;
|
keyCode = 0xDE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unhandled button event: %d",
|
"Unhandled button event: %d",
|
||||||
event->keysym.scancode);
|
event->keysym.sym);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,18 +427,13 @@ void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
|||||||
state->lsX = event->value;
|
state->lsX = event->value;
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_LEFTY:
|
case SDL_CONTROLLER_AXIS_LEFTY:
|
||||||
// Signed values have one more negative value than
|
state->lsY = -event->value;
|
||||||
// positive value, so inverting the sign on -32768
|
|
||||||
// could actually cause the value to overflow and
|
|
||||||
// wrap around to be negative again. Avoid that by
|
|
||||||
// capping the value at 32767.
|
|
||||||
state->lsY = -qMax(event->value, (short)-32767);
|
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_RIGHTX:
|
case SDL_CONTROLLER_AXIS_RIGHTX:
|
||||||
state->rsX = event->value;
|
state->rsX = event->value;
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_RIGHTY:
|
case SDL_CONTROLLER_AXIS_RIGHTY:
|
||||||
state->rsY = -qMax(event->value, (short)-32767);
|
state->rsY = -event->value;
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
||||||
state->lt = (unsigned char)(event->value * 255UL / 32767);
|
state->lt = (unsigned char)(event->value * 255UL / 32767);
|
||||||
|
|||||||
@@ -5,38 +5,16 @@
|
|||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#ifdef HAVE_FFMPEG
|
#ifdef __APPLE__
|
||||||
|
#include "video/avfoundation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "video/ffmpeg.h"
|
#include "video/ffmpeg.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_SLVIDEO
|
|
||||||
#include "video/sl.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
|
||||||
// Using full-screen desktop allows us to avoid needing to enable V-sync on Windows
|
|
||||||
// and it also avoids some strange flickering issues on my Win7 test machine
|
|
||||||
// with Intel HD 5500 graphics. It also allows Spaces to work on macOS.
|
|
||||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
|
|
||||||
#else
|
|
||||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
// Scaling the icon down on Win32 looks dreadful, so render at lower res
|
|
||||||
#define ICON_SIZE 32
|
|
||||||
#else
|
|
||||||
#define ICON_SIZE 64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
|
#include <QRandomGenerator>
|
||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QSvgRenderer>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||||
Session::clStageStarting,
|
Session::clStageStarting,
|
||||||
@@ -108,22 +86,21 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
SDL_Window* window, int videoFormat, int width, int height,
|
SDL_Window* window, int videoFormat, int width, int height,
|
||||||
int frameRate, IVideoDecoder*& chosenDecoder)
|
int frameRate, IVideoDecoder*& chosenDecoder)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_SLVIDEO
|
#ifdef __APPLE__
|
||||||
chosenDecoder = new SLVideoDecoder();
|
chosenDecoder = AVFoundationDecoderFactory::createDecoder();
|
||||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SLVideo video decoder chosen");
|
"AVFoundation-based video decoder chosen");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unable to load SLVideo decoder");
|
"Unable to load AVFoundation decoder");
|
||||||
delete chosenDecoder;
|
delete chosenDecoder;
|
||||||
chosenDecoder = nullptr;
|
chosenDecoder = nullptr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_FFMPEG
|
|
||||||
chosenDecoder = new FFmpegVideoDecoder();
|
chosenDecoder = new FFmpegVideoDecoder();
|
||||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -136,11 +113,6 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
delete chosenDecoder;
|
delete chosenDecoder;
|
||||||
chosenDecoder = nullptr;
|
chosenDecoder = nullptr;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(HAVE_FFMPEG) && !defined(HAVE_SLVIDEO)
|
|
||||||
#error No video decoding libraries available!
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If we reach this, we didn't initialize any decoders successfully
|
// If we reach this, we didn't initialize any decoders successfully
|
||||||
return false;
|
return false;
|
||||||
@@ -148,17 +120,12 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
|
|
||||||
int Session::drSetup(int videoFormat, int width, int height, int frameRate, void *, int)
|
int Session::drSetup(int videoFormat, int width, int height, int frameRate, void *, int)
|
||||||
{
|
{
|
||||||
s_ActiveSession->m_ActiveVideoFormat = videoFormat;
|
if (!chooseDecoder(s_ActiveSession->m_Preferences.videoDecoderSelection,
|
||||||
s_ActiveSession->m_ActiveVideoWidth = width;
|
s_ActiveSession->m_Window,
|
||||||
s_ActiveSession->m_ActiveVideoHeight = height;
|
videoFormat, width, height, frameRate,
|
||||||
s_ActiveSession->m_ActiveVideoFrameRate = frameRate;
|
s_ActiveSession->m_VideoDecoder)) {
|
||||||
|
return -1;
|
||||||
// Defer decoder setup until we've started streaming so we
|
}
|
||||||
// don't have to hide and show the SDL window (which seems to
|
|
||||||
// cause pointer hiding to break on Windows).
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Video stream is %dx%dx%d (format 0x%x)",
|
|
||||||
width, height, frameRate, videoFormat);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -169,16 +136,7 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
|
|||||||
// from underneath the session when we initiate destruction.
|
// from underneath the session when we initiate destruction.
|
||||||
// We need to destroy the decoder on the main thread to satisfy
|
// We need to destroy the decoder on the main thread to satisfy
|
||||||
// some API constraints (like DXVA2).
|
// some API constraints (like DXVA2).
|
||||||
|
|
||||||
SDL_AtomicLock(&s_ActiveSession->m_DecoderLock);
|
SDL_AtomicLock(&s_ActiveSession->m_DecoderLock);
|
||||||
|
|
||||||
if (s_ActiveSession->m_NeedsIdr) {
|
|
||||||
// If we reset our decoder, we'll need to request an IDR frame
|
|
||||||
s_ActiveSession->m_NeedsIdr = false;
|
|
||||||
SDL_AtomicUnlock(&s_ActiveSession->m_DecoderLock);
|
|
||||||
return DR_NEED_IDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
|
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
|
||||||
if (decoder != nullptr) {
|
if (decoder != nullptr) {
|
||||||
int ret = decoder->submitDecodeUnit(du);
|
int ret = decoder->submitDecodeUnit(du);
|
||||||
@@ -196,35 +154,23 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
|||||||
{
|
{
|
||||||
IVideoDecoder* decoder;
|
IVideoDecoder* decoder;
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
||||||
if (!window) {
|
if (!window) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to create window for hardware decode test: %s",
|
"Failed to create window for hardware decode test: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) {
|
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) {
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
|
|
||||||
bool ret = decoder->isHardwareAccelerated();
|
bool ret = decoder->isHardwareAccelerated();
|
||||||
|
|
||||||
delete decoder;
|
delete decoder;
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,11 +179,8 @@ Session::Session(NvComputer* computer, NvApp& app)
|
|||||||
m_App(app),
|
m_App(app),
|
||||||
m_Window(nullptr),
|
m_Window(nullptr),
|
||||||
m_VideoDecoder(nullptr),
|
m_VideoDecoder(nullptr),
|
||||||
m_DecoderLock(0),
|
m_DecoderLock(0)
|
||||||
m_NeedsIdr(false)
|
|
||||||
{
|
{
|
||||||
qDebug() << "Server GPU:" << m_Computer->gpuModel;
|
|
||||||
|
|
||||||
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
||||||
m_VideoCallbacks.setup = drSetup;
|
m_VideoCallbacks.setup = drSetup;
|
||||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||||
@@ -246,11 +189,7 @@ Session::Session(NvComputer* computer, NvApp& app)
|
|||||||
m_VideoCallbacks.capabilities |= CAPABILITY_DIRECT_SUBMIT;
|
m_VideoCallbacks.capabilities |= CAPABILITY_DIRECT_SUBMIT;
|
||||||
|
|
||||||
// Slice up to 4 times for parallel decode, once slice per core
|
// Slice up to 4 times for parallel decode, once slice per core
|
||||||
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(qMin(MAX_SLICES, SDL_GetCPUCount()));
|
||||||
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Encoder configured for %d slices per frame",
|
|
||||||
slices);
|
|
||||||
|
|
||||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||||
m_StreamConfig.width = m_Preferences.width;
|
m_StreamConfig.width = m_Preferences.width;
|
||||||
@@ -258,21 +197,14 @@ Session::Session(NvComputer* computer, NvApp& app)
|
|||||||
m_StreamConfig.fps = m_Preferences.fps;
|
m_StreamConfig.fps = m_Preferences.fps;
|
||||||
m_StreamConfig.bitrate = m_Preferences.bitrateKbps;
|
m_StreamConfig.bitrate = m_Preferences.bitrateKbps;
|
||||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||||
|
for (unsigned int i = 0; i < sizeof(m_StreamConfig.remoteInputAesKey); i++) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
m_StreamConfig.remoteInputAesKey[i] =
|
||||||
"Video bitrate: %d kbps",
|
(char)(QRandomGenerator::global()->generate() % 256);
|
||||||
m_StreamConfig.bitrate);
|
}
|
||||||
|
*(int*)m_StreamConfig.remoteInputAesIv = qToBigEndian(QRandomGenerator::global()->generate());
|
||||||
RAND_bytes(reinterpret_cast<unsigned char*>(m_StreamConfig.remoteInputAesKey),
|
|
||||||
sizeof(m_StreamConfig.remoteInputAesKey));
|
|
||||||
|
|
||||||
// Only the first 4 bytes are populated in the RI key IV
|
|
||||||
RAND_bytes(reinterpret_cast<unsigned char*>(m_StreamConfig.remoteInputAesIv), 4);
|
|
||||||
|
|
||||||
switch (m_Preferences.audioConfig)
|
switch (m_Preferences.audioConfig)
|
||||||
{
|
{
|
||||||
case StreamingPreferences::AC_AUTO:
|
case StreamingPreferences::AC_AUTO:
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration");
|
|
||||||
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
||||||
break;
|
break;
|
||||||
case StreamingPreferences::AC_FORCE_STEREO:
|
case StreamingPreferences::AC_FORCE_STEREO:
|
||||||
@@ -283,10 +215,6 @@ Session::Session(NvComputer* computer, NvApp& app)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Audio configuration: %d",
|
|
||||||
m_StreamConfig.audioConfiguration);
|
|
||||||
|
|
||||||
switch (m_Preferences.videoCodecConfig)
|
switch (m_Preferences.videoCodecConfig)
|
||||||
{
|
{
|
||||||
case StreamingPreferences::VCC_AUTO:
|
case StreamingPreferences::VCC_AUTO:
|
||||||
@@ -328,68 +256,42 @@ Session::Session(NvComputer* computer, NvApp& app)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::emitLaunchWarning(QString text)
|
|
||||||
{
|
|
||||||
// Emit the warning to the UI
|
|
||||||
emit displayLaunchWarning(text);
|
|
||||||
|
|
||||||
// Wait a little bit so the user can actually read what we just said.
|
|
||||||
// This wait is a little longer than the actual toast timeout (3 seconds)
|
|
||||||
// to allow it to transition off the screen before continuing.
|
|
||||||
uint32_t start = SDL_GetTicks();
|
|
||||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 3500)) {
|
|
||||||
// Pump the UI loop while we wait
|
|
||||||
SDL_Delay(5);
|
|
||||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Session::validateLaunch()
|
bool Session::validateLaunch()
|
||||||
{
|
{
|
||||||
QStringList warningList;
|
QStringList warningList;
|
||||||
|
|
||||||
if (m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
|
||||||
emitLaunchWarning("Your settings selection to force software decoding may cause poor streaming performance.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_StreamConfig.supportsHevc) {
|
if (m_StreamConfig.supportsHevc) {
|
||||||
bool hevcForced = m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
if (m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||||
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR) {
|
||||||
|
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||||
if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
emit displayLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
||||||
|
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||||
VIDEO_FORMAT_H265,
|
VIDEO_FORMAT_H265,
|
||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
m_StreamConfig.fps) &&
|
m_StreamConfig.fps)) {
|
||||||
m_Preferences.videoDecoderSelection != StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
// NOTE: HEVC currently uses only 1 slice regardless of what
|
||||||
if (hevcForced) {
|
// we provide in CAPABILITY_SLICES_PER_FRAME(), so we should
|
||||||
emitLaunchWarning("Using software decoding due to your selection to force HEVC. This may cause poor streaming performance.");
|
// never use it for software decoding (unless common-c starts
|
||||||
}
|
// respecting it for HEVC).
|
||||||
else {
|
|
||||||
emitLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
|
||||||
m_StreamConfig.supportsHevc = false;
|
m_StreamConfig.supportsHevc = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hevcForced) {
|
|
||||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
|
||||||
emitLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
|
||||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_StreamConfig.enableHdr) {
|
if (m_StreamConfig.enableHdr) {
|
||||||
// Turn HDR back off unless all criteria are met.
|
// Turn HDR back off unless all criteria are met.
|
||||||
m_StreamConfig.enableHdr = false;
|
m_StreamConfig.enableHdr = false;
|
||||||
|
|
||||||
// Check that the app supports HDR
|
// Check that the app supports HDR
|
||||||
if (!m_App.hdrSupported) {
|
if (!m_App.hdrSupported) {
|
||||||
emitLaunchWarning(m_App.name + " doesn't support HDR10.");
|
emit displayLaunchWarning(m_App.name + " doesn't support HDR10.");
|
||||||
}
|
}
|
||||||
// Check that the server GPU supports HDR
|
// Check that the server GPU supports HDR
|
||||||
else if (!(m_Computer->serverCodecModeSupport & 0x200)) {
|
else if (!(m_Computer->serverCodecModeSupport & 0x200)) {
|
||||||
emitLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
emit displayLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
||||||
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
||||||
}
|
}
|
||||||
else if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
else if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||||
@@ -397,7 +299,7 @@ bool Session::validateLaunch()
|
|||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
m_StreamConfig.fps)) {
|
m_StreamConfig.fps)) {
|
||||||
emitLaunchWarning("This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.");
|
emit displayLaunchWarning("Your client PC GPU doesn't support HEVC Main10 decoding for HDR streaming.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO: Also validate display capabilites
|
// TODO: Also validate display capabilites
|
||||||
@@ -410,7 +312,7 @@ bool Session::validateLaunch()
|
|||||||
if (m_StreamConfig.width >= 3840) {
|
if (m_StreamConfig.width >= 3840) {
|
||||||
// Only allow 4K on GFE 3.x+
|
// Only allow 4K on GFE 3.x+
|
||||||
if (m_Computer->gfeVersion.isNull() || m_Computer->gfeVersion.startsWith("2.")) {
|
if (m_Computer->gfeVersion.isNull() || m_Computer->gfeVersion.startsWith("2.")) {
|
||||||
emitLaunchWarning("GeForce Experience 3.0 or higher is required for 4K streaming.");
|
emit displayLaunchWarning("GeForce Experience 3.0 or higher is required for 4K streaming.");
|
||||||
|
|
||||||
m_StreamConfig.width = 1920;
|
m_StreamConfig.width = 1920;
|
||||||
m_StreamConfig.height = 1080;
|
m_StreamConfig.height = 1080;
|
||||||
@@ -418,7 +320,7 @@ bool Session::validateLaunch()
|
|||||||
// This list is sorted from least to greatest
|
// This list is sorted from least to greatest
|
||||||
else if (m_Computer->displayModes.last().width < 3840 ||
|
else if (m_Computer->displayModes.last().width < 3840 ||
|
||||||
(m_Computer->displayModes.last().refreshRate < 60 && m_StreamConfig.fps >= 60)) {
|
(m_Computer->displayModes.last().refreshRate < 60 && m_StreamConfig.fps >= 60)) {
|
||||||
emitLaunchWarning("Your host PC GPU doesn't support 4K streaming. "
|
emit displayLaunchWarning("Your host PC GPU doesn't support 4K streaming. "
|
||||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for 4K streaming.");
|
"A GeForce GTX 900-series (Maxwell) or later GPU is required for 4K streaming.");
|
||||||
|
|
||||||
m_StreamConfig.width = 1920;
|
m_StreamConfig.width = 1920;
|
||||||
@@ -437,6 +339,9 @@ class DeferredSessionCleanupTask : public QRunnable
|
|||||||
{
|
{
|
||||||
// Finish cleanup of the connection state
|
// Finish cleanup of the connection state
|
||||||
LiStopConnection();
|
LiStopConnection();
|
||||||
|
if (Session::s_ActiveSession->m_Window != nullptr) {
|
||||||
|
SDL_DestroyWindow(Session::s_ActiveSession->m_Window);
|
||||||
|
}
|
||||||
|
|
||||||
// Allow another session to start now that we're cleaned up
|
// Allow another session to start now that we're cleaned up
|
||||||
Session::s_ActiveSession = nullptr;
|
Session::s_ActiveSession = nullptr;
|
||||||
@@ -444,130 +349,6 @@ class DeferredSessionCleanupTask : public QRunnable
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void Session::getWindowDimensions(bool fullScreen,
|
|
||||||
int& x, int& y,
|
|
||||||
int& width, int& height)
|
|
||||||
{
|
|
||||||
int displayIndex = 0;
|
|
||||||
|
|
||||||
// If there's a display matching this exact resolution, pick that
|
|
||||||
// one (for native full-screen streaming). Otherwise, assume
|
|
||||||
// display 0 for now. TODO: Default to the screen that the Qt window is on
|
|
||||||
if (fullScreen) {
|
|
||||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0 &&
|
|
||||||
m_ActiveVideoWidth == mode.w &&
|
|
||||||
m_ActiveVideoHeight == mode.h) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Found exact resolution match on display: %d",
|
|
||||||
i);
|
|
||||||
displayIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_Window != nullptr) {
|
|
||||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
|
||||||
if (displayIndex < 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowDisplayIndex() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
|
|
||||||
// Assume display 0
|
|
||||||
displayIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullScreen) {
|
|
||||||
SDL_DisplayMode currentMode;
|
|
||||||
|
|
||||||
if (SDL_GetCurrentDisplayMode(displayIndex, ¤tMode) == 0) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using current display mode: %dx%dx%d",
|
|
||||||
currentMode.w, currentMode.h, currentMode.refresh_rate);
|
|
||||||
width = currentMode.w;
|
|
||||||
height = currentMode.h;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unable to get current display mode: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
width = m_StreamConfig.width;
|
|
||||||
height = m_StreamConfig.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-screen modes always start at the origin
|
|
||||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_Rect usableBounds;
|
|
||||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
|
||||||
width = usableBounds.w;
|
|
||||||
height = usableBounds.h;
|
|
||||||
x = usableBounds.x;
|
|
||||||
y = usableBounds.y;
|
|
||||||
|
|
||||||
if (m_Window != nullptr) {
|
|
||||||
int top, left, bottom, right;
|
|
||||||
|
|
||||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) < 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unable to get window border size");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
x += left;
|
|
||||||
y += top;
|
|
||||||
|
|
||||||
width -= left + right;
|
|
||||||
height -= top + bottom;
|
|
||||||
|
|
||||||
// If the stream window can fit within the usable drawing area with 1:1
|
|
||||||
// scaling, do that rather than filling the screen.
|
|
||||||
if (m_StreamConfig.width < width && m_StreamConfig.height < height) {
|
|
||||||
width = m_StreamConfig.width;
|
|
||||||
height = m_StreamConfig.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetDisplayUsableBounds() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
|
|
||||||
width = m_StreamConfig.width;
|
|
||||||
height = m_StreamConfig.height;
|
|
||||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Session::toggleFullscreen()
|
|
||||||
{
|
|
||||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & SDL_OS_FULLSCREEN_FLAG);
|
|
||||||
|
|
||||||
int x, y, width, height;
|
|
||||||
|
|
||||||
// If we're leaving full screen, toggle out before setting our size and position
|
|
||||||
// that way we have accurate display size metrics (not the size our stream changed it to).
|
|
||||||
if (!fullScreen) {
|
|
||||||
SDL_SetWindowFullscreen(m_Window, 0);
|
|
||||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
getWindowDimensions(fullScreen, x, y, width, height);
|
|
||||||
|
|
||||||
SDL_SetWindowPosition(m_Window, x, y);
|
|
||||||
SDL_SetWindowSize(m_Window, width, height);
|
|
||||||
|
|
||||||
if (fullScreen) {
|
|
||||||
SDL_SetWindowResizable(m_Window, SDL_FALSE);
|
|
||||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Session::exec()
|
void Session::exec()
|
||||||
{
|
{
|
||||||
// Check for validation errors/warnings and emit
|
// Check for validation errors/warnings and emit
|
||||||
@@ -594,18 +375,6 @@ void Session::exec()
|
|||||||
Q_ASSERT(m_Computer->currentGameId == 0 ||
|
Q_ASSERT(m_Computer->currentGameId == 0 ||
|
||||||
m_Computer->currentGameId == m_App.id);
|
m_Computer->currentGameId == m_App.id);
|
||||||
|
|
||||||
bool enableGameOptimizations = false;
|
|
||||||
for (const NvDisplayMode &mode : m_Computer->displayModes) {
|
|
||||||
if (mode.width == m_StreamConfig.width &&
|
|
||||||
mode.height == m_StreamConfig.height) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Found host supported resolution: %dx%d",
|
|
||||||
mode.width, mode.height);
|
|
||||||
enableGameOptimizations = prefs.gameOptimizations;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
NvHTTP http(m_Computer->activeAddress);
|
NvHTTP http(m_Computer->activeAddress);
|
||||||
if (m_Computer->currentGameId != 0) {
|
if (m_Computer->currentGameId != 0) {
|
||||||
@@ -613,7 +382,7 @@ void Session::exec()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
http.launchApp(m_App.id, &m_StreamConfig,
|
http.launchApp(m_App.id, &m_StreamConfig,
|
||||||
enableGameOptimizations,
|
prefs.gameOptimizations,
|
||||||
prefs.playAudioOnHost,
|
prefs.playAudioOnHost,
|
||||||
inputHandler.getAttachedGamepadMask());
|
inputHandler.getAttachedGamepadMask());
|
||||||
}
|
}
|
||||||
@@ -623,12 +392,64 @@ void Session::exec()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_VIDEO));
|
int flags = SDL_WINDOW_HIDDEN;
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
int width, height;
|
||||||
|
if (m_Preferences.fullScreen) {
|
||||||
|
SDL_DisplayMode desired, closest;
|
||||||
|
|
||||||
|
SDL_zero(desired);
|
||||||
|
desired.w = m_StreamConfig.width;
|
||||||
|
desired.h = m_StreamConfig.height;
|
||||||
|
desired.refresh_rate = m_StreamConfig.fps;
|
||||||
|
|
||||||
|
if (SDL_GetClosestDisplayMode(0, &desired, &closest)) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Closest match for %dx%dx%d is %dx%dx%d",
|
||||||
|
desired.w, desired.h, desired.refresh_rate,
|
||||||
|
closest.w, closest.h, closest.refresh_rate);
|
||||||
|
width = closest.w;
|
||||||
|
height = closest.h;
|
||||||
|
}
|
||||||
|
else if (SDL_GetCurrentDisplayMode(0, &closest) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using current display mode: %dx%dx%d",
|
||||||
|
closest.w, closest.h, closest.refresh_rate);
|
||||||
|
width = closest.w;
|
||||||
|
height = closest.h;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unable to get current or closest display mode");
|
||||||
|
width = m_StreamConfig.width;
|
||||||
|
height = m_StreamConfig.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags |= SDL_WINDOW_FULLSCREEN;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_DisplayMode current;
|
||||||
|
|
||||||
|
width = m_StreamConfig.width;
|
||||||
|
height = m_StreamConfig.height;
|
||||||
|
|
||||||
|
if (SDL_GetCurrentDisplayMode(0, ¤t) == 0) {
|
||||||
|
if (current.w <= m_StreamConfig.width || current.h <= m_StreamConfig.height) {
|
||||||
|
// If we match or exceed the dimensions of the display, maximize the window
|
||||||
|
flags |= SDL_WINDOW_MAXIMIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Window = SDL_CreateWindow("Moonlight",
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
flags);
|
||||||
|
if (!m_Window) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
"SDL_CreateWindow() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
emit displayLaunchError(QString::fromLocal8Bit(SDL_GetError()));
|
|
||||||
s_ActiveSessionSemaphore.release();
|
s_ActiveSessionSemaphore.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -656,7 +477,6 @@ void Session::exec()
|
|||||||
// We already displayed an error dialog in the stage failure
|
// We already displayed an error dialog in the stage failure
|
||||||
// listener.
|
// listener.
|
||||||
s_ActiveSessionSemaphore.release();
|
s_ActiveSessionSemaphore.release();
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,71 +484,11 @@ void Session::exec()
|
|||||||
emit connectionStarted();
|
emit connectionStarted();
|
||||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
int x, y, width, height;
|
// Show the streaming window
|
||||||
getWindowDimensions(m_Preferences.fullScreen,
|
SDL_ShowWindow(m_Window);
|
||||||
x, y, width, height);
|
|
||||||
|
|
||||||
m_Window = SDL_CreateWindow("Moonlight",
|
// Capture the mouse
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
m_Preferences.fullScreen ?
|
|
||||||
SDL_OS_FULLSCREEN_FLAG : 0);
|
|
||||||
if (!m_Window) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_CreateWindow() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
s_ActiveSessionSemaphore.release();
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-full screen windows, call getWindowDimensions()
|
|
||||||
// again after creating a window to allow it to account
|
|
||||||
// for window chrome size.
|
|
||||||
if (!m_Preferences.fullScreen) {
|
|
||||||
getWindowDimensions(false, x, y, width, height);
|
|
||||||
|
|
||||||
SDL_SetWindowPosition(m_Window, x, y);
|
|
||||||
SDL_SetWindowSize(m_Window, width, height);
|
|
||||||
|
|
||||||
// Passing SDL_WINDOW_RESIZABLE to set this during window
|
|
||||||
// creation causes our window to be full screen for some reason
|
|
||||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
|
||||||
QImage svgImage(ICON_SIZE, ICON_SIZE, QImage::Format_RGBA8888);
|
|
||||||
svgImage.fill(0);
|
|
||||||
|
|
||||||
QPainter svgPainter(&svgImage);
|
|
||||||
svgIconRenderer.render(&svgPainter);
|
|
||||||
SDL_Surface* iconSurface = SDL_CreateRGBSurfaceWithFormatFrom((void*)svgImage.constBits(),
|
|
||||||
svgImage.width(),
|
|
||||||
svgImage.height(),
|
|
||||||
32,
|
|
||||||
4 * svgImage.width(),
|
|
||||||
SDL_PIXELFORMAT_RGBA32);
|
|
||||||
#ifndef Q_OS_DARWIN
|
|
||||||
// Other platforms seem to preserve our Qt icon when creating a new window
|
|
||||||
if (iconSurface != nullptr) {
|
|
||||||
SDL_SetWindowIcon(m_Window, iconSurface);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef QT_DEBUG
|
|
||||||
// Capture the mouse by default on release builds only.
|
|
||||||
// This prevents the mouse from becoming trapped inside
|
|
||||||
// Moonlight when it's halted at a debug break.
|
|
||||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Stop text input. SDL enables it by default
|
|
||||||
// when we initialize the video subsystem, but this
|
|
||||||
// causes an IME popup when certain keys are held down
|
|
||||||
// on macOS.
|
|
||||||
SDL_StopTextInput();
|
|
||||||
|
|
||||||
// Disable the screen saver
|
// Disable the screen saver
|
||||||
SDL_DisableScreenSaver();
|
SDL_DisableScreenSaver();
|
||||||
@@ -744,15 +504,7 @@ void Session::exec()
|
|||||||
// Hijack this thread to be the SDL main thread. We have to do this
|
// Hijack this thread to be the SDL main thread. We have to do this
|
||||||
// because we want to suspend all Qt processing until the stream is over.
|
// because we want to suspend all Qt processing until the stream is over.
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
for (;;) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
// We explicitly use SDL_PollEvent() and SDL_Delay() because
|
|
||||||
// SDL_WaitEvent() has an internal SDL_Delay(10) inside which
|
|
||||||
// blocks this thread too long for high polling rate mice and high
|
|
||||||
// refresh rate displays.
|
|
||||||
if (!SDL_PollEvent(&event)) {
|
|
||||||
SDL_Delay(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -777,41 +529,6 @@ void Session::exec()
|
|||||||
m_VideoDecoder->renderFrame(&event.user);
|
m_VideoDecoder->renderFrame(&event.user);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SDL_WINDOWEVENT:
|
|
||||||
// We want to recreate the decoder for resizes (full-screen toggles) and the initial shown event.
|
|
||||||
// We use SDL_WINDOWEVENT_SIZE_CHANGED rather than SDL_WINDOWEVENT_RESIZED because the latter doesn't
|
|
||||||
// seem to fire when switching from windowed to full-screen on X11.
|
|
||||||
if (event.window.event != SDL_WINDOWEVENT_SIZE_CHANGED && event.window.event != SDL_WINDOWEVENT_SHOWN) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall through
|
|
||||||
case SDL_RENDER_DEVICE_RESET:
|
|
||||||
case SDL_RENDER_TARGETS_RESET:
|
|
||||||
SDL_AtomicLock(&m_DecoderLock);
|
|
||||||
|
|
||||||
// Destroy the old decoder
|
|
||||||
delete m_VideoDecoder;
|
|
||||||
|
|
||||||
// Chose a new decoder (hopefully the same one, but possibly
|
|
||||||
// not if a GPU was removed or something).
|
|
||||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
|
||||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
|
||||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
|
||||||
s_ActiveSession->m_VideoDecoder)) {
|
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to recreate decoder after reset");
|
|
||||||
goto DispatchDeferredCleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request an IDR frame to complete the reset
|
|
||||||
m_NeedsIdr = true;
|
|
||||||
|
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
inputHandler.handleKeyEvent(&event.key);
|
inputHandler.handleKeyEvent(&event.key);
|
||||||
@@ -849,6 +566,9 @@ DispatchDeferredCleanup:
|
|||||||
// so we can return to the Qt GUI ASAP.
|
// so we can return to the Qt GUI ASAP.
|
||||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
|
if (m_Window != nullptr) {
|
||||||
|
SDL_HideWindow(m_Window);
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy the decoder, since this must be done on the main thread
|
// Destroy the decoder, since this must be done on the main thread
|
||||||
SDL_AtomicLock(&m_DecoderLock);
|
SDL_AtomicLock(&m_DecoderLock);
|
||||||
@@ -856,13 +576,6 @@ DispatchDeferredCleanup:
|
|||||||
m_VideoDecoder = nullptr;
|
m_VideoDecoder = nullptr;
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
SDL_AtomicUnlock(&m_DecoderLock);
|
||||||
|
|
||||||
SDL_DestroyWindow(m_Window);
|
|
||||||
if (iconSurface != nullptr) {
|
|
||||||
SDL_FreeSurface(iconSurface);
|
|
||||||
}
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_VIDEO));
|
|
||||||
|
|
||||||
// Cleanup can take a while, so dispatch it to a worker thread.
|
// Cleanup can take a while, so dispatch it to a worker thread.
|
||||||
// When it is complete, it will release our s_ActiveSessionSemaphore
|
// When it is complete, it will release our s_ActiveSessionSemaphore
|
||||||
// reference.
|
// reference.
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void exec();
|
Q_INVOKABLE void exec();
|
||||||
|
|
||||||
static
|
|
||||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
int videoFormat, int width, int height, int frameRate);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void stageStarting(QString stage);
|
void stageStarting(QString stage);
|
||||||
|
|
||||||
@@ -39,17 +35,13 @@ signals:
|
|||||||
private:
|
private:
|
||||||
bool validateLaunch();
|
bool validateLaunch();
|
||||||
|
|
||||||
void emitLaunchWarning(QString text);
|
|
||||||
|
|
||||||
int getDecoderCapabilities();
|
int getDecoderCapabilities();
|
||||||
|
|
||||||
int sdlDetermineAudioConfiguration();
|
int sdlDetermineAudioConfiguration();
|
||||||
|
|
||||||
void getWindowDimensions(bool fullScreen,
|
static
|
||||||
int& x, int& y,
|
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
||||||
int& width, int& height);
|
int videoFormat, int width, int height, int frameRate);
|
||||||
|
|
||||||
void toggleFullscreen();
|
|
||||||
|
|
||||||
static
|
static
|
||||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||||
@@ -102,12 +94,6 @@ private:
|
|||||||
SDL_Window* m_Window;
|
SDL_Window* m_Window;
|
||||||
IVideoDecoder* m_VideoDecoder;
|
IVideoDecoder* m_VideoDecoder;
|
||||||
SDL_SpinLock m_DecoderLock;
|
SDL_SpinLock m_DecoderLock;
|
||||||
bool m_NeedsIdr;
|
|
||||||
|
|
||||||
int m_ActiveVideoFormat;
|
|
||||||
int m_ActiveVideoWidth;
|
|
||||||
int m_ActiveVideoHeight;
|
|
||||||
int m_ActiveVideoFrameRate;
|
|
||||||
|
|
||||||
static SDL_AudioDeviceID s_AudioDevice;
|
static SDL_AudioDeviceID s_AudioDevice;
|
||||||
static OpusMSDecoder* s_OpusDecoder;
|
static OpusMSDecoder* s_OpusDecoder;
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
#include "streamutils.h"
|
|
||||||
|
|
||||||
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
|
||||||
{
|
|
||||||
int dstH = dst->w * src->h / src->w;
|
|
||||||
int dstW = dst->h * src->w / src->h;
|
|
||||||
|
|
||||||
if (dstH > dst->h) {
|
|
||||||
dst->y = 0;
|
|
||||||
dst->x = (dst->w - dstW) / 2;
|
|
||||||
dst->w = dstW;
|
|
||||||
SDL_assert(dst->w * src->h / src->w <= dst->h);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dst->x = 0;
|
|
||||||
dst->y = (dst->h - dstH) / 2;
|
|
||||||
dst->h = dstH;
|
|
||||||
SDL_assert(dst->h * src->w / src->h <= dst->w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
class StreamUtils
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static
|
|
||||||
void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decoder.h"
|
||||||
|
|
||||||
|
// A factory is required to avoid pulling in
|
||||||
|
// incompatible Objective-C headers.
|
||||||
|
class AVFoundationDecoderFactory {
|
||||||
|
public:
|
||||||
|
static
|
||||||
|
IVideoDecoder* createDecoder();
|
||||||
|
};
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
#include "avfoundation.h"
|
||||||
|
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <VideoToolbox/VideoToolbox.h>
|
||||||
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
#import <CoreVideo/CoreVideo.h>
|
||||||
|
|
||||||
|
#define FRAME_START_PREFIX_SIZE 4
|
||||||
|
#define NALU_START_PREFIX_SIZE 3
|
||||||
|
#define NAL_LENGTH_PREFIX_SIZE 4
|
||||||
|
|
||||||
|
#define FUTURE_FRAMES_IN_VSYNC 3
|
||||||
|
|
||||||
|
class AVFoundationDecoder : public IVideoDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AVFoundationDecoder()
|
||||||
|
: m_DisplayLayer(nullptr),
|
||||||
|
m_FormatDesc(nullptr),
|
||||||
|
m_SpsData(nullptr),
|
||||||
|
m_PpsData(nullptr),
|
||||||
|
m_VpsData(nullptr),
|
||||||
|
m_View(nullptr),
|
||||||
|
m_NeedsIdr(false),
|
||||||
|
m_DisplayLink(nullptr),
|
||||||
|
m_VsyncTime(0),
|
||||||
|
m_LastVsyncTime(0),
|
||||||
|
m_VsyncStatsLock(0)
|
||||||
|
{
|
||||||
|
SDL_zero(m_FrameCountOnVsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~AVFoundationDecoder()
|
||||||
|
{
|
||||||
|
if (m_FormatDesc != nullptr) {
|
||||||
|
CFRelease(m_FormatDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DisplayLink != nullptr) {
|
||||||
|
CVDisplayLinkStop(m_DisplayLink);
|
||||||
|
CVDisplayLinkRelease(m_DisplayLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete m_SpsData;
|
||||||
|
delete m_PpsData;
|
||||||
|
delete m_VpsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
CVReturn
|
||||||
|
displayLinkOutputCallback(
|
||||||
|
CVDisplayLinkRef,
|
||||||
|
const CVTimeStamp*,
|
||||||
|
const CVTimeStamp* vsyncTime,
|
||||||
|
CVOptionFlags,
|
||||||
|
CVOptionFlags*,
|
||||||
|
void *displayLinkContext)
|
||||||
|
{
|
||||||
|
AVFoundationDecoder* me = reinterpret_cast<AVFoundationDecoder*>(displayLinkContext);
|
||||||
|
|
||||||
|
SDL_AtomicLock(&me->m_VsyncStatsLock);
|
||||||
|
me->m_LastVsyncTime = me->m_VsyncTime;
|
||||||
|
me->m_VsyncTime = vsyncTime->hostTime;
|
||||||
|
|
||||||
|
int framesThisVsync = *(volatile int*)&me->m_FrameCountOnVsync[0];
|
||||||
|
|
||||||
|
memmove(&me->m_FrameCountOnVsync[0],
|
||||||
|
&me->m_FrameCountOnVsync[1],
|
||||||
|
sizeof(me->m_FrameCountOnVsync) - sizeof(me->m_FrameCountOnVsync[0]));
|
||||||
|
me->m_FrameCountOnVsync[FUTURE_FRAMES_IN_VSYNC - 1] = 0;
|
||||||
|
SDL_AtomicUnlock(&me->m_VsyncStatsLock);
|
||||||
|
|
||||||
|
if (framesThisVsync != 1)
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"V-Sync interval frames: %d",
|
||||||
|
framesThisVsync);
|
||||||
|
|
||||||
|
return kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool initialize(StreamingPreferences::VideoDecoderSelection vds,
|
||||||
|
SDL_Window* window,
|
||||||
|
int videoFormat,
|
||||||
|
int, int, int) override
|
||||||
|
{
|
||||||
|
m_VideoFormat = videoFormat;
|
||||||
|
|
||||||
|
// Fail if the user wants software decode only
|
||||||
|
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
|
// Prior to 10.13, we'll just assume everything has
|
||||||
|
// H.264 support and fail open to allow VT decode.
|
||||||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||||
|
if (__builtin_available(macOS 10.13, *)) {
|
||||||
|
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_H264)) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HW accelerated H.264 decode via VT");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Assuming H.264 HW decode on < macOS 10.13");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||||
|
if (__builtin_available(macOS 10.13, *)) {
|
||||||
|
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HW accelerated HEVC decode via VT");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// Fail closed for HEVC if we're not on 10.13+
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HEVC support on < macOS 10.13");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SysWMinfo info;
|
||||||
|
|
||||||
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
|
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert(info.subsystem == SDL_SYSWM_COCOA);
|
||||||
|
|
||||||
|
// SDL adds its own content view to listen for events.
|
||||||
|
// We need to add a subview for our display layer.
|
||||||
|
NSView* contentView = info.info.cocoa.window.contentView;
|
||||||
|
m_View = [[NSView alloc] initWithFrame:contentView.bounds];
|
||||||
|
m_View.wantsLayer = YES;
|
||||||
|
[contentView addSubview: m_View];
|
||||||
|
|
||||||
|
setupDisplayLayer();
|
||||||
|
|
||||||
|
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||||
|
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, (void*)this);
|
||||||
|
CVDisplayLinkStart(m_DisplayLink);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isHardwareAccelerated() override
|
||||||
|
{
|
||||||
|
// We only use AVFoundation for HW acceleration
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int submitDecodeUnit(PDECODE_UNIT du) override
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
|
||||||
|
unsigned char* data = (unsigned char*)malloc(du->fullLength);
|
||||||
|
if (!data) {
|
||||||
|
return DR_NEED_IDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLENTRY entry = du->bufferList;
|
||||||
|
int pictureDataLength = 0;
|
||||||
|
while (entry != nullptr) {
|
||||||
|
if (entry->bufferType == BUFFER_TYPE_VPS) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got VPS");
|
||||||
|
delete m_VpsData;
|
||||||
|
m_VpsData = new QByteArray(&entry->data[FRAME_START_PREFIX_SIZE], entry->length - FRAME_START_PREFIX_SIZE);
|
||||||
|
|
||||||
|
// We got a new VPS so wait for a new SPS to match it
|
||||||
|
delete m_SpsData;
|
||||||
|
delete m_PpsData;
|
||||||
|
m_SpsData = m_PpsData = nullptr;
|
||||||
|
}
|
||||||
|
else if (entry->bufferType == BUFFER_TYPE_SPS) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got SPS");
|
||||||
|
delete m_SpsData;
|
||||||
|
m_SpsData = new QByteArray(&entry->data[FRAME_START_PREFIX_SIZE], entry->length - FRAME_START_PREFIX_SIZE);
|
||||||
|
|
||||||
|
// We got a new SPS so wait for a new PPS to match it
|
||||||
|
delete m_PpsData;
|
||||||
|
m_PpsData = nullptr;
|
||||||
|
}
|
||||||
|
else if (entry->bufferType == BUFFER_TYPE_PPS) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got PPS");
|
||||||
|
delete m_PpsData;
|
||||||
|
m_PpsData = new QByteArray(&entry->data[FRAME_START_PREFIX_SIZE], entry->length - FRAME_START_PREFIX_SIZE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Picture data
|
||||||
|
SDL_assert(entry->bufferType == BUFFER_TYPE_PICDATA);
|
||||||
|
memcpy(&data[pictureDataLength],
|
||||||
|
entry->data,
|
||||||
|
entry->length);
|
||||||
|
pictureDataLength += entry->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = entry->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert(pictureDataLength <= du->fullLength);
|
||||||
|
|
||||||
|
if (du->frameType == FRAME_TYPE_IDR) {
|
||||||
|
m_NeedsIdr = false;
|
||||||
|
|
||||||
|
if (m_FormatDesc != nullptr) {
|
||||||
|
CFRelease(m_FormatDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_VideoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
|
const uint8_t* const parameterSetPointers[] = { (uint8_t*)m_SpsData->data(),
|
||||||
|
(uint8_t*)m_PpsData->data() };
|
||||||
|
const size_t parameterSetSizes[] = { (size_t)m_SpsData->length(),
|
||||||
|
(size_t)m_PpsData->length() };
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Constructing new H264 format description");
|
||||||
|
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||||
|
2, /* count of parameter sets */
|
||||||
|
parameterSetPointers,
|
||||||
|
parameterSetSizes,
|
||||||
|
NAL_LENGTH_PREFIX_SIZE,
|
||||||
|
&m_FormatDesc);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create H264 format description: %d",
|
||||||
|
(int)status);
|
||||||
|
m_FormatDesc = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||||
|
const uint8_t* const parameterSetPointers[] = { (uint8_t*)m_VpsData->data(),
|
||||||
|
(uint8_t*)m_SpsData->data(),
|
||||||
|
(uint8_t*)m_PpsData->data() };
|
||||||
|
const size_t parameterSetSizes[] = { (size_t)m_VpsData->length(),
|
||||||
|
(size_t)m_SpsData->length(),
|
||||||
|
(size_t)m_PpsData->length() };
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Constructing new HEVC format description");
|
||||||
|
|
||||||
|
if (__builtin_available(macOS 10.13, *)) {
|
||||||
|
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
|
||||||
|
3, /* count of parameter sets */
|
||||||
|
parameterSetPointers,
|
||||||
|
parameterSetSizes,
|
||||||
|
NAL_LENGTH_PREFIX_SIZE,
|
||||||
|
nil,
|
||||||
|
&m_FormatDesc);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create HEVC format description: %d",
|
||||||
|
(int)status);
|
||||||
|
m_FormatDesc = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// This means Moonlight-common-c decided to give us an HEVC stream
|
||||||
|
// even though we said we couldn't support it. All we can do is abort().
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_FormatDesc == nullptr || m_NeedsIdr) {
|
||||||
|
// Can't decode if we haven't gotten our parameter sets yet
|
||||||
|
free(data);
|
||||||
|
return DR_NEED_IDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we're decoding actual frame data here
|
||||||
|
CMBlockBufferRef blockBuffer;
|
||||||
|
|
||||||
|
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &blockBuffer);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferCreateEmpty failed: %d",
|
||||||
|
(int)status);
|
||||||
|
free(data);
|
||||||
|
return DR_NEED_IDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastOffset = -1;
|
||||||
|
for (int i = 0; i < pictureDataLength - FRAME_START_PREFIX_SIZE; i++) {
|
||||||
|
// Search for a NALU
|
||||||
|
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
|
||||||
|
// It's the start of a new NALU
|
||||||
|
if (lastOffset != -1) {
|
||||||
|
// We've seen a start before this so enqueue that NALU
|
||||||
|
updateBufferForRange(blockBuffer, data, lastOffset, i - lastOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastOffset = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastOffset != -1) {
|
||||||
|
// Enqueue the remaining data
|
||||||
|
updateBufferForRange(blockBuffer, data, lastOffset, pictureDataLength - lastOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From now on, CMBlockBuffer owns the data pointer and will free it when it's released
|
||||||
|
|
||||||
|
CMSampleBufferRef sampleBuffer;
|
||||||
|
|
||||||
|
CMSampleTimingInfo time = {
|
||||||
|
.decodeTimeStamp = kCMTimeInvalid,
|
||||||
|
.duration = kCMTimeInvalid
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_AtomicLock(&m_VsyncStatsLock);
|
||||||
|
// Find a slot to put this frame
|
||||||
|
for (int i = 0; i < FUTURE_FRAMES_IN_VSYNC; i++) {
|
||||||
|
if (m_FrameCountOnVsync[i] == 0) {
|
||||||
|
time.presentationTimeStamp =
|
||||||
|
CMTimeMake(m_VsyncTime + (i + 1) * (m_VsyncTime - m_LastVsyncTime),
|
||||||
|
1000 * 1000 * 1000);
|
||||||
|
m_FrameCountOnVsync[i]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_AtomicUnlock(&m_VsyncStatsLock);
|
||||||
|
|
||||||
|
status = CMSampleBufferCreate(kCFAllocatorDefault,
|
||||||
|
blockBuffer,
|
||||||
|
true, NULL,
|
||||||
|
NULL, m_FormatDesc, 1, 1,
|
||||||
|
&time, 0, NULL,
|
||||||
|
&sampleBuffer);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMSampleBufferCreate failed: %d",
|
||||||
|
(int)status);
|
||||||
|
CFRelease(blockBuffer);
|
||||||
|
return DR_NEED_IDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit for callback on the main thread
|
||||||
|
queueFrame(sampleBuffer, (void*)(long)du->frameType);
|
||||||
|
|
||||||
|
return DR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called on the main thread, so it's safe to interact with
|
||||||
|
// the AVSampleBufferDisplayLayer here.
|
||||||
|
void submitBuffer(CMSampleBufferRef sampleBuffer, int frameType)
|
||||||
|
{
|
||||||
|
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Resetting failed AVSampleBufferDisplay layer");
|
||||||
|
setupDisplayLayer();
|
||||||
|
CFRelease(sampleBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
|
||||||
|
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||||
|
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);
|
||||||
|
|
||||||
|
if (frameType == FRAME_TYPE_PFRAME) {
|
||||||
|
// P-frame
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
|
||||||
|
} else {
|
||||||
|
// I-frame
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanFalse);
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanFalse);
|
||||||
|
}
|
||||||
|
|
||||||
|
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||||
|
|
||||||
|
CFRelease(sampleBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void renderFrame(SDL_UserEvent* event) override
|
||||||
|
{
|
||||||
|
submitBuffer((CMSampleBufferRef)event->data1, (int)(long)event->data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void dropFrame(SDL_UserEvent* event) override
|
||||||
|
{
|
||||||
|
// Submit anyway and let the drop happen in the render pipeline
|
||||||
|
submitBuffer((CMSampleBufferRef)event->data1, (int)(long)event->data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupDisplayLayer()
|
||||||
|
{
|
||||||
|
CALayer* oldLayer = m_DisplayLayer;
|
||||||
|
|
||||||
|
m_DisplayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||||
|
m_DisplayLayer.bounds = m_View.bounds;
|
||||||
|
m_DisplayLayer.position = CGPointMake(CGRectGetMidX(m_View.bounds), CGRectGetMidY(m_View.bounds));
|
||||||
|
m_DisplayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||||
|
|
||||||
|
CALayer* viewLayer = m_View.layer;
|
||||||
|
if (oldLayer != nil) {
|
||||||
|
[viewLayer replaceSublayer:oldLayer with:m_DisplayLayer];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[viewLayer addSublayer:m_DisplayLayer];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for another IDR frame
|
||||||
|
m_NeedsIdr = true;
|
||||||
|
|
||||||
|
delete m_SpsData;
|
||||||
|
delete m_PpsData;
|
||||||
|
delete m_VpsData;
|
||||||
|
|
||||||
|
m_SpsData = m_PpsData = m_VpsData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBufferForRange(CMBlockBufferRef existingBuffer, unsigned char* data, int offset, int nalLength)
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
size_t oldOffset = CMBlockBufferGetDataLength(existingBuffer);
|
||||||
|
|
||||||
|
// If we're at index 1 (first NALU in frame), enqueue this buffer to the memory block
|
||||||
|
// so it can handle freeing it when the block buffer is destroyed
|
||||||
|
if (offset == 1) {
|
||||||
|
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||||
|
|
||||||
|
// Pass the real buffer pointer directly (no offset)
|
||||||
|
// This will give it to the block buffer to free when it's released.
|
||||||
|
// All further calls to CMBlockBufferAppendMemoryBlock will do so
|
||||||
|
// at an offset and will not be asking the buffer to be freed.
|
||||||
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, data,
|
||||||
|
nalLength + 1, // Add 1 for the offset we decremented
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
NULL, 0, nalLength + 1, 0);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferReplaceDataBytes failed: %d",
|
||||||
|
(int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the length prefix to existing buffer
|
||||||
|
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||||
|
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||||
|
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
|
||||||
|
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferReplaceDataBytes failed: %d",
|
||||||
|
(int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Append a 4 byte buffer to this block for the length prefix
|
||||||
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, NULL,
|
||||||
|
NAL_LENGTH_PREFIX_SIZE,
|
||||||
|
kCFAllocatorDefault, NULL, 0,
|
||||||
|
NAL_LENGTH_PREFIX_SIZE, 0);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferAppendMemoryBlock failed: %d",
|
||||||
|
(int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the length prefix to the new buffer
|
||||||
|
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||||
|
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||||
|
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||||
|
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
|
||||||
|
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferReplaceDataBytes failed: %d",
|
||||||
|
(int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the buffer by reference to the block buffer
|
||||||
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, &data[offset+NALU_START_PREFIX_SIZE],
|
||||||
|
dataLength,
|
||||||
|
kCFAllocatorNull, // Don't deallocate data on free
|
||||||
|
NULL, 0, dataLength, 0);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMBlockBufferReplaceDataBytes failed: %d",
|
||||||
|
(int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||||
|
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||||
|
int m_VideoFormat;
|
||||||
|
QByteArray* m_SpsData;
|
||||||
|
QByteArray* m_PpsData;
|
||||||
|
QByteArray* m_VpsData;
|
||||||
|
NSView* m_View;
|
||||||
|
bool m_NeedsIdr;
|
||||||
|
CVDisplayLinkRef m_DisplayLink;
|
||||||
|
uint64_t m_VsyncTime, m_LastVsyncTime;
|
||||||
|
int m_FrameCountOnVsync[FUTURE_FRAMES_IN_VSYNC];
|
||||||
|
SDL_SpinLock m_VsyncStatsLock;
|
||||||
|
};
|
||||||
|
|
||||||
|
IVideoDecoder* AVFoundationDecoderFactory::createDecoder() {
|
||||||
|
return new AVFoundationDecoder();
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
#include <initguid.h>
|
#include <Initguid.h>
|
||||||
#include "dxva2.h"
|
#include "dxva2.h"
|
||||||
#include "../ffmpeg.h"
|
|
||||||
#include <streaming/streamutils.h>
|
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
@@ -10,16 +8,16 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x
|
|||||||
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
||||||
|
|
||||||
DXVA2Renderer::DXVA2Renderer() :
|
DXVA2Renderer::DXVA2Renderer() :
|
||||||
|
m_FrameIndex(0),
|
||||||
|
m_SurfacesUsed(0),
|
||||||
m_SdlRenderer(nullptr),
|
m_SdlRenderer(nullptr),
|
||||||
m_DecService(nullptr),
|
m_DecService(nullptr),
|
||||||
m_Decoder(nullptr),
|
m_Decoder(nullptr),
|
||||||
m_SurfacesUsed(0),
|
|
||||||
m_Pool(nullptr),
|
m_Pool(nullptr),
|
||||||
m_Device(nullptr),
|
m_Device(nullptr),
|
||||||
m_RenderTarget(nullptr),
|
m_RenderTarget(nullptr),
|
||||||
m_ProcService(nullptr),
|
m_ProcService(nullptr),
|
||||||
m_Processor(nullptr),
|
m_Processor(nullptr)
|
||||||
m_FrameIndex(0)
|
|
||||||
{
|
{
|
||||||
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
||||||
RtlZeroMemory(&m_DXVAContext, sizeof(m_DXVAContext));
|
RtlZeroMemory(&m_DXVAContext, sizeof(m_DXVAContext));
|
||||||
@@ -77,8 +75,11 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
|||||||
|
|
||||||
context->hwaccel_context = &m_DXVAContext;
|
context->hwaccel_context = &m_DXVAContext;
|
||||||
|
|
||||||
|
context->get_format = ffGetFormat;
|
||||||
context->get_buffer2 = ffGetBuffer2;
|
context->get_buffer2 = ffGetBuffer2;
|
||||||
|
|
||||||
|
context->opaque = this;
|
||||||
|
|
||||||
m_Pool = av_buffer_pool_init2(ARRAYSIZE(m_DecSurfaces), this, ffPoolAlloc, nullptr);
|
m_Pool = av_buffer_pool_init2(ARRAYSIZE(m_DecSurfaces), this, ffPoolAlloc, nullptr);
|
||||||
if (!m_Pool) {
|
if (!m_Pool) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -86,15 +87,12 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using DXVA2 accelerated renderer");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
||||||
{
|
{
|
||||||
DXVA2Renderer* me = (DXVA2Renderer*)((FFmpegVideoDecoder*)context->opaque)->getRenderer();
|
DXVA2Renderer* me = reinterpret_cast<DXVA2Renderer*>(context->opaque);
|
||||||
|
|
||||||
frame->buf[0] = av_buffer_pool_get(me->m_Pool);
|
frame->buf[0] = av_buffer_pool_get(me->m_Pool);
|
||||||
if (!frame->buf[0]) {
|
if (!frame->buf[0]) {
|
||||||
@@ -103,20 +101,31 @@ int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
|||||||
|
|
||||||
frame->data[3] = frame->buf[0]->data;
|
frame->data[3] = frame->buf[0]->data;
|
||||||
frame->format = AV_PIX_FMT_DXVA2_VLD;
|
frame->format = AV_PIX_FMT_DXVA2_VLD;
|
||||||
frame->width = me->m_VideoWidth;
|
frame->width = me->m_Width;
|
||||||
frame->height = me->m_VideoHeight;
|
frame->height = me->m_Height;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AVPixelFormat DXVA2Renderer::ffGetFormat(AVCodecContext*,
|
||||||
|
const enum AVPixelFormat* pixFmts)
|
||||||
|
{
|
||||||
|
const enum AVPixelFormat *p;
|
||||||
|
|
||||||
|
for (p = pixFmts; *p != -1; p++) {
|
||||||
|
if (*p == AV_PIX_FMT_DXVA2_VLD)
|
||||||
|
return *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to find DXVA2 HW surface format");
|
||||||
|
return AV_PIX_FMT_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
bool DXVA2Renderer::initializeDecoder()
|
bool DXVA2Renderer::initializeDecoder()
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
if (isDecoderBlacklisted()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoDecoderService,
|
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoDecoderService,
|
||||||
reinterpret_cast<void**>(&m_DecService));
|
reinterpret_cast<void**>(&m_DecService));
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
@@ -199,11 +208,18 @@ bool DXVA2Renderer::initializeDecoder()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alignment was already taken care of
|
int alignment;
|
||||||
SDL_assert(m_Desc.SampleWidth % 16 == 0);
|
|
||||||
SDL_assert(m_Desc.SampleHeight % 16 == 0);
|
// HEVC using DXVA requires 128B alignment
|
||||||
hr = m_DecService->CreateSurface(m_Desc.SampleWidth,
|
if (m_VideoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||||
m_Desc.SampleHeight,
|
alignment = 128;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alignment = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = m_DecService->CreateSurface(FFALIGN(m_Width, alignment),
|
||||||
|
FFALIGN(m_Height, alignment),
|
||||||
ARRAYSIZE(m_DecSurfaces) - 1,
|
ARRAYSIZE(m_DecSurfaces) - 1,
|
||||||
m_Desc.Format,
|
m_Desc.Format,
|
||||||
D3DPOOL_DEFAULT,
|
D3DPOOL_DEFAULT,
|
||||||
@@ -246,9 +262,6 @@ bool DXVA2Renderer::initializeRenderer()
|
|||||||
D3DSURFACE_DESC renderTargetDesc;
|
D3DSURFACE_DESC renderTargetDesc;
|
||||||
m_RenderTarget->GetDesc(&renderTargetDesc);
|
m_RenderTarget->GetDesc(&renderTargetDesc);
|
||||||
|
|
||||||
m_DisplayWidth = renderTargetDesc.Width;
|
|
||||||
m_DisplayHeight = renderTargetDesc.Height;
|
|
||||||
|
|
||||||
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoProcessorService,
|
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoProcessorService,
|
||||||
reinterpret_cast<void**>(&m_ProcService));
|
reinterpret_cast<void**>(&m_ProcService));
|
||||||
|
|
||||||
@@ -289,14 +302,6 @@ bool DXVA2Renderer::initializeRenderer()
|
|||||||
caps.VideoProcessorOperations);
|
caps.VideoProcessorOperations);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (!(caps.VideoProcessorOperations & DXVA2_VideoProcess_StretchX) ||
|
|
||||||
!(caps.VideoProcessorOperations & DXVA2_VideoProcess_StretchY)) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Device %d can't stretch video: %x",
|
|
||||||
i,
|
|
||||||
caps.VideoProcessorOperations);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ProcService->GetProcAmpRange(guids[i], &m_Desc, renderTargetDesc.Format, DXVA2_ProcAmp_Brightness, &m_BrightnessRange);
|
m_ProcService->GetProcAmpRange(guids[i], &m_Desc, renderTargetDesc.Format, DXVA2_ProcAmp_Brightness, &m_BrightnessRange);
|
||||||
m_ProcService->GetProcAmpRange(guids[i], &m_Desc, renderTargetDesc.Format, DXVA2_ProcAmp_Contrast, &m_ContrastRange);
|
m_ProcService->GetProcAmpRange(guids[i], &m_Desc, renderTargetDesc.Format, DXVA2_ProcAmp_Contrast, &m_ContrastRange);
|
||||||
@@ -333,117 +338,24 @@ bool DXVA2Renderer::initializeRenderer()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DXVA2Renderer::isDecoderBlacklisted()
|
|
||||||
{
|
|
||||||
IDirect3D9* d3d9;
|
|
||||||
HRESULT hr;
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
// TODO: Update for HEVC Main10
|
|
||||||
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
|
||||||
|
|
||||||
hr = m_Device->GetDirect3D(&d3d9);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
D3DCAPS9 caps;
|
|
||||||
|
|
||||||
hr = m_Device->GetDeviceCaps(&caps);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
D3DADAPTER_IDENTIFIER9 id;
|
|
||||||
|
|
||||||
hr = d3d9->GetAdapterIdentifier(caps.AdapterOrdinal, 0, &id);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Detected GPU: %s (%x:%x)",
|
|
||||||
id.Description,
|
|
||||||
id.VendorId,
|
|
||||||
id.DeviceId);
|
|
||||||
|
|
||||||
if (id.VendorId == 0x8086) {
|
|
||||||
// Intel seems to encode the series in the high byte of
|
|
||||||
// the device ID. We want to avoid the "Partial" acceleration
|
|
||||||
// support explicitly. Those will claim to have HW acceleration
|
|
||||||
// but perform badly.
|
|
||||||
// https://en.wikipedia.org/wiki/Intel_Graphics_Technology#Capabilities_(GPU_video_acceleration)
|
|
||||||
// https://raw.githubusercontent.com/GameTechDev/gpudetect/master/IntelGfx.cfg
|
|
||||||
switch (id.DeviceId & 0xFF00) {
|
|
||||||
case 0x0400: // Haswell
|
|
||||||
case 0x0A00: // Haswell
|
|
||||||
case 0x0D00: // Haswell
|
|
||||||
case 0x1600: // Broadwell
|
|
||||||
case 0x2200: // Cherry Trail and Braswell
|
|
||||||
// Blacklist these for HEVC to avoid hybrid decode
|
|
||||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Everything else is fine with whatever it says it supports
|
|
||||||
result = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (id.VendorId == 0x10DE) {
|
|
||||||
// For NVIDIA, we wait to avoid those GPUs with Feature Set E
|
|
||||||
// for HEVC decoding, since that's hybrid.
|
|
||||||
// https://en.wikipedia.org/wiki/Nvidia_PureVideo
|
|
||||||
// http://envytools.readthedocs.io/en/latest/hw/pciid.html (missing GM200)
|
|
||||||
if ((id.DeviceId >= 0x1340 && id.DeviceId <= 0x137F) || // GM108
|
|
||||||
(id.DeviceId >= 0x1380 && id.DeviceId <= 0x13BF) || // GM107
|
|
||||||
(id.DeviceId >= 0x13C0 && id.DeviceId <= 0x13FF) || // GM204
|
|
||||||
(id.DeviceId >= 0x1617 && id.DeviceId <= 0x161A) || // GM204
|
|
||||||
(id.DeviceId == 0x1667) || // GM204
|
|
||||||
(id.DeviceId >= 0x17C0 && id.DeviceId <= 0x17FF)) { // GM200
|
|
||||||
// Avoid HEVC on Feature Set E GPUs
|
|
||||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (id.VendorId == 0x1002) {
|
|
||||||
// AMD doesn't seem to do hybrid acceleration?
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unrecognized vendor ID: %x",
|
|
||||||
id.VendorId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"GetDeviceCaps() failed: %x", hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
d3d9->Release();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"GetDirect3D() failed: %x", hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"GPU blacklisted for format %x",
|
|
||||||
m_VideoFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height)
|
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height)
|
||||||
{
|
{
|
||||||
m_VideoFormat = videoFormat;
|
m_VideoFormat = videoFormat;
|
||||||
m_VideoWidth = width;
|
m_Width = width;
|
||||||
m_VideoHeight = height;
|
m_Height = height;
|
||||||
|
|
||||||
// FFmpeg will be decoding on different threads than the main thread that we're
|
// FFmpeg will be decoding on different threads than the main thread that we're
|
||||||
// currently running on right now. We must set this hint so SDL will pass
|
// currently running on right now. We must set this hint so SDL will pass
|
||||||
// D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice().
|
// D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice().
|
||||||
SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1");
|
SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1");
|
||||||
|
|
||||||
// We require full-screen desktop mode to avoid having to enable V-sync
|
// We want VSYNC when running in full-screen. DWM will give us
|
||||||
// to synchronize frame delivery (which has a much higher latency penalty).
|
// tear-free video when running composited (windowed) without any
|
||||||
m_SdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
// extra latency waiting for VSYNC.
|
||||||
|
m_SdlRenderer = SDL_CreateRenderer(window, -1,
|
||||||
|
SDL_RENDERER_ACCELERATED |
|
||||||
|
((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) ?
|
||||||
|
SDL_RENDERER_PRESENTVSYNC : 0));
|
||||||
if (!m_SdlRenderer) {
|
if (!m_SdlRenderer) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_CreateRenderer() failed: %s",
|
"SDL_CreateRenderer() failed: %s",
|
||||||
@@ -453,31 +365,15 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
|||||||
|
|
||||||
m_Device = SDL_RenderGetD3D9Device(m_SdlRenderer);
|
m_Device = SDL_RenderGetD3D9Device(m_SdlRenderer);
|
||||||
|
|
||||||
// Draw a black frame until the video stream starts rendering
|
|
||||||
SDL_SetRenderDrawColor(m_SdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
|
||||||
SDL_RenderClear(m_SdlRenderer);
|
|
||||||
SDL_RenderPresent(m_SdlRenderer);
|
|
||||||
|
|
||||||
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
||||||
|
m_Desc.SampleWidth = m_Width;
|
||||||
int alignment;
|
m_Desc.SampleHeight = m_Height;
|
||||||
|
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_ProgressiveChroma;
|
||||||
// HEVC using DXVA requires 128B alignment
|
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
|
||||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_H265) {
|
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
|
||||||
alignment = 128;
|
m_Desc.SampleFormat.VideoLighting = DXVA2_VideoLighting_dim;
|
||||||
}
|
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_BT709;
|
||||||
else {
|
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_709;
|
||||||
alignment = 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Desc.SampleWidth = FFALIGN(m_VideoWidth, alignment);
|
|
||||||
m_Desc.SampleHeight = FFALIGN(m_VideoHeight, alignment);
|
|
||||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Unknown;
|
|
||||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
|
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
|
|
||||||
m_Desc.SampleFormat.VideoLighting = DXVA2_VideoLighting_Unknown;
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
|
||||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||||
|
|
||||||
@@ -497,126 +393,43 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
|||||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
switch (frame->color_range) {
|
hr = m_Device->TestCooperativeLevel();
|
||||||
case AVCOL_RANGE_JPEG:
|
switch (hr) {
|
||||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
|
case D3D_OK:
|
||||||
break;
|
|
||||||
case AVCOL_RANGE_MPEG:
|
|
||||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_16_235;
|
|
||||||
break;
|
break;
|
||||||
|
case D3DERR_DEVICELOST:
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"D3D device lost!");
|
||||||
|
return;
|
||||||
|
case D3DERR_DEVICENOTRESET:
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"D3D device not reset!");
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
break;
|
"Unknown D3D error: %x",
|
||||||
}
|
hr);
|
||||||
|
return;
|
||||||
switch (frame->color_primaries) {
|
|
||||||
case AVCOL_PRI_BT709:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_BT709;
|
|
||||||
break;
|
|
||||||
case AVCOL_PRI_BT470M:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysM;
|
|
||||||
break;
|
|
||||||
case AVCOL_PRI_BT470BG:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysBG;
|
|
||||||
break;
|
|
||||||
case AVCOL_PRI_SMPTE170M:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE170M;
|
|
||||||
break;
|
|
||||||
case AVCOL_PRI_SMPTE240M:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE240M;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (frame->color_trc) {
|
|
||||||
case AVCOL_TRC_SMPTE170M:
|
|
||||||
case AVCOL_TRC_BT709:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_709;
|
|
||||||
break;
|
|
||||||
case AVCOL_TRC_LINEAR:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_10;
|
|
||||||
break;
|
|
||||||
case AVCOL_TRC_GAMMA22:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_22;
|
|
||||||
break;
|
|
||||||
case AVCOL_TRC_GAMMA28:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_28;
|
|
||||||
break;
|
|
||||||
case AVCOL_TRC_SMPTE240M:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_240M;
|
|
||||||
break;
|
|
||||||
case AVCOL_TRC_IEC61966_2_1:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_sRGB;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (frame->colorspace) {
|
|
||||||
case AVCOL_SPC_BT709:
|
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
|
|
||||||
break;
|
|
||||||
case AVCOL_SPC_BT470BG:
|
|
||||||
case AVCOL_SPC_SMPTE170M:
|
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
|
|
||||||
break;
|
|
||||||
case AVCOL_SPC_SMPTE240M:
|
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_SMPTE240M;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (frame->chroma_location) {
|
|
||||||
case AVCHROMA_LOC_LEFT:
|
|
||||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Horizontally_Cosited |
|
|
||||||
DXVA2_VideoChromaSubsampling_Vertically_AlignedChromaPlanes;
|
|
||||||
break;
|
|
||||||
case AVCHROMA_LOC_CENTER:
|
|
||||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Vertically_AlignedChromaPlanes;
|
|
||||||
break;
|
|
||||||
case AVCHROMA_LOC_TOPLEFT:
|
|
||||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Horizontally_Cosited |
|
|
||||||
DXVA2_VideoChromaSubsampling_Vertically_Cosited;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Unknown;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DXVA2_VideoSample sample = {};
|
DXVA2_VideoSample sample = {};
|
||||||
sample.Start = m_FrameIndex;
|
sample.Start = m_FrameIndex;
|
||||||
sample.End = m_FrameIndex + 1;
|
sample.End = m_FrameIndex + 1;
|
||||||
sample.SrcSurface = surface;
|
sample.SrcSurface = surface;
|
||||||
sample.SrcRect.right = m_VideoWidth;
|
sample.SrcRect.right = m_Desc.SampleWidth;
|
||||||
sample.SrcRect.bottom = m_VideoHeight;
|
sample.SrcRect.bottom = m_Desc.SampleHeight;
|
||||||
|
sample.DstRect = sample.SrcRect;
|
||||||
sample.SampleFormat = m_Desc.SampleFormat;
|
sample.SampleFormat = m_Desc.SampleFormat;
|
||||||
sample.PlanarAlpha = DXVA2_Fixed32OpaqueAlpha();
|
sample.PlanarAlpha = DXVA2_Fixed32OpaqueAlpha();
|
||||||
|
|
||||||
// Center in frame and preserve aspect ratio
|
|
||||||
SDL_Rect src, dst;
|
|
||||||
src.x = src.y = 0;
|
|
||||||
src.w = m_VideoWidth;
|
|
||||||
src.h = m_VideoHeight;
|
|
||||||
dst.x = dst.y = 0;
|
|
||||||
dst.w = m_DisplayWidth;
|
|
||||||
dst.h = m_DisplayHeight;
|
|
||||||
|
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
|
||||||
|
|
||||||
sample.DstRect.left = dst.x;
|
|
||||||
sample.DstRect.right = dst.x + dst.w;
|
|
||||||
sample.DstRect.top = dst.y;
|
|
||||||
sample.DstRect.bottom = dst.y + dst.h;
|
|
||||||
|
|
||||||
DXVA2_VideoProcessBltParams bltParams = {};
|
DXVA2_VideoProcessBltParams bltParams = {};
|
||||||
|
|
||||||
bltParams.TargetFrame = m_FrameIndex++;
|
bltParams.TargetFrame = m_FrameIndex++;
|
||||||
bltParams.TargetRect = sample.DstRect;
|
bltParams.TargetRect.right = m_Desc.SampleWidth;
|
||||||
|
bltParams.TargetRect.bottom = m_Desc.SampleHeight;
|
||||||
|
bltParams.BackgroundColor.Y = 0x1000;
|
||||||
|
bltParams.BackgroundColor.Cb = 0x8000;
|
||||||
|
bltParams.BackgroundColor.Cr = 0x8000;
|
||||||
bltParams.BackgroundColor.Alpha = 0xFFFF;
|
bltParams.BackgroundColor.Alpha = 0xFFFF;
|
||||||
|
|
||||||
bltParams.DestFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
bltParams.DestFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||||
@@ -628,33 +441,12 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
|||||||
|
|
||||||
bltParams.Alpha = DXVA2_Fixed32OpaqueAlpha();
|
bltParams.Alpha = DXVA2_Fixed32OpaqueAlpha();
|
||||||
|
|
||||||
if (SDL_RenderClear(m_SdlRenderer) != 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_RenderClear() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
av_frame_free(&frame);
|
|
||||||
|
|
||||||
// We're going to cheat a little bit here. It seems SDL's
|
|
||||||
// renderer may flake out in scenarios like moving the window
|
|
||||||
// between monitors, so generate a synthetic reset event for
|
|
||||||
// the main loop to consume.
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = SDL_RENDER_TARGETS_RESET;
|
|
||||||
SDL_PushEvent(&event);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr);
|
hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr);
|
||||||
av_frame_free(&frame);
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"VideoProcessBlt() failed: %x",
|
"VideoProcessBlt() failed: %x",
|
||||||
hr);
|
hr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must try to present to trigger SDL's logic to recover the render target,
|
m_Device->Present(nullptr, nullptr, nullptr, nullptr);
|
||||||
// even if VideoProcessBlt() fails.
|
|
||||||
SDL_RenderPresent(m_SdlRenderer);
|
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool initializeDecoder();
|
bool initializeDecoder();
|
||||||
bool initializeRenderer();
|
bool initializeRenderer();
|
||||||
bool isDecoderBlacklisted();
|
|
||||||
|
|
||||||
static
|
static
|
||||||
AVBufferRef* ffPoolAlloc(void* opaque, int size);
|
AVBufferRef* ffPoolAlloc(void* opaque, int size);
|
||||||
@@ -35,12 +34,13 @@ private:
|
|||||||
static
|
static
|
||||||
int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags);
|
int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags);
|
||||||
|
|
||||||
int m_VideoFormat;
|
static
|
||||||
int m_VideoWidth;
|
enum AVPixelFormat ffGetFormat(AVCodecContext*,
|
||||||
int m_VideoHeight;
|
const enum AVPixelFormat* pixFmts);
|
||||||
|
|
||||||
int m_DisplayWidth;
|
int m_VideoFormat;
|
||||||
int m_DisplayHeight;
|
int m_Width;
|
||||||
|
int m_Height;
|
||||||
|
|
||||||
SDL_Renderer* m_SdlRenderer;
|
SDL_Renderer* m_SdlRenderer;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
|
||||||
#include <Limelight.h>
|
|
||||||
|
|
||||||
SdlRenderer::SdlRenderer()
|
SdlRenderer::SdlRenderer()
|
||||||
: m_Renderer(nullptr),
|
: m_Renderer(nullptr),
|
||||||
m_Texture(nullptr)
|
m_Texture(nullptr)
|
||||||
@@ -23,10 +21,6 @@ SdlRenderer::~SdlRenderer()
|
|||||||
bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
||||||
{
|
{
|
||||||
/* Nothing to do */
|
/* Nothing to do */
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using SDL software renderer");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +41,6 @@ bool SdlRenderer::initialize(SDL_Window* window,
|
|||||||
// logical rendering surface size is equal to the stream size
|
// logical rendering surface size is equal to the stream size
|
||||||
SDL_RenderSetLogicalSize(m_Renderer, width, height);
|
SDL_RenderSetLogicalSize(m_Renderer, width, height);
|
||||||
|
|
||||||
// Draw a black frame until the video stream starts rendering
|
|
||||||
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
|
||||||
SDL_RenderClear(m_Renderer);
|
|
||||||
SDL_RenderPresent(m_Renderer);
|
|
||||||
|
|
||||||
m_Texture = SDL_CreateTexture(m_Renderer,
|
m_Texture = SDL_CreateTexture(m_Renderer,
|
||||||
SDL_PIXELFORMAT_YV12,
|
SDL_PIXELFORMAT_YV12,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
@@ -76,10 +65,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
frame->linesize[1],
|
frame->linesize[1],
|
||||||
frame->data[2],
|
frame->data[2],
|
||||||
frame->linesize[2]);
|
frame->linesize[2]);
|
||||||
|
|
||||||
// Done with the frame now
|
|
||||||
av_frame_free(&frame);
|
|
||||||
|
|
||||||
SDL_RenderClear(m_Renderer);
|
SDL_RenderClear(m_Renderer);
|
||||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||||
SDL_RenderPresent(m_Renderer);
|
SDL_RenderPresent(m_Renderer);
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
#include "vaapi.h"
|
|
||||||
#include <streaming/streamutils.h>
|
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
|
|
||||||
VAAPIRenderer::VAAPIRenderer()
|
|
||||||
: m_HwContext(nullptr),
|
|
||||||
m_X11VaLibHandle(nullptr),
|
|
||||||
m_vaPutSurface(nullptr)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
VAAPIRenderer::~VAAPIRenderer()
|
|
||||||
{
|
|
||||||
if (m_HwContext != nullptr) {
|
|
||||||
av_buffer_unref(&m_HwContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_X11VaLibHandle != nullptr) {
|
|
||||||
dlclose(m_X11VaLibHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
m_VideoWidth = width;
|
|
||||||
m_VideoHeight = height;
|
|
||||||
|
|
||||||
SDL_GetWindowSize(window, &m_DisplayWidth, &m_DisplayHeight);
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_X11) {
|
|
||||||
m_XWindow = info.info.x11.window;
|
|
||||||
|
|
||||||
m_X11VaLibHandle = dlopen("libva-x11.so", RTLD_LAZY);
|
|
||||||
if (!m_X11VaLibHandle) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"dlopen(libva.so) failed: %s",
|
|
||||||
dlerror());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_vaPutSurface = (vaPutSurface_t)dlsym(m_X11VaLibHandle, "vaPutSurface");
|
|
||||||
}
|
|
||||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VAAPI backend does not currently support Wayland");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unsupported VAAPI rendering subsystem: %d",
|
|
||||||
info.subsystem);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
|
||||||
AV_HWDEVICE_TYPE_VAAPI,
|
|
||||||
nullptr, nullptr, 0);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create VAAPI context: %d",
|
|
||||||
err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This quirk is set for the VDPAU wrapper which doesn't work with our VAAPI renderer
|
|
||||||
if (((AVVAAPIDeviceContext*)((AVHWDeviceContext*)(m_HwContext->data))->hwctx)->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES) {
|
|
||||||
// Fail and let our VDPAU renderer pick this up
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Avoiding VDPAU wrapper for VAAPI decoding");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
|
|
||||||
{
|
|
||||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using VAAPI accelerated renderer");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
VAAPIRenderer::renderFrame(AVFrame* frame)
|
|
||||||
{
|
|
||||||
VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3];
|
|
||||||
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
|
||||||
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
|
||||||
|
|
||||||
SDL_Rect src, dst;
|
|
||||||
src.x = src.y = 0;
|
|
||||||
src.w = m_VideoWidth;
|
|
||||||
src.h = m_VideoHeight;
|
|
||||||
dst.x = dst.y = 0;
|
|
||||||
dst.w = m_DisplayWidth;
|
|
||||||
dst.h = m_DisplayHeight;
|
|
||||||
|
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
|
||||||
|
|
||||||
m_vaPutSurface(vaDeviceContext->display,
|
|
||||||
surface,
|
|
||||||
m_XWindow,
|
|
||||||
0, 0,
|
|
||||||
m_VideoWidth, m_VideoHeight,
|
|
||||||
dst.x, dst.y,
|
|
||||||
dst.w, dst.h,
|
|
||||||
NULL, 0, 0);
|
|
||||||
|
|
||||||
av_frame_free(&frame);
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "renderer.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <va/va.h>
|
|
||||||
#include <va/va_x11.h>
|
|
||||||
#include <libavutil/hwcontext_vaapi.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
class VAAPIRenderer : public IFFmpegRenderer
|
|
||||||
{
|
|
||||||
typedef VAStatus (*vaPutSurface_t)(
|
|
||||||
VADisplay dpy,
|
|
||||||
VASurfaceID surface,
|
|
||||||
Drawable draw, /* X Drawable */
|
|
||||||
short srcx,
|
|
||||||
short srcy,
|
|
||||||
unsigned short srcw,
|
|
||||||
unsigned short srch,
|
|
||||||
short destx,
|
|
||||||
short desty,
|
|
||||||
unsigned short destw,
|
|
||||||
unsigned short desth,
|
|
||||||
VARectangle *cliprects,
|
|
||||||
unsigned int number_cliprects,
|
|
||||||
unsigned int flags
|
|
||||||
);
|
|
||||||
|
|
||||||
public:
|
|
||||||
VAAPIRenderer();
|
|
||||||
virtual ~VAAPIRenderer();
|
|
||||||
virtual bool initialize(SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height);
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
|
||||||
virtual void renderFrame(AVFrame* frame);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Window m_XWindow;
|
|
||||||
AVBufferRef* m_HwContext;
|
|
||||||
void* m_X11VaLibHandle;
|
|
||||||
vaPutSurface_t m_vaPutSurface;
|
|
||||||
int m_VideoWidth;
|
|
||||||
int m_VideoHeight;
|
|
||||||
int m_DisplayWidth;
|
|
||||||
int m_DisplayHeight;
|
|
||||||
};
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
#include "vdpau.h"
|
|
||||||
#include <streaming/streamutils.h>
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
|
|
||||||
#define BAIL_ON_FAIL(status, something) if ((status) != VDP_STATUS_OK) { \
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, \
|
|
||||||
#something " failed: %d", (status)); \
|
|
||||||
return false; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
|
|
||||||
BAIL_ON_FAIL(status, id)
|
|
||||||
|
|
||||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = {
|
|
||||||
VDP_RGBA_FORMAT_B8G8R8A8,
|
|
||||||
VDP_RGBA_FORMAT_R8G8B8A8
|
|
||||||
};
|
|
||||||
|
|
||||||
VDPAURenderer::VDPAURenderer()
|
|
||||||
: m_HwContext(nullptr),
|
|
||||||
m_PresentationQueueTarget(0),
|
|
||||||
m_PresentationQueue(0),
|
|
||||||
m_VideoMixer(0),
|
|
||||||
m_NextSurfaceIndex(0)
|
|
||||||
{
|
|
||||||
SDL_zero(m_OutputSurface);
|
|
||||||
}
|
|
||||||
|
|
||||||
VDPAURenderer::~VDPAURenderer()
|
|
||||||
{
|
|
||||||
if (m_PresentationQueue != 0) {
|
|
||||||
m_VdpPresentationQueueDestroy(m_PresentationQueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_VideoMixer != 0) {
|
|
||||||
m_VdpVideoMixerDestroy(m_VideoMixer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_PresentationQueueTarget != 0) {
|
|
||||||
m_VdpPresentationQueueTargetDestroy(m_PresentationQueueTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) {
|
|
||||||
if (m_OutputSurface[i] != 0) {
|
|
||||||
m_VdpOutputSurfaceDestroy(m_OutputSurface[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This must be done last as it frees VDPAU context required to call
|
|
||||||
// the functions above.
|
|
||||||
if (m_HwContext != nullptr) {
|
|
||||||
av_buffer_unref(&m_HwContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
VdpStatus status;
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
m_VideoWidth = width;
|
|
||||||
m_VideoHeight = height;
|
|
||||||
|
|
||||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
|
||||||
AV_HWDEVICE_TYPE_VDPAU,
|
|
||||||
nullptr, nullptr, 0);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create VDPAU context: %d",
|
|
||||||
err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVHWDeviceContext* devCtx = (AVHWDeviceContext*)m_HwContext->data;
|
|
||||||
AVVDPAUDeviceContext* vdpauCtx = (AVVDPAUDeviceContext*)devCtx->hwctx;
|
|
||||||
m_Device = vdpauCtx->device;
|
|
||||||
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_GET_ERROR_STRING, &m_VdpGetErrorString);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_DESTROY, &m_VdpPresentationQueueTargetDestroy);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_CREATE, &m_VdpVideoMixerCreate);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_DESTROY, &m_VdpVideoMixerDestroy);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_RENDER, &m_VdpVideoMixerRender);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, &m_VdpPresentationQueueCreate);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY, &m_VdpPresentationQueueDestroy);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, &m_VdpPresentationQueueDisplay);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_SET_BACKGROUND_COLOR, &m_VdpPresentationQueueSetBackgroundColor);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_BLOCK_UNTIL_SURFACE_IDLE, &m_VdpPresentationQueueBlockUntilSurfaceIdle);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, &m_VdpOutputSurfaceCreate);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, &m_VdpOutputSurfaceDestroy);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, &m_VdpOutputSurfaceQueryCapabilities);
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &m_VdpVideoSurfaceGetParameters);
|
|
||||||
|
|
||||||
SDL_GetWindowSize(window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight);
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_X11) {
|
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11,
|
|
||||||
&m_VdpPresentationQueueTargetCreateX11);
|
|
||||||
status = m_VdpPresentationQueueTargetCreateX11(m_Device,
|
|
||||||
info.info.x11.window,
|
|
||||||
&m_PresentationQueueTarget);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpPresentationQueueTargetCreateX11() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VDPAU backend does not currently support Wayland");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unsupported VDPAU rendering subsystem: %d",
|
|
||||||
info.subsystem);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try our available output formats to find something the GPU supports
|
|
||||||
bool foundFormat = false;
|
|
||||||
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
|
|
||||||
VdpBool supported;
|
|
||||||
uint32_t maxWidth, maxHeight;
|
|
||||||
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, k_OutputFormats[i],
|
|
||||||
&supported, &maxWidth, &maxHeight);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpOutputSurfaceQueryCapabilities() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supported) {
|
|
||||||
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
|
|
||||||
m_OutputSurfaceFormat = k_OutputFormats[i];
|
|
||||||
foundFormat = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Display size not within capabilities %dx%d vs %dx%d",
|
|
||||||
m_DisplayWidth, m_DisplayWidth,
|
|
||||||
maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundFormat) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"No compatible output surface format found!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the output surfaces
|
|
||||||
for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) {
|
|
||||||
// It seems there's some lazy freeing going on or something in VDPAU
|
|
||||||
// because we can get VDP_STATUS_RESOURCES, then wait a bit and it'll
|
|
||||||
// complete without a problem.
|
|
||||||
int tries = 1;
|
|
||||||
do {
|
|
||||||
status = m_VdpOutputSurfaceCreate(m_Device, m_OutputSurfaceFormat,
|
|
||||||
m_DisplayWidth, m_DisplayHeight,
|
|
||||||
&m_OutputSurface[i]);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpOutputSurfaceCreate() try #%d: %s",
|
|
||||||
tries,
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
SDL_Delay(250);
|
|
||||||
}
|
|
||||||
} while (status == VDP_STATUS_RESOURCES && ++tries <= 10);
|
|
||||||
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpOutputSurfaceCreate() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status = m_VdpPresentationQueueCreate(m_Device, m_PresentationQueueTarget,
|
|
||||||
&m_PresentationQueue);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpPresentationQueueCreate() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the background to opaque black
|
|
||||||
VdpColor color = {0.0, 0.0, 0.0, 1.0};
|
|
||||||
m_VdpPresentationQueueSetBackgroundColor(m_PresentationQueue, &color);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context)
|
|
||||||
{
|
|
||||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
|
||||||
|
|
||||||
// Allow HEVC usage on VDPAU. This was disabled by FFmpeg due to
|
|
||||||
// GL interop issues, but we use VDPAU for rendering so it's no issue.
|
|
||||||
// https://github.com/FFmpeg/FFmpeg/commit/64ecb78b7179cab2dbdf835463104679dbb7c895
|
|
||||||
context->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using VDPAU accelerated renderer");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VDPAURenderer::renderFrame(AVFrame* frame)
|
|
||||||
{
|
|
||||||
VdpStatus status;
|
|
||||||
VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3];
|
|
||||||
|
|
||||||
// This is safe without locking because this is always called on the main thread
|
|
||||||
VdpOutputSurface chosenSurface = m_OutputSurface[m_NextSurfaceIndex];
|
|
||||||
m_NextSurfaceIndex = (m_NextSurfaceIndex + 1) % OUTPUT_SURFACE_COUNT;
|
|
||||||
|
|
||||||
// We need to create the mixer on the fly, because we don't know the dimensions
|
|
||||||
// of our video surfaces in advance of decoding
|
|
||||||
if (m_VideoMixer == 0) {
|
|
||||||
VdpChromaType chroma;
|
|
||||||
uint32_t videoSurfaceWidth, videoSurfaceHeight;
|
|
||||||
status = m_VdpVideoSurfaceGetParameters(videoSurface, &chroma, &videoSurfaceWidth, &videoSurfaceHeight);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpVideoSurfaceGetParameters() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VDPAU surface size: %dx%d",
|
|
||||||
videoSurfaceWidth, videoSurfaceHeight);
|
|
||||||
|
|
||||||
#define PARAM_COUNT 2
|
|
||||||
const VdpVideoMixerParameter params[PARAM_COUNT] = {
|
|
||||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
|
|
||||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
|
|
||||||
};
|
|
||||||
const void* const paramValues[PARAM_COUNT] = {
|
|
||||||
&videoSurfaceWidth,
|
|
||||||
&videoSurfaceHeight,
|
|
||||||
};
|
|
||||||
|
|
||||||
status = m_VdpVideoMixerCreate(m_Device, 0, nullptr,
|
|
||||||
PARAM_COUNT, params, paramValues,
|
|
||||||
&m_VideoMixer);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpVideoMixerCreate() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for this frame to be off the screen
|
|
||||||
VdpTime pts;
|
|
||||||
m_VdpPresentationQueueBlockUntilSurfaceIdle(m_PresentationQueue, chosenSurface, &pts);
|
|
||||||
|
|
||||||
VdpRect sourceRect, outputRect;
|
|
||||||
|
|
||||||
SDL_Rect src, dst;
|
|
||||||
src.x = src.y = 0;
|
|
||||||
src.w = m_VideoWidth;
|
|
||||||
src.h = m_VideoHeight;
|
|
||||||
dst.x = dst.y = 0;
|
|
||||||
dst.w = m_DisplayWidth;
|
|
||||||
dst.h = m_DisplayHeight;
|
|
||||||
|
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
|
||||||
|
|
||||||
outputRect.x0 = dst.x;
|
|
||||||
outputRect.x1 = dst.x + dst.w;
|
|
||||||
outputRect.y0 = dst.y;
|
|
||||||
outputRect.y1 = dst.y + dst.h;
|
|
||||||
|
|
||||||
sourceRect.x0 = sourceRect.y0 = 0;
|
|
||||||
sourceRect.x1 = m_VideoWidth;
|
|
||||||
sourceRect.y1 = m_VideoHeight;
|
|
||||||
|
|
||||||
// Render the next frame into the output surface
|
|
||||||
status = m_VdpVideoMixerRender(m_VideoMixer,
|
|
||||||
VDP_INVALID_HANDLE, nullptr,
|
|
||||||
VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME,
|
|
||||||
0, nullptr,
|
|
||||||
videoSurface,
|
|
||||||
0, nullptr,
|
|
||||||
&sourceRect,
|
|
||||||
chosenSurface,
|
|
||||||
&outputRect,
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
// The decoder can have this surface back now
|
|
||||||
av_frame_free(&frame);
|
|
||||||
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpVideoMixerRender() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue the frame for display immediately
|
|
||||||
status = m_VdpPresentationQueueDisplay(m_PresentationQueue, chosenSurface, 0, 0, 0);
|
|
||||||
if (status != VDP_STATUS_OK) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"VdpPresentationQueueDisplay() failed: %s",
|
|
||||||
m_VdpGetErrorString(status));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "renderer.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <vdpau/vdpau.h>
|
|
||||||
#include <vdpau/vdpau_x11.h>
|
|
||||||
#include <libavutil/hwcontext_vdpau.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
class VDPAURenderer : public IFFmpegRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
VDPAURenderer();
|
|
||||||
virtual ~VDPAURenderer();
|
|
||||||
virtual bool initialize(SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height);
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
|
||||||
virtual void renderFrame(AVFrame* frame);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t m_VideoWidth, m_VideoHeight;
|
|
||||||
uint32_t m_DisplayWidth, m_DisplayHeight;
|
|
||||||
AVBufferRef* m_HwContext;
|
|
||||||
VdpPresentationQueueTarget m_PresentationQueueTarget;
|
|
||||||
VdpPresentationQueue m_PresentationQueue;
|
|
||||||
VdpVideoMixer m_VideoMixer;
|
|
||||||
VdpRGBAFormat m_OutputSurfaceFormat;
|
|
||||||
VdpDevice m_Device;
|
|
||||||
|
|
||||||
#define OUTPUT_SURFACE_COUNT 3
|
|
||||||
VdpOutputSurface m_OutputSurface[OUTPUT_SURFACE_COUNT];
|
|
||||||
int m_NextSurfaceIndex;
|
|
||||||
|
|
||||||
#define OUTPUT_SURFACE_FORMAT_COUNT 2
|
|
||||||
static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT];
|
|
||||||
|
|
||||||
VdpGetErrorString* m_VdpGetErrorString;
|
|
||||||
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
|
|
||||||
VdpVideoMixerCreate* m_VdpVideoMixerCreate;
|
|
||||||
VdpVideoMixerDestroy* m_VdpVideoMixerDestroy;
|
|
||||||
VdpVideoMixerRender* m_VdpVideoMixerRender;
|
|
||||||
VdpPresentationQueueCreate* m_VdpPresentationQueueCreate;
|
|
||||||
VdpPresentationQueueDestroy* m_VdpPresentationQueueDestroy;
|
|
||||||
VdpPresentationQueueDisplay* m_VdpPresentationQueueDisplay;
|
|
||||||
VdpPresentationQueueSetBackgroundColor* m_VdpPresentationQueueSetBackgroundColor;
|
|
||||||
VdpPresentationQueueBlockUntilSurfaceIdle* m_VdpPresentationQueueBlockUntilSurfaceIdle;
|
|
||||||
VdpOutputSurfaceCreate* m_VdpOutputSurfaceCreate;
|
|
||||||
VdpOutputSurfaceDestroy* m_VdpOutputSurfaceDestroy;
|
|
||||||
VdpOutputSurfaceQueryCapabilities* m_VdpOutputSurfaceQueryCapabilities;
|
|
||||||
VdpVideoSurfaceGetParameters* m_VdpVideoSurfaceGetParameters;
|
|
||||||
|
|
||||||
// X11 stuff
|
|
||||||
VdpPresentationQueueTargetCreateX11* m_VdpPresentationQueueTargetCreateX11;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -7,14 +7,9 @@
|
|||||||
#include <SDL_syswm.h>
|
#include <SDL_syswm.h>
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
#include <QQueue>
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import <VideoToolbox/VideoToolbox.h>
|
#import <VideoToolbox/VideoToolbox.h>
|
||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
#import <CoreVideo/CoreVideo.h>
|
|
||||||
|
|
||||||
#define FRAME_HISTORY_ENTRIES 8
|
|
||||||
|
|
||||||
class VTRenderer : public IFFmpegRenderer
|
class VTRenderer : public IFFmpegRenderer
|
||||||
{
|
{
|
||||||
@@ -23,10 +18,9 @@ public:
|
|||||||
: m_HwContext(nullptr),
|
: m_HwContext(nullptr),
|
||||||
m_DisplayLayer(nullptr),
|
m_DisplayLayer(nullptr),
|
||||||
m_FormatDesc(nullptr),
|
m_FormatDesc(nullptr),
|
||||||
m_View(nullptr),
|
m_View(nullptr)
|
||||||
m_DisplayLink(nullptr),
|
|
||||||
m_FrameQueueLock(0)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~VTRenderer()
|
virtual ~VTRenderer()
|
||||||
@@ -38,132 +32,8 @@ public:
|
|||||||
if (m_FormatDesc != nullptr) {
|
if (m_FormatDesc != nullptr) {
|
||||||
CFRelease(m_FormatDesc);
|
CFRelease(m_FormatDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_DisplayLink != nullptr) {
|
|
||||||
CVDisplayLinkStop(m_DisplayLink);
|
|
||||||
CVDisplayLinkRelease(m_DisplayLink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!m_FrameQueue.isEmpty()) {
|
|
||||||
AVFrame* frame = m_FrameQueue.dequeue();
|
|
||||||
av_frame_free(&frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_View != nullptr) {
|
|
||||||
[m_View removeFromSuperview];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawFrame(uint64_t vsyncTime)
|
|
||||||
{
|
|
||||||
OSStatus status;
|
|
||||||
|
|
||||||
SDL_AtomicLock(&m_FrameQueueLock);
|
|
||||||
|
|
||||||
int frameDropTarget;
|
|
||||||
|
|
||||||
// If the queue length history entries are large, be strict
|
|
||||||
// about dropping excess frames.
|
|
||||||
frameDropTarget = 1;
|
|
||||||
for (int i = 0; i < m_FrameQueueHistory.count(); i++) {
|
|
||||||
if (m_FrameQueueHistory[i] <= 1) {
|
|
||||||
// Be lenient as long as the queue length
|
|
||||||
// resolves before the end of frame history
|
|
||||||
frameDropTarget = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
|
||||||
m_FrameQueueHistory.dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
|
||||||
|
|
||||||
// Catch up if we're several frames ahead
|
|
||||||
while (m_FrameQueue.count() > frameDropTarget) {
|
|
||||||
AVFrame* frame = m_FrameQueue.dequeue();
|
|
||||||
av_frame_free(&frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_FrameQueue.isEmpty()) {
|
|
||||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the first frame
|
|
||||||
AVFrame* frame = m_FrameQueue.dequeue();
|
|
||||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
|
||||||
|
|
||||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
|
||||||
|
|
||||||
// If the format has changed or doesn't exist yet, construct it with the
|
|
||||||
// pixel buffer data
|
|
||||||
if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) {
|
|
||||||
if (m_FormatDesc != nullptr) {
|
|
||||||
CFRelease(m_FormatDesc);
|
|
||||||
}
|
|
||||||
status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault,
|
|
||||||
pixBuf, &m_FormatDesc);
|
|
||||||
if (status != noErr) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"CMVideoFormatDescriptionCreateForImageBuffer() failed: %d",
|
|
||||||
status);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue this sample for the next v-sync
|
|
||||||
CMSampleTimingInfo timingInfo = {
|
|
||||||
.duration = kCMTimeInvalid,
|
|
||||||
.decodeTimeStamp = kCMTimeInvalid,
|
|
||||||
.presentationTimeStamp = CMTimeMake(vsyncTime, 1000 * 1000 * 1000)
|
|
||||||
};
|
|
||||||
|
|
||||||
CMSampleBufferRef sampleBuffer;
|
|
||||||
status = CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault,
|
|
||||||
pixBuf,
|
|
||||||
m_FormatDesc,
|
|
||||||
&timingInfo,
|
|
||||||
&sampleBuffer);
|
|
||||||
if (status != noErr) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"CMSampleBufferCreateReadyWithImageBuffer() failed: %d",
|
|
||||||
status);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
|
||||||
|
|
||||||
CFRelease(sampleBuffer);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
CVReturn
|
|
||||||
displayLinkOutputCallback(
|
|
||||||
CVDisplayLinkRef,
|
|
||||||
const CVTimeStamp* now,
|
|
||||||
const CVTimeStamp* /* vsyncTime */,
|
|
||||||
CVOptionFlags,
|
|
||||||
CVOptionFlags*,
|
|
||||||
void *displayLinkContext)
|
|
||||||
{
|
|
||||||
VTRenderer* me = reinterpret_cast<VTRenderer*>(displayLinkContext);
|
|
||||||
|
|
||||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
|
||||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
|
||||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
|
||||||
// draw using the current host time, rather than the actual v-sync target
|
|
||||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
|
||||||
// interval, even if many ms prior, we can safely use the current host time
|
|
||||||
// and get a consistent callback for each v-sync. This reduces video latency
|
|
||||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
|
||||||
me->drawFrame(now->hostTime);
|
|
||||||
|
|
||||||
return kCVReturnSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(SDL_Window* window,
|
||||||
int videoFormat,
|
int videoFormat,
|
||||||
@@ -243,34 +113,60 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
|
||||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
|
||||||
CVDisplayLinkStart(m_DisplayLink);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override
|
virtual bool prepareDecoderContext(AVCodecContext* context) override
|
||||||
{
|
{
|
||||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Using VideoToolbox accelerated renderer");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void renderFrame(AVFrame* frame) override
|
virtual void renderFrame(AVFrame* frame) override
|
||||||
{
|
{
|
||||||
|
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||||
|
OSStatus status;
|
||||||
|
|
||||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Resetting failed AVSampleBufferDisplay layer");
|
"Resetting failed AVSampleBufferDisplay layer");
|
||||||
setupDisplayLayer();
|
setupDisplayLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AtomicLock(&m_FrameQueueLock);
|
// If the format has changed or doesn't exist yet, construct it with the
|
||||||
m_FrameQueue.enqueue(frame);
|
// pixel buffer data
|
||||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) {
|
||||||
|
status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault,
|
||||||
|
pixBuf, &m_FormatDesc);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMVideoFormatDescriptionCreateForImageBuffer() failed: %d",
|
||||||
|
status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CMSampleBufferRef sampleBuffer;
|
||||||
|
status = CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault,
|
||||||
|
pixBuf,
|
||||||
|
m_FormatDesc,
|
||||||
|
&kCMTimingInfoInvalid,
|
||||||
|
&sampleBuffer);
|
||||||
|
if (status != noErr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CMSampleBufferCreateReadyWithImageBuffer() failed: %d",
|
||||||
|
status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
|
||||||
|
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||||
|
|
||||||
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||||
|
|
||||||
|
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||||
|
|
||||||
|
CFRelease(sampleBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -296,10 +192,6 @@ private:
|
|||||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||||
NSView* m_View;
|
NSView* m_View;
|
||||||
CVDisplayLinkRef m_DisplayLink;
|
|
||||||
QQueue<AVFrame*> m_FrameQueue;
|
|
||||||
QQueue<int> m_FrameQueueHistory;
|
|
||||||
SDL_SpinLock m_FrameQueueLock;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||||
|
|||||||
@@ -1,51 +1,87 @@
|
|||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
#include "ffmpeg.h"
|
#include "ffmpeg.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef _WIN32
|
||||||
#include "ffmpeg-renderers/dxva2.h"
|
#include "ffmpeg-renderers/dxva2.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
bool FFmpegVideoDecoder::chooseDecoder(
|
||||||
#include "ffmpeg-renderers/vt.h"
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
#endif
|
SDL_Window* window,
|
||||||
|
int videoFormat,
|
||||||
|
int width, int height,
|
||||||
|
AVCodec*& chosenDecoder,
|
||||||
|
const AVCodecHWConfig*& chosenHwConfig,
|
||||||
|
IFFmpegRenderer*& newRenderer)
|
||||||
|
{
|
||||||
|
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
|
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
|
}
|
||||||
|
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||||
|
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
chosenDecoder = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LIBVA
|
if (!chosenDecoder) {
|
||||||
#include "ffmpeg-renderers/vaapi.h"
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
#endif
|
"Unable to find decoder for format: %x",
|
||||||
|
videoFormat);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LIBVDPAU
|
for (int i = 0;; i++) {
|
||||||
#include "ffmpeg-renderers/vdpau.h"
|
const AVCodecHWConfig *config = avcodec_get_hw_config(chosenDecoder, i);
|
||||||
#endif
|
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||||
|
// No matching hardware acceleration support.
|
||||||
|
// This is not an error.
|
||||||
|
chosenHwConfig = nullptr;
|
||||||
|
newRenderer = new SdlRenderer();
|
||||||
|
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||||
|
newRenderer->initialize(window, videoFormat, width, height)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete newRenderer;
|
||||||
|
newRenderer = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is gross but it allows us to use sizeof()
|
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||||
#include "ffmpeg_videosamples.cpp"
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for acceleration types we support
|
||||||
|
switch (config->device_type) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
case AV_HWDEVICE_TYPE_DXVA2:
|
||||||
|
newRenderer = new DXVA2Renderer();
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRenderer->initialize(window, videoFormat, width, height)) {
|
||||||
|
chosenHwConfig = config;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Failed to initialize
|
||||||
|
delete newRenderer;
|
||||||
|
newRenderer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
||||||
{
|
{
|
||||||
return m_HwDecodeCfg != nullptr;
|
return m_HwDecodeCfg != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
|
||||||
const enum AVPixelFormat* pixFmts)
|
|
||||||
{
|
|
||||||
FFmpegVideoDecoder* decoder = (FFmpegVideoDecoder*)context->opaque;
|
|
||||||
const enum AVPixelFormat *p;
|
|
||||||
|
|
||||||
for (p = pixFmts; *p != -1; p++) {
|
|
||||||
// Only match our hardware decoding codec or SW pixel
|
|
||||||
// format (if not using hardware decoding). It's crucial
|
|
||||||
// to override the default get_format() which will try
|
|
||||||
// to gracefully fall back to software decode and break us.
|
|
||||||
if (*p == (decoder->m_HwDecodeCfg ?
|
|
||||||
decoder->m_HwDecodeCfg->pix_fmt :
|
|
||||||
context->pix_fmt)) {
|
|
||||||
return *p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AV_PIX_FMT_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
FFmpegVideoDecoder::FFmpegVideoDecoder()
|
FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||||
: m_VideoDecoderCtx(nullptr),
|
: m_VideoDecoderCtx(nullptr),
|
||||||
m_DecodeBuffer(1024 * 1024, 0),
|
m_DecodeBuffer(1024 * 1024, 0),
|
||||||
@@ -53,7 +89,6 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
|||||||
m_Renderer(nullptr)
|
m_Renderer(nullptr)
|
||||||
{
|
{
|
||||||
av_init_packet(&m_Pkt);
|
av_init_packet(&m_Pkt);
|
||||||
SDL_AtomicSet(&m_QueuedFrames, 0);
|
|
||||||
|
|
||||||
// Use linear filtering when renderer scaling is required
|
// Use linear filtering when renderer scaling is required
|
||||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
||||||
@@ -61,46 +96,32 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
|||||||
|
|
||||||
FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
||||||
{
|
{
|
||||||
reset();
|
avcodec_close(m_VideoDecoderCtx);
|
||||||
}
|
av_free(m_VideoDecoderCtx);
|
||||||
|
m_VideoDecoderCtx = nullptr;
|
||||||
|
|
||||||
IFFmpegRenderer* FFmpegVideoDecoder::getRenderer()
|
m_HwDecodeCfg = nullptr;
|
||||||
{
|
|
||||||
return m_Renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FFmpegVideoDecoder::reset()
|
|
||||||
{
|
|
||||||
// Drop any frames still queued to ensure
|
|
||||||
// they are properly freed.
|
|
||||||
SDL_Event event;
|
|
||||||
|
|
||||||
while (SDL_AtomicGet(&m_QueuedFrames) > 0) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Waiting for %d frames to return",
|
|
||||||
SDL_AtomicGet(&m_QueuedFrames));
|
|
||||||
|
|
||||||
if (SDL_PeepEvents(&event,
|
|
||||||
1,
|
|
||||||
SDL_GETEVENT,
|
|
||||||
SDL_USEREVENT,
|
|
||||||
SDL_USEREVENT) == 1) {
|
|
||||||
dropFrame(&event.user);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_Delay(100);
|
|
||||||
SDL_PumpEvents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avcodec_free_context(&m_VideoDecoderCtx);
|
|
||||||
|
|
||||||
delete m_Renderer;
|
delete m_Renderer;
|
||||||
m_Renderer = nullptr;
|
m_Renderer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoFormat, int width, int height, bool testOnly)
|
bool FFmpegVideoDecoder::initialize(
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
|
SDL_Window* window,
|
||||||
|
int videoFormat,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int)
|
||||||
{
|
{
|
||||||
|
AVCodec* decoder;
|
||||||
|
|
||||||
|
if (!chooseDecoder(vds, window, videoFormat, width, height,
|
||||||
|
decoder, m_HwDecodeCfg, m_Renderer)) {
|
||||||
|
// Error logged in chooseDecoder()
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
m_VideoDecoderCtx = avcodec_alloc_context3(decoder);
|
m_VideoDecoderCtx = avcodec_alloc_context3(decoder);
|
||||||
if (!m_VideoDecoderCtx) {
|
if (!m_VideoDecoderCtx) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -125,20 +146,12 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoForma
|
|||||||
m_VideoDecoderCtx->width = width;
|
m_VideoDecoderCtx->width = width;
|
||||||
m_VideoDecoderCtx->height = height;
|
m_VideoDecoderCtx->height = height;
|
||||||
m_VideoDecoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // FIXME: HDR
|
m_VideoDecoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // FIXME: HDR
|
||||||
m_VideoDecoderCtx->get_format = ffGetFormat;
|
|
||||||
|
|
||||||
// Allow the renderer to attach data to this decoder
|
// Allow the renderer to attach data to this decoder
|
||||||
if (!m_Renderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
if (!m_Renderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nobody must override our ffGetFormat
|
|
||||||
SDL_assert(m_VideoDecoderCtx->get_format == ffGetFormat);
|
|
||||||
|
|
||||||
// Stash a pointer to this object in the context
|
|
||||||
SDL_assert(m_VideoDecoderCtx->opaque == nullptr);
|
|
||||||
m_VideoDecoderCtx->opaque = this;
|
|
||||||
|
|
||||||
int err = avcodec_open2(m_VideoDecoderCtx, decoder, nullptr);
|
int err = avcodec_open2(m_VideoDecoderCtx, decoder, nullptr);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -147,148 +160,9 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoForma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FFMpeg doesn't completely initialize the codec until the codec
|
|
||||||
// config data comes in. This would be too late for us to change
|
|
||||||
// our minds on the selected video codec, so we'll do a trial run
|
|
||||||
// now to see if things will actually work when the video stream
|
|
||||||
// comes in.
|
|
||||||
if (testOnly) {
|
|
||||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
|
||||||
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
|
||||||
m_Pkt.size = sizeof(k_H264TestFrame);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_Pkt.data = (uint8_t*)k_HEVCTestFrame;
|
|
||||||
m_Pkt.size = sizeof(k_HEVCTestFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
|
||||||
if (err < 0) {
|
|
||||||
char errorstring[512];
|
|
||||||
av_strerror(err, errorstring, sizeof(errorstring));
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Test decode failed: %s", errorstring);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
// Restore default log level before streaming
|
|
||||||
av_log_set_level(AV_LOG_INFO);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFmpegRenderer* FFmpegVideoDecoder::createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg)
|
|
||||||
{
|
|
||||||
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for acceleration types we support
|
|
||||||
switch (hwDecodeCfg->device_type) {
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
case AV_HWDEVICE_TYPE_DXVA2:
|
|
||||||
return new DXVA2Renderer();
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
|
||||||
return VTRendererFactory::createRenderer();
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_LIBVA
|
|
||||||
case AV_HWDEVICE_TYPE_VAAPI:
|
|
||||||
return new VAAPIRenderer();
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_LIBVDPAU
|
|
||||||
case AV_HWDEVICE_TYPE_VDPAU:
|
|
||||||
return new VDPAURenderer();
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::initialize(
|
|
||||||
StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int)
|
|
||||||
{
|
|
||||||
AVCodec* decoder;
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
// Increase log level during initialization
|
|
||||||
av_log_set_level(AV_LOG_DEBUG);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
|
||||||
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
|
||||||
}
|
|
||||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
|
||||||
decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Q_ASSERT(false);
|
|
||||||
decoder = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!decoder) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unable to find decoder for format: %x",
|
|
||||||
videoFormat);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0;; i++) {
|
|
||||||
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
|
||||||
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
|
||||||
// No matching hardware acceleration support.
|
|
||||||
// This is not an error.
|
|
||||||
m_HwDecodeCfg = nullptr;
|
|
||||||
m_Renderer = new SdlRenderer();
|
|
||||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
|
||||||
m_Renderer->initialize(window, videoFormat, width, height) &&
|
|
||||||
completeInitialization(decoder, videoFormat, width, height, false)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Renderer = createAcceleratedRenderer(config);
|
|
||||||
if (!m_Renderer) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_HwDecodeCfg = config;
|
|
||||||
// Submit test frame to ensure this codec really works
|
|
||||||
if (m_Renderer->initialize(window, videoFormat, width, height) &&
|
|
||||||
completeInitialization(decoder, videoFormat, width, height, true)) {
|
|
||||||
// OK, it worked, so now let's initialize it for real
|
|
||||||
reset();
|
|
||||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
|
||||||
m_Renderer->initialize(window, videoFormat, width, height) &&
|
|
||||||
completeInitialization(decoder, videoFormat, width, height, false)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Decoder failed to initialize after successful test");
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Failed to initialize or test frame failed, so keep looking
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||||
{
|
{
|
||||||
PLENTRY entry = du->bufferList;
|
PLENTRY entry = du->bufferList;
|
||||||
@@ -314,6 +188,8 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
|
|
||||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Decoding failed: %d", err);
|
||||||
char errorstring[512];
|
char errorstring[512];
|
||||||
av_strerror(err, errorstring, sizeof(errorstring));
|
av_strerror(err, errorstring, sizeof(errorstring));
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -332,9 +208,14 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
|
|
||||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
// Queue the frame for rendering from the main thread
|
SDL_Event event;
|
||||||
SDL_AtomicIncRef(&m_QueuedFrames);
|
|
||||||
queueFrame(frame);
|
event.type = SDL_USEREVENT;
|
||||||
|
event.user.code = SDL_CODE_FRAME_READY;
|
||||||
|
event.user.data1 = frame;
|
||||||
|
|
||||||
|
// The main thread will handle freeing this
|
||||||
|
SDL_PushEvent(&event);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
@@ -348,7 +229,7 @@ void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
|||||||
{
|
{
|
||||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||||
m_Renderer->renderFrame(frame);
|
m_Renderer->renderFrame(frame);
|
||||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
av_frame_free(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on main thread
|
// Called on main thread
|
||||||
@@ -356,5 +237,4 @@ void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
|
|||||||
{
|
{
|
||||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
|
||||||
}
|
}
|
||||||
@@ -22,27 +22,20 @@ public:
|
|||||||
virtual void renderFrame(SDL_UserEvent* event) override;
|
virtual void renderFrame(SDL_UserEvent* event) override;
|
||||||
virtual void dropFrame(SDL_UserEvent* event) override;
|
virtual void dropFrame(SDL_UserEvent* event) override;
|
||||||
|
|
||||||
virtual IFFmpegRenderer* getRenderer();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool completeInitialization(AVCodec* decoder, int videoFormat,
|
bool
|
||||||
int width, int height, bool testOnly);
|
chooseDecoder(
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
SDL_Window* window,
|
||||||
|
int videoFormat,
|
||||||
void reset();
|
int width, int height,
|
||||||
|
AVCodec*& chosenDecoder,
|
||||||
static
|
const AVCodecHWConfig*& chosenHwConfig,
|
||||||
enum AVPixelFormat ffGetFormat(AVCodecContext* context,
|
IFFmpegRenderer*& newRenderer);
|
||||||
const enum AVPixelFormat* pixFmts);
|
|
||||||
|
|
||||||
AVPacket m_Pkt;
|
AVPacket m_Pkt;
|
||||||
AVCodecContext* m_VideoDecoderCtx;
|
AVCodecContext* m_VideoDecoderCtx;
|
||||||
QByteArray m_DecodeBuffer;
|
QByteArray m_DecodeBuffer;
|
||||||
const AVCodecHWConfig* m_HwDecodeCfg;
|
const AVCodecHWConfig* m_HwDecodeCfg;
|
||||||
IFFmpegRenderer* m_Renderer;
|
IFFmpegRenderer* m_Renderer;
|
||||||
SDL_atomic_t m_QueuedFrames;
|
|
||||||
|
|
||||||
static const uint8_t k_H264TestFrame[];
|
|
||||||
static const uint8_t k_HEVCTestFrame[];
|
|
||||||
};
|
};
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#include "ffmpeg.h"
|
|
||||||
|
|
||||||
// 720p 60 FPS H.264 with 1 reference frame
|
|
||||||
const uint8_t FFmpegVideoDecoder::k_H264TestFrame[] = {
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x20, 0xac, 0x2b, 0x40, 0x28, 0x02, 0xdd, 0x80, 0xb5, 0x06, 0x06, 0x06, 0xa5, 0x00, 0x00, 0x03, 0x03, 0xe8, 0x00, 0x01, 0xd4, 0xc0, 0x8f, 0x4a, 0xa0,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x3c, 0xb0,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x65, 0xb8, 0x02, 0x01, 0x67, 0x25, 0x1b, 0xf4, 0xfa, 0x7d, 0x40, 0x1a, 0x78, 0xe5, 0x10, 0x52, 0xc2, 0xee, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0xc6, 0xef, 0xbb, 0x81, 0x85, 0x2d, 0x47, 0xda, 0xca, 0x4c, 0x00, 0x00, 0x03, 0x00, 0x02, 0x7b, 0xcf, 0x80, 0x00, 0x45, 0x40, 0x01, 0x8d, 0xa6, 0x00, 0x01, 0x64, 0x00, 0x0e, 0x03, 0xc8, 0x00, 0x0e, 0x10, 0x00, 0xbd, 0xc5, 0x00, 0x01, 0x11, 0x00, 0x0e, 0xa3, 0x80, 0x00, 0x38, 0xa0,
|
|
||||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x6e, 0x2e, 0x00, 0x83, 0x7f, 0xb4, 0x8e, 0x79, 0xa5, 0xff, 0x84, 0x3f, 0x7f, 0x34, 0x3f, 0x97, 0x1b, 0xaf, 0x31, 0x5f, 0x6e, 0xaa, 0xb6, 0xac, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x78, 0x36, 0x9d, 0xa4, 0x65, 0xf9, 0x1e, 0x5b, 0x3a, 0xb0, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x41, 0x80, 0x00, 0xc5, 0xc8, 0x00, 0x00, 0xfa, 0x00, 0x03, 0x24, 0x00, 0x0e, 0x20, 0x00, 0x3f, 0x80, 0x01, 0x32, 0x00, 0x08, 0x68, 0x00, 0x3e, 0xc0, 0x03, 0x8a,
|
|
||||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x37, 0x0b, 0x80, 0x21, 0x7f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xae, 0x1a, 0xbb, 0xae, 0x75, 0x9e, 0x35, 0xae, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x03, 0x64, 0x00, 0x09, 0x98, 0x00, 0x1e, 0xc0, 0x00, 0x64, 0x80, 0x01, 0xc4, 0x00, 0x07, 0xf0, 0x00, 0x42, 0xf0, 0x00, 0x00, 0xe1, 0x98, 0x00, 0x05, 0x4b, 0x28, 0x00, 0x06, 0x04,
|
|
||||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
|
|
||||||
};
|
|
||||||
|
|
||||||
// 720p 60 FPS HEVC with 1 reference frame
|
|
||||||
const uint8_t FFmpegVideoDecoder::k_HEVCTestFrame[] = {
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
|
|
||||||
};
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#include "sl.h"
|
|
||||||
|
|
||||||
SLVideoDecoder::SLVideoDecoder()
|
|
||||||
: m_VideoContext(nullptr),
|
|
||||||
m_VideoStream(nullptr)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SLVideoDecoder::~SLVideoDecoder()
|
|
||||||
{
|
|
||||||
if (m_VideoStream != nullptr) {
|
|
||||||
SLVideo_FreeStream(m_VideoStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_VideoContext != nullptr) {
|
|
||||||
SLVideo_FreeContext(m_VideoContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
SLVideoDecoder::isHardwareAccelerated()
|
|
||||||
{
|
|
||||||
// SLVideo is always hardware accelerated
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
SDL_Window*,
|
|
||||||
int videoFormat, int, int, int frameRate)
|
|
||||||
{
|
|
||||||
// SLVideo only supports hardware decoding
|
|
||||||
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SLVideo only supports H.264
|
|
||||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_VideoContext = SLVideo_CreateContext();
|
|
||||||
if (!m_VideoContext) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SLVideo_CreateContext() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a low latency H.264 stream
|
|
||||||
m_VideoStream = SLVideo_CreateStream(m_VideoContext, k_ESLVideoFormatH264, 1);
|
|
||||||
if (!m_VideoStream) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SLVideo_CreateStream() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SLVideo_SetStreamTargetFramerate(m_VideoStream, frameRate, 1);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
SLVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = SLVideo_BeginFrame(m_VideoStream, du->fullLength);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SLVideo_BeginFrame() failed: %d",
|
|
||||||
err);
|
|
||||||
|
|
||||||
// Need an IDR frame to resync
|
|
||||||
return DR_NEED_IDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
PLENTRY entry = du->bufferList;
|
|
||||||
while (entry != nullptr) {
|
|
||||||
err = SLVideo_WriteFrameData(m_VideoStream,
|
|
||||||
entry->data,
|
|
||||||
entry->length);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SLVideo_WriteFrameData() failed: %d",
|
|
||||||
err);
|
|
||||||
|
|
||||||
// Need an IDR frame to resync
|
|
||||||
return DR_NEED_IDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = entry->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SLVideo_SubmitFrame(m_VideoStream);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SLVideo_SubmitFrame() failed: %d",
|
|
||||||
err);
|
|
||||||
|
|
||||||
// Need an IDR frame to resync
|
|
||||||
return DR_NEED_IDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DR_OK;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "decoder.h"
|
|
||||||
|
|
||||||
#include <SLVideo.h>
|
|
||||||
|
|
||||||
class SLVideoDecoder : public IVideoDecoder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SLVideoDecoder();
|
|
||||||
virtual ~SLVideoDecoder();
|
|
||||||
virtual bool initialize(StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int frameRate);
|
|
||||||
virtual bool isHardwareAccelerated();
|
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
|
||||||
|
|
||||||
// Unused since rendering is done directly from the decode thread
|
|
||||||
virtual void renderFrame(SDL_UserEvent*) {}
|
|
||||||
virtual void dropFrame(SDL_UserEvent*) {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CSLVideoContext* m_VideoContext;
|
|
||||||
CSLVideoStream* m_VideoStream;
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define THROW_BAD_ALLOC_IF_NULL(x) \
|
#define THROW_BAD_ALLOC_IF_NULL(x) \
|
||||||
if ((x) == nullptr) throw std::bad_alloc()
|
if ((x) == nullptr) throw new std::bad_alloc()
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
SOURCES = main.cpp
|
|
||||||
LIBS += -lSLVideo
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#include <SLVideo.h>
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
SLVideo_CreateContext();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Versions/Current/Headers
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Versions/Current/Resources
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Versions/Current/SDL2
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>BuildMachineOSBuild</key>
|
|
||||||
<string>17C88</string>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>SDL2</string>
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>http://www.libsdl.org</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>org.libsdl.SDL2</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>Simple DirectMedia Layer</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>FMWK</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>2.0.8</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>SDLX</string>
|
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
|
||||||
<array>
|
|
||||||
<string>MacOSX</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>2.0.8</string>
|
|
||||||
<key>DTCompiler</key>
|
|
||||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
|
||||||
<key>DTPlatformBuild</key>
|
|
||||||
<string>9C40b</string>
|
|
||||||
<key>DTPlatformVersion</key>
|
|
||||||
<string>GM</string>
|
|
||||||
<key>DTSDKBuild</key>
|
|
||||||
<string>17C76</string>
|
|
||||||
<key>DTSDKName</key>
|
|
||||||
<string>macosx10.13</string>
|
|
||||||
<key>DTXcode</key>
|
|
||||||
<string>0920</string>
|
|
||||||
<key>DTXcodeBuild</key>
|
|
||||||
<string>9C40b</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,792 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>files</key>
|
|
||||||
<dict>
|
|
||||||
<key>Resources/Info.plist</key>
|
|
||||||
<data>
|
|
||||||
dts8ape5zBg3+8u8BQ5kWqp9cC8=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>files2</key>
|
|
||||||
<dict>
|
|
||||||
<key>Headers/SDL.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
wiqwnVgtbgjGPAOFejxz8yw7geU=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
LLwzMAHn2OsCr2SVR7M71KeQf+TiJpHk/KKojzQdRlI=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_assert.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
zWoE6/P7ZWAYr4J+EVgDAK+kot8=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
tuS18C7A11akqH9MWT0WWCb6q7kcF97Ui5OFwNtSF/k=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_atomic.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
/q15zvR2djPgTRkySENYqzisn64=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
n9rGA8zXFxasUnN0ytgLLWmW2CDCtKBeGJZcfB+nfRc=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_audio.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
9uJv81fH9Op/f3sF7/GVuNYQNOc=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
KDokTSV3kZuHCQFFrRum3VoTP1QSW2xAghjceoIArXY=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_bits.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
yp8Jj93ZE224v1+u++cOexlp9RY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
kQTBxFYnOjKSlti4H7pAYZWdkH0BpQ9Eq54E/r/nlVY=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_blendmode.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
kgTnzOrp0kqeI8lzDgQyT/gCGiM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
d/8B0iLh4aFeoaPEFqQ4C6x4ISlDJCKrybaKuHCJHL4=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_clipboard.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
qq1r82AS7fHG8G94+jvL41iKEyA=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
+r0LOXHGRf2Q9+FkqRU6i++1zEzsE5sWXxxMGrsSyKk=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_config.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
nJnfliCMDJUfOG8t4QYt5U9tFBY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
qkVvxnI+XRMTIdbzQ9tV+ERRc75r/EzWu0qRTgt5K5c=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_config_macosx.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
D0LXp0FibaaLSE6877hbyqf2Jb8=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
pCQLXwJ6HaGZiG5vr9njJtgNcIV9p0mxcSJtYoj147A=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_copying.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
0DKOkPvRlLo00X7iE2fL4i8XBww=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
nHJ8cj/FeSfLoRm5gRiDk73eyYlKn7u1BfbqkB2ofLg=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_cpuinfo.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
2wleDfJFmCBrEssrE9rcuaiqOLQ=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
MEpF0mFQFLhuTV+5uSB++q8tT2z5heESxyP1HuzuKxc=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_endian.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
WmJrxu+b1MkjtE6ttCymwH4EVC0=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
77hv0QIqlra/8gcQF81aAwP21/ZAANKiIaJRV1QK8mk=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_error.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
HMprigJ3u4PqQ7B+gD1UUbTSrJ8=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
errosoVYCD45UvQVzL4Lmfq4eCQg92a6xUZ062DGEL4=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_events.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
ptgRA0IBsCg5za/lwhTOPGNmekY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
lwISPqzee6nV4HCHaMzZCVJJAtFZmoi8cYlJkY41wKA=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_filesystem.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
9rIKoCtsAONdgfT/4QKm3a56hV0=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
l5nBvMglAIFEWf357KMyQ+E7cr4GqO9XNwevA04+VmQ=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_gamecontroller.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
sUaSBwOprX7A696W6u2oOtIne+Y=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
VZDstHk/V+tZik3q3xnDCAG3AfA+BoHk9lB406mKtxU=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_gesture.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
d80fR6I92r+G26xckkQzTRj/tj4=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
/5+tOQWQ9raopqBCExlH9ClAA/fVAfEBIrd/WlpaqIE=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_haptic.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
95rNclZzbsuV6pyeG8cbIETgS84=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
O4RUmC9HMqsjAf22Ru6z5j9HaYTsc7SxOnzOXPR3kz0=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_hints.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
UjEgXfZiLUmQTh9G+Bkx8Y1Ov6k=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
DqFPmBXNg9BovdIAlipnBKZo5mcQJCrLmhyIM/O+tvU=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_joystick.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
97SJSCx+xjRHUQ6ilndhP8TZ110=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
xYWL+BS5f9NGr7eeP+vL0kD5K6O99PXgOWLT2Bx2dRQ=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_keyboard.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
RsaE9DMLq9lFsYokvdaQ8nhIkfY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
tpZGEHU3TKCozcYf4MA9gA5bXNPSxx9rqqBaPttgr+4=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_keycode.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
9nIqgM67nCJUdjV3iiiviT8DFhk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
SpMclG+epmPQibi3fNsQe1lBUxZPJOOu4DfQjpTZYgw=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_loadso.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
m+OEcfHbykTuEo6P3/y/kuV1xjk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
Yg3OrZpF17uC69TwprPyrpn3+su3YAaaA1hTNXAHxLw=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_log.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
HcUUwymYqVSfgz2LdtuUqZSwjUY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
j4F34jvgNTEcHQQoPbBBugi4RKQLhGM+x4ndXdtDc0E=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_main.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
7I0GPDiLKlavuXuoepYsl2mS6CI=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
qwXROmpXppMPodetUGE0s/7HHmwfu3taLUeiiu+APk0=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_messagebox.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
MEB/oCw7dyinbrBmQQqh46/uRVk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
DSuDS/nAzfEi2kO06DEpxRLj5V1++p2DYYA9Oou8ewQ=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_mouse.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
I2UksqoB5v6RX64Ddwea9dW0sO0=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
gFMH6bBzvJCff8F7FMWlP6o6TydFmiXEu0daWp166wk=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_mutex.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
H6Q1bWPDKu2RsALoLzTBzPTrPkk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
WKU8g7bVD3KpQEgvXuUGrPcG5hgg+LwQBgzLQCeSVTg=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_name.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
doLASBj4oiSiP6MW9wQblnyPN1E=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
M0b8pslMMsWZp1KDVUdqX7ml3DKUVcwiTcfwGbhopvA=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengl.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
ZN9vVzyxx542ZCZ4nmASJKJ7qFM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
dazK6AC8BOZXW9m9761qsuoqe3eJozPIbSc1YEY3UVE=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengl_glext.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
nqPX1ObCVYyVzW3VbD3C/1vaTRE=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
IULBL/m+rBc3UQPqu68w6FDM/Mda58oybFnlyn3OkjE=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
aVEa5N2CHg93ZxdDuKfDtFnCbU8=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
3CmcA5B4ydtkVin3NvNmYVHfgnhJJAVYNVw1YKM0Mko=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles2.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
yQ5nSugeN94hMSNhsIOmlAstJhY=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
8H2lt37zXZs68UfBlqDGqSJTQ2+Slu7qywggJ2mIosI=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles2_gl2.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
g4y04zPFy1H/qujSNGRd0vB7ClQ=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
9GAwK0cg8YPrZHWVqMGpyVwKFz3Ay9VHiyz70jzWltA=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles2_gl2ext.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
bTlnL+42kbi+n/gH//X2p0pqeuM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
5404JOHXH4pGO60gR/uLhz9zW6fKy9jvdMw5i1WTcpo=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles2_gl2platform.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
ByFMXJFtjcRglS/e0+DujyjC3dM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
wlKRTsmTMrnniYqSvQklJfb9VF07J0MDpqzYOcfiDDY=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_opengles2_khrplatform.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
NLpJq9uBqjOpWhXISOQHPnTp/XQ=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
djHeDyzmR0e/2JNIbqpBqgi56MXlxlAbgcBFxuvwSyo=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_pixels.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
q2Uas2PGwUNo30Wiqomvrb03mp4=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
fpczDM4z7VLJW6FMvRTygnypORPdk2q89S1kCbS2fpM=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_platform.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
aM0ptLXVxYx4yXLsNYOssy/jx9Y=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
R6MDdJ2ZUd9FGKReGdJE6YyHwqczb99jK9/IDW59AmI=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_power.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
NqkQCWzmodWGdUFUZmzR9B/TApc=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
VkJkbp+5Wd4WYDemJiu8R/emdH4PZSNPweoPmzRkfxM=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_quit.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
eOEJzybg3UBfe83rKNfky9XAVGM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
mN/T7PjHVtVce+/m7tIWIH8p8lylLWPuJAkCsbEvjhg=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_rect.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
B7oWq3VOxrWbVn0YV8xpvXG6dic=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
dCsZcEfdmcr1RuOycWAtHnnQs5BWk8lw0Ubnv/aUwG4=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_render.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
ATenWUmKg6AEbv2SbCy9JfT8jCw=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
EYiKPX5wRIcWkCyROlT59wuiDupZ6yp0+YsDWOrhtnQ=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_revision.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
XBn2NI8Qm53MeZGaXVB3fQrs0uc=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
FHG6Lqi1wMmjow+JjxKYy/LAoqP+iDH8QYuJS1zr6T4=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_rwops.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
ULSy/1QoNQhBu3kk75qXo30Wcvs=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
KewlZ14FJ6CfLjd7EDc1KlVSiUhIt2P9d3p2VEJuUUA=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_scancode.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
FAcbPIsuPOYbsldZL4m0AFUeoW4=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
5VaZC9IfMuNgz3h6OJn9Q/IGB/+cpymxIrjkluCWzOo=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_shape.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
S5Ty+gKktVyifkoYT0K7E48JqdI=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
LVwSnfZBuKL1bW2zy4rt0vRPmH2/ZxfmP0xB9pLVEOE=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_stdinc.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
qBTB0Yqh+34vD3n3dN9ry9GwqJM=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
TDMriI0PPYF73lvh6suExzpMldCmX1SkLgg+wkBqt7A=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_surface.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
bssTKTIF4Yw/3a6fyF7GhaVrdU0=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
zRF/b8bCqjClZnbPorqjhlWw9aujM9RwnVemHyiC2yM=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_system.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
n2WodCV+WpbfpFSKUUKpB/i5ZPw=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
3W0Z/Fdwbc7dTyR+v4g6eVE58XcEYLTzDGO+RMwcmJc=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_syswm.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
QoebSqgb1XslKPBwMiUOM6daVKo=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
rCqmdvdibhT5MBOnvKCT0gK7Howb5uLewC321yLYr5U=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_thread.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
fXCWQD4MIniqvdB6pWVwlxNS9jk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
18iJ1yFzOTeiM30JwK3GMlbgUqzNDIQiw3ZLLr3M+XQ=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_timer.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
qfzyWhIh36SK6BAcdd50D/MfdGo=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
5Qe92nVkUnHbxgbGwxkyglvVKajhfgVhxPei6dhr4c0=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_touch.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
yyJt6piMAcoFWhFqsHk9mo3MtaI=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
9mQ2V937DAO6OjcQFMboFrnJEiqdk+2uHBzdvMYUnd0=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_types.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
oVg7CzABdDKtxCWINHaUvBRbRZ0=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
8uZym5ly46CZ4VQZCz/Y/cH4THoDS8kGqrvB+Ux/254=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_version.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
nKLee2vSNZZK9p94Up9o87OoiDE=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
F3dpo9XlvebIS2NLJ1ovH+W7fG3bzb01F8OKUeqd+U0=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_video.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
UFZPU1U3CDog3RBKdd5wu9LFQZk=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
6/OCq92BIQnGHBLmlGX4nUNnfLM7LzPoMp+lPlYY4ew=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/SDL_vulkan.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
eLdIezOR2pV1ai5rLBrtiR3NAok=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
iCB8avj2P0+ZkjVGfTchu5BAd7BJ+LeC1fJ9BU+80IE=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/begin_code.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
6RX1yuMtg6N9G2iCtCjjKzPCkuU=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
JQ71zTwAp3EzI22aBP3w80YYEcxNBRF8/0DkqO39oDo=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/close_code.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
IDKa63p9RAIkSpPXA0mU/dzgBWQ=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
oF0I0dcQDvLcuEIgqx9wEQiuqHR5KLoq1zhL/AK5geg=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Resources/Info.plist</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash</key>
|
|
||||||
<data>
|
|
||||||
dts8ape5zBg3+8u8BQ5kWqp9cC8=
|
|
||||||
</data>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
ZCgx1szeVwObsPiq6VoZh8Q9ym7l+7WXzv0HTSoFT0U=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>rules</key>
|
|
||||||
<dict>
|
|
||||||
<key>^Resources/</key>
|
|
||||||
<true/>
|
|
||||||
<key>^Resources/.*\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>optional</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1100</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/Base\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1010</real>
|
|
||||||
</dict>
|
|
||||||
<key>^version.plist$</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>rules2</key>
|
|
||||||
<dict>
|
|
||||||
<key>.*\.dSYM($|/)</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>11</real>
|
|
||||||
</dict>
|
|
||||||
<key>^(.*/)?\.DS_Store$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>2000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
|
|
||||||
<dict>
|
|
||||||
<key>nested</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>10</real>
|
|
||||||
</dict>
|
|
||||||
<key>^.*</key>
|
|
||||||
<true/>
|
|
||||||
<key>^Info\.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^PkgInfo$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/.*\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>optional</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1100</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Resources/Base\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1010</real>
|
|
||||||
</dict>
|
|
||||||
<key>^[^/]+$</key>
|
|
||||||
<dict>
|
|
||||||
<key>nested</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>10</real>
|
|
||||||
</dict>
|
|
||||||
<key>^embedded\.provisionprofile$</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^version\.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
A
|
|
||||||