Compare commits

..
53 Commits
Author SHA1 Message Date
Cameron Gutman 12b7f6b7a3 Fix appstream metadata release date for v0.8.1 2019-01-29 18:52:01 -08:00
Cameron Gutman 32e7ddf272 Version 0.8.1 2019-01-08 00:48:49 -08:00
Cameron Gutman 7091e0324e Update macOS builds to FFmpeg 4.1 2019-01-07 21:43:18 -08:00
Cameron Gutman 8f8532921c Update FFmpeg build script for Mac to avoid manual steps 2019-01-07 21:39:59 -08:00
Cameron Gutman e9936992ec Update Windows builds to FFmpeg 4.1 2019-01-07 21:08:08 -08:00
Cameron Gutman 6375055b3e Set scripts executable 2019-01-07 20:34:56 -08:00
Cameron Gutman 88b1ea296c Remove video bitrate tooltip since audio is accounted for during remote streaming 2019-01-06 15:41:00 -08:00
Cameron Gutman 2a13a869f3 Don't allow the scroll wheel to move the bitrate slider 2019-01-06 15:39:54 -08:00
Cameron Gutman 89c342bb6f Introduce more granular HTTP timeouts 2019-01-06 14:35:33 -08:00
Cameron Gutman 067f5e33aa Increase audio buffer on Linux to 25 ms. Fixes #147. Fixes #157. 2019-01-06 13:49:20 -08:00
Cameron Gutman 33695f1c6b Update common-c to reduce audio latency 2019-01-06 13:29:06 -08:00
Cameron Gutman 52ef84873e Remove excess latency control logic from SDL renderer, since it doesn't appear to work very well anyway 2019-01-06 13:28:43 -08:00
Cameron Gutman c21ba5c808 Allow the audio renderer to be overriden by the ML_AUDIO environment variable 2019-01-06 13:28:05 -08:00
Cameron Gutman 6cf408f12b Update macOS build steps 2019-01-05 19:23:01 -08:00
Cameron Gutman 8bdc2d285d Rename macOS build output to match final release name 2019-01-05 19:06:00 -08:00
Cameron Gutman a66fbcd6be Use version.txt for Info.plist too 2019-01-05 18:53:17 -08:00
Cameron Gutman 93402a62fc Archive symbol ZIP to ML_SYMBOL_ARCHIVE folder 2019-01-05 18:20:53 -08:00
Cameron Gutman 2a56fb6c70 Add config.test generated files to gitignore 2019-01-05 18:10:20 -08:00
Cameron Gutman 8276c031e9 Name the Windows output binaries to match their final names on GitHub 2019-01-05 18:09:52 -08:00
Cameron Gutman 102e5f6de9 Move the version string into version.txt 2019-01-05 18:08:34 -08:00
Cameron Gutman 1f7bdbfbcb Fail signed release builds if unstaged changes exist 2019-01-05 17:25:40 -08:00
Cameron Gutman f2fdd048c7 Use stun.moonlight-stream.org for STUN 2019-01-04 20:16:27 -08:00
Cameron Gutman 5a2cfc15a6 Ensure WoL works properly on multi-homed hosts. Fixes #135 2019-01-04 20:05:31 -08:00
Cameron Gutman 11c7131875 Replace forgotten clamp to frameCountMax removed by e75dce2543 2019-01-03 18:49:18 -08:00
Cameron Gutman 34aacc90d7 Include version string in update tooltip 2019-01-03 18:00:54 -08:00
Cameron Gutman e75dce2543 Remove one-sample-per-callback write logic to fix crackling at 44.1 KHz. Fixes #152 2019-01-03 17:23:29 -08:00
Cameron Gutman e2774309fb Fix context menus on Qt 5.9 2019-01-02 19:49:06 -08:00
Cameron Gutman dfefdc93c5 Update macOS icns 2019-01-01 19:33:08 -08:00
Cameron Gutman d46ea28bba Merge pull request #151 from Jorys-Paulin/feature/icon-tweaks
Updated Moonlight icon
2019-01-01 19:32:21 -08:00
Cameron Gutman d35c5bcaba Print socket errors when sending WoL packets 2019-01-01 19:20:09 -08:00
Cameron Gutman 2b14856d7f Update common-c for strict remote bitrate logic 2019-01-01 19:03:52 -08:00
Jorys Paulin 3316fdb80e Updated Moonlight icon 2018-12-26 13:29:49 +01:00
Cameron Gutman c5d70c48c9 Fix QMap usage where insertion on read of missing value was not intended 2018-12-26 00:59:30 -08:00
Cameron Gutman 82230ef2f5 Version 0.8.0 2018-12-25 22:29:27 -08:00
Cameron Gutman dcfd3d3e48 Add a debug print for V-sync 2018-12-25 22:19:23 -08:00
Cameron Gutman cb1e2ac32e Avoid filling our client logs with QNetworkReplyHttpImplPrivate warnings 2018-12-25 21:54:20 -08:00
Cameron Gutman ec2c15946a Update audio buffering parameters based on local testing 2018-12-25 21:45:15 -08:00
Cameron Gutman 8be73142f8 Fix cert pinning on macOS 2018-12-25 21:15:59 -08:00
Cameron Gutman d1640e3bb8 Audio latency tweaks and fix for PulseAudio with A2DP 2018-12-25 17:54:18 -08:00
Cameron Gutman 8612e6726d Remove accidentally enabled debug prints 2018-12-25 14:05:55 -08:00
Cameron Gutman 3b4694e9f8 Add libraries required by libsoundio 2018-12-25 14:05:35 -08:00
Cameron Gutman 14cb2e1999 Remove latency drop logic and replace by single frame writes 2018-12-25 14:05:17 -08:00
Cameron Gutman cfabaf334e Add frame pacing toggle 2018-12-25 12:57:00 -08:00
Cameron Gutman c054536fab Count Pacer delay in frame stats 2018-12-25 12:09:45 -08:00
Cameron Gutman 35aac18b4e Improve bitrate slider label and add tooltip 2018-12-25 11:48:34 -08:00
Cameron Gutman de3928cb4d Reduce FPs on the unmapped gamepad dialog 2018-12-25 11:41:48 -08:00
Cameron Gutman 9bf612b800 Update common-c with bitrate fix 2018-12-24 20:10:37 -08:00
Cameron Gutman d9a8d67e3d Use a shared UID for all Moonlight clients 2018-12-22 21:04:44 -08:00
Cameron Gutman 99b0a56a6a Add additional cert pinning assert 2018-12-22 20:05:45 -08:00
Cameron Gutman 61d7aa0400 Require cert pinning for HTTPS 2018-12-22 19:55:28 -08:00
Cameron Gutman 1e98594972 Handle server cert change by pairing again 2018-12-21 19:17:32 -08:00
Cameron Gutman c6383f042c Pin server cert to host during pairing 2018-12-21 18:08:07 -08:00
Cameron Gutman b4d8e0e551 Update bug_report.md 2018-12-19 23:11:15 +04:00
93 changed files with 704 additions and 414 deletions
+10 -2
View File
@@ -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
View File
@@ -2,4 +2,6 @@
**/.vs/
build/
build/
config.tests/*/.qmake.stash
config.tests/*/Makefile
+6 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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)\\\"
+2 -1
View File
@@ -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) {
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+84 -50
View File
@@ -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
View File
@@ -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;
+2 -1
View File
@@ -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
View File
@@ -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;
+6 -3
View File
@@ -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;
};
+32 -18
View File
@@ -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;
}
+1 -1
View File
@@ -23,7 +23,7 @@ public:
~NvPairingManager();
PairState
pair(QString appVersion, QString pin);
pair(QString appVersion, QString pin, QSslCertificate& serverCert);
private:
QByteArray
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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()
}
}
}
}
+18 -3
View File
@@ -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
View File
@@ -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
}
+8
View File
@@ -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.
BIN
View File
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
View File
@@ -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

+3
View File
@@ -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));
+3
View File
@@ -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();
};
+6 -2
View File
@@ -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();
-5
View File
@@ -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;
};
+1 -79
View File
@@ -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;
};
+1 -1
View File
@@ -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()) {
+11 -6
View File
@@ -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,
+2 -1
View File
@@ -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);
+3 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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:
+21 -13
View File
@@ -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);
+3 -2
View File
@@ -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);
+1 -1
View File
@@ -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) {
+2 -1
View File
@@ -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);
+1
View File
@@ -0,0 +1 @@
0.8.1
+20
View File
@@ -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.
+13
View File
@@ -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 */
+2 -2
View File
@@ -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, \
+1 -1
View File
@@ -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 -1
View File
@@ -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 */
+2
View File
@@ -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
+8
View File
@@ -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 {
+11
View File
@@ -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);
+9
View File
@@ -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)
+2 -2
View File
@@ -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.
+20
View File
@@ -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 */
+2 -2
View File
@@ -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, \
+1 -1
View File
@@ -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 -1
View File
@@ -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 */
+2
View File
@@ -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
+8
View File
@@ -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 {
+11
View File
@@ -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);
+9
View File
@@ -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)
+2 -2
View File
@@ -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.
+6 -2
View File
@@ -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
View File
Regular → Executable
View File
+6 -2
View File
@@ -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
+28 -6
View File
@@ -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