Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
973d174a30 | ||
|
|
9f35690b95 | ||
|
|
d961f2bc13 | ||
|
|
10e8277044 | ||
|
|
b4c40d9b18 | ||
|
|
e1603f0808 | ||
|
|
d4c2350d63 | ||
|
|
5e5470ae12 | ||
|
|
6230a99360 | ||
|
|
801adb6af7 | ||
|
|
8b9b96f422 | ||
|
|
160684f23e | ||
|
|
88930a9de4 | ||
|
|
af6b8c9b88 | ||
|
|
bf52d3189d | ||
|
|
5d872e8b5b | ||
|
|
4832d8007b | ||
|
|
879e084383 | ||
|
|
1ae16bb657 | ||
|
|
2177bc68db | ||
|
|
c8b887fe32 | ||
|
|
7205242f38 | ||
|
|
be33b5b387 | ||
|
|
66a123f4b9 | ||
|
|
76a4ee22b3 | ||
|
|
3a6bf4d20b | ||
|
|
09cf105cdc | ||
|
|
ce02e086a3 | ||
|
|
e8d4313454 | ||
|
|
49046f88cf | ||
|
|
5e2f32e3eb | ||
|
|
b4a1393bf6 | ||
|
|
0ccee9d806 | ||
|
|
5700272dc4 | ||
|
|
7e5a3925b3 | ||
|
|
1febe32e73 | ||
|
|
6059c982cf | ||
|
|
de7b973239 | ||
|
|
741113a607 | ||
|
|
c20daa8867 | ||
|
|
5e57a7d5c0 | ||
|
|
7c8d27700e | ||
|
|
f8621be5ee | ||
|
|
8ed87e10b4 | ||
|
|
ca26fa2dd0 | ||
|
|
1669095fb1 | ||
|
|
13eed48877 | ||
|
|
07494e6eff | ||
|
|
41b1d72825 | ||
|
|
4d2965e74f |
@@ -0,0 +1,4 @@
|
||||
issuesOpened: >
|
||||
If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://discord.gg/MySTSdq) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.<br /><br />
|
||||
This issue tracker should only be used for specific bugs or feature requests.<br /><br />
|
||||
Thank you, and happy streaming!
|
||||
+14
-13
@@ -25,38 +25,39 @@ matrix:
|
||||
- libstdc++6:i386
|
||||
- os: linux
|
||||
sudo: required
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
env: TRAVIS_TARGET=linux
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:beineri/opt-qt597-xenial'
|
||||
- sourceline: 'ppa:jonathonf/ffmpeg-4'
|
||||
- sourceline: 'ppa:savoury1/multimedia'
|
||||
- sourceline: 'ppa:savoury1/ffmpeg4'
|
||||
packages:
|
||||
- qt59base
|
||||
- qt59quickcontrols2
|
||||
- qt59svg
|
||||
- libesd0-dev
|
||||
- libgl-dev
|
||||
- qt5-default
|
||||
- qtbase5-dev
|
||||
- qtquickcontrols2-5-dev
|
||||
- libqt5svg5-dev
|
||||
- libgl1-mesa-dev
|
||||
- libegl1-mesa-dev
|
||||
- libxkbcommon-dev
|
||||
- wayland-protocols
|
||||
- libopus-dev
|
||||
- libavcodec-dev
|
||||
- libavutil-dev
|
||||
- libva-dev
|
||||
- libvdpau-dev
|
||||
- libsdl2-dev
|
||||
- qtdeclarative5-dev
|
||||
- libssl-dev
|
||||
- libsdl2-ttf-dev
|
||||
- libavcodec-dev
|
||||
- libavutil-dev
|
||||
|
||||
install:
|
||||
- '[ "$TRAVIS_TARGET" != osx ] || brew link --force qt'
|
||||
- if [[ "$TRAVIS_TARGET" = "osx" ]]; then nvm use node && npm install --global create-dmg; fi
|
||||
- if [[ "$TRAVIS_TARGET" = "linux" ]]; then export SDL2_VER=2.0.9 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi
|
||||
- if [[ "$TRAVIS_TARGET" = "linux" ]]; then export SDL2_TTF_VER=2.0.15 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi
|
||||
- if [[ "$TRAVIS_TARGET" = "steamlink" ]]; then git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk; fi
|
||||
|
||||
before_script:
|
||||
- '[ "$TRAVIS_TARGET" != "linux" ] || source /opt/qt59/bin/qt59-env.sh'
|
||||
- '[ "$TRAVIS_TARGET" = "steamlink" ] || qmake -v'
|
||||
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
|
||||
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
|
||||
|
||||
@@ -151,6 +151,29 @@ private:
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/235
|
||||
L"nvdlist.dll",
|
||||
L"nvdlistx.dll",
|
||||
|
||||
// In some unknown circumstances, RTSS tries to hook in the middle of an instruction, leaving garbage
|
||||
// code inside d3d9.dll that causes a crash when executed:
|
||||
//
|
||||
// 0:000> u
|
||||
// d3d9!D3D9GetCurrentOwnershipMode+0x5d:
|
||||
// 00007ff8`95b95861 9b wait
|
||||
// 00007ff8`95b95862 a7 cmps dword ptr [rsi],dword ptr [rdi] <--- crash happens here
|
||||
// 00007ff8`95b95863 ff ???
|
||||
// 00007ff8`95b95864 bfe8ca8a00 mov edi,8ACAE8h
|
||||
// 00007ff8`95b95869 00eb add bl,ch
|
||||
// 00007ff8`95b9586b f1 ???
|
||||
// 00007ff8`95b9586c b808000000 mov eax,8
|
||||
// 00007ff8`95b95871 ebe6 jmp d3d9!D3D9GetCurrentOwnershipMode+0x55 (00007ff8`95b95859)
|
||||
//
|
||||
// Disassembling starting at the exact address of the attempted hook yields the intended jmp instruction
|
||||
//
|
||||
// 0:000> u d3d9!D3D9GetCurrentOwnershipMode+0x5c:
|
||||
// 00007ff8`95b95860 e99ba7ffbf jmp 00007ff8`55b90000
|
||||
//
|
||||
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
|
||||
L"RTSSHooks.dll",
|
||||
L"RTSSHooks64.dll",
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+1
-1
Submodule app/SDL_GameControllerDB updated: dca1a629d9...aeebc86f9f
+1
-1
@@ -114,7 +114,7 @@ win32:!winrt {
|
||||
}
|
||||
macx {
|
||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
|
||||
|
||||
# For libsoundio
|
||||
LIBS += -framework CoreAudio -framework AudioUnit
|
||||
|
||||
@@ -28,7 +28,7 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
||||
|
||||
void AutoUpdateChecker::start()
|
||||
{
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) || defined(STEAM_LINK) // Only run update checker on platforms without auto-update
|
||||
// We'll get a callback when this is finished
|
||||
QUrl url("https://moonlight-stream.org/updates/qt.json");
|
||||
m_Nam.get(QNetworkRequest(url));
|
||||
@@ -43,6 +43,15 @@ void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>&
|
||||
}
|
||||
}
|
||||
|
||||
QString AutoUpdateChecker::getPlatform()
|
||||
{
|
||||
#ifdef STEAM_LINK
|
||||
return QStringLiteral("steamlink");
|
||||
#else
|
||||
return QSysInfo::productType();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||
{
|
||||
Q_ASSERT(reply->isFinished());
|
||||
@@ -88,7 +97,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||
}
|
||||
|
||||
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
|
||||
updateObj["platform"] == QSysInfo::productType()) {
|
||||
updateObj["platform"] == getPlatform()) {
|
||||
qDebug() << "Found update manifest match for current platform";
|
||||
|
||||
QString latestVersion = updateObj["version"].toString();
|
||||
@@ -137,7 +146,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||
}
|
||||
|
||||
qWarning() << "No entry in update manifest found for current platform: "
|
||||
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
|
||||
<< QSysInfo::buildCpuArchitecture() << getPlatform();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Update checking failed with error: " << reply->error();
|
||||
|
||||
@@ -20,6 +20,8 @@ private slots:
|
||||
private:
|
||||
void parseStringToVersionQuad(QString& string, QVector<int>& version);
|
||||
|
||||
QString getPlatform();
|
||||
|
||||
QVector<int> m_CurrentVersionQuad;
|
||||
QNetworkAccessManager m_Nam;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QUdpSocket>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QNetworkProxy>
|
||||
|
||||
#define SER_NAME "hostname"
|
||||
#define SER_UUID "uuid"
|
||||
@@ -237,6 +238,81 @@ bool NvComputer::wake()
|
||||
return success;
|
||||
}
|
||||
|
||||
bool NvComputer::isReachableOverVpn()
|
||||
{
|
||||
if (activeAddress.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpSocket s;
|
||||
|
||||
s.setProxy(QNetworkProxy::NoProxy);
|
||||
s.connectToHost(activeAddress, 47984);
|
||||
if (s.waitForConnected(3000)) {
|
||||
Q_ASSERT(!s.localAddress().isNull());
|
||||
|
||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
||||
// Ensure the interface is up
|
||||
if ((nic.flags() & QNetworkInterface::IsUp) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
|
||||
if (addr.ip() == s.localAddress()) {
|
||||
qInfo() << "Found matching interface:" << nic.humanReadableName() << nic.hardwareAddress() << nic.flags();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||
qInfo() << "Interface Type:" << nic.type();
|
||||
qInfo() << "Interface MTU:" << nic.maximumTransmissionUnit();
|
||||
|
||||
if (nic.type() == QNetworkInterface::Virtual ||
|
||||
nic.type() == QNetworkInterface::Ppp) {
|
||||
// Treat PPP and virtual interfaces as likely VPNs
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
|
||||
// Treat MTUs under 1500 as likely VPNs
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
|
||||
// Treat point-to-point links as likely VPNs.
|
||||
// This check detects OpenVPN on Unix-like OSes.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
||||
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nic.humanReadableName().startsWith("ZeroTier")) {
|
||||
// ZeroTier interfaces always start with "ZeroTier"
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nic.humanReadableName().contains("VPN")) {
|
||||
// This one is just a final heuristic if all else fails
|
||||
return true;
|
||||
}
|
||||
|
||||
// Didn't meet any of our VPN heuristics
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "No match found for address:" << s.localAddress();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// If we fail to connect, just pretend that it's not a VPN
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QString> NvComputer::uniqueAddresses()
|
||||
{
|
||||
QVector<QString> uniqueAddressList;
|
||||
|
||||
@@ -29,6 +29,9 @@ public:
|
||||
bool
|
||||
wake();
|
||||
|
||||
bool
|
||||
isReachableOverVpn();
|
||||
|
||||
QVector<QString>
|
||||
uniqueAddresses();
|
||||
|
||||
|
||||
@@ -33,6 +33,19 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="1.2.0" date="2019-12-17">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Improved video encoding efficiency</li>
|
||||
<li>Improved performance when streaming over a VPN</li>
|
||||
<li>Fixed RTSP handshake error on certain networks</li>
|
||||
<li>Fixed rumble not working on certain gamepads</li>
|
||||
<li>Fixed pairing error when double-clicking on an unpaired PC</li>
|
||||
<li>Updated included SDL gamepad mappings</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.1.1" date="2019-09-29">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
|
||||
+9
-3
@@ -104,7 +104,7 @@ CenteredGridView {
|
||||
Image {
|
||||
id: pcIcon
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
source: "qrc:/res/ic_tv_white_48px.svg"
|
||||
source: "qrc:/res/desktop_windows-48px.svg"
|
||||
sourceSize {
|
||||
width: 200
|
||||
height: 200
|
||||
@@ -116,7 +116,7 @@ CenteredGridView {
|
||||
id: stateIcon
|
||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||
anchors.verticalCenter: pcIcon.verticalCenter
|
||||
anchors.verticalCenterOffset: -10
|
||||
anchors.verticalCenterOffset: -15
|
||||
visible: !model.statusUnknown && (!model.online || !model.paired)
|
||||
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
||||
sourceSize {
|
||||
@@ -129,7 +129,7 @@ CenteredGridView {
|
||||
id: statusUnknownSpinner
|
||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||
anchors.verticalCenter: pcIcon.verticalCenter
|
||||
anchors.verticalCenterOffset: -10
|
||||
anchors.verticalCenterOffset: -15
|
||||
width: 75
|
||||
height: 75
|
||||
visible: model.statusUnknown
|
||||
@@ -235,6 +235,12 @@ CenteredGridView {
|
||||
|
||||
NavigableMessageDialog {
|
||||
id: pairDialog
|
||||
|
||||
// Pairing dialog must be modal to prevent double-clicks from triggering
|
||||
// pairing twice
|
||||
modal: true
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
|
||||
// don't allow edits to the rest of the window while open
|
||||
property string pin : "0000"
|
||||
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
|
||||
|
||||
+5
-1
@@ -245,7 +245,11 @@ ApplicationWindow {
|
||||
// an update is available
|
||||
visible: false
|
||||
|
||||
onClicked: Qt.openUrlExternally(browserUrl);
|
||||
onClicked: {
|
||||
if (SystemProperties.hasBrowser) {
|
||||
Qt.openUrlExternally(browserUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvailable(version, url)
|
||||
{
|
||||
|
||||
+8
-1
@@ -9,6 +9,7 @@
|
||||
#include <QPalette>
|
||||
#include <QFont>
|
||||
#include <QCursor>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
// Don't let SDL hook our main function, since Qt is already
|
||||
// doing the same thing. This needs to be before any headers
|
||||
@@ -52,7 +53,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
static QTime s_LoggerTime;
|
||||
static QElapsedTimer s_LoggerTime;
|
||||
static QTextStream s_LoggerStream(stdout);
|
||||
static QMutex s_LoggerLock;
|
||||
#ifdef LOG_TO_FILE
|
||||
@@ -284,8 +285,14 @@ int main(int argc, char *argv[])
|
||||
AntiHookingDummyImport();
|
||||
#endif
|
||||
|
||||
// Enable High DPI support
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
// Enable fractional High DPI scaling on Qt 5.14 and later
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
#endif
|
||||
|
||||
// This avoids using the default keychain for SSL, which may cause
|
||||
// password prompts on macOS.
|
||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="48" height="48" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/>
|
||||
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -1,4 +0,0 @@
|
||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 303 B |
@@ -1,9 +0,0 @@
|
||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path d="M0 0h24v24H0V0z" id="a"/>
|
||||
</defs>
|
||||
<clipPath id="b">
|
||||
<use overflow="visible" xlink:href="#a"/>
|
||||
</clipPath>
|
||||
<path clip-path="url(#b)" d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-7v2H8v-2h8z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 467 B |
@@ -1,4 +0,0 @@
|
||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 273 B |
+1
-3
@@ -1,9 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>res/ic_remove_circle_outline_white_48px.svg</file>
|
||||
<file>res/ic_remove_from_queue_white_48px.svg</file>
|
||||
<file>res/ic_videogame_asset_white_48px.svg</file>
|
||||
<file>res/ic_tv_white_48px.svg</file>
|
||||
<file>res/desktop_windows-48px.svg</file>
|
||||
<file>res/ic_add_to_queue_white_48px.svg</file>
|
||||
<file>res/baseline-lock-24px.svg</file>
|
||||
<file>res/baseline-play_circle_filled_white-48px.svg</file>
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#define SER_CONNWARNINGS "connwarnings"
|
||||
#define SER_RICHPRESENCE "richpresence"
|
||||
#define SER_GAMEPADMOUSE "gamepadmouse"
|
||||
#define SER_DEFAULTVER "defaultver"
|
||||
|
||||
#define CURRENT_DEFAULT_VER 1
|
||||
|
||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -36,6 +39,8 @@ void StreamingPreferences::reload()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
int defaultVer = settings.value(SER_DEFAULTVER, 0).toInt();
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
||||
#else
|
||||
@@ -69,6 +74,16 @@ void StreamingPreferences::reload()
|
||||
// Try to load from the old preference value too
|
||||
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
|
||||
recommendedFullScreenMode : WindowMode::WM_WINDOWED)).toInt());
|
||||
|
||||
// Perform default settings updates as required based on last default version
|
||||
if (defaultVer == 0) {
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Update window mode setting on macOS from full-screen (old default) to borderless windowed (new default)
|
||||
if (windowMode == WindowMode::WM_FULLSCREEN) {
|
||||
windowMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingPreferences::save()
|
||||
@@ -96,6 +111,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
|
||||
@@ -145,29 +145,34 @@ int Session::arInit(int /* audioConfiguration */,
|
||||
|
||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
||||
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
|
||||
|
||||
// Create the Opus decoder with the renderer's preferred channel mapping
|
||||
s_ActiveSession->m_OpusDecoder =
|
||||
opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
|
||||
s_ActiveSession->m_AudioConfig.channelCount,
|
||||
s_ActiveSession->m_AudioConfig.streams,
|
||||
s_ActiveSession->m_AudioConfig.coupledStreams,
|
||||
s_ActiveSession->m_AudioConfig.mapping,
|
||||
&error);
|
||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
||||
delete s_ActiveSession->m_AudioRenderer;
|
||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(opusConfig);
|
||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
return -2;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
opusConfig->channelCount);
|
||||
s_ActiveSession->m_AudioConfig.channelCount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,14 @@ public:
|
||||
virtual bool submitAudio(int bytesWritten) = 0;
|
||||
|
||||
virtual int getCapabilities() = 0;
|
||||
|
||||
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION) {
|
||||
// Use default channel mapping:
|
||||
// 0 - Front Left
|
||||
// 1 - Front Right
|
||||
// 2 - Center
|
||||
// 3 - LFE
|
||||
// 4 - Surround Left
|
||||
// 5 - Surround Right
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,6 +21,5 @@ public:
|
||||
private:
|
||||
SDL_AudioDeviceID m_AudioDevice;
|
||||
void* m_AudioBuffer;
|
||||
int m_FrameDurationMs;
|
||||
int m_FrameSize;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
||||
want.samples = opusConfig->samplesPerFrame;
|
||||
|
||||
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||
m_FrameDurationMs = opusConfig->samplesPerFrame / 48;
|
||||
|
||||
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (m_AudioDevice == 0) {
|
||||
@@ -97,7 +96,7 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||
|
||||
// Don't queue if there's already more than 30 ms of audio data waiting
|
||||
// in Moonlight's audio queue.
|
||||
if (LiGetPendingAudioFrames() * m_FrameDurationMs > 30) {
|
||||
if (LiGetPendingAudioDuration() > 30) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,6 +117,6 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||
|
||||
int SdlAudioRenderer::getCapabilities()
|
||||
{
|
||||
// Direct submit can't be used because we use LiGetPendingAudioFrames()
|
||||
return 0;
|
||||
// Direct submit can't be used because we use LiGetPendingAudioDuration()
|
||||
return CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
||||
// it's hard to avoid since we get crushed by CPU limitations.
|
||||
m_MaxQueuedAudioMs = 40 * opusConfig->channelCount / 2;
|
||||
|
||||
m_FrameDuration = opusConfig->samplesPerFrame / 48;
|
||||
m_AudioBufferSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
||||
opusConfig->sampleRate,
|
||||
@@ -37,12 +36,35 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using SLAudio renderer with %d ms frames",
|
||||
m_FrameDuration);
|
||||
"Using SLAudio renderer with %d samples per frame",
|
||||
opusConfig->samplesPerFrame);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define SWAP_CHANNEL(i, j) \
|
||||
tmp = opusConfig->mapping[i]; \
|
||||
opusConfig->mapping[i] = opusConfig->mapping[j]; \
|
||||
opusConfig->mapping[j] = tmp
|
||||
|
||||
void SLAudioRenderer::remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
|
||||
unsigned char tmp;
|
||||
|
||||
if (opusConfig->channelCount == 6) {
|
||||
// The Moonlight's default channel order is FL,FR,C,LFE,RL,RR
|
||||
// SLAudio expects FL,C,FR,RL,RR,LFE so we swap the channels around to match
|
||||
|
||||
// Swap FR and C - now FL,C,FR,LFE,RL,RR
|
||||
SWAP_CHANNEL(1, 2);
|
||||
|
||||
// Swap LFE and RR - now FL,C,FR,RR,RL,LFE
|
||||
SWAP_CHANNEL(3, 5);
|
||||
|
||||
// Swap RR and RL - now FL,C,FR,RL,RR,LFE
|
||||
SWAP_CHANNEL(3, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void* SLAudioRenderer::getAudioBuffer(int* size)
|
||||
{
|
||||
SDL_assert(*size == m_AudioBufferSize);
|
||||
@@ -77,7 +99,7 @@ bool SLAudioRenderer::submitAudio(int bytesWritten)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LiGetPendingAudioFrames() * m_FrameDuration < m_MaxQueuedAudioMs) {
|
||||
if (LiGetPendingAudioDuration() < m_MaxQueuedAudioMs) {
|
||||
SLAudio_SubmitFrame(m_AudioStream);
|
||||
m_AudioBuffer = nullptr;
|
||||
}
|
||||
@@ -92,7 +114,7 @@ bool SLAudioRenderer::submitAudio(int bytesWritten)
|
||||
|
||||
int SLAudioRenderer::getCapabilities()
|
||||
{
|
||||
return CAPABILITY_SLOW_OPUS_DECODER;
|
||||
return CAPABILITY_SLOW_OPUS_DECODER | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
|
||||
}
|
||||
|
||||
void SLAudioRenderer::slLogCallback(void*, ESLAudioLog logLevel, const char *message)
|
||||
|
||||
@@ -18,6 +18,8 @@ public:
|
||||
|
||||
virtual int getCapabilities();
|
||||
|
||||
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
||||
|
||||
private:
|
||||
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
||||
|
||||
@@ -26,6 +28,5 @@ private:
|
||||
|
||||
void* m_AudioBuffer;
|
||||
int m_AudioBufferSize;
|
||||
int m_FrameDuration;
|
||||
int m_MaxQueuedAudioMs;
|
||||
};
|
||||
|
||||
@@ -4,29 +4,13 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// GFE sends us packets in 5 ms chunks
|
||||
const double SoundIoAudioRenderer::k_RawSampleLengthSec = 0.005;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// PulseAudio and ALSA require more than just 5 ms samples
|
||||
// for some reason, so write a minimum of 25 ms each time to
|
||||
// prevent underruns on Bluetooth.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/147
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/157
|
||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = 0.025;
|
||||
#else
|
||||
// This determines the size of the buffers we'll
|
||||
// get from CoreAudio. It is also the minimum
|
||||
// size that we will write when called to fill a buffer.
|
||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = k_RawSampleLengthSec;
|
||||
#endif
|
||||
|
||||
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
||||
: m_OpusChannelCount(0),
|
||||
m_SoundIo(nullptr),
|
||||
m_Device(nullptr),
|
||||
m_OutputStream(nullptr),
|
||||
m_RingBuffer(nullptr),
|
||||
m_AudioPacketDuration(0),
|
||||
m_Latency(0),
|
||||
m_Errored(false)
|
||||
{
|
||||
@@ -172,9 +156,11 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
||||
return false;
|
||||
}
|
||||
|
||||
m_AudioPacketDuration = (opusConfig->samplesPerFrame / (opusConfig->sampleRate / 1000)) / 1000.0;
|
||||
|
||||
m_OutputStream->format = SoundIoFormatS16NE;
|
||||
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
||||
m_OutputStream->software_latency = k_MinSampleLengthSec;
|
||||
m_OutputStream->software_latency = m_AudioPacketDuration;
|
||||
m_OutputStream->name = "Moonlight";
|
||||
m_OutputStream->userdata = this;
|
||||
m_OutputStream->error_callback = sioErrorCallback;
|
||||
@@ -237,25 +223,21 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
||||
|
||||
int packetsToBuffer;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// PulseAudio and ALSA need the large buffer (see comment on k_MinSampleLengthSec),
|
||||
// so we need a buffer at least double that size to allow packets to arrive
|
||||
// while we're writing to the sink.
|
||||
packetsToBuffer = (int)(k_MinSampleLengthSec / k_RawSampleLengthSec) * 2;
|
||||
#else
|
||||
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
|
||||
// 15 ms buffer seems to be fine for WASAPI
|
||||
packetsToBuffer = 3;
|
||||
packetsToBuffer = (int)ceil(0.015 / m_AudioPacketDuration);
|
||||
}
|
||||
else {
|
||||
// 30 ms buffer on CoreAudio to avoid glitching on macOS
|
||||
packetsToBuffer = 6;
|
||||
packetsToBuffer = (int)ceil(0.030 / m_AudioPacketDuration);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Always buffer at least 2 packets
|
||||
packetsToBuffer = qMax(2, packetsToBuffer);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio buffer size: %d packets",
|
||||
packetsToBuffer);
|
||||
"Audio buffer size: %f seconds",
|
||||
packetsToBuffer * m_AudioPacketDuration);
|
||||
|
||||
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
|
||||
m_OutputStream->bytes_per_sample *
|
||||
@@ -319,7 +301,8 @@ bool SoundIoAudioRenderer::submitAudio(int bytesWritten)
|
||||
|
||||
int SoundIoAudioRenderer::getCapabilities()
|
||||
{
|
||||
return CAPABILITY_DIRECT_SUBMIT;
|
||||
// TODO: Tweak buffer sizes then re-enable arbitrary audio duration
|
||||
return CAPABILITY_DIRECT_SUBMIT /* | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION */;
|
||||
}
|
||||
|
||||
void SoundIoAudioRenderer::sioErrorCallback(SoundIoOutStream* stream, int err)
|
||||
@@ -389,11 +372,11 @@ void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameC
|
||||
// Ensure we always write at least a buffer, even if it's silence, to avoid
|
||||
// busy looping when no audio data is available while libsoundio tries to keep
|
||||
// us from starving the output device.
|
||||
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_MinSampleLengthSec));
|
||||
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * me->m_AudioPacketDuration));
|
||||
|
||||
// Clamp frameCountMax to minSampleLen * 4 to stop our latency from growing if audio packets lag.
|
||||
// Clamp frameCountMax to at least 2 packets or 20 ms to stop our latency from growing if audio packets lag.
|
||||
// This makes sure that we never increase our latency far beyond what the sink is consuming.
|
||||
frameCountMax = qMin(frameCountMax, (int)(stream->sample_rate * k_MinSampleLengthSec) * 4);
|
||||
frameCountMax = qMin(frameCountMax, (int)(stream->sample_rate * qMax(me->m_AudioPacketDuration * 2, 0.020)));
|
||||
frameCountMin = qMin(frameCountMin, frameCountMax);
|
||||
|
||||
// Clamp framesLeft to frameCountMax
|
||||
|
||||
@@ -36,9 +36,7 @@ private:
|
||||
struct SoundIoOutStream* m_OutputStream;
|
||||
struct SoundIoRingBuffer* m_RingBuffer;
|
||||
struct SoundIoChannelLayout m_EffectiveLayout;
|
||||
double m_AudioPacketDuration;
|
||||
double m_Latency;
|
||||
bool m_Errored;
|
||||
|
||||
static const double k_RawSampleLengthSec;
|
||||
static const double k_MinSampleLengthSec;
|
||||
};
|
||||
|
||||
+37
-2
@@ -41,6 +41,10 @@
|
||||
// Determines the maximum motion amount before allowing movement
|
||||
#define MOUSE_EMULATION_DEADZONE 2
|
||||
|
||||
// Haptic capabilities (in addition to those from SDL_HapticQuery())
|
||||
#define ML_HAPTIC_GC_RUMBLE (1U << 16)
|
||||
#define ML_HAPTIC_SIMPLE_RUMBLE (1U << 17)
|
||||
|
||||
const int SdlInputHandler::k_ButtonMap[] = {
|
||||
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
||||
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
||||
@@ -112,12 +116,14 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
||||
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize the gamepad mask with currently attached gamepads to avoid
|
||||
// causing gamepads to unexpectedly disappear and reappear on the host
|
||||
@@ -141,9 +147,11 @@ SdlInputHandler::~SdlInputHandler()
|
||||
Session::get()->notifyMouseEmulationMode(false);
|
||||
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
||||
}
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (m_GamepadState[i].haptic != nullptr) {
|
||||
SDL_HapticClose(m_GamepadState[i].haptic);
|
||||
}
|
||||
#endif
|
||||
if (m_GamepadState[i].controller != nullptr) {
|
||||
SDL_GameControllerClose(m_GamepadState[i].controller);
|
||||
}
|
||||
@@ -154,8 +162,10 @@ SdlInputHandler::~SdlInputHandler()
|
||||
SDL_RemoveTimer(m_RightButtonReleaseTimer);
|
||||
SDL_RemoveTimer(m_DragTimer);
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
||||
#endif
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||
@@ -905,6 +915,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
||||
SDL_GameController* controller;
|
||||
const char* mapping;
|
||||
char guidStr[33];
|
||||
uint32_t hapticCaps;
|
||||
|
||||
controller = SDL_GameControllerOpen(event->which);
|
||||
if (controller == NULL) {
|
||||
@@ -947,10 +958,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
||||
|
||||
state->controller = controller;
|
||||
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
// Perform a no-op rumble to see if haptics are supported
|
||||
hapticCaps = SDL_GameControllerRumble(controller, 0, 0, 0) == 0 ?
|
||||
ML_HAPTIC_GC_RUMBLE : 0;
|
||||
#else
|
||||
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
|
||||
state->hapticEffectId = -1;
|
||||
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_NONE;
|
||||
if (state->haptic != nullptr) {
|
||||
// Query for supported haptic effects
|
||||
hapticCaps = SDL_HapticQuery(state->haptic);
|
||||
hapticCaps |= SDL_HapticRumbleSupported(state->haptic) ?
|
||||
ML_HAPTIC_SIMPLE_RUMBLE : 0;
|
||||
|
||||
if ((SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
||||
if (SDL_HapticRumbleSupported(state->haptic)) {
|
||||
if (SDL_HapticRumbleInit(state->haptic) == 0) {
|
||||
@@ -965,16 +987,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
||||
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_LEFTRIGHT;
|
||||
}
|
||||
}
|
||||
else {
|
||||
hapticCaps = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
|
||||
guidStr, sizeof(guidStr));
|
||||
mapping = SDL_GameControllerMapping(state->controller);
|
||||
name = SDL_GameControllerName(state->controller);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Gamepad %d (player %d) is: %s (%s -> %s)",
|
||||
"Gamepad %d (player %d) is: %s (haptic capabilities: 0x%x) (mapping: %s -> %s)",
|
||||
i,
|
||||
state->index,
|
||||
name != nullptr ? name : "<null>",
|
||||
hapticCaps,
|
||||
guidStr,
|
||||
mapping != nullptr ? mapping : "<null>");
|
||||
if (mapping != nullptr) {
|
||||
@@ -1004,9 +1031,12 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
||||
}
|
||||
|
||||
SDL_GameControllerClose(state->controller);
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (state->haptic != nullptr) {
|
||||
SDL_HapticClose(state->haptic);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Remove this from the gamepad mask in MC-mode
|
||||
if (m_MultiController) {
|
||||
@@ -1067,6 +1097,11 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
||||
return;
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (m_GamepadState[controllerNumber].controller != nullptr) {
|
||||
SDL_GameControllerRumble(m_GamepadState[controllerNumber].controller, lowFreqMotor, highFreqMotor, 30000);
|
||||
}
|
||||
#else
|
||||
// Check if the controller supports haptics (and if the controller exists at all)
|
||||
SDL_Haptic* haptic = m_GamepadState[controllerNumber].haptic;
|
||||
if (haptic == nullptr) {
|
||||
@@ -1110,7 +1145,7 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
||||
GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT*lowFreqMotor) / 65535.0),
|
||||
SDL_HAPTIC_INFINITY);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
struct GamepadState {
|
||||
SDL_GameController* controller;
|
||||
SDL_JoystickID jsId;
|
||||
short index;
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_Haptic* haptic;
|
||||
int hapticMethod;
|
||||
int hapticEffectId;
|
||||
short index;
|
||||
#endif
|
||||
|
||||
SDL_TimerID mouseEmulationTimer;
|
||||
uint32_t lastStartDownTime;
|
||||
|
||||
+41
-31
@@ -267,21 +267,28 @@ bool Session::isHardwareDecodeAvailable(SDL_Window* window,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::getDecoderCapabilities(SDL_Window* window,
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate)
|
||||
bool Session::populateDecoderProperties(SDL_Window* window)
|
||||
{
|
||||
IVideoDecoder* decoder;
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
||||
if (!chooseDecoder(m_Preferences->videoDecoderSelection,
|
||||
window,
|
||||
m_StreamConfig.enableHdr ? VIDEO_FORMAT_H265_MAIN10 :
|
||||
(m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264),
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps,
|
||||
true, false, true, decoder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int caps = decoder->getDecoderCapabilities();
|
||||
m_VideoCallbacks.capabilities = decoder->getDecoderCapabilities();
|
||||
|
||||
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
|
||||
|
||||
delete decoder;
|
||||
|
||||
return caps;
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
|
||||
@@ -343,21 +350,12 @@ bool Session::initialize()
|
||||
m_VideoCallbacks.setup = drSetup;
|
||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||
|
||||
// Slice up to 4 times for parallel decode, once slice per core
|
||||
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
||||
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Encoder configured for %d slices per frame",
|
||||
slices);
|
||||
|
||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||
m_StreamConfig.width = m_Preferences->width;
|
||||
m_StreamConfig.height = m_Preferences->height;
|
||||
m_StreamConfig.fps = m_Preferences->fps;
|
||||
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||
m_StreamConfig.packetSize = 1392;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Video bitrate: %d kbps",
|
||||
@@ -432,15 +430,6 @@ bool Session::initialize()
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the capability flags from the chosen decoder/renderer
|
||||
// Requires m_StreamConfig.supportsHevc to be initialized
|
||||
m_VideoCallbacks.capabilities |= getDecoderCapabilities(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps);
|
||||
|
||||
switch (m_Preferences->windowMode)
|
||||
{
|
||||
default:
|
||||
@@ -456,6 +445,12 @@ bool Session::initialize()
|
||||
// signals for them, if appropriate
|
||||
bool ret = validateLaunch(testWindow);
|
||||
|
||||
if (ret) {
|
||||
// Populate decoder-dependent properties.
|
||||
// Must be done after validateLaunch() since m_StreamConfig is finalized.
|
||||
ret = populateDecoderProperties(testWindow);
|
||||
}
|
||||
|
||||
SDL_DestroyWindow(testWindow);
|
||||
|
||||
if (!ret) {
|
||||
@@ -502,13 +497,14 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
||||
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||
|
||||
if (!isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps) &&
|
||||
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO) {
|
||||
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Already checked in initialize()
|
||||
!isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
if (hevcForced) {
|
||||
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
|
||||
}
|
||||
@@ -594,6 +590,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
||||
}
|
||||
|
||||
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
!m_StreamConfig.enableHdr && // HEVC Main10 was already checked for hardware decode support above
|
||||
!isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
@@ -937,6 +934,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
}
|
||||
|
||||
// isReachableOverVpn() does network I/O, so we only attempt to check
|
||||
// VPN reachability if we've already contacted the PC successfully
|
||||
if (m_Computer->isReachableOverVpn()) {
|
||||
// It looks like our route to this PC is over a VPN.
|
||||
// Treat it as remote even if the target address is in RFC 1918 address space.
|
||||
m_StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
|
||||
m_StreamConfig.packetSize = 1024;
|
||||
}
|
||||
else {
|
||||
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||
m_StreamConfig.packetSize = 1392;
|
||||
}
|
||||
|
||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||
&m_VideoCallbacks,
|
||||
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
|
||||
|
||||
@@ -61,10 +61,7 @@ private:
|
||||
|
||||
void emitLaunchWarning(QString text);
|
||||
|
||||
static
|
||||
int getDecoderCapabilities(SDL_Window* window,
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
bool populateDecoderProperties(SDL_Window* window);
|
||||
|
||||
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
|
||||
virtual bool isHardwareAccelerated() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual int getDecoderColorspace() = 0;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||
virtual void renderFrameOnMainThread() = 0;
|
||||
};
|
||||
|
||||
@@ -382,9 +382,6 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
HRESULT hr;
|
||||
bool result = false;
|
||||
|
||||
// TODO: Update for HEVC Main10
|
||||
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
||||
|
||||
if (qgetenv("DXVA2_DISABLE_DECODER_BLACKLIST") == "1") {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"DXVA2 decoder blacklist is disabled");
|
||||
@@ -432,6 +429,15 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
"GPU blacklisted for HEVC due to hybrid decode");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
break;
|
||||
case 0x1900: // Skylake
|
||||
// Blacklist these for HEVC Main10 to avoid hybrid decode.
|
||||
// Regular HEVC Main is fine though.
|
||||
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for HEVC Main10 due to hybrid decode");
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
|
||||
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
|
||||
@@ -545,6 +551,8 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
d3dpp.BackBufferWidth = currentMode.Width;
|
||||
d3dpp.BackBufferHeight = currentMode.Height;
|
||||
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
||||
|
||||
// TODO: Need to select 10-bit color format for HDR
|
||||
d3dpp.BackBufferFormat = currentMode.Format;
|
||||
}
|
||||
else {
|
||||
@@ -680,7 +688,13 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
|
||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||
|
||||
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0');
|
||||
}
|
||||
else {
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||
}
|
||||
|
||||
if (!initializeDevice(params->window, params->enableVsync)) {
|
||||
return false;
|
||||
@@ -762,6 +776,20 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
||||
}
|
||||
}
|
||||
|
||||
int DXVA2Renderer::getDecoderColorspace()
|
||||
{
|
||||
if (isDXVideoProcessorAPIBlacklisted()) {
|
||||
// StretchRect() assumes Rec 601 on Intel GPUs
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
else {
|
||||
// VideoProcessBlt() *should* properly handle whatever, since
|
||||
// we provide colorspace information. However, AMD GPUs seem to
|
||||
// always assume Rec 709, so we'll use that as our default.
|
||||
return COLORSPACE_REC_709;
|
||||
}
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrame(AVFrame *frame)
|
||||
{
|
||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||
virtual void renderFrame(AVFrame* frame) override;
|
||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
|
||||
private:
|
||||
bool initializeDecoder();
|
||||
|
||||
@@ -258,7 +258,7 @@ void Pacer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
// Count time spent in Pacer's queues
|
||||
Uint32 beforeRender = SDL_GetTicks();
|
||||
m_VideoStats->totalPacerTime += beforeRender - frame->pts;
|
||||
m_VideoStats->totalPacerTime += beforeRender - frame->pkt_dts;
|
||||
|
||||
// Render it
|
||||
m_VsyncRenderer->renderFrame(frame);
|
||||
|
||||
@@ -31,6 +31,11 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int getDecoderColorspace() {
|
||||
// Rec 601 is default
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
|
||||
virtual FramePacingConstraint getFramePacingConstraint() {
|
||||
// No pacing preference
|
||||
return PACING_ANY;
|
||||
@@ -47,9 +52,14 @@ public:
|
||||
}
|
||||
|
||||
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
|
||||
// Planar YUV 4:2:0
|
||||
SDL_assert(videoFormat != VIDEO_FORMAT_H265_MAIN10);
|
||||
return AV_PIX_FMT_YUV420P;
|
||||
if (videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
// 10-bit YUV 4:2:0
|
||||
return AV_PIX_FMT_P010;
|
||||
}
|
||||
else {
|
||||
// Planar YUV 4:2:0
|
||||
return AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
}
|
||||
|
||||
// IOverlayRenderer
|
||||
|
||||
@@ -127,10 +127,25 @@ bool SdlRenderer::isRenderThreadSupported()
|
||||
return true;
|
||||
}
|
||||
|
||||
int SdlRenderer::getDecoderCapabilities()
|
||||
{
|
||||
// Slice up to 4 times for parallel decode, once slice per core
|
||||
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Encoder configured for %d slices per frame",
|
||||
slices);
|
||||
return CAPABILITY_SLICES_PER_FRAME(slices);
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
{
|
||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
|
||||
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
// SDL doesn't support rendering YUV 10-bit textures yet
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
||||
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
||||
@@ -255,6 +270,11 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
// av_hwframe_transfer_data() can nuke frame metadata,
|
||||
// so anything other than width, height, and format must
|
||||
// be set *after* calling av_hwframe_transfer_data().
|
||||
swFrame->colorspace = frame->colorspace;
|
||||
|
||||
frame = swFrame;
|
||||
}
|
||||
|
||||
@@ -277,6 +297,18 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
switch (frame->colorspace)
|
||||
{
|
||||
case AVCOL_SPC_BT709:
|
||||
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
|
||||
break;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
default:
|
||||
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
|
||||
break;
|
||||
}
|
||||
|
||||
m_Texture = SDL_CreateTexture(m_Renderer,
|
||||
sdlFormat,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
virtual void renderFrame(AVFrame* frame) override;
|
||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||
virtual bool isRenderThreadSupported() override;
|
||||
virtual int getDecoderCapabilities() override;
|
||||
|
||||
private:
|
||||
void renderOverlay(Overlay::OverlayType type);
|
||||
|
||||
@@ -219,6 +219,13 @@ VAAPIRenderer::isDirectRenderingSupported()
|
||||
return m_WindowSystem == SDL_SYSWM_X11;
|
||||
}
|
||||
|
||||
int VAAPIRenderer::getDecoderColorspace()
|
||||
{
|
||||
// Gallium drivers don't support Rec 709 yet - https://gitlab.freedesktop.org/mesa/mesa/issues/1915
|
||||
// Intel-vaapi-driver defaults to Rec 601 - https://github.com/intel/intel-vaapi-driver/blob/021bcb79d1bd873bbd9fbca55f40320344bab866/src/i965_output_dri.c#L186
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
|
||||
void
|
||||
VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
@@ -238,6 +245,27 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
|
||||
if (m_WindowSystem == SDL_SYSWM_X11) {
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
unsigned int flags = 0;
|
||||
|
||||
// NB: Not all VAAPI drivers respect these flags. Many drivers
|
||||
// just ignore them and do the color conversion as Rec 601.
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_BT709:
|
||||
flags |= VA_SRC_BT709;
|
||||
break;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
flags |= VA_SRC_BT601;
|
||||
break;
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
flags |= VA_SRC_SMPTE_240;
|
||||
break;
|
||||
default:
|
||||
// Unknown colorspace
|
||||
SDL_assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
vaPutSurface(vaDeviceContext->display,
|
||||
surface,
|
||||
m_XWindow,
|
||||
@@ -245,7 +273,7 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
m_VideoWidth, m_VideoHeight,
|
||||
dst.x, dst.y,
|
||||
dst.w, dst.h,
|
||||
NULL, 0, 0);
|
||||
NULL, 0, flags);
|
||||
#endif
|
||||
}
|
||||
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual void renderFrame(AVFrame* frame) override;
|
||||
virtual bool needsTestFrame() override;
|
||||
virtual bool isDirectRenderingSupported() override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
|
||||
private:
|
||||
int m_WindowSystem;
|
||||
|
||||
@@ -12,11 +12,16 @@
|
||||
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
|
||||
BAIL_ON_FAIL(status, id)
|
||||
|
||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = {
|
||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats8Bit[] = {
|
||||
VDP_RGBA_FORMAT_B8G8R8A8,
|
||||
VDP_RGBA_FORMAT_R8G8B8A8
|
||||
};
|
||||
|
||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats10Bit[] = {
|
||||
VDP_RGBA_FORMAT_B10G10R10A2,
|
||||
VDP_RGBA_FORMAT_R10G10B10A2
|
||||
};
|
||||
|
||||
VDPAURenderer::VDPAURenderer()
|
||||
: m_HwContext(nullptr),
|
||||
m_PresentationQueueTarget(0),
|
||||
@@ -143,7 +148,11 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
||||
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
|
||||
VdpBool supported;
|
||||
uint32_t maxWidth, maxHeight;
|
||||
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, k_OutputFormats[i],
|
||||
VdpRGBAFormat candidateFormat =
|
||||
params->videoFormat == VIDEO_FORMAT_H265_MAIN10 ?
|
||||
k_OutputFormats10Bit[i] : k_OutputFormats8Bit[i];
|
||||
|
||||
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, candidateFormat,
|
||||
&supported, &maxWidth, &maxHeight);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -154,7 +163,7 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
||||
|
||||
if (supported) {
|
||||
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
|
||||
m_OutputSurfaceFormat = k_OutputFormats[i];
|
||||
m_OutputSurfaceFormat = candidateFormat;
|
||||
foundFormat = true;
|
||||
break;
|
||||
}
|
||||
@@ -241,6 +250,15 @@ bool VDPAURenderer::needsTestFrame()
|
||||
return true;
|
||||
}
|
||||
|
||||
int VDPAURenderer::getDecoderColorspace()
|
||||
{
|
||||
// VDPAU defaults to Rec 601.
|
||||
// https://http.download.nvidia.com/XFree86/vdpau/doxygen/html/group___vdp_video_mixer.html#ga65580813e9045d94b739ed2bb8b62b46
|
||||
//
|
||||
// AMD and Nvidia GPUs both correctly process Rec 601, so let's not try our luck using a non-default colorspace.
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
VdpStatus status;
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||
virtual void renderFrame(AVFrame* frame) override;
|
||||
virtual bool needsTestFrame() override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
|
||||
private:
|
||||
uint32_t m_VideoWidth, m_VideoHeight;
|
||||
@@ -33,7 +34,8 @@ private:
|
||||
int m_NextSurfaceIndex;
|
||||
|
||||
#define OUTPUT_SURFACE_FORMAT_COUNT 2
|
||||
static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||
static const VdpRGBAFormat k_OutputFormats8Bit[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||
static const VdpRGBAFormat k_OutputFormats10Bit[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||
|
||||
VdpGetErrorString* m_VdpGetErrorString;
|
||||
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <dispatch/dispatch.h>
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
class VTRenderer : public IFFmpegRenderer
|
||||
{
|
||||
@@ -234,6 +235,52 @@ public:
|
||||
"No HW accelerated HEVC decode via VT");
|
||||
return false;
|
||||
}
|
||||
|
||||
// HEVC Main10 requires more extensive checks because there's no
|
||||
// simple API to check for Main10 hardware decoding, and if we don't
|
||||
// have it, we'll silently get software decoding with horrible performance.
|
||||
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
|
||||
if (__builtin_available(macOS 10.14, *)) {
|
||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
if (device == nullptr) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get default Metal device");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude all GPUs earlier than macOSGPUFamily2
|
||||
// https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1
|
||||
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
|
||||
if ([device.name containsString:@"Intel"]) {
|
||||
// 500-series Intel GPUs are Skylake and don't support Main10 hardware decoding
|
||||
if ([device.name containsString:@" 5"]) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No HEVC Main10 support on Skylake iGPU");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ([device.name containsString:@"AMD"]) {
|
||||
// FirePro D, M200, and M300 series GPUs don't support Main10 hardware decoding
|
||||
if ([device.name containsString:@"FirePro D"] ||
|
||||
[device.name containsString:@" M2"] ||
|
||||
[device.name containsString:@" M3"]) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No HEVC Main10 support on AMD GPUs until Polaris");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Fail closed for HEVC Main10 if we're not on 10.14+
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No HEVC Main10 support on < macOS 10.14");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
@@ -50,6 +50,11 @@ int FFmpegVideoDecoder::getDecoderCapabilities()
|
||||
return m_BackendRenderer->getDecoderCapabilities();
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::getDecoderColorspace()
|
||||
{
|
||||
return m_FrontendRenderer->getDecoderColorspace();
|
||||
}
|
||||
|
||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
||||
const enum AVPixelFormat* pixFmts)
|
||||
{
|
||||
@@ -264,13 +269,24 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
||||
// now to see if things will actually work when the video stream
|
||||
// comes in.
|
||||
if (testFrame) {
|
||||
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
switch (params->videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
||||
m_Pkt.size = sizeof(k_H264TestFrame);
|
||||
}
|
||||
else {
|
||||
m_Pkt.data = (uint8_t*)k_HEVCTestFrame;
|
||||
m_Pkt.size = sizeof(k_HEVCTestFrame);
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
m_Pkt.data = (uint8_t*)k_HEVCMainTestFrame;
|
||||
m_Pkt.size = sizeof(k_HEVCMainTestFrame);
|
||||
break;
|
||||
case VIDEO_FORMAT_H265_MAIN10:
|
||||
m_Pkt.data = (uint8_t*)k_HEVCMain10TestFrame;
|
||||
m_Pkt.size = sizeof(k_HEVCMain10TestFrame);
|
||||
break;
|
||||
default:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No test frame for format: %x",
|
||||
params->videoFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
||||
@@ -720,8 +736,11 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
// Restore default log level after a successful decode
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
|
||||
// Store the presentation time
|
||||
frame->pts = du->presentationTimeMs;
|
||||
|
||||
// Capture a frame timestamp to measuring pacing delay
|
||||
frame->pts = SDL_GetTicks();
|
||||
frame->pkt_dts = SDL_GetTicks();
|
||||
|
||||
// Count time in avcodec_send_packet() and avcodec_receive_frame()
|
||||
// as time spent decoding
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||
virtual bool isHardwareAccelerated() override;
|
||||
virtual int getDecoderCapabilities() override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||
virtual void renderFrameOnMainThread() override;
|
||||
|
||||
@@ -66,5 +67,6 @@ private:
|
||||
bool m_TestOnly;
|
||||
|
||||
static const uint8_t k_H264TestFrame[];
|
||||
static const uint8_t k_HEVCTestFrame[];
|
||||
static const uint8_t k_HEVCMainTestFrame[];
|
||||
static const uint8_t k_HEVCMain10TestFrame[];
|
||||
};
|
||||
|
||||
@@ -10,10 +10,21 @@ const uint8_t FFmpegVideoDecoder::k_H264TestFrame[] = {
|
||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
|
||||
};
|
||||
|
||||
// 720p 60 FPS HEVC with 1 reference frame
|
||||
const uint8_t FFmpegVideoDecoder::k_HEVCTestFrame[] = {
|
||||
// 720p 60 FPS HEVC Main with 1 reference frame
|
||||
const uint8_t FFmpegVideoDecoder::k_HEVCMainTestFrame[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
||||
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
|
||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
|
||||
};
|
||||
|
||||
// 720p 60 FPS HEVC Main10 with 1 reference frame
|
||||
const uint8_t FFmpegVideoDecoder::k_HEVCMain10TestFrame[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
||||
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x12, 0xd9, 0x6b, 0x4a, 0x42, 0x59, 0x2e, 0x30, 0x16, 0xa0, 0xc0, 0xc0, 0xd4, 0x82, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x78, 0x5f, 0x1a, 0x2d,
|
||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf3, 0xc0, 0x4c, 0x90,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xbc, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x26, 0x43, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x05, 0xb4,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x2d, 0xc3, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x24,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x36, 0x83, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4
|
||||
};
|
||||
|
||||
@@ -31,6 +31,12 @@ SLVideoDecoder::getDecoderCapabilities()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
SLVideoDecoder::getDecoderColorspace()
|
||||
{
|
||||
return COLORSPACE_REC_709;
|
||||
}
|
||||
|
||||
bool
|
||||
SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||
{
|
||||
@@ -59,6 +65,7 @@ SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||
return false;
|
||||
}
|
||||
|
||||
SLVideo_SetStreamVideoTransferMatrix(m_VideoStream, k_ESLVideoTransferMatrix_BT709);
|
||||
SLVideo_SetStreamTargetFramerate(m_VideoStream, params->frameRate, 1);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -12,6 +12,7 @@ public:
|
||||
virtual bool initialize(PDECODER_PARAMETERS params);
|
||||
virtual bool isHardwareAccelerated();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual int getDecoderColorspace();
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
// Unused since rendering is done directly from the decode thread
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.1.1
|
||||
1.2.0
|
||||
Submodule moonlight-common-c/moonlight-common-c updated: 7de68207f0...e60a7ef75f
+15
-8
@@ -11,14 +11,21 @@ if [ "$BUILD_CONFIG" != "Debug" ] && [ "$BUILD_CONFIG" != "Release" ]; then
|
||||
fail "Invalid build configuration - expected 'Debug' or 'Release'"
|
||||
fi
|
||||
|
||||
[ "$SIGNING_KEY" == "" ] || git diff-index --quiet HEAD -- || fail "Signed release builds must not have unstaged changes!"
|
||||
|
||||
BUILD_ROOT=$PWD/build
|
||||
SOURCE_ROOT=$PWD
|
||||
BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG
|
||||
INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG
|
||||
VERSION=`cat $SOURCE_ROOT/app/version.txt`
|
||||
|
||||
if [ "$SIGNING_PROVIDER_SHORTNAME" == "" ]; then
|
||||
SIGNING_PROVIDER_SHORTNAME=$SIGNING_IDENTITY
|
||||
fi
|
||||
if [ "$SIGNING_IDENTITY" == "" ]; then
|
||||
SIGNING_IDENTITY=$SIGNING_PROVIDER_SHORTNAME
|
||||
fi
|
||||
|
||||
[ "$SIGNING_IDENTITY" == "" ] || git diff-index --quiet HEAD -- || fail "Signed release builds must not have unstaged changes!"
|
||||
|
||||
echo Cleaning output directories
|
||||
rm -rf $BUILD_FOLDER
|
||||
rm -rf $INSTALLER_FOLDER
|
||||
@@ -48,14 +55,14 @@ if [ "$BUILD_CONFIG" == "Debug" ]; then EXTRA_ARGS="$EXTRA_ARGS -use-debug-libs"
|
||||
echo Extra deployment arguments: $EXTRA_ARGS
|
||||
macdeployqt $BUILD_FOLDER/app/Moonlight.app $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app/gui -appstore-compliant || fail "macdeployqt failed!"
|
||||
|
||||
if [ "$SIGNING_KEY" != "" ]; then
|
||||
if [ "$SIGNING_IDENTITY" != "" ]; then
|
||||
echo Signing app bundle
|
||||
codesign --force --deep --options runtime --timestamp --sign $SIGNING_KEY $BUILD_FOLDER/app/Moonlight.app || fail "Signing failed!"
|
||||
codesign --force --deep --options runtime --timestamp --sign "$SIGNING_IDENTITY" $BUILD_FOLDER/app/Moonlight.app || fail "Signing failed!"
|
||||
fi
|
||||
|
||||
echo Creating DMG
|
||||
if [ "$SIGNING_KEY" != "" ]; then
|
||||
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER --identity=$SIGNING_KEY || fail "create-dmg failed!"
|
||||
if [ "$SIGNING_IDENTITY" != "" ]; then
|
||||
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER --identity="$SIGNING_IDENTITY" || fail "create-dmg failed!"
|
||||
else
|
||||
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER
|
||||
case $? in
|
||||
@@ -67,13 +74,13 @@ fi
|
||||
|
||||
if [ "$NOTARY_USERNAME" != "" ] && [ "$NOTARY_PASSWORD" != "" ]; then
|
||||
echo Uploading to App Notary service
|
||||
xcrun altool -t osx -f $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg --primary-bundle-id com.moonlight-stream.Moonlight --notarize-app --username "$NOTARY_USERNAME" --password "$NOTARY_PASSWORD" --asc-provider $SIGNING_KEY || fail "Notary submission failed"
|
||||
xcrun altool -t osx -f $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg --primary-bundle-id com.moonlight-stream.Moonlight --notarize-app --username "$NOTARY_USERNAME" --password "$NOTARY_PASSWORD" --asc-provider "$SIGNING_PROVIDER_SHORTNAME" || fail "Notary submission failed"
|
||||
|
||||
echo Waiting 5 minutes for notarization to complete
|
||||
sleep 300
|
||||
|
||||
echo Getting notarization status
|
||||
xcrun altool -t osx --notarization-history 0 --username "$NOTARY_USERNAME" --password "$NOTARY_PASSWORD" --asc-provider $SIGNING_KEY || fail "Unable to fetch notarization history!"
|
||||
xcrun altool -t osx --notarization-history 0 --username "$NOTARY_USERNAME" --password "$NOTARY_PASSWORD" --asc-provider "$SIGNING_PROVIDER_SHORTNAME" || fail "Unable to fetch notarization history!"
|
||||
|
||||
echo Stapling notary ticket to DMG
|
||||
xcrun stapler staple -v $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg || fail "Notary ticket stapling failed!"
|
||||
|
||||
@@ -159,6 +159,12 @@ if "%SIGN%"=="1" (
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
if "%ML_SYMBOL_STORE%" NEQ "" (
|
||||
echo Publishing binaries to symbol store: %ML_SYMBOL_STORE%
|
||||
symstore add /r /f %DEPLOY_FOLDER%\*.* /s %ML_SYMBOL_STORE% /t Moonlight
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
echo Building MSI
|
||||
msbuild %SOURCE_ROOT%\wix\Moonlight\Moonlight.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
# Run from root of the Git repo to archive a source tarball
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
git diff-index --quiet HEAD -- || fail "Source archives must not have unstaged changes!"
|
||||
|
||||
BUILD_ROOT=$PWD/build
|
||||
ARCHIVE_FOLDER=$BUILD_ROOT/source
|
||||
VERSION=`cat app/version.txt`
|
||||
|
||||
echo Cleaning output directories
|
||||
rm -rf $ARCHIVE_FOLDER
|
||||
mkdir $BUILD_ROOT
|
||||
mkdir $ARCHIVE_FOLDER
|
||||
|
||||
scripts/git-archive-all.sh --format tar.gz $ARCHIVE_FOLDER/MoonlightSrc-$VERSION.tar.gz || fail "Archive failed"
|
||||
|
||||
echo Archive successful
|
||||
Executable
+315
@@ -0,0 +1,315 @@
|
||||
#!/bin/bash -
|
||||
#
|
||||
# File: git-archive-all.sh
|
||||
#
|
||||
# Description: A utility script that builds an archive file(s) of all
|
||||
# git repositories and submodules in the current path.
|
||||
# Useful for creating a single tarfile of a git super-
|
||||
# project that contains other submodules.
|
||||
#
|
||||
# Examples: Use git-archive-all.sh to create archive distributions
|
||||
# from git repositories. To use, simply do:
|
||||
#
|
||||
# cd $GIT_DIR; git-archive-all.sh
|
||||
#
|
||||
# where $GIT_DIR is the root of your git superproject.
|
||||
#
|
||||
# License: GPL3+
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# DEBUGGING
|
||||
set -e # Exit on error (i.e., "be strict").
|
||||
set -C # noclobber
|
||||
|
||||
# TRAP SIGNALS
|
||||
trap 'cleanup' QUIT EXIT
|
||||
|
||||
# For security reasons, explicitly set the internal field separator
|
||||
# to newline, space, tab
|
||||
OLD_IFS=$IFS
|
||||
IFS="$(printf '\n \t')"
|
||||
|
||||
function cleanup () {
|
||||
rm -f $TMPFILE
|
||||
rm -f $TMPLIST
|
||||
rm -f $TOARCHIVE
|
||||
IFS="$OLD_IFS"
|
||||
}
|
||||
|
||||
function usage () {
|
||||
echo "Usage is as follows:"
|
||||
echo
|
||||
echo "$PROGRAM <--version>"
|
||||
echo " Prints the program version number on a line by itself and exits."
|
||||
echo
|
||||
echo "$PROGRAM <--usage|--help|-?>"
|
||||
echo " Prints this usage output and exits."
|
||||
echo
|
||||
echo "$PROGRAM [--format <fmt>] [--prefix <path>] [--verbose|-v] [--separate|-s]"
|
||||
echo " [--worktree-attributes] [--tree-ish|-t <tree-ish>] [output_file]"
|
||||
echo " Creates an archive for the entire git superproject, and its submodules"
|
||||
echo " using the passed parameters, described below."
|
||||
echo
|
||||
echo " If '--format' is specified, the archive is created with the named"
|
||||
echo " git archiver backend. Obviously, this must be a backend that git archive"
|
||||
echo " understands. The format defaults to 'tar' if not specified."
|
||||
echo
|
||||
echo " If '--prefix' is specified, the archive's superproject and all submodules"
|
||||
echo " are created with the <path> prefix named. The default is to not use one."
|
||||
echo
|
||||
echo " If '--worktree-attributes' is specified, the invidual archive commands will"
|
||||
echo " look for attributes in .gitattributes in the working directory too."
|
||||
echo
|
||||
echo " If '--separate' or '-s' is specified, individual archives will be created"
|
||||
echo " for each of the superproject itself and its submodules. The default is to"
|
||||
echo " concatenate individual archives into one larger archive."
|
||||
echo
|
||||
echo " If '--tree-ish' is specified, the archive will be created based on whatever"
|
||||
echo " you define the tree-ish to be. Branch names, commit hash, etc. are acceptable."
|
||||
echo " Defaults to HEAD if not specified. See git archive's documentation for more"
|
||||
echo " information on what a tree-ish is."
|
||||
echo
|
||||
echo " If 'output_file' is specified, the resulting archive is created as the"
|
||||
echo " file named. This parameter is essentially a path that must be writeable."
|
||||
echo " When combined with '--separate' ('-s') this path must refer to a directory."
|
||||
echo " Without this parameter or when combined with '--separate' the resulting"
|
||||
echo " archive(s) are named with a dot-separated path of the archived directory and"
|
||||
echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')."
|
||||
echo
|
||||
echo " The special value '-' (single dash) is treated as STDOUT and, when used, the"
|
||||
echo " --separate option is ignored. Use a double-dash to separate the outfile from"
|
||||
echo " the value of previous options. For example, to write a .zip file to STDOUT:"
|
||||
echo
|
||||
echo " ./$PROGRAM --format zip -- -"
|
||||
echo
|
||||
echo " If '--verbose' or '-v' is specified, progress will be printed."
|
||||
}
|
||||
|
||||
function version () {
|
||||
echo "$PROGRAM version $VERSION"
|
||||
}
|
||||
|
||||
# Internal variables and initializations.
|
||||
readonly PROGRAM=`basename "$0"`
|
||||
readonly VERSION=0.3
|
||||
|
||||
SEPARATE=0
|
||||
VERBOSE=0
|
||||
|
||||
TARCMD=`command -v gtar || command -v gnutar || command -v tar`
|
||||
FORMAT=tar
|
||||
PREFIX=
|
||||
TREEISH=HEAD
|
||||
ARCHIVE_OPTS=
|
||||
|
||||
# RETURN VALUES/EXIT STATUS CODES
|
||||
readonly E_BAD_OPTION=254
|
||||
readonly E_UNKNOWN=255
|
||||
|
||||
# Process command-line arguments.
|
||||
while test $# -gt 0; do
|
||||
if [ x"$1" == x"--" ]; then
|
||||
# detect argument termination
|
||||
shift
|
||||
break
|
||||
fi
|
||||
case $1 in
|
||||
--format )
|
||||
shift
|
||||
FORMAT="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--prefix )
|
||||
shift
|
||||
PREFIX="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--worktree-attributes )
|
||||
ARCHIVE_OPTS+=" $1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--separate | -s )
|
||||
shift
|
||||
SEPARATE=1
|
||||
;;
|
||||
|
||||
--tree-ish | -t )
|
||||
shift
|
||||
TREEISH="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--version )
|
||||
version
|
||||
exit
|
||||
;;
|
||||
|
||||
--verbose | -v )
|
||||
shift
|
||||
VERBOSE=1
|
||||
;;
|
||||
|
||||
-? | --usage | --help )
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
|
||||
-* )
|
||||
echo "Unrecognized option: $1" >&2
|
||||
usage
|
||||
exit $E_BAD_OPTION
|
||||
;;
|
||||
|
||||
* )
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
OLD_PWD="`pwd`"
|
||||
TMPDIR=${TMPDIR:-/tmp}
|
||||
TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress
|
||||
TMPLIST=`mktemp "$TMPDIR/$PROGRAM.submodules.XXXXXX"`
|
||||
TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"`
|
||||
OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default
|
||||
|
||||
if [ ! -z "$1" ]; then
|
||||
OUT_FILE="$1"
|
||||
if [ "-" == "$OUT_FILE" ]; then
|
||||
SEPARATE=0
|
||||
fi
|
||||
shift
|
||||
fi
|
||||
|
||||
# Validate parameters; error early, error often.
|
||||
if [ "-" == "$OUT_FILE" -o $SEPARATE -ne 1 ] && [ "$FORMAT" == "tar" -a `$TARCMD --help | grep -q -- "--concatenate"; echo $?` -ne 0 ]; then
|
||||
echo "Your 'tar' does not support the '--concatenate' option, which we need"
|
||||
echo "to produce a single tarfile. Either install a compatible tar (such as"
|
||||
echo "gnutar), or invoke $PROGRAM with the '--separate' option."
|
||||
exit
|
||||
elif [ $SEPARATE -eq 1 -a ! -d "$OUT_FILE" ]; then
|
||||
echo "When creating multiple archives, your destination must be a directory."
|
||||
echo "If it's not, you risk being surprised when your files are overwritten."
|
||||
exit
|
||||
elif [ `git config -l | grep -q '^core\.bare=true'; echo $?` -eq 0 ]; then
|
||||
echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)."
|
||||
exit
|
||||
fi
|
||||
|
||||
# Create the superproject's git-archive
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "creating superproject archive..."
|
||||
fi
|
||||
rm -f $TMPDIR/$(basename "$(pwd)").$FORMAT
|
||||
git archive --format=$FORMAT --prefix="$PREFIX" $ARCHIVE_OPTS $TREEISH > $TMPDIR/$(basename "$(pwd)").$FORMAT
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
echo $TMPDIR/$(basename "$(pwd)").$FORMAT >| $TMPFILE # clobber on purpose
|
||||
superfile=`head -n 1 $TMPFILE`
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "looking for subprojects..."
|
||||
fi
|
||||
# find all '.git' dirs, these show us the remaining to-be-archived dirs
|
||||
# we only want directories that are below the current directory
|
||||
find . -mindepth 2 -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
|
||||
# as of version 1.7.8, git places the submodule .git directories under the superprojects .git dir
|
||||
# the submodules get a .git file that points to their .git dir. we need to find all of these too
|
||||
find . -mindepth 2 -name '.git' -type f -print | xargs grep -l "gitdir" | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
echo " found:"
|
||||
cat $TOARCHIVE | while read arch
|
||||
do
|
||||
echo " $arch"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "archiving submodules..."
|
||||
fi
|
||||
git submodule >>"$TMPLIST"
|
||||
while read path; do
|
||||
TREEISH=$(grep "^ .*${path%/} " "$TMPLIST" | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path
|
||||
cd "$path"
|
||||
rm -f "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT
|
||||
git archive --format=$FORMAT --prefix="${PREFIX}$path" $ARCHIVE_OPTS ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT
|
||||
if [ $FORMAT == 'zip' ]; then
|
||||
# delete the empty directory entry; zipped submodules won't unzip if we don't do this
|
||||
zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null 2>&1 || true # remove trailing '/'
|
||||
fi
|
||||
echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE
|
||||
cd "$OLD_PWD"
|
||||
done < $TOARCHIVE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "concatenating archives into single archive..."
|
||||
fi
|
||||
# Concatenate archives into a super-archive.
|
||||
if [ $SEPARATE -eq 0 -o "-" == "$OUT_FILE" ]; then
|
||||
if [ $FORMAT == 'tar.gz' ]; then
|
||||
gunzip $superfile
|
||||
superfile=${superfile:0: -3} # Remove '.gz'
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
gunzip $file
|
||||
file=${file:0: -3}
|
||||
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
|
||||
done
|
||||
gzip $superfile
|
||||
superfile=$superfile.gz
|
||||
elif [ $FORMAT == 'tar' ]; then
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
|
||||
done
|
||||
elif [ $FORMAT == 'zip' ]; then
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
# zip incorrectly stores the full path, so cd and then grow
|
||||
cd `dirname "$file"`
|
||||
zip -g "$superfile" `basename "$file"` && rm -f "$file"
|
||||
done
|
||||
cd "$OLD_PWD"
|
||||
fi
|
||||
|
||||
echo "$superfile" >| $TMPFILE # clobber on purpose
|
||||
fi
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "moving archive to $OUT_FILE..."
|
||||
fi
|
||||
while read file; do
|
||||
if [ "-" == "$OUT_FILE" ]; then
|
||||
cat "$file" && rm -f "$file"
|
||||
else
|
||||
mv "$file" "$OUT_FILE"
|
||||
fi
|
||||
done < $TMPFILE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
@@ -17,21 +17,6 @@
|
||||
<![CDATA[VersionNT >= v6.1]]>
|
||||
</bal:Condition>
|
||||
|
||||
<!-- https://stackoverflow.com/questions/46637094/how-can-i-find-the-upgrade-code-for-an-installed-msi-file -->
|
||||
<?if $(env.ARCH) ~= x86 ?>
|
||||
<util:ProductSearch Id="Find_VCRedist_x86"
|
||||
UpgradeCode="{C78B8E51-0C65-377E-85D1-282F689FE505}"
|
||||
Result="state"
|
||||
Variable="VCRedist_Present" />
|
||||
<?elseif $(env.ARCH) ~= x64 ?>
|
||||
<util:ProductSearch Id="Find_VCRedist_x64"
|
||||
UpgradeCode="{9B0BAA88-E15F-3A1F-ACC0-B206E9DDF71C}"
|
||||
Result="state"
|
||||
Variable="VCRedist_Present" />
|
||||
<?else ?>
|
||||
<?error Bad ARCH value ?>
|
||||
<?endif ?>
|
||||
|
||||
<?if $(env.ARCH) ~= x64 ?>
|
||||
<bal:Condition Message="This Moonlight installer requires a 64-bit (x64) OS. Please download the 32-bit (x86) installer.">
|
||||
VersionNT64
|
||||
@@ -83,7 +68,6 @@
|
||||
<ExePackage Cache="no"
|
||||
PerMachine="yes"
|
||||
Permanent="yes"
|
||||
InstallCondition="VCRedist_Present = 2"
|
||||
Vital="yes"
|
||||
Compressed="yes"
|
||||
SourceFile="$(env.VCToolsRedistDir)vcredist_$(env.ARCH).exe"
|
||||
|
||||
Reference in New Issue
Block a user