mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-13 20:14:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
719024ec01 | ||
|
|
f0659b4f3c | ||
|
|
a649f2736f | ||
|
|
046ccb1dee | ||
|
|
d340556c28 | ||
|
|
32250db126 | ||
|
|
cb8c30db2e | ||
|
|
dacd5a9e3d | ||
|
|
859d8b96a7 | ||
|
|
02981e3cee | ||
|
|
49892b3ce8 | ||
|
|
b223e7ec36 | ||
|
|
0926337c1d | ||
|
|
346e72a678 | ||
|
|
d612e47b62 | ||
|
|
c138cbf994 | ||
|
|
dfe275ab67 | ||
|
|
d6cfbdb273 | ||
|
|
8a27fa7bb5 | ||
|
|
b9a6fb1fe5 | ||
|
|
4b6194e15e | ||
|
|
e1b3f90f8b | ||
|
|
c5cc0b8c59 | ||
|
|
5c849ec666 | ||
|
|
c2a30cbe19 | ||
|
|
31eddd8da0 | ||
|
|
abf2a14ee6 | ||
|
|
e555874485 | ||
|
|
a7aef8f147 | ||
|
|
5b8c11f1a9 | ||
|
|
645040c943 | ||
|
|
fcc5ea348c | ||
|
|
3fbb45448f | ||
|
|
8a83289709 | ||
|
|
08175de069 | ||
|
|
808933cd70 | ||
|
|
384dd2adab | ||
|
|
4a37952b41 | ||
|
|
1046d264d8 | ||
|
|
bb235ab1ae | ||
|
|
ef88b83727 | ||
|
|
5612e9864e | ||
|
|
8302187dee | ||
|
|
77d07c7c4d | ||
|
|
b2265080fd | ||
|
|
0d70bd9ac0 | ||
|
|
8f63d59b81 | ||
|
|
0395c0cc95 | ||
|
|
72065412a3 | ||
|
|
7af2cdd0ef | ||
|
|
67c801c4e7 | ||
|
|
f7c8ad4c5a | ||
|
|
61092b34de | ||
|
|
1e9e96fb8c | ||
|
|
17d1ced07c | ||
|
|
a26ced04ff | ||
|
|
154b5b6ded | ||
|
|
8219dbef6f | ||
|
|
3da724e76a | ||
|
|
1616d237ae | ||
|
|
3d21facc9f | ||
|
|
fead2a08a3 | ||
|
|
d8acf24af0 | ||
|
|
97412b3c79 | ||
|
|
6145913068 | ||
|
|
e56bb6785f | ||
|
|
6d63d6c54a | ||
|
|
6e8a103e70 | ||
|
|
00d3530f64 | ||
|
|
1e6ca18193 | ||
|
|
5a32992497 | ||
|
|
79ba00a2f4 | ||
|
|
fdfaf8ec70 | ||
|
|
162c58b697 | ||
|
|
75959ca692 | ||
|
|
06fbcd19c8 | ||
|
|
36e0468a99 | ||
|
|
c4d85cf928 | ||
|
|
daf8495acb | ||
|
|
9999ded933 | ||
|
|
80128e8293 | ||
|
|
9579b2c85e | ||
|
|
eb7f184848 | ||
|
|
71b21920e2 | ||
|
|
91b787a5be | ||
|
|
11f659aadb | ||
|
|
0870dd15a8 | ||
|
|
7cd52041d8 | ||
|
|
f2bcb49478 | ||
|
|
60a4bda94f | ||
|
|
54cfc46500 | ||
|
|
c09559ade5 | ||
|
|
3e9aea1f7a | ||
|
|
f0c292f508 | ||
|
|
1444ba2c35 | ||
|
|
fac4c85133 | ||
|
|
97a09e0571 | ||
|
|
c7121516c1 | ||
|
|
56a5fbf63b | ||
|
|
80604a752b | ||
|
|
3fe6e18609 | ||
|
|
47f3f30d83 | ||
|
|
085a904970 | ||
|
|
0fa7723f51 | ||
|
|
84ea483b4c | ||
|
|
4de3aa96b6 | ||
|
|
1193943228 | ||
|
|
2959faca0c | ||
|
|
d0df4fa3a0 | ||
|
|
0a301dd2f9 | ||
|
|
43e3679297 | ||
|
|
0455063854 | ||
|
|
12d90d77c7 | ||
|
|
61d7749aaf | ||
|
|
69fb4a0c37 | ||
|
|
d2c9f058f8 | ||
|
|
acaa3afa0f | ||
|
|
3c58e21cbf | ||
|
|
9d3c5b9bc6 | ||
|
|
ce34358d46 | ||
|
|
278bcec238 | ||
|
|
128f458848 | ||
|
|
7ae2bcd6db | ||
|
|
130f252f97 | ||
|
|
d339e05a3b | ||
|
|
933f4813aa | ||
|
|
f14e71c1d6 | ||
|
|
d78dbd7b0f | ||
|
|
71a6ef6eba | ||
|
|
4873862d16 | ||
|
|
425efe3d72 | ||
|
|
666dc13c01 | ||
|
|
267f9f5759 | ||
|
|
c8d8c00f84 | ||
|
|
dcc567ebda | ||
|
|
a199a3ccc3 | ||
|
|
8159e07427 | ||
|
|
32d70758c5 | ||
|
|
3a9e284f29 | ||
|
|
c658daeac6 | ||
|
|
f605169b15 | ||
|
|
f82b495bcc | ||
|
|
33b44a87c5 | ||
|
|
a88d994fcb | ||
|
|
dd9224d9a6 | ||
|
|
48728efee0 | ||
|
|
043c55ae66 | ||
|
|
fa3f822cf1 | ||
|
|
5afab126b3 | ||
|
|
bee8378795 | ||
|
|
cbd42f4a58 | ||
|
|
3333fd7fc8 | ||
|
|
142258b4b5 | ||
|
|
01213b58ba | ||
|
|
51df7032bb | ||
|
|
c033ce5ecf | ||
|
|
bfaf57cfd0 | ||
|
|
6c523570e5 | ||
|
|
047e5bdd81 | ||
|
|
6b1559d397 | ||
|
|
a3167b8152 | ||
|
|
cbeb23f16a | ||
|
|
b4a6512784 | ||
|
|
1af2742013 | ||
|
|
8048938158 | ||
|
|
3689c7f982 | ||
|
|
96c7057fee | ||
|
|
df7c4c82a9 | ||
|
|
18d8db338b | ||
|
|
9637639e31 | ||
|
|
17abe8f164 | ||
|
|
badaaece04 | ||
|
|
56119eebae | ||
|
|
c17c8e2a2c | ||
|
|
e45a60f2ed | ||
|
|
ebe9356937 | ||
|
|
0094305299 | ||
|
|
b27050d3bf | ||
|
|
d941e596ad | ||
|
|
ddaec92da5 | ||
|
|
1c0a4c92a8 | ||
|
|
383c00afbd | ||
|
|
d3b2bff8ad | ||
|
|
77aaa6141d | ||
|
|
2bb1af61d0 | ||
|
|
40b29472b3 | ||
|
|
5d5029de59 | ||
|
|
cc96e6a222 | ||
|
|
581c9438ca | ||
|
|
54bd427f26 | ||
|
|
c55d184195 | ||
|
|
b279f94789 | ||
|
|
f3a897e99a | ||
|
|
392b1b5904 | ||
|
|
4daa8066a6 | ||
|
|
8a89951286 | ||
|
|
c41ed55291 |
@@ -139,7 +139,7 @@ private:
|
||||
L"SS2OSD.dll", // ASUS Sonic Studio 2
|
||||
L"Nahimic2OSD.dll",
|
||||
L"NahimicMSIOSD.dll",
|
||||
L"nhAsusPhoebusOSD.dll" // ASUS Phoebus
|
||||
L"nhAsusPhoebusOSD.dll", // ASUS Phoebus
|
||||
|
||||
// This DLL has been seen in several crash reports. Some Googling
|
||||
// suggests it's highly unstable and causes issues in many games.
|
||||
|
||||
@@ -43,12 +43,12 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
|
||||
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
|
||||
* Select **MSVC 2019** option during Qt installation. MinGW is not supported.
|
||||
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
||||
* [WiX Toolset](https://wixtoolset.org/releases/) v3.11 or later (only if building installers for non-development PCs)
|
||||
* [WiX Toolset](https://wixtoolset.org/releases/v3-14-0-5722/) v3.14.0.5722 or later (only if building installers for non-development PCs)
|
||||
|
||||
### macOS Build Requirements
|
||||
* Qt 5.15 SDK or later
|
||||
* macOS High Sierra (10.13) or later
|
||||
* Xcode 11
|
||||
* Qt 6.2 SDK or later
|
||||
* macOS Mojave (10.14) or later
|
||||
* Xcode 13
|
||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||
|
||||
### Linux/Unix Build Requirements
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13</string>
|
||||
<string>10.14</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
|
||||
+1
-1
Submodule app/SDL_GameControllerDB updated: 376c576317...2924d8a562
+30
-7
@@ -33,19 +33,21 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
win32 {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||
|
||||
contains(QT_ARCH, i386) {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/x86
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/x86
|
||||
}
|
||||
contains(QT_ARCH, x86_64) {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/x64
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/x64
|
||||
}
|
||||
contains(QT_ARCH, arm64) {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/arm64
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/arm64
|
||||
}
|
||||
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib qwave.lib
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
|
||||
}
|
||||
macx {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include
|
||||
@@ -88,6 +90,11 @@ unix:!macx {
|
||||
PKGCONFIG += libdrm
|
||||
CONFIG += libdrm
|
||||
}
|
||||
|
||||
packagesExist(ffnvcodec) {
|
||||
PKGCONFIG += ffnvcodec
|
||||
CONFIG += cuda
|
||||
}
|
||||
}
|
||||
|
||||
packagesExist(wayland-client) {
|
||||
@@ -118,6 +125,7 @@ macx {
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
backend/nvaddress.cpp \
|
||||
backend/nvapp.cpp \
|
||||
main.cpp \
|
||||
backend/computerseeker.cpp \
|
||||
@@ -135,7 +143,6 @@ SOURCES += \
|
||||
settings/mappingfetcher.cpp \
|
||||
settings/streamingpreferences.cpp \
|
||||
streaming/input/abstouch.cpp \
|
||||
streaming/input/clipboard.cpp \
|
||||
streaming/input/gamepad.cpp \
|
||||
streaming/input/input.cpp \
|
||||
streaming/input/keyboard.cpp \
|
||||
@@ -156,6 +163,7 @@ SOURCES += \
|
||||
wm.cpp
|
||||
|
||||
HEADERS += \
|
||||
backend/nvaddress.h \
|
||||
backend/nvapp.h \
|
||||
settings/compatfetcher.h \
|
||||
settings/mappingfetcher.h \
|
||||
@@ -195,7 +203,6 @@ ffmpeg {
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
||||
streaming/video/ffmpeg-renderers/cuda.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
||||
|
||||
@@ -203,7 +210,6 @@ ffmpeg {
|
||||
streaming/video/ffmpeg.h \
|
||||
streaming/video/ffmpeg-renderers/renderer.h \
|
||||
streaming/video/ffmpeg-renderers/sdlvid.h \
|
||||
streaming/video/ffmpeg-renderers/cuda.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
|
||||
}
|
||||
@@ -260,6 +266,13 @@ libdrm {
|
||||
LIBS += -ldl
|
||||
}
|
||||
}
|
||||
cuda {
|
||||
message(CUDA support enabled)
|
||||
|
||||
DEFINES += HAVE_CUDA
|
||||
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
|
||||
}
|
||||
config_EGL {
|
||||
message(EGL renderer selected)
|
||||
|
||||
@@ -332,7 +345,17 @@ TRANSLATIONS += \
|
||||
languages/qml_fr.ts \
|
||||
languages/qml_nb_NO.ts \
|
||||
languages/qml_ru.ts \
|
||||
languages/qml_es.ts
|
||||
languages/qml_es.ts \
|
||||
languages/qml_ja.ts \
|
||||
languages/qml_vi.ts \
|
||||
languages/qml_th.ts \
|
||||
languages/qml_ko.ts \
|
||||
languages/qml_hu.ts \
|
||||
languages/qml_nl.ts \
|
||||
languages/qml_sv.ts \
|
||||
languages/qml_tr.ts \
|
||||
languages/qml_uk.ts \
|
||||
languages/qml_zh_Hant.ts
|
||||
|
||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||
QML_IMPORT_PATH =
|
||||
|
||||
@@ -106,7 +106,7 @@ void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QU
|
||||
|
||||
QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
|
||||
{
|
||||
NvHTTP http(computer->activeAddress, computer->serverCert);
|
||||
NvHTTP http(computer);
|
||||
|
||||
QString cachePath = getFilePathForBoxArt(computer, appId);
|
||||
QImage image;
|
||||
|
||||
@@ -27,9 +27,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool tryPollComputer(QString address, bool& changed)
|
||||
bool tryPollComputer(NvAddress address, bool& changed)
|
||||
{
|
||||
NvHTTP http(address, m_Computer->serverCert);
|
||||
NvHTTP http(address, 0, m_Computer->serverCert);
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
@@ -38,7 +38,7 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
NvComputer newState(address, serverInfo, QSslCertificate());
|
||||
NvComputer newState(http, serverInfo);
|
||||
|
||||
// Ensure the machine that responded is the one we intended to contact
|
||||
if (m_Computer->uuid != newState.uuid) {
|
||||
@@ -52,9 +52,7 @@ private:
|
||||
|
||||
bool updateAppList(bool& changed)
|
||||
{
|
||||
Q_ASSERT(m_Computer->activeAddress != nullptr);
|
||||
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
NvHTTP http(m_Computer);
|
||||
|
||||
QVector<NvApp> appList;
|
||||
|
||||
@@ -88,7 +86,7 @@ private:
|
||||
|
||||
if (tryPollComputer(address, stateChanged)) {
|
||||
if (!wasOnline) {
|
||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
|
||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString();
|
||||
}
|
||||
online = true;
|
||||
break;
|
||||
@@ -326,7 +324,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
|
||||
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
|
||||
// it's not currently reachable.
|
||||
addNewHost(address.toString(), true, v6Global);
|
||||
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
@@ -340,7 +338,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
|
||||
address.isInSubnet(QHostAddress("fec0::"), 10) ||
|
||||
address.isInSubnet(QHostAddress("fc00::"), 7)) {
|
||||
addNewHost(address.toString(), true, v6Global);
|
||||
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -470,7 +468,7 @@ signals:
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
NvPairingManager pairingManager(m_Computer->activeAddress);
|
||||
NvPairingManager pairingManager(m_Computer);
|
||||
|
||||
try {
|
||||
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin, m_Computer->serverCert);
|
||||
@@ -526,7 +524,7 @@ signals:
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
NvHTTP http(m_Computer);
|
||||
|
||||
try {
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
@@ -592,12 +590,24 @@ void ComputerManager::stopPollingAsync()
|
||||
}
|
||||
}
|
||||
|
||||
void ComputerManager::addNewHostManually(QString address)
|
||||
{
|
||||
QUrl url = QUrl::fromUserInput(address);
|
||||
if (url.isValid() && !url.host().isEmpty()) {
|
||||
// If there wasn't a port specified, use the default
|
||||
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
|
||||
}
|
||||
else {
|
||||
emit computerAddCompleted(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
class PendingAddTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PendingAddTask(ComputerManager* computerManager, QString address, QHostAddress mdnsIpv6Address, bool mdns)
|
||||
PendingAddTask(ComputerManager* computerManager, NvAddress address, NvAddress mdnsIpv6Address, bool mdns)
|
||||
: m_ComputerManager(computerManager),
|
||||
m_Address(address),
|
||||
m_MdnsIpv6Address(mdnsIpv6Address),
|
||||
@@ -661,15 +671,15 @@ private:
|
||||
|
||||
void run()
|
||||
{
|
||||
NvHTTP http(m_Address, QSslCertificate());
|
||||
NvHTTP http(m_Address, 0, QSslCertificate());
|
||||
|
||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user") << m_MdnsIpv6Address;
|
||||
qInfo() << "Processing new PC at" << m_Address.toString() << "from" << (m_Mdns ? "mDNS" : "user") << "with IPv6 address" << m_MdnsIpv6Address.toString();
|
||||
|
||||
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
||||
QString serverInfo = fetchServerInfo(http);
|
||||
if (serverInfo.isEmpty() && !m_MdnsIpv6Address.isNull()) {
|
||||
// Retry using the global IPv6 address if the IPv4 or link-local IPv6 address fails
|
||||
http.setAddress(m_MdnsIpv6Address.toString());
|
||||
http.setAddress(m_MdnsIpv6Address);
|
||||
serverInfo = fetchServerInfo(http);
|
||||
}
|
||||
if (serverInfo.isEmpty()) {
|
||||
@@ -677,7 +687,7 @@ private:
|
||||
}
|
||||
|
||||
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
||||
NvComputer* newComputer = new NvComputer(http.address(), serverInfo, QSslCertificate());
|
||||
NvComputer* newComputer = new NvComputer(http, serverInfo);
|
||||
|
||||
// Check if we have a record of this host UUID to pull the pinned cert
|
||||
NvComputer* existingComputer;
|
||||
@@ -691,13 +701,14 @@ private:
|
||||
|
||||
// Fetch serverinfo again over HTTPS with the pinned cert
|
||||
if (existingComputer != nullptr) {
|
||||
Q_ASSERT(http.httpsPort() != 0);
|
||||
serverInfo = fetchServerInfo(http);
|
||||
if (serverInfo.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the polled computer with the HTTPS information
|
||||
NvComputer httpsComputer(http.address(), serverInfo, QSslCertificate());
|
||||
NvComputer httpsComputer(http, serverInfo);
|
||||
newComputer->update(httpsComputer);
|
||||
}
|
||||
|
||||
@@ -711,11 +722,11 @@ private:
|
||||
}
|
||||
|
||||
// Get the WAN IP address using STUN if we're on mDNS over IPv4
|
||||
if (QHostAddress(newComputer->localAddress).protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
if (QHostAddress(newComputer->localAddress.address()).protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
quint32 addr;
|
||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||
if (err == 0) {
|
||||
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
||||
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
|
||||
}
|
||||
else {
|
||||
qWarning() << "STUN failed to get WAN address:" << err;
|
||||
@@ -723,15 +734,15 @@ private:
|
||||
}
|
||||
|
||||
if (!m_MdnsIpv6Address.isNull()) {
|
||||
Q_ASSERT(m_MdnsIpv6Address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
newComputer->ipv6Address = m_MdnsIpv6Address.toString();
|
||||
Q_ASSERT(QHostAddress(m_MdnsIpv6Address.address()).protocol() == QAbstractSocket::IPv6Protocol);
|
||||
newComputer->ipv6Address = m_MdnsIpv6Address;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newComputer->manualAddress = m_Address;
|
||||
}
|
||||
|
||||
QHostAddress hostAddress(m_Address);
|
||||
QHostAddress hostAddress(m_Address.address());
|
||||
bool addressIsSiteLocalV4 =
|
||||
hostAddress.isInSubnet(QHostAddress("10.0.0.0"), 8) ||
|
||||
hostAddress.isInSubnet(QHostAddress("172.16.0.0"), 12) ||
|
||||
@@ -756,7 +767,7 @@ private:
|
||||
|
||||
// Tell our client if something changed
|
||||
if (changed) {
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress.toString();
|
||||
emit computerStateChanged(existingComputer);
|
||||
}
|
||||
}
|
||||
@@ -777,7 +788,7 @@ private:
|
||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||
if (err == 0) {
|
||||
lock.relock();
|
||||
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
||||
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
|
||||
lock.unlock();
|
||||
}
|
||||
else {
|
||||
@@ -797,12 +808,12 @@ private:
|
||||
}
|
||||
|
||||
ComputerManager* m_ComputerManager;
|
||||
QString m_Address;
|
||||
QHostAddress m_MdnsIpv6Address;
|
||||
NvAddress m_Address;
|
||||
NvAddress m_MdnsIpv6Address;
|
||||
bool m_Mdns;
|
||||
};
|
||||
|
||||
void ComputerManager::addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address)
|
||||
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
|
||||
{
|
||||
// Punt to a worker thread to avoid stalling the
|
||||
// UI while waiting for serverinfo query to complete
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
|
||||
const QMdnsEngine::Service& service)
|
||||
: m_Hostname(service.hostname()),
|
||||
m_Port(service.port()),
|
||||
m_Server(server),
|
||||
m_Resolver(nullptr)
|
||||
{
|
||||
@@ -41,6 +42,11 @@ public:
|
||||
return m_Hostname;
|
||||
}
|
||||
|
||||
uint16_t port()
|
||||
{
|
||||
return m_Port;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void handleResolvedTimeout()
|
||||
{
|
||||
@@ -75,6 +81,7 @@ private:
|
||||
}
|
||||
|
||||
QByteArray m_Hostname;
|
||||
uint16_t m_Port;
|
||||
QMdnsEngine::Server* m_Server;
|
||||
QMdnsEngine::Resolver* m_Resolver;
|
||||
QVector<QHostAddress> m_Addresses;
|
||||
@@ -169,7 +176,9 @@ public:
|
||||
|
||||
Q_INVOKABLE void stopPollingAsync();
|
||||
|
||||
Q_INVOKABLE void addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address = QHostAddress());
|
||||
Q_INVOKABLE void addNewHostManually(QString address);
|
||||
|
||||
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
|
||||
|
||||
void pairHost(NvComputer* computer, QString pin);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ void ComputerSeeker::start(int timeout)
|
||||
// if m_ComputerName is UUID, or the name that doesn't resolve to an IP
|
||||
// address) and by polling it using mDNS, hopefully one of these methods
|
||||
// would find the host
|
||||
m_ComputerManager->addNewHost(m_ComputerName, false);
|
||||
m_ComputerManager->addNewHostManually(m_ComputerName);
|
||||
m_ComputerManager->startPolling();
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const QString& addr : computer->uniqueAddresses()) {
|
||||
if (addr.toLower() == value) {
|
||||
for (const NvAddress& addr : computer->uniqueAddresses()) {
|
||||
if (addr.address().toLower() == value || addr.toString().toLower() == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "nvaddress.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
|
||||
NvAddress::NvAddress()
|
||||
{
|
||||
setAddress(nullptr);
|
||||
setPort(0);
|
||||
}
|
||||
|
||||
NvAddress::NvAddress(QString addr, uint16_t port)
|
||||
{
|
||||
setAddress(addr);
|
||||
setPort(port);
|
||||
}
|
||||
|
||||
NvAddress::NvAddress(QHostAddress addr, uint16_t port)
|
||||
{
|
||||
setAddress(addr);
|
||||
setPort(port);
|
||||
}
|
||||
|
||||
uint16_t NvAddress::port() const
|
||||
{
|
||||
return m_Port;
|
||||
}
|
||||
|
||||
QString NvAddress::address() const
|
||||
{
|
||||
return m_Address;
|
||||
}
|
||||
|
||||
void NvAddress::setPort(uint16_t port)
|
||||
{
|
||||
m_Port = port;
|
||||
}
|
||||
|
||||
void NvAddress::setAddress(QString addr)
|
||||
{
|
||||
m_Address = addr;
|
||||
}
|
||||
|
||||
void NvAddress::setAddress(QHostAddress addr)
|
||||
{
|
||||
m_Address = addr.toString();
|
||||
}
|
||||
|
||||
bool NvAddress::isNull() const
|
||||
{
|
||||
return m_Address.isEmpty();
|
||||
}
|
||||
|
||||
QString NvAddress::toString() const
|
||||
{
|
||||
if (m_Address.isEmpty()) {
|
||||
return "<NULL>";
|
||||
}
|
||||
|
||||
if (QHostAddress(m_Address).protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
return QString("[%1]:%2").arg(m_Address).arg(m_Port);
|
||||
}
|
||||
else {
|
||||
return QString("%1:%2").arg(m_Address).arg(m_Port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <QHostAddress>
|
||||
|
||||
#define DEFAULT_HTTP_PORT 47989
|
||||
#define DEFAULT_HTTPS_PORT 47984
|
||||
|
||||
class NvAddress
|
||||
{
|
||||
public:
|
||||
NvAddress();
|
||||
explicit NvAddress(QString addr, uint16_t port);
|
||||
explicit NvAddress(QHostAddress addr, uint16_t port);
|
||||
|
||||
uint16_t port() const;
|
||||
void setPort(uint16_t port);
|
||||
|
||||
QString address() const;
|
||||
void setAddress(QString addr);
|
||||
void setAddress(QHostAddress addr);
|
||||
|
||||
bool isNull() const;
|
||||
QString toString() const;
|
||||
|
||||
bool operator==(const NvAddress& other) const
|
||||
{
|
||||
return m_Address == other.m_Address &&
|
||||
m_Port == other.m_Port;
|
||||
}
|
||||
|
||||
bool operator!=(const NvAddress& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_Address;
|
||||
uint16_t m_Port;
|
||||
};
|
||||
+70
-26
@@ -11,9 +11,13 @@
|
||||
#define SER_UUID "uuid"
|
||||
#define SER_MAC "mac"
|
||||
#define SER_LOCALADDR "localaddress"
|
||||
#define SER_LOCALPORT "localport"
|
||||
#define SER_REMOTEADDR "remoteaddress"
|
||||
#define SER_REMOTEPORT "remoteport"
|
||||
#define SER_MANUALADDR "manualaddress"
|
||||
#define SER_MANUALPORT "manualport"
|
||||
#define SER_IPV6ADDR "ipv6address"
|
||||
#define SER_IPV6PORT "ipv6port"
|
||||
#define SER_APPLIST "apps"
|
||||
#define SER_SRVCERT "srvcert"
|
||||
#define SER_CUSTOMNAME "customname"
|
||||
@@ -24,10 +28,14 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
this->uuid = settings.value(SER_UUID).toString();
|
||||
this->hasCustomName = settings.value(SER_CUSTOMNAME).toBool();
|
||||
this->macAddress = settings.value(SER_MAC).toByteArray();
|
||||
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
||||
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
||||
this->ipv6Address = settings.value(SER_IPV6ADDR).toString();
|
||||
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
||||
this->localAddress = NvAddress(settings.value(SER_LOCALADDR).toString(),
|
||||
settings.value(SER_LOCALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
||||
this->remoteAddress = NvAddress(settings.value(SER_REMOTEADDR).toString(),
|
||||
settings.value(SER_REMOTEPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
||||
this->ipv6Address = NvAddress(settings.value(SER_IPV6ADDR).toString(),
|
||||
settings.value(SER_IPV6PORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
||||
this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(),
|
||||
settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
||||
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
||||
|
||||
int appCount = settings.beginReadArray(SER_APPLIST);
|
||||
@@ -41,7 +49,6 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
settings.endArray();
|
||||
sortAppList();
|
||||
|
||||
this->activeAddress = nullptr;
|
||||
this->currentGameId = 0;
|
||||
this->pairState = PS_UNKNOWN;
|
||||
this->state = CS_UNKNOWN;
|
||||
@@ -52,6 +59,15 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
this->pendingQuit = false;
|
||||
this->gpuModel = nullptr;
|
||||
this->isSupportedServerVersion = true;
|
||||
this->externalPort = this->remoteAddress.port();
|
||||
this->activeHttpsPort = 0;
|
||||
}
|
||||
|
||||
void NvComputer::setRemoteAddress(QHostAddress address)
|
||||
{
|
||||
Q_ASSERT(this->externalPort != 0);
|
||||
|
||||
this->remoteAddress = NvAddress(address, this->externalPort);
|
||||
}
|
||||
|
||||
void NvComputer::serialize(QSettings& settings) const
|
||||
@@ -62,10 +78,14 @@ void NvComputer::serialize(QSettings& settings) const
|
||||
settings.setValue(SER_CUSTOMNAME, hasCustomName);
|
||||
settings.setValue(SER_UUID, uuid);
|
||||
settings.setValue(SER_MAC, macAddress);
|
||||
settings.setValue(SER_LOCALADDR, localAddress);
|
||||
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
||||
settings.setValue(SER_IPV6ADDR, ipv6Address);
|
||||
settings.setValue(SER_MANUALADDR, manualAddress);
|
||||
settings.setValue(SER_LOCALADDR, localAddress.address());
|
||||
settings.setValue(SER_LOCALPORT, localAddress.port());
|
||||
settings.setValue(SER_REMOTEADDR, remoteAddress.address());
|
||||
settings.setValue(SER_REMOTEPORT, remoteAddress.port());
|
||||
settings.setValue(SER_IPV6ADDR, ipv6Address.address());
|
||||
settings.setValue(SER_IPV6PORT, ipv6Address.port());
|
||||
settings.setValue(SER_MANUALADDR, manualAddress.address());
|
||||
settings.setValue(SER_MANUALPORT, manualAddress.port());
|
||||
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
||||
|
||||
// Avoid deleting an existing applist if we couldn't get one
|
||||
@@ -87,9 +107,9 @@ void NvComputer::sortAppList()
|
||||
});
|
||||
}
|
||||
|
||||
NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serverCert)
|
||||
NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
|
||||
{
|
||||
this->serverCert = serverCert;
|
||||
this->serverCert = http.serverCert();
|
||||
|
||||
this->hasCustomName = false;
|
||||
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
||||
@@ -130,19 +150,38 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
|
||||
});
|
||||
|
||||
// We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder
|
||||
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
|
||||
if (this->localAddress.startsWith("127.")) {
|
||||
this->localAddress = QString();
|
||||
this->localAddress = NvAddress(NvHTTP::getXmlString(serverInfo, "LocalIP"), http.httpPort());
|
||||
if (this->localAddress.address().startsWith("127.")) {
|
||||
this->localAddress = NvAddress();
|
||||
}
|
||||
|
||||
QString httpsPort = NvHTTP::getXmlString(serverInfo, "HttpsPort");
|
||||
if (httpsPort.isEmpty() || (this->activeHttpsPort = httpsPort.toUShort()) == 0) {
|
||||
this->activeHttpsPort = DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
|
||||
// This is an extension which is not present in GFE. It is present for Sunshine to be able
|
||||
// to support dynamic HTTP WAN ports without requiring the user to manually enter the port.
|
||||
QString remotePortStr = NvHTTP::getXmlString(serverInfo, "ExternalPort");
|
||||
if (remotePortStr.isEmpty() || (this->externalPort = remotePortStr.toUShort()) == 0) {
|
||||
this->externalPort = DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
QString remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
||||
if (!remoteAddress.isEmpty()) {
|
||||
this->remoteAddress = NvAddress(remoteAddress, this->externalPort);
|
||||
}
|
||||
else {
|
||||
this->remoteAddress = NvAddress();
|
||||
}
|
||||
|
||||
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||
PS_PAIRED : PS_NOT_PAIRED;
|
||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
|
||||
this->activeAddress = address;
|
||||
this->activeAddress = http.address();
|
||||
this->state = NvComputer::CS_ONLINE;
|
||||
this->pendingQuit = false;
|
||||
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
|
||||
@@ -177,7 +216,10 @@ bool NvComputer::wake()
|
||||
// Add the addresses that we know this host to be
|
||||
// and broadcast addresses for this link just in
|
||||
// case the host has timed out in ARP entries.
|
||||
QVector<QString> addressList = uniqueAddresses();
|
||||
QVector<QString> addressList;
|
||||
for (const NvAddress& addr : uniqueAddresses()) {
|
||||
addressList.append(addr.address());
|
||||
}
|
||||
addressList.append("255.255.255.255");
|
||||
|
||||
// Try to broadcast on all available NICs
|
||||
@@ -238,14 +280,14 @@ bool NvComputer::wake()
|
||||
|
||||
bool NvComputer::isReachableOverVpn()
|
||||
{
|
||||
if (activeAddress.isEmpty()) {
|
||||
if (activeAddress.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpSocket s;
|
||||
|
||||
s.setProxy(QNetworkProxy::NoProxy);
|
||||
s.connectToHost(activeAddress, 47984);
|
||||
s.connectToHost(activeAddress.address(), activeAddress.port());
|
||||
if (s.waitForConnected(3000)) {
|
||||
Q_ASSERT(!s.localAddress().isNull());
|
||||
|
||||
@@ -331,9 +373,9 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<QString> NvComputer::uniqueAddresses() const
|
||||
QVector<NvAddress> NvComputer::uniqueAddresses() const
|
||||
{
|
||||
QVector<QString> uniqueAddressList;
|
||||
QVector<NvAddress> uniqueAddressList;
|
||||
|
||||
// Start with addresses correctly ordered
|
||||
uniqueAddressList.append(activeAddress);
|
||||
@@ -344,7 +386,7 @@ QVector<QString> NvComputer::uniqueAddresses() const
|
||||
|
||||
// Prune duplicates (always giving precedence to the first)
|
||||
for (int i = 0; i < uniqueAddressList.count(); i++) {
|
||||
if (uniqueAddressList[i].isEmpty()) {
|
||||
if (uniqueAddressList[i].isNull()) {
|
||||
uniqueAddressList.remove(i);
|
||||
i--;
|
||||
continue;
|
||||
@@ -400,10 +442,12 @@ bool NvComputer::update(NvComputer& that)
|
||||
ASSIGN_IF_CHANGED(name);
|
||||
}
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(ipv6Address);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(localAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(remoteAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(ipv6Address);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(manualAddress);
|
||||
ASSIGN_IF_CHANGED(activeHttpsPort);
|
||||
ASSIGN_IF_CHANGED(externalPort);
|
||||
ASSIGN_IF_CHANGED(pairState);
|
||||
ASSIGN_IF_CHANGED(serverCodecModeSupport);
|
||||
ASSIGN_IF_CHANGED(currentGameId);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvhttp.h"
|
||||
#include "nvaddress.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QReadWriteLock>
|
||||
@@ -21,10 +22,13 @@ private:
|
||||
bool pendingQuit;
|
||||
|
||||
public:
|
||||
explicit NvComputer(QString address, QString serverInfo, QSslCertificate serverCert);
|
||||
explicit NvComputer(NvHTTP& http, QString serverInfo);
|
||||
|
||||
explicit NvComputer(QSettings& settings);
|
||||
|
||||
void
|
||||
setRemoteAddress(QHostAddress);
|
||||
|
||||
bool
|
||||
update(NvComputer& that);
|
||||
|
||||
@@ -34,7 +38,7 @@ public:
|
||||
bool
|
||||
isReachableOverVpn();
|
||||
|
||||
QVector<QString>
|
||||
QVector<NvAddress>
|
||||
uniqueAddresses() const;
|
||||
|
||||
void
|
||||
@@ -57,7 +61,8 @@ public:
|
||||
// Ephemeral traits
|
||||
ComputerState state;
|
||||
PairState pairState;
|
||||
QString activeAddress;
|
||||
NvAddress activeAddress;
|
||||
uint16_t activeHttpsPort;
|
||||
int currentGameId;
|
||||
QString gfeVersion;
|
||||
QString appVersion;
|
||||
@@ -68,10 +73,10 @@ public:
|
||||
bool isSupportedServerVersion;
|
||||
|
||||
// Persisted traits
|
||||
QString localAddress;
|
||||
QString remoteAddress;
|
||||
QString ipv6Address;
|
||||
QString manualAddress;
|
||||
NvAddress localAddress;
|
||||
NvAddress remoteAddress;
|
||||
NvAddress ipv6Address;
|
||||
NvAddress manualAddress;
|
||||
QByteArray macAddress;
|
||||
QString name;
|
||||
bool hasCustomName;
|
||||
@@ -81,4 +86,7 @@ public:
|
||||
|
||||
// Synchronization
|
||||
mutable QReadWriteLock lock;
|
||||
|
||||
private:
|
||||
uint16_t externalPort;
|
||||
};
|
||||
|
||||
+70
-14
@@ -1,4 +1,4 @@
|
||||
#include "nvhttp.h"
|
||||
#include "nvcomputer.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <QDebug>
|
||||
@@ -18,15 +18,14 @@
|
||||
#define RESUME_TIMEOUT_MS 30000
|
||||
#define QUIT_TIMEOUT_MS 30000
|
||||
|
||||
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
||||
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert) :
|
||||
m_ServerCert(serverCert)
|
||||
{
|
||||
m_BaseUrlHttp.setScheme("http");
|
||||
m_BaseUrlHttps.setScheme("https");
|
||||
m_BaseUrlHttp.setPort(47989);
|
||||
m_BaseUrlHttps.setPort(47984);
|
||||
|
||||
setAddress(address);
|
||||
setHttpsPort(httpsPort);
|
||||
|
||||
// Never use a proxy server
|
||||
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
||||
@@ -35,26 +34,54 @@ NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
||||
connect(&m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
|
||||
}
|
||||
|
||||
NvHTTP::NvHTTP(NvComputer* computer) :
|
||||
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
||||
{
|
||||
m_ServerCert = serverCert;
|
||||
}
|
||||
|
||||
void NvHTTP::setAddress(QString address)
|
||||
void NvHTTP::setAddress(NvAddress address)
|
||||
{
|
||||
Q_ASSERT(!address.isEmpty());
|
||||
Q_ASSERT(!address.isNull());
|
||||
|
||||
m_Address = address;
|
||||
|
||||
m_BaseUrlHttp.setHost(address);
|
||||
m_BaseUrlHttps.setHost(address);
|
||||
m_BaseUrlHttp.setHost(address.address());
|
||||
m_BaseUrlHttps.setHost(address.address());
|
||||
|
||||
m_BaseUrlHttp.setPort(address.port());
|
||||
}
|
||||
|
||||
QString NvHTTP::address()
|
||||
void NvHTTP::setHttpsPort(uint16_t port)
|
||||
{
|
||||
m_BaseUrlHttps.setPort(port);
|
||||
}
|
||||
|
||||
NvAddress NvHTTP::address()
|
||||
{
|
||||
return m_Address;
|
||||
}
|
||||
|
||||
QSslCertificate NvHTTP::serverCert()
|
||||
{
|
||||
return m_ServerCert;
|
||||
}
|
||||
|
||||
uint16_t NvHTTP::httpPort()
|
||||
{
|
||||
return m_BaseUrlHttp.port();
|
||||
}
|
||||
|
||||
uint16_t NvHTTP::httpsPort()
|
||||
{
|
||||
return m_BaseUrlHttps.port();
|
||||
}
|
||||
|
||||
QVector<int>
|
||||
NvHTTP::parseQuad(QString quad)
|
||||
{
|
||||
@@ -98,8 +125,8 @@ NvHTTP::getServerInfo(NvLogLevel logLevel, bool fastFail)
|
||||
{
|
||||
QString serverInfo;
|
||||
|
||||
// Check if we have a pinned cert for this host yet
|
||||
if (!m_ServerCert.isNull())
|
||||
// Check if we have a pinned cert and HTTPS port for this host yet
|
||||
if (!m_ServerCert.isNull() && httpsPort() != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -134,13 +161,26 @@ NvHTTP::getServerInfo(NvLogLevel logLevel, bool fastFail)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use HTTP prior to pairing
|
||||
// Only use HTTP prior to pairing or fetching HTTPS port
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
fastFail ? FAST_FAIL_TIMEOUT_MS : REQUEST_TIMEOUT_MS,
|
||||
logLevel);
|
||||
verifyResponseStatus(serverInfo);
|
||||
|
||||
// Populate the HTTPS port
|
||||
uint16_t httpsPort = getXmlString(serverInfo, "HttpsPort").toUShort();
|
||||
if (httpsPort == 0) {
|
||||
httpsPort = DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
setHttpsPort(httpsPort);
|
||||
|
||||
// If we just needed to determine the HTTPS port, we'll try again over
|
||||
// HTTPS now that we have the port number
|
||||
if (!m_ServerCert.isNull()) {
|
||||
return getServerInfo(logLevel, fastFail);
|
||||
}
|
||||
}
|
||||
|
||||
return serverInfo;
|
||||
@@ -151,7 +191,8 @@ NvHTTP::launchApp(int appId,
|
||||
PSTREAM_CONFIGURATION streamConfig,
|
||||
bool sops,
|
||||
bool localAudio,
|
||||
int gamepadMask)
|
||||
int gamepadMask,
|
||||
QString& rtspSessionUrl)
|
||||
{
|
||||
int riKeyId;
|
||||
|
||||
@@ -181,12 +222,16 @@ NvHTTP::launchApp(int appId,
|
||||
"&gcmap="+QString::number(gamepadMask),
|
||||
LAUNCH_TIMEOUT_MS);
|
||||
|
||||
qInfo() << "Launch response:" << response;
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
|
||||
rtspSessionUrl = getXmlString(response, "sessionUrl0");
|
||||
}
|
||||
|
||||
void
|
||||
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
||||
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig, QString& rtspSessionUrl)
|
||||
{
|
||||
int riKeyId;
|
||||
|
||||
@@ -201,8 +246,12 @@ NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
||||
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration)),
|
||||
RESUME_TIMEOUT_MS);
|
||||
|
||||
qInfo() << "Resume response:" << response;
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
|
||||
rtspSessionUrl = getXmlString(response, "sessionUrl0");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -214,6 +263,8 @@ NvHTTP::quitApp()
|
||||
nullptr,
|
||||
QUIT_TIMEOUT_MS);
|
||||
|
||||
qInfo() << "Quit response:" << response;
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
|
||||
@@ -330,6 +381,8 @@ NvHTTP::verifyResponseStatus(QString xml)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw GfeHttpResponseException(-1, "Malformed GFE XML (missing root element)");
|
||||
}
|
||||
|
||||
QImage
|
||||
@@ -435,6 +488,9 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
// Port must be set
|
||||
Q_ASSERT(baseUrl.port(0) != 0);
|
||||
|
||||
// Build a URL for the request
|
||||
QUrl url(baseUrl);
|
||||
url.setPath("/" + command);
|
||||
|
||||
+20
-6
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "identitymanager.h"
|
||||
#include "nvapp.h"
|
||||
#include "nvaddress.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
@@ -9,6 +10,8 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class NvComputer;
|
||||
|
||||
class NvDisplayMode
|
||||
{
|
||||
public:
|
||||
@@ -106,7 +109,9 @@ public:
|
||||
NVLL_VERBOSE
|
||||
};
|
||||
|
||||
explicit NvHTTP(QString address, QSslCertificate serverCert);
|
||||
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert);
|
||||
|
||||
explicit NvHTTP(NvComputer* computer);
|
||||
|
||||
static
|
||||
int
|
||||
@@ -138,9 +143,16 @@ public:
|
||||
|
||||
void setServerCert(QSslCertificate serverCert);
|
||||
|
||||
void setAddress(QString address);
|
||||
void setAddress(NvAddress address);
|
||||
void setHttpsPort(uint16_t port);
|
||||
|
||||
QString address();
|
||||
NvAddress address();
|
||||
|
||||
QSslCertificate serverCert();
|
||||
|
||||
uint16_t httpPort();
|
||||
|
||||
uint16_t httpsPort();
|
||||
|
||||
static
|
||||
QVector<int>
|
||||
@@ -150,14 +162,16 @@ public:
|
||||
quitApp();
|
||||
|
||||
void
|
||||
resumeApp(PSTREAM_CONFIGURATION streamConfig);
|
||||
resumeApp(PSTREAM_CONFIGURATION streamConfig,
|
||||
QString& rtspSessionUrl);
|
||||
|
||||
void
|
||||
launchApp(int appId,
|
||||
PSTREAM_CONFIGURATION streamConfig,
|
||||
bool sops,
|
||||
bool localAudio,
|
||||
int gamepadMask);
|
||||
int gamepadMask,
|
||||
QString& rtspSessionUrl);
|
||||
|
||||
QVector<NvApp>
|
||||
getAppList();
|
||||
@@ -182,7 +196,7 @@ private:
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel);
|
||||
|
||||
QString m_Address;
|
||||
NvAddress m_Address;
|
||||
QNetworkAccessManager m_Nam;
|
||||
QSslCertificate m_ServerCert;
|
||||
};
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
#define REQUEST_TIMEOUT_MS 5000
|
||||
|
||||
NvPairingManager::NvPairingManager(QString address) :
|
||||
m_Http(address, QSslCertificate())
|
||||
NvPairingManager::NvPairingManager(NvComputer* computer) :
|
||||
m_Http(computer)
|
||||
{
|
||||
QByteArray cert = IdentityManager::get()->getCertificate();
|
||||
BIO *bio = BIO_new_mem_buf(cert.data(), -1);
|
||||
@@ -167,11 +167,7 @@ NvPairingManager::signMessage(const QByteArray& message)
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
|
||||
THROW_BAD_ALLOC_IF_NULL(ctx);
|
||||
|
||||
const EVP_MD *md = EVP_get_digestbyname("SHA256");
|
||||
THROW_BAD_ALLOC_IF_NULL(md);
|
||||
|
||||
EVP_DigestInit_ex(ctx, md, NULL);
|
||||
EVP_DigestSignInit(ctx, NULL, md, NULL, m_PrivateKey);
|
||||
EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, m_PrivateKey);
|
||||
EVP_DigestSignUpdate(ctx, reinterpret_cast<unsigned char*>(const_cast<char*>(message.data())), message.length());
|
||||
|
||||
size_t signatureLength = 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
ALREADY_IN_PROGRESS
|
||||
};
|
||||
|
||||
explicit NvPairingManager(QString address);
|
||||
explicit NvPairingManager(NvComputer* computer);
|
||||
|
||||
~NvPairingManager();
|
||||
|
||||
|
||||
@@ -64,6 +64,16 @@ SystemProperties::SystemProperties()
|
||||
hasDiscordIntegration = false;
|
||||
#endif
|
||||
|
||||
// TODO: Do something smarter than this. We should be able to query
|
||||
// this from the decoder (or just try VIDEO_FORMAT_H265_MAIN10 and
|
||||
// fail if we don't get a hardware accelerated decoder).
|
||||
#ifdef Q_OS_DARWIN
|
||||
// HDR is supported by the VideoToolbox renderer
|
||||
supportsHdr = true;
|
||||
#else
|
||||
supportsHdr = false;
|
||||
#endif
|
||||
|
||||
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
||||
|
||||
// Populate data that requires talking to SDL. We do it all in one shot
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
|
||||
Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT)
|
||||
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
|
||||
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT)
|
||||
|
||||
Q_INVOKABLE void refreshDisplays();
|
||||
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
||||
@@ -54,5 +55,6 @@ private:
|
||||
QList<QRect> monitorDesktopResolutions;
|
||||
QList<QRect> monitorNativeResolutions;
|
||||
QString versionString;
|
||||
bool supportsHdr;
|
||||
};
|
||||
|
||||
|
||||
@@ -350,8 +350,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --fps option
|
||||
if (parser.isSet("fps")) {
|
||||
preferences->fps = parser.getIntOption("fps");
|
||||
if (!inRange(preferences->fps, 30, 120)) {
|
||||
parser.showError("FPS must be in range: 30 - 120");
|
||||
if (!inRange(preferences->fps, 30, 240)) {
|
||||
parser.showError("FPS must be in range: 30 - 240");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,36 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="3.2.0" date="2022-01-18">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Reduced CPU usage while streaming</li>
|
||||
<li>Non-ASCII characters are now supported when pasting clipboard data to the host</li>
|
||||
<li>Added support for zero-copy NVDEC rendering for Nvidia Wayland systems</li>
|
||||
<li>Significantly improved performance with asynchronous decoders like V4L2M2M on Linux</li>
|
||||
<li>Added support for color compression planes on newer Intel systems when using VAAPI decoding</li>
|
||||
<li>Added support for copy-back rendering in non-default pixel formats (like the Raspberry Pi SAND format)</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed unexpected GUI input after using the gamepad quit combo</li>
|
||||
<li>Updated included gamepad mappings</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.1.4" date="2021-06-25">
|
||||
<description>
|
||||
<p>Changes:</p>
|
||||
<ul>
|
||||
<li>Added network latency statistics to the performance overlay</li>
|
||||
<li>Improved audio quality on unreliable connections (requires GeForce Experience 3.19 or later)</li>
|
||||
<li>Reduced video frame drops on unreliable connections (requires GeForce Experience 3.22 or later)</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.1.3" date="2021-05-07">
|
||||
<description>
|
||||
<p>Changes:</p>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,5 +1,6 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ToolButton {
|
||||
property string iconSource
|
||||
@@ -20,8 +21,7 @@ ToolButton {
|
||||
|
||||
// This determines the size of the Material highlight. We increase it
|
||||
// from the default because we use larger than normal icons for TV readability.
|
||||
background.width: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
|
||||
background.height: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
|
||||
Layout.preferredHeight: parent.height
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
clicked()
|
||||
|
||||
+53
-10
@@ -766,7 +766,7 @@ Flickable {
|
||||
val: StreamingPreferences.LANG_AUTO
|
||||
}
|
||||
ListElement {
|
||||
text: "Deutsch" //German
|
||||
text: "Deutsch" // German
|
||||
val: StreamingPreferences.LANG_DE
|
||||
}
|
||||
ListElement {
|
||||
@@ -774,11 +774,11 @@ Flickable {
|
||||
val: StreamingPreferences.LANG_EN
|
||||
}
|
||||
ListElement {
|
||||
text: "Français" //French
|
||||
text: "Français" // French
|
||||
val: StreamingPreferences.LANG_FR
|
||||
}
|
||||
ListElement {
|
||||
text: "简体中文" //Simplified Chinese
|
||||
text: "简体中文" // Simplified Chinese
|
||||
val: StreamingPreferences.LANG_ZH_CN
|
||||
}
|
||||
ListElement {
|
||||
@@ -789,11 +789,50 @@ Flickable {
|
||||
text: "русский" // Russian
|
||||
val: StreamingPreferences.LANG_RU
|
||||
}
|
||||
// Don't list Spanish until it is more complete
|
||||
/*ListElement {
|
||||
ListElement {
|
||||
text: "Español" // Spanish
|
||||
val: StreamingPreferences.LANG_ES
|
||||
}*/
|
||||
}
|
||||
ListElement {
|
||||
text: "日本語" // Japanese
|
||||
val: StreamingPreferences.LANG_JA
|
||||
}
|
||||
ListElement {
|
||||
text: "Tiếng Việt" // Vietnamese
|
||||
val: StreamingPreferences.LANG_VI
|
||||
}
|
||||
ListElement {
|
||||
text: "ภาษาไทย" // Thai
|
||||
val: StreamingPreferences.LANG_TH
|
||||
}
|
||||
ListElement {
|
||||
text: "한국어" // Korean
|
||||
val: StreamingPreferences.LANG_KO
|
||||
}
|
||||
/* ListElement {
|
||||
text: "Magyar" // Hungarian
|
||||
val: StreamingPreferences.LANG_HU
|
||||
} */
|
||||
ListElement {
|
||||
text: "Nederlands" // Dutch
|
||||
val: StreamingPreferences.LANG_NL
|
||||
}
|
||||
ListElement {
|
||||
text: "Svenska" // Swedish
|
||||
val: StreamingPreferences.LANG_SV
|
||||
}
|
||||
/* ListElement {
|
||||
text: "Türkçe" // Turkish
|
||||
val: StreamingPreferences.LANG_TR
|
||||
} */
|
||||
/* ListElement {
|
||||
text: "Українська" // Ukrainian
|
||||
val: StreamingPreferences.LANG_UK
|
||||
} */
|
||||
ListElement {
|
||||
text: "繁体字" // Traditional Chinese
|
||||
val: StreamingPreferences.LANG_ZH_HANT
|
||||
}
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
@@ -1245,6 +1284,13 @@ Flickable {
|
||||
AutoResizingComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
if (SystemProperties.supportsHdr) {
|
||||
codecListModel.append({
|
||||
"text": qsTr("HEVC HDR (Experimental)"),
|
||||
"val": StreamingPreferences.VCC_FORCE_HEVC_HDR
|
||||
});
|
||||
}
|
||||
|
||||
var saved_vcc = StreamingPreferences.videoCodecConfig
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < codecListModel.count; i++) {
|
||||
@@ -1273,10 +1319,7 @@ Flickable {
|
||||
text: qsTr("HEVC (H.265)")
|
||||
val: StreamingPreferences.VCC_FORCE_HEVC
|
||||
}
|
||||
/*ListElement {
|
||||
text: qsTr("HEVC HDR (Experimental)")
|
||||
val: StreamingPreferences.VCC_FORCE_HEVC_HDR
|
||||
}*/
|
||||
/* HEVC HDR will be appended here */
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
|
||||
+3
-3
@@ -215,7 +215,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 20
|
||||
spacing: 10
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
anchors.fill: parent
|
||||
@@ -412,7 +412,7 @@ ApplicationWindow {
|
||||
|
||||
ErrorMessageDialog {
|
||||
id: noHwDecoderDialog
|
||||
text: qsTr("No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
|
||||
text: qsTr("No functioning hardware accelerated video decoder was detected by Moonlight. " +
|
||||
"Your streaming performance may be severely degraded in this configuration.")
|
||||
helpText: qsTr("Click the Help button for more information on solving this problem.")
|
||||
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
|
||||
@@ -493,7 +493,7 @@ ApplicationWindow {
|
||||
|
||||
onAccepted: {
|
||||
if (editText.text) {
|
||||
ComputerManager.addNewHost(editText.text.trim(), false)
|
||||
ComputerManager.addNewHostManually(editText.text.trim())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
|
||||
: m_Enabled(false),
|
||||
m_UiNavMode(false),
|
||||
m_FirstPoll(false),
|
||||
m_LastAxisNavigationEventTime(0)
|
||||
{
|
||||
m_PollingTimer = new QTimer(this);
|
||||
@@ -62,6 +63,9 @@ void SdlGamepadKeyNavigation::enable()
|
||||
}
|
||||
}
|
||||
|
||||
// Flush events on the first poll
|
||||
m_FirstPoll = true;
|
||||
|
||||
// Poll every 50 ms for a new joystick event
|
||||
m_PollingTimer->start(50);
|
||||
|
||||
@@ -91,6 +95,16 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
||||
SDL_Event event;
|
||||
StreamingPreferences prefs;
|
||||
|
||||
// Discard any pending button events on the first poll to avoid picking up
|
||||
// stale input data from the stream session (like the quit combo).
|
||||
if (m_FirstPoll) {
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN);
|
||||
SDL_FlushEvent(SDL_CONTROLLERBUTTONUP);
|
||||
|
||||
m_FirstPoll = false;
|
||||
}
|
||||
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
|
||||
@@ -34,5 +34,6 @@ private:
|
||||
QList<SDL_GameController*> m_Gamepads;
|
||||
bool m_Enabled;
|
||||
bool m_UiNavMode;
|
||||
bool m_FirstPoll;
|
||||
Uint32 m_LastAxisNavigationEventTime;
|
||||
};
|
||||
|
||||
Binary file not shown.
+97
-74
@@ -93,7 +93,7 @@
|
||||
<context>
|
||||
<name>NvHTTP</name>
|
||||
<message>
|
||||
<location filename="../backend/nvhttp.cpp" line="327"/>
|
||||
<location filename="../backend/nvhttp.cpp" line="378"/>
|
||||
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
|
||||
<translation>Audioaufnahmegerät fehlt. Eine Neuinstallation von GeForece Experience sollte diesen Fehler beheben.</translation>
|
||||
</message>
|
||||
@@ -246,7 +246,7 @@
|
||||
<context>
|
||||
<name>PendingQuitTask</name>
|
||||
<message>
|
||||
<location filename="../backend/computermanager.cpp" line="542"/>
|
||||
<location filename="../backend/computermanager.cpp" line="540"/>
|
||||
<source>The running game wasn't started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
|
||||
<translation>Die laufende App wurde von einem anderem PC gestartet. Beende die App auf dem Host manuell oder vom PC welcher die App gestartet hat.</translation>
|
||||
</message>
|
||||
@@ -327,112 +327,126 @@
|
||||
<translation>Verbindung getrennt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="592"/>
|
||||
<location filename="../streaming/session.cpp" line="613"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="597"/>
|
||||
<location filename="../streaming/session.cpp" line="618"/>
|
||||
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
|
||||
<translation>Die Auswahl des Remote Desktop Maus Modus kann in Spielen zu Problemen führen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="602"/>
|
||||
<location filename="../streaming/session.cpp" line="623"/>
|
||||
<source>HDR is not supported with software decoding.</source>
|
||||
<translation>HDR wird nicht von Software Dekodierung unterstützt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="606"/>
|
||||
<location filename="../streaming/session.cpp" line="627"/>
|
||||
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
|
||||
<translation>Software Dekodierung kann zu schlecher streaming Leitung führen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="611"/>
|
||||
<location filename="../streaming/session.cpp" line="632"/>
|
||||
<source>Using unsupported FPS options may cause stuttering or lag.</source>
|
||||
<translation>Nicht unterstützte FPS Optionen können stottern verursachen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="614"/>
|
||||
<location filename="../streaming/session.cpp" line="635"/>
|
||||
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
|
||||
<translation>V-Sync wird deaktiviert wenn mit einer höheren Framerate als der des Display gestreamt wird.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="631"/>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>Da deine Grafikkarte kein HEVC Decodierung unterstützt wird Software Dekodierung benutzt. Die kann zu einer schlechten streaming Leistung führen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="634"/>
|
||||
<source>This PC's GPU doesn't support HEVC decoding.</source>
|
||||
<translation>Die Grafikkarte deines PC's unterstützt keine HEVC Dekodierung.</translation>
|
||||
<translation type="vanished">Die Grafikkarte deines PC's unterstützt keine HEVC Dekodierung.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="641"/>
|
||||
<location filename="../streaming/session.cpp" line="645"/>
|
||||
<source>Your host PC GPU doesn't support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
|
||||
<translation>Die Grafikkarte deines Host's unterstützt kein HEVC. Es wird mindestens eine GeForce GTX 900-Series (Maxwell) oder neuer benötigt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="658"/>
|
||||
<location filename="../streaming/session.cpp" line="676"/>
|
||||
<source>Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="686"/>
|
||||
<source>Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="689"/>
|
||||
<source>Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="700"/>
|
||||
<source>%1 doesn't support HDR10.</source>
|
||||
<translation>%1 unterstützt kein HDR10.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<location filename="../streaming/session.cpp" line="704"/>
|
||||
<source>Your host PC GPU doesn't support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
|
||||
<translation>Die Grafikkarte deines Host's unterstützt kein HDR. Es wird mindestens eine GeForce GTX 1000-Series (Pascal) oder neuer benötigt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="671"/>
|
||||
<location filename="../streaming/session.cpp" line="713"/>
|
||||
<source>This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.</source>
|
||||
<translation>Die Grafikkarte deines PC's unterstützt keine HEVC Main10 Dekodierung für HDR streaming.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="684"/>
|
||||
<location filename="../streaming/session.cpp" line="726"/>
|
||||
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
|
||||
<translation>Für 4K Streaming wird mindestens GeForce Experience 3.0 benötigt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="699"/>
|
||||
<location filename="../streaming/session.cpp" line="741"/>
|
||||
<source>Your selected surround sound setting is not supported by the current audio device.</source>
|
||||
<translation>Deine ausgewählte Surround Sound Einstellung wird von dem momentanen Audiogerät nicht untertützt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="706"/>
|
||||
<location filename="../streaming/session.cpp" line="748"/>
|
||||
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
|
||||
<translation>Zugriff auf das Audiogerät fehlgeschlagen. Es wird kein Ton für diese Session verfügbar sein.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="711"/>
|
||||
<location filename="../streaming/session.cpp" line="753"/>
|
||||
<source>An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this.</source>
|
||||
<translation>Ein angeschlossener Controller hat keine Tastenbelegung und ist somit nicht nutzbar. Besuche die Moonlight Hilfe um dies zu lösen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="719"/>
|
||||
<location filename="../streaming/session.cpp" line="761"/>
|
||||
<source>Your host PC's GPU doesn't support streaming video resolutions over 4K.</source>
|
||||
<translation>Die Grafikkarte deines Host's unterstüzt keine Videoauflösung über 4K zum Streamen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="723"/>
|
||||
<location filename="../streaming/session.cpp" line="765"/>
|
||||
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
|
||||
<translation>Videoauflösungen über 4K sind nur mit dem HEVC Codec Verfügbar.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="737"/>
|
||||
<location filename="../streaming/session.cpp" line="779"/>
|
||||
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.</source>
|
||||
<translation>Erzwungene Hardware Dekodierung kann nicht ausgewählt werden, da der PC keine Hardware Decodierung unterstützt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="740"/>
|
||||
<location filename="../streaming/session.cpp" line="782"/>
|
||||
<source>Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.</source>
|
||||
<translation>Erzwungene Hardware Dekodierung ist nicht mit deinem ausgewähltem Codec kompatibel, da die Grafikkarte deines PC's den ausgewählen Codec nicht unterstützt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1041"/>
|
||||
<location filename="../streaming/session.cpp" line="1107"/>
|
||||
<source>GeForce Experience returned error: %1</source>
|
||||
<translation>GeForce Experience hat einen Fehler gemeldet: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1519"/>
|
||||
<location filename="../streaming/session.cpp" line="1639"/>
|
||||
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
|
||||
<translation>Video Decoder konnte nicht initialisiert werden. Bitte prüfe deine Streaming Einstellungen und versuche es erneut.</translation>
|
||||
</message>
|
||||
@@ -534,7 +548,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="573"/>
|
||||
<location filename="../gui/SettingsView.qml" line="860"/>
|
||||
<location filename="../gui/SettingsView.qml" line="899"/>
|
||||
<source>Fullscreen</source>
|
||||
<translation>Vollbild</translation>
|
||||
</message>
|
||||
@@ -559,7 +573,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="581"/>
|
||||
<location filename="../gui/SettingsView.qml" line="852"/>
|
||||
<location filename="../gui/SettingsView.qml" line="891"/>
|
||||
<source>Windowed</source>
|
||||
<translation>Fenster</translation>
|
||||
</message>
|
||||
@@ -639,32 +653,32 @@
|
||||
<translation>Automatisch</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="805"/>
|
||||
<location filename="../gui/SettingsView.qml" line="844"/>
|
||||
<source>You must restart Moonlight for this change to take effect</source>
|
||||
<translation>Du musst Moonlight neustarten, damit diese Einstellung aktiv wird</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="819"/>
|
||||
<location filename="../gui/SettingsView.qml" line="858"/>
|
||||
<source>GUI display mode</source>
|
||||
<translation>Anzeigemodus der Oberfläche</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="856"/>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<source>Maximized</source>
|
||||
<translation>Maximiert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="913"/>
|
||||
<location filename="../gui/SettingsView.qml" line="952"/>
|
||||
<source>Input Settings</source>
|
||||
<translation>Eingabe Einstellungen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<location filename="../gui/SettingsView.qml" line="973"/>
|
||||
<source>This enables seamless mouse control without capturing the client's mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
|
||||
<translation>Dies ermöglicht eine nahtlose Maussteuerung, ohne den Mauszeiger des PCs zu erfassen. Es ist ideal für die Remote-Desktop-Nutzung, funktioniert aber bei den meisten Spielen nicht.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="936"/>
|
||||
<location filename="../gui/SettingsView.qml" line="975"/>
|
||||
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
|
||||
<translation>HINWEIS: Aufgrund eines Fehlers in GeForce Experience funktioniert diese Option möglicherweise nicht richtig, wenn Ihr Host mehrere Monitore hat.</translation>
|
||||
</message>
|
||||
@@ -677,77 +691,77 @@
|
||||
<translation type="vanished">Dies ermöglicht die Erfassung von systemweiten Tastenkombinationen wie Alt+Tab, die normalerweise vom Client beim Streamen im Vollbildmodus ausgeführt werden würden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="955"/>
|
||||
<location filename="../gui/SettingsView.qml" line="994"/>
|
||||
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
|
||||
<translation>HINWEIS: Bestimmte Tastenkombinationen wie Strg+Alt+Entf unter Windows können von keiner Anwendung abgefangen werden, auch nicht von Moonlight.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1018"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1057"/>
|
||||
<source>Use touchscreen as a virtual trackpad</source>
|
||||
<translation>Touchscreen als virtuelles Trackpad verwenden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1035"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1074"/>
|
||||
<source>Swap left and right mouse buttons</source>
|
||||
<translation>Linke und rechte Maustaste vertauschen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1047"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1086"/>
|
||||
<source>Reverse mouse scrolling direction</source>
|
||||
<translation>Scrollrichtung umkehren</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1061"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1100"/>
|
||||
<source>Gamepad Settings</source>
|
||||
<translation>Controller Einstellungen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1071"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1110"/>
|
||||
<source>Swap A/B and X/Y gamepad buttons</source>
|
||||
<translation>Tausche A/B und X/Y Controller Tasten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1089"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1128"/>
|
||||
<source>This switches gamepads into a Nintendo-style button layout</source>
|
||||
<translation>Dies ändert das Controller Tastenlayout in ein Nintento Controller Style Layout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1095"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1134"/>
|
||||
<source>Force gamepad #1 always connected</source>
|
||||
<translation>Erzwinge das Controller #1 immer verbunden ist</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1105"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
|
||||
<translation>Erzwingt, dass ein Controller immer mit dem Host verbunden bleibt, auch wenn keine Gamepads an diesen PC angeschlossen sind.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="935"/>
|
||||
<location filename="../gui/SettingsView.qml" line="974"/>
|
||||
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
|
||||
<translation>Du kannst dies während des Streamens umstellen mit Strg+Alt+Shift+M.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1113"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1152"/>
|
||||
<source>Enable mouse control with gamepads by holding the 'Start' button</source>
|
||||
<translation>Aktivieren Sie die Maussteuerung mit Controllern, indem Sie die 'Start'-Taste gedrückt halten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1124"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1163"/>
|
||||
<source>Process gamepad input when Moonlight is in the background</source>
|
||||
<translation>Controller-Eingaben ausführen auch wenn Moonlight im Hintergrund ist</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1135"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1174"/>
|
||||
<source>Allows Moonlight to capture gamepad inputs even if it's not the current window in focus</source>
|
||||
<translation>Erlaubt es Moonlight Controller-Eingaben an den Stream zu schicken auch wenn es nicht das aktive Fenster ist</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1183"/>
|
||||
<source>Host Settings</source>
|
||||
<translation>Host Einstellungen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1184"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<source>Advanced Settings</source>
|
||||
<translation>Erweiterte Einstellungen</translation>
|
||||
</message>
|
||||
@@ -757,118 +771,123 @@
|
||||
<translation>Audio Einstellungen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="873"/>
|
||||
<location filename="../gui/SettingsView.qml" line="912"/>
|
||||
<source>Show connection quality warnings</source>
|
||||
<translation>Warnung bei Verbindungsproblemen anzeigen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="885"/>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<source>Discord Rich Presence integration</source>
|
||||
<translation>Integration von Discord Rich Presence</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<source>Updates your Discord status to display the name of the game you're streaming.</source>
|
||||
<translation>Aktualisiert deinen Discord Status um den Namen der aktuellen App anzuzeigen, wenn du streamst.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1106"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1145"/>
|
||||
<source>Only enable this option when streaming a game that doesn't support gamepads being connected after startup.</source>
|
||||
<translation>Aktiviere diese Option nur wenn du ein Spiel streamst, dass die Verbindung von Controllern nach dem Start nicht unterstützt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<location filename="../gui/SettingsView.qml" line="963"/>
|
||||
<source>Optimize mouse for remote desktop instead of games</source>
|
||||
<translation>Optimiere die Maus für Remote Desktop statt für Apps</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="946"/>
|
||||
<location filename="../gui/SettingsView.qml" line="985"/>
|
||||
<source>Capture system keyboard shortcuts</source>
|
||||
<translation>Erfasse Systemtastatur Shortcuts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="954"/>
|
||||
<location filename="../gui/SettingsView.qml" line="993"/>
|
||||
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
|
||||
<translation>Dies aktiviert das Erfassen von Systemweiten Tastatur Shortcuts wie Alt+Tab, welche nomalerweise auf dem Client ausgeführt werden würden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="984"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1023"/>
|
||||
<source>in fullscreen</source>
|
||||
<translation>im Vollbildmodus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="988"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1027"/>
|
||||
<source>always</source>
|
||||
<translation>immer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1028"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1067"/>
|
||||
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
|
||||
<translation>Wenn aktiviert, verhält sich der Touchscreen wie ein Trackpad. Wenn deaktiviert, steuert der Touchscreen direkt den Mauszeiger.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1154"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1193"/>
|
||||
<source>Optimize game settings for streaming</source>
|
||||
<translation>Optimiere die Spieleinstellungen für das Streamen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1165"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1204"/>
|
||||
<source>Quit app on host PC after ending stream</source>
|
||||
<translation>Schließe die App auf dem Host nach dem beenden des Streams</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1175"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1214"/>
|
||||
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
|
||||
<translation>Dadurch wird die App, dass du streamst, geschlossen, wenn du deinen Stream beendest. Du verlierst dabei alle ungespeicherten Fortschritte!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1194"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1233"/>
|
||||
<source>Video decoder</source>
|
||||
<translation>Video Decoder</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1219"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1265"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1258"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1311"/>
|
||||
<source>Automatic (Recommended)</source>
|
||||
<translation>Automatisch (empfohlen)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1262"/>
|
||||
<source>Force software decoding</source>
|
||||
<translation>Erwzinge Software decoding</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1227"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1266"/>
|
||||
<source>Force hardware decoding</source>
|
||||
<translation>Erzwinge Hardware decoding</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1240"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1279"/>
|
||||
<source>Video codec</source>
|
||||
<translation>Video Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1269"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1315"/>
|
||||
<source>H.264</source>
|
||||
<translation>H.264</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1273"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1319"/>
|
||||
<source>HEVC (H.265)</source>
|
||||
<translation>HEVC (H.265)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1290"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1289"/>
|
||||
<source>HEVC HDR (Experimental)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1333"/>
|
||||
<source>Unlock unsupported FPS options</source>
|
||||
<translation>Zeige nicht unterstützte FPS Optionen an</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1309"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1352"/>
|
||||
<source>Automatically find PCs on the local network (Recommended)</source>
|
||||
<translation>Suche automatisch nach Hosts im lokalen Netzwerk (empfohlen)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1334"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1377"/>
|
||||
<source>Automatically detect blocked connections (Recommended)</source>
|
||||
<translation>Erkenne automatisch geblockte Verbindungen (empfohlen)</translation>
|
||||
</message>
|
||||
@@ -968,9 +987,8 @@
|
||||
<translation>Controllertasten belegen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Moonlight hat keinen funktionierenden hardwarebeschleunigten H.264-Videodecoder erkannt. Deine Streaming-Performance kann in dieser Konfiguration stark beeinträchtigt sein.</translation>
|
||||
<translation type="vanished">Moonlight hat keinen funktionierenden hardwarebeschleunigten H.264-Videodecoder erkannt. Deine Streaming-Performance kann in dieser Konfiguration stark beeinträchtigt sein.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="423"/>
|
||||
@@ -992,6 +1010,11 @@
|
||||
<source>Help</source>
|
||||
<translation>Hilfe</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="417"/>
|
||||
<source>Click the Help button for more information on solving this problem.</source>
|
||||
|
||||
Binary file not shown.
+272
-249
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+120
-97
@@ -87,13 +87,13 @@
|
||||
<message>
|
||||
<location filename="../gui/GamepadMapper.qml" line="4"/>
|
||||
<source>Gamepad Mapping</source>
|
||||
<translation>Configuration du contrôleur</translation>
|
||||
<translation>Configuration de la manette</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NvHTTP</name>
|
||||
<message>
|
||||
<location filename="../backend/nvhttp.cpp" line="327"/>
|
||||
<location filename="../backend/nvhttp.cpp" line="378"/>
|
||||
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
|
||||
<translation>Dispositif de capture audio manquant. La réinstallation de GeForce Experience devrait résoudre cette erreur.</translation>
|
||||
</message>
|
||||
@@ -145,7 +145,7 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="193"/>
|
||||
<source>Test Network</source>
|
||||
<translation>Test Réseau</translation>
|
||||
<translation>Test réseau</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="202"/>
|
||||
@@ -200,7 +200,7 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="345"/>
|
||||
<source>Your PC's current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
|
||||
<translation>La connexion réseau actuelle de votre PC semble bloquer le Moonlight. Il se peut que la diffusion en continu sur Internet ne fonctionne pas lorsque vous êtes connecté à ce réseau.</translation>
|
||||
<translation>La connexion réseau actuelle de votre PC semble bloquer Moonlight. Il se peut que le stream depuis Internet ne fonctionne pas lorsque vous êtes connecté à ce réseau.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="345"/>
|
||||
@@ -225,12 +225,12 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="171"/>
|
||||
<source>Online</source>
|
||||
<translation></translation>
|
||||
<translation>En ligne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="171"/>
|
||||
<source>Offline</source>
|
||||
<translation></translation>
|
||||
<translation>Hors ligne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="177"/>
|
||||
@@ -246,9 +246,9 @@
|
||||
<context>
|
||||
<name>PendingQuitTask</name>
|
||||
<message>
|
||||
<location filename="../backend/computermanager.cpp" line="542"/>
|
||||
<location filename="../backend/computermanager.cpp" line="540"/>
|
||||
<source>The running game wasn't started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
|
||||
<translation>Le jeu en cours d’exécution n'a pas été démarré par ce PC. Vous devez fermer le jeu sur le PC hôte manuellement ou utiliser l'appareil qui a démarré le jeu à l'origine.</translation>
|
||||
<translation>Le jeu en cours d’exécution n'a pas été démarré par ce PC. Vous devez le fermer manuellement sur le PC hôte ou le fermer depuis l'appareil qui a démarré ce jeu.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -257,7 +257,7 @@
|
||||
<location filename="../cli/quitstream.cpp" line="76"/>
|
||||
<location filename="../cli/startstream.cpp" line="133"/>
|
||||
<source>Failed to connect to %1</source>
|
||||
<translation>N'a pas pu se connecter à %1</translation>
|
||||
<translation>La connexion à %1 a échoué</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../cli/quitstream.cpp" line="88"/>
|
||||
@@ -274,7 +274,7 @@
|
||||
<message>
|
||||
<location filename="../cli/startstream.cpp" line="137"/>
|
||||
<source>Failed to find application %1</source>
|
||||
<translation>N'a pas trouvé l'application %1</translation>
|
||||
<translation>L'application %1 n'a pas été trouvée</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -327,112 +327,126 @@
|
||||
<translation>Connexion terminée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="592"/>
|
||||
<location filename="../streaming/session.cpp" line="613"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation>La version de GeForce Experience sur %1 n'est pas supportée par cette version de Moonlight. Vous devez mettre à jour Moonlight pour streamer depuis %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="597"/>
|
||||
<location filename="../streaming/session.cpp" line="618"/>
|
||||
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
|
||||
<translation>Votre sélection pour activer le mode de souris de bureau à distance peut causer des problèmes dans les jeux.</translation>
|
||||
<translation>Le mode souris pour bureau à distance que vous avez activé peut causer des problèmes dans les jeux.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="602"/>
|
||||
<location filename="../streaming/session.cpp" line="623"/>
|
||||
<source>HDR is not supported with software decoding.</source>
|
||||
<translation>Le HDR n'est pas pris en charge par le décodage logiciel.</translation>
|
||||
<translation>Le HDR n'est pas pris en charge par le décodeur logiciel.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="606"/>
|
||||
<location filename="../streaming/session.cpp" line="627"/>
|
||||
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
|
||||
<translation>La sélection de vos paramètres pour forcer le décodage logiciel peut entraîner de mauvaises performances de streaming.</translation>
|
||||
<translation>Vos réglages pour forcer le décodage logiciel peut causer de mauvaises performances.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="611"/>
|
||||
<location filename="../streaming/session.cpp" line="632"/>
|
||||
<source>Using unsupported FPS options may cause stuttering or lag.</source>
|
||||
<translation>L'utilisation d'options FPS non prises en charge peut entraîner des saccades ou des lags.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="614"/>
|
||||
<location filename="../streaming/session.cpp" line="635"/>
|
||||
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
|
||||
<translation>La synchronisation verticale (V-sync) sera désactivée lors de la diffusion en continu à une fréquence d'images supérieure à celle de l'affichage.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="631"/>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>Utilisation du décodage logiciel dû à votre sélection pour forcer le codec HEVC sans support GPU. Ceci peut entraîner de mauvaises performances de streaming.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="634"/>
|
||||
<source>This PC's GPU doesn't support HEVC decoding.</source>
|
||||
<translation>Le GPU de ce PC ne prend pas en charge le décodage HEVC.</translation>
|
||||
<translation type="vanished">Le GPU de ce PC ne prend pas en charge le décodage HEVC.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="641"/>
|
||||
<location filename="../streaming/session.cpp" line="645"/>
|
||||
<source>Your host PC GPU doesn't support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
|
||||
<translation>Le GPU de votre PC hôte ne prend pas en charge le codec HEVC. Un GPU GeForce GTX série 900 (Maxwell) ou plus récent est nécessaire pour le streaming HEVC.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="658"/>
|
||||
<location filename="../streaming/session.cpp" line="676"/>
|
||||
<source>Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>Le décodage logiciel est utilisé car vous voulez forcer l'utilisation du H.264 sans support matériel du GPU. Ceci peut causer des problèmes de performances.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="686"/>
|
||||
<source>Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance.</source>
|
||||
<translation>Votre PC hôte et PC client ne supportent pas les mêmes codecs vidéo. Ceci peut causer des problèmes de performance.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="689"/>
|
||||
<source>Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance.</source>
|
||||
<translation>Le GPU de votre client ne supporte pas le décodage matériel du H.264. Ceci peut causer des problèmes de performance.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="700"/>
|
||||
<source>%1 doesn't support HDR10.</source>
|
||||
<translation>%1 ne prend pas en charge le HDR10.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<location filename="../streaming/session.cpp" line="704"/>
|
||||
<source>Your host PC GPU doesn't support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
|
||||
<translation>Le GPU de votre PC hôte ne prend pas en charge le streaming HDR. Un GPU GeForce GTX série 1000 (Pascal) ou plus récent est nécessaire pour la diffusion HDR.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="671"/>
|
||||
<location filename="../streaming/session.cpp" line="713"/>
|
||||
<source>This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.</source>
|
||||
<translation>Le GPU de ce PC ne prend pas en charge le décodage HEVC pour le streaming HDR.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="684"/>
|
||||
<location filename="../streaming/session.cpp" line="726"/>
|
||||
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
|
||||
<translation>GeForce Experience 3.0 ou supérieur est requis pour le streaming 4K.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="699"/>
|
||||
<location filename="../streaming/session.cpp" line="741"/>
|
||||
<source>Your selected surround sound setting is not supported by the current audio device.</source>
|
||||
<translation>Le réglage du son surround que vous avez sélectionné n'est pas pris en charge par l'appareil audio actuel.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="706"/>
|
||||
<location filename="../streaming/session.cpp" line="748"/>
|
||||
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
|
||||
<translation>Échec de l'ouverture de l'appareil audio. L'audio ne sera pas disponible pendant cette session.</translation>
|
||||
<translation>L'ouverture de l'appareil audio à échoué. L'audio ne sera pas disponible pendant cette session.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="711"/>
|
||||
<location filename="../streaming/session.cpp" line="753"/>
|
||||
<source>An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this.</source>
|
||||
<translation>Une manette de jeu attachée n'a pas été configurée et ne sera pas utilisable. Visitez l'aide Moonlight pour résoudre ce problème.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="719"/>
|
||||
<location filename="../streaming/session.cpp" line="761"/>
|
||||
<source>Your host PC's GPU doesn't support streaming video resolutions over 4K.</source>
|
||||
<translation>Le GPU de votre PC hôte ne prend pas en charge les résolutions vidéo en continu supérieures à 4K.</translation>
|
||||
<translation>Le GPU de votre PC hôte ne prend pas en charge le streaming dans des résolutions supérieures à la 4K.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="723"/>
|
||||
<location filename="../streaming/session.cpp" line="765"/>
|
||||
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
|
||||
<translation>Les résolutions vidéo supérieures à 4K sont uniquement prises en charge par le codec HEVC.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="737"/>
|
||||
<location filename="../streaming/session.cpp" line="779"/>
|
||||
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.</source>
|
||||
<translation>Votre choix pour forcer le décodage matériel ne peut être satisfait en raison de l'absence de support de décodage matériel sur le GPU de ce PC.</translation>
|
||||
<translation>Impossible d'utiliser le décodage matériel car le GPU de cet ordinateur ne supporte pas le décodage matériel.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="740"/>
|
||||
<location filename="../streaming/session.cpp" line="782"/>
|
||||
<source>Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.</source>
|
||||
<translation>Votre sélection de codecs et le réglage du décodage matériel ne sont pas compatibles. Le GPU de ce PC ne prend pas en charge le décodage du codec que vous avez choisi.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1041"/>
|
||||
<location filename="../streaming/session.cpp" line="1107"/>
|
||||
<source>GeForce Experience returned error: %1</source>
|
||||
<translation>GeForce Experience a renvoyé l'erreur : %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1519"/>
|
||||
<location filename="../streaming/session.cpp" line="1639"/>
|
||||
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
|
||||
<translation>Impossible d'initialiser le décodeur vidéo. Veuillez vérifier vos paramètres de diffusion en continu et réessayez.</translation>
|
||||
</message>
|
||||
@@ -452,7 +466,7 @@
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="78"/>
|
||||
<source>Setting values too high for your PC or network connection may cause lag, stuttering, or errors.</source>
|
||||
<translation>Fixer des valeurs trop élevées pour votre PC ou votre connexion réseau, peut entraîner un décalage, des saccades ou des erreurs.</translation>
|
||||
<translation>Des réglages trop élevés pour votre PC ou connexion peuvent causer du lag, des saccades et d'autres problèmes.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="60"/>
|
||||
@@ -519,7 +533,7 @@
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="501"/>
|
||||
<source>Lower the bitrate on slower connections. Raise the bitrate to increase image quality.</source>
|
||||
<translation>Baissez le débit binaire sur les connexions plus lentes. Augmentez le débit binaire pour améliorer la qualité de l'image.</translation>
|
||||
<translation>Baissez le débit si votre connexion est lente. Augmentez-le pour plus de qualité d'image.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="519"/>
|
||||
@@ -538,7 +552,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="573"/>
|
||||
<location filename="../gui/SettingsView.qml" line="860"/>
|
||||
<location filename="../gui/SettingsView.qml" line="899"/>
|
||||
<source>Fullscreen</source>
|
||||
<translation>Plein écran</translation>
|
||||
</message>
|
||||
@@ -558,7 +572,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="581"/>
|
||||
<location filename="../gui/SettingsView.qml" line="852"/>
|
||||
<location filename="../gui/SettingsView.qml" line="891"/>
|
||||
<source>Windowed</source>
|
||||
<translation>Fenêtré</translation>
|
||||
</message>
|
||||
@@ -569,7 +583,7 @@
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="599"/>
|
||||
<source>V-Sync</source>
|
||||
<translation>synchronisation verticale</translation>
|
||||
<translation>Synchronisation verticale (V-Sync)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="609"/>
|
||||
@@ -584,7 +598,7 @@
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="626"/>
|
||||
<source>Frame pacing reduces micro-stutter by delaying frames that come in too early</source>
|
||||
<translation>Frame pacing réduit les micro-bégaiements en retardant les images qui arrivent trop tôt</translation>
|
||||
<translation>Le Frame pacing réduit les micro-saccades en retardant les images qui arrivent trop tôt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="636"/>
|
||||
@@ -642,87 +656,87 @@
|
||||
<translation>Automatique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="805"/>
|
||||
<location filename="../gui/SettingsView.qml" line="844"/>
|
||||
<source>You must restart Moonlight for this change to take effect</source>
|
||||
<translation>Vous devez redémarrer Moonlight pour que ce changement prenne effet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="819"/>
|
||||
<location filename="../gui/SettingsView.qml" line="858"/>
|
||||
<source>GUI display mode</source>
|
||||
<translation>Mode d'affichage de l'interface graphique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="856"/>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<source>Maximized</source>
|
||||
<translation>Maximisé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="913"/>
|
||||
<location filename="../gui/SettingsView.qml" line="952"/>
|
||||
<source>Input Settings</source>
|
||||
<translation>Paramètres d'entrée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<location filename="../gui/SettingsView.qml" line="973"/>
|
||||
<source>This enables seamless mouse control without capturing the client's mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
|
||||
<translation>Cela permet un contrôle transparent de la souris, sans capturer le curseur du client. C'est idéal pour l’utilisation du bureau à distance, mais ne fonctionnera pas dans la plupart des jeux.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="936"/>
|
||||
<location filename="../gui/SettingsView.qml" line="975"/>
|
||||
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
|
||||
<translation>REMARQUE : En raison d'un bogue dans GeForce Experience, cette option peut ne pas fonctionner correctement si votre PC hôte a plusieurs moniteurs.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="955"/>
|
||||
<location filename="../gui/SettingsView.qml" line="994"/>
|
||||
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
|
||||
<translation>REMARQUE : Certains raccourcis clavier comme Ctrl+Alt+Suppr sur Windows ne peuvent jamais être interceptés, y compris par Moonlight.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="984"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1023"/>
|
||||
<source>in fullscreen</source>
|
||||
<translation>en plein écran</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="988"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1027"/>
|
||||
<source>always</source>
|
||||
<translation>toujours</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1018"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1057"/>
|
||||
<source>Use touchscreen as a virtual trackpad</source>
|
||||
<translation>Utiliser l'écran tactile comme un trackpad virtuel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1035"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1074"/>
|
||||
<source>Swap left and right mouse buttons</source>
|
||||
<translation>Permuter les boutons gauche et droit de la souris</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1047"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1086"/>
|
||||
<source>Reverse mouse scrolling direction</source>
|
||||
<translation>Inverser le sens de défilement de la souris</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1061"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1100"/>
|
||||
<source>Gamepad Settings</source>
|
||||
<translation>Paramètres de la manette</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1071"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1110"/>
|
||||
<source>Swap A/B and X/Y gamepad buttons</source>
|
||||
<translation>Inverser les boutons A/B et X/Y de la manette</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1089"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1128"/>
|
||||
<source>This switches gamepads into a Nintendo-style button layout</source>
|
||||
<translation>Permet d'utiliser les boutons comme sur une manette Nintendo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1095"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1134"/>
|
||||
<source>Force gamepad #1 always connected</source>
|
||||
<translation>Forcer la manette #1 à être toujours connectée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1105"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
|
||||
<translation>Force une seule manette de jeu à toujours rester connectée à l'hôte, même si aucune manette de jeu n'est réellement connectée à ce PC.</translation>
|
||||
</message>
|
||||
@@ -731,42 +745,42 @@
|
||||
<translation type="vanished">Cela permet de contrôler la souris sans avoir à capturer le curseur du client. Cela ne fonctionne pas dans la plupart des jeux.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="935"/>
|
||||
<location filename="../gui/SettingsView.qml" line="974"/>
|
||||
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
|
||||
<translation>Vous pouvez basculer ceci pendant la diffusion en utilisant Ctrl+Alt+Maj+M.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="946"/>
|
||||
<location filename="../gui/SettingsView.qml" line="985"/>
|
||||
<source>Capture system keyboard shortcuts</source>
|
||||
<translation>Capturer les raccourcis clavier système</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="954"/>
|
||||
<location filename="../gui/SettingsView.qml" line="993"/>
|
||||
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
|
||||
<translation>Ceci permet la capture de raccourcis clavier système comme Alt+Tab, qui seraient normalement utilisés par le système d’exploitation client.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1113"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1152"/>
|
||||
<source>Enable mouse control with gamepads by holding the 'Start' button</source>
|
||||
<translation>Active le contrôle de la souris par la manette en maintenant le bouton « Start »</translation>
|
||||
<translation>Active le contrôle de la souris par la manette en maintenant « Start »</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1124"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1163"/>
|
||||
<source>Process gamepad input when Moonlight is in the background</source>
|
||||
<translation>Capturer la manette même lorsque Moonlight est en arrière-plan</translation>
|
||||
<translation>Capturer la manette même quand Moonlight est en arrière-plan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1135"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1174"/>
|
||||
<source>Allows Moonlight to capture gamepad inputs even if it's not the current window in focus</source>
|
||||
<translation>Permet à Moonlight de capturer la manette même si Moonlight n'est pas au premier plan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1183"/>
|
||||
<source>Host Settings</source>
|
||||
<translation>Paramètres de l'hôte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1184"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<source>Advanced Settings</source>
|
||||
<translation>Paramètres avancés</translation>
|
||||
</message>
|
||||
@@ -780,17 +794,17 @@
|
||||
<translation type="vanished">Maximiser la fenêtre Moonlight au démarrage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="873"/>
|
||||
<location filename="../gui/SettingsView.qml" line="912"/>
|
||||
<source>Show connection quality warnings</source>
|
||||
<translation>Afficher les avertissements sur la qualité de la connexion</translation>
|
||||
<translation>Afficher des avertissements sur la qualité de la connexion</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="885"/>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<source>Discord Rich Presence integration</source>
|
||||
<translation>Intégration de la présence Discord Rich</translation>
|
||||
<translation>Activer l'intégration Discord Rich Presence</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<source>Updates your Discord status to display the name of the game you're streaming.</source>
|
||||
<translation>Met à jour votre statut Discord pour afficher le nom du jeu que vous diffusez en continu.</translation>
|
||||
</message>
|
||||
@@ -799,12 +813,12 @@
|
||||
<translation type="vanished">Forcé la manette de jeu Force #1 à être toujours présente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1106"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1145"/>
|
||||
<source>Only enable this option when streaming a game that doesn't support gamepads being connected after startup.</source>
|
||||
<translation>N'activez cette option que lorsque vous diffusez en continu un jeu qui ne prend pas en charge la connexion des manettes de jeu après le démarrage.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<location filename="../gui/SettingsView.qml" line="963"/>
|
||||
<source>Optimize mouse for remote desktop instead of games</source>
|
||||
<translation>Optimiser la souris pour le bureau à distance plutôt que pour les jeux</translation>
|
||||
</message>
|
||||
@@ -813,7 +827,7 @@
|
||||
<translation type="vanished">Utiliser l'écran tactile comme trackpad</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1028"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1067"/>
|
||||
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
|
||||
<translation>Lorsqu'il est coché, l'écran tactile agit comme un pavé tactile. Lorsqu'il n'est pas coché, l'écran tactile contrôle directement le pointeur de la souris.</translation>
|
||||
</message>
|
||||
@@ -834,7 +848,7 @@
|
||||
<translation type="vanished">Lorsque la case est cochée, permuter les boutons gauche et droit de la souris</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1154"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1193"/>
|
||||
<source>Optimize game settings for streaming</source>
|
||||
<translation>Optimiser les paramètres du jeu pour la diffusion en continu</translation>
|
||||
</message>
|
||||
@@ -843,63 +857,68 @@
|
||||
<translation type="vanished">Lecture audio sur le PC hôte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1165"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1204"/>
|
||||
<source>Quit app on host PC after ending stream</source>
|
||||
<translation>Quitter l'application sur le PC hôte après avoir terminé le flux</translation>
|
||||
<translation>Fermer l'application sur le PC hôte à la fin du stream</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1175"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1214"/>
|
||||
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
|
||||
<translation>Ceci fermera l'application ou le jeu que vous êtes en train de diffuser lorsque vous aurez terminé votre flux. Vous perdrez tout progrès non sauvegardé !</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1194"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1233"/>
|
||||
<source>Video decoder</source>
|
||||
<translation>Décodeur vidéo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1219"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1265"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1258"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1311"/>
|
||||
<source>Automatic (Recommended)</source>
|
||||
<translation>Automatique (recommandé)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1262"/>
|
||||
<source>Force software decoding</source>
|
||||
<translation>Forcer le décodage logiciel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1227"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1266"/>
|
||||
<source>Force hardware decoding</source>
|
||||
<translation>Forcer le décodage matériel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1240"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1279"/>
|
||||
<source>Video codec</source>
|
||||
<translation>Codec vidéo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1269"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1315"/>
|
||||
<source>H.264</source>
|
||||
<translation>H.264</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1273"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1319"/>
|
||||
<source>HEVC (H.265)</source>
|
||||
<translation>HEVC (H.265)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1290"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1289"/>
|
||||
<source>HEVC HDR (Experimental)</source>
|
||||
<translation>HEVC HDR (Expérimental)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1333"/>
|
||||
<source>Unlock unsupported FPS options</source>
|
||||
<translation>Débloquer les options FPS non supportées</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1309"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1352"/>
|
||||
<source>Automatically find PCs on the local network (Recommended)</source>
|
||||
<translation>Trouver automatiquement les PC sur le réseau local (recommandé)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1334"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1377"/>
|
||||
<source>Automatically detect blocked connections (Recommended)</source>
|
||||
<translation>Détecter automatiquement les connexions bloquées (recommandé)</translation>
|
||||
</message>
|
||||
@@ -999,9 +1018,8 @@
|
||||
<translation>Configuration du contrôleur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Aucun décodeur vidéo H.264 accéléré par le matériel n'a été détecté par Moonlight. Dans cette configuration, les performances de streaming peuvent être fortement dégradées.</translation>
|
||||
<translation type="vanished">Aucun décodeur vidéo H.264 accéléré par le matériel n'a été détecté par Moonlight. Dans cette configuration, les performances de streaming peuvent être fortement dégradées.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="423"/>
|
||||
@@ -1023,6 +1041,11 @@
|
||||
<source>Help</source>
|
||||
<translation>Aide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Aucun décodeur vidéo matériel n'a été détecté par Moonlight. Les performances de streaming risquent d'être sévèrement dégradées dans cette configuration.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="417"/>
|
||||
<source>Click the Help button for more information on solving this problem.</source>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+99
-76
@@ -93,7 +93,7 @@
|
||||
<context>
|
||||
<name>NvHTTP</name>
|
||||
<message>
|
||||
<location filename="../backend/nvhttp.cpp" line="327"/>
|
||||
<location filename="../backend/nvhttp.cpp" line="378"/>
|
||||
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
|
||||
<translation>Manglende lydopptaksenhet. Å reinstallere GeForce Experience bør løse dette problemet.</translation>
|
||||
</message>
|
||||
@@ -160,7 +160,7 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="224"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Versjonen av GeForce Experience på %1 støttes ikke av denne byggversjonen av Moonlight. Du må oppdatere Moonlight for å strømme fra %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="247"/>
|
||||
@@ -246,7 +246,7 @@
|
||||
<context>
|
||||
<name>PendingQuitTask</name>
|
||||
<message>
|
||||
<location filename="../backend/computermanager.cpp" line="542"/>
|
||||
<location filename="../backend/computermanager.cpp" line="540"/>
|
||||
<source>The running game wasn't started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
|
||||
<translation>Kjørende spill ble ikke startet på denne PC-en. Du må avslutte spillet på verts-PC-en manuelt eller bruke enheten som opprinnelig startet spillet.</translation>
|
||||
</message>
|
||||
@@ -327,112 +327,126 @@
|
||||
<translation>Tilkobling avsluttet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="592"/>
|
||||
<location filename="../streaming/session.cpp" line="613"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Versjonen av GeForce Experience på %1 støttes ikke av denne byggversjonen av Moonlight. Du må oppdatere Moonlight for å strømme fra %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="597"/>
|
||||
<location filename="../streaming/session.cpp" line="618"/>
|
||||
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
|
||||
<translation type="unfinished">At du har satt på fjernskrivebordsmusemodus kan forårsake problemer i spill.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="602"/>
|
||||
<location filename="../streaming/session.cpp" line="623"/>
|
||||
<source>HDR is not supported with software decoding.</source>
|
||||
<translation>HDR er ikke støttet med mykvaredekoding.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="606"/>
|
||||
<location filename="../streaming/session.cpp" line="627"/>
|
||||
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
|
||||
<translation type="unfinished">At du har satt på tvungen mykvaredekoding kan forårsake dårlig strømmingsytelse.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="611"/>
|
||||
<location filename="../streaming/session.cpp" line="632"/>
|
||||
<source>Using unsupported FPS options may cause stuttering or lag.</source>
|
||||
<translation>Bruk av ustøttede BPS-valg kan forårsake hakking eller forsinkelse.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="614"/>
|
||||
<location filename="../streaming/session.cpp" line="635"/>
|
||||
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
|
||||
<translation>VSync vil bli avskrudd under strømming i høyere bilderammetakt enn skjermen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="631"/>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished">Bruker mykvaredekoding som følge av ditt valg om påtvungen HEVC uten skjermkortsstøtte. Dette kan forårsake dårlig strømmingsytelse.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="634"/>
|
||||
<source>This PC's GPU doesn't support HEVC decoding.</source>
|
||||
<translation>PC-ens skjermkort støtter ikke HEVC-dekoding.</translation>
|
||||
<translation type="vanished">PC-ens skjermkort støtter ikke HEVC-dekoding.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="641"/>
|
||||
<location filename="../streaming/session.cpp" line="645"/>
|
||||
<source>Your host PC GPU doesn't support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
|
||||
<translation type="unfinished">Skjermkortet i din verts-PC støtter ikke HEVC. Et GeForce GTX kort i 900-serien (Maxwell) eller senere kreves.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="658"/>
|
||||
<location filename="../streaming/session.cpp" line="676"/>
|
||||
<source>Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="686"/>
|
||||
<source>Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="689"/>
|
||||
<source>Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="700"/>
|
||||
<source>%1 doesn't support HDR10.</source>
|
||||
<translation>%1 støtter ikke HDR10.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<location filename="../streaming/session.cpp" line="704"/>
|
||||
<source>Your host PC GPU doesn't support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
|
||||
<translation type="unfinished">Skjermkortet i din verts-PC støtter ikke HDR-strømming. Et GeForce GTX kort i 1000-serien (Pascal) eller senere kreves.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="671"/>
|
||||
<location filename="../streaming/session.cpp" line="713"/>
|
||||
<source>This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.</source>
|
||||
<translation>Denne PC-ens skjermkort støtter ikke HEVC Main10-dekoding for HDR-strømming.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="684"/>
|
||||
<location filename="../streaming/session.cpp" line="726"/>
|
||||
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
|
||||
<translation type="unfinished">GeForce Experience 3.0 eller senere kreves for 4k-strømming.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="699"/>
|
||||
<location filename="../streaming/session.cpp" line="741"/>
|
||||
<source>Your selected surround sound setting is not supported by the current audio device.</source>
|
||||
<translation>Din valgte kringlydsinnstilling støttes ikke på nåværende lydenhet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="706"/>
|
||||
<location filename="../streaming/session.cpp" line="748"/>
|
||||
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
|
||||
<translation>Klarte ikke å åpne lydenhet. Lyd vil være utilgjengelig under denne økten.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="711"/>
|
||||
<location filename="../streaming/session.cpp" line="753"/>
|
||||
<source>An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this.</source>
|
||||
<translation>En tilknyttet spillkontroller har ingen tastetilknytninger, og vil ikke kunne brukes. Besøk Moonlight-hjelpen for å løse dette.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="719"/>
|
||||
<location filename="../streaming/session.cpp" line="761"/>
|
||||
<source>Your host PC's GPU doesn't support streaming video resolutions over 4K.</source>
|
||||
<translation type="unfinished">Skjermkortet i din verts-PC støtter ikke strømming av videooppløsninger over 4k.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="723"/>
|
||||
<location filename="../streaming/session.cpp" line="765"/>
|
||||
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
|
||||
<translation>Videooppløsninger over 4k støttes kun av HEVC-kodeket.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="737"/>
|
||||
<location filename="../streaming/session.cpp" line="779"/>
|
||||
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.</source>
|
||||
<translation type="unfinished">At du har satt på tvungen maskinvaredekoding har ingen innvirkning siden det ikke er støtte for det på denne PC-ens skjermkort.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="740"/>
|
||||
<location filename="../streaming/session.cpp" line="782"/>
|
||||
<source>Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.</source>
|
||||
<translation type="unfinished">At du har valgt tungen maskinvaredekoding fungerer ikke sammen med kodekvalget. Denne PC-ens skjermkort mangler støtte for dekoding i valgt kodek.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1041"/>
|
||||
<location filename="../streaming/session.cpp" line="1107"/>
|
||||
<source>GeForce Experience returned error: %1</source>
|
||||
<translation>GeForce Experience returnerte en feil: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1519"/>
|
||||
<location filename="../streaming/session.cpp" line="1639"/>
|
||||
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
|
||||
<translation type="unfinished">Kunne ikke starte videodekoder. Sjekk strømmingsinnstillingene dine og prøv igjen.</translation>
|
||||
</message>
|
||||
@@ -534,7 +548,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="573"/>
|
||||
<location filename="../gui/SettingsView.qml" line="860"/>
|
||||
<location filename="../gui/SettingsView.qml" line="899"/>
|
||||
<source>Fullscreen</source>
|
||||
<translation>Fullskjermsvisning</translation>
|
||||
</message>
|
||||
@@ -559,7 +573,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="581"/>
|
||||
<location filename="../gui/SettingsView.qml" line="852"/>
|
||||
<location filename="../gui/SettingsView.qml" line="891"/>
|
||||
<source>Windowed</source>
|
||||
<translation>Vindusmodus</translation>
|
||||
</message>
|
||||
@@ -639,32 +653,32 @@
|
||||
<translation>Automatisk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="805"/>
|
||||
<location filename="../gui/SettingsView.qml" line="844"/>
|
||||
<source>You must restart Moonlight for this change to take effect</source>
|
||||
<translation>Du må starte Moonlight på ny for at denne endringen skal tre i effekt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="819"/>
|
||||
<location filename="../gui/SettingsView.qml" line="858"/>
|
||||
<source>GUI display mode</source>
|
||||
<translation>Visningsmodus for brukergrensesnitt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="856"/>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<source>Maximized</source>
|
||||
<translation>Maksimert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="913"/>
|
||||
<location filename="../gui/SettingsView.qml" line="952"/>
|
||||
<source>Input Settings</source>
|
||||
<translation type="unfinished">Inndata-innstillinger</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<location filename="../gui/SettingsView.qml" line="973"/>
|
||||
<source>This enables seamless mouse control without capturing the client's mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
|
||||
<translation type="unfinished">Skrur på sømløs musekontroll uten å fange klientens musepeker. Dette er ideelt for fjernskrivebordsbruk, men fungerer ikke i de fleste spill.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="936"/>
|
||||
<location filename="../gui/SettingsView.qml" line="975"/>
|
||||
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
|
||||
<translation type="unfinished">Merk: Som følge av en feil i GeForce Experience, vil dette alternativet muligens ikke fungere riktig hvis din verts-PC har flere skjermer.</translation>
|
||||
</message>
|
||||
@@ -677,77 +691,77 @@
|
||||
<translation type="vanished">Dies ermöglicht die Erfassung von systemweiten Tastenkombinationen wie Alt+Tab, die normalerweise vom Client beim Streamen im Vollbildmodus ausgeführt werden würden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="955"/>
|
||||
<location filename="../gui/SettingsView.qml" line="994"/>
|
||||
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
|
||||
<translation type="unfinished">Merk: Gitte tastatursnarveier, som Ctrl+Alt+Del på Windows kan ikke fanges av noe program, inkludert Moonlight.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1018"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1057"/>
|
||||
<source>Use touchscreen as a virtual trackpad</source>
|
||||
<translation>Bruk pekeskjerm som virtuell pekeflate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1035"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1074"/>
|
||||
<source>Swap left and right mouse buttons</source>
|
||||
<translation>Bytt venstre og høyre musetaster</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1047"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1086"/>
|
||||
<source>Reverse mouse scrolling direction</source>
|
||||
<translation>Omvendt muserullingsretning</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1061"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1100"/>
|
||||
<source>Gamepad Settings</source>
|
||||
<translation>Spillkontrolller-innstillinger</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1071"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1110"/>
|
||||
<source>Swap A/B and X/Y gamepad buttons</source>
|
||||
<translation>Veksle A/B- og X/Y-spillkontrollerknapper</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1089"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1128"/>
|
||||
<source>This switches gamepads into a Nintendo-style button layout</source>
|
||||
<translation>Dette bytter spillkontrollere til knappeoppsett i Nintendo-stil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1095"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1134"/>
|
||||
<source>Force gamepad #1 always connected</source>
|
||||
<translation type="unfinished">Krev at spillkontroller nr. 1 alltid er tilkoblet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1105"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
|
||||
<translation type="unfinished">Krever at én spillkontroller alltid er tilkoblet verten, selv om ingen spillkontrollere faktisk er tilkoblet til denne PC-en.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="935"/>
|
||||
<location filename="../gui/SettingsView.qml" line="974"/>
|
||||
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
|
||||
<translation>Du kan veksle dette under strømming ved bruk av Ctrl+Alt+Shift+M.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1113"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1152"/>
|
||||
<source>Enable mouse control with gamepads by holding the 'Start' button</source>
|
||||
<translation type="unfinished">Skrur på musekontroll ved bruk av spillkontrollere ved å holde «Start»-knappen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1124"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1163"/>
|
||||
<source>Process gamepad input when Moonlight is in the background</source>
|
||||
<translation>Behandle spillkontrollerinndata når Moonlight er i bakgrunnen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1135"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1174"/>
|
||||
<source>Allows Moonlight to capture gamepad inputs even if it's not the current window in focus</source>
|
||||
<translation type="unfinished">Lar Moonlight fange opp spillkontollerinndata selv når det ikke er i fokus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1183"/>
|
||||
<source>Host Settings</source>
|
||||
<translation>Vertsinnstillinger</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1184"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<source>Advanced Settings</source>
|
||||
<translation>Avanserte innstillinger</translation>
|
||||
</message>
|
||||
@@ -757,118 +771,123 @@
|
||||
<translation>Lydoppsett</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="873"/>
|
||||
<location filename="../gui/SettingsView.qml" line="912"/>
|
||||
<source>Show connection quality warnings</source>
|
||||
<translation>Vis kvalitetsadvarsler om tilkoblingen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="885"/>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<source>Discord Rich Presence integration</source>
|
||||
<translation>Rik tilstedeværelsesintegrasjon for Discord</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<source>Updates your Discord status to display the name of the game you're streaming.</source>
|
||||
<translation>Oppdaterer din Discord-status til å vise navnet på spillet du strømmer.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1106"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1145"/>
|
||||
<source>Only enable this option when streaming a game that doesn't support gamepads being connected after startup.</source>
|
||||
<translation type="unfinished">Kun skru på dette når du strømmer et spill som ikke støtter av spillkontrollere er tilkoblet etter oppstart.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<location filename="../gui/SettingsView.qml" line="963"/>
|
||||
<source>Optimize mouse for remote desktop instead of games</source>
|
||||
<translation>Optimaliser mus for fjernskrivebord istedenfor spill</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="946"/>
|
||||
<location filename="../gui/SettingsView.qml" line="985"/>
|
||||
<source>Capture system keyboard shortcuts</source>
|
||||
<translation>Fang systemtastatursarveier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="954"/>
|
||||
<location filename="../gui/SettingsView.qml" line="993"/>
|
||||
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
|
||||
<translation>Lar deg fange tastatursnarveier på hele systemet, slik som Alt+Tav som normalt ville blitt håndtert av klient-OS under strømming.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="984"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1023"/>
|
||||
<source>in fullscreen</source>
|
||||
<translation>i fullskjermsmodus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="988"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1027"/>
|
||||
<source>always</source>
|
||||
<translation>alltid</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1028"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1067"/>
|
||||
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
|
||||
<translation type="unfinished">Får pekeskjermen til å oppføre seg som pekeflate. Ellers vil pekeskjermen kontrollere musepekeren direkte.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1154"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1193"/>
|
||||
<source>Optimize game settings for streaming</source>
|
||||
<translation>Optimaliser spillinnstillinger for strømming</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1165"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1204"/>
|
||||
<source>Quit app on host PC after ending stream</source>
|
||||
<translation>Avslutt program på verts-PC etter at strømmen fullendes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1175"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1214"/>
|
||||
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
|
||||
<translation>Dette vil lukke programmet eller spillet du strømmer når du avslutter strømmen din. All ulagret fremdrift vil gå tapt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1194"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1233"/>
|
||||
<source>Video decoder</source>
|
||||
<translation>Videodekoder</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1219"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1265"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1258"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1311"/>
|
||||
<source>Automatic (Recommended)</source>
|
||||
<translation>Automatisk (anbefalt)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1262"/>
|
||||
<source>Force software decoding</source>
|
||||
<translation>Påtving mykvaredekoding</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1227"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1266"/>
|
||||
<source>Force hardware decoding</source>
|
||||
<translation>Påtving maskinvaredekoding</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1240"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1279"/>
|
||||
<source>Video codec</source>
|
||||
<translation>Videokodek</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1269"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1315"/>
|
||||
<source>H.264</source>
|
||||
<translation>H.264</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1273"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1319"/>
|
||||
<source>HEVC (H.265)</source>
|
||||
<translation>HEVC (H.265)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1290"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1289"/>
|
||||
<source>HEVC HDR (Experimental)</source>
|
||||
<translation>HEVC-HDR (eksperimentelt)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1333"/>
|
||||
<source>Unlock unsupported FPS options</source>
|
||||
<translation>Lås opp ustøttede BPS-innstillinger</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1309"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1352"/>
|
||||
<source>Automatically find PCs on the local network (Recommended)</source>
|
||||
<translation>Finn PC-er på lokalnettverket automatisk (anbefales)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1334"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1377"/>
|
||||
<source>Automatically detect blocked connections (Recommended)</source>
|
||||
<translation>Oppdag blokkerte tilkoblinger automatisk (anbefales)</translation>
|
||||
</message>
|
||||
@@ -968,9 +987,8 @@
|
||||
<translation>Tilknytning av taster på spillkontroller</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation type="unfinished">Ingen funksjonell maskinvareakselerert H.264-videodekoder ble oppdaget av Moonlight. Din strømmingsytelse kan nedgraderes betydelig i dette oppsettet.</translation>
|
||||
<translation type="obsolete">Ingen funksjonell maskinvareakselerert H.264-videodekoder ble oppdaget av Moonlight. Din strømmingsytelse kan nedgraderes betydelig i dette oppsettet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="423"/>
|
||||
@@ -992,6 +1010,11 @@
|
||||
<source>Help</source>
|
||||
<translation>Hjelp</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="417"/>
|
||||
<source>Click the Help button for more information on solving this problem.</source>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+99
-76
@@ -93,7 +93,7 @@
|
||||
<context>
|
||||
<name>NvHTTP</name>
|
||||
<message>
|
||||
<location filename="../backend/nvhttp.cpp" line="327"/>
|
||||
<location filename="../backend/nvhttp.cpp" line="378"/>
|
||||
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
|
||||
<translation>Потеряно виртуальное аудиоустройство. Попробуйте переустановить GeForce Experience.</translation>
|
||||
</message>
|
||||
@@ -160,7 +160,7 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="224"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Версия GeForce Experience на %1 не поддерживается данной сборкой Moonlight. Вы должны обновить Moonlight для потоковой передачи с %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="247"/>
|
||||
@@ -246,7 +246,7 @@
|
||||
<context>
|
||||
<name>PendingQuitTask</name>
|
||||
<message>
|
||||
<location filename="../backend/computermanager.cpp" line="542"/>
|
||||
<location filename="../backend/computermanager.cpp" line="540"/>
|
||||
<source>The running game wasn't started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
|
||||
<translation>Выбранная игра уже запущена. Вручную закройте игру на хост-ПК, или используйте устройство, с которого она была запущена.</translation>
|
||||
</message>
|
||||
@@ -327,112 +327,126 @@
|
||||
<translation>Подключение разорвано</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="592"/>
|
||||
<location filename="../streaming/session.cpp" line="613"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Версия GeForce Experience на %1 не поддерживается данной сборкой Moonlight. Вы должны обновить Moonlight для потоковой передачи с %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="597"/>
|
||||
<location filename="../streaming/session.cpp" line="618"/>
|
||||
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
|
||||
<translation>Вы выбрали режим удалённого рабочего стола. Это может вызвать проблемы с мышью в некоторых играх.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="602"/>
|
||||
<location filename="../streaming/session.cpp" line="623"/>
|
||||
<source>HDR is not supported with software decoding.</source>
|
||||
<translation>HDR не поддерживается с программным декодированием.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="606"/>
|
||||
<location filename="../streaming/session.cpp" line="627"/>
|
||||
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
|
||||
<translation>Вы выбрали программное декодирование, что может ухудшить производительность.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="611"/>
|
||||
<location filename="../streaming/session.cpp" line="632"/>
|
||||
<source>Using unsupported FPS options may cause stuttering or lag.</source>
|
||||
<translation>Неподдерживаемые значения FPS могут вызвать подтормаживания или повысить задержку.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="614"/>
|
||||
<location filename="../streaming/session.cpp" line="635"/>
|
||||
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
|
||||
<translation>V-sync будет отключён, если частота кадров стрима превышает частоту развёртки экрана.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="631"/>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>Ваша видеокарта не поддерживает аппаратное декодирование HEVC, используется программное декодирование. Это может ухудшить производительность.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="634"/>
|
||||
<source>This PC's GPU doesn't support HEVC decoding.</source>
|
||||
<translation>Видеокарта этого ПК не поддерживает декодирование HEVC.</translation>
|
||||
<translation type="vanished">Видеокарта этого ПК не поддерживает декодирование HEVC.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="641"/>
|
||||
<location filename="../streaming/session.cpp" line="645"/>
|
||||
<source>Your host PC GPU doesn't support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
|
||||
<translation>Видеокарта хост-ПК не поддеживает HEVC. Требуется видеокарты GTX 900 серии или новее. Используйте H264.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="658"/>
|
||||
<location filename="../streaming/session.cpp" line="676"/>
|
||||
<source>Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="686"/>
|
||||
<source>Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="689"/>
|
||||
<source>Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="700"/>
|
||||
<source>%1 doesn't support HDR10.</source>
|
||||
<translation>%1 не поддеживает HDR10.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<location filename="../streaming/session.cpp" line="704"/>
|
||||
<source>Your host PC GPU doesn't support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
|
||||
<translation>Видеокарты ПК-хоста не поддеживает стрим в HDR. Нужна видеокарта GTX серии 1000 или новее.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="671"/>
|
||||
<location filename="../streaming/session.cpp" line="713"/>
|
||||
<source>This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.</source>
|
||||
<translation>Видеокарта этого ПК не поддерживает декодирование HEVC профиля Main10, HDR недоступен.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="684"/>
|
||||
<location filename="../streaming/session.cpp" line="726"/>
|
||||
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
|
||||
<translation>Разрешение 4K требует GeForce Experience 3.0 или новее.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="699"/>
|
||||
<location filename="../streaming/session.cpp" line="741"/>
|
||||
<source>Your selected surround sound setting is not supported by the current audio device.</source>
|
||||
<translation>Ваша настройка объёмного звука не поддерживается текущим аудиоустройством.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="706"/>
|
||||
<location filename="../streaming/session.cpp" line="748"/>
|
||||
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
|
||||
<translation>Не удалось открыть аудиоустройство на этом ПК. Звук будет отключён на этот раз.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="711"/>
|
||||
<location filename="../streaming/session.cpp" line="753"/>
|
||||
<source>An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this.</source>
|
||||
<translation>Подключённый геймпад не настроен и не может быть использован. Посетите раздел помощи Moonlight, чтобы исправить это.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="719"/>
|
||||
<location filename="../streaming/session.cpp" line="761"/>
|
||||
<source>Your host PC's GPU doesn't support streaming video resolutions over 4K.</source>
|
||||
<translation>Видеокарта хост-ПК не поддерживает разрешения выше 4K.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="723"/>
|
||||
<location filename="../streaming/session.cpp" line="765"/>
|
||||
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
|
||||
<translation>Разрешения выше 4K работают только с HEVC.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="737"/>
|
||||
<location filename="../streaming/session.cpp" line="779"/>
|
||||
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.</source>
|
||||
<translation>Вы выбрали аппаратное декодирование, но видеокарта этого ПК не поддерживает аппаратное декодирование для выбранного кодека.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="740"/>
|
||||
<location filename="../streaming/session.cpp" line="782"/>
|
||||
<source>Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.</source>
|
||||
<translation>Вы выбрали аппаратное декодирование, но видеокарта этого ПК не поддерживает аппаратное декодирование для выбранного кодека.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1041"/>
|
||||
<location filename="../streaming/session.cpp" line="1107"/>
|
||||
<source>GeForce Experience returned error: %1</source>
|
||||
<translation>Ошибка GeForce Experience: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1519"/>
|
||||
<location filename="../streaming/session.cpp" line="1639"/>
|
||||
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
|
||||
<translation>Не удалось запустить декодирование видео. Проверьте настройки стрима и попробуйте ещё раз.</translation>
|
||||
</message>
|
||||
@@ -534,7 +548,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="573"/>
|
||||
<location filename="../gui/SettingsView.qml" line="860"/>
|
||||
<location filename="../gui/SettingsView.qml" line="899"/>
|
||||
<source>Fullscreen</source>
|
||||
<translation>На весь экран</translation>
|
||||
</message>
|
||||
@@ -559,7 +573,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="581"/>
|
||||
<location filename="../gui/SettingsView.qml" line="852"/>
|
||||
<location filename="../gui/SettingsView.qml" line="891"/>
|
||||
<source>Windowed</source>
|
||||
<translation>В окне</translation>
|
||||
</message>
|
||||
@@ -639,32 +653,32 @@
|
||||
<translation>Автоматически</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="805"/>
|
||||
<location filename="../gui/SettingsView.qml" line="844"/>
|
||||
<source>You must restart Moonlight for this change to take effect</source>
|
||||
<translation>Для применения изменений необходим перезапуск Moonlight</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="819"/>
|
||||
<location filename="../gui/SettingsView.qml" line="858"/>
|
||||
<source>GUI display mode</source>
|
||||
<translation>Режим показа интерфейса</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="856"/>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<source>Maximized</source>
|
||||
<translation>Развёрнуто</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="913"/>
|
||||
<location filename="../gui/SettingsView.qml" line="952"/>
|
||||
<source>Input Settings</source>
|
||||
<translation>Настройки ввода</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<location filename="../gui/SettingsView.qml" line="973"/>
|
||||
<source>This enables seamless mouse control without capturing the client's mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
|
||||
<translation>Удобно для удалённого рабочего стола, т.к. курсор мыши не будет "захвачен" и может уходить за пределы окна Moonlight. Не будет работать в большинстве игр.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="936"/>
|
||||
<location filename="../gui/SettingsView.qml" line="975"/>
|
||||
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
|
||||
<translation>Предупреждение: Из-за бага GeForce Experience эта настройка может не работать, если к хост-ПК подключено несколько мониторов.</translation>
|
||||
</message>
|
||||
@@ -677,77 +691,77 @@
|
||||
<translation type="vanished">Dies ermöglicht die Erfassung von systemweiten Tastenkombinationen wie Alt+Tab, die normalerweise vom Client beim Streamen im Vollbildmodus ausgeführt werden würden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="955"/>
|
||||
<location filename="../gui/SettingsView.qml" line="994"/>
|
||||
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
|
||||
<translation>Предупреждение: некоторые комбинации клавиш, вроде Ctrl-Alt-Del, не могут быть перехвачены.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1018"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1057"/>
|
||||
<source>Use touchscreen as a virtual trackpad</source>
|
||||
<translation>Использовать сенсорный экран как виртуальный тачпад</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1035"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1074"/>
|
||||
<source>Swap left and right mouse buttons</source>
|
||||
<translation>Поменять местами левую и правую кнопки мыши</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1047"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1086"/>
|
||||
<source>Reverse mouse scrolling direction</source>
|
||||
<translation>Колесо мыши крутит в обратном направлении</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1061"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1100"/>
|
||||
<source>Gamepad Settings</source>
|
||||
<translation>Настройки геймпада</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1071"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1110"/>
|
||||
<source>Swap A/B and X/Y gamepad buttons</source>
|
||||
<translation>Поменять местами кнопки A/B и X/Y</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1089"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1128"/>
|
||||
<source>This switches gamepads into a Nintendo-style button layout</source>
|
||||
<translation>Меняет назначения кнопок в стиль Nintendo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1095"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1134"/>
|
||||
<source>Force gamepad #1 always connected</source>
|
||||
<translation>Принудительно оставить геймпад №1 подключённым</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1105"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
|
||||
<translation>Хотя бы один виртуальный геймпад всегда будет "подключён" к хост-ПК, даже если физически к ПК-хосту не подключено ни одного.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="935"/>
|
||||
<location filename="../gui/SettingsView.qml" line="974"/>
|
||||
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
|
||||
<translation>Вы можете переключить эту настройку во время стрима нажатием Ctrl+Alt+Shift+M.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1113"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1152"/>
|
||||
<source>Enable mouse control with gamepads by holding the 'Start' button</source>
|
||||
<translation>Переходить в режим управления курсором мыши удержанием кнопки "Start"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1124"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1163"/>
|
||||
<source>Process gamepad input when Moonlight is in the background</source>
|
||||
<translation>Геймпад работает, когда окно Moonlight в фоне</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1135"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1174"/>
|
||||
<source>Allows Moonlight to capture gamepad inputs even if it's not the current window in focus</source>
|
||||
<translation>Позволяет захватывать ввод с геймпада, даже когда окно Moonlight неактивно</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1183"/>
|
||||
<source>Host Settings</source>
|
||||
<translation>Настройки ПК-хоста</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1184"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<source>Advanced Settings</source>
|
||||
<translation>Расширенные настройки</translation>
|
||||
</message>
|
||||
@@ -757,118 +771,123 @@
|
||||
<translation>Настройки звука</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="873"/>
|
||||
<location filename="../gui/SettingsView.qml" line="912"/>
|
||||
<source>Show connection quality warnings</source>
|
||||
<translation>Показывать предупреждения о качестве соединения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="885"/>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<source>Discord Rich Presence integration</source>
|
||||
<translation>Интеграция в Discord Rich Presence</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<source>Updates your Discord status to display the name of the game you're streaming.</source>
|
||||
<translation>Пишет статус в Discord с названием запущенноый игры.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1106"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1145"/>
|
||||
<source>Only enable this option when streaming a game that doesn't support gamepads being connected after startup.</source>
|
||||
<translation>Необходимо для игр, не поддерживающих "горячее" подключение геймпадов.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<location filename="../gui/SettingsView.qml" line="963"/>
|
||||
<source>Optimize mouse for remote desktop instead of games</source>
|
||||
<translation>Оптимизировать мышь для удалённого рабочего стола вместо игр</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="946"/>
|
||||
<location filename="../gui/SettingsView.qml" line="985"/>
|
||||
<source>Capture system keyboard shortcuts</source>
|
||||
<translation>Захватывать нажатия горячих клавиш</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="954"/>
|
||||
<location filename="../gui/SettingsView.qml" line="993"/>
|
||||
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
|
||||
<translation>Это включает захват комбинаций вроде Alt+Tab, которые иначе бы обычно обрабатывались операционной системой.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="984"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1023"/>
|
||||
<source>in fullscreen</source>
|
||||
<translation>только в режиме на весь экран</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="988"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1027"/>
|
||||
<source>always</source>
|
||||
<translation>всегда</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1028"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1067"/>
|
||||
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
|
||||
<translation>Если включено, сенсорный экран будет использоваться как тачпад. Если отключено, сенсорный экран будет прямо управлять курсором мыши.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1154"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1193"/>
|
||||
<source>Optimize game settings for streaming</source>
|
||||
<translation>Позволить GeForce Experience оптимизировать настройки игры</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1165"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1204"/>
|
||||
<source>Quit app on host PC after ending stream</source>
|
||||
<translation>Завершить игру на ПК-хосте при выходе</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1175"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1214"/>
|
||||
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
|
||||
<translation>Это выйдет из игры или приложения при закрытии окна Moonlight или нажатии Ctrl+Alt+Shift+Q. Несохранённый прогресс может быть утерян!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1194"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1233"/>
|
||||
<source>Video decoder</source>
|
||||
<translation>Декодер видео</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1219"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1265"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1258"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1311"/>
|
||||
<source>Automatic (Recommended)</source>
|
||||
<translation>Автоматически (рекомендуется)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1262"/>
|
||||
<source>Force software decoding</source>
|
||||
<translation>Программный</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1227"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1266"/>
|
||||
<source>Force hardware decoding</source>
|
||||
<translation>Аппаратный</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1240"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1279"/>
|
||||
<source>Video codec</source>
|
||||
<translation>Видеокодек</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1269"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1315"/>
|
||||
<source>H.264</source>
|
||||
<translation>H.264</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1273"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1319"/>
|
||||
<source>HEVC (H.265)</source>
|
||||
<translation>HEVC (H.265)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1290"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1289"/>
|
||||
<source>HEVC HDR (Experimental)</source>
|
||||
<translation>HEVC HDR (Экспериментальный)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1333"/>
|
||||
<source>Unlock unsupported FPS options</source>
|
||||
<translation>Включить неподдерживаемые настройки FPS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1309"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1352"/>
|
||||
<source>Automatically find PCs on the local network (Recommended)</source>
|
||||
<translation>Автоматически найти ПК-хосты в локальной сети (рекомендуется)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1334"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1377"/>
|
||||
<source>Automatically detect blocked connections (Recommended)</source>
|
||||
<translation>Автоматически определять проблемы с подключением (рекомендуется)</translation>
|
||||
</message>
|
||||
@@ -968,9 +987,8 @@
|
||||
<translation>Назначение кнопок геймпада</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Moonlight не может найти декодер для H.264. Программное декодирование может быть намного медленнее.</translation>
|
||||
<translation type="vanished">Moonlight не может найти декодер для H.264. Программное декодирование может быть намного медленнее.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="423"/>
|
||||
@@ -992,6 +1010,11 @@
|
||||
<source>Help</source>
|
||||
<translation>Помощь</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="417"/>
|
||||
<source>Click the Help button for more information on solving this problem.</source>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+98
-75
@@ -93,7 +93,7 @@
|
||||
<context>
|
||||
<name>NvHTTP</name>
|
||||
<message>
|
||||
<location filename="../backend/nvhttp.cpp" line="327"/>
|
||||
<location filename="../backend/nvhttp.cpp" line="378"/>
|
||||
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
|
||||
<translation>缺少音频捕获设备。重新安装 GeForce Experience 可以解决此错误。</translation>
|
||||
</message>
|
||||
@@ -173,7 +173,7 @@
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="224"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>在 %1 上已安装的GeForce Experience版本不支持当前版本的Moonlight。你必须升级Moonlight才能从 %1 串流。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/PcView.qml" line="247"/>
|
||||
@@ -234,7 +234,7 @@
|
||||
<context>
|
||||
<name>PendingQuitTask</name>
|
||||
<message>
|
||||
<location filename="../backend/computermanager.cpp" line="542"/>
|
||||
<location filename="../backend/computermanager.cpp" line="540"/>
|
||||
<source>The running game wasn't started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
|
||||
<translation>正在运行的游戏不是由这台计算机启动的。你必须在目标计算机上手动退出游戏或使用最初启动游戏的设备。</translation>
|
||||
</message>
|
||||
@@ -311,112 +311,126 @@
|
||||
<translation>会话已终止</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="592"/>
|
||||
<location filename="../streaming/session.cpp" line="613"/>
|
||||
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>%1 上的 GeForce Experience 版本不受当前版本的 Moonlight 支持。你必须更新 Moonlight才能从 %1 串流。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="597"/>
|
||||
<location filename="../streaming/session.cpp" line="618"/>
|
||||
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
|
||||
<translation>启用远程桌面鼠标模式可能会导致游戏出现问题。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="602"/>
|
||||
<location filename="../streaming/session.cpp" line="623"/>
|
||||
<source>HDR is not supported with software decoding.</source>
|
||||
<translation>软件解码不支持 HDR。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="606"/>
|
||||
<location filename="../streaming/session.cpp" line="627"/>
|
||||
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
|
||||
<translation>你选择的用于强制软件解码的设置可能会导致流式传输性能不佳。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="611"/>
|
||||
<location filename="../streaming/session.cpp" line="632"/>
|
||||
<source>Using unsupported FPS options may cause stuttering or lag.</source>
|
||||
<translation>使用不受支持的 FPS 选项可能会导致卡顿或延迟。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="614"/>
|
||||
<location filename="../streaming/session.cpp" line="635"/>
|
||||
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
|
||||
<translation>当流式传输的帧率高于显示时,垂直同步将被禁用。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="631"/>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>根据你的选择使用软件解码,以在没有显卡支持的情况下强制 HEVC。这可能会导致流式传输性能不佳。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="634"/>
|
||||
<source>This PC's GPU doesn't support HEVC decoding.</source>
|
||||
<translation>此计算机的显卡不支持 HEVC 解码。</translation>
|
||||
<translation type="vanished">此计算机的显卡不支持 HEVC 解码。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="641"/>
|
||||
<location filename="../streaming/session.cpp" line="645"/>
|
||||
<source>Your host PC GPU doesn't support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
|
||||
<translation>目标计算机的显卡不支持 HEVC。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="658"/>
|
||||
<location filename="../streaming/session.cpp" line="676"/>
|
||||
<source>Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance.</source>
|
||||
<translation>使用软件解码,因为你选择强制使用没有 GPU 支持的H.264。这可能会导致较差的流性能。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="686"/>
|
||||
<source>Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance.</source>
|
||||
<translation>你的主机和客户端 PC 不支持相同的视频编解码器。这可能会导致较差的流性能。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="689"/>
|
||||
<source>Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance.</source>
|
||||
<translation>你的客户端 GPU 不支持 H.264 解码。这可能会导致较差的流性能。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="700"/>
|
||||
<source>%1 doesn't support HDR10.</source>
|
||||
<translation>%1 不支持 HDR10。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="662"/>
|
||||
<location filename="../streaming/session.cpp" line="704"/>
|
||||
<source>Your host PC GPU doesn't support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
|
||||
<translation>目标计算机的显卡不支持 HDR 流。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="671"/>
|
||||
<location filename="../streaming/session.cpp" line="713"/>
|
||||
<source>This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.</source>
|
||||
<translation>此计算机的显卡不支持 HEVC Main10 解码 HDR 流。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="684"/>
|
||||
<location filename="../streaming/session.cpp" line="726"/>
|
||||
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
|
||||
<translation>4K 流式传输需要 GeForce Experience 3.0 或更高版本。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="699"/>
|
||||
<location filename="../streaming/session.cpp" line="741"/>
|
||||
<source>Your selected surround sound setting is not supported by the current audio device.</source>
|
||||
<translation>当前音频设备不支持你选择的环绕声设置。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="706"/>
|
||||
<location filename="../streaming/session.cpp" line="748"/>
|
||||
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
|
||||
<translation>无法打开音频设备。音频在此会话期间将不可用。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="711"/>
|
||||
<location filename="../streaming/session.cpp" line="753"/>
|
||||
<source>An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this.</source>
|
||||
<translation>附加的手柄没有映射,因此无法使用。请访问 Moonlight 帮助解决此问题。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="719"/>
|
||||
<location filename="../streaming/session.cpp" line="761"/>
|
||||
<source>Your host PC's GPU doesn't support streaming video resolutions over 4K.</source>
|
||||
<translation>目标计算机的显卡不支持 4K 以上的流式传输视频分辨率。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="723"/>
|
||||
<location filename="../streaming/session.cpp" line="765"/>
|
||||
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
|
||||
<translation>只有 HEVC 编解码器才支持 4K 以上的视频分辨率。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="737"/>
|
||||
<location filename="../streaming/session.cpp" line="779"/>
|
||||
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.</source>
|
||||
<translation>由于此计算机的显卡上缺少硬件解码支持,无法满足强制硬件解码的选择。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="740"/>
|
||||
<location filename="../streaming/session.cpp" line="782"/>
|
||||
<source>Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.</source>
|
||||
<translation>你选择的编解码器和强制硬件解码设置不兼容。此计算机的显卡不支持解码你选择的编解码器。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1041"/>
|
||||
<location filename="../streaming/session.cpp" line="1107"/>
|
||||
<source>GeForce Experience returned error: %1</source>
|
||||
<translation>GeForce Experience 返回错误: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../streaming/session.cpp" line="1519"/>
|
||||
<location filename="../streaming/session.cpp" line="1639"/>
|
||||
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
|
||||
<translation>无法初始化视频解码器。请检查你的流式传输设置,然后重试。</translation>
|
||||
</message>
|
||||
@@ -522,7 +536,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="573"/>
|
||||
<location filename="../gui/SettingsView.qml" line="860"/>
|
||||
<location filename="../gui/SettingsView.qml" line="899"/>
|
||||
<source>Fullscreen</source>
|
||||
<translation>全屏</translation>
|
||||
</message>
|
||||
@@ -533,7 +547,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="581"/>
|
||||
<location filename="../gui/SettingsView.qml" line="852"/>
|
||||
<location filename="../gui/SettingsView.qml" line="891"/>
|
||||
<source>Windowed</source>
|
||||
<translation>窗口化</translation>
|
||||
</message>
|
||||
@@ -623,67 +637,67 @@
|
||||
<translation>自动化</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="805"/>
|
||||
<location filename="../gui/SettingsView.qml" line="844"/>
|
||||
<source>You must restart Moonlight for this change to take effect</source>
|
||||
<translation>你必须重新启动 Moonlight 才能使更改生效</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="819"/>
|
||||
<location filename="../gui/SettingsView.qml" line="858"/>
|
||||
<source>GUI display mode</source>
|
||||
<translation>窗口显示模式</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="856"/>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<source>Maximized</source>
|
||||
<translation>最大化</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="873"/>
|
||||
<location filename="../gui/SettingsView.qml" line="912"/>
|
||||
<source>Show connection quality warnings</source>
|
||||
<translation>显示连接质量警告</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="885"/>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<source>Discord Rich Presence integration</source>
|
||||
<translation>Discord Rich Presence 集成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="895"/>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<source>Updates your Discord status to display the name of the game you're streaming.</source>
|
||||
<translation>更新你的 Discord 状态,以显示正在运行的游戏名称。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="913"/>
|
||||
<location filename="../gui/SettingsView.qml" line="952"/>
|
||||
<source>Input Settings</source>
|
||||
<translation>输入设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="924"/>
|
||||
<location filename="../gui/SettingsView.qml" line="963"/>
|
||||
<source>Optimize mouse for remote desktop instead of games</source>
|
||||
<translation>为远程桌面而不是游戏优化鼠标</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="934"/>
|
||||
<location filename="../gui/SettingsView.qml" line="973"/>
|
||||
<source>This enables seamless mouse control without capturing the client's mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
|
||||
<translation>这可以实现无缝鼠标控制,而无需捕获客户端的鼠标光标。这是理想的远程桌面使用方案,但不会在大多数游戏中工作。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="935"/>
|
||||
<location filename="../gui/SettingsView.qml" line="974"/>
|
||||
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
|
||||
<translation>你可以使用 Ctrl+Alt+Shift+M 切换鼠标控制模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="936"/>
|
||||
<location filename="../gui/SettingsView.qml" line="975"/>
|
||||
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
|
||||
<translation>注意:由于 GeForce Experience 的 Bug,如果你的目标计算机有多个显示器,则此选项可能无法正常工作。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="946"/>
|
||||
<location filename="../gui/SettingsView.qml" line="985"/>
|
||||
<source>Capture system keyboard shortcuts</source>
|
||||
<translation>捕获系统快捷键</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="954"/>
|
||||
<location filename="../gui/SettingsView.qml" line="993"/>
|
||||
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
|
||||
<translation>这样可以捕获系统范围的键盘快捷键,例如 Alt + Tab,这些快捷键通常在流式传输时由客户端系统处理。</translation>
|
||||
</message>
|
||||
@@ -696,158 +710,163 @@
|
||||
<translation type="vanished">这允许捕获系统范围内的键盘快捷键(如Alt+Tab),这些快捷键通常在全屏流式传输时由客户端操作系统处理。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="955"/>
|
||||
<location filename="../gui/SettingsView.qml" line="994"/>
|
||||
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
|
||||
<translation>注意:Windows 上的某些键盘快捷键(如 Ctrl+Alt+Del)不能被任何应用程序截获,包括 Moonlight。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="984"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1023"/>
|
||||
<source>in fullscreen</source>
|
||||
<translation>全屏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="988"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1027"/>
|
||||
<source>always</source>
|
||||
<translation>总是</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1018"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1057"/>
|
||||
<source>Use touchscreen as a virtual trackpad</source>
|
||||
<translation>将触摸屏用作虚拟触控板</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1028"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1067"/>
|
||||
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
|
||||
<translation>选中时,触摸屏就像一个触控板。取消选中时,触摸屏将直接控制鼠标指针。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1035"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1074"/>
|
||||
<source>Swap left and right mouse buttons</source>
|
||||
<translation>交换鼠标左键和右键</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1047"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1086"/>
|
||||
<source>Reverse mouse scrolling direction</source>
|
||||
<translation>反转鼠标滚动方向</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1061"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1100"/>
|
||||
<source>Gamepad Settings</source>
|
||||
<translation>手柄设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1071"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1110"/>
|
||||
<source>Swap A/B and X/Y gamepad buttons</source>
|
||||
<translation>交换手柄的 A/B 和 X/Y 按钮</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1089"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1128"/>
|
||||
<source>This switches gamepads into a Nintendo-style button layout</source>
|
||||
<translation>这会将手柄切换为任天堂风格的按钮布局</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1095"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1134"/>
|
||||
<source>Force gamepad #1 always connected</source>
|
||||
<translation>强制手柄 #1 始终连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1105"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
|
||||
<translation>强制单个手柄始终与目标计算机保持连接,即使没有任何手柄实际连接到此计算机。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1106"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1145"/>
|
||||
<source>Only enable this option when streaming a game that doesn't support gamepads being connected after startup.</source>
|
||||
<translation>仅在游戏启动后不支持手柄连接时才启用此选项。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1113"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1152"/>
|
||||
<source>Enable mouse control with gamepads by holding the 'Start' button</source>
|
||||
<translation>按住手柄的“Start”按钮启用鼠标控制</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1124"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1163"/>
|
||||
<source>Process gamepad input when Moonlight is in the background</source>
|
||||
<translation>当 Moonlight 在后台时处理手柄输入</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1135"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1174"/>
|
||||
<source>Allows Moonlight to capture gamepad inputs even if it's not the current window in focus</source>
|
||||
<translation>即使不是焦点窗口,也允许 Moonlight 捕获手柄的输入</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1144"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1183"/>
|
||||
<source>Host Settings</source>
|
||||
<translation>目标计算机设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1154"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1193"/>
|
||||
<source>Optimize game settings for streaming</source>
|
||||
<translation>优化游戏设置以进行流式传输</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1165"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1204"/>
|
||||
<source>Quit app on host PC after ending stream</source>
|
||||
<translation>流式传输结束后退出目标计算机上的应用程序</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1175"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1214"/>
|
||||
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
|
||||
<translation>当你结束流式传输时,这将关闭正在运行的应用程序或游戏。这会导致你失去所有未保存的游戏进度!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1184"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<source>Advanced Settings</source>
|
||||
<translation>高级设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1194"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1233"/>
|
||||
<source>Video decoder</source>
|
||||
<translation>视频解码器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1219"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1265"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1258"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1311"/>
|
||||
<source>Automatic (Recommended)</source>
|
||||
<translation>自动 (推荐)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1223"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1262"/>
|
||||
<source>Force software decoding</source>
|
||||
<translation>强制软件解码</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1227"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1266"/>
|
||||
<source>Force hardware decoding</source>
|
||||
<translation>强制硬件解码</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1240"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1279"/>
|
||||
<source>Video codec</source>
|
||||
<translation>视频编解码器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1269"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1315"/>
|
||||
<source>H.264</source>
|
||||
<translation>H.264</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1273"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1319"/>
|
||||
<source>HEVC (H.265)</source>
|
||||
<translation>HEVC (H.265)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1290"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1289"/>
|
||||
<source>HEVC HDR (Experimental)</source>
|
||||
<translation>HEVC HDR (实验性)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1333"/>
|
||||
<source>Unlock unsupported FPS options</source>
|
||||
<translation>解锁不受支持的 FPS 选项</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1309"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1352"/>
|
||||
<source>Automatically find PCs on the local network (Recommended)</source>
|
||||
<translation>自动在本地网络上查找计算机 (推荐)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/SettingsView.qml" line="1334"/>
|
||||
<location filename="../gui/SettingsView.qml" line="1377"/>
|
||||
<source>Automatically detect blocked connections (Recommended)</source>
|
||||
<translation>自动检测被阻止的连接 (推荐)</translation>
|
||||
</message>
|
||||
@@ -953,8 +972,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="415"/>
|
||||
<source>No functioning hardware accelerated video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Moonlight 未检测到有效的硬件加速视频解码器。在此配置中,你的流性能可能会严重下降。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
|
||||
<translation>Moonlight 未检测到功能正常的硬件加速 H.264 视频解码器。</translation>
|
||||
<translation type="vanished">Moonlight 未检测到功能正常的硬件加速 H.264 视频解码器。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/main.qml" line="417"/>
|
||||
|
||||
+4
-2
@@ -442,9 +442,11 @@ int main(int argc, char *argv[])
|
||||
// the mouse motion exactly how it was given to us.
|
||||
SDL_SetHint("SDL_MOUSE_RELATIVE_SCALING", "0");
|
||||
|
||||
// Set our app name for SDL to use with PulseAudio. This matches what we provide
|
||||
// as our app name to libsoundio too.
|
||||
// Set our app name for SDL to use with PulseAudio and PipeWire. This matches what we
|
||||
// provide as our app name to libsoundio too. On SDL 2.0.18+, SDL_APP_NAME is also used
|
||||
// for screensaver inhibitor reporting.
|
||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "Moonlight");
|
||||
SDL_SetHint("SDL_APP_NAME", "Moonlight");
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
||||
|
||||
@@ -29,6 +29,26 @@
|
||||
<file>languages/qml_ru.qm</file>
|
||||
<file>languages/qml_es.ts</file>
|
||||
<file>languages/qml_es.qm</file>
|
||||
<file>languages/qml_ja.ts</file>
|
||||
<file>languages/qml_ja.qm</file>
|
||||
<file>languages/qml_vi.ts</file>
|
||||
<file>languages/qml_vi.qm</file>
|
||||
<file>languages/qml_th.ts</file>
|
||||
<file>languages/qml_th.qm</file>
|
||||
<file>languages/qml_ko.ts</file>
|
||||
<file>languages/qml_ko.qm</file>
|
||||
<file>languages/qml_hu.ts</file>
|
||||
<file>languages/qml_hu.qm</file>
|
||||
<file>languages/qml_nl.ts</file>
|
||||
<file>languages/qml_nl.qm</file>
|
||||
<file>languages/qml_sv.ts</file>
|
||||
<file>languages/qml_sv.qm</file>
|
||||
<file>languages/qml_tr.ts</file>
|
||||
<file>languages/qml_tr.qm</file>
|
||||
<file>languages/qml_uk.ts</file>
|
||||
<file>languages/qml_uk.qm</file>
|
||||
<file>languages/qml_zh_Hant.ts</file>
|
||||
<file>languages/qml_zh_Hant.qm</file>
|
||||
</qresource>
|
||||
<qresource prefix="/data">
|
||||
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
|
||||
|
||||
@@ -195,6 +195,26 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
|
||||
return "ru";
|
||||
case LANG_ES:
|
||||
return "es";
|
||||
case LANG_JA:
|
||||
return "ja";
|
||||
case LANG_VI:
|
||||
return "vi";
|
||||
case LANG_TH:
|
||||
return "th";
|
||||
case LANG_KO:
|
||||
return "ko";
|
||||
case LANG_HU:
|
||||
return "hu";
|
||||
case LANG_NL:
|
||||
return "nl";
|
||||
case LANG_SV:
|
||||
return "sv";
|
||||
case LANG_TR:
|
||||
return "tr";
|
||||
case LANG_UK:
|
||||
return "uk";
|
||||
case LANG_ZH_HANT:
|
||||
return "zh_Hant";
|
||||
case LANG_AUTO:
|
||||
default:
|
||||
return QLocale::system().name();
|
||||
|
||||
@@ -72,7 +72,17 @@ public:
|
||||
LANG_DE,
|
||||
LANG_NB_NO,
|
||||
LANG_RU,
|
||||
LANG_ES
|
||||
LANG_ES,
|
||||
LANG_JA,
|
||||
LANG_VI,
|
||||
LANG_TH,
|
||||
LANG_KO,
|
||||
LANG_HU,
|
||||
LANG_NL,
|
||||
LANG_SV,
|
||||
LANG_TR,
|
||||
LANG_UK,
|
||||
LANG_ZH_HANT
|
||||
};
|
||||
Q_ENUM(Language);
|
||||
|
||||
|
||||
@@ -55,20 +55,11 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
|
||||
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_WIN32) && !defined(Q_OS_DARWIN)
|
||||
// Linux defaults to SDL due to persistent glitching issues under libsoundio.
|
||||
// Platforms that libsoundio doesn't support also default to SDL.
|
||||
// Default to SDL and use libsoundio as a fallback
|
||||
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
|
||||
#ifdef HAVE_SOUNDIO
|
||||
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
|
||||
#endif
|
||||
#else
|
||||
// Windows and macOS default to libsoundio and fall back to SDL
|
||||
#ifdef HAVE_SOUNDIO
|
||||
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
|
||||
#endif
|
||||
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
int SdlInputHandler::clipboardThreadProc(void *ptr)
|
||||
{
|
||||
auto me = (SdlInputHandler*)ptr;
|
||||
|
||||
while (!SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
|
||||
QString clipboardData;
|
||||
|
||||
SDL_LockMutex(me->m_ClipboardLock);
|
||||
for (;;) {
|
||||
clipboardData = me->m_ClipboardData;
|
||||
me->m_ClipboardData.clear();
|
||||
if (!clipboardData.isEmpty() || SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
|
||||
break;
|
||||
}
|
||||
SDL_CondWait(me->m_ClipboardHasData, me->m_ClipboardLock);
|
||||
}
|
||||
SDL_UnlockMutex(me->m_ClipboardLock);
|
||||
|
||||
// We might get here on shutdown, so don't send text if there's
|
||||
// nothing to send.
|
||||
if (!clipboardData.isEmpty()) {
|
||||
me->sendText(clipboardData);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAP_KEY(c, sc) \
|
||||
case c: \
|
||||
event.key.keysym.scancode = sc; \
|
||||
break
|
||||
|
||||
#define MAP_KEY_SHIFT(c, sc) \
|
||||
case c: \
|
||||
event.key.keysym.scancode = sc; \
|
||||
event.key.keysym.mod = KMOD_SHIFT; \
|
||||
break
|
||||
|
||||
void SdlInputHandler::sendText(QString& string)
|
||||
{
|
||||
for (int i = 0; i < string.size(); i++) {
|
||||
char16_t c = string[i].unicode();
|
||||
SDL_Event event = {};
|
||||
|
||||
// If we're sending a very long string, we might get a termination request
|
||||
// while we're in the middle of sending a string. In that case, just bail.
|
||||
if (SDL_AtomicGet(&m_ShutdownClipboardThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
event.key.keysym.scancode = (SDL_Scancode)((c - 'A') + SDL_SCANCODE_A);
|
||||
event.key.keysym.mod = KMOD_SHIFT;
|
||||
}
|
||||
else if (c >= 'a' && c <= 'z') {
|
||||
event.key.keysym.scancode = (SDL_Scancode)((c - 'a') + SDL_SCANCODE_A);
|
||||
}
|
||||
else if (c >= '1' && c <= '9') {
|
||||
event.key.keysym.scancode = (SDL_Scancode)((c - '1') + SDL_SCANCODE_1);
|
||||
}
|
||||
else {
|
||||
// Fixup some Unicode characters to be expressible in ASCII
|
||||
switch (c) {
|
||||
case u'\U0000201C': // Left "
|
||||
case u'\U0000201D': // Right "
|
||||
c = '"';
|
||||
break;
|
||||
|
||||
case u'\U00002018': // Left '
|
||||
case u'\U00002019': // Right '
|
||||
c = '\'';
|
||||
break;
|
||||
|
||||
case u'\U00002013': // Smart -
|
||||
c = '-';
|
||||
break;
|
||||
|
||||
case u'\U00002026': // Ellipsis (...)
|
||||
// Convert this character into 3 periods
|
||||
string.insert(i+1, '.');
|
||||
string.insert(i+1, '.');
|
||||
c = '.';
|
||||
break;
|
||||
|
||||
case u'\U000000A0': // Non-breaking space
|
||||
c = ' ';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Nothing
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch (c) {
|
||||
|
||||
// Handle CRLF separately to avoid duplicate newlines
|
||||
case '\r':
|
||||
if (string[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
event.key.keysym.scancode = SDL_SCANCODE_RETURN;
|
||||
break;
|
||||
|
||||
MAP_KEY('\b', SDL_SCANCODE_BACKSPACE);
|
||||
MAP_KEY('\n', SDL_SCANCODE_RETURN);
|
||||
MAP_KEY('\t', SDL_SCANCODE_TAB);
|
||||
|
||||
MAP_KEY(' ', SDL_SCANCODE_SPACE);
|
||||
MAP_KEY_SHIFT('!', SDL_SCANCODE_1);
|
||||
MAP_KEY_SHIFT('"', SDL_SCANCODE_APOSTROPHE);
|
||||
MAP_KEY_SHIFT('#', SDL_SCANCODE_3);
|
||||
MAP_KEY_SHIFT('$', SDL_SCANCODE_4);
|
||||
MAP_KEY_SHIFT('%', SDL_SCANCODE_5);
|
||||
MAP_KEY_SHIFT('&', SDL_SCANCODE_7);
|
||||
MAP_KEY('\'', SDL_SCANCODE_APOSTROPHE);
|
||||
MAP_KEY_SHIFT('(', SDL_SCANCODE_9);
|
||||
MAP_KEY_SHIFT(')', SDL_SCANCODE_0);
|
||||
MAP_KEY_SHIFT('*', SDL_SCANCODE_8);
|
||||
MAP_KEY_SHIFT('+', SDL_SCANCODE_EQUALS);
|
||||
MAP_KEY(',', SDL_SCANCODE_COMMA);
|
||||
MAP_KEY('-', SDL_SCANCODE_MINUS);
|
||||
MAP_KEY('.', SDL_SCANCODE_PERIOD);
|
||||
MAP_KEY('/', SDL_SCANCODE_SLASH);
|
||||
MAP_KEY('0', SDL_SCANCODE_0);
|
||||
|
||||
MAP_KEY_SHIFT(':', SDL_SCANCODE_SEMICOLON);
|
||||
MAP_KEY(';', SDL_SCANCODE_SEMICOLON);
|
||||
MAP_KEY_SHIFT('<', SDL_SCANCODE_COMMA);
|
||||
MAP_KEY('=', SDL_SCANCODE_EQUALS);
|
||||
MAP_KEY_SHIFT('>', SDL_SCANCODE_PERIOD);
|
||||
MAP_KEY_SHIFT('?', SDL_SCANCODE_SLASH);
|
||||
MAP_KEY_SHIFT('@', SDL_SCANCODE_2);
|
||||
|
||||
MAP_KEY('[', SDL_SCANCODE_LEFTBRACKET);
|
||||
MAP_KEY('\\', SDL_SCANCODE_BACKSLASH);
|
||||
MAP_KEY(']', SDL_SCANCODE_RIGHTBRACKET);
|
||||
MAP_KEY_SHIFT('^', SDL_SCANCODE_6);
|
||||
MAP_KEY_SHIFT('_', SDL_SCANCODE_MINUS);
|
||||
MAP_KEY('`', SDL_SCANCODE_GRAVE);
|
||||
|
||||
MAP_KEY_SHIFT('{', SDL_SCANCODE_LEFTBRACKET);
|
||||
MAP_KEY_SHIFT('|', SDL_SCANCODE_BACKSLASH);
|
||||
MAP_KEY_SHIFT('}', SDL_SCANCODE_RIGHTBRACKET);
|
||||
MAP_KEY_SHIFT('~', SDL_SCANCODE_GRAVE);
|
||||
|
||||
default:
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pasting text - non-ASCII character value 'U+%x' ignored",
|
||||
c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key.keysym.mod & KMOD_SHIFT) {
|
||||
SDL_Event modifierEvent = {};
|
||||
modifierEvent.type = SDL_KEYDOWN;
|
||||
modifierEvent.key.state = SDL_PRESSED;
|
||||
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
|
||||
handleKeyEvent(&modifierEvent.key);
|
||||
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.key.state = SDL_PRESSED;
|
||||
handleKeyEvent(&event.key);
|
||||
|
||||
SDL_Delay(10);
|
||||
|
||||
event.type = SDL_KEYUP;
|
||||
event.key.state = SDL_RELEASED;
|
||||
handleKeyEvent(&event.key);
|
||||
|
||||
if (event.key.keysym.mod & KMOD_SHIFT) {
|
||||
SDL_Event modifierEvent = {};
|
||||
modifierEvent.type = SDL_KEYUP;
|
||||
modifierEvent.key.state = SDL_RELEASED;
|
||||
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
|
||||
handleKeyEvent(&modifierEvent.key);
|
||||
|
||||
SDL_Delay(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,9 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
||||
state->controller = controller;
|
||||
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
hapticCaps = SDL_GameControllerHasRumble(controller) ? ML_HAPTIC_GC_RUMBLE : 0;
|
||||
#elif SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
// Perform a tiny rumble to see if haptics are supported.
|
||||
// NB: We cannot use zeros for rumble intensity or SDL will not actually call the JS driver
|
||||
// and we'll get a (potentially false) success value returned.
|
||||
|
||||
@@ -33,8 +33,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
m_RightButtonReleaseTimer(0),
|
||||
m_DragTimer(0),
|
||||
m_DragButton(0),
|
||||
m_NumFingersDown(0),
|
||||
m_ClipboardData()
|
||||
m_NumFingersDown(0)
|
||||
{
|
||||
// System keys are always captured when running without a DE
|
||||
if (!WMUtils::isRunningDesktopEnvironment()) {
|
||||
@@ -172,10 +171,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
CGSGetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), &m_OldHotKeyMode);
|
||||
#endif
|
||||
|
||||
// Initialize the gamepad mask with currently attached gamepads to avoid
|
||||
// causing gamepads to unexpectedly disappear and reappear on the host
|
||||
// during stream startup as we detect currently attached gamepads one at a time.
|
||||
@@ -202,13 +197,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
}
|
||||
|
||||
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
|
||||
|
||||
// Initialize state used by the clipboard thread before we start it
|
||||
SDL_AtomicSet(&m_ShutdownClipboardThread, 0);
|
||||
m_ClipboardHasData = SDL_CreateCond();
|
||||
m_ClipboardLock = SDL_CreateMutex();
|
||||
|
||||
m_ClipboardThread = SDL_CreateThread(SdlInputHandler::clipboardThreadProc, "Clipboard Sender", this);
|
||||
}
|
||||
|
||||
SdlInputHandler::~SdlInputHandler()
|
||||
@@ -234,21 +222,6 @@ SdlInputHandler::~SdlInputHandler()
|
||||
SDL_RemoveTimer(m_RightButtonReleaseTimer);
|
||||
SDL_RemoveTimer(m_DragTimer);
|
||||
|
||||
// Wake up the clipboard thread to terminate it
|
||||
SDL_AtomicSet(&m_ShutdownClipboardThread, 1);
|
||||
SDL_CondBroadcast(m_ClipboardHasData);
|
||||
|
||||
// Wait for it to terminate
|
||||
SDL_WaitThread(m_ClipboardThread, nullptr);
|
||||
|
||||
// Now we can safely clean up its resources
|
||||
SDL_DestroyCond(m_ClipboardHasData);
|
||||
SDL_DestroyMutex(m_ClipboardLock);
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
|
||||
#endif
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
||||
@@ -328,27 +301,11 @@ void SdlInputHandler::notifyFocusLost()
|
||||
setCaptureActive(false);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Ungrab the keyboard
|
||||
updateKeyboardGrabState();
|
||||
#endif
|
||||
|
||||
// Raise all keys that are currently pressed. If we don't do this, certain keys
|
||||
// used in shortcuts that cause focus loss (such as Alt+Tab) may get stuck down.
|
||||
raiseAllKeys();
|
||||
}
|
||||
|
||||
void SdlInputHandler::notifyFocusGained()
|
||||
{
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Re-grab the keyboard if it was grabbed before focus loss
|
||||
// FIXME: We only do this on macOS because we get a spurious
|
||||
// focus gain when in SDL_WINDOW_FULLSCREEN_DESKTOP on Windows
|
||||
// immediately after losing focus by clicking on another window.
|
||||
updateKeyboardGrabState();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SdlInputHandler::isCaptureActive()
|
||||
{
|
||||
if (SDL_GetRelativeMouseMode()) {
|
||||
@@ -372,18 +329,6 @@ void SdlInputHandler::updateKeyboardGrabState()
|
||||
// Ungrab if it's fullscreen only and we left fullscreen
|
||||
shouldGrab = false;
|
||||
}
|
||||
#ifdef Q_OS_DARWIN
|
||||
else if (!(windowFlags & SDL_WINDOW_INPUT_FOCUS)) {
|
||||
// Ungrab if we lose input focus on macOS. SDL will handle
|
||||
// this internally for platforms where we use the SDL
|
||||
// SDL_SetWindowKeyboardGrab() API exclusively.
|
||||
//
|
||||
// NB: On X11, we may not have input focus at the time of
|
||||
// the initial keyboard grab update, so we must not enable
|
||||
// this codepath on Linux.
|
||||
shouldGrab = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't close the window on Alt+F4 when keyboard grab is enabled
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, shouldGrab ? "1" : "0");
|
||||
@@ -401,20 +346,12 @@ void SdlInputHandler::updateKeyboardGrabState()
|
||||
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) {
|
||||
SDL_SetWindowGrab(m_Window, SDL_TRUE);
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
// SDL doesn't support this private macOS API
|
||||
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), CGSGlobalHotKeyDisable);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
||||
// Allow the keyboard to leave the window
|
||||
SDL_SetWindowKeyboardGrab(m_Window, SDL_FALSE);
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
// SDL doesn't support this private macOS API
|
||||
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -461,14 +398,6 @@ void SdlInputHandler::setCaptureActive(bool active)
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!m_AbsoluteMouseMode) {
|
||||
// If our window is occluded when mouse is captured, the mouse may
|
||||
// get stuck on top of the occluding window and not be properly
|
||||
// captured. We can avoid this by raising our window before we
|
||||
// capture the mouse.
|
||||
SDL_RaiseWindow(m_Window);
|
||||
}
|
||||
|
||||
// If we're in relative mode, try to activate SDL's relative mouse mode
|
||||
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
|
||||
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor
|
||||
|
||||
@@ -29,25 +29,6 @@ struct GamepadState {
|
||||
unsigned char lt, rt;
|
||||
};
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include <CoreGraphics/CGError.h>
|
||||
extern "C" {
|
||||
typedef int CGSConnection;
|
||||
typedef enum {
|
||||
CGSGlobalHotKeyEnable = 0,
|
||||
CGSGlobalHotKeyDisable = 1,
|
||||
} CGSGlobalHotKeyOperatingMode;
|
||||
|
||||
extern CGSConnection _CGSDefaultConnection(void);
|
||||
|
||||
extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection,
|
||||
CGSGlobalHotKeyOperatingMode* mode);
|
||||
|
||||
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection,
|
||||
CGSGlobalHotKeyOperatingMode mode);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define MAX_GAMEPADS 4
|
||||
#define MAX_FINGERS 2
|
||||
|
||||
@@ -98,8 +79,6 @@ public:
|
||||
|
||||
void notifyFocusLost();
|
||||
|
||||
void notifyFocusGained();
|
||||
|
||||
bool isCaptureActive();
|
||||
|
||||
bool isSystemKeyCaptureActive();
|
||||
@@ -159,9 +138,6 @@ private:
|
||||
static
|
||||
Uint32 dragTimerCallback(Uint32 interval, void* param);
|
||||
|
||||
static
|
||||
int clipboardThreadProc(void *ptr);
|
||||
|
||||
SDL_Window* m_Window;
|
||||
bool m_MultiController;
|
||||
bool m_GamepadMouse;
|
||||
@@ -190,10 +166,6 @@ private:
|
||||
StreamingPreferences::CaptureSysKeysMode m_CaptureSystemKeysMode;
|
||||
int m_MouseCursorCapturedVisibilityState;
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
CGSGlobalHotKeyOperatingMode m_OldHotKeyMode;
|
||||
#endif
|
||||
|
||||
struct {
|
||||
KeyCombo keyCombo;
|
||||
SDL_Keycode keyCode;
|
||||
@@ -216,11 +188,5 @@ private:
|
||||
char m_DragButton;
|
||||
int m_NumFingersDown;
|
||||
|
||||
SDL_Thread* m_ClipboardThread;
|
||||
SDL_atomic_t m_ShutdownClipboardThread;
|
||||
QString m_ClipboardData;
|
||||
SDL_cond* m_ClipboardHasData;
|
||||
SDL_mutex* m_ClipboardLock;
|
||||
|
||||
static const int k_ButtonMap[];
|
||||
};
|
||||
|
||||
@@ -101,13 +101,20 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
||||
// with the text we're going to type.
|
||||
raiseAllKeys();
|
||||
|
||||
const char* text;
|
||||
char* text;
|
||||
if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) {
|
||||
// Append this data to the clipboard data string for the thread to process
|
||||
SDL_LockMutex(m_ClipboardLock);
|
||||
m_ClipboardData.append(text);
|
||||
SDL_CondSignal(m_ClipboardHasData);
|
||||
SDL_UnlockMutex(m_ClipboardLock);
|
||||
// Sending both CR and LF will lead to two newlines in the destination for
|
||||
// each newline in the source, so we fix up any CRLFs into just a single LF.
|
||||
for (char* c = text; *c != 0; c++) {
|
||||
if (*c == '\r' && *(c + 1) == '\n') {
|
||||
// We're using strlen() rather than strlen() - 1 since we need to add 1
|
||||
// to copy the null terminator which is not included in strlen()'s count.
|
||||
memmove(c, c + 1, strlen(c));
|
||||
}
|
||||
}
|
||||
|
||||
// Send this text to the PC
|
||||
LiSendUtf8TextEvent(text, strlen(text));
|
||||
|
||||
// SDL_GetClipboardText() allocates, so we must free
|
||||
SDL_free((void*)text);
|
||||
@@ -124,8 +131,6 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
||||
}
|
||||
}
|
||||
|
||||
#define IS_SYNTHETIC_KEY_EVENT(x) ((x)->timestamp == 0)
|
||||
|
||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
{
|
||||
short keyCode;
|
||||
@@ -138,9 +143,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
}
|
||||
|
||||
// Check for our special key combos
|
||||
// Ignore timestamp == 0 which are sent from our keyboard code.
|
||||
if (!IS_SYNTHETIC_KEY_EVENT(event) &&
|
||||
(event->state == SDL_PRESSED) &&
|
||||
if ((event->state == SDL_PRESSED) &&
|
||||
(event->keysym.mod & KMOD_CTRL) &&
|
||||
(event->keysym.mod & KMOD_ALT) &&
|
||||
(event->keysym.mod & KMOD_SHIFT)) {
|
||||
@@ -403,16 +406,12 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a synthetic keypress from the clipboard code,
|
||||
// this will be on a non-main thread, so don't touch m_KeysDown.
|
||||
if (!IS_SYNTHETIC_KEY_EVENT(event)) {
|
||||
// Track the key state so we always know which keys are down
|
||||
if (event->state == SDL_PRESSED) {
|
||||
m_KeysDown.insert(keyCode);
|
||||
}
|
||||
else {
|
||||
m_KeysDown.remove(keyCode);
|
||||
}
|
||||
// Track the key state so we always know which keys are down
|
||||
if (event->state == SDL_PRESSED) {
|
||||
m_KeysDown.insert(keyCode);
|
||||
}
|
||||
else {
|
||||
m_KeysDown.remove(keyCode);
|
||||
}
|
||||
|
||||
LiSendKeyboardEvent(0x8000 | keyCode,
|
||||
|
||||
@@ -212,6 +212,16 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
if (event->preciseY != 0.0f) {
|
||||
// Invert the scroll direction if needed
|
||||
if (m_ReverseScrollDirection) {
|
||||
event->preciseY = -event->preciseY;
|
||||
}
|
||||
|
||||
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
|
||||
}
|
||||
#else
|
||||
if (event->y != 0) {
|
||||
// Invert the scroll direction if needed
|
||||
if (m_ReverseScrollDirection) {
|
||||
@@ -220,6 +230,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||
|
||||
LiSendScrollEvent((signed char)event->y);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SdlInputHandler::isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth, int windowHeight)
|
||||
|
||||
+166
-36
@@ -264,14 +264,12 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
|
||||
// safely return DR_OK and wait for m_NeedsIdr to be set by
|
||||
// the decoder reinitialization code.
|
||||
|
||||
if (SDL_AtomicTryLock(&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;
|
||||
}
|
||||
if (s_ActiveSession->getAndClearPendingIdrFrameStatus()) {
|
||||
// If we reset our decoder, we'll need to request an IDR frame
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
if (SDL_AtomicTryLock(&s_ActiveSession->m_DecoderLock)) {
|
||||
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
|
||||
if (decoder != nullptr) {
|
||||
int ret = decoder->submitDecodeUnit(du);
|
||||
@@ -307,6 +305,23 @@ void Session::getDecoderInfo(SDL_Window* window,
|
||||
maxResolution = decoder->getDecoderMaxResolution();
|
||||
|
||||
delete decoder;
|
||||
|
||||
// If we don't get back a hardware H.264 decoder, see if we have a hardware
|
||||
// HEVC decoder. This can be the case on the Raspberry Pi with Full KMS
|
||||
// when not running in X11. Everything since Maxwell in 2014 can encode HEVC,
|
||||
// so we probably don't need to be concerned about a lack of fast H.264
|
||||
// decoding enough to bug the user about it every launch.
|
||||
if (!isHardwareAccelerated) {
|
||||
if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE,
|
||||
window, VIDEO_FORMAT_H265, 1920, 1080, 60,
|
||||
false, false, true, decoder)) {
|
||||
isHardwareAccelerated = decoder->isHardwareAccelerated();
|
||||
isFullScreenOnly = decoder->isAlwaysFullScreen();
|
||||
maxResolution = decoder->getDecoderMaxResolution();
|
||||
|
||||
delete decoder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Session::isHardwareDecodeAvailable(SDL_Window* window,
|
||||
@@ -342,6 +357,13 @@ bool Session::populateDecoderProperties(SDL_Window* window)
|
||||
}
|
||||
|
||||
m_VideoCallbacks.capabilities = decoder->getDecoderCapabilities();
|
||||
if (m_VideoCallbacks.capabilities & CAPABILITY_PULL_RENDERER) {
|
||||
// It is an error to pass a push callback when in pull mode
|
||||
m_VideoCallbacks.submitDecodeUnit = nullptr;
|
||||
}
|
||||
else {
|
||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||
}
|
||||
|
||||
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
|
||||
|
||||
@@ -362,7 +384,6 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
||||
m_Window(nullptr),
|
||||
m_VideoDecoder(nullptr),
|
||||
m_DecoderLock(0),
|
||||
m_NeedsIdr(false),
|
||||
m_AudioDisabled(false),
|
||||
m_AudioMuted(false),
|
||||
m_DisplayOriginX(0),
|
||||
@@ -380,6 +401,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
||||
m_AudioSampleCount(0),
|
||||
m_DropAudioEndTime(0)
|
||||
{
|
||||
SDL_AtomicSet(&m_NeedsIdr, 0);
|
||||
}
|
||||
|
||||
// NB: This may not get destroyed for a long time! Don't put any vital cleanup here.
|
||||
@@ -424,7 +446,6 @@ bool Session::initialize()
|
||||
|
||||
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
||||
m_VideoCallbacks.setup = drSetup;
|
||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||
|
||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||
m_StreamConfig.width = m_Preferences->width;
|
||||
@@ -619,32 +640,53 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
||||
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||
|
||||
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Already checked in initialize()
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||
if (hevcForced) {
|
||||
emitLaunchWarning(tr("Your host PC GPU doesn't support HEVC. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming."));
|
||||
}
|
||||
|
||||
// Moonlight-common-c will handle this case already, but we want
|
||||
// to set this explicitly here so we can do our hardware acceleration
|
||||
// check below.
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||
hevcForced && // Auto VCC is already checked in initialize()
|
||||
!isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
if (hevcForced) {
|
||||
emitLaunchWarning(tr("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance."));
|
||||
emitLaunchWarning(tr("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance."));
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_StreamConfig.supportsHevc &&
|
||||
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO &&
|
||||
!isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
VIDEO_FORMAT_H264,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
|
||||
if (m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_H264) {
|
||||
emitLaunchWarning(tr("Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance."));
|
||||
}
|
||||
else {
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0 &&
|
||||
isHardwareDecodeAvailable(testWindow,
|
||||
m_Preferences->videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
emitLaunchWarning(tr("Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance."));
|
||||
}
|
||||
else {
|
||||
emitLaunchWarning(tr("This PC's GPU doesn't support HEVC decoding."));
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hevcForced) {
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||
emitLaunchWarning(tr("Your host PC GPU doesn't support HEVC. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming."));
|
||||
|
||||
// Moonlight-common-c will handle this case already, but we want
|
||||
// to set this explicitly here so we can do our hardware acceleration
|
||||
// check below.
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
emitLaunchWarning(tr("Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -777,12 +819,17 @@ private:
|
||||
emit m_Session->sessionFinished(m_Session->m_PortTestResults);
|
||||
}
|
||||
|
||||
// The video decoder must already be destroyed, since it could
|
||||
// try to interact with APIs that can only be called between
|
||||
// LiStartConnection() and LiStopConnection().
|
||||
SDL_assert(m_Session->m_VideoDecoder == nullptr);
|
||||
|
||||
// Finish cleanup of the connection state
|
||||
LiStopConnection();
|
||||
|
||||
// Perform a best-effort app quit
|
||||
if (shouldQuit) {
|
||||
NvHTTP http(m_Session->m_Computer->activeAddress, m_Session->m_Computer->serverCert);
|
||||
NvHTTP http(m_Session->m_Computer);
|
||||
|
||||
// Logging is already done inside NvHTTP
|
||||
try {
|
||||
@@ -919,6 +966,24 @@ void Session::updateOptimalWindowDisplayMode()
|
||||
}
|
||||
}
|
||||
|
||||
// For KMSDRM backends where we exclusively own the display, we should opt
|
||||
// to change the resolution if that allows us to hit the desired frame rate
|
||||
if (bestMode.refresh_rate == 0 && strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Detected display mode: %dx%dx%d",
|
||||
mode.w, mode.h, mode.refresh_rate);
|
||||
if (mode.w >= m_ActiveVideoWidth && mode.h >= m_ActiveVideoHeight &&
|
||||
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMode.refresh_rate == 0) {
|
||||
// We may find no match if the user has moved a 120 FPS
|
||||
// stream onto a 60 Hz monitor (since no refresh rate can
|
||||
@@ -989,7 +1054,10 @@ class AsyncConnectionStartThread : public QThread
|
||||
public:
|
||||
AsyncConnectionStartThread(Session* session) :
|
||||
QThread(nullptr),
|
||||
m_Session(session) {}
|
||||
m_Session(session)
|
||||
{
|
||||
setObjectName("Async Conn Start");
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
@@ -1026,16 +1094,19 @@ bool Session::startConnectionAsync()
|
||||
}
|
||||
}
|
||||
|
||||
QString rtspSessionUrl;
|
||||
|
||||
try {
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
NvHTTP http(m_Computer);
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
http.resumeApp(&m_StreamConfig);
|
||||
http.resumeApp(&m_StreamConfig, rtspSessionUrl);
|
||||
}
|
||||
else {
|
||||
http.launchApp(m_App.id, &m_StreamConfig,
|
||||
enableGameOptimizations,
|
||||
m_Preferences->playAudioOnHost,
|
||||
m_InputHandler->getAttachedGamepadMask());
|
||||
m_InputHandler->getAttachedGamepadMask(),
|
||||
rtspSessionUrl);
|
||||
}
|
||||
} catch (const GfeHttpResponseException& e) {
|
||||
emit displayLaunchError(tr("GeForce Experience returned error: %1").arg(e.toQString()));
|
||||
@@ -1045,7 +1116,7 @@ bool Session::startConnectionAsync()
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
|
||||
QByteArray hostnameStr = m_Computer->activeAddress.address().toLatin1();
|
||||
QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
|
||||
|
||||
SERVER_INFORMATION hostInfo;
|
||||
@@ -1061,6 +1132,13 @@ bool Session::startConnectionAsync()
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
}
|
||||
|
||||
// Older GFE and Sunshine versions didn't have this field
|
||||
QByteArray rtspSessionUrlStr;
|
||||
if (!rtspSessionUrl.isEmpty()) {
|
||||
rtspSessionUrlStr = rtspSessionUrl.toLatin1();
|
||||
hostInfo.rtspSessionUrl = rtspSessionUrlStr.data();
|
||||
}
|
||||
|
||||
if (m_Preferences->packetSize != 0) {
|
||||
// Override default packet size and remote streaming detection
|
||||
// NB: Using STREAM_CFG_AUTO will cap our packet size at 1024 for remote hosts.
|
||||
@@ -1116,12 +1194,20 @@ void Session::flushWindowEvents()
|
||||
SDL_PushEvent(&flushEvent);
|
||||
}
|
||||
|
||||
bool Session::getAndClearPendingIdrFrameStatus()
|
||||
{
|
||||
return SDL_AtomicSet(&m_NeedsIdr, 0);
|
||||
}
|
||||
|
||||
class ExecThread : public QThread
|
||||
{
|
||||
public:
|
||||
ExecThread(Session* session) :
|
||||
QThread(nullptr),
|
||||
m_Session(session) {}
|
||||
m_Session(session)
|
||||
{
|
||||
setObjectName("Session Exec");
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
@@ -1309,6 +1395,7 @@ void Session::execInternal()
|
||||
|
||||
bool needsFirstEnterCapture = false;
|
||||
bool needsPostDecoderCreationCapture = false;
|
||||
bool ignoreDuplicateResizes = false;
|
||||
|
||||
// HACK: For Wayland, we wait until we get the first SDL_WINDOWEVENT_ENTER
|
||||
// event where it seems to work consistently on GNOME. For other platforms,
|
||||
@@ -1317,6 +1404,13 @@ void Session::execInternal()
|
||||
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
|
||||
// Native Wayland: Capture on SDL_WINDOWEVENT_ENTER
|
||||
needsFirstEnterCapture = true;
|
||||
|
||||
// As of SDL 2.0.20, we get duplicate SDL_WINDOWEVENT_SIZE_CHANGED events
|
||||
// when focus is lost and gained under GNOME Wayland. This causes us to
|
||||
// recreate our renderer needlessly. This is fixed in SDL 2.0.21.
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 21)
|
||||
ignoreDuplicateResizes = true;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// X11/XWayland: Capture after decoder creation
|
||||
@@ -1354,7 +1448,23 @@ void Session::execInternal()
|
||||
// Hijack this thread to be the SDL main thread. We have to do this
|
||||
// because we want to suspend all Qt processing until the stream is over.
|
||||
SDL_Event event;
|
||||
Sint32 lastResizeW = -1, lastResizeH = -1;
|
||||
for (;;) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18) && !defined(STEAM_LINK)
|
||||
// SDL 2.0.18 has a proper wait event implementation that uses platform
|
||||
// support to block on events rather than polling on Windows, macOS, X11,
|
||||
// and Wayland. It will fall back to 1 ms polling if a joystick is
|
||||
// connected, so we don't use it for STEAM_LINK to ensure we only poll
|
||||
// every 10 ms.
|
||||
//
|
||||
// NB: This behavior was introduced in SDL 2.0.16, but had a few critical
|
||||
// issues that could cause indefinite timeouts, delayed joystick detection,
|
||||
// and other problems.
|
||||
if (!SDL_WaitEventTimeout(&event, 1000)) {
|
||||
presence.runCallbacks();
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
// We explicitly use SDL_PollEvent() and SDL_Delay() because
|
||||
// SDL_WaitEvent() has an internal SDL_Delay(10) inside which
|
||||
// blocks this thread too long for high polling rate mice and high
|
||||
@@ -1370,6 +1480,7 @@ void Session::execInternal()
|
||||
presence.runCallbacks();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -1411,13 +1522,14 @@ void Session::execInternal()
|
||||
if (m_Preferences->muteOnFocusLoss) {
|
||||
m_AudioMuted = false;
|
||||
}
|
||||
m_InputHandler->notifyFocusGained();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
m_InputHandler->notifyMouseLeave();
|
||||
break;
|
||||
}
|
||||
|
||||
presence.runCallbacks();
|
||||
|
||||
// Capture the mouse on SDL_WINDOWEVENT_ENTER if needed
|
||||
if (needsFirstEnterCapture && event.window.event == SDL_WINDOWEVENT_ENTER) {
|
||||
m_InputHandler->setCaptureActive(true);
|
||||
@@ -1460,6 +1572,19 @@ void Session::execInternal()
|
||||
break;
|
||||
}
|
||||
|
||||
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||
if (ignoreDuplicateResizes && lastResizeW == event.window.data1 && lastResizeH == event.window.data2) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Dropping duplicate size changed event: %d %d",
|
||||
event.window.data1,
|
||||
event.window.data2);
|
||||
break;
|
||||
}
|
||||
|
||||
lastResizeW = event.window.data1;
|
||||
lastResizeH = event.window.data2;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Recreating renderer for window event: %d (%d %d)",
|
||||
event.window.event,
|
||||
@@ -1530,17 +1655,19 @@ void Session::execInternal()
|
||||
}
|
||||
|
||||
// Request an IDR frame to complete the reset
|
||||
m_NeedsIdr = true;
|
||||
SDL_AtomicSet(&m_NeedsIdr, 1);
|
||||
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
break;
|
||||
|
||||
case SDL_KEYUP:
|
||||
case SDL_KEYDOWN:
|
||||
presence.runCallbacks();
|
||||
m_InputHandler->handleKeyEvent(&event.key);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
presence.runCallbacks();
|
||||
m_InputHandler->handleMouseButtonEvent(&event.button);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
@@ -1554,6 +1681,7 @@ void Session::execInternal()
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
presence.runCallbacks();
|
||||
m_InputHandler->handleControllerButtonEvent(&event.cbutton);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
@@ -1594,6 +1722,8 @@ DispatchDeferredCleanup:
|
||||
SDL_AtomicUnlock(&m_InputHandlerLock);
|
||||
|
||||
// Destroy the decoder, since this must be done on the main thread
|
||||
// NB: This must happen before LiStopConnection() for pull-based
|
||||
// decoders.
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
delete m_VideoDecoder;
|
||||
m_VideoDecoder = nullptr;
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
|
||||
void flushWindowEvents();
|
||||
|
||||
bool getAndClearPendingIdrFrameStatus();
|
||||
|
||||
signals:
|
||||
void stageStarting(QString stage);
|
||||
|
||||
@@ -145,7 +147,7 @@ private:
|
||||
SDL_Window* m_Window;
|
||||
IVideoDecoder* m_VideoDecoder;
|
||||
SDL_SpinLock m_DecoderLock;
|
||||
bool m_NeedsIdr;
|
||||
SDL_atomic_t m_NeedsIdr;
|
||||
bool m_AudioDisabled;
|
||||
bool m_AudioMuted;
|
||||
Uint32 m_FullScreenFlag;
|
||||
|
||||
@@ -19,6 +19,8 @@ typedef struct _VIDEO_STATS {
|
||||
uint32_t totalDecodeTime;
|
||||
uint32_t totalPacerTime;
|
||||
uint32_t totalRenderTime;
|
||||
uint32_t lastRtt;
|
||||
uint32_t lastRttVariance;
|
||||
float totalFps;
|
||||
float receivedFps;
|
||||
float decodedFps;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "cuda.h"
|
||||
|
||||
#include <SDL_opengl.h>
|
||||
|
||||
CUDARenderer::CUDARenderer()
|
||||
: m_HwContext(nullptr)
|
||||
{
|
||||
@@ -55,3 +57,157 @@ bool CUDARenderer::isDirectRenderingSupported()
|
||||
return false;
|
||||
}
|
||||
|
||||
CUDAGLInteropHelper::CUDAGLInteropHelper(AVHWDeviceContext* context)
|
||||
: m_Funcs(nullptr),
|
||||
m_Context((AVCUDADeviceContext*)context->hwctx)
|
||||
{
|
||||
memset(m_Resources, 0, sizeof(m_Resources));
|
||||
|
||||
// One-time init of CUDA library
|
||||
cuda_load_functions(&m_Funcs, nullptr);
|
||||
if (m_Funcs == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize CUDA library");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CUDAGLInteropHelper::~CUDAGLInteropHelper()
|
||||
{
|
||||
unregisterTextures();
|
||||
|
||||
if (m_Funcs != nullptr) {
|
||||
cuda_free_functions(&m_Funcs);
|
||||
}
|
||||
}
|
||||
|
||||
bool CUDAGLInteropHelper::registerBoundTextures()
|
||||
{
|
||||
int err;
|
||||
|
||||
if (m_Funcs == nullptr) {
|
||||
// Already logged in constructor
|
||||
return false;
|
||||
}
|
||||
|
||||
// Push FFmpeg's CUDA context to use for our CUDA operations
|
||||
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register each plane as a separate resource
|
||||
for (int i = 0; i < NV12_PLANES; i++) {
|
||||
GLint tex;
|
||||
|
||||
// Get the ID of this plane's texture
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &tex);
|
||||
|
||||
// Register it with CUDA
|
||||
err = m_Funcs->cuGraphicsGLRegisterImage(&m_Resources[i], tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsGLRegisterImage() failed: %d", err);
|
||||
m_Resources[i] = 0;
|
||||
unregisterTextures();
|
||||
goto Exit;
|
||||
}
|
||||
}
|
||||
|
||||
Exit:
|
||||
{
|
||||
CUcontext dummy;
|
||||
m_Funcs->cuCtxPopCurrent(&dummy);
|
||||
}
|
||||
return err == CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
void CUDAGLInteropHelper::unregisterTextures()
|
||||
{
|
||||
int err;
|
||||
|
||||
if (m_Funcs == nullptr) {
|
||||
// Already logged in constructor
|
||||
return;
|
||||
}
|
||||
|
||||
// Push FFmpeg's CUDA context to use for our CUDA operations
|
||||
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NV12_PLANES; i++) {
|
||||
if (m_Resources[i] != 0) {
|
||||
m_Funcs->cuGraphicsUnregisterResource(m_Resources[i]);
|
||||
m_Resources[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
CUcontext dummy;
|
||||
m_Funcs->cuCtxPopCurrent(&dummy);
|
||||
}
|
||||
}
|
||||
|
||||
bool CUDAGLInteropHelper::copyCudaFrameToTextures(AVFrame* frame)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (m_Funcs == nullptr) {
|
||||
// Already logged in constructor
|
||||
return false;
|
||||
}
|
||||
|
||||
// Push FFmpeg's CUDA context to use for our CUDA operations
|
||||
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map our resources
|
||||
err = m_Funcs->cuGraphicsMapResources(NV12_PLANES, m_Resources, m_Context->stream);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsMapResources() failed: %d", err);
|
||||
goto PopCtxExit;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NV12_PLANES; i++) {
|
||||
CUarray cudaArray;
|
||||
|
||||
// Get a pointer to the mapped array for this plane
|
||||
err = m_Funcs->cuGraphicsSubResourceGetMappedArray(&cudaArray, m_Resources[i], 0, 0);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsSubResourceGetMappedArray() failed: %d", err);
|
||||
goto UnmapExit;
|
||||
}
|
||||
|
||||
// Do the copy
|
||||
CUDA_MEMCPY2D cu2d = {
|
||||
.srcMemoryType = CU_MEMORYTYPE_DEVICE,
|
||||
.srcDevice = (CUdeviceptr)frame->data[i],
|
||||
.srcPitch = (size_t)frame->linesize[i],
|
||||
.dstMemoryType = CU_MEMORYTYPE_ARRAY,
|
||||
.dstArray = cudaArray,
|
||||
.dstPitch = (size_t)frame->width >> i,
|
||||
.WidthInBytes = (size_t)frame->width,
|
||||
.Height = (size_t)frame->height >> i
|
||||
};
|
||||
err = m_Funcs->cuMemcpy2D(&cu2d);
|
||||
if (err != CUDA_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuMemcpy2D() failed: %d", err);
|
||||
goto UnmapExit;
|
||||
}
|
||||
}
|
||||
|
||||
UnmapExit:
|
||||
m_Funcs->cuGraphicsUnmapResources(NV12_PLANES, m_Resources, m_Context->stream);
|
||||
PopCtxExit:
|
||||
{
|
||||
CUcontext dummy;
|
||||
m_Funcs->cuCtxPopCurrent(&dummy);
|
||||
}
|
||||
return err == CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <ffnvcodec/dynlink_loader.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/hwcontext_cuda.h>
|
||||
}
|
||||
|
||||
class CUDARenderer : public IFFmpegRenderer {
|
||||
public:
|
||||
CUDARenderer();
|
||||
@@ -16,3 +22,21 @@ private:
|
||||
AVBufferRef* m_HwContext;
|
||||
};
|
||||
|
||||
#define NV12_PLANES 2
|
||||
|
||||
// Helper class used by SDLRenderer to read our CUDA frame
|
||||
class CUDAGLInteropHelper {
|
||||
public:
|
||||
CUDAGLInteropHelper(AVHWDeviceContext* context);
|
||||
~CUDAGLInteropHelper();
|
||||
|
||||
bool registerBoundTextures();
|
||||
void unregisterTextures();
|
||||
|
||||
bool copyCudaFrameToTextures(AVFrame* frame);
|
||||
|
||||
private:
|
||||
CudaFunctions* m_Funcs;
|
||||
AVCUDADeviceContext* m_Context;
|
||||
CUgraphicsResource m_Resources[NV12_PLANES];
|
||||
};
|
||||
|
||||
@@ -90,17 +90,28 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
else
|
||||
#endif
|
||||
{
|
||||
const char* device = SDL_getenv("DRM_DEV");
|
||||
const char* userDevice = SDL_getenv("DRM_DEV");
|
||||
if (userDevice != nullptr) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Opening user-specified DRM device: %s",
|
||||
userDevice);
|
||||
|
||||
if (device == nullptr) {
|
||||
device = "/dev/dri/card0";
|
||||
m_DrmFd = open(userDevice, O_RDWR | O_CLOEXEC);
|
||||
}
|
||||
else {
|
||||
const char* defaultDevices[] = {"/dev/dri/renderD128", "/dev/dri/card0"};
|
||||
|
||||
for (unsigned int i = 0; i < SDL_arraysize(defaultDevices); i++) {
|
||||
m_DrmFd = open(defaultDevices[i], O_RDWR | O_CLOEXEC);
|
||||
if (m_DrmFd >= 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Opened DRM device: %s",
|
||||
defaultDevices[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Opening DRM device: %s",
|
||||
device);
|
||||
|
||||
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
|
||||
if (m_DrmFd < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open DRM device: %d",
|
||||
@@ -131,14 +142,20 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_EGL
|
||||
// Still return true if we fail to initialize DRM direct rendering
|
||||
// stuff, since we have EGL that we can use for indirect rendering.
|
||||
// stuff, since we have EGLRenderer and SDLRenderer that we can use
|
||||
// for indirect rendering. Our FFmpeg renderer selection code will
|
||||
// handle the case where those also fail to render the test frame.
|
||||
const bool DIRECT_RENDERING_INIT_FAILED = true;
|
||||
#else
|
||||
// Fail if we can't initialize direct rendering and we don't have EGL.
|
||||
const bool DIRECT_RENDERING_INIT_FAILED = false;
|
||||
#endif
|
||||
|
||||
// If we're not sharing the DRM FD with SDL, that means we don't
|
||||
// have DRM master, so we can't call drmModeSetPlane(). We can
|
||||
// use EGLRenderer or SDLRenderer to render in this situation.
|
||||
if (!m_SdlOwnsDrmFd) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Direct rendering via DRM is disabled");
|
||||
return DIRECT_RENDERING_INIT_FAILED;
|
||||
}
|
||||
|
||||
drmModeRes* resources = drmModeGetResources(m_DrmFd);
|
||||
if (resources == nullptr) {
|
||||
@@ -517,6 +534,41 @@ ssize_t DrmRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
|
||||
}
|
||||
}
|
||||
|
||||
// Add colorspace data if present
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||
attribs[attribIndex++] = EGL_ITU_REC2020_EXT;
|
||||
break;
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_FCC:
|
||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||
attribs[attribIndex++] = EGL_ITU_REC601_EXT;
|
||||
break;
|
||||
case AVCOL_SPC_BT709:
|
||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||
attribs[attribIndex++] = EGL_ITU_REC709_EXT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Add color range data if present
|
||||
switch (frame->color_range) {
|
||||
case AVCOL_RANGE_JPEG:
|
||||
attribs[attribIndex++] = EGL_SAMPLE_RANGE_HINT_EXT;
|
||||
attribs[attribIndex++] = EGL_YUV_FULL_RANGE_EXT;
|
||||
break;
|
||||
case AVCOL_RANGE_MPEG:
|
||||
attribs[attribIndex++] = EGL_SAMPLE_RANGE_HINT_EXT;
|
||||
attribs[attribIndex++] = EGL_YUV_NARROW_RANGE_EXT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Terminate the attribute list
|
||||
attribs[attribIndex++] = EGL_NONE;
|
||||
SDL_assert(attribIndex <= MAX_ATTRIB_COUNT);
|
||||
|
||||
@@ -109,6 +109,11 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context, AVDictionary*
|
||||
context->hwaccel_context = &m_DXVAContext;
|
||||
|
||||
context->get_buffer2 = ffGetBuffer2;
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 60
|
||||
AV_NOWARN_DEPRECATED(
|
||||
context->thread_safe_callbacks = 1;
|
||||
)
|
||||
#endif
|
||||
|
||||
m_Pool = av_buffer_pool_init2(ARRAYSIZE(m_DecSurfaces), this, ffPoolAlloc, nullptr);
|
||||
if (!m_Pool) {
|
||||
@@ -489,10 +494,11 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
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
|
||||
// is not affected on my Intel HD 515.
|
||||
// is not affected on my Intel HD 515. Also account for the driver version rollover
|
||||
// that happened with the 101.1069 series.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/32
|
||||
// https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
|
||||
if (LOWORD(id.DriverVersion.LowPart) < 4836) {
|
||||
if (HIWORD(id.DriverVersion.LowPart) < 100 && LOWORD(id.DriverVersion.LowPart) < 4836) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Detected buggy Intel GPU driver installed. Update your Intel GPU driver to enable HEVC!");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
|
||||
@@ -71,8 +71,6 @@ EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer)
|
||||
m_Window(nullptr),
|
||||
m_Backend(backendRenderer),
|
||||
m_VAO(0),
|
||||
m_ColorSpace(AVCOL_SPC_NB),
|
||||
m_ColorFull(false),
|
||||
m_BlockingSwapBuffers(false),
|
||||
m_LastFrame(av_frame_alloc()),
|
||||
m_glEGLImageTargetTexture2DOES(nullptr),
|
||||
@@ -624,14 +622,17 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
return err == GL_NO_ERROR;
|
||||
}
|
||||
|
||||
const float *EGLRenderer::getColorOffsets() {
|
||||
const float *EGLRenderer::getColorOffsets(const AVFrame* frame) {
|
||||
static const float limitedOffsets[] = { 16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
||||
static const float fullOffsets[] = { 0.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
||||
|
||||
return m_ColorFull ? fullOffsets : limitedOffsets;
|
||||
// This handles the case where the color range is unknown,
|
||||
// so that we use Limited color range which is the default
|
||||
// behavior for Moonlight.
|
||||
return (frame->color_range == AVCOL_RANGE_JPEG) ? fullOffsets : limitedOffsets;
|
||||
}
|
||||
|
||||
const float *EGLRenderer::getColorMatrix() {
|
||||
const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
|
||||
/* The conversion matrices are shamelessly stolen from linux:
|
||||
* drivers/media/platform/imx-pxp.c:pxp_setup_csc
|
||||
*/
|
||||
@@ -666,25 +667,29 @@ const float *EGLRenderer::getColorMatrix() {
|
||||
1.4746f, -0.5714f, 0.0f
|
||||
};
|
||||
|
||||
switch (m_ColorSpace) {
|
||||
// This handles the case where the color range is unknown,
|
||||
// so that we use Limited color range which is the default
|
||||
// behavior for Moonlight.
|
||||
bool fullRange = (frame->color_range == AVCOL_RANGE_JPEG);
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_BT470BG:
|
||||
return m_ColorFull ? bt601Full : bt601Lim;
|
||||
return fullRange ? bt601Full : bt601Lim;
|
||||
case AVCOL_SPC_BT709:
|
||||
return m_ColorFull ? bt709Full : bt709Lim;
|
||||
return fullRange ? bt709Full : bt709Lim;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return m_ColorFull ? bt2020Full : bt2020Lim;
|
||||
return fullRange ? bt2020Full : bt2020Lim;
|
||||
default:
|
||||
// Some backends don't populate this, so we'll assume
|
||||
// the host gave us what we asked for by default.
|
||||
switch (getDecoderColorspace()) {
|
||||
case COLORSPACE_REC_601:
|
||||
return m_ColorFull ? bt601Full : bt601Lim;
|
||||
return fullRange ? bt601Full : bt601Lim;
|
||||
case COLORSPACE_REC_709:
|
||||
return m_ColorFull ? bt709Full : bt709Lim;
|
||||
return fullRange ? bt709Full : bt709Lim;
|
||||
case COLORSPACE_REC_2020:
|
||||
return m_ColorFull ? bt2020Full : bt2020Lim;
|
||||
return fullRange ? bt2020Full : bt2020Lim;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
}
|
||||
@@ -696,9 +701,6 @@ const float *EGLRenderer::getColorMatrix() {
|
||||
bool EGLRenderer::specialize() {
|
||||
SDL_assert(!m_VAO);
|
||||
|
||||
// Attach our GL context to the render thread
|
||||
SDL_GL_MakeCurrent(m_Window, m_Context);
|
||||
|
||||
if (!compileShaders())
|
||||
return false;
|
||||
|
||||
@@ -760,6 +762,12 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach our GL context to the render thread
|
||||
// NB: It should already be current, unless the SDL render event watcher
|
||||
// performs a rendering operation (like a viewport update on resize) on
|
||||
// our fake SDL_Renderer. If it's already current, this is a no-op.
|
||||
SDL_GL_MakeCurrent(m_Window, m_Context);
|
||||
|
||||
// Find the native read-back format and load the shaders
|
||||
if (m_EGLImagePixelFormat == AV_PIX_FMT_NONE) {
|
||||
m_EGLImagePixelFormat = m_Backend->getEGLImagePixelFormat();
|
||||
@@ -767,13 +775,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
||||
|
||||
SDL_assert(m_EGLImagePixelFormat != AV_PIX_FMT_NONE);
|
||||
|
||||
m_ColorSpace = frame->colorspace;
|
||||
|
||||
// This handles the case where the color range is unknown,
|
||||
// so that we use Limited color range which is the default
|
||||
// behavior for Moonlight.
|
||||
m_ColorFull = frame->color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
if (!specialize()) {
|
||||
m_EGLImagePixelFormat = AV_PIX_FMT_NONE;
|
||||
return;
|
||||
@@ -795,8 +796,8 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
||||
|
||||
// Bind parameters for the shaders
|
||||
if (m_EGLImagePixelFormat == AV_PIX_FMT_NV12) {
|
||||
glUniformMatrix3fv(m_ShaderProgramParams[NV12_PARAM_YUVMAT], 1, GL_FALSE, getColorMatrix());
|
||||
glUniform3fv(m_ShaderProgramParams[NV12_PARAM_OFFSET], 1, getColorOffsets());
|
||||
glUniformMatrix3fv(m_ShaderProgramParams[NV12_PARAM_YUVMAT], 1, GL_FALSE, getColorMatrix(frame));
|
||||
glUniform3fv(m_ShaderProgramParams[NV12_PARAM_OFFSET], 1, getColorOffsets(frame));
|
||||
glUniform1i(m_ShaderProgramParams[NV12_PARAM_PLANE1], 0);
|
||||
glUniform1i(m_ShaderProgramParams[NV12_PARAM_PLANE2], 1);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ private:
|
||||
unsigned compileShader(const char* vertexShaderSrc, const char* fragmentShaderSrc);
|
||||
bool compileShaders();
|
||||
bool specialize();
|
||||
const float *getColorOffsets();
|
||||
const float *getColorMatrix();
|
||||
const float *getColorOffsets(const AVFrame* frame);
|
||||
const float *getColorMatrix(const AVFrame* frame);
|
||||
static int loadAndBuildShader(int shaderType, const char *filename);
|
||||
bool openDisplay(unsigned int platform, void* nativeDisplay);
|
||||
|
||||
@@ -43,8 +43,6 @@ private:
|
||||
SDL_Window *m_Window;
|
||||
IFFmpegRenderer *m_Backend;
|
||||
unsigned int m_VAO;
|
||||
int m_ColorSpace;
|
||||
bool m_ColorFull;
|
||||
bool m_BlockingSwapBuffers;
|
||||
AVFrame* m_LastFrame;
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES;
|
||||
|
||||
@@ -5,8 +5,16 @@
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
// HACK: Avoid including X11 headers which conflict with QDir
|
||||
#ifdef SDL_VIDEO_DRIVER_X11
|
||||
#undef SDL_VIDEO_DRIVER_X11
|
||||
#endif
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
|
||||
MmalRenderer::MmalRenderer()
|
||||
: m_Renderer(nullptr),
|
||||
m_InputPort(nullptr),
|
||||
@@ -108,6 +116,10 @@ bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
{
|
||||
MMAL_STATUS_T status;
|
||||
|
||||
if (!isMmalOverlaySupported()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Window = params->window;
|
||||
m_VideoWidth = params->width;
|
||||
m_VideoHeight = params->height;
|
||||
@@ -132,6 +144,12 @@ bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
m_InputPort->format->es->video.crop.y = 0;
|
||||
m_InputPort->format->es->video.crop.width = params->width;
|
||||
m_InputPort->format->es->video.crop.height = params->height;
|
||||
|
||||
// Setting colorspace like this doesn't seem to make a difference,
|
||||
// but we'll do it just in case it starts working in the future.
|
||||
// The default appears to be Rec. 709 already.
|
||||
m_InputPort->format->es->video.color_space = MMAL_COLOR_SPACE_ITUR_BT709;
|
||||
|
||||
status = mmal_port_format_commit(m_InputPort);
|
||||
if (status != MMAL_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -192,6 +210,13 @@ bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
return true;
|
||||
}
|
||||
|
||||
int MmalRenderer::getDecoderColorspace()
|
||||
{
|
||||
// MMAL seems to always use Rec. 709 colorspace for rendering
|
||||
// even when we try to set something else in the input format.
|
||||
return COLORSPACE_REC_709;
|
||||
}
|
||||
|
||||
void MmalRenderer::setupBackground(PDECODER_PARAMETERS params)
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
@@ -227,6 +252,78 @@ void MmalRenderer::InputPortCallback(MMAL_PORT_T*, MMAL_BUFFER_HEADER_T* buffer)
|
||||
mmal_buffer_header_release(buffer);
|
||||
}
|
||||
|
||||
// MMAL rendering will silently fail in Full KMS mode. We'll see if that's
|
||||
// enabled by reading sysfs. It's gross but it works.
|
||||
bool MmalRenderer::getDtDeviceStatus(QString name, bool ifUnknown)
|
||||
{
|
||||
QDir dir("/sys/firmware/devicetree/base/soc");
|
||||
QStringList matchingDir = dir.entryList(QStringList(name + "@*"), QDir::Dirs);
|
||||
if (matchingDir.length() != 1) {
|
||||
Q_ASSERT(matchingDir.isEmpty());
|
||||
return ifUnknown;
|
||||
}
|
||||
|
||||
if (!dir.cd(matchingDir.first())) {
|
||||
return ifUnknown;
|
||||
}
|
||||
|
||||
QFile statusFile(dir.filePath("status"));
|
||||
if (!statusFile.open(QFile::ReadOnly)) {
|
||||
// Per Device Tree docs, missing 'status' means enabled
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray statusData = statusFile.readAll();
|
||||
QString statusString(statusData);
|
||||
|
||||
// Per Device Tree docs, 'okay' and 'ok' are both acceptable
|
||||
return statusData == "okay" || statusData == "ok";
|
||||
}
|
||||
|
||||
bool MmalRenderer::isMmalOverlaySupported()
|
||||
{
|
||||
if (qgetenv("MMAL_DISABLE_SUPPORT_CHECK") == "1") {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"MMAL overlay support check is disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mmalOverlayCheckCompleted = false;
|
||||
static bool mmalOverlayCheckResult = true;
|
||||
|
||||
// This overlay support check is expensive, so only do it once. The stuff we're
|
||||
// checking can't change without restarting Moonlight (or the whole system).
|
||||
if (mmalOverlayCheckCompleted) {
|
||||
SDL_MemoryBarrierAcquire();
|
||||
return mmalOverlayCheckResult;
|
||||
}
|
||||
|
||||
// vc4-fkms-v3d - firmwarekms is 'okay', hvs is 'disabled'
|
||||
// vc4-kms-v3d - firmwarekms is 'disabled', hvs is 'okay' <- this is the bad one
|
||||
// none - firmwarekms is 'disabled', hvs is 'disabled'
|
||||
if (!getDtDeviceStatus("firmwarekms", true) && getDtDeviceStatus("hvs", true)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Full KMS Mode is enabled! Hardware accelerated H.264 decoding will be unavailable!");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Change 'dtoverlay=vc4-kms-v3d' to 'dtoverlay=vc4-fkms-v3d' in /boot/config.txt to fix this!");
|
||||
mmalOverlayCheckResult = false;
|
||||
}
|
||||
|
||||
// /dev/video19 is the rpivid stateless HEVC decoder
|
||||
if (!QFile::exists("/dev/video19")) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Raspberry Pi HEVC decoder is not enabled! Add 'dtoverlay=rpivid-v4l2' to your /boot/config.txt to fix this!");
|
||||
}
|
||||
else if (strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") != 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Raspberry Pi HEVC decoder cannot be used from within a desktop environment. H.264 will be used instead.");
|
||||
}
|
||||
|
||||
SDL_MemoryBarrierRelease();
|
||||
mmalOverlayCheckCompleted = true;
|
||||
return mmalOverlayCheckResult;
|
||||
}
|
||||
|
||||
enum AVPixelFormat MmalRenderer::getPreferredPixelFormat(int)
|
||||
{
|
||||
// Opaque MMAL buffers
|
||||
|
||||
@@ -17,9 +17,12 @@ public:
|
||||
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
|
||||
virtual bool needsTestFrame() override;
|
||||
virtual int getRendererAttributes() override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
|
||||
private:
|
||||
static void InputPortCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer);
|
||||
bool getDtDeviceStatus(QString name, bool ifUnknown);
|
||||
bool isMmalOverlaySupported();
|
||||
|
||||
void setupBackground(PDECODER_PARAMETERS params);
|
||||
void updateDisplayRegion();
|
||||
|
||||
@@ -48,6 +48,17 @@ typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platf
|
||||
#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278
|
||||
#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279
|
||||
#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A
|
||||
#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B
|
||||
#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C
|
||||
#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D
|
||||
#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E
|
||||
#define EGL_ITU_REC601_EXT 0x327F
|
||||
#define EGL_ITU_REC709_EXT 0x3280
|
||||
#define EGL_ITU_REC2020_EXT 0x3281
|
||||
#define EGL_YUV_FULL_RANGE_EXT 0x3282
|
||||
#define EGL_YUV_NARROW_RANGE_EXT 0x3283
|
||||
#define EGL_YUV_CHROMA_SITING_0_EXT 0x3284
|
||||
#define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285
|
||||
#endif
|
||||
|
||||
#ifndef EGL_EXT_image_dma_buf_import_modifiers
|
||||
|
||||
@@ -6,15 +6,28 @@
|
||||
#include <Limelight.h>
|
||||
|
||||
SdlRenderer::SdlRenderer()
|
||||
: m_Renderer(nullptr),
|
||||
: m_VideoFormat(0),
|
||||
m_Renderer(nullptr),
|
||||
m_Texture(nullptr),
|
||||
m_SwPixelFormat(AV_PIX_FMT_NONE)
|
||||
m_SwPixelFormat(AV_PIX_FMT_NONE),
|
||||
m_ColorSpace(AVCOL_SPC_UNSPECIFIED),
|
||||
m_MapFrame(false)
|
||||
{
|
||||
SDL_zero(m_OverlayTextures);
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
m_CudaGLHelper = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
SdlRenderer::~SdlRenderer()
|
||||
{
|
||||
#ifdef HAVE_CUDA
|
||||
if (m_CudaGLHelper != nullptr) {
|
||||
delete m_CudaGLHelper;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||
if (m_OverlayTextures[i] != nullptr) {
|
||||
SDL_DestroyTexture(m_OverlayTextures[i]);
|
||||
@@ -77,6 +90,8 @@ bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
{
|
||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
|
||||
m_VideoFormat = params->videoFormat;
|
||||
|
||||
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||
// SDL doesn't support rendering YUV 10-bit textures yet
|
||||
return false;
|
||||
@@ -193,6 +208,137 @@ void SdlRenderer::renderOverlay(Overlay::OverlayType type)
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlRenderer::initializeReadBackFormat(AVBufferRef* hwFrameCtxRef, AVFrame* testFrame)
|
||||
{
|
||||
auto hwFrameCtx = (AVHWFramesContext*)hwFrameCtxRef->data;
|
||||
int err;
|
||||
enum AVPixelFormat *formats;
|
||||
AVFrame* outputFrame;
|
||||
|
||||
// This function must only be called once per instance
|
||||
SDL_assert(m_SwPixelFormat == AV_PIX_FMT_NONE);
|
||||
SDL_assert(!m_MapFrame);
|
||||
|
||||
// Try direct mapping before resorting to copying the frame
|
||||
outputFrame = av_frame_alloc();
|
||||
if (outputFrame != nullptr) {
|
||||
err = av_hwframe_map(outputFrame, testFrame, AV_HWFRAME_MAP_READ);
|
||||
if (err == 0) {
|
||||
if (isPixelFormatSupported(m_VideoFormat, (AVPixelFormat)outputFrame->format)) {
|
||||
m_SwPixelFormat = (AVPixelFormat)outputFrame->format;
|
||||
m_MapFrame = true;
|
||||
goto Exit;
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Skipping unsupported hwframe mapping format: %d",
|
||||
outputFrame->format);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_map() is unsupported (error: %d)",
|
||||
err);
|
||||
SDL_assert(err == AVERROR(ENOSYS));
|
||||
}
|
||||
}
|
||||
|
||||
// Direct mapping didn't work, so let's see what transfer formats we have
|
||||
err = av_hwframe_transfer_get_formats(hwFrameCtxRef, AV_HWFRAME_TRANSFER_DIRECTION_FROM, &formats, 0);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_transfer_get_formats() failed: %d",
|
||||
err);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
// NB: In this algorithm, we prefer to get a preferred hardware readback format
|
||||
// and non-preferred rendering format rather than the other way around. This is
|
||||
// why we loop through the readback format list in order, rather than searching
|
||||
// for the format from getPreferredPixelFormat() in the list.
|
||||
for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) {
|
||||
SDL_assert(m_VideoFormat != 0);
|
||||
if (!isPixelFormatSupported(m_VideoFormat, formats[i])) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Skipping unsupported hwframe transfer format %d",
|
||||
formats[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_SwPixelFormat = formats[i];
|
||||
break;
|
||||
}
|
||||
|
||||
av_freep(&formats);
|
||||
|
||||
Exit:
|
||||
av_frame_free(&outputFrame);
|
||||
|
||||
// If we didn't find any supported formats, try hwFrameCtx->sw_format.
|
||||
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
|
||||
if (isPixelFormatSupported(m_VideoFormat, hwFrameCtx->sw_format)) {
|
||||
m_SwPixelFormat = hwFrameCtx->sw_format;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find compatible hwframe transfer format (sw_format = %d)",
|
||||
hwFrameCtx->sw_format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected hwframe->swframe format: %d (mapping: %s)",
|
||||
m_SwPixelFormat,
|
||||
m_MapFrame ? "yes" : "no");
|
||||
return true;
|
||||
}
|
||||
|
||||
AVFrame* SdlRenderer::getSwFrameFromHwFrame(AVFrame* hwFrame)
|
||||
{
|
||||
int err;
|
||||
|
||||
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
|
||||
|
||||
AVFrame* swFrame = av_frame_alloc();
|
||||
if (swFrame == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
swFrame->format = m_SwPixelFormat;
|
||||
|
||||
if (m_MapFrame) {
|
||||
// We don't use AV_HWFRAME_MAP_DIRECT here because it can cause huge
|
||||
// performance penalties on Intel hardware with VAAPI due to mappings
|
||||
// being uncached memory.
|
||||
err = av_hwframe_map(swFrame, hwFrame, AV_HWFRAME_MAP_READ);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_map() failed: %d",
|
||||
err);
|
||||
av_frame_free(&swFrame);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
err = av_hwframe_transfer_data(swFrame, hwFrame, 0);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_transfer_data() failed: %d",
|
||||
err);
|
||||
av_frame_free(&swFrame);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// av_hwframe_transfer_data() doesn't transfer metadata
|
||||
// (and can even nuke existing metadata in dst), so we
|
||||
// will propagate metadata manually afterwards.
|
||||
av_frame_copy_props(swFrame, hwFrame);
|
||||
}
|
||||
|
||||
return swFrame;
|
||||
}
|
||||
|
||||
void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
int err;
|
||||
@@ -203,46 +349,40 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame->hw_frames_ctx != nullptr) {
|
||||
if (frame->hw_frames_ctx != nullptr && frame->format != AV_PIX_FMT_CUDA) {
|
||||
#ifdef HAVE_CUDA
|
||||
ReadbackRetry:
|
||||
#endif
|
||||
// If we are acting as the frontend for a hardware
|
||||
// accelerated decoder, we'll need to read the frame
|
||||
// back to render it.
|
||||
|
||||
// Find the native read-back format
|
||||
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
|
||||
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
|
||||
initializeReadBackFormat(frame->hw_frames_ctx, frame);
|
||||
|
||||
m_SwPixelFormat = hwFrameCtx->sw_format;
|
||||
// If we don't support any of the hw transfer formats, we should
|
||||
// have failed inside testRenderFrame() and not made it here.
|
||||
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected read-back format: %d",
|
||||
m_SwPixelFormat);
|
||||
}
|
||||
|
||||
swFrame = av_frame_alloc();
|
||||
// Map or copy this hwframe to a swframe that we can work with
|
||||
frame = swFrame = getSwFrameFromHwFrame(frame);
|
||||
if (swFrame == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
swFrame->width = frame->width;
|
||||
swFrame->height = frame->height;
|
||||
swFrame->format = m_SwPixelFormat;
|
||||
|
||||
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
||||
if (err != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_transfer_data() failed: %d",
|
||||
err);
|
||||
goto Exit;
|
||||
// Because the specific YUV color conversion shader is established at
|
||||
// texture creation for most SDL render backends, we need to recreate
|
||||
// the texture when the colorspace changes.
|
||||
if (frame->colorspace != m_ColorSpace) {
|
||||
if (m_Texture != nullptr) {
|
||||
SDL_DestroyTexture(m_Texture);
|
||||
m_Texture = nullptr;
|
||||
}
|
||||
|
||||
// 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;
|
||||
m_ColorSpace = frame->colorspace;
|
||||
}
|
||||
|
||||
if (m_Texture == nullptr) {
|
||||
@@ -254,6 +394,7 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
sdlFormat = SDL_PIXELFORMAT_YV12;
|
||||
break;
|
||||
case AV_PIX_FMT_CUDA:
|
||||
case AV_PIX_FMT_NV12:
|
||||
sdlFormat = SDL_PIXELFORMAT_NV12;
|
||||
break;
|
||||
@@ -288,9 +429,35 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
SDL_GetError());
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
if (frame->format == AV_PIX_FMT_CUDA) {
|
||||
SDL_assert(m_CudaGLHelper == nullptr);
|
||||
m_CudaGLHelper = new CUDAGLInteropHelper(((AVHWFramesContext*)frame->hw_frames_ctx->data)->device_ctx);
|
||||
|
||||
SDL_GL_BindTexture(m_Texture, nullptr, nullptr);
|
||||
if (!m_CudaGLHelper->registerBoundTextures()) {
|
||||
// If we can't register textures, fall back to normal read-back rendering
|
||||
delete m_CudaGLHelper;
|
||||
m_CudaGLHelper = nullptr;
|
||||
}
|
||||
SDL_GL_UnbindTexture(m_Texture);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (frame->format == AV_PIX_FMT_YUV420P) {
|
||||
if (frame->format == AV_PIX_FMT_CUDA) {
|
||||
#ifdef HAVE_CUDA
|
||||
if (m_CudaGLHelper == nullptr || !m_CudaGLHelper->copyCudaFrameToTextures(frame)) {
|
||||
goto ReadbackRetry;
|
||||
}
|
||||
#else
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Got CUDA frame, but not built with CUDA support!");
|
||||
goto Exit;
|
||||
#endif
|
||||
}
|
||||
else if (frame->format == AV_PIX_FMT_YUV420P) {
|
||||
SDL_UpdateYUVTexture(m_Texture, nullptr,
|
||||
frame->data[0],
|
||||
frame->linesize[0],
|
||||
@@ -313,9 +480,9 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
#endif
|
||||
{
|
||||
char* pixels;
|
||||
int pitch;
|
||||
int texturePitch;
|
||||
|
||||
err = SDL_LockTexture(m_Texture, nullptr, (void**)&pixels, &pitch);
|
||||
err = SDL_LockTexture(m_Texture, nullptr, (void**)&pixels, &texturePitch);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_LockTexture() failed: %s",
|
||||
@@ -323,12 +490,37 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
memcpy(pixels,
|
||||
frame->data[0],
|
||||
frame->linesize[0] * frame->height);
|
||||
memcpy(pixels + (frame->linesize[0] * frame->height),
|
||||
frame->data[1],
|
||||
frame->linesize[1] * frame->height / 2);
|
||||
// If the planar pitches match, we can use a single memcpy() to transfer
|
||||
// the data. If not, we'll need to do separate memcpy() calls for each
|
||||
// line to ensure the pitch doesn't get screwed up.
|
||||
|
||||
if (frame->linesize[0] == texturePitch) {
|
||||
memcpy(pixels,
|
||||
frame->data[0],
|
||||
frame->linesize[0] * frame->height);
|
||||
}
|
||||
else {
|
||||
int pitch = SDL_min(frame->linesize[0], texturePitch);
|
||||
for (int i = 0; i < frame->height; i++) {
|
||||
memcpy(pixels + (texturePitch * i),
|
||||
frame->data[0] + (frame->linesize[0] * i),
|
||||
pitch);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame->linesize[1] == texturePitch) {
|
||||
memcpy(pixels + (texturePitch * frame->height),
|
||||
frame->data[1],
|
||||
frame->linesize[1] * frame->height / 2);
|
||||
}
|
||||
else {
|
||||
int pitch = SDL_min(frame->linesize[1], texturePitch);
|
||||
for (int i = 0; i < frame->height / 2; i++) {
|
||||
memcpy(pixels + (texturePitch * (frame->height + i)),
|
||||
frame->data[1] + (frame->linesize[1] * i),
|
||||
pitch);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockTexture(m_Texture);
|
||||
}
|
||||
@@ -359,27 +551,36 @@ bool SdlRenderer::testRenderFrame(AVFrame* frame)
|
||||
// back to render it. Test that this can be done
|
||||
// for the given frame successfully.
|
||||
if (frame->hw_frames_ctx != nullptr) {
|
||||
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
|
||||
#ifdef HAVE_MMAL
|
||||
// FFmpeg for Raspberry Pi has NEON-optimized routines that allow
|
||||
// us to use av_hwframe_transfer_data() to convert from SAND frames
|
||||
// to standard fully-planar YUV. Unfortunately, the combination of
|
||||
// doing this conversion on the CPU and the slow GL texture upload
|
||||
// performance on the Pi make the performance of this approach
|
||||
// unacceptable. Don't use this copyback path on the Pi by default
|
||||
// to ensure we fall back to H.264 (with the MMAL renderer) in X11
|
||||
// rather than using HEVC+copyback and getting terrible performance.
|
||||
if (qgetenv("RPI_ALLOW_COPYBACK_RENDER") != "1") {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
AVFrame* swFrame = av_frame_alloc();
|
||||
if (!initializeReadBackFormat(frame->hw_frames_ctx, frame)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AVFrame* swFrame = getSwFrameFromHwFrame(frame);
|
||||
if (swFrame == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
swFrame->width = frame->width;
|
||||
swFrame->height = frame->height;
|
||||
swFrame->format = hwFrameCtx->sw_format;
|
||||
|
||||
int err = av_hwframe_transfer_data(swFrame, frame, 0);
|
||||
|
||||
av_frame_free(&swFrame);
|
||||
|
||||
if (err != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"av_hwframe_transfer_data() failed: %d",
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!isPixelFormatSupported(m_VideoFormat, (AVPixelFormat)frame->format)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Swframe pixel format unsupported: %d",
|
||||
frame->format);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
#include "cuda.h"
|
||||
#endif
|
||||
|
||||
class SdlRenderer : public IFFmpegRenderer {
|
||||
public:
|
||||
SdlRenderer();
|
||||
@@ -15,11 +19,20 @@ public:
|
||||
|
||||
private:
|
||||
void renderOverlay(Overlay::OverlayType type);
|
||||
bool initializeReadBackFormat(AVBufferRef* hwFrameCtxRef, AVFrame* testFrame);
|
||||
AVFrame* getSwFrameFromHwFrame(AVFrame* hwFrame);
|
||||
|
||||
int m_VideoFormat;
|
||||
SDL_Renderer* m_Renderer;
|
||||
SDL_Texture* m_Texture;
|
||||
int m_SwPixelFormat;
|
||||
enum AVPixelFormat m_SwPixelFormat;
|
||||
enum AVColorSpace m_ColorSpace;
|
||||
bool m_MapFrame;
|
||||
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
|
||||
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
CUDAGLInteropHelper* m_CudaGLHelper;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ VAAPIRenderer::openDisplay(SDL_Window* window)
|
||||
|
||||
m_WindowSystem = info.subsystem;
|
||||
if (info.subsystem == SDL_SYSWM_X11) {
|
||||
m_XWindow = info.info.x11.window;
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
m_XWindow = info.info.x11.window;
|
||||
display = vaGetDisplay(info.info.x11.display);
|
||||
if (display == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -543,29 +543,89 @@ VAAPIRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
|
||||
|
||||
for (size_t i = 0; i < m_PrimeDescriptor.num_layers; ++i) {
|
||||
const auto &layer = m_PrimeDescriptor.layers[i];
|
||||
const auto &object = m_PrimeDescriptor.objects[layer.object_index[0]];
|
||||
|
||||
const int EGL_ATTRIB_COUNT = 17;
|
||||
// Max 30 attributes (1 key + 1 value for each)
|
||||
const int EGL_ATTRIB_COUNT = 30 * 2;
|
||||
EGLAttrib attribs[EGL_ATTRIB_COUNT] = {
|
||||
EGL_LINUX_DRM_FOURCC_EXT, layer.drm_format,
|
||||
EGL_WIDTH, i == 0 ? frame->width : frame->width / 2,
|
||||
EGL_HEIGHT, i == 0 ? frame->height : frame->height / 2,
|
||||
EGL_DMA_BUF_PLANE0_FD_EXT, object.fd,
|
||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, layer.offset[0],
|
||||
EGL_DMA_BUF_PLANE0_PITCH_EXT, layer.pitch[0],
|
||||
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (EGLint)object.drm_format_modifier,
|
||||
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (EGLint)(object.drm_format_modifier >> 32),
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
// Cut off the attribute array before the modifiers if they aren't supported
|
||||
if (!m_EGLExtDmaBuf) {
|
||||
SDL_assert(attribs[12] == EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT);
|
||||
SDL_assert(attribs[14] == EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT);
|
||||
SDL_assert(attribs[16] == EGL_NONE);
|
||||
attribs[12] = EGL_NONE;
|
||||
int attribIndex = 6;
|
||||
for (size_t j = 0; j < layer.num_planes; j++) {
|
||||
const auto &object = m_PrimeDescriptor.objects[layer.object_index[j]];
|
||||
|
||||
switch (j) {
|
||||
case 0:
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
||||
attribs[attribIndex++] = object.fd;
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
||||
attribs[attribIndex++] = layer.offset[0];
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
||||
attribs[attribIndex++] = layer.pitch[0];
|
||||
if (m_EGLExtDmaBuf) {
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF);
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier >> 32);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_FD_EXT;
|
||||
attribs[attribIndex++] = object.fd;
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
|
||||
attribs[attribIndex++] = layer.offset[1];
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
|
||||
attribs[attribIndex++] = layer.pitch[1];
|
||||
if (m_EGLExtDmaBuf) {
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF);
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier >> 32);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_FD_EXT;
|
||||
attribs[attribIndex++] = object.fd;
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
|
||||
attribs[attribIndex++] = layer.offset[2];
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
|
||||
attribs[attribIndex++] = layer.pitch[2];
|
||||
if (m_EGLExtDmaBuf) {
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF);
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier >> 32);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_FD_EXT;
|
||||
attribs[attribIndex++] = object.fd;
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
|
||||
attribs[attribIndex++] = layer.offset[3];
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
|
||||
attribs[attribIndex++] = layer.pitch[3];
|
||||
if (m_EGLExtDmaBuf) {
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF);
|
||||
attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
|
||||
attribs[attribIndex++] = (EGLint)(object.drm_format_modifier >> 32);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the attribute list
|
||||
attribs[attribIndex++] = EGL_NONE;
|
||||
SDL_assert(attribIndex <= EGL_ATTRIB_COUNT);
|
||||
|
||||
if (m_eglCreateImage) {
|
||||
images[i] = m_eglCreateImage(dpy, EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT,
|
||||
@@ -579,7 +639,7 @@ VAAPIRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
|
||||
else {
|
||||
// Cast the EGLAttrib array elements to EGLint for the KHR extension
|
||||
EGLint intAttribs[EGL_ATTRIB_COUNT];
|
||||
for (int i = 0; i < EGL_ATTRIB_COUNT; i++) {
|
||||
for (int i = 0; i < attribIndex; i++) {
|
||||
intAttribs[i] = (EGLint)attribs[i];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
#include "renderer.h"
|
||||
|
||||
// Avoid X11 if SDL was built without it
|
||||
#ifndef SDL_VIDEO_DRIVER_X11
|
||||
#if !defined(SDL_VIDEO_DRIVER_X11) && defined(HAVE_LIBVA_X11)
|
||||
#warning Unable to use libva-x11 without SDL support
|
||||
#undef HAVE_LIBVA_X11
|
||||
#endif
|
||||
|
||||
// Avoid Wayland if SDL was built without it
|
||||
#ifndef SDL_VIDEO_DRIVER_WAYLAND
|
||||
#if !defined(SDL_VIDEO_DRIVER_WAYLAND) && defined(HAVE_LIBVA_WAYLAND)
|
||||
#warning Unable to use libva-wayland without SDL support
|
||||
#undef HAVE_LIBVA_WAYLAND
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <streaming/session.h>
|
||||
#include "vdpau.h"
|
||||
#include <streaming/streamutils.h>
|
||||
#include <utils.h>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
@@ -93,6 +94,36 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
||||
#endif
|
||||
};
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU is not supported on Wayland");
|
||||
return false;
|
||||
}
|
||||
else if (info.subsystem != SDL_SYSWM_X11) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU is not supported on the current subsystem: %d",
|
||||
info.subsystem);
|
||||
return false;
|
||||
}
|
||||
else if (qgetenv("VDPAU_XWAYLAND") != "1" && WMUtils::isRunningWayland()) {
|
||||
// VDPAU initialization causes Moonlight to crash when using XWayland in a Flatpak
|
||||
// on a system with the Nvidia 495.44 driver. VDPAU won't work under XWayland anyway,
|
||||
// so let's not risk trying it (unless the user wants to roll the dice).
|
||||
// https://gitlab.freedesktop.org/vdpau/libvdpau/-/issues/2
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU is disabled on XWayland. Set VDPAU_XWAYLAND=1 to try your luck.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_VideoWidth = params->width;
|
||||
m_VideoHeight = params->height;
|
||||
|
||||
@@ -158,39 +189,17 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
||||
|
||||
SDL_GetWindowSize(params->window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight);
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
||||
|
||||
if (info.subsystem == SDL_SYSWM_X11) {
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11,
|
||||
&m_VdpPresentationQueueTargetCreateX11);
|
||||
status = m_VdpPresentationQueueTargetCreateX11(m_Device,
|
||||
info.info.x11.window,
|
||||
&m_PresentationQueueTarget);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpPresentationQueueTargetCreateX11() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11,
|
||||
&m_VdpPresentationQueueTargetCreateX11);
|
||||
status = m_VdpPresentationQueueTargetCreateX11(m_Device,
|
||||
info.info.x11.window,
|
||||
&m_PresentationQueueTarget);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU backend does not currently support Wayland");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unsupported VDPAU rendering subsystem: %d",
|
||||
info.subsystem);
|
||||
"VdpPresentationQueueTargetCreateX11() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
m_FormatDesc(nullptr),
|
||||
m_StreamView(nullptr),
|
||||
m_DisplayLink(nullptr),
|
||||
m_LastAvColorSpace(AVCOL_SPC_UNSPECIFIED),
|
||||
m_ColorSpace(nullptr),
|
||||
m_VsyncMutex(nullptr),
|
||||
m_VsyncPassed(nullptr)
|
||||
@@ -185,6 +186,47 @@ public:
|
||||
kCVImageBufferChromaSubsampling_420,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
|
||||
switch (frame->chroma_location) {
|
||||
case AVCHROMA_LOC_LEFT:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_Left,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCHROMA_LOC_CENTER:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_Center,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCHROMA_LOC_TOPLEFT:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_TopLeft,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCHROMA_LOC_TOP:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_Top,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCHROMA_LOC_BOTTOMLEFT:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_BottomLeft,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCHROMA_LOC_BOTTOM:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferChromaLocationTopFieldKey,
|
||||
kCVImageBufferChromaLocation_Bottom,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (frame->color_primaries) {
|
||||
case AVCOL_PRI_BT709:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
@@ -222,10 +264,27 @@ public:
|
||||
kCVImageBufferTransferFunction_ITU_R_2020,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
case AVCOL_TRC_SMPTE2084:
|
||||
CVBufferSetAttachment(pixBuf,
|
||||
kCVImageBufferTransferFunctionKey,
|
||||
kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset m_ColorSpace if the colorspace changes. This can happen when
|
||||
// a game enters HDR mode (Rec 601 -> Rec 2020).
|
||||
if (frame->colorspace != m_LastAvColorSpace) {
|
||||
if (m_ColorSpace != nullptr) {
|
||||
CGColorSpaceRelease(m_ColorSpace);
|
||||
m_ColorSpace = nullptr;
|
||||
}
|
||||
|
||||
m_LastAvColorSpace = frame->colorspace;
|
||||
}
|
||||
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_BT709:
|
||||
if (m_ColorSpace == nullptr) {
|
||||
@@ -278,6 +337,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (m_DisplayLink != nullptr) {
|
||||
// Vsync is enabled, so wait for a swap before returning
|
||||
SDL_LockMutex(m_VsyncMutex);
|
||||
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync wait timed out after 100 ms");
|
||||
}
|
||||
SDL_UnlockMutex(m_VsyncMutex);
|
||||
}
|
||||
|
||||
// Queue this sample for the next v-sync
|
||||
CMSampleTimingInfo timingInfo = {
|
||||
.duration = kCMTimeInvalid,
|
||||
@@ -301,16 +370,6 @@ public:
|
||||
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
CFRelease(sampleBuffer);
|
||||
|
||||
if (m_DisplayLink != nullptr) {
|
||||
// Vsync is enabled, so wait for a swap before returning
|
||||
SDL_LockMutex(m_VsyncMutex);
|
||||
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync wait timed out after 100 ms");
|
||||
}
|
||||
SDL_UnlockMutex(m_VsyncMutex);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool initialize(PDECODER_PARAMETERS params) override
|
||||
@@ -318,89 +377,56 @@ public:
|
||||
int err;
|
||||
|
||||
if (params->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
|
||||
{
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_H264)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Assuming H.264 HW decode on < macOS 10.13");
|
||||
"No HW accelerated H.264 decode via VT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||
if (__builtin_available(macOS 10.13, *)) {
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"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) {
|
||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
if (device == nullptr) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No HW accelerated HEVC decode via VT");
|
||||
"Unable to get default Metal device");
|
||||
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) {
|
||||
// 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,
|
||||
"Unable to get default Metal device");
|
||||
"No HEVC Main10 support on Skylake iGPU");
|
||||
[device release];
|
||||
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");
|
||||
[device release];
|
||||
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");
|
||||
[device release];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[device release];
|
||||
}
|
||||
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 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");
|
||||
[device release];
|
||||
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;
|
||||
|
||||
[device release];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,6 +452,23 @@ public:
|
||||
m_DisplayLayer.bounds = m_StreamView.bounds;
|
||||
m_DisplayLayer.position = CGPointMake(CGRectGetMidX(m_StreamView.bounds), CGRectGetMidY(m_StreamView.bounds));
|
||||
m_DisplayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||
m_DisplayLayer.opaque = YES;
|
||||
|
||||
// This workaround prevents the image from going through processing that doesn't preserve
|
||||
// the colorspace information properly in some cases. HDR seems to be okay without this,
|
||||
// so we'll exclude it out of caution.
|
||||
// See https://github.com/moonlight-stream/moonlight-qt/issues/493 for more details.
|
||||
if (params->videoFormat != VIDEO_FORMAT_H265_MAIN10) {
|
||||
if (info.info.cocoa.window.screen != nullptr) {
|
||||
m_DisplayLayer.shouldRasterize = YES;
|
||||
m_DisplayLayer.rasterizationScale = info.info.cocoa.window.screen.backingScaleFactor;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to rasterize layer due to missing NSScreen");
|
||||
SDL_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a layer-hosted view by setting the layer before wantsLayer
|
||||
// This avoids us having to add our AVSampleBufferDisplayLayer as a
|
||||
@@ -554,6 +597,7 @@ private:
|
||||
NSView* m_StreamView;
|
||||
NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
AVColorSpace m_LastAvColorSpace;
|
||||
CGColorSpaceRef m_ColorSpace;
|
||||
SDL_mutex* m_VsyncMutex;
|
||||
SDL_cond* m_VsyncPassed;
|
||||
|
||||
+224
-81
@@ -6,7 +6,6 @@
|
||||
#include <h264_stream.h>
|
||||
|
||||
#include "ffmpeg-renderers/sdlvid.h"
|
||||
#include "ffmpeg-renderers/cuda.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "ffmpeg-renderers/dxva2.h"
|
||||
@@ -36,6 +35,10 @@
|
||||
#include "ffmpeg-renderers/eglvid.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
#include "ffmpeg-renderers/cuda.h"
|
||||
#endif
|
||||
|
||||
// This is gross but it allows us to use sizeof()
|
||||
#include "ffmpeg_videosamples.cpp"
|
||||
|
||||
@@ -67,6 +70,9 @@ int FFmpegVideoDecoder::getDecoderCapabilities()
|
||||
capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
|
||||
}
|
||||
|
||||
// We use our own decoder thread with the "pull" model
|
||||
capabilities |= CAPABILITY_PULL_RENDERER;
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@@ -131,12 +137,15 @@ FFmpegVideoDecoder::FFmpegVideoDecoder(bool testOnly)
|
||||
m_StreamFps(0),
|
||||
m_VideoFormat(0),
|
||||
m_NeedsSpsFixup(false),
|
||||
m_TestOnly(testOnly)
|
||||
m_TestOnly(testOnly),
|
||||
m_DecoderThread(nullptr)
|
||||
{
|
||||
SDL_zero(m_ActiveWndVideoStats);
|
||||
SDL_zero(m_LastWndVideoStats);
|
||||
SDL_zero(m_GlobalVideoStats);
|
||||
|
||||
SDL_AtomicSet(&m_DecoderThreadShouldQuit, 0);
|
||||
|
||||
// Use linear filtering when renderer scaling is required
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
||||
}
|
||||
@@ -161,6 +170,19 @@ IFFmpegRenderer* FFmpegVideoDecoder::getBackendRenderer()
|
||||
|
||||
void FFmpegVideoDecoder::reset()
|
||||
{
|
||||
// Terminate the decoder thread before doing anything else.
|
||||
// It might be touching things we're about to free.
|
||||
if (m_DecoderThread != nullptr) {
|
||||
SDL_AtomicSet(&m_DecoderThreadShouldQuit, 1);
|
||||
LiWakeWaitForVideoFrame();
|
||||
SDL_WaitThread(m_DecoderThread, NULL);
|
||||
SDL_AtomicSet(&m_DecoderThreadShouldQuit, 0);
|
||||
m_DecoderThread = nullptr;
|
||||
}
|
||||
|
||||
m_FramesIn = m_FramesOut = 0;
|
||||
m_FrameInfoQueue.clear();
|
||||
|
||||
delete m_Pacer;
|
||||
m_Pacer = nullptr;
|
||||
|
||||
@@ -226,7 +248,7 @@ bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params, bool
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly)
|
||||
bool FFmpegVideoDecoder::completeInitialization(const AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly)
|
||||
{
|
||||
// In test-only mode, we should only see test frames
|
||||
SDL_assert(!m_TestOnly || testFrame);
|
||||
@@ -353,7 +375,7 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Test decode failed: %s", errorstring);
|
||||
"Test decode failed (avcodec_send_packet): %s", errorstring);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -370,22 +392,24 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
||||
}
|
||||
}
|
||||
|
||||
if (err < 0) {
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Test decode failed (avcodec_receive_frame): %s", errorstring);
|
||||
av_frame_free(&frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow the renderer to do any validation it wants on this frame
|
||||
if (!m_FrontendRenderer->testRenderFrame(frame)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Render test failed");
|
||||
"Test decode failed (testRenderFrame)");
|
||||
av_frame_free(&frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
if (err < 0) {
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Test decode failed: %s", errorstring);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((params->videoFormat & VIDEO_FORMAT_MASK_H264) &&
|
||||
@@ -400,6 +424,15 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
||||
|
||||
// Tell overlay manager to use this frontend renderer
|
||||
Session::get()->getOverlayManager().setOverlayRenderer(m_FrontendRenderer);
|
||||
|
||||
// Only create the decoder thread when instantiating the decoder for real. It will use APIs from
|
||||
// moonlight-common-c that can only be legally called with an established connection.
|
||||
m_DecoderThread = SDL_CreateThread(FFmpegVideoDecoder::decoderThreadProcThunk, "FFDecoder", (void*)this);
|
||||
if (m_DecoderThread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder thread: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -418,6 +451,16 @@ void FFmpegVideoDecoder::addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst)
|
||||
dst.totalPacerTime += src.totalPacerTime;
|
||||
dst.totalRenderTime += src.totalRenderTime;
|
||||
|
||||
if (!LiGetEstimatedRttInfo(&dst.lastRtt, &dst.lastRttVariance)) {
|
||||
dst.lastRtt = 0;
|
||||
dst.lastRttVariance = 0;
|
||||
}
|
||||
else {
|
||||
// Our logic to determine if RTT is valid depends on us never
|
||||
// getting an RTT of 0. ENet currently ensures RTTs are >= 1.
|
||||
SDL_assert(dst.lastRtt > 0);
|
||||
}
|
||||
|
||||
Uint32 now = SDL_GetTicks();
|
||||
|
||||
// Initialize the measurement start point if this is the first video stat window
|
||||
@@ -482,16 +525,25 @@ void FFmpegVideoDecoder::stringifyVideoStats(VIDEO_STATS& stats, char* output)
|
||||
}
|
||||
|
||||
if (stats.renderedFrames != 0) {
|
||||
char rttString[32];
|
||||
|
||||
if (stats.lastRtt != 0) {
|
||||
sprintf(rttString, "%u ms (variance: %u ms)", stats.lastRtt, stats.lastRttVariance);
|
||||
}
|
||||
else {
|
||||
sprintf(rttString, "N/A");
|
||||
}
|
||||
|
||||
offset += sprintf(&output[offset],
|
||||
"Frames dropped by your network connection: %.2f%%\n"
|
||||
"Frames dropped due to network jitter: %.2f%%\n"
|
||||
"Average receive time: %.2f ms\n"
|
||||
"Average network latency: %s\n"
|
||||
"Average decoding time: %.2f ms\n"
|
||||
"Average frame queue delay: %.2f ms\n"
|
||||
"Average rendering time (including monitor V-sync latency): %.2f ms\n",
|
||||
(float)stats.networkDroppedFrames / stats.totalFrames * 100,
|
||||
(float)stats.pacerDroppedFrames / stats.decodedFrames * 100,
|
||||
(float)stats.totalReassemblyTime / stats.receivedFrames,
|
||||
rttString,
|
||||
(float)stats.totalDecodeTime / stats.decodedFrames,
|
||||
(float)stats.totalPacerTime / stats.renderedFrames,
|
||||
(float)stats.totalRenderTime / stats.renderedFrames);
|
||||
@@ -548,11 +600,11 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
|
||||
// Second pass for our second-tier hwaccel implementations
|
||||
else if (pass == 1) {
|
||||
switch (hwDecodeCfg->device_type) {
|
||||
#ifdef HAVE_CUDA
|
||||
case AV_HWDEVICE_TYPE_CUDA:
|
||||
// CUDA should only be used if all other options fail, since it requires
|
||||
// read-back of frames. This should only be used for the NVIDIA+Wayland case
|
||||
// with VDPAU covering the NVIDIA+X11 scenario.
|
||||
// CUDA should only be used to cover the NVIDIA+Wayland case
|
||||
return new CUDARenderer();
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -563,7 +615,7 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
|
||||
}
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::tryInitializeRenderer(AVCodec* decoder,
|
||||
bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder,
|
||||
PDECODER_PARAMETERS params,
|
||||
const AVCodecHWConfig* hwConfig,
|
||||
std::function<IFFmpegRenderer*()> createRendererFunc)
|
||||
@@ -648,7 +700,7 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(AVCodec* decoder,
|
||||
bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decoderName,
|
||||
PDECODER_PARAMETERS params)
|
||||
{
|
||||
AVCodec* decoder = avcodec_find_decoder_by_name(decoderName);
|
||||
const AVCodec* decoder = avcodec_find_decoder_by_name(decoderName);
|
||||
if (decoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -682,7 +734,11 @@ bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decod
|
||||
#ifdef HAVE_MMAL
|
||||
TRY_PREFERRED_PIXEL_FORMAT(MmalRenderer);
|
||||
#endif
|
||||
TRY_PREFERRED_PIXEL_FORMAT(SdlRenderer);
|
||||
// HACK: Avoid using YUV420P on h264_mmal. It can cause a deadlock inside the MMAL libraries.
|
||||
// Even if it didn't completely deadlock us, the performance would likely be atrocious.
|
||||
if (strcmp(decoderName, "h264_mmal") != 0) {
|
||||
TRY_PREFERRED_PIXEL_FORMAT(SdlRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing prefers any of them. Let's see if anyone will tolerate one.
|
||||
@@ -693,7 +749,10 @@ bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decod
|
||||
#ifdef HAVE_MMAL
|
||||
TRY_SUPPORTED_PIXEL_FORMAT(MmalRenderer);
|
||||
#endif
|
||||
TRY_SUPPORTED_PIXEL_FORMAT(SdlRenderer);
|
||||
// HACK: See comment above
|
||||
if (strcmp(decoderName, "h264_mmal") != 0) {
|
||||
TRY_SUPPORTED_PIXEL_FORMAT(SdlRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it here, we couldn't find anything
|
||||
@@ -748,7 +807,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||
}
|
||||
}
|
||||
|
||||
AVCodec* decoder;
|
||||
const AVCodec* decoder;
|
||||
|
||||
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
@@ -787,7 +846,21 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||
|
||||
// Continue with special non-hwaccel hardware decoders
|
||||
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
QList<const char *> knownAvcCodecs = { "h264_mmal", "h264_rkmpp", "h264_nvmpi", "h264_v4l2m2m" };
|
||||
QList<const char *> knownAvcCodecs = {
|
||||
#ifdef HAVE_MMAL
|
||||
"h264_mmal",
|
||||
#endif
|
||||
"h264_rkmpp",
|
||||
"h264_nvmpi",
|
||||
#ifndef HAVE_MMAL
|
||||
// Only enable V4L2M2M by default on non-MMAL (RPi) builds. The performance
|
||||
// of the V4L2M2M wrapper around MMAL is not enough for 1080p 60 FPS, so we
|
||||
// would rather show the missing hardware acceleration warning when the user
|
||||
// is in Full KMS mode rather than try to use a poorly performing hwaccel.
|
||||
// See discussion on https://github.com/jc-kynesim/rpi-ffmpeg/pull/25
|
||||
"h264_v4l2m2m",
|
||||
#endif
|
||||
};
|
||||
for (const char* codec : knownAvcCodecs) {
|
||||
if (tryInitializeRendererForDecoderByName(codec, params)) {
|
||||
return true;
|
||||
@@ -876,6 +949,119 @@ void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset)
|
||||
}
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::decoderThreadProcThunk(void *context)
|
||||
{
|
||||
((FFmpegVideoDecoder*)context)->decoderThreadProc();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FFmpegVideoDecoder::decoderThreadProc()
|
||||
{
|
||||
while (!SDL_AtomicGet(&m_DecoderThreadShouldQuit)) {
|
||||
if (m_FramesIn == m_FramesOut) {
|
||||
VIDEO_FRAME_HANDLE handle;
|
||||
PDECODE_UNIT du;
|
||||
|
||||
// Waiting for input. All output frames have been received.
|
||||
// Block until we receive a new frame from the host.
|
||||
if (!LiWaitForNextVideoFrame(&handle, &du)) {
|
||||
// This might be a signal from the main thread to exit
|
||||
continue;
|
||||
}
|
||||
|
||||
LiCompleteVideoFrame(handle, submitDecodeUnit(du));
|
||||
}
|
||||
|
||||
if (m_FramesIn != m_FramesOut) {
|
||||
SDL_assert(m_FramesIn > m_FramesOut);
|
||||
|
||||
// We have output frames to receive. Let's poll until we get one,
|
||||
// and submit new input data if/when we get it.
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
// Failed to allocate a frame but we did submit,
|
||||
// so we can return DR_OK
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to allocate frame");
|
||||
continue;
|
||||
}
|
||||
|
||||
int err;
|
||||
do {
|
||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||
if (err == 0) {
|
||||
SDL_assert(m_FrameInfoQueue.size() == m_FramesIn - m_FramesOut);
|
||||
m_FramesOut++;
|
||||
|
||||
// Reset failed decodes count if we reached this far
|
||||
m_ConsecutiveFailedDecodes = 0;
|
||||
|
||||
// Restore default log level after a successful decode
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
|
||||
// Capture a frame timestamp to measuring pacing delay
|
||||
frame->pkt_dts = SDL_GetTicks();
|
||||
|
||||
if (!m_FrameInfoQueue.isEmpty()) {
|
||||
FrameInfoTuple infoTuple = m_FrameInfoQueue.dequeue();
|
||||
|
||||
// Count time in avcodec_send_packet() and avcodec_receive_frame()
|
||||
// as time spent decoding. Also count time spent in the decode unit
|
||||
// queue because that's directly caused by decoder latency.
|
||||
m_ActiveWndVideoStats.totalDecodeTime += LiGetMillis() - infoTuple.enqueueTimeMs;
|
||||
|
||||
// Store the presentation time
|
||||
frame->pts = infoTuple.presentationTimeMs;
|
||||
}
|
||||
|
||||
m_ActiveWndVideoStats.decodedFrames++;
|
||||
|
||||
// Queue the frame for rendering (or render now if pacer is disabled)
|
||||
m_Pacer->submitFrame(frame);
|
||||
}
|
||||
else if (err == AVERROR(EAGAIN)) {
|
||||
VIDEO_FRAME_HANDLE handle;
|
||||
PDECODE_UNIT du;
|
||||
|
||||
// No output data, so let's try to submit more input data,
|
||||
// while we're waiting for this to frame to come back.
|
||||
if (LiPollNextVideoFrame(&handle, &du)) {
|
||||
// FIXME: Handle EAGAIN on avcodec_send_packet() properly?
|
||||
LiCompleteVideoFrame(handle, submitDecodeUnit(du));
|
||||
}
|
||||
else {
|
||||
// No output data or input data. Let's wait a little bit.
|
||||
SDL_Delay(2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"avcodec_receive_frame() failed: %s", errorstring);
|
||||
|
||||
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting decoder due to consistent failure");
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_DEVICE_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
// Don't consume any additional data
|
||||
SDL_AtomicSet(&m_DecoderThreadShouldQuit, 1);
|
||||
}
|
||||
}
|
||||
} while (err == AVERROR(EAGAIN) && !SDL_AtomicGet(&m_DecoderThreadShouldQuit));
|
||||
|
||||
if (err != 0) {
|
||||
// Free the frame if we failed to submit it
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
{
|
||||
PLENTRY entry = du->bufferList;
|
||||
@@ -883,6 +1069,16 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
|
||||
SDL_assert(!m_TestOnly);
|
||||
|
||||
// If this is the first frame, reject anything that's not an IDR frame
|
||||
if (m_FramesIn == 0 && du->frameType != FRAME_TYPE_IDR) {
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// Bail immediately if we need an IDR frame to continue
|
||||
if (Session::get()->getAndClearPendingIdrFrameStatus()) {
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
if (!m_LastFrameNumber) {
|
||||
m_ActiveWndVideoStats.measurementStartTimestamp = SDL_GetTicks();
|
||||
m_LastFrameNumber = du->frameNumber;
|
||||
@@ -962,70 +1158,17 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_DEVICE_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
// Don't consume any additional data
|
||||
SDL_AtomicSet(&m_DecoderThreadShouldQuit, 1);
|
||||
}
|
||||
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
m_FrameInfoQueue.enqueue({ du->enqueueTimeMs, du->presentationTimeMs});
|
||||
|
||||
m_FramesIn++;
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
// Failed to allocate a frame but we did submit,
|
||||
// so we can return DR_OK
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to allocate frame");
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||
if (err == 0) {
|
||||
m_FramesOut++;
|
||||
|
||||
// Reset failed decodes count if we reached this far
|
||||
m_ConsecutiveFailedDecodes = 0;
|
||||
|
||||
// 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->pkt_dts = SDL_GetTicks();
|
||||
|
||||
// Count time in avcodec_send_packet() and avcodec_receive_frame()
|
||||
// as time spent decoding. Also count time spent in the decode unit
|
||||
// queue because that's directly caused by decoder latency.
|
||||
m_ActiveWndVideoStats.totalDecodeTime += LiGetMillis() - du->enqueueTimeMs;
|
||||
|
||||
// Also count the frame-to-frame delay if the decoder is delaying frames
|
||||
// until a subsequent frame is submitted.
|
||||
m_ActiveWndVideoStats.totalDecodeTime += (m_FramesIn - m_FramesOut) * (1000 / m_StreamFps);
|
||||
|
||||
m_ActiveWndVideoStats.decodedFrames++;
|
||||
|
||||
// Queue the frame for rendering (or render now if pacer is disabled)
|
||||
m_Pacer->submitFrame(frame);
|
||||
}
|
||||
else {
|
||||
av_frame_free(&frame);
|
||||
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"avcodec_receive_frame() failed: %s", errorstring);
|
||||
|
||||
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting decoder due to consistent failure");
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_DEVICE_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <QQueue>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "ffmpeg-renderers/renderer.h"
|
||||
@@ -26,7 +27,7 @@ public:
|
||||
virtual IFFmpegRenderer* getBackendRenderer();
|
||||
|
||||
private:
|
||||
bool completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly);
|
||||
bool completeInitialization(const AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly);
|
||||
|
||||
void stringifyVideoStats(VIDEO_STATS& stats, char* output);
|
||||
|
||||
@@ -39,7 +40,7 @@ private:
|
||||
bool tryInitializeRendererForDecoderByName(const char* decoderName,
|
||||
PDECODER_PARAMETERS params);
|
||||
|
||||
bool tryInitializeRenderer(AVCodec* decoder,
|
||||
bool tryInitializeRenderer(const AVCodec* decoder,
|
||||
PDECODER_PARAMETERS params,
|
||||
const AVCodecHWConfig* hwConfig,
|
||||
std::function<IFFmpegRenderer*()> createRendererFunc);
|
||||
@@ -54,6 +55,10 @@ private:
|
||||
enum AVPixelFormat ffGetFormat(AVCodecContext* context,
|
||||
const enum AVPixelFormat* pixFmts);
|
||||
|
||||
void decoderThreadProc();
|
||||
|
||||
static int decoderThreadProcThunk(void* context);
|
||||
|
||||
AVPacket* m_Pkt;
|
||||
AVCodecContext* m_VideoDecoderCtx;
|
||||
QByteArray m_DecodeBuffer;
|
||||
@@ -74,6 +79,14 @@ private:
|
||||
int m_VideoFormat;
|
||||
bool m_NeedsSpsFixup;
|
||||
bool m_TestOnly;
|
||||
SDL_Thread* m_DecoderThread;
|
||||
SDL_atomic_t m_DecoderThreadShouldQuit;
|
||||
|
||||
typedef struct {
|
||||
uint64_t enqueueTimeMs;
|
||||
uint32_t presentationTimeMs;
|
||||
} FrameInfoTuple;
|
||||
QQueue<FrameInfoTuple> m_FrameInfoQueue;
|
||||
|
||||
static const uint8_t k_H264TestFrame[];
|
||||
static const uint8_t k_HEVCMainTestFrame[];
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.1.3
|
||||
3.2.0
|
||||
+11
-11
@@ -4,13 +4,13 @@ clone_depth: 1
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
QTDIR: C:\Qt\5.15
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: macOS
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: macOS-BigSur
|
||||
BUILD_TARGET: macos
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1604
|
||||
BUILD_TARGET: steamlink
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1604
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
|
||||
BUILD_TARGET: linux
|
||||
FFMPEG_CONFIGURE_ARGS: --enable-pic --disable-static --enable-shared --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-nvdec --enable-hwaccel=h264_nvdec --enable-hwaccel=hevc_nvdec --enable-hwaccel=h264_vaapi --enable-hwaccel=hevc_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=hevc_vdpau
|
||||
|
||||
@@ -19,14 +19,14 @@ install:
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || npm install --global create-dmg'
|
||||
- sh: '[ "$BUILD_TARGET" != steamlink ] || sudo apt install -y libc6:i386 libstdc++6:i386'
|
||||
- sh: '[ "$BUILD_TARGET" != steamlink ] || git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-bionic'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt update || true'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt514base qt514quickcontrols2 qt514svg qt514wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_REV=dfa64eadd38a6a8125290dcf121f1bc285e98dad && git clone https://github.com/libsdl-org/SDL.git SDL2 && cd SDL2 && git checkout $SDL2_REV && ./configure --enable-video-kmsdrm && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt515base qt515quickcontrols2 qt515svg qt515wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_REV=a0e3c884d4519d4ae02f5c22c7557759eaea60ca && git clone https://github.com/libsdl-org/SDL.git SDL2 && cd SDL2 && git checkout $SDL2_REV && ./configure --enable-video-kmsdrm && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_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'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.11.0 && git clone https://github.com/intel/libva.git && cd libva && git checkout $LIBVA_VER && ./autogen.sh && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export NVHDR_VER=8.1.24.12 && wget https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVHDR_VER/nv-codec-headers-$NVHDR_VER.tar.gz && tar -xvf nv-codec-headers-$NVHDR_VER.tar.gz && cd nv-codec-headers-$NVHDR_VER && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export FFMPEG_VER=4.3.1 && wget https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VER.tar.bz2 && tar -xvf ffmpeg-$FFMPEG_VER.tar.bz2 && cd ffmpeg-$FFMPEG_VER && ./configure $FFMPEG_CONFIGURE_ARGS && make -j$(nproc) && sudo make install && sudo ldconfig && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.13.0 && git clone https://github.com/intel/libva.git && cd libva && git checkout $LIBVA_VER && ./autogen.sh && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export NVHDR_VER=8.1.24.13 && wget https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVHDR_VER/nv-codec-headers-$NVHDR_VER.tar.gz && tar -xvf nv-codec-headers-$NVHDR_VER.tar.gz && cd nv-codec-headers-$NVHDR_VER && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export FFMPEG_VER=4.4.1 && wget https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VER.tar.bz2 && tar -xvf ffmpeg-$FFMPEG_VER.tar.bz2 && cd ffmpeg-$FFMPEG_VER && ./configure $FFMPEG_CONFIGURE_ARGS && make -j$(nproc) && sudo make install && sudo ldconfig && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then mkdir $HOME/bin && wget -O $HOME/bin/linuxdeployqt https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage && chmod a+x $HOME/bin/linuxdeployqt; fi'
|
||||
|
||||
before_build:
|
||||
@@ -43,9 +43,9 @@ build_script:
|
||||
- cmd: 'set PATH=%OLDPATH%;%QTDIR%\msvc2019\bin;%ProgramFiles(x86)%\WiX Toolset v3.11\bin'
|
||||
- cmd: 'scripts\build-arch.bat Release x86'
|
||||
- cmd: 'scripts\generate-bundle.bat Release'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || source /opt/qt514/bin/qt514-env.sh'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || source /opt/qt515/bin/qt515-env.sh'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || PATH=$PATH:$HOME/bin scripts/build-appimage.sh'
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || PATH=$PATH:$HOME/Qt/5.15/clang_64/bin scripts/generate-dmg.sh Release'
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || PATH=$PATH:$HOME/Qt/6.2/macos/bin scripts/generate-dmg.sh Release'
|
||||
- sh: '[ "$BUILD_TARGET" != steamlink ] || STEAMLINK_SDK_PATH=$HOME/steamlink-sdk scripts/build-steamlink-app.sh'
|
||||
|
||||
after_build:
|
||||
|
||||
+1
-1
Submodule libs updated: c5149747ff...d0ed5c99af
Submodule moonlight-common-c/moonlight-common-c updated: ccaca624f3...cfe75eb569
@@ -19,6 +19,16 @@ CONFIG += warn_off
|
||||
include(../globaldefs.pri)
|
||||
|
||||
win32 {
|
||||
contains(QT_ARCH, i386) {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/x86
|
||||
}
|
||||
contains(QT_ARCH, x86_64) {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/x64
|
||||
}
|
||||
contains(QT_ARCH, arm64) {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include/arm64
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||
}
|
||||
macx {
|
||||
@@ -56,8 +66,8 @@ SOURCES += \
|
||||
$$COMMON_C_DIR/src/Platform.c \
|
||||
$$COMMON_C_DIR/src/PlatformCrypto.c \
|
||||
$$COMMON_C_DIR/src/PlatformSockets.c \
|
||||
$$COMMON_C_DIR/src/RtpFecQueue.c \
|
||||
$$COMMON_C_DIR/src/RtpReorderQueue.c \
|
||||
$$COMMON_C_DIR/src/RtpAudioQueue.c \
|
||||
$$COMMON_C_DIR/src/RtpVideoQueue.c \
|
||||
$$COMMON_C_DIR/src/RtspConnection.c \
|
||||
$$COMMON_C_DIR/src/RtspParser.c \
|
||||
$$COMMON_C_DIR/src/SdpGenerator.c \
|
||||
|
||||
@@ -44,7 +44,7 @@ if /I "%ARCH%" NEQ "x86" (
|
||||
)
|
||||
)
|
||||
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 b28642b756ebec4884d1063dfa4de803a6dcecdc /v
|
||||
|
||||
set BUILD_ROOT=%cd%\build
|
||||
set SOURCE_ROOT=%cd%
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
rm -rf /tmp/ffmpeg
|
||||
mkdir /tmp/ffmpeg
|
||||
./configure --prefix=/tmp/ffmpeg --extra-cflags="-mmacosx-version-min=10.13" --install-name-dir=@loader_path/../Frameworks --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_videotoolbox --enable-hwaccel=hevc_videotoolbox
|
||||
make clean
|
||||
make -j4
|
||||
codesign -s "$SIGNING_IDENTITY" libavcodec/libavcodec.dylib
|
||||
codesign -s "$SIGNING_IDENTITY" libavutil/libavutil.dylib
|
||||
codesign -vv libavcodec/libavcodec.dylib
|
||||
codesign -vv libavutil/libavutil.dylib
|
||||
make install
|
||||
@@ -1,3 +0,0 @@
|
||||
./configure --toolchain=msvc --enable-cross-compile --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_dxva2 --enable-hwaccel=hevc_dxva2 --enable-hwaccel=h264_d3d11va --enable-hwaccel=hevc_d3d11va --enable-hwaccel=h264_d3d11va2 --enable-hwaccel=hevc_d3d11va2
|
||||
make clean
|
||||
make -j4
|
||||
@@ -1,3 +0,0 @@
|
||||
./configure --toolchain=msvc --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_dxva2 --enable-hwaccel=hevc_dxva2 --enable-hwaccel=h264_d3d11va --enable-hwaccel=hevc_d3d11va --enable-hwaccel=h264_d3d11va2 --enable-hwaccel=hevc_d3d11va2
|
||||
make clean
|
||||
make -j4
|
||||
@@ -39,7 +39,7 @@ if /I "%BUILD_CONFIG%"=="debug" (
|
||||
)
|
||||
)
|
||||
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 b28642b756ebec4884d1063dfa4de803a6dcecdc /v
|
||||
|
||||
set BUILD_ROOT=%cd%\build
|
||||
set SOURCE_ROOT=%cd%
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user