mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-12 03:24:00 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57137412b5 |
@@ -1,5 +1 @@
|
||||
*pro.user
|
||||
|
||||
|
||||
**/.vs/
|
||||
build/
|
||||
@@ -1,6 +1,9 @@
|
||||
[submodule "moonlight-common-c/moonlight-common-c"]
|
||||
path = moonlight-common-c/moonlight-common-c
|
||||
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"]
|
||||
path = qmdnsengine/qmdnsengine
|
||||
url = https://github.com/cgutman/qmdnsengine.git
|
||||
|
||||
-41
@@ -1,41 +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'
|
||||
packages:
|
||||
- qt59base
|
||||
- qt59quickcontrols2
|
||||
- qt59svg
|
||||
- libesd0-dev
|
||||
- libgl-dev
|
||||
- libopus-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](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).
|
||||
|
||||
This client is currently still in development (pre-alpha), but the streaming performance is already much better than the older Moonlight Chrome client on many machines. Test releases may be available for your OS on the [Releases page](https://github.com/moonlight-stream/moonlight-qt/releases).
|
||||
|
||||
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 90 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
|
||||
This is a work-in-progress port and is only suitable for development use at this point.
|
||||
|
||||
## Building
|
||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download (select MSVC toolchain on Windows)
|
||||
2. 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
|
||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||
3. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||
4. Open the project in Qt Creator or build from qmake on the command line
|
||||
|
||||
## Contribute
|
||||
|
||||
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||
|
||||
1. Fork us
|
||||
2. Write code
|
||||
3. Send Pull Requests
|
||||
|
||||
+6
-10
@@ -3,28 +3,24 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Moonlight</string>
|
||||
<string>moonlight-qt</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Stream games from your NVIDIA GameStream-enabled PC</string>
|
||||
<string>Created by Qt/QMake</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>moonlight</string>
|
||||
<string>macos</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.moonlight-stream.Moonlight</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<string>com.yourcompany.moonlight-qt</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NOTE</key>
|
||||
<string>This file was generated by Qt/QMake.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.4</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
+36
-94
@@ -1,16 +1,7 @@
|
||||
QT += core quick network quickcontrols2 svg
|
||||
QT += core quick network quickcontrols2
|
||||
CONFIG += c++11
|
||||
|
||||
unix:!macx {
|
||||
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
|
||||
|
||||
TARGET = moonlight-qt
|
||||
TEMPLATE = app
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
@@ -37,32 +28,20 @@ win32 {
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib
|
||||
}
|
||||
macx {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include
|
||||
LIBS += -L$$PWD/../libs/mac/lib
|
||||
}
|
||||
|
||||
unix:!macx {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += openssl sdl2 opus
|
||||
LIBS += -ldl
|
||||
|
||||
packagesExist(libavcodec) {
|
||||
PKGCONFIG += libavcodec libavutil
|
||||
CONFIG += ffmpeg
|
||||
|
||||
packagesExist(libva) {
|
||||
CONFIG += libva
|
||||
}
|
||||
}
|
||||
PKGCONFIG += openssl sdl2 libavcodec libavdevice libavformat libavutil
|
||||
}
|
||||
win32 {
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavutil -lopus
|
||||
CONFIG += ffmpeg
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavdevice -lavformat -lavutil
|
||||
}
|
||||
macx {
|
||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
|
||||
CONFIG += ffmpeg
|
||||
LIBS += -lssl -lcrypto -lSDL2 -lavcodec.58 -lavdevice.58 -lavformat.58 -lavutil.56
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework AppKit
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
@@ -76,8 +55,17 @@ SOURCES += \
|
||||
streaming/input.cpp \
|
||||
streaming/session.cpp \
|
||||
streaming/audio.cpp \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
gui/computermodel.cpp \
|
||||
gui/appmodel.cpp
|
||||
gui/appmodel.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdl.cpp
|
||||
|
||||
win32 {
|
||||
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
||||
}
|
||||
macx {
|
||||
SOURCES += streaming/video/avfoundation.mm
|
||||
}
|
||||
|
||||
HEADERS += \
|
||||
utils.h \
|
||||
@@ -91,46 +79,15 @@ HEADERS += \
|
||||
streaming/session.hpp \
|
||||
gui/computermodel.h \
|
||||
gui/appmodel.h \
|
||||
streaming/video/decoder.h
|
||||
streaming/video/decoder.h \
|
||||
streaming/video/ffmpeg.h \
|
||||
streaming/video/ffmpeg-renderers/renderer.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-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
|
||||
}
|
||||
config_SLVideo {
|
||||
message(SLVideo decoder/renderer selected)
|
||||
|
||||
DEFINES += HAVE_SLVIDEO
|
||||
LIBS += -lSLVideo
|
||||
SOURCES += streaming/video/sl.cpp
|
||||
HEADERS += streaming/video/sl.h
|
||||
}
|
||||
win32 {
|
||||
message(DXVA2 renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/dxva2.h
|
||||
}
|
||||
macx {
|
||||
message(VideoToolbox renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/vt.mm
|
||||
HEADERS += streaming/video/ffmpeg-renderers/vt.h
|
||||
HEADERS += streaming/video/avfoundation.h
|
||||
}
|
||||
|
||||
RESOURCES += \
|
||||
@@ -150,6 +107,13 @@ else:unix: LIBS += -L$$OUT_PWD/../moonlight-common-c/ -lmoonlight-common-c
|
||||
INCLUDEPATH += $$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
|
||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/debug/ -lqmdnsengine
|
||||
else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
||||
@@ -157,36 +121,14 @@ else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
||||
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||
|
||||
unix:!macx: {
|
||||
isEmpty(PREFIX) {
|
||||
PREFIX = /usr/local
|
||||
}
|
||||
isEmpty(BINDIR) {
|
||||
BINDIR = bin
|
||||
}
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
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 {
|
||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
||||
APP_QML_FILES.files = res/macos.icns
|
||||
APP_QML_FILES.path = Contents/Resources
|
||||
QMAKE_BUNDLE_DATA += APP_QML_FILES
|
||||
}
|
||||
|
||||
VERSION = 0.0.4
|
||||
|
||||
@@ -140,12 +140,12 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
bool NvComputer::wake()
|
||||
{
|
||||
if (state == NvComputer::CS_ONLINE) {
|
||||
qWarning() << name << "is already online";
|
||||
qWarning() << name << " is already online";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (macAddress.isNull()) {
|
||||
qWarning() << name << "has no MAC address stored";
|
||||
qWarning() << name << " has no MAC address stored";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ bool NvComputer::wake()
|
||||
// Send to all ports
|
||||
for (quint16 port : WOL_PORTS) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -286,56 +286,6 @@ ComputerManager::ComputerManager(QObject *parent)
|
||||
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()
|
||||
{
|
||||
QSettings settings;
|
||||
@@ -362,7 +312,7 @@ void ComputerManager::startPolling()
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||
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);
|
||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||
@@ -381,7 +331,7 @@ void ComputerManager::startPolling()
|
||||
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||
const QHostAddress& address)
|
||||
{
|
||||
qInfo() << "Resolved" << computer->hostname() << "to" << address.toString();
|
||||
qDebug() << "Resolved " << computer->hostname() << " to " << address.toString();
|
||||
|
||||
addNewHost(address.toString(), true);
|
||||
|
||||
|
||||
@@ -148,26 +148,31 @@ private:
|
||||
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
||||
while (!isInterruptionRequested()) {
|
||||
bool stateChanged = false;
|
||||
bool online = false;
|
||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING; i++) {
|
||||
for (auto& address : m_Computer->uniqueAddresses()) {
|
||||
if (isInterruptionRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryPollComputer(address, stateChanged)) {
|
||||
online = true;
|
||||
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
|
||||
// Note: we don't need to acquire the read lock here,
|
||||
// because we're on the writing thread.
|
||||
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||
stateChanged = true;
|
||||
if (m_Computer->state != NvComputer::CS_ONLINE) {
|
||||
if (m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||
stateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
||||
@@ -244,8 +249,6 @@ class ComputerManager : public QObject
|
||||
public:
|
||||
explicit ComputerManager(QObject *parent = nullptr);
|
||||
|
||||
virtual ~ComputerManager();
|
||||
|
||||
Q_INVOKABLE void startPolling();
|
||||
|
||||
Q_INVOKABLE void stopPollingAsync();
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRandomGenerator64>
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#define SER_UNIQUEID "uniqueid"
|
||||
#define SER_CERT "certificate"
|
||||
@@ -101,7 +101,7 @@ void IdentityManager::createCredentials(QSettings& settings)
|
||||
settings.setValue(SER_CERT, m_CachedPemCert);
|
||||
settings.setValue(SER_KEY, m_CachedPrivateKey);
|
||||
|
||||
qInfo() << "Wrote new identity credentials to settings";
|
||||
qDebug() << "Wrote new identity credentials to settings";
|
||||
}
|
||||
|
||||
IdentityManager::IdentityManager()
|
||||
@@ -112,7 +112,7 @@ IdentityManager::IdentityManager()
|
||||
m_CachedPrivateKey = settings.value(SER_KEY).toByteArray();
|
||||
|
||||
if (m_CachedPemCert.isEmpty() || m_CachedPrivateKey.isEmpty()) {
|
||||
qInfo() << "No existing credentials found";
|
||||
qDebug() << "No existing credentials found";
|
||||
createCredentials(settings);
|
||||
}
|
||||
else if (getSslCertificate().isNull()) {
|
||||
@@ -157,14 +157,10 @@ IdentityManager::getSslKey()
|
||||
|
||||
// We must write out our PEM in the old PKCS1 format for SecureTransport
|
||||
// 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;
|
||||
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);
|
||||
|
||||
BIO_free(bio);
|
||||
@@ -191,15 +187,14 @@ IdentityManager::getUniqueId()
|
||||
// Load the unique ID from settings
|
||||
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
||||
if (!m_CachedUniqueId.isEmpty() && !m_CachedUniqueId.isNull()) {
|
||||
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
||||
qDebug() << "Loaded unique ID from settings: " << m_CachedUniqueId;
|
||||
}
|
||||
else {
|
||||
// Generate a new unique ID in base 16
|
||||
uint64_t uid;
|
||||
RAND_bytes(reinterpret_cast<unsigned char*>(&uid), sizeof(uid));
|
||||
m_CachedUniqueId = QString::number(uid, 16);
|
||||
m_CachedUniqueId = QString::number(
|
||||
QRandomGenerator64::securelySeeded().generate64(), 16);
|
||||
|
||||
qInfo() << "Generated new unique ID:" << m_CachedUniqueId;
|
||||
qDebug() << "Generated new unique ID: " << m_CachedUniqueId;
|
||||
|
||||
settings.setValue(SER_UNIQUEID, m_CachedUniqueId);
|
||||
}
|
||||
|
||||
+7
-22
@@ -123,11 +123,6 @@ NvHTTP::launchApp(int appId,
|
||||
bool localAudio,
|
||||
int gamepadMask)
|
||||
{
|
||||
int riKeyId;
|
||||
|
||||
memcpy(&riKeyId, streamConfig->remoteInputAesIv, sizeof(riKeyId));
|
||||
riKeyId = qFromBigEndian(riKeyId);
|
||||
|
||||
QString response =
|
||||
openConnectionToString(m_BaseUrlHttps,
|
||||
"launch",
|
||||
@@ -137,7 +132,7 @@ NvHTTP::launchApp(int appId,
|
||||
QString::number(streamConfig->fps)+
|
||||
"&additionalStates=1&sops="+QString::number(sops ? 1 : 0)+
|
||||
"&rikey="+QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex()+
|
||||
"&rikeyid="+QString::number(riKeyId)+
|
||||
"&rikeyid="+QString::number(qFromBigEndian(*(int*)streamConfig->remoteInputAesIv))+
|
||||
(streamConfig->enableHdr ?
|
||||
"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0" :
|
||||
"")+
|
||||
@@ -154,16 +149,11 @@ NvHTTP::launchApp(int appId,
|
||||
void
|
||||
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
||||
{
|
||||
int riKeyId;
|
||||
|
||||
memcpy(&riKeyId, streamConfig->remoteInputAesIv, sizeof(riKeyId));
|
||||
riKeyId = qFromBigEndian(riKeyId);
|
||||
|
||||
QString response =
|
||||
openConnectionToString(m_BaseUrlHttps,
|
||||
"resume",
|
||||
"rikey="+QString(QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex())+
|
||||
"&rikeyid="+QString::number(riKeyId)+
|
||||
"&rikeyid="+QString::number(qFromBigEndian(*(int*)streamConfig->remoteInputAesIv))+
|
||||
"&surroundAudioInfo="+getSurroundAudioInfoString(streamConfig->audioConfiguration),
|
||||
false);
|
||||
|
||||
@@ -275,10 +265,7 @@ NvHTTP::verifyResponseStatus(QString xml)
|
||||
else
|
||||
{
|
||||
QString statusMessage = xmlReader.attributes().value("status_message").toString();
|
||||
if (statusCode != 401) {
|
||||
// 401 is expected for unpaired PCs when we fetch serverinfo over HTTPS
|
||||
qWarning() << "Request failed:" << statusCode << statusMessage;
|
||||
}
|
||||
qDebug() << "Request failed: " << statusCode << " " << statusMessage;
|
||||
throw GfeHttpResponseException(statusCode, statusMessage);
|
||||
}
|
||||
}
|
||||
@@ -343,9 +330,7 @@ NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout);
|
||||
QString ret;
|
||||
|
||||
QTextStream stream(reply);
|
||||
stream.setCodec("UTF-8");
|
||||
ret = stream.readAll();
|
||||
ret = QTextStream(reply).readAll();
|
||||
delete reply;
|
||||
|
||||
return ret;
|
||||
@@ -381,13 +366,13 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
{
|
||||
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
||||
}
|
||||
qInfo() << "Executing request:" << url.toString();
|
||||
qDebug() << "Executing request: " << url.toString();
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
// Abort the request if it timed out
|
||||
if (!reply->isFinished())
|
||||
{
|
||||
qWarning() << "Aborting timed out request for" << url.toString();
|
||||
qDebug() << "Aborting timed out request for " << url.toString();
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
@@ -398,7 +383,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
// Handle error
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qWarning() << command << " request failed with error " << reply->error();
|
||||
qDebug() << command << " request failed with error " << reply->error();
|
||||
GfeHttpResponseException exception(reply->error(), reply->errorString());
|
||||
delete reply;
|
||||
throw exception;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "nvpairingmanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/rand.h>
|
||||
@@ -19,7 +21,7 @@ NvPairingManager::NvPairingManager(QString address) :
|
||||
BIO_free_all(bio);
|
||||
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();
|
||||
@@ -30,7 +32,7 @@ NvPairingManager::NvPairingManager(QString address) :
|
||||
BIO_free_all(bio);
|
||||
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);
|
||||
BIO_free_all(bio);
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
||||
ASN1_BIT_STRING *asnSignature = cert->signature;
|
||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, cert);
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
ASN1_BIT_STRING *asnSignature;
|
||||
#else
|
||||
const ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, cert);
|
||||
const ASN1_BIT_STRING *asnSignature;
|
||||
#endif
|
||||
|
||||
X509_get0_signature(&asnSignature, NULL, cert);
|
||||
|
||||
QByteArray signature(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
||||
|
||||
X509_free(cert);
|
||||
@@ -164,7 +164,7 @@ NvPairingManager::PairState
|
||||
NvPairingManager::pair(QString appVersion, QString pin)
|
||||
{
|
||||
int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
|
||||
qInfo() << "Pairing with server generation:" << serverMajorVersion;
|
||||
qDebug() << "Pairing with server generation: " << serverMajorVersion;
|
||||
|
||||
QCryptographicHash::Algorithm hashAlgo;
|
||||
int hashLength;
|
||||
@@ -196,14 +196,14 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvHTTP::verifyResponseStatus(getCert);
|
||||
if (NvHTTP::getXmlString(getCert, "paired") != "1")
|
||||
{
|
||||
qCritical() << "Failed pairing at stage #1";
|
||||
qDebug() << "Failed pairing at stage #1";
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
QByteArray serverCert = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
||||
if (serverCert == nullptr)
|
||||
{
|
||||
qCritical() << "Server likely already pairing";
|
||||
qDebug() << "Server likely already pairing";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
return PairState::ALREADY_IN_PROGRESS;
|
||||
}
|
||||
@@ -218,7 +218,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvHTTP::verifyResponseStatus(challengeXml);
|
||||
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);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
@@ -228,15 +228,13 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
QByteArray challengeResponse;
|
||||
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
||||
ASN1_BIT_STRING *asnSignature = m_Cert->signature;
|
||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
||||
#else
|
||||
#else
|
||||
const ASN1_BIT_STRING *asnSignature;
|
||||
#endif
|
||||
|
||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
||||
#endif
|
||||
|
||||
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
|
||||
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
||||
@@ -251,7 +249,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvHTTP::verifyResponseStatus(respXml);
|
||||
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);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
@@ -264,7 +262,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
serverSignature,
|
||||
serverCert))
|
||||
{
|
||||
qCritical() << "MITM detected";
|
||||
qDebug() << "MITM detected";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
@@ -275,7 +273,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
expectedResponseData.append(serverSecret);
|
||||
if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse)
|
||||
{
|
||||
qCritical() << "Incorrect PIN";
|
||||
qDebug() << "Incorrect PIN";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
return PairState::PIN_WRONG;
|
||||
}
|
||||
@@ -292,7 +290,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvHTTP::verifyResponseStatus(secretRespXml);
|
||||
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);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
@@ -304,7 +302,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvHTTP::verifyResponseStatus(pairChallengeXml);
|
||||
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);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
@@ -1,50 +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 or other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
||||
<p>GameStream utilizes the NVENC hardware encoder present on NVIDIA GPUs for low-latency streaming at up 4K 60 FPS. Moonlight can utilize VAAPI hardware acceleration for high-performance H.264 and HEVC video decoding with low CPU usage.</p>
|
||||
</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.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;
|
||||
+3
-4
@@ -12,7 +12,7 @@ GridView {
|
||||
id: appGrid
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 225; cellHeight: 350;
|
||||
@@ -72,12 +72,11 @@ GridView {
|
||||
color: "white"
|
||||
|
||||
width: parent.width
|
||||
height: 125
|
||||
height: 100
|
||||
anchors.top: appIcon.bottom
|
||||
font.pointSize: 22
|
||||
font.pointSize: 26
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
+30
-92
@@ -6,7 +6,6 @@ import QtQuick.Layouts 1.3
|
||||
import ComputerModel 1.0
|
||||
|
||||
import ComputerManager 1.0
|
||||
import StreamingPreferences 1.0
|
||||
|
||||
GridView {
|
||||
property ComputerModel computerModel : createModel()
|
||||
@@ -14,7 +13,7 @@ GridView {
|
||||
id: pcGrid
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 350; cellHeight: 350;
|
||||
@@ -26,17 +25,9 @@ GridView {
|
||||
// routine to run, but only if we start as invisible
|
||||
visible: false
|
||||
|
||||
StreamingPreferences {
|
||||
id: prefs
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Setup signals on CM
|
||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||
|
||||
if (!prefs.hasAnyHardwareAcceleration()) {
|
||||
noHwDecoderDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
@@ -142,68 +133,44 @@ GridView {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: pcContextMenu
|
||||
MenuItem {
|
||||
text: "Wake PC"
|
||||
onTriggered: computerModel.wakeComputer(index)
|
||||
}
|
||||
MenuItem {
|
||||
text: "Delete PC"
|
||||
onTriggered: {
|
||||
deletePcDialog.pcIndex = index
|
||||
// get confirmation first, actual closing is called from the dialog
|
||||
deletePcDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton;
|
||||
onClicked: {
|
||||
if(mouse.button === Qt.LeftButton) {
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView)
|
||||
appView.computerIndex = index
|
||||
appView.objectName = model.name
|
||||
stackView.push(appView)
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView)
|
||||
appView.computerIndex = index
|
||||
appView.objectName = model.name
|
||||
stackView.push(appView)
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Stop polling, since pairing may make GFE unresponsive
|
||||
ComputerManager.stopPollingAsync()
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Stop polling, since pairing may make GFE unresponsive
|
||||
ComputerManager.stopPollingAsync()
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
errorDialog.open()
|
||||
}
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,19 +184,6 @@ GridView {
|
||||
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 {
|
||||
id: pairDialog
|
||||
// don't allow edits to the rest of the window while open
|
||||
@@ -242,22 +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 unpair from this PC?"
|
||||
standardButtons: StandardButton.Yes |StandardButton.No
|
||||
onYes: {
|
||||
console.log("deleting PC pairing for PC at index: " + pcIndex)
|
||||
computerModel.deleteComputer(pcIndex);
|
||||
// hack to remove the child from the gridview
|
||||
model = createModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Dialog {
|
||||
id: addPcDialog
|
||||
property string label: "Enter the IP address of your GameStream PC"
|
||||
|
||||
+116
-256
@@ -16,13 +16,14 @@ ScrollView {
|
||||
}
|
||||
|
||||
Column {
|
||||
padding: 10
|
||||
id: settingsColumn1
|
||||
width: settingsPage.width / 2 - padding
|
||||
x: 10
|
||||
y: 10
|
||||
width: settingsPage.width
|
||||
height: 400
|
||||
|
||||
GroupBox {
|
||||
id: basicSettingsGroupBox
|
||||
width: (parent.width - 2 * parent.padding)
|
||||
width: (parent.width - 20)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Basic Settings</font>"
|
||||
font.pointSize: 12
|
||||
@@ -34,7 +35,7 @@ ScrollView {
|
||||
Label {
|
||||
width: parent.width
|
||||
id: resFPStitle
|
||||
text: qsTr("Resolution and FPS")
|
||||
text: qsTr("Resolution and FPS target")
|
||||
font.pointSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
@@ -43,113 +44,84 @@ ScrollView {
|
||||
Label {
|
||||
width: parent.width
|
||||
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
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 5
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
// 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_height = prefs.height
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < resolutionComboBox.count; i++) {
|
||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
||||
if (saved_width === el_width && saved_height === el_height) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id: resolutionComboBox
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: resolutionListModel
|
||||
ListElement {
|
||||
text: "720p"
|
||||
video_width: "1280"
|
||||
video_height: "720"
|
||||
}
|
||||
ListElement {
|
||||
text: "1080p"
|
||||
video_width: "1920"
|
||||
video_height: "1080"
|
||||
}
|
||||
ListElement {
|
||||
text: "1440p"
|
||||
video_width: "2560"
|
||||
video_height: "1440"
|
||||
}
|
||||
ListElement {
|
||||
text: "4K"
|
||||
video_width: "3840"
|
||||
video_height: "2160"
|
||||
}
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
prefs.height = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
// load the saved width/height/fps, and iterate through the ComboBox until a match is found
|
||||
// set it to that index.
|
||||
var saved_width = prefs.width
|
||||
var saved_height = prefs.height
|
||||
var saved_fps = prefs.fps
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < resolutionComboBox.count; i++) {
|
||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
||||
var el_fps = parseInt(resolutionListModel.get(i).video_fps);
|
||||
if(saved_width === el_width &&
|
||||
saved_height === el_height &&
|
||||
saved_fps === el_fps) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (el_fps === saved_fps) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
id: resolutionComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: resolutionListModel
|
||||
ListElement {
|
||||
text: "720p 30 FPS"
|
||||
video_width: "1280"
|
||||
video_height: "720"
|
||||
video_fps: "30"
|
||||
}
|
||||
|
||||
id: fpsComboBox
|
||||
font.pointSize: 9
|
||||
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
|
||||
ListElement {
|
||||
text: "720p 60 FPS"
|
||||
video_width: "1280"
|
||||
video_height: "720"
|
||||
video_fps: "60"
|
||||
}
|
||||
// ::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
|
||||
ListElement {
|
||||
text: "1080p 30 FPS"
|
||||
video_width: "1920"
|
||||
video_height: "1080"
|
||||
video_fps: "30"
|
||||
}
|
||||
ListElement {
|
||||
text: "1080p 60 FPS"
|
||||
video_width: "1920"
|
||||
video_height: "1080"
|
||||
video_fps: "60"
|
||||
}
|
||||
ListElement {
|
||||
text: "4K 30 FPS"
|
||||
video_width: "3840"
|
||||
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 : {
|
||||
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
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);
|
||||
slider.value = prefs.bitrateKbps
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +151,7 @@ ScrollView {
|
||||
|
||||
stepSize: 500
|
||||
from : 500
|
||||
to: 150000
|
||||
to: 100000
|
||||
|
||||
snapMode: "SnapOnRelease"
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
@@ -205,7 +177,7 @@ ScrollView {
|
||||
GroupBox {
|
||||
|
||||
id: audioSettingsGroupBox
|
||||
width: (parent.width - 2 * parent.padding)
|
||||
width: (parent.width - 20)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Audio Settings</font>"
|
||||
font.pointSize: 12
|
||||
@@ -214,66 +186,24 @@ ScrollView {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
id: resAudioTitle
|
||||
text: qsTr("Audio configuration")
|
||||
font.pointSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
CheckBox {
|
||||
id: surroundSoundCheck
|
||||
text: "<font color=\"white\">Enable 5.1 surround sound</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
// the backend actually supports auto/stereo/5.1. We'll expose stereo/5.1
|
||||
checked: prefs.audioConfig === StreamingPreferences.AC_FORCE_SURROUND
|
||||
|
||||
id: audioComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
font.pointSize: 9
|
||||
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 {
|
||||
id: gamepadSettingsGroupBox
|
||||
width: (parent.width - parent.padding)
|
||||
width: (parent.width - 20)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Gamepad Settings</font>"
|
||||
font.pointSize: 12
|
||||
@@ -291,12 +221,38 @@ ScrollView {
|
||||
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 {
|
||||
id: hostSettingsGroupBox
|
||||
width: (parent.width - parent.padding)
|
||||
width: (parent.width - 20)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Host Settings</font>"
|
||||
font.pointSize: 12
|
||||
@@ -329,7 +285,7 @@ ScrollView {
|
||||
|
||||
GroupBox {
|
||||
id: advancedSettingsGroupBox
|
||||
width: (parent.width - parent.padding)
|
||||
width: (parent.width - 20)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Advanced Settings</font>"
|
||||
font.pointSize: 12
|
||||
@@ -338,107 +294,11 @@ ScrollView {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
id: resVDSTitle
|
||||
text: qsTr("Video decoder")
|
||||
font.pointSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
CheckBox {
|
||||
id: neverDropFramesCheck
|
||||
text: "<font color=\"white\">Never drop frames</font>"
|
||||
font.pointSize: 12
|
||||
}
|
||||
|
||||
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)
|
||||
font.pointSize: 9
|
||||
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)
|
||||
font.pointSize: 9
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-10
@@ -123,15 +123,10 @@ void AppModel::handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl /* image
|
||||
Q_ASSERT(computer == m_Computer);
|
||||
|
||||
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
|
||||
emit dataChanged(createIndex(index, 0),
|
||||
createIndex(index, 0),
|
||||
QVector<int>() << BoxArtRole);
|
||||
}
|
||||
else {
|
||||
qWarning() << "App not found for box art callback:" << app.name;
|
||||
}
|
||||
// Let our view know the box art data has changed for this app
|
||||
emit dataChanged(createIndex(index, 0),
|
||||
createIndex(index, 0),
|
||||
QVector<int>() << BoxArtRole);
|
||||
}
|
||||
|
||||
@@ -80,9 +80,6 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
// TODO: Implement gamepad mapping then unhide this button
|
||||
visible: false
|
||||
|
||||
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||
|
||||
|
||||
+5
-129
@@ -2,8 +2,6 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QIcon>
|
||||
#include <QQuickStyle>
|
||||
#include <QMutex>
|
||||
#include <QtDebug>
|
||||
|
||||
// Don't let SDL hook our main function, since Qt is already
|
||||
// doing the same thing. This needs to be before any headers
|
||||
@@ -16,134 +14,8 @@
|
||||
#include "streaming/session.hpp"
|
||||
#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[])
|
||||
{
|
||||
#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);
|
||||
|
||||
// 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())
|
||||
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();
|
||||
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_Init() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 345 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
+22
-68
@@ -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"
|
||||
id="svg3761"
|
||||
viewBox="0 0 680 680"
|
||||
height="680"
|
||||
width="680"
|
||||
version="1.0">
|
||||
<metadata
|
||||
id="metadata3767">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</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" />
|
||||
<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">
|
||||
<defs>
|
||||
<filter id="f1" y="-40%" width="120%" height="220%">
|
||||
<feOffset xmlns="http://www.w3.org/2000/svg" result="offOut" in="SourceAlpha" dy="-100"/>
|
||||
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="200"/>
|
||||
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- -105, 521 are within 0.5 pixel-perfect -->
|
||||
<!-- offsetting more to show shadow -->
|
||||
<g transform="translate(-95,530) scale(0.100000,-0.100000)" filter="url(#f1)">
|
||||
<circle cx="4755" cy="1505" r="3700" fill="#565C64">
|
||||
</circle>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +1,4 @@
|
||||
#include "streamingpreferences.h"
|
||||
#include "streaming/session.hpp"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
@@ -58,53 +57,24 @@ void StreamingPreferences::save()
|
||||
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 90 FPS, since I can't seem to get
|
||||
// much more out of GFE even with the game rendering at > 300 FPS.
|
||||
maxFrameRate = qMax(maxFrameRate, qMin(90, mode.refresh_rate));
|
||||
}
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
return maxFrameRate;
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
{
|
||||
if (width * height <= 1280 * 720) {
|
||||
return static_cast<int>(5000 * (fps / 30.0));
|
||||
if (width * height * fps <= 1280 * 720 * 30) {
|
||||
return 5000;
|
||||
}
|
||||
else if (width * height <= 1920 * 1080) {
|
||||
return static_cast<int>(10000 * (fps / 30.0));
|
||||
else if (width * height * fps <= 1280 * 720 * 60) {
|
||||
return 10000;
|
||||
}
|
||||
else if (width * height <= 2560 * 1440) {
|
||||
return static_cast<int>(20000 * (fps / 30.0));
|
||||
else if (width * height * fps <= 1920 * 1080 * 30) {
|
||||
return 10000;
|
||||
}
|
||||
else /* if (width * height <= 3840 * 2160) */ {
|
||||
return static_cast<int>(40000 * (fps / 30.0));
|
||||
else if (width * height * fps <= 1920 * 1080 * 60) {
|
||||
return 20000;
|
||||
}
|
||||
else if (width * height * fps <= 3840 * 2160 * 30) {
|
||||
return 40000;
|
||||
}
|
||||
else /* if (width * height * fps <= 3840 * 2160 * 60) */ {
|
||||
return 80000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ public:
|
||||
|
||||
Q_INVOKABLE void save();
|
||||
|
||||
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
||||
|
||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
||||
|
||||
void reload();
|
||||
|
||||
enum AudioConfig
|
||||
|
||||
+5
-35
@@ -23,14 +23,6 @@ int Session::sdlDetermineAudioConfiguration()
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
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);
|
||||
want.freq = 48000;
|
||||
@@ -46,8 +38,7 @@ int Session::sdlDetermineAudioConfiguration()
|
||||
"Failed to open audio device");
|
||||
// We'll probably have issues during audio stream init, but we'll
|
||||
// try anyway
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
goto Exit;
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
@@ -59,15 +50,11 @@ int Session::sdlDetermineAudioConfiguration()
|
||||
// We don't support quadraphonic or 7.1 surround, but SDL
|
||||
// should be able to downmix or upmix better from 5.1 than
|
||||
// from stereo, so use 5.1 in non-stereo cases.
|
||||
ret = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
Exit:
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
@@ -77,14 +64,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
SDL_AudioSpec want, have;
|
||||
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);
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
@@ -101,7 +80,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -117,7 +95,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
error);
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -2;
|
||||
}
|
||||
|
||||
@@ -127,7 +104,7 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
s_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
#ifdef _WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
s_BaselinePendingData = qMax(s_BaselinePendingData, SDL_GetQueuedAudioSize(s_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
@@ -142,10 +119,6 @@ int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
s_SampleIndex = 0;
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
opusConfig->channelCount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -167,10 +140,7 @@ void Session::sdlAudioCleanup()
|
||||
s_AudioDevice = 0;
|
||||
|
||||
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||
s_OpusDecoder = nullptr;
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
s_OpusDecoder = NULL;
|
||||
}
|
||||
|
||||
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
|
||||
+92
-103
@@ -9,7 +9,7 @@
|
||||
#ifndef VK_F1
|
||||
#define VK_F1 0x70
|
||||
#define VK_F13 0x7C
|
||||
#define VK_NUMPAD0 0x60
|
||||
#define VK_NUMPAD1 0x61
|
||||
#endif
|
||||
|
||||
const int SdlInputHandler::k_ButtonMap[] = {
|
||||
@@ -23,15 +23,6 @@ const int SdlInputHandler::k_ButtonMap[] = {
|
||||
SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
: m_MultiController(multiController)
|
||||
{
|
||||
// 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) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
m_GamepadMask = 0x1;
|
||||
@@ -47,9 +38,6 @@ SdlInputHandler::~SdlInputHandler()
|
||||
SDL_GameControllerClose(m_GamepadState[i].controller);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||
}
|
||||
|
||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
@@ -63,26 +51,26 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
(event->keysym.mod & KMOD_ALT) &&
|
||||
(event->keysym.mod & KMOD_SHIFT)) {
|
||||
|
||||
// Force raise all keys in the combo to avoid
|
||||
// leaving them down after disconnecting
|
||||
LiSendKeyboardEvent(0xA0, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA1, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA2, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA3, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA4, 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);
|
||||
// Force raise all keys in the combo to avoid
|
||||
// leaving them down after disconnecting
|
||||
LiSendKeyboardEvent(0xA0, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA1, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA2, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA3, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA4, KEY_ACTION_UP, 0);
|
||||
LiSendKeyboardEvent(0xA5, KEY_ACTION_UP, 0);
|
||||
|
||||
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
|
||||
event.type = SDL_QUIT;
|
||||
event.quit.timestamp = SDL_GetTicks();
|
||||
@@ -100,7 +88,12 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
else if (event->keysym.sym == SDLK_x) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"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;
|
||||
}
|
||||
}
|
||||
@@ -122,199 +115,195 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
modifiers |= MODIFIER_SHIFT;
|
||||
}
|
||||
|
||||
// Set keycode. We explicitly use scancode here because GFE will try to correct
|
||||
// for AZERTY layouts on the host but it depends on receiving VK_ values matching
|
||||
// a QWERTY layout to work.
|
||||
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;
|
||||
// Set keycode
|
||||
if (event->keysym.sym >= SDLK_0 && event->keysym.sym <= SDLK_9) {
|
||||
keyCode = (event->keysym.sym - SDLK_0) + VK_0;
|
||||
}
|
||||
else if (event->keysym.scancode >= SDL_SCANCODE_A && event->keysym.scancode <= SDL_SCANCODE_Z) {
|
||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_A) + VK_A;
|
||||
else if (event->keysym.sym >= SDLK_a && event->keysym.sym <= SDLK_z) {
|
||||
keyCode = (event->keysym.sym - SDLK_a) + VK_A;
|
||||
}
|
||||
else if (event->keysym.scancode >= SDL_SCANCODE_F1 && event->keysym.scancode <= SDL_SCANCODE_F12) {
|
||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_F1) + VK_F1;
|
||||
else if (event->keysym.sym >= SDLK_F1 && event->keysym.sym <= SDLK_F12) {
|
||||
keyCode = (event->keysym.sym - SDLK_F1) + VK_F1;
|
||||
}
|
||||
else if (event->keysym.scancode >= SDL_SCANCODE_F13 && event->keysym.scancode <= SDL_SCANCODE_F24) {
|
||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_F13) + VK_F13;
|
||||
else if (event->keysym.sym >= SDLK_F13 && event->keysym.sym <= SDLK_F24) {
|
||||
keyCode = (event->keysym.sym - SDLK_F13) + VK_F13;
|
||||
}
|
||||
else if (event->keysym.scancode >= SDL_SCANCODE_KP_1 && event->keysym.scancode <= SDL_SCANCODE_KP_9) {
|
||||
// SDL defines SDL_SCANCODE_KP_0 > SDL_SCANCODE_KP_9, so we need to handle that manually
|
||||
keyCode = (event->keysym.scancode - SDL_SCANCODE_KP_1) + VK_NUMPAD0 + 1;
|
||||
else if (event->keysym.sym >= SDLK_KP_1 && event->keysym.sym <= SDLK_KP_9) {
|
||||
// SDL defines SDLK_KP_0 > SDLK_KP_9, so we need to handle that manually
|
||||
keyCode = (event->keysym.sym - SDLK_KP_1) + VK_NUMPAD1;
|
||||
}
|
||||
else {
|
||||
switch (event->keysym.scancode) {
|
||||
case SDL_SCANCODE_BACKSPACE:
|
||||
switch (event->keysym.sym) {
|
||||
case SDLK_BACKSPACE:
|
||||
keyCode = 0x08;
|
||||
break;
|
||||
case SDL_SCANCODE_TAB:
|
||||
case SDLK_TAB:
|
||||
keyCode = 0x09;
|
||||
break;
|
||||
case SDL_SCANCODE_CLEAR:
|
||||
case SDLK_CLEAR:
|
||||
keyCode = 0x0C;
|
||||
break;
|
||||
case SDL_SCANCODE_RETURN:
|
||||
case SDLK_RETURN:
|
||||
keyCode = 0x0D;
|
||||
break;
|
||||
case SDL_SCANCODE_PAUSE:
|
||||
case SDLK_PAUSE:
|
||||
keyCode = 0x13;
|
||||
break;
|
||||
case SDL_SCANCODE_CAPSLOCK:
|
||||
case SDLK_CAPSLOCK:
|
||||
keyCode = 0x14;
|
||||
break;
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
case SDLK_ESCAPE:
|
||||
keyCode = 0x1B;
|
||||
break;
|
||||
case SDL_SCANCODE_SPACE:
|
||||
case SDLK_SPACE:
|
||||
keyCode = 0x20;
|
||||
break;
|
||||
case SDL_SCANCODE_PAGEUP:
|
||||
case SDLK_PAGEUP:
|
||||
keyCode = 0x21;
|
||||
break;
|
||||
case SDL_SCANCODE_PAGEDOWN:
|
||||
case SDLK_PAGEDOWN:
|
||||
keyCode = 0x22;
|
||||
break;
|
||||
case SDL_SCANCODE_END:
|
||||
case SDLK_END:
|
||||
keyCode = 0x23;
|
||||
break;
|
||||
case SDL_SCANCODE_HOME:
|
||||
case SDLK_HOME:
|
||||
keyCode = 0x24;
|
||||
break;
|
||||
case SDL_SCANCODE_LEFT:
|
||||
case SDLK_LEFT:
|
||||
keyCode = 0x25;
|
||||
break;
|
||||
case SDL_SCANCODE_UP:
|
||||
case SDLK_UP:
|
||||
keyCode = 0x26;
|
||||
break;
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
case SDLK_RIGHT:
|
||||
keyCode = 0x27;
|
||||
break;
|
||||
case SDL_SCANCODE_DOWN:
|
||||
case SDLK_DOWN:
|
||||
keyCode = 0x28;
|
||||
break;
|
||||
case SDL_SCANCODE_SELECT:
|
||||
case SDLK_SELECT:
|
||||
keyCode = 0x29;
|
||||
break;
|
||||
case SDL_SCANCODE_EXECUTE:
|
||||
case SDLK_EXECUTE:
|
||||
keyCode = 0x2B;
|
||||
break;
|
||||
case SDL_SCANCODE_PRINTSCREEN:
|
||||
case SDLK_PRINTSCREEN:
|
||||
keyCode = 0x2C;
|
||||
break;
|
||||
case SDL_SCANCODE_INSERT:
|
||||
case SDLK_INSERT:
|
||||
keyCode = 0x2D;
|
||||
break;
|
||||
case SDL_SCANCODE_DELETE:
|
||||
case SDLK_DELETE:
|
||||
keyCode = 0x2E;
|
||||
break;
|
||||
case SDL_SCANCODE_HELP:
|
||||
case SDLK_HELP:
|
||||
keyCode = 0x2F;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_0:
|
||||
// See comment above about why we only handle SDL_SCANCODE_KP_0 here
|
||||
keyCode = VK_NUMPAD0;
|
||||
case SDLK_KP_0:
|
||||
// See comment above about why we only handle KP_0 here
|
||||
keyCode = 0x60;
|
||||
break;
|
||||
case SDL_SCANCODE_0:
|
||||
// See comment above about why we only handle SDL_SCANCODE_0 here
|
||||
keyCode = VK_0;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_MULTIPLY:
|
||||
case SDLK_KP_MULTIPLY:
|
||||
keyCode = 0x6A;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_PLUS:
|
||||
case SDLK_KP_PLUS:
|
||||
keyCode = 0x6B;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_COMMA:
|
||||
case SDLK_KP_COMMA:
|
||||
keyCode = 0x6C;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_MINUS:
|
||||
case SDLK_KP_MINUS:
|
||||
keyCode = 0x6D;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_DECIMAL:
|
||||
case SDLK_KP_DECIMAL:
|
||||
keyCode = 0x6E;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_DIVIDE:
|
||||
case SDLK_KP_DIVIDE:
|
||||
keyCode = 0x6F;
|
||||
break;
|
||||
case SDL_SCANCODE_NUMLOCKCLEAR:
|
||||
case SDLK_NUMLOCKCLEAR:
|
||||
keyCode = 0x90;
|
||||
break;
|
||||
case SDL_SCANCODE_SCROLLLOCK:
|
||||
case SDLK_SCROLLLOCK:
|
||||
keyCode = 0x91;
|
||||
break;
|
||||
case SDL_SCANCODE_LSHIFT:
|
||||
case SDLK_LSHIFT:
|
||||
keyCode = 0xA0;
|
||||
break;
|
||||
case SDL_SCANCODE_RSHIFT:
|
||||
case SDLK_RSHIFT:
|
||||
keyCode = 0xA1;
|
||||
break;
|
||||
case SDL_SCANCODE_LCTRL:
|
||||
case SDLK_LCTRL:
|
||||
keyCode = 0xA2;
|
||||
break;
|
||||
case SDL_SCANCODE_RCTRL:
|
||||
case SDLK_RCTRL:
|
||||
keyCode = 0xA3;
|
||||
break;
|
||||
case SDL_SCANCODE_LALT:
|
||||
case SDLK_LALT:
|
||||
keyCode = 0xA4;
|
||||
break;
|
||||
case SDL_SCANCODE_RALT:
|
||||
case SDLK_RALT:
|
||||
keyCode = 0xA5;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_BACK:
|
||||
case SDLK_AC_BACK:
|
||||
keyCode = 0xA6;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_FORWARD:
|
||||
case SDLK_AC_FORWARD:
|
||||
keyCode = 0xA7;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_REFRESH:
|
||||
case SDLK_AC_REFRESH:
|
||||
keyCode = 0xA8;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_STOP:
|
||||
case SDLK_AC_STOP:
|
||||
keyCode = 0xA9;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_SEARCH:
|
||||
case SDLK_AC_SEARCH:
|
||||
keyCode = 0xAA;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_BOOKMARKS:
|
||||
case SDLK_AC_BOOKMARKS:
|
||||
keyCode = 0xAB;
|
||||
break;
|
||||
case SDL_SCANCODE_AC_HOME:
|
||||
case SDLK_AC_HOME:
|
||||
keyCode = 0xAC;
|
||||
break;
|
||||
case SDL_SCANCODE_SEMICOLON:
|
||||
case SDLK_SEMICOLON:
|
||||
keyCode = 0xBA;
|
||||
break;
|
||||
case SDL_SCANCODE_COMMA:
|
||||
case SDLK_PLUS:
|
||||
keyCode = 0xBB;
|
||||
break;
|
||||
case SDLK_COMMA:
|
||||
keyCode = 0xBC;
|
||||
break;
|
||||
case SDL_SCANCODE_MINUS:
|
||||
case SDLK_MINUS:
|
||||
keyCode = 0xBD;
|
||||
break;
|
||||
case SDL_SCANCODE_PERIOD:
|
||||
case SDLK_PERIOD:
|
||||
keyCode = 0xBE;
|
||||
break;
|
||||
case SDL_SCANCODE_SLASH:
|
||||
case SDLK_SLASH:
|
||||
keyCode = 0xBF;
|
||||
break;
|
||||
case SDL_SCANCODE_GRAVE:
|
||||
case SDLK_BACKQUOTE:
|
||||
keyCode = 0xC0;
|
||||
break;
|
||||
case SDL_SCANCODE_LEFTBRACKET:
|
||||
case SDLK_LEFTBRACKET:
|
||||
keyCode = 0xDB;
|
||||
break;
|
||||
case SDL_SCANCODE_BACKSLASH:
|
||||
case SDLK_BACKSLASH:
|
||||
keyCode = 0xDC;
|
||||
break;
|
||||
case SDL_SCANCODE_RIGHTBRACKET:
|
||||
case SDLK_RIGHTBRACKET:
|
||||
keyCode = 0xDD;
|
||||
break;
|
||||
case SDL_SCANCODE_APOSTROPHE:
|
||||
case SDLK_QUOTE:
|
||||
keyCode = 0xDE;
|
||||
break;
|
||||
default:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unhandled button event: %d",
|
||||
event->keysym.scancode);
|
||||
event->keysym.sym);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+99
-301
@@ -5,38 +5,16 @@
|
||||
#include <SDL.h>
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#ifdef __APPLE__
|
||||
#include "video/avfoundation.h"
|
||||
#endif
|
||||
|
||||
#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 <QCoreApplication>
|
||||
#include <QThreadPool>
|
||||
#include <QSvgRenderer>
|
||||
#include <QPainter>
|
||||
#include <QImage>
|
||||
|
||||
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||
Session::clStageStarting,
|
||||
@@ -108,22 +86,21 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, IVideoDecoder*& chosenDecoder)
|
||||
{
|
||||
#ifdef HAVE_SLVIDEO
|
||||
chosenDecoder = new SLVideoDecoder();
|
||||
#ifdef __APPLE__
|
||||
chosenDecoder = AVFoundationDecoderFactory::createDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SLVideo video decoder chosen");
|
||||
"AVFoundation-based video decoder chosen");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to load SLVideo decoder");
|
||||
"Unable to load AVFoundation decoder");
|
||||
delete chosenDecoder;
|
||||
chosenDecoder = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
chosenDecoder = new FFmpegVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -136,7 +113,6 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
delete chosenDecoder;
|
||||
chosenDecoder = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If we reach this, we didn't initialize any decoders successfully
|
||||
return false;
|
||||
@@ -144,17 +120,12 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
|
||||
int Session::drSetup(int videoFormat, int width, int height, int frameRate, void *, int)
|
||||
{
|
||||
s_ActiveSession->m_ActiveVideoFormat = videoFormat;
|
||||
s_ActiveSession->m_ActiveVideoWidth = width;
|
||||
s_ActiveSession->m_ActiveVideoHeight = height;
|
||||
s_ActiveSession->m_ActiveVideoFrameRate = frameRate;
|
||||
|
||||
// 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);
|
||||
if (!chooseDecoder(s_ActiveSession->m_Preferences.videoDecoderSelection,
|
||||
s_ActiveSession->m_Window,
|
||||
videoFormat, width, height, frameRate,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -165,16 +136,7 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
|
||||
// from underneath the session when we initiate destruction.
|
||||
// We need to destroy the decoder on the main thread to satisfy
|
||||
// some API constraints (like DXVA2).
|
||||
|
||||
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;
|
||||
if (decoder != nullptr) {
|
||||
int ret = decoder->submitDecodeUnit(du);
|
||||
@@ -192,35 +154,23 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
{
|
||||
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);
|
||||
if (!window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
bool ret = decoder->isHardwareAccelerated();
|
||||
|
||||
delete decoder;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -229,8 +179,7 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_App(app),
|
||||
m_Window(nullptr),
|
||||
m_VideoDecoder(nullptr),
|
||||
m_DecoderLock(0),
|
||||
m_NeedsIdr(false)
|
||||
m_DecoderLock(0)
|
||||
{
|
||||
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
||||
m_VideoCallbacks.setup = drSetup;
|
||||
@@ -248,16 +197,14 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_StreamConfig.fps = m_Preferences.fps;
|
||||
m_StreamConfig.bitrate = m_Preferences.bitrateKbps;
|
||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
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);
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(m_StreamConfig.remoteInputAesKey); i++) {
|
||||
m_StreamConfig.remoteInputAesKey[i] =
|
||||
(char)(QRandomGenerator::global()->generate() % 256);
|
||||
}
|
||||
*(int*)m_StreamConfig.remoteInputAesIv = qToBigEndian(QRandomGenerator::global()->generate());
|
||||
switch (m_Preferences.audioConfig)
|
||||
{
|
||||
case StreamingPreferences::AC_AUTO:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration");
|
||||
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_STEREO:
|
||||
@@ -268,10 +215,6 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio configuration: %d",
|
||||
m_StreamConfig.audioConfiguration);
|
||||
|
||||
switch (m_Preferences.videoCodecConfig)
|
||||
{
|
||||
case StreamingPreferences::VCC_AUTO:
|
||||
@@ -318,30 +261,23 @@ bool Session::validateLaunch()
|
||||
QStringList warningList;
|
||||
|
||||
if (m_StreamConfig.supportsHevc) {
|
||||
bool hevcForced = m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||
|
||||
if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
if (m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR) {
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||
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,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
// NOTE: HEVC currently uses only 1 slice regardless of what
|
||||
// we provide in CAPABILITY_SLICES_PER_FRAME(), so we should
|
||||
// never use it for software decoding (unless common-c starts
|
||||
// respecting it for HEVC).
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
|
||||
if (hevcForced) {
|
||||
emit displayLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
||||
}
|
||||
}
|
||||
|
||||
if (hevcForced) {
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||
emit displayLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +299,7 @@ bool Session::validateLaunch()
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
emit displayLaunchWarning("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 {
|
||||
// TODO: Also validate display capabilites
|
||||
@@ -403,6 +339,9 @@ class DeferredSessionCleanupTask : public QRunnable
|
||||
{
|
||||
// Finish cleanup of the connection state
|
||||
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
|
||||
Session::s_ActiveSession = nullptr;
|
||||
@@ -410,103 +349,6 @@ class DeferredSessionCleanupTask : public QRunnable
|
||||
}
|
||||
};
|
||||
|
||||
void Session::getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
int& width, int& height)
|
||||
{
|
||||
int displayIndex = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
getWindowDimensions(fullScreen, x, y, width, height);
|
||||
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
|
||||
if (fullScreen) {
|
||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::exec()
|
||||
{
|
||||
// Check for validation errors/warnings and emit
|
||||
@@ -550,12 +392,64 @@ void Session::exec()
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_VIDEO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
int flags = SDL_WINDOW_HIDDEN;
|
||||
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_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
SDL_GetError());
|
||||
emit displayLaunchError(QString::fromLocal8Bit(SDL_GetError()));
|
||||
s_ActiveSessionSemaphore.release();
|
||||
return;
|
||||
}
|
||||
@@ -583,7 +477,6 @@ void Session::exec()
|
||||
// We already displayed an error dialog in the stage failure
|
||||
// listener.
|
||||
s_ActiveSessionSemaphore.release();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -591,67 +484,11 @@ void Session::exec()
|
||||
emit connectionStarted();
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(m_Preferences.fullScreen,
|
||||
x, y, width, height);
|
||||
// Show the streaming window
|
||||
SDL_ShowWindow(m_Window);
|
||||
|
||||
m_Window = SDL_CreateWindow("Moonlight",
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
// Capture the mouse
|
||||
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
|
||||
SDL_DisableScreenSaver();
|
||||
@@ -692,41 +529,6 @@ void Session::exec()
|
||||
m_VideoDecoder->renderFrame(&event.user);
|
||||
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_KEYDOWN:
|
||||
inputHandler.handleKeyEvent(&event.key);
|
||||
@@ -764,6 +566,9 @@ DispatchDeferredCleanup:
|
||||
// so we can return to the Qt GUI ASAP.
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
SDL_EnableScreenSaver();
|
||||
if (m_Window != nullptr) {
|
||||
SDL_HideWindow(m_Window);
|
||||
}
|
||||
|
||||
// Destroy the decoder, since this must be done on the main thread
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
@@ -771,13 +576,6 @@ DispatchDeferredCleanup:
|
||||
m_VideoDecoder = nullptr;
|
||||
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.
|
||||
// When it is complete, it will release our s_ActiveSessionSemaphore
|
||||
// reference.
|
||||
|
||||
@@ -21,10 +21,6 @@ public:
|
||||
|
||||
Q_INVOKABLE void exec();
|
||||
|
||||
static
|
||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
signals:
|
||||
void stageStarting(QString stage);
|
||||
|
||||
@@ -43,11 +39,9 @@ private:
|
||||
|
||||
int sdlDetermineAudioConfiguration();
|
||||
|
||||
void getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
int& width, int& height);
|
||||
|
||||
void toggleFullscreen();
|
||||
static
|
||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
static
|
||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
@@ -100,12 +94,6 @@ private:
|
||||
SDL_Window* m_Window;
|
||||
IVideoDecoder* m_VideoDecoder;
|
||||
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 OpusMSDecoder* s_OpusDecoder;
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
#include <initguid.h>
|
||||
#include <Initguid.h>
|
||||
#include "dxva2.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
@@ -8,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(); }
|
||||
|
||||
DXVA2Renderer::DXVA2Renderer() :
|
||||
m_FrameIndex(0),
|
||||
m_SurfacesUsed(0),
|
||||
m_SdlRenderer(nullptr),
|
||||
m_DecService(nullptr),
|
||||
m_Decoder(nullptr),
|
||||
m_SurfacesUsed(0),
|
||||
m_Pool(nullptr),
|
||||
m_Device(nullptr),
|
||||
m_RenderTarget(nullptr),
|
||||
m_ProcService(nullptr),
|
||||
m_Processor(nullptr),
|
||||
m_FrameIndex(0)
|
||||
m_Processor(nullptr)
|
||||
{
|
||||
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
||||
RtlZeroMemory(&m_DXVAContext, sizeof(m_DXVAContext));
|
||||
@@ -87,9 +87,6 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using DXVA2 accelerated renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -104,8 +101,8 @@ int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
||||
|
||||
frame->data[3] = frame->buf[0]->data;
|
||||
frame->format = AV_PIX_FMT_DXVA2_VLD;
|
||||
frame->width = me->m_VideoWidth;
|
||||
frame->height = me->m_VideoHeight;
|
||||
frame->width = me->m_Width;
|
||||
frame->height = me->m_Height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -221,8 +218,8 @@ bool DXVA2Renderer::initializeDecoder()
|
||||
alignment = 16;
|
||||
}
|
||||
|
||||
hr = m_DecService->CreateSurface(FFALIGN(m_VideoWidth, alignment),
|
||||
FFALIGN(m_VideoHeight, alignment),
|
||||
hr = m_DecService->CreateSurface(FFALIGN(m_Width, alignment),
|
||||
FFALIGN(m_Height, alignment),
|
||||
ARRAYSIZE(m_DecSurfaces) - 1,
|
||||
m_Desc.Format,
|
||||
D3DPOOL_DEFAULT,
|
||||
@@ -265,9 +262,6 @@ bool DXVA2Renderer::initializeRenderer()
|
||||
D3DSURFACE_DESC renderTargetDesc;
|
||||
m_RenderTarget->GetDesc(&renderTargetDesc);
|
||||
|
||||
m_DisplayWidth = renderTargetDesc.Width;
|
||||
m_DisplayHeight = renderTargetDesc.Height;
|
||||
|
||||
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoProcessorService,
|
||||
reinterpret_cast<void**>(&m_ProcService));
|
||||
|
||||
@@ -308,14 +302,6 @@ bool DXVA2Renderer::initializeRenderer()
|
||||
caps.VideoProcessorOperations);
|
||||
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_Contrast, &m_ContrastRange);
|
||||
@@ -355,17 +341,21 @@ bool DXVA2Renderer::initializeRenderer()
|
||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height)
|
||||
{
|
||||
m_VideoFormat = videoFormat;
|
||||
m_VideoWidth = width;
|
||||
m_VideoHeight = height;
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
|
||||
// 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
|
||||
// D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice().
|
||||
SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1");
|
||||
|
||||
// We require full-screen desktop mode to avoid having to enable V-sync
|
||||
// to synchronize frame delivery (which has a much higher latency penalty).
|
||||
m_SdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
// We want VSYNC when running in full-screen. DWM will give us
|
||||
// tear-free video when running composited (windowed) without any
|
||||
// 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) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateRenderer() failed: %s",
|
||||
@@ -375,20 +365,15 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
||||
|
||||
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));
|
||||
m_Desc.SampleWidth = m_VideoWidth;
|
||||
m_Desc.SampleHeight = m_VideoHeight;
|
||||
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.SampleWidth = m_Width;
|
||||
m_Desc.SampleHeight = m_Height;
|
||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_ProgressiveChroma;
|
||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
|
||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
|
||||
m_Desc.SampleFormat.VideoLighting = DXVA2_VideoLighting_dim;
|
||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_BT709;
|
||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_709;
|
||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||
|
||||
@@ -408,95 +393,23 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||
HRESULT hr;
|
||||
|
||||
switch (frame->color_range) {
|
||||
case AVCOL_RANGE_JPEG:
|
||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
|
||||
break;
|
||||
case AVCOL_RANGE_MPEG:
|
||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_16_235;
|
||||
hr = m_Device->TestCooperativeLevel();
|
||||
switch (hr) {
|
||||
case D3D_OK:
|
||||
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:
|
||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unknown D3D error: %x",
|
||||
hr);
|
||||
return;
|
||||
}
|
||||
|
||||
DXVA2_VideoSample sample = {};
|
||||
@@ -505,31 +418,18 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
sample.SrcSurface = surface;
|
||||
sample.SrcRect.right = m_Desc.SampleWidth;
|
||||
sample.SrcRect.bottom = m_Desc.SampleHeight;
|
||||
sample.DstRect = sample.SrcRect;
|
||||
sample.SampleFormat = m_Desc.SampleFormat;
|
||||
sample.PlanarAlpha = DXVA2_Fixed32OpaqueAlpha();
|
||||
|
||||
// Center in frame and preserve aspect ratio
|
||||
double srcAspectRatio = (double)m_Desc.SampleWidth / (double)m_Desc.SampleHeight;
|
||||
double dstAspectRatio = (double)m_DisplayWidth / (double)m_DisplayHeight;
|
||||
if (dstAspectRatio < srcAspectRatio) {
|
||||
// Greater height per width
|
||||
int drawHeight = (int)(m_DisplayWidth / srcAspectRatio);
|
||||
sample.DstRect.top = (m_DisplayHeight - drawHeight) / 2;
|
||||
sample.DstRect.bottom = sample.DstRect.bottom + drawHeight;
|
||||
sample.DstRect.right = m_DisplayWidth;
|
||||
}
|
||||
else {
|
||||
// Greater width per height
|
||||
int drawWidth = (int)(m_DisplayHeight * srcAspectRatio);
|
||||
sample.DstRect.bottom = m_DisplayHeight;
|
||||
sample.DstRect.left = (m_DisplayWidth - drawWidth) / 2;
|
||||
sample.DstRect.right = sample.DstRect.left + drawWidth;
|
||||
}
|
||||
|
||||
DXVA2_VideoProcessBltParams bltParams = {};
|
||||
|
||||
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.DestFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||
@@ -541,33 +441,12 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
|
||||
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);
|
||||
av_frame_free(&frame);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VideoProcessBlt() failed: %x",
|
||||
hr);
|
||||
}
|
||||
|
||||
// We must try to present to trigger SDL's logic to recover the render target,
|
||||
// even if VideoProcessBlt() fails.
|
||||
SDL_RenderPresent(m_SdlRenderer);
|
||||
m_Device->Present(nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,8 @@ private:
|
||||
const enum AVPixelFormat* pixFmts);
|
||||
|
||||
int m_VideoFormat;
|
||||
int m_VideoWidth;
|
||||
int m_VideoHeight;
|
||||
|
||||
int m_DisplayWidth;
|
||||
int m_DisplayHeight;
|
||||
int m_Width;
|
||||
int m_Height;
|
||||
|
||||
SDL_Renderer* m_SdlRenderer;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "renderer.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
SdlRenderer::SdlRenderer()
|
||||
: m_Renderer(nullptr),
|
||||
m_Texture(nullptr)
|
||||
@@ -23,26 +21,14 @@ SdlRenderer::~SdlRenderer()
|
||||
bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
||||
{
|
||||
/* Nothing to do */
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using SDL software renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
// NOTE: HEVC currently uses only 1 slice regardless of what
|
||||
// we provide in CAPABILITY_SLICES_PER_FRAME(), so we should
|
||||
// never use it for software decoding (unless common-c starts
|
||||
// respecting it for HEVC).
|
||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (!m_Renderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -55,11 +41,6 @@ bool SdlRenderer::initialize(SDL_Window* window,
|
||||
// logical rendering surface size is equal to the stream size
|
||||
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,
|
||||
SDL_PIXELFORMAT_YV12,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
@@ -84,10 +65,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
frame->linesize[1],
|
||||
frame->data[2],
|
||||
frame->linesize[2]);
|
||||
|
||||
// Done with the frame now
|
||||
av_frame_free(&frame);
|
||||
|
||||
SDL_RenderClear(m_Renderer);
|
||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(m_Renderer);
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "vaapi.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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Center in frame and preserve aspect ratio
|
||||
int x, y, width, height;
|
||||
double srcAspectRatio = (double)m_VideoWidth / (double)m_VideoHeight;
|
||||
double dstAspectRatio = (double)m_DisplayWidth / (double)m_DisplayHeight;
|
||||
if (dstAspectRatio < srcAspectRatio) {
|
||||
// Greater height per width
|
||||
int drawHeight = (int)(m_DisplayWidth / srcAspectRatio);
|
||||
y = (m_DisplayHeight - drawHeight) / 2;
|
||||
height = drawHeight;
|
||||
x = 0;
|
||||
width = m_DisplayWidth;
|
||||
}
|
||||
else {
|
||||
// Greater width per height
|
||||
int drawWidth = (int)(m_DisplayHeight * srcAspectRatio);
|
||||
y = 0;
|
||||
height = m_DisplayHeight;
|
||||
x = (m_DisplayWidth - drawWidth) / 2;
|
||||
width = drawWidth;
|
||||
}
|
||||
|
||||
m_vaPutSurface(vaDeviceContext->display,
|
||||
surface,
|
||||
m_XWindow,
|
||||
0, 0,
|
||||
m_VideoWidth, m_VideoHeight,
|
||||
x, y,
|
||||
width, height,
|
||||
NULL, 0, 0);
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
@@ -1,50 +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;
|
||||
};
|
||||
|
||||
@@ -7,14 +7,9 @@
|
||||
#include <SDL_syswm.h>
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <QQueue>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
|
||||
#define FRAME_HISTORY_ENTRIES 8
|
||||
|
||||
class VTRenderer : public IFFmpegRenderer
|
||||
{
|
||||
@@ -23,10 +18,9 @@ public:
|
||||
: m_HwContext(nullptr),
|
||||
m_DisplayLayer(nullptr),
|
||||
m_FormatDesc(nullptr),
|
||||
m_View(nullptr),
|
||||
m_DisplayLink(nullptr),
|
||||
m_FrameQueueLock(0)
|
||||
m_View(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~VTRenderer()
|
||||
@@ -38,132 +32,8 @@ public:
|
||||
if (m_FormatDesc != nullptr) {
|
||||
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,
|
||||
int videoFormat,
|
||||
@@ -243,34 +113,60 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
CVDisplayLinkStart(m_DisplayLink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) override
|
||||
{
|
||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using VideoToolbox accelerated renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void renderFrame(AVFrame* frame) override
|
||||
{
|
||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||
OSStatus status;
|
||||
|
||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting failed AVSampleBufferDisplay layer");
|
||||
setupDisplayLayer();
|
||||
}
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
m_FrameQueue.enqueue(frame);
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
// If the format has changed or doesn't exist yet, construct it with the
|
||||
// pixel buffer data
|
||||
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:
|
||||
@@ -296,10 +192,6 @@ private:
|
||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||
NSView* m_View;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
QQueue<AVFrame*> m_FrameQueue;
|
||||
QQueue<int> m_FrameQueueHistory;
|
||||
SDL_SpinLock m_FrameQueueLock;
|
||||
};
|
||||
|
||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
#include <Limelight.h>
|
||||
#include "ffmpeg.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#ifdef _WIN32
|
||||
#include "ffmpeg-renderers/dxva2.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "ffmpeg-renderers/vt.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBVA
|
||||
#include "ffmpeg-renderers/vaapi.h"
|
||||
#endif
|
||||
|
||||
bool FFmpegVideoDecoder::chooseDecoder(
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window,
|
||||
@@ -64,20 +56,10 @@ bool FFmpegVideoDecoder::chooseDecoder(
|
||||
|
||||
// Look for acceleration types we support
|
||||
switch (config->device_type) {
|
||||
#ifdef Q_OS_WIN32
|
||||
#ifdef _WIN32
|
||||
case AV_HWDEVICE_TYPE_DXVA2:
|
||||
newRenderer = new DXVA2Renderer();
|
||||
break;
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
||||
newRenderer = VTRendererFactory::createRenderer();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_LIBVA
|
||||
case AV_HWDEVICE_TYPE_VAAPI:
|
||||
newRenderer = new VAAPIRenderer();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
continue;
|
||||
@@ -107,7 +89,6 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||
m_Renderer(nullptr)
|
||||
{
|
||||
av_init_packet(&m_Pkt);
|
||||
SDL_AtomicSet(&m_QueuedFrames, 0);
|
||||
|
||||
// Use linear filtering when renderer scaling is required
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
||||
@@ -115,31 +96,14 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||
|
||||
FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
||||
{
|
||||
// Drop any frames still queued to ensure
|
||||
// they are properly freed.
|
||||
SDL_Event event;
|
||||
avcodec_close(m_VideoDecoderCtx);
|
||||
av_free(m_VideoDecoderCtx);
|
||||
m_VideoDecoderCtx = nullptr;
|
||||
|
||||
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);
|
||||
m_HwDecodeCfg = nullptr;
|
||||
|
||||
delete m_Renderer;
|
||||
m_Renderer = nullptr;
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::initialize(
|
||||
@@ -244,9 +208,14 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
|
||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||
if (err == 0) {
|
||||
// Queue the frame for rendering from the main thread
|
||||
SDL_AtomicIncRef(&m_QueuedFrames);
|
||||
queueFrame(frame);
|
||||
SDL_Event event;
|
||||
|
||||
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 {
|
||||
av_frame_free(&frame);
|
||||
@@ -260,7 +229,7 @@ void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
m_Renderer->renderFrame(frame);
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
// Called on main thread
|
||||
@@ -268,5 +237,4 @@ void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
av_frame_free(&frame);
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
}
|
||||
|
||||
@@ -38,5 +38,4 @@ private:
|
||||
QByteArray m_DecodeBuffer;
|
||||
const AVCodecHWConfig* m_HwDecodeCfg;
|
||||
IFFmpegRenderer* m_Renderer;
|
||||
SDL_atomic_t m_QueuedFrames;
|
||||
};
|
||||
|
||||
@@ -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
-1
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user