Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12b7f6b7a3 | ||
|
|
32e7ddf272 | ||
|
|
7091e0324e | ||
|
|
8f8532921c | ||
|
|
e9936992ec | ||
|
|
6375055b3e | ||
|
|
88b1ea296c | ||
|
|
2a13a869f3 | ||
|
|
89c342bb6f | ||
|
|
067f5e33aa | ||
|
|
33695f1c6b | ||
|
|
52ef84873e | ||
|
|
c21ba5c808 | ||
|
|
6cf408f12b | ||
|
|
8bdc2d285d | ||
|
|
a66fbcd6be | ||
|
|
93402a62fc | ||
|
|
2a56fb6c70 | ||
|
|
8276c031e9 | ||
|
|
102e5f6de9 | ||
|
|
1f7bdbfbcb | ||
|
|
f2fdd048c7 | ||
|
|
5a2cfc15a6 | ||
|
|
11c7131875 | ||
|
|
34aacc90d7 | ||
|
|
e75dce2543 | ||
|
|
e2774309fb | ||
|
|
dfefdc93c5 | ||
|
|
d46ea28bba | ||
|
|
d35c5bcaba | ||
|
|
2b14856d7f | ||
|
|
3316fdb80e | ||
|
|
c5d70c48c9 | ||
|
|
82230ef2f5 | ||
|
|
dcfd3d3e48 | ||
|
|
cb1e2ac32e | ||
|
|
ec2c15946a | ||
|
|
8be73142f8 | ||
|
|
d1640e3bb8 | ||
|
|
8612e6726d | ||
|
|
3b4694e9f8 | ||
|
|
14cb2e1999 | ||
|
|
cfabaf334e | ||
|
|
c054536fab | ||
|
|
35aac18b4e | ||
|
|
de3928cb4d | ||
|
|
9bf612b800 | ||
|
|
d9a8d67e3d | ||
|
|
99b0a56a6a | ||
|
|
61d7aa0400 | ||
|
|
1e98594972 | ||
|
|
c6383f042c | ||
|
|
b4d8e0e551 |
@@ -13,6 +13,9 @@ Any special steps that are required for the bug to appear.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
|
||||
**Affected games**
|
||||
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
||||
|
||||
**Moonlight settings (please complete the following information)**
|
||||
- Have any settings been adjusted from defaults?
|
||||
- If so, which settings have been changed?
|
||||
@@ -22,10 +25,15 @@ If applicable, add screenshots to help explain your problem. If the issue is rel
|
||||
- OS: [e.g. Windows 10 1803]
|
||||
- Moonlight Version: [e.g. v0.2.0]
|
||||
|
||||
**Logs (please attach)**
|
||||
**Server PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1803]
|
||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
||||
- Nvidia GPU driver: [e.g. 417.35]
|
||||
|
||||
**Moonlight Logs (please attach)**
|
||||
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
||||
- On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there.
|
||||
- On Linux, logs print to the terminal when running via `flatpak run com.moonlight_stream.Moonlight`
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
Anything else you think may be relevant to the issue
|
||||
|
||||
+3
-1
@@ -2,4 +2,6 @@
|
||||
|
||||
|
||||
**/.vs/
|
||||
build/
|
||||
build/
|
||||
config.tests/*/.qmake.stash
|
||||
config.tests/*/Makefile
|
||||
|
||||
@@ -34,23 +34,25 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
||||
* WiX Toolset v3.11 or later (only if building installers for non-development PCs)
|
||||
|
||||
### Mac-specific Requirements
|
||||
* macOS El Capitan (10.11) or later
|
||||
* Xcode with High Sierra (10.13) SDK (Mojave SDK not yet supported until Qt 5.12 later this year)
|
||||
* macOS Sierra (10.12) or later
|
||||
* Xcode 10
|
||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||
|
||||
### Linux-specific Requirements
|
||||
* GCC or Clang
|
||||
* Install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel`
|
||||
* Install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
||||
* FFmpeg 4.0 is required to build. If your distro doesn't package FFmpeg 4.0, you can build and install it from source on http://ffmpeg.org/
|
||||
|
||||
### Build Setup Steps
|
||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||
* You may also use Homebrew on macOS or your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
||||
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
||||
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||
3. Open the project in Qt Creator or build from qmake on the command line.
|
||||
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
||||
* For Windows builds, use `scripts\generate-installers.bat`. Execute this script from the root of the repository within a Qt command prompt. Ensure WiX and 7-Zip binary directories are in your `%PATH%`.
|
||||
* For macOS builds, use `scripts/generate-dmg.sh`. Execute this script from the root of the repository and ensure Qt's `bin` folder is in your `$PATH`.
|
||||
* To build from the command line for development use, run `qmake moonlight-qt.pro` then `make debug` or `make release`
|
||||
|
||||
## Contribute
|
||||
1. Fork us
|
||||
|
||||
+2
-2
@@ -23,9 +23,9 @@
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.7.0</string>
|
||||
<string>VERSION</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.7.0</string>
|
||||
<string>VERSION</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight</string>
|
||||
</dict>
|
||||
|
||||
+7
-3
@@ -314,7 +314,11 @@ win32 {
|
||||
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
||||
}
|
||||
macx {
|
||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||
# Create Info.plist in object dir with the correct version string
|
||||
system(cp $$PWD/Info.plist $$OUT_PWD/Info.plist)
|
||||
system(sed -i -e 's/VERSION/$$cat(version.txt)/g' $$OUT_PWD/Info.plist)
|
||||
|
||||
QMAKE_INFO_PLIST = $$OUT_PWD/Info.plist
|
||||
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns SDL_GameControllerDB/gamecontrollerdb.txt
|
||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||
@@ -327,5 +331,5 @@ macx {
|
||||
QMAKE_RPATHDIR += @executable_path/../Frameworks
|
||||
}
|
||||
|
||||
VERSION = 0.7.0
|
||||
DEFINES += VERSION_STR=\\\"0.7.0\\\"
|
||||
VERSION = "$$cat(version.txt)"
|
||||
DEFINES += VERSION_STR=\\\"$$cat(version.txt)\\\"
|
||||
|
||||
@@ -116,7 +116,8 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||
if (currentVer < latestVer) {
|
||||
qDebug() << "Update available";
|
||||
|
||||
emit onUpdateAvailable(updateObj["browser_url"].toString());
|
||||
emit onUpdateAvailable(updateObj["version"].toString(),
|
||||
updateObj["browser_url"].toString());
|
||||
return;
|
||||
}
|
||||
else if (currentVer > latestVer) {
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
signals:
|
||||
void onUpdateAvailable(QString url);
|
||||
void onUpdateAvailable(QString newVersion, QString url);
|
||||
|
||||
private slots:
|
||||
void handleUpdateCheckRequestFinished(QNetworkReply* reply);
|
||||
|
||||
@@ -97,7 +97,7 @@ void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QU
|
||||
|
||||
QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
|
||||
{
|
||||
NvHTTP http(computer->activeAddress);
|
||||
NvHTTP http(computer->activeAddress, computer->serverCert);
|
||||
|
||||
QString cachePath = getFilePathForBoxArt(computer, appId);
|
||||
QImage image;
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
private:
|
||||
bool tryPollComputer(QString address, bool& changed)
|
||||
{
|
||||
NvHTTP http(address);
|
||||
NvHTTP http(address, m_Computer->serverCert);
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
@@ -36,7 +36,7 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
NvComputer newState(address, serverInfo);
|
||||
NvComputer newState(address, serverInfo, QSslCertificate());
|
||||
|
||||
// Ensure the machine that responded is the one we intended to contact
|
||||
if (m_Computer->uuid != newState.uuid) {
|
||||
@@ -52,7 +52,7 @@ private:
|
||||
{
|
||||
Q_ASSERT(m_Computer->activeAddress != nullptr);
|
||||
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
|
||||
QVector<NvApp> appList;
|
||||
|
||||
@@ -218,7 +218,7 @@ void ComputerManager::saveHosts()
|
||||
settings.beginWriteArray(SER_HOSTS);
|
||||
for (int i = 0; i < m_KnownHosts.count(); i++) {
|
||||
settings.setArrayIndex(i);
|
||||
m_KnownHosts[m_KnownHosts.keys()[i]]->serialize(settings);
|
||||
m_KnownHosts.value(m_KnownHosts.keys()[i])->serialize(settings);
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
@@ -266,7 +266,7 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
|
||||
}
|
||||
|
||||
if (m_PollThreads.contains(computer->uuid)) {
|
||||
Q_ASSERT(m_PollThreads[computer->uuid]->isRunning());
|
||||
Q_ASSERT(m_PollThreads.value(computer->uuid)->isRunning());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -324,9 +324,8 @@ public:
|
||||
{
|
||||
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||
|
||||
pollingThread = m_ComputerManager->m_PollThreads[m_Computer->uuid];
|
||||
pollingThread = m_ComputerManager->m_PollThreads.take(m_Computer->uuid);
|
||||
|
||||
m_ComputerManager->m_PollThreads.remove(m_Computer->uuid);
|
||||
m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid);
|
||||
}
|
||||
|
||||
@@ -385,7 +384,7 @@ private:
|
||||
NvPairingManager pairingManager(m_Computer->activeAddress);
|
||||
|
||||
try {
|
||||
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin);
|
||||
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin, m_Computer->serverCert);
|
||||
switch (result)
|
||||
{
|
||||
case NvPairingManager::PairState::PIN_WRONG:
|
||||
@@ -438,7 +437,7 @@ signals:
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
|
||||
try {
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
@@ -538,12 +537,8 @@ signals:
|
||||
void computerStateChanged(NvComputer* computer);
|
||||
|
||||
private:
|
||||
void run()
|
||||
QString fetchServerInfo(NvHTTP& http)
|
||||
{
|
||||
NvHTTP http(m_Address);
|
||||
|
||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
// There's a race condition between GameStream servers reporting presence over
|
||||
@@ -564,14 +559,51 @@ private:
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return serverInfo;
|
||||
} catch (...) {
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(false);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
NvHTTP http(m_Address, QSslCertificate());
|
||||
|
||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
||||
|
||||
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
||||
QString serverInfo = fetchServerInfo(http);
|
||||
if (serverInfo.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NvComputer* newComputer = new NvComputer(m_Address, serverInfo);
|
||||
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
||||
NvComputer* newComputer = new NvComputer(m_Address, serverInfo, QSslCertificate());
|
||||
|
||||
// Check if we have a record of this host UUID to pull the pinned cert
|
||||
NvComputer* existingComputer;
|
||||
{
|
||||
QReadLocker lock(&m_ComputerManager->m_Lock);
|
||||
existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
|
||||
if (existingComputer != nullptr) {
|
||||
http.setServerCert(existingComputer->serverCert);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch serverinfo again over HTTPS with the pinned cert
|
||||
if (existingComputer != nullptr) {
|
||||
serverInfo = fetchServerInfo(http);
|
||||
if (serverInfo.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the polled computer with the HTTPS information
|
||||
NvComputer httpsComputer(m_Address, serverInfo, QSslCertificate());
|
||||
newComputer->update(httpsComputer);
|
||||
}
|
||||
|
||||
// Update addresses depending on the context
|
||||
if (m_Mdns) {
|
||||
@@ -579,7 +611,7 @@ private:
|
||||
|
||||
// Get the WAN IP address using STUN if we're on mDNS
|
||||
quint32 addr;
|
||||
int err = LiFindExternalAddressIP4("stun.stunprotocol.org", 3478, &addr);
|
||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||
if (err == 0) {
|
||||
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
||||
}
|
||||
@@ -591,46 +623,48 @@ private:
|
||||
newComputer->manualAddress = m_Address;
|
||||
}
|
||||
|
||||
// Check if this PC already exists
|
||||
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid];
|
||||
if (existingComputer != nullptr) {
|
||||
// Fold it into the existing PC
|
||||
bool changed = existingComputer->update(*newComputer);
|
||||
delete newComputer;
|
||||
{
|
||||
// Check if this PC already exists
|
||||
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
|
||||
if (existingComputer != nullptr) {
|
||||
// Fold it into the existing PC
|
||||
bool changed = existingComputer->update(*newComputer);
|
||||
delete newComputer;
|
||||
|
||||
// Drop the lock before notifying
|
||||
lock.unlock();
|
||||
// Drop the lock before notifying
|
||||
lock.unlock();
|
||||
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
}
|
||||
|
||||
// Tell our client if something changed
|
||||
if (changed) {
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||
emit computerStateChanged(existingComputer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Store this in our active sets
|
||||
m_ComputerManager->m_KnownHosts[newComputer->uuid] = newComputer;
|
||||
|
||||
// Tell our client if something changed
|
||||
if (changed) {
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||
emit computerStateChanged(existingComputer);
|
||||
// Start polling if enabled (write lock required)
|
||||
m_ComputerManager->startPollingComputer(newComputer);
|
||||
|
||||
// Drop the lock before notifying
|
||||
lock.unlock();
|
||||
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
}
|
||||
|
||||
// Tell our client about this new PC
|
||||
emit computerStateChanged(newComputer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Store this in our active sets
|
||||
m_ComputerManager->m_KnownHosts[newComputer->uuid] = newComputer;
|
||||
|
||||
// Start polling if enabled (write lock required)
|
||||
m_ComputerManager->startPollingComputer(newComputer);
|
||||
|
||||
// Drop the lock before notifying
|
||||
lock.unlock();
|
||||
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
}
|
||||
|
||||
// Tell our client about this new PC
|
||||
emit computerStateChanged(newComputer);
|
||||
}
|
||||
}
|
||||
|
||||
ComputerManager* m_ComputerManager;
|
||||
|
||||
+49
-11
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
#define SER_NAME "hostname"
|
||||
#define SER_UUID "uuid"
|
||||
@@ -10,6 +11,7 @@
|
||||
#define SER_REMOTEADDR "remoteaddress"
|
||||
#define SER_MANUALADDR "manualaddress"
|
||||
#define SER_APPLIST "apps"
|
||||
#define SER_SRVCERT "srvcert"
|
||||
|
||||
#define SER_APPNAME "name"
|
||||
#define SER_APPID "id"
|
||||
@@ -23,6 +25,7 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
||||
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
||||
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
||||
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
||||
|
||||
int appCount = settings.beginReadArray(SER_APPLIST);
|
||||
for (int i = 0; i < appCount; i++) {
|
||||
@@ -61,6 +64,7 @@ void NvComputer::serialize(QSettings& settings)
|
||||
settings.setValue(SER_LOCALADDR, localAddress);
|
||||
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
||||
settings.setValue(SER_MANUALADDR, manualAddress);
|
||||
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
||||
|
||||
// Avoid deleting an existing applist if we couldn't get one
|
||||
if (!appList.isEmpty()) {
|
||||
@@ -84,8 +88,10 @@ void NvComputer::sortAppList()
|
||||
});
|
||||
}
|
||||
|
||||
NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serverCert)
|
||||
{
|
||||
this->serverCert = serverCert;
|
||||
|
||||
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
||||
if (this->name.isEmpty()) {
|
||||
this->name = "UNKNOWN";
|
||||
@@ -167,6 +173,32 @@ bool NvComputer::wake()
|
||||
QVector<QString> addressList = uniqueAddresses();
|
||||
addressList.append("255.255.255.255");
|
||||
|
||||
// Try to broadcast on all available NICs
|
||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
||||
// Ensure the interface is up and skip the loopback adapter
|
||||
if ((nic.flags() & QNetworkInterface::IsUp) == 0 ||
|
||||
(nic.flags() & QNetworkInterface::IsLoopBack) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QHostAddress allNodesMulticast("FF02::1");
|
||||
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
|
||||
// Store the scope ID for this NIC if IPv6 is enabled
|
||||
if (!addr.ip().scopeId().isEmpty()) {
|
||||
allNodesMulticast.setScopeId(addr.ip().scopeId());
|
||||
}
|
||||
|
||||
// Skip IPv6 which doesn't support broadcast
|
||||
if (!addr.broadcast().isNull()) {
|
||||
addressList.append(addr.broadcast().toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!allNodesMulticast.scopeId().isEmpty()) {
|
||||
addressList.append(allNodesMulticast.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Try all unique address strings or host names
|
||||
bool success = false;
|
||||
for (QString& addressString : addressList) {
|
||||
@@ -181,16 +213,14 @@ bool NvComputer::wake()
|
||||
for (QHostAddress& address : hostInfo.addresses()) {
|
||||
QUdpSocket sock;
|
||||
|
||||
// Bind to any address on the correct protocol
|
||||
if (sock.bind(address.protocol() == QUdpSocket::IPv4Protocol ?
|
||||
QHostAddress::AnyIPv4 : QHostAddress::AnyIPv6)) {
|
||||
|
||||
// Send to all ports
|
||||
for (quint16 port : WOL_PORTS) {
|
||||
if (sock.writeDatagram(wolPayload, address, port)) {
|
||||
qInfo().nospace().noquote() << "Send WoL packet to " << name << " via " << address.toString() << ":" << port;
|
||||
success = true;
|
||||
}
|
||||
// Send to all ports
|
||||
for (quint16 port : WOL_PORTS) {
|
||||
if (sock.writeDatagram(wolPayload, address, port)) {
|
||||
qInfo().nospace().noquote() << "Sent WoL packet to " << name << " via " << address.toString() << ":" << port;
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
qWarning() << "Send failed:" << sock.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,6 +285,13 @@ bool NvComputer::update(NvComputer& that)
|
||||
changed = true; \
|
||||
}
|
||||
|
||||
#define ASSIGN_IF_CHANGED_AND_NONNULL(field) \
|
||||
if (!that.field.isNull() && \
|
||||
this->field != that.field) { \
|
||||
this->field = that.field; \
|
||||
changed = true; \
|
||||
}
|
||||
|
||||
ASSIGN_IF_CHANGED(name);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
||||
@@ -269,6 +306,7 @@ bool NvComputer::update(NvComputer& that)
|
||||
ASSIGN_IF_CHANGED(appVersion);
|
||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||
ASSIGN_IF_CHANGED(gpuModel);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
||||
return changed;
|
||||
|
||||
@@ -19,7 +19,7 @@ private:
|
||||
bool pendingQuit;
|
||||
|
||||
public:
|
||||
explicit NvComputer(QString address, QString serverInfo);
|
||||
explicit NvComputer(QString address, QString serverInfo, QSslCertificate serverCert);
|
||||
|
||||
explicit NvComputer(QSettings& settings);
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
QByteArray macAddress;
|
||||
QString name;
|
||||
QString uuid;
|
||||
QSslCertificate serverCert;
|
||||
QVector<NvApp> appList;
|
||||
|
||||
// Synchronization
|
||||
|
||||
+95
-40
@@ -13,9 +13,13 @@
|
||||
#include <QNetworkProxy>
|
||||
|
||||
#define REQUEST_TIMEOUT_MS 5000
|
||||
#define LAUNCH_TIMEOUT_MS 120000
|
||||
#define RESUME_TIMEOUT_MS 30000
|
||||
#define QUIT_TIMEOUT_MS 30000
|
||||
|
||||
NvHTTP::NvHTTP(QString address) :
|
||||
m_Address(address)
|
||||
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
||||
m_Address(address),
|
||||
m_ServerCert(serverCert)
|
||||
{
|
||||
Q_ASSERT(!address.isEmpty());
|
||||
|
||||
@@ -31,6 +35,11 @@ NvHTTP::NvHTTP(QString address) :
|
||||
m_Nam.setProxy(noProxy);
|
||||
}
|
||||
|
||||
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
||||
{
|
||||
m_ServerCert = serverCert;
|
||||
}
|
||||
|
||||
QVector<int>
|
||||
NvHTTP::parseQuad(QString quad)
|
||||
{
|
||||
@@ -73,36 +82,50 @@ NvHTTP::getServerInfo(NvLogLevel logLevel)
|
||||
{
|
||||
QString serverInfo;
|
||||
|
||||
try
|
||||
// Check if we have a pinned cert for this host yet
|
||||
if (!m_ServerCert.isNull())
|
||||
{
|
||||
// Always try HTTPS first, since it properly reports
|
||||
// pairing status (and a few other attributes).
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttps,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true,
|
||||
logLevel);
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
catch (const GfeHttpResponseException& e)
|
||||
{
|
||||
if (e.getStatusCode() == 401)
|
||||
try
|
||||
{
|
||||
// Certificate validation error, fallback to HTTP
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||
// Always try HTTPS first, since it properly reports
|
||||
// pairing status (and a few other attributes).
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttps,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
logLevel);
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
else
|
||||
catch (const GfeHttpResponseException& e)
|
||||
{
|
||||
// Rethrow real errors
|
||||
throw e;
|
||||
if (e.getStatusCode() == 401)
|
||||
{
|
||||
// Certificate validation error, fallback to HTTP
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
logLevel);
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rethrow real errors
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use HTTP prior to pairing
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
logLevel);
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
|
||||
return serverInfo;
|
||||
}
|
||||
@@ -160,7 +183,7 @@ NvHTTP::launchApp(int appId,
|
||||
"&surroundAudioInfo="+getSurroundAudioInfoString(streamConfig->audioConfiguration)+
|
||||
"&remoteControllersBitmap="+QString::number(gamepadMask)+
|
||||
"&gcmap="+QString::number(gamepadMask),
|
||||
false);
|
||||
LAUNCH_TIMEOUT_MS);
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
@@ -180,7 +203,7 @@ NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
|
||||
"rikey="+QString(QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex())+
|
||||
"&rikeyid="+QString::number(riKeyId)+
|
||||
"&surroundAudioInfo="+getSurroundAudioInfoString(streamConfig->audioConfiguration),
|
||||
false);
|
||||
RESUME_TIMEOUT_MS);
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
@@ -193,7 +216,7 @@ NvHTTP::quitApp()
|
||||
openConnectionToString(m_BaseUrlHttps,
|
||||
"cancel",
|
||||
nullptr,
|
||||
false);
|
||||
QUIT_TIMEOUT_MS);
|
||||
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(response);
|
||||
@@ -240,7 +263,7 @@ NvHTTP::getAppList()
|
||||
QString appxml = openConnectionToString(m_BaseUrlHttps,
|
||||
"applist",
|
||||
nullptr,
|
||||
true,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
NvLogLevel::ERROR);
|
||||
verifyResponseStatus(appxml);
|
||||
|
||||
@@ -308,7 +331,7 @@ NvHTTP::getBoxArt(int appId)
|
||||
"appasset",
|
||||
"appid="+QString::number(appId)+
|
||||
"&AssetType=2&AssetIdx=0",
|
||||
true,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
NvLogLevel::VERBOSE);
|
||||
QImage image = QImageReader(reply).read();
|
||||
delete reply;
|
||||
@@ -355,10 +378,10 @@ QString
|
||||
NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout, logLevel);
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, timeoutMs, logLevel);
|
||||
QString ret;
|
||||
|
||||
QTextStream stream(reply);
|
||||
@@ -373,14 +396,18 @@ QNetworkReply*
|
||||
NvHTTP::openConnection(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
// Build a URL for the request
|
||||
QUrl url(baseUrl);
|
||||
url.setPath("/" + command);
|
||||
url.setQuery("uniqueid=" + IdentityManager::get()->getUniqueId() +
|
||||
"&uuid=" + QUuid::createUuid().toRfc4122().toHex() +
|
||||
|
||||
// Use a common UID for Moonlight clients to allow them to quit
|
||||
// games for each other (otherwise GFE gets screwed up and it requires
|
||||
// manual intervention to solve).
|
||||
url.setQuery("uniqueid=0123456789ABCDEF&uuid=" +
|
||||
QUuid::createUuid().toRfc4122().toHex() +
|
||||
((arguments != nullptr) ? ("&" + arguments) : ""));
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
@@ -390,15 +417,28 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
|
||||
QNetworkReply* reply = m_Nam.get(request);
|
||||
|
||||
// Ignore self-signed certificate errors (since GFE uses them)
|
||||
reply->ignoreSslErrors();
|
||||
// Assert that we always have a server cert for HTTPS, since
|
||||
// the request will fail anyway if we do not.
|
||||
Q_ASSERT(!m_ServerCert.isNull() || baseUrl == m_BaseUrlHttp);
|
||||
|
||||
if (!m_ServerCert.isNull()) {
|
||||
// Pin the server certificate received during pairing
|
||||
QList<QSslError> expectedSslErrors;
|
||||
expectedSslErrors.append(QSslError(QSslError::HostNameMismatch, m_ServerCert));
|
||||
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, m_ServerCert));
|
||||
|
||||
// The SecureTransport backend for Qt TLS on macOS throws CertificateUntrusted
|
||||
// instead of SelfSignedCertificate, so we will need to allow that error too.
|
||||
expectedSslErrors.append(QSslError(QSslError::CertificateUntrusted, m_ServerCert));
|
||||
|
||||
reply->ignoreSslErrors(expectedSslErrors);
|
||||
}
|
||||
|
||||
// Run the request with a timeout if requested
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
if (enableTimeout)
|
||||
{
|
||||
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
||||
if (timeoutMs) {
|
||||
QTimer::singleShot(timeoutMs, &loop, SLOT(quit()));
|
||||
}
|
||||
if (logLevel >= NvLogLevel::VERBOSE) {
|
||||
qInfo() << "Executing request:" << url.toString();
|
||||
@@ -424,9 +464,24 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
if (logLevel >= NvLogLevel::ERROR) {
|
||||
qWarning() << command << " request failed with error " << reply->error();
|
||||
}
|
||||
QtNetworkReplyException exception(reply->error(), reply->errorString());
|
||||
delete reply;
|
||||
throw exception;
|
||||
|
||||
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
||||
// This will trigger falling back to HTTP for the serverinfo query
|
||||
// then pairing again to get the updated certificate.
|
||||
GfeHttpResponseException exception(401, "Server certificate mismatch");
|
||||
delete reply;
|
||||
throw exception;
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::OperationCanceledError) {
|
||||
QtNetworkReplyException exception(QNetworkReply::TimeoutError, "Request timed out");
|
||||
delete reply;
|
||||
throw exception;
|
||||
}
|
||||
else {
|
||||
QtNetworkReplyException exception(reply->error(), reply->errorString());
|
||||
delete reply;
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
return reply;
|
||||
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
VERBOSE
|
||||
};
|
||||
|
||||
explicit NvHTTP(QString address);
|
||||
explicit NvHTTP(QString address, QSslCertificate serverCert);
|
||||
|
||||
static
|
||||
int
|
||||
@@ -149,9 +149,11 @@ public:
|
||||
openConnectionToString(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel = NvLogLevel::VERBOSE);
|
||||
|
||||
void setServerCert(QSslCertificate serverCert);
|
||||
|
||||
static
|
||||
QVector<int>
|
||||
parseQuad(QString quad);
|
||||
@@ -186,9 +188,10 @@ private:
|
||||
openConnection(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
int timeoutMs,
|
||||
NvLogLevel logLevel);
|
||||
|
||||
QString m_Address;
|
||||
QNetworkAccessManager m_Nam;
|
||||
QSslCertificate m_ServerCert;
|
||||
};
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#define REQUEST_TIMEOUT_MS 5000
|
||||
|
||||
NvPairingManager::NvPairingManager(QString address) :
|
||||
m_Http(address)
|
||||
m_Http(address, QSslCertificate())
|
||||
{
|
||||
QByteArray cert = IdentityManager::get()->getCertificate();
|
||||
BIO *bio = BIO_new_mem_buf(cert.data(), -1);
|
||||
@@ -161,7 +163,7 @@ NvPairingManager::saltPin(QByteArray salt, QString pin)
|
||||
}
|
||||
|
||||
NvPairingManager::PairState
|
||||
NvPairingManager::pair(QString appVersion, QString pin)
|
||||
NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverCert)
|
||||
{
|
||||
int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
|
||||
qInfo() << "Pairing with server generation:" << serverMajorVersion;
|
||||
@@ -192,7 +194,7 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&phrase=getservercert&salt=" +
|
||||
salt.toHex() + "&clientcert=" + IdentityManager::get()->getCertificate().toHex(),
|
||||
false);
|
||||
0);
|
||||
NvHTTP::verifyResponseStatus(getCert);
|
||||
if (NvHTTP::getXmlString(getCert, "paired") != "1")
|
||||
{
|
||||
@@ -200,26 +202,38 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
QByteArray serverCert = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
||||
if (serverCert == nullptr)
|
||||
QByteArray serverCertStr = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
||||
if (serverCertStr == nullptr)
|
||||
{
|
||||
qCritical() << "Server likely already pairing";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::ALREADY_IN_PROGRESS;
|
||||
}
|
||||
|
||||
serverCert = QSslCertificate(serverCertStr);
|
||||
if (serverCert.isNull()) {
|
||||
Q_ASSERT(!serverCert.isNull());
|
||||
|
||||
qCritical() << "Failed to parse plaincert";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
// Pin this cert for TLS
|
||||
m_Http.setServerCert(serverCert);
|
||||
|
||||
QByteArray randomChallenge = generateRandomBytes(16);
|
||||
QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey);
|
||||
QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&clientchallenge=" +
|
||||
encryptedChallenge.toHex(),
|
||||
true);
|
||||
REQUEST_TIMEOUT_MS);
|
||||
NvHTTP::verifyResponseStatus(challengeXml);
|
||||
if (NvHTTP::getXmlString(challengeXml, "paired") != "1")
|
||||
{
|
||||
qCritical() << "Failed pairing at stage #2";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
@@ -247,12 +261,12 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&serverchallengeresp=" +
|
||||
encryptedChallengeResponseHash.toHex(),
|
||||
true);
|
||||
REQUEST_TIMEOUT_MS);
|
||||
NvHTTP::verifyResponseStatus(respXml);
|
||||
if (NvHTTP::getXmlString(respXml, "paired") != "1")
|
||||
{
|
||||
qCritical() << "Failed pairing at stage #3";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
@@ -262,21 +276,21 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
|
||||
if (!verifySignature(serverSecret,
|
||||
serverSignature,
|
||||
serverCert))
|
||||
serverCertStr))
|
||||
{
|
||||
qCritical() << "MITM detected";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
QByteArray expectedResponseData;
|
||||
expectedResponseData.append(randomChallenge);
|
||||
expectedResponseData.append(getSignatureFromPemCert(serverCert));
|
||||
expectedResponseData.append(getSignatureFromPemCert(serverCertStr));
|
||||
expectedResponseData.append(serverSecret);
|
||||
if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse)
|
||||
{
|
||||
qCritical() << "Incorrect PIN";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::PIN_WRONG;
|
||||
}
|
||||
|
||||
@@ -288,24 +302,24 @@ NvPairingManager::pair(QString appVersion, QString pin)
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&clientpairingsecret=" +
|
||||
clientPairingSecret.toHex(),
|
||||
true);
|
||||
REQUEST_TIMEOUT_MS);
|
||||
NvHTTP::verifyResponseStatus(secretRespXml);
|
||||
if (NvHTTP::getXmlString(secretRespXml, "paired") != "1")
|
||||
{
|
||||
qCritical() << "Failed pairing at stage #4";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
QString pairChallengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttps,
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&phrase=pairchallenge",
|
||||
true);
|
||||
REQUEST_TIMEOUT_MS);
|
||||
NvHTTP::verifyResponseStatus(pairChallengeXml);
|
||||
if (NvHTTP::getXmlString(pairChallengeXml, "paired") != "1")
|
||||
{
|
||||
qCritical() << "Failed pairing at stage #5";
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
|
||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
~NvPairingManager();
|
||||
|
||||
PairState
|
||||
pair(QString appVersion, QString pin);
|
||||
pair(QString appVersion, QString pin, QSslCertificate& serverCert);
|
||||
|
||||
private:
|
||||
QByteArray
|
||||
|
||||
@@ -287,6 +287,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
parser.addToggleOption("mouse-acceleration", "mouse acceleration");
|
||||
parser.addToggleOption("game-optimization", "game optimizations");
|
||||
parser.addToggleOption("audio-on-host", "audio on host PC");
|
||||
parser.addToggleOption("frame-pacing", "frame pacing");
|
||||
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
|
||||
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
|
||||
|
||||
@@ -372,6 +373,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --audio-on-host and --no-audio-on-host options
|
||||
preferences->playAudioOnHost = parser.getToggleOptionValue("audio-on-host", preferences->playAudioOnHost);
|
||||
|
||||
// Resolve --frame-pacing and --no-frame-pacing options
|
||||
preferences->framePacing = parser.getToggleOptionValue("frame-pacing", preferences->framePacing);
|
||||
|
||||
// Resolve --video-codec option
|
||||
if (parser.isSet("video-codec")) {
|
||||
preferences->videoCodecConfig = mapValue(m_VideoCodecMap, parser.getChoiceOptionValue("video-codec"));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<name>Moonlight</name>
|
||||
<summary>Stream games from your NVIDIA GameStream-enabled PC</summary>
|
||||
<summary>Play games remotely from your NVIDIA GameStream-enabled PC</summary>
|
||||
|
||||
<description>
|
||||
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
||||
@@ -33,6 +33,41 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="0.8.1" date="2019-01-08">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Stricter adherence to the specified bitrate when streaming over the Internet</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed audio crackling that impacted certain systems</li>
|
||||
<li>Fixed Wake-on-LAN packets failing to send on some systems</li>
|
||||
<li>Reduced audio latency when resuming a session where audio is already playing</li>
|
||||
<li>Added option to enable the old SDL audio renderer by setting ML_AUDIO=SDL</li>
|
||||
<li>Updated to use the new Qt 5.12 Flatpak runtime</li>
|
||||
<li>Updated to FFmpeg 4.1</li>
|
||||
<li>Minor UI tweaks</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.8.0" date="2018-12-25">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Improved security of pairing - After this update, you must re-pair with your PC!</li>
|
||||
<li>Enabled quitting games started by other Moonlight clients also running this update</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed the video bitrate slider not working above certain values on GFE 3.16</li>
|
||||
<li>Fixed broken Bluetooth audio with PulseAudio</li>
|
||||
<li>Reduced audio latency</li>
|
||||
<li>Fixed some non-gamepad devices triggering the unmapped gamepad dialog box</li>
|
||||
<li>Minor UI tweaks</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.7.0" date="2018-12-05">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
|
||||
+7
-1
@@ -134,7 +134,13 @@ GridView {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
// popup() ensures the menu appears under the mouse cursor
|
||||
appContextMenu.popup()
|
||||
if (appContextMenu.popup) {
|
||||
appContextMenu.popup()
|
||||
}
|
||||
else {
|
||||
// Qt 5.9 doesn't have popup()
|
||||
appContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -192,7 +192,13 @@ GridView {
|
||||
onClicked: {
|
||||
if (!model.addPc) {
|
||||
// popup() ensures the menu appears under the mouse cursor
|
||||
pcContextMenu.popup()
|
||||
if (pcContextMenu.popup) {
|
||||
pcContextMenu.popup()
|
||||
}
|
||||
else {
|
||||
// Qt 5.9 doesn't have popup()
|
||||
pcContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,14 +300,13 @@ Flickable {
|
||||
Label {
|
||||
width: parent.width
|
||||
id: bitrateDesc
|
||||
text: qsTr("Lower bitrate to reduce lag and stuttering. Raise bitrate to increase image quality.")
|
||||
text: qsTr("Lower the bitrate on slower connections. Raise the bitrate to increase image quality.")
|
||||
font.pointSize: 9
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
wheelEnabled: true
|
||||
|
||||
value: prefs.bitrateKbps
|
||||
|
||||
@@ -378,7 +377,7 @@ Flickable {
|
||||
CheckBox {
|
||||
id: vsyncCheck
|
||||
hoverEnabled: true
|
||||
text: "Enable V-Sync"
|
||||
text: "V-Sync"
|
||||
font.pointSize: 12
|
||||
checked: prefs.enableVsync
|
||||
onCheckedChanged: {
|
||||
@@ -390,6 +389,22 @@ Flickable {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: framePacingCheck
|
||||
hoverEnabled: true
|
||||
text: "Frame pacing"
|
||||
font.pointSize: 12
|
||||
enabled: prefs.enableVsync
|
||||
checked: prefs.enableVsync && prefs.framePacing
|
||||
onCheckedChanged: {
|
||||
prefs.framePacing = checked
|
||||
}
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Frame pacing reduces micro-stutter by delaying frames that come in too early"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -235,7 +235,6 @@ ApplicationWindow {
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Update available for Moonlight"
|
||||
|
||||
// Invisible until we get a callback notifying us that
|
||||
// an update is available
|
||||
@@ -243,8 +242,9 @@ ApplicationWindow {
|
||||
|
||||
onClicked: Qt.openUrlExternally(browserUrl);
|
||||
|
||||
function updateAvailable(url)
|
||||
function updateAvailable(version, url)
|
||||
{
|
||||
ToolTip.text = "Update available for Moonlight: Version " + version
|
||||
updateButton.browserUrl = url
|
||||
updateButton.visible = true
|
||||
}
|
||||
|
||||
@@ -136,6 +136,14 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
||||
break;
|
||||
}
|
||||
|
||||
// HACK: Avoid printing this internal Qt warning which seems to print every time we
|
||||
// start our event loop for the QNetworkReply. I have found this warning in many
|
||||
// unrelated bug reports, but none actually tackle this warning itself. It seems to be
|
||||
// new in Qt 5.12.
|
||||
if (msg.startsWith("QNetworkReplyHttpImplPrivate::_q_startOperation was called more than once")) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - Qt %2: %3\n").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 328 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 2.6 KiB |
+1
-69
@@ -1,69 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg3761"
|
||||
viewBox="0 0 680 680"
|
||||
height="680"
|
||||
width="680"
|
||||
version="1.0">
|
||||
<metadata
|
||||
id="metadata3767">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3765" />
|
||||
<g
|
||||
id="g3759"
|
||||
transform="translate(-135,491) scale(0.100000,-0.100000)">
|
||||
<circle
|
||||
id="circle3741"
|
||||
fill="#565C64"
|
||||
r="3400"
|
||||
cy="1505"
|
||||
cx="4755" />
|
||||
<path
|
||||
id="path3743"
|
||||
style="fill:#ffffff"
|
||||
d="M4550 4230 c-582 -48 -1123 -271 -1568 -646 l-84 -70 869 -869 c477 -477 871 -868 876 -868 4 0 6 552 5 1228 l-3 1227 -25 1 c-14 0 -45 -1 -70 -3z" />
|
||||
<path
|
||||
id="path3745"
|
||||
style="fill:#ffffff"
|
||||
d="M4867 4230 c-10 -9 -9 -2453 1 -2453 4 0 398 391 875 868 l869 869 -84 70 c-344 290 -749 492 -1178 587 -166 36 -469 74 -483 59z" />
|
||||
<path
|
||||
id="path3747"
|
||||
style="fill:#ffffff"
|
||||
d="M2673 3275 c-241 -285 -412 -595 -526 -953 -63 -196 -111 -458 -119 -640 l-3 -70 1228 -3 c675 -1 1227 1 1227 5 0 5 -391 399 -868 876 l-869 869 -70 -84z" />
|
||||
<path
|
||||
id="path3749"
|
||||
style="fill:#ffffff"
|
||||
d="M5898 2490 c-478 -477 -868 -871 -868 -876 0 -4 552 -6 1228 -5 l1227 3 -3 70 c-14 326 -124 729 -287 1055 -91 182 -259 430 -387 573 l-43 48 -867 -868z" />
|
||||
<path
|
||||
id="path3751"
|
||||
style="fill:#ffffff"
|
||||
d="M2026 1388 c-7 -11 10 -198 30 -321 76 -486 291 -952 617 -1338 l70 -84 869 869 c477 477 868 871 868 875 0 11 -2448 10 -2454 -1z" />
|
||||
<path
|
||||
id="path3753"
|
||||
style="fill:#ffffff"
|
||||
d="M5030 1390 c0 -5 391 -399 868 -876 l869 -869 70 84 c241 285 412 595 526 953 63 196 111 458 119 640 l3 70 -1227 3 c-676 1 -1228 -1 -1228 -5z" />
|
||||
<path
|
||||
id="path3755"
|
||||
style="fill:#ffffff"
|
||||
d="M3767 359 l-869 -869 84 -70 c285 -241 595 -412 953 -526 196 -63 458 -111 640 -119 l70 -3 3 1228 c1 675 -1 1227 -5 1227 -5 0 -399 -391 -876 -868z" />
|
||||
<path
|
||||
id="path3757"
|
||||
style="fill:#ffffff"
|
||||
d="M4862 0 l3 -1228 70 3 c182 8 444 56 640 119 358 114 668 285 953 526 l84 70 -869 869 c-477 477 -871 868 -876 868 -4 0 -6 -552 -5 -1227z" />
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 256 256" width="256" height="256"><g><circle vector-effect="non-scaling-stroke" cx="128" cy="128" r="128" fill="rgb(86,92,100)"/><circle vector-effect="non-scaling-stroke" cx="128.00000000000003" cy="128.00000000000003" r="96" fill="rgb(255,255,255)"/><path d=" M 137.66 132 L 224 132 L 224 124 L 137.66 124 L 198.71 62.95 L 193.05 57.29 L 132 118.34 L 132 32 L 124 32 L 124 118.34 L 62.95 57.29 L 57.29 62.95 L 118.34 124 L 32 124 L 32 132 L 118.34 132 L 57.29 193.05 L 62.95 198.71 L 124 137.66 L 124 224 L 132 224 L 132 137.66 L 193.05 198.71 L 198.71 193.05 L 137.66 132 Z " fill-rule="evenodd" fill="rgb(86,92,100)"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 820 B |
@@ -23,6 +23,7 @@
|
||||
#define SER_QUITAPPAFTER "quitAppAfter"
|
||||
#define SER_MOUSEACCELERATION "mouseacceleration"
|
||||
#define SER_STARTWINDOWED "startwindowed"
|
||||
#define SER_FRAMEPACING "framepacing"
|
||||
|
||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -47,6 +48,7 @@ void StreamingPreferences::reload()
|
||||
quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool();
|
||||
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
||||
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
||||
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
|
||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||
@@ -76,6 +78,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_QUITAPPAFTER, quitAppAfter);
|
||||
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
||||
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
||||
settings.setValue(SER_FRAMEPACING, framePacing);
|
||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged)
|
||||
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
||||
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
||||
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
|
||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||
@@ -95,6 +96,7 @@ public:
|
||||
bool quitAppAfter;
|
||||
bool mouseAcceleration;
|
||||
bool startWindowed;
|
||||
bool framePacing;
|
||||
AudioConfig audioConfig;
|
||||
VideoCodecConfig videoCodecConfig;
|
||||
VideoDecoderSelection videoDecoderSelection;
|
||||
@@ -116,5 +118,6 @@ signals:
|
||||
void videoDecoderSelectionChanged();
|
||||
void windowModeChanged();
|
||||
void startWindowedChanged();
|
||||
void framePacingChanged();
|
||||
};
|
||||
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
|
||||
#ifdef HAVE_SOUNDIO
|
||||
#include "renderers/soundioaudiorenderer.h"
|
||||
#else
|
||||
#include "renderers/sdl.h"
|
||||
#endif
|
||||
|
||||
#include "renderers/sdl.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
IAudioRenderer* Session::createAudioRenderer()
|
||||
{
|
||||
#ifdef HAVE_SOUNDIO
|
||||
if (qgetenv("ML_AUDIO") == "SDL") {
|
||||
return new SdlAudioRenderer();
|
||||
}
|
||||
|
||||
return new SoundIoAudioRenderer();
|
||||
#else
|
||||
return new SdlAudioRenderer();
|
||||
|
||||
@@ -16,9 +16,4 @@ public:
|
||||
|
||||
private:
|
||||
SDL_AudioDeviceID m_AudioDevice;
|
||||
Uint32 m_ChannelCount;
|
||||
Uint32 m_PendingDrops;
|
||||
Uint32 m_PendingHardDrops;
|
||||
Uint32 m_SampleIndex;
|
||||
Uint32 m_BaselinePendingData;
|
||||
};
|
||||
|
||||
@@ -5,18 +5,8 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define MIN_QUEUED_FRAMES 2
|
||||
#define MAX_QUEUED_FRAMES 4
|
||||
#define STOP_THE_WORLD_LIMIT 20
|
||||
#define DROP_RATIO_DENOM 32
|
||||
|
||||
SdlAudioRenderer::SdlAudioRenderer()
|
||||
: m_AudioDevice(0),
|
||||
m_ChannelCount(0),
|
||||
m_PendingDrops(0),
|
||||
m_PendingHardDrops(0),
|
||||
m_SampleIndex(0),
|
||||
m_BaselinePendingData(0)
|
||||
: m_AudioDevice(0)
|
||||
{
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
@@ -61,27 +51,6 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
||||
have.samples,
|
||||
have.size);
|
||||
|
||||
// SDL counts pending samples in the queued
|
||||
// audio size using the WASAPI backend. This
|
||||
// includes silence, which can throw off our
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
m_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
m_BaselinePendingData = qMax(m_BaselinePendingData, SDL_GetQueuedAudioSize(m_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
}
|
||||
#endif
|
||||
m_BaselinePendingData *= 2;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Baseline pending audio data: %d bytes",
|
||||
m_BaselinePendingData);
|
||||
|
||||
m_ChannelCount = opusConfig->channelCount;
|
||||
m_SampleIndex = 0;
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
|
||||
// Start playback
|
||||
SDL_PauseAudioDevice(m_AudioDevice, 0);
|
||||
|
||||
@@ -102,53 +71,6 @@ SdlAudioRenderer::~SdlAudioRenderer()
|
||||
|
||||
bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||
{
|
||||
m_SampleIndex++;
|
||||
|
||||
Uint32 queuedAudio = SDL_GetQueuedAudioSize(m_AudioDevice);
|
||||
if (queuedAudio > m_BaselinePendingData) {
|
||||
queuedAudio -= m_BaselinePendingData;
|
||||
}
|
||||
else {
|
||||
queuedAudio = 0;
|
||||
}
|
||||
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * m_ChannelCount * sizeof(short));
|
||||
|
||||
// We must check this prior to the below checks to ensure we don't
|
||||
// underflow if framesQueued - m_PendingHardDrops < 0.
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
}
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES, checking first
|
||||
// to ensure we don't underflow.
|
||||
else if (framesQueued > m_PendingHardDrops &&
|
||||
framesQueued - m_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
m_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
m_PendingHardDrops);
|
||||
}
|
||||
// If we're under the stop the world limit, we can drop samples
|
||||
// gracefully over the next little while.
|
||||
else if (framesQueued > m_PendingHardDrops + m_PendingDrops &&
|
||||
framesQueued - m_PendingHardDrops - m_PendingDrops > MAX_QUEUED_FRAMES) {
|
||||
m_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (m_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
m_PendingHardDrops--;
|
||||
return true;
|
||||
}
|
||||
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||
// Normal drops are interspersed with the audio data
|
||||
// to hide the glitches.
|
||||
m_PendingDrops--;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
|
||||
@@ -4,19 +4,30 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// This determines the size of the buffers we'll
|
||||
// get from CoreAudio. Since GFE sends us packets
|
||||
// in 5 ms chunks, we'll give them to the OS in
|
||||
// buffers of the same size. It is also the minimum
|
||||
// size that we will write when called to fill a buffer.
|
||||
// GFE sends us packets in 5 ms chunks
|
||||
const double SoundIoAudioRenderer::k_RawSampleLengthSec = 0.005;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// PulseAudio and ALSA require more than just 5 ms samples
|
||||
// for some reason, so write a minimum of 25 ms each time to
|
||||
// prevent underruns on Bluetooth.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/147
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/157
|
||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = 0.025;
|
||||
#else
|
||||
// This determines the size of the buffers we'll
|
||||
// get from CoreAudio. It is also the minimum
|
||||
// size that we will write when called to fill a buffer.
|
||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = k_RawSampleLengthSec;
|
||||
#endif
|
||||
|
||||
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
||||
: m_OpusChannelCount(0),
|
||||
m_SoundIo(nullptr),
|
||||
m_Device(nullptr),
|
||||
m_OutputStream(nullptr),
|
||||
m_RingBuffer(nullptr),
|
||||
m_Latency(0),
|
||||
m_Errored(false)
|
||||
{
|
||||
|
||||
@@ -24,6 +35,10 @@ SoundIoAudioRenderer::SoundIoAudioRenderer()
|
||||
|
||||
SoundIoAudioRenderer::~SoundIoAudioRenderer()
|
||||
{
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio latency: %f",
|
||||
m_Latency);
|
||||
|
||||
if (m_OutputStream != nullptr) {
|
||||
soundio_outstream_destroy(m_OutputStream);
|
||||
}
|
||||
@@ -159,7 +174,7 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
||||
|
||||
m_OutputStream->format = SoundIoFormatS16NE;
|
||||
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
||||
m_OutputStream->software_latency = k_RawSampleLengthSec;
|
||||
m_OutputStream->software_latency = k_MinSampleLengthSec;
|
||||
m_OutputStream->name = "Moonlight";
|
||||
m_OutputStream->userdata = this;
|
||||
m_OutputStream->error_callback = sioErrorCallback;
|
||||
@@ -220,12 +235,33 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer up to 6 packets of audio (30 ms) to smooth
|
||||
// out network packet delivery jitter
|
||||
int packetsToBuffer;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// PulseAudio and ALSA need the large buffer (see comment on k_MinSampleLengthSec),
|
||||
// so we need a buffer at least double that size to allow packets to arrive
|
||||
// while we're writing to the sink.
|
||||
packetsToBuffer = (int)(k_MinSampleLengthSec / k_RawSampleLengthSec) * 2;
|
||||
#else
|
||||
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
|
||||
// 15 ms buffer seems to be fine for WASAPI
|
||||
packetsToBuffer = 3;
|
||||
}
|
||||
else {
|
||||
// 30 ms buffer on CoreAudio to avoid glitching on macOS
|
||||
packetsToBuffer = 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio buffer size: %d packets",
|
||||
packetsToBuffer);
|
||||
|
||||
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
|
||||
m_OutputStream->bytes_per_sample *
|
||||
m_OpusChannelCount *
|
||||
SAMPLES_PER_FRAME * 6);
|
||||
SAMPLES_PER_FRAME *
|
||||
packetsToBuffer);
|
||||
if (m_RingBuffer == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"soundio_ring_buffer_create() failed");
|
||||
@@ -240,6 +276,11 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
||||
return false;
|
||||
}
|
||||
|
||||
// HACK: For some reason, a constant latency hangs around in the audio pipeline
|
||||
// unless we wait for the audio stream to drain before actually submitting any samples.
|
||||
// This is a gross hack, but it works remarkably well.
|
||||
SDL_Delay(500);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -336,35 +377,12 @@ void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameC
|
||||
// Ensure we always write at least a buffer, even if it's silence, to avoid
|
||||
// busy looping when no audio data is available while libsoundio tries to keep
|
||||
// us from starving the output device.
|
||||
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_RawSampleLengthSec));
|
||||
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_MinSampleLengthSec));
|
||||
frameCountMin = qMin(frameCountMin, frameCountMax);
|
||||
|
||||
// Place an upper-bound on audio stream latency to
|
||||
// avoid accumulating packets in queue-based backends
|
||||
// like WASAPI, ALSA, and PulseAudio.
|
||||
//
|
||||
// This bound was set by testing on several Windows machines.
|
||||
// The highest latency was found on a XPS 9343 running Windows 7
|
||||
// in Steam Big Picture and the 5.1 audio test clip from Fraunhofer.
|
||||
// Track latency on queueing-based backends
|
||||
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
|
||||
double latency;
|
||||
if (soundio_outstream_get_latency(stream, &latency) == SoundIoErrorNone) {
|
||||
if (latency > 0.050) {
|
||||
// If our latency is higher than desired, drop these samples to gracefully lower
|
||||
// the latency without glitching too much. Dropping the whole buffer causes
|
||||
// a much more noticeable glitch. This approach also ensures that we don't
|
||||
// accidentally underflow if the driver/kernel side is delayed and isn't
|
||||
// consuming data fast enough. Dropping a frame at a time and re-evaluating
|
||||
// each time ensures that we'll stop dropping if latency returns to normal.
|
||||
readPtr += framesLeft * stream->bytes_per_sample * me->m_OpusChannelCount;
|
||||
bytesRead += framesLeft * stream->bytes_per_sample * me->m_OpusChannelCount;
|
||||
framesLeft = 0;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Latency exceeded drop cap: %f",
|
||||
latency);
|
||||
}
|
||||
}
|
||||
soundio_outstream_get_latency(stream, &me->m_Latency);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
|
||||
@@ -32,7 +32,9 @@ private:
|
||||
struct SoundIoOutStream* m_OutputStream;
|
||||
struct SoundIoRingBuffer* m_RingBuffer;
|
||||
struct SoundIoChannelLayout m_EffectiveLayout;
|
||||
double m_Latency;
|
||||
bool m_Errored;
|
||||
|
||||
static const double k_RawSampleLengthSec;
|
||||
static const double k_MinSampleLengthSec;
|
||||
};
|
||||
|
||||
@@ -971,7 +971,7 @@ QString SdlInputHandler::getUnmappedGamepads()
|
||||
"Number of axes: %d | Number of buttons: %d | Number of hats: %d",
|
||||
numAxes, numButtons, numHats);
|
||||
|
||||
if (numAxes >= 4 && numButtons >= 8 && numHats <= 1) {
|
||||
if ((numAxes >= 4 && numAxes <= 8) && numButtons >= 8 && numHats <= 1) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Joystick likely to be an unmapped game controller");
|
||||
if (!ret.isEmpty()) {
|
||||
|
||||
@@ -99,12 +99,16 @@ void Session::clLogMessage(const char* format, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define CALL_INITIALIZE(dec) (dec)->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)
|
||||
#define CALL_INITIALIZE(dec) (dec)->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync, enableFramePacing)
|
||||
|
||||
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder)
|
||||
int frameRate, bool enableVsync, bool enableFramePacing, IVideoDecoder*& chosenDecoder)
|
||||
{
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync %s",
|
||||
enableVsync ? "enabled" : "disabled");
|
||||
|
||||
#ifdef HAVE_SLVIDEO
|
||||
chosenDecoder = new SLVideoDecoder();
|
||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
||||
@@ -217,7 +221,7 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
@@ -257,7 +261,7 @@ int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
@@ -593,7 +597,7 @@ private:
|
||||
|
||||
// Perform a best-effort app quit
|
||||
if (shouldQuit) {
|
||||
NvHTTP http(m_Session->m_Computer->activeAddress);
|
||||
NvHTTP http(m_Session->m_Computer->activeAddress, m_Session->m_Computer->serverCert);
|
||||
|
||||
// Logging is already done inside NvHTTP
|
||||
try {
|
||||
@@ -812,7 +816,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
m_Computer->currentGameId == m_App.id);
|
||||
|
||||
try {
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
http.resumeApp(&m_StreamConfig);
|
||||
}
|
||||
@@ -1120,6 +1124,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
enableVsync,
|
||||
enableVsync && m_Preferences->framePacing,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
|
||||
@@ -72,7 +72,8 @@ private:
|
||||
static
|
||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder);
|
||||
int frameRate, bool enableVsync, bool enableFramePacing,
|
||||
IVideoDecoder*& chosenDecoder);
|
||||
|
||||
static
|
||||
void clStageStarting(int stage);
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef struct _VIDEO_STATS {
|
||||
uint32_t pacerDroppedFrames;
|
||||
uint32_t totalReassemblyTime;
|
||||
uint32_t totalDecodeTime;
|
||||
uint32_t totalPacerTime;
|
||||
uint32_t totalRenderTime;
|
||||
float receivedFps;
|
||||
float decodedFps;
|
||||
@@ -32,7 +33,8 @@ public:
|
||||
int width,
|
||||
int height,
|
||||
int frameRate,
|
||||
bool enableVsync) = 0;
|
||||
bool enableVsync,
|
||||
bool enableFramePacing) = 0;
|
||||
virtual bool isHardwareAccelerated() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||
|
||||
@@ -644,9 +644,9 @@ int DXVA2Renderer::getDecoderCapabilities()
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint DXVA2Renderer::getVsyncConstraint()
|
||||
IFFmpegRenderer::FramePacingConstraint DXVA2Renderer::getFramePacingConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
return PACING_ANY;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame)
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
virtual FramePacingConstraint getFramePacingConstraint();
|
||||
|
||||
private:
|
||||
bool initializeDecoder();
|
||||
|
||||
@@ -87,7 +87,6 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
|
||||
if (m_FrameQueue.isEmpty()) {
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
@@ -112,8 +111,11 @@ RenderNextFrame:
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
// Render it
|
||||
// Count time spent in Pacer's queues
|
||||
Uint32 beforeRender = SDL_GetTicks();
|
||||
m_VideoStats->totalPacerTime += beforeRender - frame->pts;
|
||||
|
||||
// Render it
|
||||
m_VsyncRenderer->renderFrameAtVsync(frame);
|
||||
m_VideoStats->totalRenderTime += SDL_GetTicks() - beforeRender;
|
||||
m_VideoStats->renderedFrames++;
|
||||
@@ -122,15 +124,14 @@ RenderNextFrame:
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
||||
{
|
||||
m_MaxVideoFps = maxVideoFps;
|
||||
m_EnableVsync = enableVsync;
|
||||
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
||||
|
||||
if (m_EnableVsync) {
|
||||
if (enablePacing) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Frame pacing in tear-free mode: target %d Hz with %d FPS stream",
|
||||
"Frame pacing active: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
@@ -148,7 +149,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Minimal latency tearing mode: target %d Hz with %d FPS stream",
|
||||
"Frame pacing disabled: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
|
||||
void submitFrame(AVFrame* frame);
|
||||
|
||||
bool initialize(SDL_Window* window, int maxVideoFps, bool enableVsync);
|
||||
bool initialize(SDL_Window* window, int maxVideoFps, bool enablePacing);
|
||||
|
||||
void vsyncCallback(int timeUntilNextVsyncMillis);
|
||||
|
||||
@@ -35,6 +35,5 @@ private:
|
||||
IFFmpegRenderer* m_VsyncRenderer;
|
||||
int m_MaxVideoFps;
|
||||
int m_DisplayFps;
|
||||
bool m_EnableVsync;
|
||||
PVIDEO_STATS m_VideoStats;
|
||||
};
|
||||
|
||||
@@ -8,10 +8,10 @@ extern "C" {
|
||||
|
||||
class IFFmpegRenderer {
|
||||
public:
|
||||
enum VSyncConstraint {
|
||||
VSYNC_FORCE_OFF,
|
||||
VSYNC_FORCE_ON,
|
||||
VSYNC_ANY
|
||||
enum FramePacingConstraint {
|
||||
PACING_FORCE_OFF,
|
||||
PACING_FORCE_ON,
|
||||
PACING_ANY
|
||||
};
|
||||
|
||||
virtual ~IFFmpegRenderer() {}
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) = 0;
|
||||
virtual bool needsTestFrame() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual VSyncConstraint getVsyncConstraint() = 0;
|
||||
virtual FramePacingConstraint getFramePacingConstraint() = 0;
|
||||
};
|
||||
|
||||
class SdlRenderer : public IFFmpegRenderer {
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
virtual FramePacingConstraint getFramePacingConstraint();
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_Renderer;
|
||||
|
||||
@@ -43,9 +43,9 @@ int SdlRenderer::getDecoderCapabilities()
|
||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint SdlRenderer::getVsyncConstraint()
|
||||
IFFmpegRenderer::FramePacingConstraint SdlRenderer::getFramePacingConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
return PACING_ANY;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
|
||||
@@ -177,9 +177,9 @@ VAAPIRenderer::getDecoderCapabilities()
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VAAPIRenderer::getVsyncConstraint()
|
||||
IFFmpegRenderer::FramePacingConstraint VAAPIRenderer::getFramePacingConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
return PACING_ANY;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
virtual FramePacingConstraint getFramePacingConstraint();
|
||||
|
||||
private:
|
||||
int m_WindowSystem;
|
||||
|
||||
@@ -246,9 +246,9 @@ int VDPAURenderer::getDecoderCapabilities()
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VDPAURenderer::getVsyncConstraint()
|
||||
IFFmpegRenderer::FramePacingConstraint VDPAURenderer::getFramePacingConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
return PACING_ANY;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
virtual FramePacingConstraint getFramePacingConstraint();
|
||||
|
||||
private:
|
||||
uint32_t m_VideoWidth, m_VideoHeight;
|
||||
|
||||
@@ -211,12 +211,12 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual IFFmpegRenderer::VSyncConstraint getVsyncConstraint() override
|
||||
virtual IFFmpegRenderer::FramePacingConstraint getFramePacingConstraint() override
|
||||
{
|
||||
// This renderer is inherently tied to V-sync due how we're
|
||||
// rendering with AVSampleBufferDisplay layer. Running without
|
||||
// the V-Sync source leads to massive stuttering.
|
||||
return VSYNC_FORCE_ON;
|
||||
return PACING_FORCE_ON;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -131,27 +131,27 @@ void FFmpegVideoDecoder::reset()
|
||||
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly)
|
||||
int maxFps, bool enableFramePacing, bool testOnly)
|
||||
{
|
||||
auto vsyncConstraint = m_Renderer->getVsyncConstraint();
|
||||
if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_OFF && enableVsync) {
|
||||
auto vsyncConstraint = m_Renderer->getFramePacingConstraint();
|
||||
if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_OFF && enableFramePacing) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully disabled by the active renderer");
|
||||
enableVsync = false;
|
||||
"Frame pacing is forcefully disabled by the active renderer");
|
||||
enableFramePacing = false;
|
||||
}
|
||||
else if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_ON && !enableVsync) {
|
||||
else if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_ON && !enableFramePacing) {
|
||||
// FIXME: This duplicates logic in Session.cpp
|
||||
int displayHz = StreamUtils::getDisplayRefreshRate(window);
|
||||
if (displayHz + 5 >= maxFps) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully enabled by the active renderer");
|
||||
enableVsync = true;
|
||||
"Frame pacing is forcefully enabled by the active renderer");
|
||||
enableFramePacing = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_StreamFps = maxFps;
|
||||
m_Pacer = new Pacer(m_Renderer, &m_ActiveWndVideoStats);
|
||||
if (!m_Pacer->initialize(window, maxFps, enableVsync)) {
|
||||
if (!m_Pacer->initialize(window, maxFps, enableFramePacing)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,6 +258,7 @@ void FFmpegVideoDecoder::addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst)
|
||||
dst.pacerDroppedFrames += src.pacerDroppedFrames;
|
||||
dst.totalReassemblyTime += src.totalReassemblyTime;
|
||||
dst.totalDecodeTime += src.totalDecodeTime;
|
||||
dst.totalPacerTime += src.totalPacerTime;
|
||||
dst.totalRenderTime += src.totalRenderTime;
|
||||
|
||||
Uint32 now = SDL_GetTicks();
|
||||
@@ -311,6 +312,9 @@ void FFmpegVideoDecoder::logVideoStats(VIDEO_STATS& stats, const char* title)
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Average decode time: %.2f ms",
|
||||
(float)stats.totalDecodeTime / stats.decodedFrames);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Average frame pacing delay: %.2f ms",
|
||||
(float)stats.totalPacerTime / stats.renderedFrames);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Average render time: %.2f ms",
|
||||
(float)stats.totalRenderTime / stats.renderedFrames);
|
||||
@@ -359,7 +363,8 @@ bool FFmpegVideoDecoder::initialize(
|
||||
int width,
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync)
|
||||
bool enableVsync,
|
||||
bool enableFramePacing)
|
||||
{
|
||||
AVCodec* decoder;
|
||||
|
||||
@@ -395,7 +400,7 @@ bool FFmpegVideoDecoder::initialize(
|
||||
m_Renderer = new SdlRenderer();
|
||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -412,13 +417,13 @@ bool FFmpegVideoDecoder::initialize(
|
||||
m_HwDecodeCfg = config;
|
||||
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||
if (m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, m_Renderer->needsTestFrame())) {
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, m_Renderer->needsTestFrame())) {
|
||||
if (m_Renderer->needsTestFrame()) {
|
||||
// The test worked, so now let's initialize it for real
|
||||
reset();
|
||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -577,6 +582,9 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
// Reset failed decodes count if we reached this far
|
||||
m_ConsecutiveFailedDecodes = 0;
|
||||
|
||||
// Capture a frame timestamp to measuring pacing delay
|
||||
frame->pts = SDL_GetTicks();
|
||||
|
||||
// Queue the frame for rendering from the main thread
|
||||
SDL_AtomicIncRef(&m_QueuedFrames);
|
||||
queueFrame(frame);
|
||||
|
||||
@@ -18,7 +18,8 @@ public:
|
||||
int width,
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync) override;
|
||||
bool enableVsync,
|
||||
bool enableFramePacing) override;
|
||||
virtual bool isHardwareAccelerated() override;
|
||||
virtual int getDecoderCapabilities() override;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||
@@ -30,7 +31,7 @@ public:
|
||||
private:
|
||||
bool completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly);
|
||||
int maxFps, bool enableFramePacing, bool testOnly);
|
||||
|
||||
void logVideoStats(VIDEO_STATS& stats, const char* title);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ SLVideoDecoder::getDecoderCapabilities()
|
||||
bool
|
||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window*,
|
||||
int videoFormat, int, int, int frameRate, bool)
|
||||
int videoFormat, int, int, int frameRate, bool, bool)
|
||||
{
|
||||
// SLVideo only supports hardware decoding
|
||||
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
|
||||
@@ -15,7 +15,8 @@ public:
|
||||
int width,
|
||||
int height,
|
||||
int frameRate,
|
||||
bool enableVsync);
|
||||
bool enableVsync,
|
||||
bool enableFramePacing);
|
||||
virtual bool isHardwareAccelerated();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0.8.1
|
||||
@@ -409,6 +409,7 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_DXV,
|
||||
AV_CODEC_ID_SCREENPRESSO,
|
||||
AV_CODEC_ID_RSCC,
|
||||
AV_CODEC_ID_AVS2,
|
||||
|
||||
AV_CODEC_ID_Y41P = 0x8000,
|
||||
AV_CODEC_ID_AVRP,
|
||||
@@ -446,6 +447,11 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_SVG,
|
||||
AV_CODEC_ID_GDV,
|
||||
AV_CODEC_ID_FITS,
|
||||
AV_CODEC_ID_IMM4,
|
||||
AV_CODEC_ID_PROSUMER,
|
||||
AV_CODEC_ID_MWSC,
|
||||
AV_CODEC_ID_WCMV,
|
||||
AV_CODEC_ID_RASC,
|
||||
|
||||
/* various PCM "codecs" */
|
||||
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
|
||||
@@ -485,6 +491,7 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_PCM_S64BE,
|
||||
AV_CODEC_ID_PCM_F16LE,
|
||||
AV_CODEC_ID_PCM_F24LE,
|
||||
AV_CODEC_ID_PCM_VIDC,
|
||||
|
||||
/* various ADPCM codecs */
|
||||
AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
|
||||
@@ -1359,6 +1366,12 @@ enum AVPacketSideDataType {
|
||||
*/
|
||||
AV_PKT_DATA_ENCRYPTION_INFO,
|
||||
|
||||
/**
|
||||
* Active Format Description data consisting of a single byte as specified
|
||||
* in ETSI TS 101 154 using AVActiveFormatDescription enum.
|
||||
*/
|
||||
AV_PKT_DATA_AFD,
|
||||
|
||||
/**
|
||||
* The number of side data types.
|
||||
* This is not part of the public API/ABI in the sense that it may
|
||||
@@ -1614,6 +1627,7 @@ typedef struct AVCodecContext {
|
||||
* The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger
|
||||
* than extradata_size to avoid problems if it is read with the bitstream reader.
|
||||
* The bytewise contents of extradata must not depend on the architecture or CPU endianness.
|
||||
* Must be allocated with the av_malloc() family of functions.
|
||||
* - encoding: Set/allocated/freed by libavcodec.
|
||||
* - decoding: Set/allocated/freed by user.
|
||||
*/
|
||||
@@ -5768,6 +5782,7 @@ typedef struct AVBitStreamFilter {
|
||||
int (*init)(AVBSFContext *ctx);
|
||||
int (*filter)(AVBSFContext *ctx, AVPacket *pkt);
|
||||
void (*close)(AVBSFContext *ctx);
|
||||
void (*flush)(AVBSFContext *ctx);
|
||||
} AVBitStreamFilter;
|
||||
|
||||
#if FF_API_OLD_BSF
|
||||
@@ -5894,6 +5909,11 @@ int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt);
|
||||
*/
|
||||
int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt);
|
||||
|
||||
/**
|
||||
* Reset the internal bitstream filter state / flush internal buffers.
|
||||
*/
|
||||
void av_bsf_flush(AVBSFContext *ctx);
|
||||
|
||||
/**
|
||||
* Free a bitstream filter context and everything associated with it; write NULL
|
||||
* into the supplied pointer.
|
||||
|
||||
@@ -85,4 +85,17 @@ typedef struct MediaCodecBuffer AVMediaCodecBuffer;
|
||||
*/
|
||||
int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render);
|
||||
|
||||
/**
|
||||
* Release a MediaCodec buffer and render it at the given time to the surface
|
||||
* that is associated with the decoder. The timestamp must be within one second
|
||||
* of the current java/lang/System#nanoTime() (which is implemented using
|
||||
* CLOCK_MONOTONIC on Android). See the Android MediaCodec documentation
|
||||
* of android/media/MediaCodec#releaseOutputBuffer(int,long) for more details.
|
||||
*
|
||||
* @param buffer the buffer to render
|
||||
* @param time timestamp in nanoseconds of when to render the buffer
|
||||
* @return 0 on success, < 0 otherwise
|
||||
*/
|
||||
int av_mediacodec_render_buffer_at_time(AVMediaCodecBuffer *buffer, int64_t time);
|
||||
|
||||
#endif /* AVCODEC_MEDIACODEC_H */
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include "libavutil/version.h"
|
||||
|
||||
#define LIBAVCODEC_VERSION_MAJOR 58
|
||||
#define LIBAVCODEC_VERSION_MINOR 21
|
||||
#define LIBAVCODEC_VERSION_MICRO 104
|
||||
#define LIBAVCODEC_VERSION_MINOR 35
|
||||
#define LIBAVCODEC_VERSION_MICRO 100
|
||||
|
||||
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
|
||||
LIBAVCODEC_VERSION_MINOR, \
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Assert that floating point opperations can be executed.
|
||||
* Assert that floating point operations can be executed.
|
||||
*
|
||||
* This will av_assert0() that the cpu is not in MMX state on X86
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated by version.sh, do not manually edit! */
|
||||
#ifndef AVUTIL_FFVERSION_H
|
||||
#define AVUTIL_FFVERSION_H
|
||||
#define FFMPEG_VERSION "N-91434-gcced03dd66"
|
||||
#define FFMPEG_VERSION "4.1"
|
||||
#endif /* AVUTIL_FFVERSION_H */
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
* allocated buffer or map it with mmap() when available.
|
||||
* In case of success set *bufptr to the read or mmapped buffer, and
|
||||
* *size to the size in bytes of the buffer in *bufptr.
|
||||
* Unlike mmap this function succeeds with zero sized files, in this
|
||||
* case *bufptr will be set to NULL and *size will be set to 0.
|
||||
* The returned buffer must be released with av_file_unmap().
|
||||
*
|
||||
* @param log_offset loglevel offset used for logging
|
||||
|
||||
@@ -158,6 +158,14 @@ enum AVFrameSideDataType {
|
||||
*/
|
||||
AV_FRAME_DATA_QP_TABLE_DATA,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Timecode which conforms to SMPTE ST 12-1. The data is an array of 4 uint32_t
|
||||
* where the first uint32_t describes how many (1-3) of the other timecodes are used.
|
||||
* The timecode format is described in the av_timecode_get_smpte_from_framenum()
|
||||
* function in libavutil/timecode.c.
|
||||
*/
|
||||
AV_FRAME_DATA_S12M_TIMECODE,
|
||||
};
|
||||
|
||||
enum AVActiveFormatDescription {
|
||||
|
||||
@@ -343,7 +343,13 @@ char *av_get_pix_fmt_string(char *buf, int buf_size,
|
||||
* format writes the values corresponding to the palette
|
||||
* component c in data[1] to dst, rather than the palette indexes in
|
||||
* data[0]. The behavior is undefined if the format is not paletted.
|
||||
* @param dst_element_size size of elements in dst array (2 or 4 byte)
|
||||
*/
|
||||
void av_read_image_line2(void *dst, const uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int read_pal_component,
|
||||
int dst_element_size);
|
||||
|
||||
void av_read_image_line(uint16_t *dst, const uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int read_pal_component);
|
||||
@@ -361,7 +367,12 @@ void av_read_image_line(uint16_t *dst, const uint8_t *data[4],
|
||||
* @param y the vertical coordinate of the first pixel to write
|
||||
* @param w the width of the line to write, that is the number of
|
||||
* values to write to the image line
|
||||
* @param src_element_size size of elements in src array (2 or 4 byte)
|
||||
*/
|
||||
void av_write_image_line2(const void *src, uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int src_element_size);
|
||||
|
||||
void av_write_image_line(const uint16_t *src, uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w);
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
* This is stored as BGRA on little-endian CPU architectures and ARGB on
|
||||
* big-endian CPUs.
|
||||
*
|
||||
* @note
|
||||
* If the resolution is not a multiple of the chroma subsampling factor
|
||||
* then the chroma plane resolution must be rounded up.
|
||||
*
|
||||
* @par
|
||||
* When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized
|
||||
* image data is stored in AVFrame.data[0]. The palette is transported in
|
||||
@@ -333,6 +337,9 @@ enum AVPixelFormat {
|
||||
AV_PIX_FMT_GRAY14BE, ///< Y , 14bpp, big-endian
|
||||
AV_PIX_FMT_GRAY14LE, ///< Y , 14bpp, little-endian
|
||||
|
||||
AV_PIX_FMT_GRAYF32BE, ///< IEEE-754 single precision Y, 32bpp, big-endian
|
||||
AV_PIX_FMT_GRAYF32LE, ///< IEEE-754 single precision Y, 32bpp, little-endian
|
||||
|
||||
AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions
|
||||
};
|
||||
|
||||
@@ -401,6 +408,8 @@ enum AVPixelFormat {
|
||||
#define AV_PIX_FMT_GBRPF32 AV_PIX_FMT_NE(GBRPF32BE, GBRPF32LE)
|
||||
#define AV_PIX_FMT_GBRAPF32 AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
|
||||
|
||||
#define AV_PIX_FMT_GRAYF32 AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
|
||||
|
||||
#define AV_PIX_FMT_YUVA420P9 AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
|
||||
#define AV_PIX_FMT_YUVA422P9 AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
|
||||
#define AV_PIX_FMT_YUVA444P9 AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
*/
|
||||
|
||||
#define LIBAVUTIL_VERSION_MAJOR 56
|
||||
#define LIBAVUTIL_VERSION_MINOR 18
|
||||
#define LIBAVUTIL_VERSION_MICRO 102
|
||||
#define LIBAVUTIL_VERSION_MINOR 22
|
||||
#define LIBAVUTIL_VERSION_MICRO 100
|
||||
|
||||
#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
||||
LIBAVUTIL_VERSION_MINOR, \
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -409,6 +409,7 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_DXV,
|
||||
AV_CODEC_ID_SCREENPRESSO,
|
||||
AV_CODEC_ID_RSCC,
|
||||
AV_CODEC_ID_AVS2,
|
||||
|
||||
AV_CODEC_ID_Y41P = 0x8000,
|
||||
AV_CODEC_ID_AVRP,
|
||||
@@ -446,6 +447,11 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_SVG,
|
||||
AV_CODEC_ID_GDV,
|
||||
AV_CODEC_ID_FITS,
|
||||
AV_CODEC_ID_IMM4,
|
||||
AV_CODEC_ID_PROSUMER,
|
||||
AV_CODEC_ID_MWSC,
|
||||
AV_CODEC_ID_WCMV,
|
||||
AV_CODEC_ID_RASC,
|
||||
|
||||
/* various PCM "codecs" */
|
||||
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
|
||||
@@ -485,6 +491,7 @@ enum AVCodecID {
|
||||
AV_CODEC_ID_PCM_S64BE,
|
||||
AV_CODEC_ID_PCM_F16LE,
|
||||
AV_CODEC_ID_PCM_F24LE,
|
||||
AV_CODEC_ID_PCM_VIDC,
|
||||
|
||||
/* various ADPCM codecs */
|
||||
AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
|
||||
@@ -1359,6 +1366,12 @@ enum AVPacketSideDataType {
|
||||
*/
|
||||
AV_PKT_DATA_ENCRYPTION_INFO,
|
||||
|
||||
/**
|
||||
* Active Format Description data consisting of a single byte as specified
|
||||
* in ETSI TS 101 154 using AVActiveFormatDescription enum.
|
||||
*/
|
||||
AV_PKT_DATA_AFD,
|
||||
|
||||
/**
|
||||
* The number of side data types.
|
||||
* This is not part of the public API/ABI in the sense that it may
|
||||
@@ -1614,6 +1627,7 @@ typedef struct AVCodecContext {
|
||||
* The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger
|
||||
* than extradata_size to avoid problems if it is read with the bitstream reader.
|
||||
* The bytewise contents of extradata must not depend on the architecture or CPU endianness.
|
||||
* Must be allocated with the av_malloc() family of functions.
|
||||
* - encoding: Set/allocated/freed by libavcodec.
|
||||
* - decoding: Set/allocated/freed by user.
|
||||
*/
|
||||
@@ -5768,6 +5782,7 @@ typedef struct AVBitStreamFilter {
|
||||
int (*init)(AVBSFContext *ctx);
|
||||
int (*filter)(AVBSFContext *ctx, AVPacket *pkt);
|
||||
void (*close)(AVBSFContext *ctx);
|
||||
void (*flush)(AVBSFContext *ctx);
|
||||
} AVBitStreamFilter;
|
||||
|
||||
#if FF_API_OLD_BSF
|
||||
@@ -5894,6 +5909,11 @@ int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt);
|
||||
*/
|
||||
int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt);
|
||||
|
||||
/**
|
||||
* Reset the internal bitstream filter state / flush internal buffers.
|
||||
*/
|
||||
void av_bsf_flush(AVBSFContext *ctx);
|
||||
|
||||
/**
|
||||
* Free a bitstream filter context and everything associated with it; write NULL
|
||||
* into the supplied pointer.
|
||||
|
||||
@@ -85,4 +85,17 @@ typedef struct MediaCodecBuffer AVMediaCodecBuffer;
|
||||
*/
|
||||
int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render);
|
||||
|
||||
/**
|
||||
* Release a MediaCodec buffer and render it at the given time to the surface
|
||||
* that is associated with the decoder. The timestamp must be within one second
|
||||
* of the current java/lang/System#nanoTime() (which is implemented using
|
||||
* CLOCK_MONOTONIC on Android). See the Android MediaCodec documentation
|
||||
* of android/media/MediaCodec#releaseOutputBuffer(int,long) for more details.
|
||||
*
|
||||
* @param buffer the buffer to render
|
||||
* @param time timestamp in nanoseconds of when to render the buffer
|
||||
* @return 0 on success, < 0 otherwise
|
||||
*/
|
||||
int av_mediacodec_render_buffer_at_time(AVMediaCodecBuffer *buffer, int64_t time);
|
||||
|
||||
#endif /* AVCODEC_MEDIACODEC_H */
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include "libavutil/version.h"
|
||||
|
||||
#define LIBAVCODEC_VERSION_MAJOR 58
|
||||
#define LIBAVCODEC_VERSION_MINOR 21
|
||||
#define LIBAVCODEC_VERSION_MICRO 104
|
||||
#define LIBAVCODEC_VERSION_MINOR 35
|
||||
#define LIBAVCODEC_VERSION_MICRO 100
|
||||
|
||||
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
|
||||
LIBAVCODEC_VERSION_MINOR, \
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Assert that floating point opperations can be executed.
|
||||
* Assert that floating point operations can be executed.
|
||||
*
|
||||
* This will av_assert0() that the cpu is not in MMX state on X86
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated by version.sh, do not manually edit! */
|
||||
#ifndef AVUTIL_FFVERSION_H
|
||||
#define AVUTIL_FFVERSION_H
|
||||
#define FFMPEG_VERSION "N-91434-gcced03dd66"
|
||||
#define FFMPEG_VERSION "4.1"
|
||||
#endif /* AVUTIL_FFVERSION_H */
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
* allocated buffer or map it with mmap() when available.
|
||||
* In case of success set *bufptr to the read or mmapped buffer, and
|
||||
* *size to the size in bytes of the buffer in *bufptr.
|
||||
* Unlike mmap this function succeeds with zero sized files, in this
|
||||
* case *bufptr will be set to NULL and *size will be set to 0.
|
||||
* The returned buffer must be released with av_file_unmap().
|
||||
*
|
||||
* @param log_offset loglevel offset used for logging
|
||||
|
||||
@@ -158,6 +158,14 @@ enum AVFrameSideDataType {
|
||||
*/
|
||||
AV_FRAME_DATA_QP_TABLE_DATA,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Timecode which conforms to SMPTE ST 12-1. The data is an array of 4 uint32_t
|
||||
* where the first uint32_t describes how many (1-3) of the other timecodes are used.
|
||||
* The timecode format is described in the av_timecode_get_smpte_from_framenum()
|
||||
* function in libavutil/timecode.c.
|
||||
*/
|
||||
AV_FRAME_DATA_S12M_TIMECODE,
|
||||
};
|
||||
|
||||
enum AVActiveFormatDescription {
|
||||
|
||||
@@ -343,7 +343,13 @@ char *av_get_pix_fmt_string(char *buf, int buf_size,
|
||||
* format writes the values corresponding to the palette
|
||||
* component c in data[1] to dst, rather than the palette indexes in
|
||||
* data[0]. The behavior is undefined if the format is not paletted.
|
||||
* @param dst_element_size size of elements in dst array (2 or 4 byte)
|
||||
*/
|
||||
void av_read_image_line2(void *dst, const uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int read_pal_component,
|
||||
int dst_element_size);
|
||||
|
||||
void av_read_image_line(uint16_t *dst, const uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int read_pal_component);
|
||||
@@ -361,7 +367,12 @@ void av_read_image_line(uint16_t *dst, const uint8_t *data[4],
|
||||
* @param y the vertical coordinate of the first pixel to write
|
||||
* @param w the width of the line to write, that is the number of
|
||||
* values to write to the image line
|
||||
* @param src_element_size size of elements in src array (2 or 4 byte)
|
||||
*/
|
||||
void av_write_image_line2(const void *src, uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w, int src_element_size);
|
||||
|
||||
void av_write_image_line(const uint16_t *src, uint8_t *data[4],
|
||||
const int linesize[4], const AVPixFmtDescriptor *desc,
|
||||
int x, int y, int c, int w);
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
* This is stored as BGRA on little-endian CPU architectures and ARGB on
|
||||
* big-endian CPUs.
|
||||
*
|
||||
* @note
|
||||
* If the resolution is not a multiple of the chroma subsampling factor
|
||||
* then the chroma plane resolution must be rounded up.
|
||||
*
|
||||
* @par
|
||||
* When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized
|
||||
* image data is stored in AVFrame.data[0]. The palette is transported in
|
||||
@@ -333,6 +337,9 @@ enum AVPixelFormat {
|
||||
AV_PIX_FMT_GRAY14BE, ///< Y , 14bpp, big-endian
|
||||
AV_PIX_FMT_GRAY14LE, ///< Y , 14bpp, little-endian
|
||||
|
||||
AV_PIX_FMT_GRAYF32BE, ///< IEEE-754 single precision Y, 32bpp, big-endian
|
||||
AV_PIX_FMT_GRAYF32LE, ///< IEEE-754 single precision Y, 32bpp, little-endian
|
||||
|
||||
AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions
|
||||
};
|
||||
|
||||
@@ -401,6 +408,8 @@ enum AVPixelFormat {
|
||||
#define AV_PIX_FMT_GBRPF32 AV_PIX_FMT_NE(GBRPF32BE, GBRPF32LE)
|
||||
#define AV_PIX_FMT_GBRAPF32 AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
|
||||
|
||||
#define AV_PIX_FMT_GRAYF32 AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
|
||||
|
||||
#define AV_PIX_FMT_YUVA420P9 AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
|
||||
#define AV_PIX_FMT_YUVA422P9 AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
|
||||
#define AV_PIX_FMT_YUVA444P9 AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
*/
|
||||
|
||||
#define LIBAVUTIL_VERSION_MAJOR 56
|
||||
#define LIBAVUTIL_VERSION_MINOR 18
|
||||
#define LIBAVUTIL_VERSION_MICRO 102
|
||||
#define LIBAVUTIL_VERSION_MINOR 22
|
||||
#define LIBAVUTIL_VERSION_MICRO 100
|
||||
|
||||
#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
||||
LIBAVUTIL_VERSION_MINOR, \
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule moonlight-common-c/moonlight-common-c updated: 80eba25bb9...f884003066
@@ -1,3 +1,7 @@
|
||||
./configure --extra-cflags="-mmacosx-version-min=10.10" --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_videotoolbox --enable-hwaccel=hevc_videotoolbox
|
||||
./configure --extra-cflags="-mmacosx-version-min=10.11" --install-name-dir=@loader_path/../lib --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
|
||||
make -j4
|
||||
codesign -s 45U78722YL libavcodec/libavcodec.dylib
|
||||
codesign -s 45U78722YL libavutil/libavutil.dylib
|
||||
codesign -vv libavcodec/libavcodec.dylib
|
||||
codesign -vv libavutil/libavutil.dylib
|
||||
Regular → Executable
Regular → Executable
@@ -11,10 +11,13 @@ if [ "$BUILD_CONFIG" != "Debug" ] && [ "$BUILD_CONFIG" != "Release" ]; then
|
||||
fail "Invalid build configuration"
|
||||
fi
|
||||
|
||||
[ "$SIGNING_KEY" == "" ] || git diff-index --quiet HEAD -- || fail "Signed release builds must not have unstaged changes!"
|
||||
|
||||
BUILD_ROOT=$PWD/build
|
||||
SOURCE_ROOT=$PWD
|
||||
BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG
|
||||
INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG
|
||||
VERSION=`cat $SOURCE_ROOT/app/version.txt`
|
||||
|
||||
echo Cleaning output directories
|
||||
rm -rf $BUILD_FOLDER
|
||||
@@ -35,8 +38,8 @@ popd
|
||||
|
||||
echo Saving dSYM file
|
||||
pushd $BUILD_FOLDER
|
||||
dsymutil app/Moonlight.app/Contents/MacOS/Moonlight -o Moonlight.dsym || fail "dSYM creation failed!"
|
||||
cp -R Moonlight.dsym $INSTALLER_FOLDER || fail "dSYM copy failed!"
|
||||
dsymutil app/Moonlight.app/Contents/MacOS/Moonlight -o Moonlight-$VERSION.dsym || fail "dSYM creation failed!"
|
||||
cp -R Moonlight-$VERSION.dsym $INSTALLER_FOLDER || fail "dSYM copy failed!"
|
||||
popd
|
||||
|
||||
echo Copying dylib dependencies
|
||||
@@ -52,3 +55,4 @@ macdeployqt $BUILD_FOLDER/app/Moonlight.app $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app
|
||||
|
||||
echo Creating DMG
|
||||
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER || fail "create-dmg failed!"
|
||||
mv $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg $INSTALLER_FOLDER/Moonlight-$VERSION.dmg
|
||||
@@ -17,6 +17,13 @@ if /I "%BUILD_CONFIG%"=="debug" (
|
||||
set BUILD_CONFIG=release
|
||||
set SIGN=1
|
||||
set MUST_DEPLOY_SYMBOLS=1
|
||||
|
||||
rem Fail if there are unstaged changes
|
||||
git diff-index --quiet HEAD --
|
||||
if !ERRORLEVEL! NEQ 0 (
|
||||
echo Signed release builds must not have unstaged changes!
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo Invalid build configuration
|
||||
exit /b 1
|
||||
@@ -43,6 +50,7 @@ set BUILD_FOLDER=%BUILD_ROOT%\build-%ARCH%-%BUILD_CONFIG%
|
||||
set DEPLOY_FOLDER=%BUILD_ROOT%\deploy-%ARCH%-%BUILD_CONFIG%
|
||||
set INSTALLER_FOLDER=%BUILD_ROOT%\installer-%ARCH%-%BUILD_CONFIG%
|
||||
set SYMBOLS_FOLDER=%BUILD_ROOT%\symbols-%ARCH%-%BUILD_CONFIG%
|
||||
set /p VERSION=<%SOURCE_ROOT%\app\version.txt
|
||||
|
||||
echo Cleaning output directories
|
||||
rmdir /s /q %DEPLOY_FOLDER%
|
||||
@@ -74,7 +82,7 @@ for /r "%BUILD_FOLDER%" %%f in (*.pdb) do (
|
||||
)
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.pdb %SYMBOLS_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip %SYMBOLS_FOLDER%\*.pdb
|
||||
7z a %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols-%ARCH%-%VERSION%.zip %SYMBOLS_FOLDER%\*.pdb
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%ML_SYMBOL_STORE%" NEQ "" (
|
||||
@@ -88,6 +96,17 @@ if "%ML_SYMBOL_STORE%" NEQ "" (
|
||||
)
|
||||
)
|
||||
|
||||
if "%ML_SYMBOL_ARCHIVE%" NEQ "" (
|
||||
echo Copying PDB ZIP to symbol archive: %ML_SYMBOL_ARCHIVE%
|
||||
copy %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols-%ARCH%-%VERSION%.zip %ML_SYMBOL_ARCHIVE%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
) else (
|
||||
if "%MUST_DEPLOY_SYMBOLS%"=="1" (
|
||||
echo "A symbol archive directory must be specified in ML_SYMBOL_ARCHIVE for signed release builds"
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo Copying DLL dependencies
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.dll %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
@@ -150,6 +169,9 @@ if "%SIGN%"=="1" (
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
rem Rename the installer to match the publishing convention
|
||||
ren %INSTALLER_FOLDER%\MoonlightSetup.exe MoonlightSetup-%ARCH%-%VERSION%.exe
|
||||
|
||||
echo Building portable package
|
||||
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||
rem and should not be harvested for inclusion in the full installer
|
||||
@@ -157,19 +179,19 @@ copy "%VCToolsRedistDir%%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
rem This file tells Moonlight that it's a portable installation
|
||||
echo. > %DEPLOY_FOLDER%\portable.dat
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %INSTALLER_FOLDER%\MoonlightPortable.zip %DEPLOY_FOLDER%\*
|
||||
7z a %INSTALLER_FOLDER%\MoonlightPortable-%ARCH%-%VERSION%.zip %DEPLOY_FOLDER%\*
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if /i "%APPVEYOR%"=="true" (
|
||||
echo Pushing artifacts
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup-%ARCH%-%VERSION%.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightPortable.zip -FileName MoonlightPortable-%ARCH%-%BUILD_CONFIG%.zip
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightPortable-%ARCH%-%VERSION%.zip -FileName MoonlightPortable-%ARCH%-%BUILD_CONFIG%.zip
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip -FileName MoonlightDebuggingSymbols-%ARCH%-%BUILD_CONFIG%.zip
|
||||
appveyor PushArtifact %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols-%ARCH%-%VERSION%.zip -FileName MoonlightDebuggingSymbols-%ARCH%-%BUILD_CONFIG%.zip
|
||||
)
|
||||
|
||||
echo Build successful!
|
||||
echo Build successful for Moonlight v%VERSION%!
|
||||
exit /b 0
|
||||
|
||||
:Error
|
||||
|
||||
Reference in New Issue
Block a user