Compare commits

...
134 Commits
Author SHA1 Message Date
Cameron Gutman 973d174a30 Version 1.2.0 (take 2) 2019-12-17 18:25:37 -08:00
Cameron Gutman 9f35690b95 Update DMG signing to support disambiguating identity shortnames 2019-12-17 18:10:39 -08:00
Cameron Gutman d961f2bc13 Switch to QElapsedTimer since QTime is deprecated for our usecase in Qt 5.14 2019-12-16 23:16:31 -08:00
Cameron Gutman 10e8277044 Version 1.2.0 2019-12-16 22:51:11 -08:00
Cameron Gutman b4c40d9b18 Update SDL_GameControllerDB submodule 2019-12-16 22:38:58 -08:00
Cameron Gutman e1603f0808 Explicitly use Rec 601 for VDPAU 2019-12-16 18:02:28 -08:00
Cameron Gutman d4c2350d63 Fix DXVA2 rendering with the wrong colorspace on AMD GPUs 2019-12-15 13:29:19 -08:00
Cameron Gutman 5e5470ae12 Specify video transfer matrix for SLVideo 2019-12-15 11:27:47 -08:00
Cameron Gutman 6230a99360 Fix colorspace conversion with SDL as frontend renderer 2019-12-14 17:17:29 -08:00
Cameron Gutman 801adb6af7 Add colorspace support to VAAPI renderer 2019-12-14 16:58:04 -08:00
Cameron Gutman 8b9b96f422 Only enable slicing when CPU decoding 2019-12-14 15:34:48 -08:00
Cameron Gutman 160684f23e Allow decoders to choose a desired colorspace 2019-12-14 15:25:56 -08:00
Cameron Gutman 88930a9de4 Optimize decoder capability checking 2019-12-14 15:20:44 -08:00
Cameron Gutman af6b8c9b88 Update common-c with colorspace configuration support 2019-12-14 14:40:02 -08:00
Cameron Gutman bf52d3189d Switch to Bionic build image on Travis CI 2019-12-14 13:25:57 -08:00
Cameron Gutman 5d872e8b5b Add auto update check to Steam Link 2019-12-13 20:21:35 -08:00
Cameron Gutman 4832d8007b Print joystick haptic capabilities to log 2019-12-13 18:43:35 -08:00
Cameron Gutman 879e084383 Enable Qt 5.14's new fractional DPI scaling 2019-12-12 20:36:23 -08:00
Cameron Gutman 1ae16bb657 Add source archival script 2019-12-09 18:27:51 -08:00
Cameron Gutman 2177bc68db Add git-archive-all.sh script from https://github.com/meitar/git-archive-all.sh 2019-12-09 18:27:51 -08:00
Cameron Gutman c8b887fe32 Use the new SDL_GameControllerRumble() API on SDL 2.0.9 to fix rumble on HIDAPI gamepads 2019-12-07 16:17:01 -08:00
Cameron Gutman 7205242f38 Improve VPN detection on Windows 2019-12-07 16:00:03 -08:00
Cameron Gutman be33b5b387 Update default window mode on macOS after upgrading 2019-12-07 13:27:47 -08:00
Cameron Gutman 66a123f4b9 Store binaries on the symbol server too 2019-12-03 19:14:19 -08:00
Cameron Gutman 76a4ee22b3 Update SDL_GameControllerDB submodule 2019-12-03 19:02:29 -08:00
Cameron Gutman 3a6bf4d20b Disable arbitrary audio duration for libsoundio until further testing is done 2019-12-03 19:00:43 -08:00
Cameron Gutman 09cf105cdc Block RTSS hooks due to user reported crashes 2019-12-03 18:31:18 -08:00
Cameron Gutman ce02e086a3 Add missing return when no interface address matches 2019-12-02 17:27:57 -08:00
Cameron Gutman e8d4313454 Fix build on Qt 5.10 and earlier 2019-12-02 17:18:29 -08:00
Cameron Gutman 49046f88cf Ignore unknown MTUs 2019-12-01 22:10:07 -08:00
Cameron Gutman 5e2f32e3eb Force remote streaming mode if connecting through a VPN 2019-12-01 21:50:36 -08:00
Cameron Gutman b4a1393bf6 Update common-c with PMTUD and low bitrate audio fixes 2019-12-01 19:45:15 -08:00
Cameron Gutman 0ccee9d806 Support arbitrary audio duration in libsoundio renderer 2019-12-01 19:43:22 -08:00
Cameron Gutman 5700272dc4 Improve low bandwidth audio performance for SDL and SLAudio and fix RTSP issues with broken PMTUD 2019-11-30 22:24:28 -06:00
Cameron Gutman 7e5a3925b3 Fix typo in channel mapping 2019-11-17 14:35:37 -08:00
Cameron Gutman 1febe32e73 Fix 5.1 channel mapping on Steam Link 2019-11-17 12:22:57 -08:00
Cameron Gutman 6059c982cf Fix auto-comment line breaks 2019-11-16 11:58:07 -08:00
Cameron Gutman de7b973239 Plumb presentation time from server into AVFrame.pts field 2019-11-11 18:03:47 -08:00
Cameron Gutman 741113a607 Add configuration for auto-comment bot 2019-11-09 15:04:28 -08:00
Cameron Gutman c20daa8867 Add compatibility checks for HEVC Main10 in VT backend 2019-11-05 18:36:43 -08:00
Cameron Gutman 5e57a7d5c0 Prevent SDL renderer from trying to render 10-bit output 2019-11-05 17:29:21 -08:00
Cameron Gutman 7c8d27700e Add 10-bit output support to VDPAU renderer 2019-11-05 17:09:44 -08:00
Cameron Gutman f8621be5ee Add an HEVC Main10 test frame 2019-11-05 17:08:25 -08:00
Cameron Gutman 8ed87e10b4 Change PC icon to be consistent with iOS and Android 2019-11-05 17:06:57 -08:00
Cameron Gutman ca26fa2dd0 Add basic HEVC Main10 support in DXVA2 renderer 2019-11-04 22:57:12 -08:00
Cameron Gutman 1669095fb1 Prevent double-clicks from triggering pairing twice. Fixes #286 2019-11-04 19:30:38 -08:00
Cameron Gutman 13eed48877 Update moonlight-common-c submodule 2019-11-02 21:49:00 -07:00
Cameron Gutman 07494e6eff Fix Linux build 2019-10-12 19:44:59 -07:00
Cameron Gutman 41b1d72825 Update moonlight-common-c submodule 2019-10-12 19:22:19 -07:00
Cameron Gutman 4d2965e74f Always install the VC redistributable 2019-09-30 00:11:37 -07:00
Cameron Gutman 12526bfca4 Version 1.1.1 2019-09-29 22:11:39 -07:00
Cameron Gutman 3e2c344140 Display the Moonlight icon in Discord Rich Presence info 2019-09-29 22:06:12 -07:00
Cameron Gutman 33730dd584 Display the update tooltip on launch if an update is available 2019-09-29 21:32:24 -07:00
Cameron Gutman 1cb03b1882 Add padding to app labels 2019-09-29 21:26:07 -07:00
Cameron Gutman 2f0a6db414 Add hacks for SDL+Mutter interaction bugs 2019-09-29 21:05:31 -07:00
Cameron Gutman a42328d971 Use VSWhere to locate Visual Studio and redistributables 2019-09-29 15:01:53 -07:00
Cameron Gutman 96ff51d598 Use SDL audio for platforms that libsoundio doesn't support 2019-09-22 12:03:53 -07:00
Cameron Gutman 7c3d3af681 Fix control stream connection error on multi-homed hosts 2019-09-14 14:08:38 -07:00
Cameron Gutman 6942bca5a7 Link to download page from download badge 2019-08-25 23:24:34 -07:00
Cameron Gutman 9eac9a190e Add Downloads badge 2019-08-25 23:20:55 -07:00
Cameron Gutman 40e4fbd9a6 Update common-c for parallel STUN requests 2019-08-20 19:05:54 -07:00
Cameron Gutman 7b6996ed80 Add a little help to the build scripts 2019-08-13 18:07:42 -07:00
Cameron Gutman ef2827a9d8 Update common-c for STUN fallback 2019-08-07 23:14:07 -07:00
Cameron Gutman dbf98ce3a6 Require keyframes even for software H.264 decoding 2019-08-07 22:38:31 -07:00
Cameron Gutman ee7cb5123d Add NVDEC to list of supported video acceleration APIs 2019-08-05 18:31:38 -07:00
Cameron Gutman 77e1d6ab8a Version 1.1 2019-08-05 18:28:26 -07:00
Cameron Gutman 1640bed7a5 Improve reliability of mDNS discovery 2019-08-04 18:04:33 -07:00
Cameron Gutman c6fd5cbbce Update common-c to fix high quality surround sound over the Internet 2019-08-04 15:15:51 -07:00
Cameron Gutman 5288041da5 Prevent us from consuming more than 4 samples per write callback to bound latency 2019-08-03 12:47:44 -07:00
Cameron Gutman a848fb1dd1 Add Steam Link to Travis CI 2019-08-02 21:31:07 -07:00
Cameron Gutman 172945434c Build the full DMG on Travis CI 2019-08-02 20:52:23 -07:00
Cameron Gutman a0967e0281 Fix building a dmg with no code signing identities 2019-08-02 20:40:20 -07:00
Cameron Gutman deab717c73 Remove AppVeyor OpenSSL hack 2019-08-02 20:21:10 -07:00
Cameron Gutman 5db5a088ea Fix NVDEC artifacts if the renderer is delayed for a bit 2019-08-01 20:25:58 -07:00
Cameron Gutman 5e106d2f19 Only store the local address from mDNS if it actually worked 2019-07-31 22:16:11 -07:00
Cameron Gutman 940da6bc73 Fix discovery of IPv6-only hosts 2019-07-31 22:07:20 -07:00
Cameron Gutman d110bff253 Update qmdnsengine to improve reliability of mDNS on multi-homed systems 2019-07-31 22:03:27 -07:00
Cameron Gutman 4caa329c36 Update Windows to SDL 2.0.10 with USE_D3D9EX 2019-07-28 12:01:12 -07:00
Cameron Gutman 7c7d279337 Update SDL_GameControllerDB to dca1a629d9e094baa17fff8ba1642cabc6125eed 2019-07-26 16:43:20 -04:00
Cameron Gutman 831e6ee76d Update moonlight-common-c to fix termination error code on GFE 3.19.0.107 2019-07-26 16:43:20 -04:00
Cameron Gutman b01842ad02 Update macOS to SDL 2.0.10 2019-07-26 16:43:20 -04:00
Cameron Gutman 7acf91883a Rework audio renderer selection to fall back and treat ML_AUDIO as a hard selection 2019-07-26 12:50:45 -04:00
Cameron Gutman 897f6fdd3e Improve handling of IPv6-only cases 2019-07-15 01:07:31 -07:00
Cameron Gutman 6f9c3fbc38 Add support for zero configuration IPv6 streaming 2019-07-14 17:56:57 -07:00
Cameron Gutman bcbd1a5b4b Fix subsystem print in VAAPI 2019-07-07 15:41:26 -07:00
Cameron Gutman 6468efd7e4 Add VAAPI DRM support 2019-07-07 15:32:38 -07:00
Cameron Gutman 66863789c7 Update common-c for compatibility with legacy servers 2019-07-05 22:52:42 -07:00
Cameron Gutman b30058fc29 Fix pairing to Gen 3-5 servers 2019-07-05 21:04:04 -07:00
Cameron Gutman a41cd0b070 Generate SHA-256 client certificates instead of SHA-1 2019-07-05 20:57:21 -07:00
Cameron Gutman ea8b905f02 Missed a call to SDL_SetRelativeMouseMode 2019-07-02 22:39:09 -07:00
Cameron Gutman 16b301236b Add a mouse capture fallback if SDL_SetRelativeMouseMode() fails 2019-07-02 22:17:38 -07:00
Cameron Gutman 22162dda83 Fix runtime SDL version report 2019-06-30 18:48:11 -07:00
Cameron Gutman 024af89be3 Add an option to disable the gamepad mouse button toggle 2019-06-29 18:24:59 -07:00
Cameron Gutman e1a697658e Add Discord Rich Presence for macOS 2019-06-29 17:55:21 -07:00
Cameron Gutman 0a5051f959 Add Discord Rich Presence integration for Windows 2019-06-29 17:40:30 -07:00
Cameron Gutman 82b6b60b31 Take advantage of SDL 2.0.10 bugfixes and new APIs. Fixes #133 2019-06-29 16:08:04 -07:00
Cameron Gutman 06b3c3636a Print compile-time and run-time SDL versions 2019-06-29 15:43:45 -07:00
Cameron Gutman e5712a9474 Move dylibs into the Frameworks folder in the app bundle 2019-06-29 15:39:20 -07:00
Cameron Gutman 6ff845c53b Use borderless windowed mode as the default on macOS. Fixes #246 2019-06-29 14:58:27 -07:00
Cameron Gutman 1c225ed04d Use CUDA acceleration if other hwaccels are unavailable 2019-06-26 21:54:29 -07:00
Cameron Gutman 612b738968 Use the preferred sw_format when reading frames back 2019-06-26 21:25:41 -07:00
Cameron Gutman 87356ef594 Add CUDA acceleration support 2019-06-26 21:02:33 -07:00
Cameron Gutman 20a4dfa6d6 Update bug report template 2019-06-25 21:41:02 -07:00
Cameron Gutman 524cc97f51 Use borderless windowed mode when toggling to full-screen from an initially windowed stream. Fixes #248 2019-06-23 13:29:57 -07:00
Cameron Gutman fc849fb51a Switch back to SDL for audio on Linux. Fixes #245 2019-06-23 13:16:00 -07:00
Cameron Gutman c88651939a Re-add latency bounding on the SDL audio renderer 2019-06-23 13:14:55 -07:00
Cameron Gutman 01254f67f0 Allow audio renderers to specify capabilities 2019-06-23 12:49:37 -07:00
Cameron Gutman 212ed33b21 Block Optimus from using the dGPU due to driver bugs. Fixes #240. Fixes #235 2019-06-22 19:49:05 -07:00
Cameron Gutman bd3f8aef21 Remove OpenSSL 1.0.2 for Qt 5.12.4/5.13 2019-06-18 19:54:35 -07:00
Cameron Gutman 3785acea29 Update to OpenSSL 1.1.1c in preparation for Qt 5.12.4/5.13 2019-06-16 15:20:04 -07:00
Cameron Gutman 866e2f1762 Version 1.0.1 2019-05-27 09:31:01 -07:00
Cameron Gutman 0a705d6752 Fix video on GFE 3.19 2019-05-27 02:24:55 -07:00
Cameron Gutman 4f74fd5354 Fix uninitialized variables and a race condition with CVDisplayLinkStart() 2019-05-21 09:09:16 -07:00
Cameron Gutman 3df9a5d01c Version 1.0.0 2019-05-21 00:14:12 -07:00
Cameron Gutman 2682829bd3 Fix Steam Link audio latency cap 2019-05-20 23:57:27 -07:00
Cameron Gutman 30cfa2607d Hide mouse cursor after streaming on Steam Link 2019-05-19 17:43:46 -07:00
Cameron Gutman 4467c2e6ad Update SDL_GameControllerDB 2019-05-19 13:46:05 -07:00
Cameron Gutman 2c0e8a0ddf Send WoL packet to 48002 and 48010 to workaround ISP blocking of 7 and 9 2019-05-19 13:36:05 -07:00
Cameron Gutman 4c17f32a2e Fix extra non-working gamepad appearing to the host 2019-05-19 13:24:06 -07:00
Cameron Gutman 674220087f Finish mouse emulation support with overlay and bugfixes 2019-05-19 13:10:42 -07:00
Cameron Gutman fc8d5d5799 Add gamepad mouse emulation support 2019-05-19 12:17:23 -07:00
Cameron Gutman 4e4f04c174 Focus on first item when a gamepad is connected 2019-05-19 11:08:23 -07:00
Cameron Gutman 65c21f3392 Improve gamepad navigation on settings page 2019-05-19 10:16:54 -07:00
Cameron Gutman 97fb30cdf1 Fix a few bugs in CVDisplayLink integration in VTRenderer 2019-05-19 09:52:59 -07:00
Cameron Gutman c975279589 Only enable exception-based thread naming on debug builds 2019-05-13 17:54:03 -07:00
Cameron Gutman b3ee7a635f Cap queued audio at 40 ms on Steam Link 2019-05-11 19:09:59 -07:00
Cameron Gutman c2b12868bb Move DisplayLinkVsyncSource back into VTRenderer to reduce latency 2019-05-11 18:33:12 -07:00
Cameron Gutman bdbb03e16f Request 20 ms audio frames on Steam Link to reduce CPU overhead 2019-05-04 15:46:11 -07:00
Cameron Gutman 53138d7c16 Name threads for easier debugging 2019-05-03 21:18:58 -07:00
Cameron Gutman 18d1d35104 Use QByteArray::reserve() rather than reallocating each time we must resize 2019-05-02 22:54:18 -07:00
Cameron Gutman 99d9de35de Add NV21 pixel format support to SDL renderer 2019-05-02 22:51:28 -07:00
Cameron Gutman e6a48481a5 Restore the bulk submission optimization for Steam Link 2019-05-01 22:31:52 -07:00
Cameron Gutman 21f2b1224a Decode directly into the audio renderer's buffer to avoid a copy 2019-05-01 21:27:41 -07:00
Cameron Gutman 187f47a353 Update common-c to fix receive time corruption 2019-04-30 23:23:02 -07:00
321 changed files with 11297 additions and 5538 deletions
+6 -1
View File
@@ -1,8 +1,10 @@
---
name: Bug report
about: Create a report to help us address broken functionality
about: Create a report to help us address issues in Moonlight
---
**READ ME FIRST!**
This issue tracker is not the right place to ask for troubleshooting help if you're not able to connect or stream at all. For those requests, please use the #pc-help channel on our Discord server where there are many more people who can help (or direct you back here if it looks like a Moonlight bug after all). https://discord.gg/MySTSdq
**Describe the bug**
A clear and concise description of what the bug is.
@@ -16,6 +18,9 @@ If applicable, add screenshots to help explain your problem. If the issue is rel
**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.
**Other Moonlight clients**
- Does the issue occur when using Moonlight on iOS or Android?
**Moonlight settings (please complete the following information)**
- Have any settings been adjusted from defaults?
- If so, which settings have been changed?
+4
View File
@@ -0,0 +1,4 @@
issuesOpened: >
If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://discord.gg/MySTSdq) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.<br /><br />
This issue tracker should only be used for specific bugs or feature requests.<br /><br />
Thank you, and happy streaming!
+43 -24
View File
@@ -3,51 +3,70 @@ language: cpp
git:
depth: 1
env:
- NJOBS=2
matrix:
include:
- os: osx
osx_image: xcode10.2
compiler: clang
env: TRAVIS_TARGET=osx
addons:
homebrew:
packages:
- qt
- os: linux
sudo: required
dist: xenial
compiler: gcc
env: TRAVIS_TARGET=steamlink
addons:
apt:
packages:
- libc6:i386
- libstdc++6:i386
- os: linux
sudo: required
dist: bionic
compiler: gcc
env: TRAVIS_TARGET=linux
addons:
apt:
sources:
- sourceline: 'ppa:beineri/opt-qt597-xenial'
- sourceline: 'ppa:jonathonf/ffmpeg-4'
- sourceline: 'ppa:savoury1/multimedia'
- sourceline: 'ppa:savoury1/ffmpeg4'
packages:
- qt59base
- qt59quickcontrols2
- qt59svg
- libesd0-dev
- libgl-dev
- qt5-default
- qtbase5-dev
- qtquickcontrols2-5-dev
- libqt5svg5-dev
- libgl1-mesa-dev
- libegl1-mesa-dev
- libxkbcommon-dev
- wayland-protocols
- libopus-dev
- libavcodec-dev
- libavutil-dev
- libva-dev
- libvdpau-dev
- libsdl2-dev
- qtdeclarative5-dev
- libssl-dev
- libsdl2-ttf-dev
- libavcodec-dev
- libavutil-dev
install:
- '[ "$TRAVIS_OS_NAME" != osx ] || brew install qt5'
- '[ "$TRAVIS_OS_NAME" != osx ] || brew link --force qt5'
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_VER=2.0.9 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make -j$NJOBS && sudo make install && cd ..; fi
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_TTF_VER=2.0.15 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$NJOBS && sudo make install && cd ..; fi
- '[ "$TRAVIS_TARGET" != osx ] || brew link --force qt'
- if [[ "$TRAVIS_TARGET" = "osx" ]]; then nvm use node && npm install --global create-dmg; fi
- if [[ "$TRAVIS_TARGET" = "steamlink" ]]; then git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk; fi
before_script:
- '[ "$TRAVIS_OS_NAME" != "linux" ] || source /opt/qt59/bin/qt59-env.sh'
- qmake -v
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
- '[ "$TRAVIS_OS_NAME" != osx ] || export QMAKESPEC=macx-clang'
- '[ "$TRAVIS_TARGET" = "steamlink" ] || qmake -v'
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
- '[ "$TRAVIS_TARGET" != osx ] || export QMAKESPEC=macx-clang'
- '[ "$TRAVIS_TARGET" != "steamlink" ] || export STEAMLINK_SDK_PATH=$HOME/steamlink-sdk'
script:
- qmake moonlight-qt.pro -spec $QMAKESPEC
- make -j$NJOBS debug
- make -j$NJOBS release
- '[ "$TRAVIS_TARGET" != "linux" ] || qmake moonlight-qt.pro -spec $QMAKESPEC'
- '[ "$TRAVIS_TARGET" != "linux" ] || make -j$(nproc) debug'
- '[ "$TRAVIS_TARGET" != "linux" ] || make -j$(nproc) release'
- '[ "$TRAVIS_TARGET" != "osx" ] || scripts/generate-dmg.sh Release'
- '[ "$TRAVIS_TARGET" != "steamlink" ] || scripts/build-steamlink-app.sh'
+35
View File
@@ -135,10 +135,45 @@ private:
// D3D9 exclusive fullscreen in a way that causes CreateDeviceEx()
// to deadlock. https://github.com/moonlight-stream/moonlight-qt/issues/102
L"NahimicOSD.dll",
// This DLL has been seen in several crash reports. Some Googling
// suggests it's highly unstable and causes issues in many games.
L"EZFRD32.dll",
L"EZFRD64.dll",
// These are the dList DLLs for Optimus hybrid graphics DDI.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
//
// We forcefully block them from loading because Optimus has a bug that
// deadlocks DXVA2 when we present with D3DPRESENT_DONOTWAIT. This will prevent
// Optimus from ever using the dGPU even if the user has requested it.
// https://github.com/moonlight-stream/moonlight-qt/issues/240
// https://github.com/moonlight-stream/moonlight-qt/issues/235
L"nvdlist.dll",
L"nvdlistx.dll",
// In some unknown circumstances, RTSS tries to hook in the middle of an instruction, leaving garbage
// code inside d3d9.dll that causes a crash when executed:
//
// 0:000> u
// d3d9!D3D9GetCurrentOwnershipMode+0x5d:
// 00007ff8`95b95861 9b wait
// 00007ff8`95b95862 a7 cmps dword ptr [rsi],dword ptr [rdi] <--- crash happens here
// 00007ff8`95b95863 ff ???
// 00007ff8`95b95864 bfe8ca8a00 mov edi,8ACAE8h
// 00007ff8`95b95869 00eb add bl,ch
// 00007ff8`95b9586b f1 ???
// 00007ff8`95b9586c b808000000 mov eax,8
// 00007ff8`95b95871 ebe6 jmp d3d9!D3D9GetCurrentOwnershipMode+0x55 (00007ff8`95b95859)
//
// Disassembling starting at the exact address of the attempted hook yields the intended jmp instruction
//
// 0:000> u d3d9!D3D9GetCurrentOwnershipMode+0x5c:
// 00007ff8`95b95860 e99ba7ffbf jmp 00007ff8`55b90000
//
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
L"RTSSHooks.dll",
L"RTSSHooks64.dll",
};
};
+1
View File
@@ -8,6 +8,7 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
[![Windows AppVeyor Status](https://ci.appveyor.com/api/projects/status/glj5cxqwy2w3bglv/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
[![Mac and Linux Travis CI Status](https://travis-ci.org/moonlight-stream/moonlight-qt.svg?branch=master)](https://travis-ci.org/moonlight-stream/moonlight-qt)
[![Downloads](https://img.shields.io/github/downloads/moonlight-stream/moonlight-qt/total)](https://github.com/moonlight-stream/moonlight-qt/releases)
## Features
- Hardware accelerated video decoding on Windows, Mac, and Linux
+25 -8
View File
@@ -84,6 +84,9 @@ unix:!macx {
packagesExist(libva-wayland) {
CONFIG += libva-wayland
}
packagesExist(libva-drm) {
CONFIG += libva-drm
}
CONFIG += libva
}
@@ -107,16 +110,16 @@ win32 {
CONFIG += ffmpeg
}
win32:!winrt {
CONFIG += soundio
CONFIG += soundio discord-rpc
}
macx {
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
# For libsoundio
LIBS += -framework CoreAudio -framework AudioUnit
CONFIG += ffmpeg soundio
CONFIG += ffmpeg soundio discord-rpc
}
SOURCES += \
@@ -128,6 +131,7 @@ SOURCES += \
backend/nvpairingmanager.cpp \
backend/computermanager.cpp \
backend/boxartmanager.cpp \
backend/richpresencemanager.cpp \
cli/commandlineparser.cpp \
cli/quitstream.cpp \
cli/startstream.cpp \
@@ -155,6 +159,7 @@ HEADERS += \
backend/nvpairingmanager.h \
backend/computermanager.h \
backend/boxartmanager.h \
backend/richpresencemanager.h \
cli/commandlineparser.h \
cli/quitstream.h \
cli/startstream.h \
@@ -182,6 +187,7 @@ ffmpeg {
SOURCES += \
streaming/video/ffmpeg.cpp \
streaming/video/ffmpeg-renderers/sdlvid.cpp \
streaming/video/ffmpeg-renderers/cuda.cpp \
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
@@ -189,6 +195,7 @@ ffmpeg {
streaming/video/ffmpeg.h \
streaming/video/ffmpeg-renderers/renderer.h \
streaming/video/ffmpeg-renderers/sdlvid.h \
streaming/video/ffmpeg-renderers/cuda.h \
streaming/video/ffmpeg-renderers/pacer/pacer.h \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
}
@@ -212,6 +219,12 @@ libva-wayland {
PKGCONFIG += libva-wayland
DEFINES += HAVE_LIBVA_WAYLAND
}
libva-wayland {
message(VAAPI DRM support enabled)
PKGCONFIG += libva-drm
DEFINES += HAVE_LIBVA_DRM
}
libvdpau {
message(VDPAU renderer selected)
@@ -261,12 +274,10 @@ macx {
message(VideoToolbox renderer selected)
SOURCES += \
streaming/video/ffmpeg-renderers/vt.mm \
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.mm
streaming/video/ffmpeg-renderers/vt.mm
HEADERS += \
streaming/video/ffmpeg-renderers/vt.h \
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.h
streaming/video/ffmpeg-renderers/vt.h
}
soundio {
message(libsoundio audio renderer selected)
@@ -275,6 +286,12 @@ soundio {
SOURCES += streaming/audio/renderers/soundioaudiorenderer.cpp
HEADERS += streaming/audio/renderers/soundioaudiorenderer.h
}
discord-rpc {
message(Discord integration enabled)
LIBS += -ldiscord-rpc
DEFINES += HAVE_DISCORD
}
RESOURCES += \
resources.qrc \
@@ -367,7 +384,7 @@ macx {
APP_BUNDLE_RESOURCES.files = moonlight.icns
APP_BUNDLE_RESOURCES.path = Contents/Resources
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true)
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true) $$files(../libs/mac/lib/*.dylib, true)
APP_BUNDLE_FRAMEWORKS.path = Contents/Frameworks
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES APP_BUNDLE_FRAMEWORKS
+12 -3
View File
@@ -28,7 +28,7 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
void AutoUpdateChecker::start()
{
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) || defined(STEAM_LINK) // Only run update checker on platforms without auto-update
// We'll get a callback when this is finished
QUrl url("https://moonlight-stream.org/updates/qt.json");
m_Nam.get(QNetworkRequest(url));
@@ -43,6 +43,15 @@ void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>&
}
}
QString AutoUpdateChecker::getPlatform()
{
#ifdef STEAM_LINK
return QStringLiteral("steamlink");
#else
return QSysInfo::productType();
#endif
}
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
@@ -88,7 +97,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
}
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
updateObj["platform"] == QSysInfo::productType()) {
updateObj["platform"] == getPlatform()) {
qDebug() << "Found update manifest match for current platform";
QString latestVersion = updateObj["version"].toString();
@@ -137,7 +146,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
}
qWarning() << "No entry in update manifest found for current platform: "
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
<< QSysInfo::buildCpuArchitecture() << getPlatform();
}
else {
qWarning() << "Update checking failed with error: " << reply->error();
+2
View File
@@ -20,6 +20,8 @@ private slots:
private:
void parseStringToVersionQuad(QString& string, QVector<int>& version);
QString getPlatform();
QVector<int> m_CurrentVersionQuad;
QNetworkAccessManager m_Nam;
};
+104 -21
View File
@@ -216,6 +216,42 @@ void ComputerManager::saveHosts()
settings.endArray();
}
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)
{
for (const QHostAddress& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
if (address.isInSubnet(QHostAddress("fe80::"), 10)) {
// Link-local
continue;
}
if (address.isInSubnet(QHostAddress("fec0::"), 10)) {
qInfo() << "Ignoring site-local address:" << address;
continue;
}
if (address.isInSubnet(QHostAddress("fc00::"), 7)) {
qInfo() << "Ignoring ULA:" << address;
continue;
}
if (address.isInSubnet(QHostAddress("2002::"), 16)) {
qInfo() << "Ignoring 6to4 address:" << address;
continue;
}
if (address.isInSubnet(QHostAddress("2001::"), 32)) {
qInfo() << "Ignoring Teredo address:" << address;
continue;
}
return address;
}
}
return QHostAddress();
}
void ComputerManager::startPolling()
{
QWriteLocker lock(&m_Lock);
@@ -233,9 +269,9 @@ void ComputerManager::startPolling()
this, [this](const QMdnsEngine::Service& service) {
qInfo() << "Discovered mDNS host:" << service.hostname();
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, service);
connect(pendingComputer, &MdnsPendingComputer::resolvedHost,
this, &ComputerManager::handleMdnsServiceResolved);
m_PendingResolution.append(pendingComputer);
});
}
@@ -277,11 +313,39 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
}
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
const QHostAddress& address)
QVector<QHostAddress>& addresses)
{
qInfo() << "Resolved" << computer->hostname() << "to" << address.toString();
QHostAddress v6Global = getBestGlobalAddressV6(addresses);
bool added = false;
addNewHost(address.toString(), true);
// Add the host using the IPv4 address
for (const QHostAddress& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
// NB: We don't just call addNewHost() here with v6Global because the IPv6
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
// it's not currently reachable.
addNewHost(address.toString(), true, v6Global);
added = true;
break;
}
}
if (!added) {
// If we get here, there wasn't an IPv4 address so we'll do it v6-only
for (const QHostAddress& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
// Use a link-local or site-local address for the "local address"
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
address.isInSubnet(QHostAddress("fec0::"), 10) ||
address.isInSubnet(QHostAddress("fc00::"), 7)) {
addNewHost(address.toString(), true, v6Global);
added = true;
break;
}
}
}
}
m_PendingResolution.removeOne(computer);
computer->deleteLater();
@@ -508,9 +572,10 @@ class PendingAddTask : public QObject, public QRunnable
Q_OBJECT
public:
PendingAddTask(ComputerManager* computerManager, QString address, bool mdns)
PendingAddTask(ComputerManager* computerManager, QString address, QHostAddress mdnsIpv6Address, bool mdns)
: m_ComputerManager(computerManager),
m_Address(address),
m_MdnsIpv6Address(mdnsIpv6Address),
m_Mdns(mdns)
{
connect(this, &PendingAddTask::computerAddCompleted,
@@ -560,16 +625,21 @@ private:
{
NvHTTP http(m_Address, QSslCertificate());
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user") << m_MdnsIpv6Address;
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
QString serverInfo = fetchServerInfo(http);
if (serverInfo.isEmpty() && !m_MdnsIpv6Address.isNull()) {
// Retry using the global IPv6 address if the IPv4 or link-local IPv6 address fails
http.setAddress(m_MdnsIpv6Address.toString());
serverInfo = fetchServerInfo(http);
}
if (serverInfo.isEmpty()) {
return;
}
// Create initial newComputer using HTTP serverinfo with no pinned cert
NvComputer* newComputer = new NvComputer(m_Address, serverInfo, QSslCertificate());
NvComputer* newComputer = new NvComputer(http.address(), serverInfo, QSslCertificate());
// Check if we have a record of this host UUID to pull the pinned cert
NvComputer* existingComputer;
@@ -589,22 +659,34 @@ private:
}
// Update the polled computer with the HTTPS information
NvComputer httpsComputer(m_Address, serverInfo, QSslCertificate());
NvComputer httpsComputer(http.address(), serverInfo, QSslCertificate());
newComputer->update(httpsComputer);
}
// Update addresses depending on the context
if (m_Mdns) {
newComputer->localAddress = m_Address;
// Get the WAN IP address using STUN if we're on mDNS
quint32 addr;
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
if (err == 0) {
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
// Only update local address if we actually reached the PC via this address.
// If we reached it via the IPv6 address after the local address failed,
// don't store the non-working local address.
if (http.address() == m_Address) {
newComputer->localAddress = m_Address;
}
else {
qWarning() << "STUN failed to get WAN address:" << err;
// Get the WAN IP address using STUN if we're on mDNS over IPv4
if (QHostAddress(newComputer->localAddress).protocol() == QAbstractSocket::IPv4Protocol) {
quint32 addr;
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
if (err == 0) {
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
}
else {
qWarning() << "STUN failed to get WAN address:" << err;
}
}
if (!m_MdnsIpv6Address.isNull()) {
Q_ASSERT(m_MdnsIpv6Address.protocol() == QAbstractSocket::IPv6Protocol);
newComputer->ipv6Address = m_MdnsIpv6Address.toString();
}
}
else {
@@ -657,14 +739,15 @@ private:
ComputerManager* m_ComputerManager;
QString m_Address;
QHostAddress m_MdnsIpv6Address;
bool m_Mdns;
};
void ComputerManager::addNewHost(QString address, bool mdns)
void ComputerManager::addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address)
{
// Punt to a worker thread to avoid stalling the
// UI while waiting for serverinfo query to complete
PendingAddTask* addTask = new PendingAddTask(this, address, mdns);
PendingAddTask* addTask = new PendingAddTask(this, address, mdnsIpv6Address, mdns);
QThreadPool::globalInstance()->start(addTask);
}
+42 -12
View File
@@ -13,6 +13,7 @@
#include <QReadWriteLock>
#include <QSettings>
#include <QRunnable>
#include <QTimer>
class MdnsPendingComputer : public QObject
{
@@ -20,13 +21,18 @@ class MdnsPendingComputer : public QObject
public:
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
QMdnsEngine::Cache* cache,
const QMdnsEngine::Service& service)
: m_Hostname(service.hostname()),
m_Resolver(server, m_Hostname, cache)
m_Server(server),
m_Resolver(nullptr)
{
connect(&m_Resolver, SIGNAL(resolved(QHostAddress)),
this, SLOT(handleResolved(QHostAddress)));
// Start resolving
resolve();
}
virtual ~MdnsPendingComputer()
{
delete m_Resolver;
}
QString hostname()
@@ -35,20 +41,42 @@ public:
}
private slots:
void handleResolved(const QHostAddress& address)
void handleResolvedTimeout()
{
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
m_Resolver.disconnect();
emit resolvedv4(this, address);
if (m_Addresses.isEmpty()) {
// Try again
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
resolve();
}
else {
Q_ASSERT(!m_Addresses.isEmpty());
emit resolvedHost(this, m_Addresses);
}
}
void handleResolvedAddress(const QHostAddress& address)
{
qInfo() << "Resolved" << hostname() << "to" << address;
m_Addresses.push_back(address);
}
signals:
void resolvedv4(MdnsPendingComputer*, const QHostAddress&);
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
private:
void resolve()
{
delete m_Resolver;
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
this, &MdnsPendingComputer::handleResolvedAddress);
QTimer::singleShot(2000, this, SLOT(handleResolvedTimeout()));
}
QByteArray m_Hostname;
QMdnsEngine::Resolver m_Resolver;
QMdnsEngine::Server* m_Server;
QMdnsEngine::Resolver* m_Resolver;
QVector<QHostAddress> m_Addresses;
};
class ComputerPollingEntry
@@ -140,7 +168,7 @@ public:
Q_INVOKABLE void stopPollingAsync();
Q_INVOKABLE void addNewHost(QString address, bool mdns);
Q_INVOKABLE void addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address = QHostAddress());
void pairHost(NvComputer* computer, QString pin);
@@ -165,11 +193,13 @@ private slots:
void handleComputerStateChanged(NvComputer* computer);
void handleMdnsServiceResolved(MdnsPendingComputer* computer, const QHostAddress& address);
void handleMdnsServiceResolved(MdnsPendingComputer* computer, QVector<QHostAddress>& addresses);
private:
void saveHosts();
QHostAddress getBestGlobalAddressV6(QVector<QHostAddress>& addresses);
void startPollingComputer(NvComputer* computer);
int m_PollingRef;
+12 -5
View File
@@ -39,11 +39,18 @@ void ComputerSeeker::onComputerUpdated(NvComputer *computer)
bool ComputerSeeker::matchComputer(NvComputer *computer) const
{
QString value = m_ComputerName.toLower();
return computer->name.toLower() == value ||
computer->localAddress.toLower() == value ||
computer->remoteAddress.toLower() == value ||
computer->manualAddress.toLower() == value ||
computer->uuid.toLower() == value;
if (computer->name.toLower() == value || computer->uuid.toLower() == value) {
return true;
}
for (const QString& addr : computer->uniqueAddresses()) {
if (addr.toLower() == value) {
return true;
}
}
return false;
}
bool ComputerSeeker::isOnline(NvComputer *computer) const
+1 -1
View File
@@ -75,7 +75,7 @@ void IdentityManager::createCredentials(QSettings& settings)
-1, -1, 0);
X509_set_issuer_name(cert, name);
X509_sign(cert, pk, EVP_sha1());
X509_sign(cert, pk, EVP_sha256());
BIO* biokey = BIO_new(BIO_s_mem());
THROW_BAD_ALLOC_IF_NULL(biokey);
+87 -1
View File
@@ -3,6 +3,7 @@
#include <QUdpSocket>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QNetworkProxy>
#define SER_NAME "hostname"
#define SER_UUID "uuid"
@@ -10,6 +11,7 @@
#define SER_LOCALADDR "localaddress"
#define SER_REMOTEADDR "remoteaddress"
#define SER_MANUALADDR "manualaddress"
#define SER_IPV6ADDR "ipv6address"
#define SER_APPLIST "apps"
#define SER_SRVCERT "srvcert"
@@ -24,6 +26,7 @@ NvComputer::NvComputer(QSettings& settings)
this->macAddress = settings.value(SER_MAC).toByteArray();
this->localAddress = settings.value(SER_LOCALADDR).toString();
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
this->ipv6Address = settings.value(SER_IPV6ADDR).toString();
this->manualAddress = settings.value(SER_MANUALADDR).toString();
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
@@ -63,6 +66,7 @@ void NvComputer::serialize(QSettings& settings)
settings.setValue(SER_MAC, macAddress);
settings.setValue(SER_LOCALADDR, localAddress);
settings.setValue(SER_REMOTEADDR, remoteAddress);
settings.setValue(SER_IPV6ADDR, ipv6Address);
settings.setValue(SER_MANUALADDR, manualAddress);
settings.setValue(SER_SRVCERT, serverCert.toPem());
@@ -129,7 +133,12 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
mode2.width * mode2.height * mode2.refreshRate;
});
// We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
if (this->localAddress.startsWith("127.")) {
this->localAddress = QString();
}
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
PS_PAIRED : PS_NOT_PAIRED;
@@ -156,7 +165,7 @@ bool NvComputer::wake()
const quint16 WOL_PORTS[] = {
7, 9, // Standard WOL ports
47998, 47999, 48000, // Ports opened by GFE
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
};
// Create the WoL payload
@@ -229,6 +238,81 @@ bool NvComputer::wake()
return success;
}
bool NvComputer::isReachableOverVpn()
{
if (activeAddress.isEmpty()) {
return false;
}
QTcpSocket s;
s.setProxy(QNetworkProxy::NoProxy);
s.connectToHost(activeAddress, 47984);
if (s.waitForConnected(3000)) {
Q_ASSERT(!s.localAddress().isNull());
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
// Ensure the interface is up
if ((nic.flags() & QNetworkInterface::IsUp) == 0) {
continue;
}
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
if (addr.ip() == s.localAddress()) {
qInfo() << "Found matching interface:" << nic.humanReadableName() << nic.hardwareAddress() << nic.flags();
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
qInfo() << "Interface Type:" << nic.type();
qInfo() << "Interface MTU:" << nic.maximumTransmissionUnit();
if (nic.type() == QNetworkInterface::Virtual ||
nic.type() == QNetworkInterface::Ppp) {
// Treat PPP and virtual interfaces as likely VPNs
return true;
}
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
// Treat MTUs under 1500 as likely VPNs
return true;
}
#endif
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
// Treat point-to-point links as likely VPNs.
// This check detects OpenVPN on Unix-like OSes.
return true;
}
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
return true;
}
if (nic.humanReadableName().startsWith("ZeroTier")) {
// ZeroTier interfaces always start with "ZeroTier"
return true;
}
if (nic.humanReadableName().contains("VPN")) {
// This one is just a final heuristic if all else fails
return true;
}
// Didn't meet any of our VPN heuristics
return false;
}
}
}
qWarning() << "No match found for address:" << s.localAddress();
return false;
}
else {
// If we fail to connect, just pretend that it's not a VPN
return false;
}
}
QVector<QString> NvComputer::uniqueAddresses()
{
QVector<QString> uniqueAddressList;
@@ -237,6 +321,7 @@ QVector<QString> NvComputer::uniqueAddresses()
uniqueAddressList.append(activeAddress);
uniqueAddressList.append(localAddress);
uniqueAddressList.append(remoteAddress);
uniqueAddressList.append(ipv6Address);
uniqueAddressList.append(manualAddress);
// Prune duplicates (always giving precedence to the first)
@@ -296,6 +381,7 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(ipv6Address);
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
ASSIGN_IF_CHANGED(pairState);
ASSIGN_IF_CHANGED(serverCodecModeSupport);
+4
View File
@@ -29,6 +29,9 @@ public:
bool
wake();
bool
isReachableOverVpn();
QVector<QString>
uniqueAddresses();
@@ -64,6 +67,7 @@ public:
// Persisted traits
QString localAddress;
QString remoteAddress;
QString ipv6Address;
QString manualAddress;
QByteArray macAddress;
QString name;
+17 -5
View File
@@ -18,18 +18,15 @@
#define QUIT_TIMEOUT_MS 30000
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
m_Address(address),
m_ServerCert(serverCert)
{
Q_ASSERT(!address.isEmpty());
m_BaseUrlHttp.setScheme("http");
m_BaseUrlHttps.setScheme("https");
m_BaseUrlHttp.setHost(address);
m_BaseUrlHttps.setHost(address);
m_BaseUrlHttp.setPort(47989);
m_BaseUrlHttps.setPort(47984);
setAddress(address);
// Never use a proxy server
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
m_Nam.setProxy(noProxy);
@@ -42,6 +39,21 @@ void NvHTTP::setServerCert(QSslCertificate serverCert)
m_ServerCert = serverCert;
}
void NvHTTP::setAddress(QString address)
{
Q_ASSERT(!address.isEmpty());
m_Address = address;
m_BaseUrlHttp.setHost(address);
m_BaseUrlHttps.setHost(address);
}
QString NvHTTP::address()
{
return m_Address;
}
QVector<int>
NvHTTP::parseQuad(QString quad)
{
+4
View File
@@ -156,6 +156,10 @@ public:
void setServerCert(QSslCertificate serverCert);
void setAddress(QString address);
QString address();
static
QVector<int>
parseQuad(QString quad);
+3 -1
View File
@@ -256,7 +256,9 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
challengeResponse.append(clientSecretData);
QByteArray encryptedChallengeResponseHash = encrypt(QCryptographicHash::hash(challengeResponse, hashAlgo), &encKey);
QByteArray paddedHash = QCryptographicHash::hash(challengeResponse, hashAlgo);
paddedHash.resize(32);
QByteArray encryptedChallengeResponseHash = encrypt(paddedHash, &encKey);
QString respXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
"pair",
"devicename=roth&updateState=1&serverchallengeresp=" +
+64
View File
@@ -0,0 +1,64 @@
#include "richpresencemanager.h"
#include <QDebug>
RichPresenceManager::RichPresenceManager(StreamingPreferences& prefs, QString gameName)
: m_DiscordActive(false)
{
#ifdef HAVE_DISCORD
if (prefs.richPresence) {
DiscordEventHandlers handlers = {};
handlers.ready = discordReady;
handlers.disconnected = discordDisconnected;
handlers.errored = discordErrored;
Discord_Initialize("594668102021677159", &handlers, 0, nullptr);
m_DiscordActive = true;
}
if (m_DiscordActive) {
QByteArray stateStr = (QString("Streaming ") + gameName).toUtf8();
DiscordRichPresence discordPresence = {};
discordPresence.state = stateStr.data();
discordPresence.startTimestamp = time(nullptr);
discordPresence.largeImageKey = "icon";
Discord_UpdatePresence(&discordPresence);
}
#endif
}
RichPresenceManager::~RichPresenceManager()
{
#ifdef HAVE_DISCORD
if (m_DiscordActive) {
Discord_ClearPresence();
Discord_Shutdown();
}
#endif
}
void RichPresenceManager::runCallbacks()
{
#ifdef HAVE_DISCORD
if (m_DiscordActive) {
Discord_RunCallbacks();
}
#endif
}
#ifdef HAVE_DISCORD
void RichPresenceManager::discordReady(const DiscordUser* request)
{
qInfo() << "Discord integration ready for user:" << request->username;
}
void RichPresenceManager::discordDisconnected(int errorCode, const char *message)
{
qInfo() << "Discord integration disconnected:" << errorCode << message;
}
void RichPresenceManager::discordErrored(int errorCode, const char *message)
{
qWarning() << "Discord integration error:" << errorCode << message;
}
#endif
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "settings/streamingpreferences.h"
#ifdef HAVE_DISCORD
#include <discord_rpc.h>
#endif
class RichPresenceManager
{
public:
RichPresenceManager(StreamingPreferences& prefs, QString gameName);
~RichPresenceManager();
void runCallbacks();
private:
#ifdef HAVE_DISCORD
static void discordReady(const DiscordUser* request);
static void discordDisconnected(int errorCode, const char* message);
static void discordErrored(int errorCode, const char* message);
#endif
bool m_DiscordActive;
};
+6
View File
@@ -22,6 +22,12 @@ SystemProperties::SystemProperties()
hasBrowser = false;
#endif
#ifdef HAVE_DISCORD
hasDiscordIntegration = true;
#else
hasDiscordIntegration = false;
#endif
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
// Populate data that requires talking to SDL. We do it all in one shot
+2
View File
@@ -15,6 +15,7 @@ public:
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
@@ -32,6 +33,7 @@ private:
bool isRunningXWayland;
bool isWow64;
bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads;
int maximumStreamingFrameRate;
QList<QRect> monitorDesktopResolutions;
@@ -12,7 +12,7 @@
<ul>
<li>Streaming at up to 4K resolution</li>
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
<li>5.1 surround sound audio support</li>
<li>HEVC support for better video compression efficiency</li>
<li>Gamepad support with force feedback for up to 4 players</li>
@@ -33,6 +33,79 @@
</screenshots>
<releases>
<release version="1.2.0" date="2019-12-17">
<description>
<p>Bugfixes:</p>
<ul>
<li>Improved video encoding efficiency</li>
<li>Improved performance when streaming over a VPN</li>
<li>Fixed RTSP handshake error on certain networks</li>
<li>Fixed rumble not working on certain gamepads</li>
<li>Fixed pairing error when double-clicking on an unpaired PC</li>
<li>Updated included SDL gamepad mappings</li>
</ul>
</description>
</release>
<release version="1.1.1" date="2019-09-29">
<description>
<p>Bugfixes:</p>
<ul>
<li>Improved reliability of public IP address detection</li>
<li>Fixed connecting to a host PC with multiple network connections</li>
<li>Fixed a hang after exiting a full-screen stream with GNOME on Wayland</li>
<li>Fixed gamepad hotplugging</li>
<li>Updated to SDL 2.0.10</li>
<li>Updated to Qt 5.13.1</li>
</ul>
</description>
</release>
<release version="1.1.0" date="2019-08-05">
<description>
<p>New features:</p>
<ul>
<li>Added support for NVDEC for NVIDIA GPUs on Wayland</li>
<li>Added an option to disable gamepad mouse mode for games that require holding the Start button</li>
<li>Added automatic detection of IPv6 addresses for Internet streaming over IPv6 networks</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Improved speed and reliability of automatic PC detection on the local network</li>
<li>Fixed audio latency increasing under certain network conditions</li>
<li>Fixed 5.1 surround sound sometimes causing no audio to play when streaming over the Internet</li>
<li>Fixed graceful termination of games not being detected properly on GFE 3.19</li>
<li>Fixed several audio issues on Linux by switching back to SDL for audio</li>
<li>Changed client certificate signature algorithm from SHA-1 to SHA-256 for Debian Buster compatibility</li>
<li>Updated included gamepad mappings</li>
</ul>
</description>
</release>
<release version="1.0.1" date="2019-05-27">
<description>
<p>Bugfixes:</p>
<ul>
<li>Fixed broken video on GeForce Experience 3.19</li>
</ul>
</description>
</release>
<release version="1.0.0" date="2019-05-21">
<description>
<p>New features:</p>
<ul>
<li>Mouse mode for gamepads (Press and hold Start to toggle)</li>
<li>Improved gamepad UI navigation</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Fixed duplicate non-working gamepads appearing on the host</li>
<li>Fixed excessive frame queue delay on macOS</li>
<li>Fixed excessive audio latency on Steam Link</li>
<li>Fixed hiding mouse cursor after streaming on Steam Link</li>
<li>Fixed incorrect receive time stats on 32-bit platforms</li>
<li>Added UDP ports 48002 and 48010 for Wake-on-LAN</li>
<li>Updated included gamepad mappings</li>
</ul>
</description>
</release>
<release version="0.10.1" date="2019-04-28">
<description>
<p>New features:</p>
+8
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls 2.2
import AppModel 1.0
import ComputerManager 1.0
import SdlGamepadKeyNavigation 1.0
CenteredGridView {
property int computerIndex
@@ -32,6 +33,11 @@ CenteredGridView {
StackView.onActivated: {
appModel.computerLost.connect(computerLost)
activated = true
// Highlight the first item if a gamepad is connected
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
currentIndex = 0
}
}
StackView.onDeactivating: {
@@ -145,6 +151,8 @@ CenteredGridView {
text: model.name
width: appIcon.width
height: model.running ? 175 : appIcon.height
leftPadding: 20
rightPadding: 20
anchors.left: appIcon.left
anchors.right: appIcon.right
anchors.bottom: appIcon.bottom
+11
View File
@@ -1,6 +1,8 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import SdlGamepadKeyNavigation 1.0
// https://stackoverflow.com/questions/45029968/how-do-i-set-the-combobox-width-to-fit-the-largest-item
ComboBox {
property int textWidth
@@ -28,4 +30,13 @@ ComboBox {
textWidth = Math.max(popupMetrics.width, textWidth)
}
}
popup.onAboutToShow: {
// Switch to normal navigation for combo boxes
SdlGamepadKeyNavigation.setUiNavMode(false)
}
popup.onAboutToHide: {
SdlGamepadKeyNavigation.setUiNavMode(true)
}
}
+20 -3
View File
@@ -5,6 +5,7 @@ import ComputerModel 1.0
import ComputerManager 1.0
import StreamingPreferences 1.0
import SdlGamepadKeyNavigation 1.0
CenteredGridView {
property ComputerModel computerModel : createModel()
@@ -27,6 +28,16 @@ CenteredGridView {
StackView.onActivated: {
// Setup signals on CM
ComputerManager.computerAddCompleted.connect(addComplete)
// This is a bit of a hack to do this here as opposed to main.qml, but
// we need it enabled before calling getConnectedGamepads() and PcView
// is never destroyed, so it should be okay.
SdlGamepadKeyNavigation.enable()
// Highlight the first item if a gamepad is connected
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
currentIndex = 0
}
}
StackView.onDeactivating: {
@@ -93,7 +104,7 @@ CenteredGridView {
Image {
id: pcIcon
anchors.horizontalCenter: parent.horizontalCenter
source: "qrc:/res/ic_tv_white_48px.svg"
source: "qrc:/res/desktop_windows-48px.svg"
sourceSize {
width: 200
height: 200
@@ -105,7 +116,7 @@ CenteredGridView {
id: stateIcon
anchors.horizontalCenter: pcIcon.horizontalCenter
anchors.verticalCenter: pcIcon.verticalCenter
anchors.verticalCenterOffset: -10
anchors.verticalCenterOffset: -15
visible: !model.statusUnknown && (!model.online || !model.paired)
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
sourceSize {
@@ -118,7 +129,7 @@ CenteredGridView {
id: statusUnknownSpinner
anchors.horizontalCenter: pcIcon.horizontalCenter
anchors.verticalCenter: pcIcon.verticalCenter
anchors.verticalCenterOffset: -10
anchors.verticalCenterOffset: -15
width: 75
height: 75
visible: model.statusUnknown
@@ -224,6 +235,12 @@ CenteredGridView {
NavigableMessageDialog {
id: pairDialog
// Pairing dialog must be modal to prevent double-clicks from triggering
// pairing twice
modal: true
closePolicy: Popup.CloseOnEscape
// don't allow edits to the rest of the window while open
property string pin : "0000"
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
+52 -3
View File
@@ -25,11 +25,18 @@ Flickable {
}
StackView.onActivated: {
SdlGamepadKeyNavigation.setSettingsMode(true)
// This enables Tab and BackTab based navigation rather than arrow keys.
// It is required to shift focus between controls on the settings page.
SdlGamepadKeyNavigation.setUiNavMode(true)
// Highlight the first item if a gamepad is connected
if (SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
resolutionComboBox.forceActiveFocus(Qt.TabFocus)
}
}
StackView.onDeactivating: {
SdlGamepadKeyNavigation.setSettingsMode(false)
SdlGamepadKeyNavigation.setUiNavMode(false)
// Save the prefs so the Session can observe the changes
StreamingPreferences.save()
@@ -321,6 +328,16 @@ Flickable {
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
// Set the recommended option based on the OS
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
if (thisWm === StreamingPreferences.recommendedFullScreenMode) {
windowModeListModel.get(i).text += " (Recommended)"
windowModeListModel.move(i, 0, 1);
break
}
}
var savedWm = StreamingPreferences.windowMode
currentIndex = 0
for (var i = 0; i < windowModeListModel.count; i++) {
@@ -339,7 +356,7 @@ Flickable {
model: ListModel {
id: windowModeListModel
ListElement {
text: "Full-screen (Recommended)"
text: "Full-screen"
val: StreamingPreferences.WM_FULLSCREEN
}
ListElement {
@@ -482,6 +499,22 @@ Flickable {
StreamingPreferences.connectionWarnings = checked
}
}
CheckBox {
visible: SystemProperties.hasDiscordIntegration
id: discordPresenceCheck
text: "Discord Rich Presence integration"
font.pointSize: 12
checked: StreamingPreferences.richPresence
onCheckedChanged: {
StreamingPreferences.richPresence = checked
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: "Updates your Discord status to display the name of the game you're streaming."
}
}
}
}
@@ -534,6 +567,22 @@ Flickable {
ToolTip.visible: hovered
ToolTip.text: "When checked, mouse input is not accelerated or scaled by the OS before passing to Moonlight"
}
CheckBox {
id: gamepadMouseCheck
hoverEnabled: true
text: "Gamepad mouse mode support"
font.pointSize: 12
checked: StreamingPreferences.gamepadMouse
onCheckedChanged: {
StreamingPreferences.gamepadMouse = checked
}
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
ToolTip.text: "When enabled, holding the Start button will toggle mouse mode"
}
}
}
+6 -6
View File
@@ -19,10 +19,6 @@ ApplicationWindow {
visibility: StreamingPreferences.startWindowed ? "Windowed" : "Maximized"
Component.onCompleted: {
SdlGamepadKeyNavigation.enable()
}
StackView {
id: stackView
initialItem: initialView
@@ -243,13 +239,17 @@ ApplicationWindow {
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
ToolTip.visible: hovered || visible
// Invisible until we get a callback notifying us that
// an update is available
visible: false
onClicked: Qt.openUrlExternally(browserUrl);
onClicked: {
if (SystemProperties.hasBrowser) {
Qt.openUrlExternally(browserUrl);
}
}
function updateAvailable(version, url)
{
+8 -36
View File
@@ -10,7 +10,7 @@
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
: m_Enabled(false),
m_SettingsMode(false),
m_UiNavMode(false),
m_LastAxisNavigationEventTime(0)
{
m_PollingTimer = new QTimer(this);
@@ -106,7 +106,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_DPAD_UP:
if (m_SettingsMode) {
if (m_UiNavMode) {
// Back-tab
sendKey(type, Qt::Key_Tab, Qt::ShiftModifier);
}
@@ -115,7 +115,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
}
break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
if (m_SettingsMode) {
if (m_UiNavMode) {
sendKey(type, Qt::Key_Tab);
}
else {
@@ -124,24 +124,12 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
sendKey(type, Qt::Key_Left);
if (m_SettingsMode) {
// Some settings controls respond to left/right (like the slider)
// and others respond to up/down (like combo boxes). They seem to
// be mutually exclusive though so let's just send both.
sendKey(type, Qt::Key_Up);
}
break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
sendKey(type, Qt::Key_Right);
if (m_SettingsMode) {
// Some settings controls respond to left/right (like the slider)
// and others respond to up/down (like combo boxes). They seem to
// be mutually exclusive though so let's just send both.
sendKey(type, Qt::Key_Down);
}
break;
case SDL_CONTROLLER_BUTTON_A:
if (m_SettingsMode) {
if (m_UiNavMode) {
sendKey(type, Qt::Key_Space);
}
else {
@@ -183,7 +171,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
// Do nothing
}
else if (leftY < -30000) {
if (m_SettingsMode) {
if (m_UiNavMode) {
// Back-tab
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab, Qt::ShiftModifier);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab, Qt::ShiftModifier);
@@ -196,7 +184,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
m_LastAxisNavigationEventTime = SDL_GetTicks();
}
else if (leftY > 30000) {
if (m_SettingsMode) {
if (m_UiNavMode) {
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab);
}
@@ -210,27 +198,11 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
else if (leftX < -30000) {
sendKey(QEvent::Type::KeyPress, Qt::Key_Left);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Left);
if (m_SettingsMode) {
// Some settings controls respond to left/right (like the slider)
// and others respond to up/down (like combo boxes). They seem to
// be mutually exclusive though so let's just send both.
sendKey(QEvent::Type::KeyPress, Qt::Key_Up);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Up);
}
m_LastAxisNavigationEventTime = SDL_GetTicks();
}
else if (leftX > 30000) {
sendKey(QEvent::Type::KeyPress, Qt::Key_Right);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Right);
if (m_SettingsMode) {
// Some settings controls respond to left/right (like the slider)
// and others respond to up/down (like combo boxes). They seem to
// be mutually exclusive though so let's just send both.
sendKey(QEvent::Type::KeyPress, Qt::Key_Down);
sendKey(QEvent::Type::KeyRelease, Qt::Key_Down);
}
m_LastAxisNavigationEventTime = SDL_GetTicks();
}
}
@@ -246,9 +218,9 @@ void SdlGamepadKeyNavigation::sendKey(QEvent::Type type, Qt::Key key, Qt::Keyboa
}
}
void SdlGamepadKeyNavigation::setSettingsMode(bool settingsMode)
void SdlGamepadKeyNavigation::setUiNavMode(bool uiNavMode)
{
m_SettingsMode = settingsMode;
m_UiNavMode = uiNavMode;
}
int SdlGamepadKeyNavigation::getConnectedGamepads()
+2 -2
View File
@@ -18,7 +18,7 @@ public:
Q_INVOKABLE void disable();
Q_INVOKABLE void setSettingsMode(bool settingsMode);
Q_INVOKABLE void setUiNavMode(bool settingsMode);
Q_INVOKABLE int getConnectedGamepads();
@@ -32,6 +32,6 @@ private:
QTimer* m_PollingTimer;
QList<SDL_GameController*> m_Gamepads;
bool m_Enabled;
bool m_SettingsMode;
bool m_UiNavMode;
Uint32 m_LastAxisNavigationEventTime;
};
+27 -1
View File
@@ -9,6 +9,7 @@
#include <QPalette>
#include <QFont>
#include <QCursor>
#include <QElapsedTimer>
// Don't let SDL hook our main function, since Qt is already
// doing the same thing. This needs to be before any headers
@@ -52,7 +53,7 @@
#endif
#ifdef USE_CUSTOM_LOGGER
static QTime s_LoggerTime;
static QElapsedTimer s_LoggerTime;
static QTextStream s_LoggerStream(stdout);
static QMutex s_LoggerLock;
#ifdef LOG_TO_FILE
@@ -284,8 +285,14 @@ int main(int argc, char *argv[])
AntiHookingDummyImport();
#endif
// Enable High DPI support
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
// Enable fractional High DPI scaling on Qt 5.14 and later
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// This avoids using the default keychain for SSL, which may cause
// password prompts on macOS.
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
@@ -309,6 +316,18 @@ int main(int argc, char *argv[])
// Register custom metatypes for use in signals
qRegisterMetaType<NvApp>("NvApp");
SDL_version compileVersion;
SDL_VERSION(&compileVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Compiled with SDL %d.%d.%d",
compileVersion.major, compileVersion.minor, compileVersion.patch);
SDL_version runtimeVersion;
SDL_GetVersion(&runtimeVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Running with SDL %d.%d.%d",
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
// initializing the SDL video subsystem to have any effect.
@@ -344,6 +363,13 @@ int main(int argc, char *argv[])
// Disable minimize on focus loss by default. Users seem to want this off by default.
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
#ifdef QT_DEBUG
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
// when throwing the exceptions, so we don't enable it for release builds out
// of caution.
SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "0");
#endif
QGuiApplication app(argc, argv);
// After the QGuiApplication is created, the platform stuff will be initialized
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="48" height="48" viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0V0z"/>
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"/>
</svg>

After

Width:  |  Height:  |  Size: 276 B

@@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 303 B

@@ -1,9 +0,0 @@
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M0 0h24v24H0V0z" id="a"/>
</defs>
<clipPath id="b">
<use overflow="visible" xlink:href="#a"/>
</clipPath>
<path clip-path="url(#b)" d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-7v2H8v-2h8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 467 B

-4
View File
@@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/>
</svg>

Before

Width:  |  Height:  |  Size: 273 B

+1 -3
View File
@@ -1,9 +1,7 @@
<RCC>
<qresource prefix="/">
<file>res/ic_remove_circle_outline_white_48px.svg</file>
<file>res/ic_remove_from_queue_white_48px.svg</file>
<file>res/ic_videogame_asset_white_48px.svg</file>
<file>res/ic_tv_white_48px.svg</file>
<file>res/desktop_windows-48px.svg</file>
<file>res/ic_add_to_queue_white_48px.svg</file>
<file>res/baseline-lock-24px.svg</file>
<file>res/baseline-play_circle_filled_white-48px.svg</file>
+29 -1
View File
@@ -23,6 +23,11 @@
#define SER_STARTWINDOWED "startwindowed"
#define SER_FRAMEPACING "framepacing"
#define SER_CONNWARNINGS "connwarnings"
#define SER_RICHPRESENCE "richpresence"
#define SER_GAMEPADMOUSE "gamepadmouse"
#define SER_DEFAULTVER "defaultver"
#define CURRENT_DEFAULT_VER 1
StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent)
@@ -34,6 +39,14 @@ void StreamingPreferences::reload()
{
QSettings settings;
int defaultVer = settings.value(SER_DEFAULTVER, 0).toInt();
#ifdef Q_OS_DARWIN
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
#else
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN;
#endif
width = settings.value(SER_WIDTH, 1280).toInt();
height = settings.value(SER_HEIGHT, 720).toInt();
fps = settings.value(SER_FPS, 60).toInt();
@@ -49,6 +62,8 @@ void StreamingPreferences::reload()
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
@@ -58,7 +73,17 @@ void StreamingPreferences::reload()
windowMode = static_cast<WindowMode>(settings.value(SER_WINDOWMODE,
// Try to load from the old preference value too
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
WindowMode::WM_FULLSCREEN : WindowMode::WM_WINDOWED)).toInt());
recommendedFullScreenMode : WindowMode::WM_WINDOWED)).toInt());
// Perform default settings updates as required based on last default version
if (defaultVer == 0) {
#ifdef Q_OS_DARWIN
// Update window mode setting on macOS from full-screen (old default) to borderless windowed (new default)
if (windowMode == WindowMode::WM_FULLSCREEN) {
windowMode = WindowMode::WM_FULLSCREEN_DESKTOP;
}
#endif
}
}
void StreamingPreferences::save()
@@ -80,10 +105,13 @@ void StreamingPreferences::save()
settings.setValue(SER_STARTWINDOWED, startWindowed);
settings.setValue(SER_FRAMEPACING, framePacing);
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
settings.setValue(SER_RICHPRESENCE, richPresence);
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
}
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
+8
View File
@@ -64,10 +64,13 @@ public:
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
// Directly accessible members for preferences
int width;
@@ -85,10 +88,13 @@ public:
bool startWindowed;
bool framePacing;
bool connectionWarnings;
bool richPresence;
bool gamepadMouse;
AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig;
VideoDecoderSelection videoDecoderSelection;
WindowMode windowMode;
WindowMode recommendedFullScreenMode;
signals:
void displayModeChanged();
@@ -108,5 +114,7 @@ signals:
void startWindowedChanged();
void framePacingChanged();
void connectionWarningsChanged();
void richPresenceChanged();
void gamepadMouseChanged();
};
+135 -51
View File
@@ -13,34 +13,106 @@
#include <Limelight.h>
IAudioRenderer* Session::createAudioRenderer()
{
#if defined(HAVE_SOUNDIO)
if (qgetenv("ML_AUDIO") == "SDL") {
return new SdlAudioRenderer();
}
return new SoundIoAudioRenderer();
#elif defined(HAVE_SLAUDIO)
return new SLAudioRenderer();
#else
return new SdlAudioRenderer();
#endif
#define TRY_INIT_RENDERER(renderer, opusConfig) \
{ \
IAudioRenderer* __renderer = new renderer(); \
if (__renderer->prepareForPlayback(opusConfig)) \
return __renderer; \
delete __renderer; \
}
bool Session::testAudio(int audioConfiguration)
IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig)
{
IAudioRenderer* audioRenderer;
audioRenderer = createAudioRenderer();
if (audioRenderer == nullptr) {
return false;
// Handle explicit ML_AUDIO setting and fail if the requested backend fails
QString mlAudio = qgetenv("ML_AUDIO").toLower();
if (mlAudio == "sdl") {
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
return nullptr;
}
#ifdef HAVE_SOUNDIO
else if (mlAudio == "libsoundio") {
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
return nullptr;
}
#endif
#if defined(HAVE_SLAUDIO)
else if (mlAudio == "slaudio") {
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
return nullptr;
}
#endif
else if (!mlAudio.isEmpty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unknown audio backend: %s",
SDL_getenv("ML_AUDIO"));
return nullptr;
}
// -------------- Automatic backend selection below this line ---------------
#if defined(HAVE_SLAUDIO)
// Steam Link should always have SLAudio
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
#endif
#if !defined(Q_OS_WIN32) && !defined(Q_OS_DARWIN)
// Linux defaults to SDL due to persistent glitching issues under libsoundio.
// Platforms that libsoundio doesn't support also default to SDL.
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#ifdef HAVE_SOUNDIO
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
#endif
#else
// Windows and macOS default to libsoundio and fall back to SDL
#ifdef HAVE_SOUNDIO
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
#endif
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#endif
return nullptr;
}
int Session::getAudioRendererCapabilities(int audioConfiguration)
{
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
// the renderer the channel count and sample rate.
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
opusConfig.sampleRate = 48000;
opusConfig.samplesPerFrame = 240;
switch (audioConfiguration)
{
case AUDIO_CONFIGURATION_STEREO:
opusConfig.channelCount = 2;
break;
case AUDIO_CONFIGURATION_51_SURROUND:
opusConfig.channelCount = 6;
break;
default:
SDL_assert(false);
return 0;
}
IAudioRenderer* audioRenderer = createAudioRenderer(&opusConfig);
if (audioRenderer == nullptr) {
return 0;
}
int caps = audioRenderer->getCapabilities();
delete audioRenderer;
return caps;
}
bool Session::testAudio(int audioConfiguration)
{
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
// the renderer the channel count and sample rate.
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
opusConfig.sampleRate = 48000;
opusConfig.samplesPerFrame = 240;
switch (audioConfiguration)
{
@@ -55,11 +127,14 @@ bool Session::testAudio(int audioConfiguration)
return false;
}
bool ret = audioRenderer->prepareForPlayback(&opusConfig);
IAudioRenderer* audioRenderer = createAudioRenderer(&opusConfig);
if (audioRenderer == nullptr) {
return false;
}
delete audioRenderer;
return ret;
return true;
}
int Session::arInit(int /* audioConfiguration */,
@@ -70,30 +145,34 @@ int Session::arInit(int /* audioConfiguration */,
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
if (s_ActiveSession->m_AudioRenderer == nullptr) {
return -2;
}
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
// Create the Opus decoder with the renderer's preferred channel mapping
s_ActiveSession->m_OpusDecoder =
opus_multistream_decoder_create(opusConfig->sampleRate,
opusConfig->channelCount,
opusConfig->streams,
opusConfig->coupledStreams,
opusConfig->mapping,
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
s_ActiveSession->m_AudioConfig.channelCount,
s_ActiveSession->m_AudioConfig.streams,
s_ActiveSession->m_AudioConfig.coupledStreams,
s_ActiveSession->m_AudioConfig.mapping,
&error);
if (s_ActiveSession->m_OpusDecoder == NULL) {
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create decoder: %d",
error);
return -1;
}
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
delete s_ActiveSession->m_AudioRenderer;
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
return -2;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio stream has %d channels",
opusConfig->channelCount);
s_ActiveSession->m_AudioConfig.channelCount);
return 0;
}
@@ -143,25 +222,34 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
s_ActiveSession->m_AudioSampleCount++;
if (s_ActiveSession->m_AudioRenderer != nullptr) {
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
if (buffer == nullptr) {
return;
}
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
(unsigned char*)sampleData,
sampleLength,
s_ActiveSession->m_OpusDecodeBuffer,
SAMPLES_PER_FRAME,
(short*)buffer,
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
0);
// Update desiredSize with the number of bytes actually populated by the decoding operation
if (samplesDecoded > 0) {
if (!s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
static_cast<int>(
sizeof(short) *
samplesDecoded *
s_ActiveSession->m_AudioConfig.channelCount))) {
SDL_assert(desiredSize >= sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount);
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
}
else {
desiredSize = 0;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Reinitializing audio renderer after failure");
if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredSize)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Reinitializing audio renderer after failure");
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
}
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
}
}
@@ -175,11 +263,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
// so we return to real-time playback and don't accumulate latency.
Uint32 audioReinitStartTime = SDL_GetTicks();
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(&s_ActiveSession->m_AudioConfig)) {
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
}
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
Uint32 audioReinitStopTime = SDL_GetTicks();
+15 -4
View File
@@ -2,9 +2,6 @@
#include <Limelight.h>
#define MAX_CHANNELS 6
#define SAMPLES_PER_FRAME 240
class IAudioRenderer
{
public:
@@ -12,6 +9,20 @@ public:
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
virtual void* getAudioBuffer(int* size) = 0;
// Return false if an unrecoverable error has occurred and the renderer must be reinitialized
virtual bool submitAudio(short* audioBuffer, int audioSize) = 0;
virtual bool submitAudio(int bytesWritten) = 0;
virtual int getCapabilities() = 0;
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION) {
// Use default channel mapping:
// 0 - Front Left
// 1 - Front Right
// 2 - Center
// 3 - LFE
// 4 - Surround Left
// 5 - Surround Right
}
};
+7 -1
View File
@@ -12,8 +12,14 @@ public:
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
virtual bool submitAudio(short* audioBuffer, int audioSize);
virtual void* getAudioBuffer(int* size);
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
private:
SDL_AudioDeviceID m_AudioDevice;
void* m_AudioBuffer;
int m_FrameSize;
};
+46 -5
View File
@@ -6,7 +6,8 @@
#include <QtGlobal>
SdlAudioRenderer::SdlAudioRenderer()
: m_AudioDevice(0)
: m_AudioDevice(0),
m_AudioBuffer(nullptr)
{
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
@@ -30,14 +31,22 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
// frames contain a non-power of 2 number of samples,
// so the slop would require buffering another full frame.
// Specifying non-Po2 seems to work for our supported platforms.
want.samples = SAMPLES_PER_FRAME;
want.samples = opusConfig->samplesPerFrame;
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (m_AudioDevice == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open audio device: %s",
SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return false;
}
m_AudioBuffer = malloc(m_FrameSize);
if (m_AudioBuffer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to allocate audio buffer");
return false;
}
@@ -65,13 +74,39 @@ SdlAudioRenderer::~SdlAudioRenderer()
SDL_CloseAudioDevice(m_AudioDevice);
}
if (m_AudioBuffer != nullptr) {
free(m_AudioBuffer);
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
}
bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
void* SdlAudioRenderer::getAudioBuffer(int*)
{
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
return m_AudioBuffer;
}
bool SdlAudioRenderer::submitAudio(int bytesWritten)
{
if (bytesWritten == 0) {
// Nothing to do
return true;
}
// Don't queue if there's already more than 30 ms of audio data waiting
// in Moonlight's audio queue.
if (LiGetPendingAudioDuration() > 30) {
return true;
}
// Provide backpressure on the queue to ensure too many frames don't build up
// in SDL's audio queue.
while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
SDL_Delay(1);
}
if (SDL_QueueAudio(m_AudioDevice, m_AudioBuffer, bytesWritten) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to queue audio sample: %s",
SDL_GetError());
@@ -79,3 +114,9 @@ bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
return true;
}
int SdlAudioRenderer::getCapabilities()
{
// Direct submit can't be used because we use LiGetPendingAudioDuration()
return CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
}
+60 -30
View File
@@ -2,16 +2,10 @@
#include <SDL.h>
// To reduce CPU load on the Steam Link, we need to accumulate several frames
// before submitting for playback. Higher frames per submission saves more CPU
// but increases audio latency.
#define FRAMES_PER_SUBMISSION 4
SLAudioRenderer::SLAudioRenderer()
: m_AudioContext(nullptr),
m_AudioStream(nullptr),
m_AudioBuffer(nullptr),
m_AudioBufferBytesFilled(0)
m_AudioBuffer(nullptr)
{
SLAudio_SetLogFunction(SLAudioRenderer::slLogCallback, nullptr);
}
@@ -25,7 +19,11 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
return false;
}
m_AudioBufferSize = SAMPLES_PER_FRAME * sizeof(short) * opusConfig->channelCount * FRAMES_PER_SUBMISSION;
// This number is pretty conservative (especially for surround), but
// it's hard to avoid since we get crushed by CPU limitations.
m_MaxQueuedAudioMs = 40 * opusConfig->channelCount / 2;
m_AudioBufferSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
opusConfig->sampleRate,
opusConfig->channelCount,
@@ -38,17 +36,49 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using SLAudio renderer");
"Using SLAudio renderer with %d samples per frame",
opusConfig->samplesPerFrame);
return true;
}
#define SWAP_CHANNEL(i, j) \
tmp = opusConfig->mapping[i]; \
opusConfig->mapping[i] = opusConfig->mapping[j]; \
opusConfig->mapping[j] = tmp
void SLAudioRenderer::remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
unsigned char tmp;
if (opusConfig->channelCount == 6) {
// The Moonlight's default channel order is FL,FR,C,LFE,RL,RR
// SLAudio expects FL,C,FR,RL,RR,LFE so we swap the channels around to match
// Swap FR and C - now FL,C,FR,LFE,RL,RR
SWAP_CHANNEL(1, 2);
// Swap LFE and RR - now FL,C,FR,RR,RL,LFE
SWAP_CHANNEL(3, 5);
// Swap RR and RL - now FL,C,FR,RL,RR,LFE
SWAP_CHANNEL(3, 4);
}
}
void* SLAudioRenderer::getAudioBuffer(int* size)
{
SDL_assert(*size == m_AudioBufferSize);
if (m_AudioBuffer == nullptr) {
m_AudioBuffer = SLAudio_BeginFrame(m_AudioStream);
}
return m_AudioBuffer;
}
SLAudioRenderer::~SLAudioRenderer()
{
if (m_AudioBufferBytesFilled != 0) {
// We had a buffer in flight when we quit. Just in case
// SLAudio doesn't handle this properly, we'll zero and submit
// it just to be safe.
if (m_AudioBuffer != nullptr) {
memset(m_AudioBuffer, 0, m_AudioBufferSize);
SLAudio_SubmitFrame(m_AudioStream);
}
@@ -62,32 +92,32 @@ SLAudioRenderer::~SLAudioRenderer()
}
}
bool SLAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
bool SLAudioRenderer::submitAudio(int bytesWritten)
{
if (m_AudioBufferBytesFilled == 0) {
// Get a new audio buffer from SLAudio
m_AudioBuffer = (char*)SLAudio_BeginFrame(m_AudioStream);
if (m_AudioBuffer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SLAudio_BeginFrame() failed");
return true;
}
if (bytesWritten == 0) {
// This buffer will be reused next time
return true;
}
// Accumulate several frames of audio before submitting to reduce CPU load
SDL_assert(audioSize <= m_AudioBufferSize - m_AudioBufferBytesFilled);
memcpy(&m_AudioBuffer[m_AudioBufferBytesFilled], audioBuffer, audioSize);
m_AudioBufferBytesFilled += audioSize;
// Submit the buffer when it's full
if (m_AudioBufferBytesFilled == m_AudioBufferSize) {
if (LiGetPendingAudioDuration() < m_MaxQueuedAudioMs) {
SLAudio_SubmitFrame(m_AudioStream);
m_AudioBufferBytesFilled = 0;
m_AudioBuffer = nullptr;
}
else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Too many queued audio frames: %d",
LiGetPendingAudioFrames());
}
return true;
}
void SLAudioRenderer::slLogCallback(void *context, ESLAudioLog logLevel, const char *message)
int SLAudioRenderer::getCapabilities()
{
return CAPABILITY_SLOW_OPUS_DECODER | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
}
void SLAudioRenderer::slLogCallback(void*, ESLAudioLog logLevel, const char *message)
{
SDL_LogPriority priority;
+10 -3
View File
@@ -12,14 +12,21 @@ public:
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
virtual bool submitAudio(short* audioBuffer, int audioSize);
virtual void* getAudioBuffer(int* size);
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig);
private:
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
CSLAudioContext* m_AudioContext;
CSLAudioStream* m_AudioStream;
char* m_AudioBuffer;
void* m_AudioBuffer;
int m_AudioBufferSize;
int m_AudioBufferBytesFilled;
int m_MaxQueuedAudioMs;
};
@@ -4,29 +4,13 @@
#include <QtGlobal>
// GFE sends us packets in 5 ms chunks
const double SoundIoAudioRenderer::k_RawSampleLengthSec = 0.005;
#ifdef Q_OS_LINUX
// PulseAudio and ALSA require more than just 5 ms samples
// for some reason, so write a minimum of 25 ms each time to
// prevent underruns on Bluetooth.
// https://github.com/moonlight-stream/moonlight-qt/issues/147
// https://github.com/moonlight-stream/moonlight-qt/issues/157
const double SoundIoAudioRenderer::k_MinSampleLengthSec = 0.025;
#else
// This determines the size of the buffers we'll
// get from CoreAudio. It is also the minimum
// size that we will write when called to fill a buffer.
const double SoundIoAudioRenderer::k_MinSampleLengthSec = k_RawSampleLengthSec;
#endif
SoundIoAudioRenderer::SoundIoAudioRenderer()
: m_OpusChannelCount(0),
m_SoundIo(nullptr),
m_Device(nullptr),
m_OutputStream(nullptr),
m_RingBuffer(nullptr),
m_AudioPacketDuration(0),
m_Latency(0),
m_Errored(false)
{
@@ -172,9 +156,11 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
return false;
}
m_AudioPacketDuration = (opusConfig->samplesPerFrame / (opusConfig->sampleRate / 1000)) / 1000.0;
m_OutputStream->format = SoundIoFormatS16NE;
m_OutputStream->sample_rate = opusConfig->sampleRate;
m_OutputStream->software_latency = k_MinSampleLengthSec;
m_OutputStream->software_latency = m_AudioPacketDuration;
m_OutputStream->name = "Moonlight";
m_OutputStream->userdata = this;
m_OutputStream->error_callback = sioErrorCallback;
@@ -237,30 +223,26 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
int packetsToBuffer;
#ifdef Q_OS_LINUX
// PulseAudio and ALSA need the large buffer (see comment on k_MinSampleLengthSec),
// so we need a buffer at least double that size to allow packets to arrive
// while we're writing to the sink.
packetsToBuffer = (int)(k_MinSampleLengthSec / k_RawSampleLengthSec) * 2;
#else
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
// 15 ms buffer seems to be fine for WASAPI
packetsToBuffer = 3;
packetsToBuffer = (int)ceil(0.015 / m_AudioPacketDuration);
}
else {
// 30 ms buffer on CoreAudio to avoid glitching on macOS
packetsToBuffer = 6;
packetsToBuffer = (int)ceil(0.030 / m_AudioPacketDuration);
}
#endif
// Always buffer at least 2 packets
packetsToBuffer = qMax(2, packetsToBuffer);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio buffer size: %d packets",
packetsToBuffer);
"Audio buffer size: %f seconds",
packetsToBuffer * m_AudioPacketDuration);
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
m_OutputStream->bytes_per_sample *
m_OpusChannelCount *
SAMPLES_PER_FRAME *
opusConfig->samplesPerFrame *
packetsToBuffer);
if (m_RingBuffer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -284,15 +266,8 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
return true;
}
bool SoundIoAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
void* SoundIoAudioRenderer::getAudioBuffer(int* size)
{
if (m_Errored) {
return false;
}
// Flush events to update with new device arrivals
soundio_flush_events(m_SoundIo);
// We must always write a full frame of audio. If we don't,
// the reader will get out of sync with the writer and our
// channels will get all mixed up. To ensure this is always
@@ -300,13 +275,36 @@ bool SoundIoAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
// of our frame size.
int bytesFree = soundio_ring_buffer_free_count(m_RingBuffer);
int bytesPerFrame = m_OpusChannelCount * m_OutputStream->bytes_per_sample;
int bytesToWrite = qMin(audioSize, (bytesFree / bytesPerFrame) * bytesPerFrame);
memcpy(soundio_ring_buffer_write_ptr(m_RingBuffer), audioBuffer, bytesToWrite);
soundio_ring_buffer_advance_write_ptr(m_RingBuffer, bytesToWrite);
*size = qMin(*size, (bytesFree / bytesPerFrame) * bytesPerFrame);
return soundio_ring_buffer_write_ptr(m_RingBuffer);
}
bool SoundIoAudioRenderer::submitAudio(int bytesWritten)
{
if (m_Errored) {
return false;
}
if (bytesWritten == 0) {
// Nothing to do
return true;
}
// Flush events to update with new device arrivals
soundio_flush_events(m_SoundIo);
// Advance the write pointer
soundio_ring_buffer_advance_write_ptr(m_RingBuffer, bytesWritten);
return true;
}
int SoundIoAudioRenderer::getCapabilities()
{
// TODO: Tweak buffer sizes then re-enable arbitrary audio duration
return CAPABILITY_DIRECT_SUBMIT /* | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION */;
}
void SoundIoAudioRenderer::sioErrorCallback(SoundIoOutStream* stream, int err)
{
auto me = reinterpret_cast<SoundIoAudioRenderer*>(stream->userdata);
@@ -371,15 +369,19 @@ void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameC
(me->m_OpusChannelCount * stream->bytes_per_sample);
int bytesRead = 0;
// Clamp framesLeft to frameCountMax
framesLeft = qMin(framesLeft, frameCountMax);
// Ensure we always write at least a buffer, even if it's silence, to avoid
// busy looping when no audio data is available while libsoundio tries to keep
// us from starving the output device.
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_MinSampleLengthSec));
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * me->m_AudioPacketDuration));
// Clamp frameCountMax to at least 2 packets or 20 ms to stop our latency from growing if audio packets lag.
// This makes sure that we never increase our latency far beyond what the sink is consuming.
frameCountMax = qMin(frameCountMax, (int)(stream->sample_rate * qMax(me->m_AudioPacketDuration * 2, 0.020)));
frameCountMin = qMin(frameCountMin, frameCountMax);
// Clamp framesLeft to frameCountMax
framesLeft = qMin(framesLeft, frameCountMax);
// Track latency on queueing-based backends
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
soundio_outstream_get_latency(stream, &me->m_Latency);
@@ -13,7 +13,11 @@ public:
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
virtual bool submitAudio(short* audioBuffer, int audioSize);
virtual void* getAudioBuffer(int* size);
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
private:
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
@@ -32,9 +36,7 @@ private:
struct SoundIoOutStream* m_OutputStream;
struct SoundIoRingBuffer* m_RingBuffer;
struct SoundIoChannelLayout m_EffectiveLayout;
double m_AudioPacketDuration;
double m_Latency;
bool m_Errored;
static const double k_RawSampleLengthSec;
static const double k_MinSampleLengthSec;
};
+241 -14
View File
@@ -5,6 +5,7 @@
#include "path.h"
#include <QtGlobal>
#include <QtMath>
#include <QDir>
#define VK_0 0x30
@@ -28,6 +29,22 @@
// How far the finger can move before it cancels a drag or tap
#define DEAD_ZONE_DELTA 0.1f
// How long the Start button must be pressed to toggle mouse emulation
#define MOUSE_EMULATION_LONG_PRESS_TIME 750
// How long between polling the gamepad to send virtual mouse input
#define MOUSE_EMULATION_POLLING_INTERVAL 50
// Determines how fast the mouse will move each interval
#define MOUSE_EMULATION_MOTION_MULTIPLIER 4
// Determines the maximum motion amount before allowing movement
#define MOUSE_EMULATION_DEADZONE 2
// Haptic capabilities (in addition to those from SDL_HapticQuery())
#define ML_HAPTIC_GC_RUMBLE (1U << 16)
#define ML_HAPTIC_SIMPLE_RUMBLE (1U << 17)
const int SdlInputHandler::k_ButtonMap[] = {
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
@@ -38,7 +55,9 @@ const int SdlInputHandler::k_ButtonMap[] = {
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
: m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse),
m_MouseMoveTimer(0),
m_FakeCaptureActive(false),
m_LeftButtonReleaseTimer(0),
m_RightButtonReleaseTimer(0),
m_DragTimer(0),
@@ -58,9 +77,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
prefs.mouseAcceleration ? "1" : "0",
SDL_HINT_OVERRIDE);
#ifdef Q_OS_DARWIN
#if defined(Q_OS_DARWIN) && !SDL_VERSION_ATLEAST(2, 0, 10)
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS.
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS until
// SDL 2.0.10 where the bug is fixed.
// https://github.com/moonlight-stream/moonlight-qt/issues/133
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
@@ -81,6 +101,12 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
MappingManager mappingManager;
mappingManager.applyMappings();
// Flush gamepad arrival and departure events which may be queued before
// starting the gamecontroller subsystem again. This prevents us from
// receiving duplicate arrival and departure events for the same gamepad.
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED);
// We need to reinit this each time, since you only get
// an initial set of gamepad arrival events once per init.
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
@@ -90,12 +116,14 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
SDL_GetError());
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s",
SDL_GetError());
}
#endif
// Initialize the gamepad mask with currently attached gamepads to avoid
// causing gamepads to unexpectedly disappear and reappear on the host
@@ -115,9 +143,15 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
SdlInputHandler::~SdlInputHandler()
{
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].mouseEmulationTimer != 0) {
Session::get()->notifyMouseEmulationMode(false);
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (m_GamepadState[i].haptic != nullptr) {
SDL_HapticClose(m_GamepadState[i].haptic);
}
#endif
if (m_GamepadState[i].controller != nullptr) {
SDL_GameControllerClose(m_GamepadState[i].controller);
}
@@ -128,8 +162,10 @@ SdlInputHandler::~SdlInputHandler()
SDL_RemoveTimer(m_RightButtonReleaseTimer);
SDL_RemoveTimer(m_DragTimer);
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
#endif
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
@@ -139,6 +175,14 @@ SdlInputHandler::~SdlInputHandler()
// Return background event handling to off
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
#ifdef STEAM_LINK
// Hide SDL's cursor on Steam Link after quitting the stream.
// FIXME: We should also do this for other situations where SDL
// and Qt will draw their own mouse cursors like KMSDRM or RPi
// video backends.
SDL_ShowCursor(SDL_DISABLE);
#endif
}
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
@@ -180,7 +224,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
"Detected mouse capture toggle combo (SDLK)");
// Stop handling future input
SDL_SetRelativeMouseMode((SDL_bool)!SDL_GetRelativeMouseMode());
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
@@ -228,7 +272,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
"Detected mouse capture toggle combo (scancode)");
// Stop handling future input
SDL_SetRelativeMouseMode((SDL_bool)!SDL_GetRelativeMouseMode());
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
@@ -261,7 +305,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
}
}
if (!SDL_GetRelativeMouseMode()) {
if (!isCaptureActive()) {
// Not capturing
return;
}
@@ -507,11 +551,11 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
// the pressed event was consumed by this code).
if (event->button == SDL_BUTTON_LEFT &&
event->state == SDL_RELEASED &&
!SDL_GetRelativeMouseMode()) {
SDL_SetRelativeMouseMode(SDL_TRUE);
!isCaptureActive()) {
setCaptureActive(true);
return;
}
else if (!SDL_GetRelativeMouseMode()) {
else if (!isCaptureActive()) {
// Not capturing
return;
}
@@ -552,7 +596,7 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
{
if (!SDL_GetRelativeMouseMode()) {
if (!isCaptureActive()) {
// Not capturing
return;
}
@@ -569,7 +613,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
{
if (!SDL_GetRelativeMouseMode()) {
if (!isCaptureActive()) {
// Not capturing
return;
}
@@ -658,6 +702,41 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
return interval;
}
Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param)
{
auto gamepad = reinterpret_cast<GamepadState*>(param);
short rawX;
short rawY;
// Determine which analog stick is currently receiving the strongest input
if ((uint32_t)qAbs(gamepad->lsX) + qAbs(gamepad->lsY) > (uint32_t)qAbs(gamepad->rsX) + qAbs(gamepad->rsY)) {
rawX = gamepad->lsX;
rawY = -gamepad->lsY;
}
else {
rawX = gamepad->rsX;
rawY = -gamepad->rsY;
}
float deltaX;
float deltaY;
// Produce a base vector for mouse movement with increased speed as we deviate further from center
deltaX = qPow(rawX / 32766.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
deltaY = qPow(rawY / 32766.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
// Enforce deadzones
deltaX = qAbs(deltaX) > MOUSE_EMULATION_DEADZONE ? deltaX - MOUSE_EMULATION_DEADZONE : 0;
deltaY = qAbs(deltaY) > MOUSE_EMULATION_DEADZONE ? deltaY - MOUSE_EMULATION_DEADZONE : 0;
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent((short)deltaX, (short)deltaY);
}
return interval;
}
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
{
SDL_JoystickID gameControllerId = event->which;
@@ -716,7 +795,10 @@ void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERAXISMOTION);
}
sendGamepadState(state);
// Only send the gamepad state to the host if it's not in mouse emulation mode
if (state->mouseEmulationTimer == 0) {
sendGamepadState(state);
}
}
void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* event)
@@ -728,9 +810,76 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
if (event->state == SDL_PRESSED) {
state->buttons |= k_ButtonMap[event->button];
if (event->button == SDL_CONTROLLER_BUTTON_START) {
state->lastStartDownTime = SDL_GetTicks();
}
else if (state->mouseEmulationTimer != 0) {
if (event->button == SDL_CONTROLLER_BUTTON_A) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_MIDDLE);
}
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X2);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
LiSendScrollEvent(1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
LiSendScrollEvent(-1);
}
}
}
else {
state->buttons &= ~k_ButtonMap[event->button];
if (event->button == SDL_CONTROLLER_BUTTON_START) {
if (SDL_GetTicks() - state->lastStartDownTime > MOUSE_EMULATION_LONG_PRESS_TIME) {
if (state->mouseEmulationTimer != 0) {
SDL_RemoveTimer(state->mouseEmulationTimer);
state->mouseEmulationTimer = 0;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Mouse emulation deactivated");
Session::get()->notifyMouseEmulationMode(false);
}
else if (m_GamepadMouse) {
// Send the start button up event to the host, since we won't do it below
sendGamepadState(state);
state->mouseEmulationTimer = SDL_AddTimer(MOUSE_EMULATION_POLLING_INTERVAL, SdlInputHandler::mouseEmulationTimerCallback, state);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Mouse emulation active");
Session::get()->notifyMouseEmulationMode(true);
}
}
}
else if (state->mouseEmulationTimer != 0) {
if (event->button == SDL_CONTROLLER_BUTTON_A) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
}
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X2);
}
}
}
// Handle Start+Select+L1+R1 as a gamepad quit combo
@@ -750,7 +899,10 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
return;
}
sendGamepadState(state);
// Only send the gamepad state to the host if it's not in mouse emulation mode
if (state->mouseEmulationTimer == 0) {
sendGamepadState(state);
}
}
void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
@@ -763,6 +915,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
SDL_GameController* controller;
const char* mapping;
char guidStr[33];
uint32_t hapticCaps;
controller = SDL_GameControllerOpen(event->which);
if (controller == NULL) {
@@ -780,6 +933,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
i = 0;
for (; i < MAX_GAMEPADS; i++) {
SDL_assert(m_GamepadState[i].controller != controller);
if (m_GamepadState[i].controller == NULL) {
// Found an empty slot
break;
@@ -804,10 +958,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
state->controller = controller;
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
#if SDL_VERSION_ATLEAST(2, 0, 9)
// Perform a no-op rumble to see if haptics are supported
hapticCaps = SDL_GameControllerRumble(controller, 0, 0, 0) == 0 ?
ML_HAPTIC_GC_RUMBLE : 0;
#else
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
state->hapticEffectId = -1;
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_NONE;
if (state->haptic != nullptr) {
// Query for supported haptic effects
hapticCaps = SDL_HapticQuery(state->haptic);
hapticCaps |= SDL_HapticRumbleSupported(state->haptic) ?
ML_HAPTIC_SIMPLE_RUMBLE : 0;
if ((SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
if (SDL_HapticRumbleSupported(state->haptic)) {
if (SDL_HapticRumbleInit(state->haptic) == 0) {
@@ -822,16 +987,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_LEFTRIGHT;
}
}
else {
hapticCaps = 0;
}
#endif
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
guidStr, sizeof(guidStr));
mapping = SDL_GameControllerMapping(state->controller);
name = SDL_GameControllerName(state->controller);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Gamepad %d (player %d) is: %s (%s -> %s)",
"Gamepad %d (player %d) is: %s (haptic capabilities: 0x%x) (mapping: %s -> %s)",
i,
state->index,
name != nullptr ? name : "<null>",
hapticCaps,
guidStr,
mapping != nullptr ? mapping : "<null>");
if (mapping != nullptr) {
@@ -855,10 +1025,18 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
state = findStateForGamepad(event->which);
if (state != NULL) {
if (state->mouseEmulationTimer != 0) {
Session::get()->notifyMouseEmulationMode(false);
SDL_RemoveTimer(state->mouseEmulationTimer);
}
SDL_GameControllerClose(state->controller);
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (state->haptic != nullptr) {
SDL_HapticClose(state->haptic);
}
#endif
// Remove this from the gamepad mask in MC-mode
if (m_MultiController) {
@@ -919,6 +1097,11 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
return;
}
#if SDL_VERSION_ATLEAST(2, 0, 9)
if (m_GamepadState[controllerNumber].controller != nullptr) {
SDL_GameControllerRumble(m_GamepadState[controllerNumber].controller, lowFreqMotor, highFreqMotor, 30000);
}
#else
// Check if the controller supports haptics (and if the controller exists at all)
SDL_Haptic* haptic = m_GamepadState[controllerNumber].haptic;
if (haptic == nullptr) {
@@ -962,13 +1145,28 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT*lowFreqMotor) / 65535.0),
SDL_HAPTIC_INFINITY);
}
#endif
}
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
{
int fingerIndex = -1;
#if SDL_VERSION_ATLEAST(2, 0, 10)
if (SDL_GetTouchDeviceType(event->touchId) != SDL_TOUCH_DEVICE_DIRECT) {
// Ignore anything that isn't a touchscreen. We may get callbacks
// for trackpads, but we want to handle those in the mouse path.
return;
}
#elif defined(Q_OS_DARWIN)
// SDL2 sends touch events from trackpads by default on
// macOS. This totally screws our actual mouse handling,
// so we must explicitly ignore touch events on macOS
// until SDL 2.0.10 where we have SDL_GetTouchDeviceType()
// to tell them apart.
return;
#endif
// Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area.
// Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher
// or lower than those values as touch events continue for touches started within the client area that
@@ -1137,6 +1335,35 @@ void SdlInputHandler::raiseAllKeys()
m_KeysDown.clear();
}
bool SdlInputHandler::isCaptureActive()
{
if (SDL_GetRelativeMouseMode()) {
return true;
}
// Some platforms don't support SDL_SetRelativeMouseMode
return m_FakeCaptureActive;
}
void SdlInputHandler::setCaptureActive(bool active)
{
if (active) {
// Try to activate SDL's relative mouse mode
if (SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
// Relative mouse mode didn't work, so we'll use fake capture
SDL_ShowCursor(SDL_DISABLE);
m_FakeCaptureActive = true;
}
}
else if (m_FakeCaptureActive) {
SDL_ShowCursor(SDL_ENABLE);
m_FakeCaptureActive = false;
}
else {
SDL_SetRelativeMouseMode(SDL_FALSE);
}
}
QString SdlInputHandler::getUnmappedGamepads()
{
QString ret;
+16 -1
View File
@@ -8,10 +8,16 @@
struct GamepadState {
SDL_GameController* controller;
SDL_JoystickID jsId;
short index;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_Haptic* haptic;
int hapticMethod;
int hapticEffectId;
short index;
#endif
SDL_TimerID mouseEmulationTimer;
uint32_t lastStartDownTime;
short buttons;
short lsX, lsY;
@@ -61,6 +67,10 @@ public:
void raiseAllKeys();
bool isCaptureActive();
void setCaptureActive(bool active);
static
QString getUnmappedGamepads();
@@ -82,13 +92,18 @@ private:
static
Uint32 mouseMoveTimerCallback(Uint32 interval, void* param);
static
Uint32 mouseEmulationTimerCallback(Uint32 interval, void* param);
bool m_MultiController;
bool m_GamepadMouse;
SDL_TimerID m_MouseMoveTimer;
SDL_atomic_t m_MouseDeltaX;
SDL_atomic_t m_MouseDeltaY;
int m_GamepadMask;
GamepadState m_GamepadState[MAX_GAMEPADS];
QSet<short> m_KeysDown;
bool m_FakeCaptureActive;
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
float m_CumulativeDelta[MAX_FINGERS];
+103 -58
View File
@@ -1,6 +1,7 @@
#include "session.h"
#include "settings/streamingpreferences.h"
#include "streaming/streamutils.h"
#include "backend/richpresencemanager.h"
#include <Limelight.h>
#include <SDL.h>
@@ -43,20 +44,6 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
Session::clConnectionStatusUpdate
};
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
Session::arInit,
nullptr,
nullptr,
Session::arCleanup,
Session::arDecodeAndPlaySample,
CAPABILITY_DIRECT_SUBMIT |
#ifdef STEAM_LINK
CAPABILITY_SLOW_OPUS_DECODER
#else
0
#endif
};
Session* Session::s_ActiveSession;
QSemaphore Session::s_ActiveSessionSemaphore(1);
@@ -130,6 +117,11 @@ void Session::clConnectionStatusUpdate(int connectionStatus)
return;
}
if (s_ActiveSession->m_MouseEmulationRefCount > 0) {
// Don't display the overlay if mouse emulation is already using it
return;
}
switch (connectionStatus)
{
case CONN_STATUS_POOR:
@@ -275,21 +267,28 @@ bool Session::isHardwareDecodeAvailable(SDL_Window* window,
return ret;
}
int Session::getDecoderCapabilities(SDL_Window* window,
StreamingPreferences::VideoDecoderSelection vds,
int videoFormat, int width, int height, int frameRate)
bool Session::populateDecoderProperties(SDL_Window* window)
{
IVideoDecoder* decoder;
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
if (!chooseDecoder(m_Preferences->videoDecoderSelection,
window,
m_StreamConfig.enableHdr ? VIDEO_FORMAT_H265_MAIN10 :
(m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264),
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps,
true, false, true, decoder)) {
return false;
}
int caps = decoder->getDecoderCapabilities();
m_VideoCallbacks.capabilities = decoder->getDecoderCapabilities();
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
delete decoder;
return caps;
return true;
}
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
@@ -307,6 +306,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
m_InputHandler(nullptr),
m_InputHandlerLock(0),
m_MouseEmulationRefCount(0),
m_OpusDecoder(nullptr),
m_AudioRenderer(nullptr),
m_AudioSampleCount(0),
@@ -350,21 +350,12 @@ bool Session::initialize()
m_VideoCallbacks.setup = drSetup;
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
// Slice up to 4 times for parallel decode, once slice per core
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Encoder configured for %d slices per frame",
slices);
LiInitializeStreamConfiguration(&m_StreamConfig);
m_StreamConfig.width = m_Preferences->width;
m_StreamConfig.height = m_Preferences->height;
m_StreamConfig.fps = m_Preferences->fps;
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
m_StreamConfig.packetSize = 1392;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Video bitrate: %d kbps",
@@ -386,6 +377,12 @@ bool Session::initialize()
break;
}
LiInitializeAudioCallbacks(&m_AudioCallbacks);
m_AudioCallbacks.init = arInit;
m_AudioCallbacks.cleanup = arCleanup;
m_AudioCallbacks.decodeAndPlaySample = arDecodeAndPlaySample;
m_AudioCallbacks.capabilities = getAudioRendererCapabilities(m_StreamConfig.audioConfiguration);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio configuration: %d",
m_StreamConfig.audioConfiguration);
@@ -433,22 +430,13 @@ bool Session::initialize()
break;
}
// Add the capability flags from the chosen decoder/renderer
// Requires m_StreamConfig.supportsHevc to be initialized
m_VideoCallbacks.capabilities |= getDecoderCapabilities(testWindow,
m_Preferences->videoDecoderSelection,
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps);
switch (m_Preferences->windowMode)
{
default:
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
break;
case StreamingPreferences::WM_FULLSCREEN:
default:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
break;
}
@@ -457,6 +445,12 @@ bool Session::initialize()
// signals for them, if appropriate
bool ret = validateLaunch(testWindow);
if (ret) {
// Populate decoder-dependent properties.
// Must be done after validateLaunch() since m_StreamConfig is finalized.
ret = populateDecoderProperties(testWindow);
}
SDL_DestroyWindow(testWindow);
if (!ret) {
@@ -503,13 +497,14 @@ bool Session::validateLaunch(SDL_Window* testWindow)
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
if (!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
VIDEO_FORMAT_H265,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps) &&
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO) {
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Already checked in initialize()
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
VIDEO_FORMAT_H265,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps)) {
if (hevcForced) {
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
}
@@ -595,6 +590,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
}
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
!m_StreamConfig.enableHdr && // HEVC Main10 was already checked for hardware decode support above
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
@@ -825,6 +821,22 @@ void Session::toggleFullscreen()
}
}
void Session::notifyMouseEmulationMode(bool enabled)
{
m_MouseEmulationRefCount += enabled ? 1 : -1;
SDL_assert(m_MouseEmulationRefCount >= 0);
// We re-use the status update overlay for mouse mode notification
if (m_MouseEmulationRefCount > 0) {
strcpy(m_OverlayManager.getOverlayText(Overlay::OverlayStatusUpdate), "Gamepad mouse mode active\nLong press Start to deactivate");
m_OverlayManager.setOverlayTextUpdated(Overlay::OverlayStatusUpdate);
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
}
else {
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
}
}
void Session::exec(int displayOriginX, int displayOriginY)
{
m_DisplayOriginX = displayOriginX;
@@ -922,9 +934,22 @@ void Session::exec(int displayOriginX, int displayOriginY)
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
}
// isReachableOverVpn() does network I/O, so we only attempt to check
// VPN reachability if we've already contacted the PC successfully
if (m_Computer->isReachableOverVpn()) {
// It looks like our route to this PC is over a VPN.
// Treat it as remote even if the target address is in RFC 1918 address space.
m_StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
m_StreamConfig.packetSize = 1024;
}
else {
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
m_StreamConfig.packetSize = 1392;
}
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
&m_VideoCallbacks,
m_AudioDisabled ? nullptr : &k_AudioCallbacks,
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
NULL, 0, NULL, 0);
if (err != 0) {
// We already displayed an error dialog in the stage failure
@@ -1009,7 +1034,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
// This prevents the mouse from becoming trapped inside
// Moonlight when it's halted at a debug break.
if (m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED) {
SDL_SetRelativeMouseMode(SDL_TRUE);
// HACK: This doesn't work on Wayland until we render a frame, so
// just don't do it for now.
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) {
m_InputHandler->setCaptureActive(true);
}
}
#endif
@@ -1033,6 +1062,9 @@ void Session::exec(int displayOriginX, int displayOriginY)
// (m_UnexpectedTermination is set back to true).
m_UnexpectedTermination = false;
// Start rich presence to indicate we're in game
RichPresenceManager presence(prefs, m_App.name);
// Hijack this thread to be the SDL main thread. We have to do this
// because we want to suspend all Qt processing until the stream is over.
SDL_Event event;
@@ -1049,6 +1081,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
// ARM core in the Steam Link, so we will wait 10 ms instead.
SDL_Delay(10);
#endif
presence.runCallbacks();
continue;
}
switch (event.type) {
@@ -1082,20 +1115,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
SDL_GetWindowPosition(m_Window, &x, &y);
SDL_GetWindowSize(m_Window, &width, &height);
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
SDL_SetRelativeMouseMode(SDL_TRUE);
m_InputHandler->setCaptureActive(true);
}
}
}
#endif
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
// This lets user to interact with our window's title bar and with the buttons in it.
// Doing this while the window is full-screen breaks the transition out of FS
// (desktop and exclusive), so we must check for that before releasing mouse capture.
if (!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN)) {
SDL_SetRelativeMouseMode(SDL_FALSE);
m_InputHandler->setCaptureActive(false);
}
// Raise all keys that are currently pressed. If we don't do this, certain keys
@@ -1217,24 +1249,18 @@ void Session::exec(int displayOriginX, int displayOriginY)
case SDL_JOYDEVICEADDED:
m_InputHandler->handleJoystickArrivalEvent(&event.jdevice);
break;
// SDL2 sends touch events from trackpads by default on
// macOS. This totally screws our actual mouse handling,
// so we must explicitly ignore touch events on macOS.
#ifndef Q_OS_DARWIN
case SDL_FINGERDOWN:
case SDL_FINGERMOTION:
case SDL_FINGERUP:
m_InputHandler->handleTouchFingerEvent(&event.tfinger);
break;
#endif
}
}
DispatchDeferredCleanup:
// Uncapture the mouse and hide the window immediately,
// so we can return to the Qt GUI ASAP.
SDL_SetRelativeMouseMode(SDL_FALSE);
m_InputHandler->setCaptureActive(false);
SDL_EnableScreenSaver();
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
@@ -1256,6 +1282,25 @@ DispatchDeferredCleanup:
m_VideoDecoder = nullptr;
SDL_AtomicUnlock(&m_DecoderLock);
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
// HACK: SDL (as of 2.0.10) has a bug that causes Mutter not to destroy the window
// surface when in full-screen unless we render more frames after we request
// to exit full-screen. The amount of frames required is variable but 500 ms
// of frames seems sufficient in my testing.
SDL_SetWindowFullscreen(m_Window, 0);
SDL_Renderer* renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
if (renderer != nullptr) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
for (int i = 0; i < 10; i++)
{
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
SDL_Delay(50);
}
SDL_DestroyRenderer(renderer);
}
}
// This must be called after the decoder is deleted, because
// the renderer may want to interact with the window
SDL_DestroyWindow(m_Window);
+8 -9
View File
@@ -61,22 +61,21 @@ private:
void emitLaunchWarning(QString text);
static
int getDecoderCapabilities(SDL_Window* window,
StreamingPreferences::VideoDecoderSelection vds,
int videoFormat, int width, int height, int frameRate);
bool populateDecoderProperties(SDL_Window* window);
IAudioRenderer* createAudioRenderer();
int detectAudioConfiguration();
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
bool testAudio(int audioConfiguration);
int getAudioRendererCapabilities(int audioConfiguration);
void getWindowDimensions(int& x, int& y,
int& width, int& height);
void toggleFullscreen();
void notifyMouseEmulationMode(bool enabled);
void updateOptimalWindowDisplayMode();
static
@@ -127,6 +126,7 @@ private:
StreamingPreferences* m_Preferences;
STREAM_CONFIGURATION m_StreamConfig;
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
AUDIO_RENDERER_CALLBACKS m_AudioCallbacks;
NvComputer* m_Computer;
NvApp m_App;
SDL_Window* m_Window;
@@ -141,6 +141,7 @@ private:
bool m_UnexpectedTermination;
SdlInputHandler* m_InputHandler;
SDL_SpinLock m_InputHandlerLock;
int m_MouseEmulationRefCount;
int m_ActiveVideoFormat;
int m_ActiveVideoWidth;
@@ -148,7 +149,6 @@ private:
int m_ActiveVideoFrameRate;
OpusMSDecoder* m_OpusDecoder;
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
IAudioRenderer* m_AudioRenderer;
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
int m_AudioSampleCount;
@@ -156,7 +156,6 @@ private:
Overlay::OverlayManager m_OverlayManager;
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
static Session* s_ActiveSession;
static QSemaphore s_ActiveSessionSemaphore;
+1
View File
@@ -44,6 +44,7 @@ public:
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
virtual bool isHardwareAccelerated() = 0;
virtual int getDecoderCapabilities() = 0;
virtual int getDecoderColorspace() = 0;
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
virtual void renderFrameOnMainThread() = 0;
};
@@ -0,0 +1,57 @@
#include "cuda.h"
CUDARenderer::CUDARenderer()
: m_HwContext(nullptr)
{
}
CUDARenderer::~CUDARenderer()
{
if (m_HwContext != nullptr) {
av_buffer_unref(&m_HwContext);
}
}
bool CUDARenderer::initialize(PDECODER_PARAMETERS)
{
int err;
err = av_hwdevice_ctx_create(&m_HwContext, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 0);
if (err != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"av_hwdevice_ctx_create(CUDA) failed: %d",
err);
return false;
}
return true;
}
bool CUDARenderer::prepareDecoderContext(AVCodecContext* context)
{
context->hw_device_ctx = av_buffer_ref(m_HwContext);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using CUDA accelerated decoder");
return true;
}
void CUDARenderer::renderFrame(AVFrame*)
{
// We only support indirect rendering
SDL_assert(false);
}
bool CUDARenderer::needsTestFrame()
{
return true;
}
bool CUDARenderer::isDirectRenderingSupported()
{
// We only support rendering via SDL read-back
return false;
}
@@ -0,0 +1,18 @@
#pragma once
#include "renderer.h"
class CUDARenderer : public IFFmpegRenderer {
public:
CUDARenderer();
virtual ~CUDARenderer() override;
virtual bool initialize(PDECODER_PARAMETERS) override;
virtual bool prepareDecoderContext(AVCodecContext* context) override;
virtual void renderFrame(AVFrame* frame) override;
virtual bool needsTestFrame() override;
virtual bool isDirectRenderingSupported() override;
private:
AVBufferRef* m_HwContext;
};
+32 -4
View File
@@ -382,9 +382,6 @@ bool DXVA2Renderer::isDecoderBlacklisted()
HRESULT hr;
bool result = false;
// TODO: Update for HEVC Main10
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
if (qgetenv("DXVA2_DISABLE_DECODER_BLACKLIST") == "1") {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"DXVA2 decoder blacklist is disabled");
@@ -432,6 +429,15 @@ bool DXVA2Renderer::isDecoderBlacklisted()
"GPU blacklisted for HEVC due to hybrid decode");
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
break;
case 0x1900: // Skylake
// Blacklist these for HEVC Main10 to avoid hybrid decode.
// Regular HEVC Main is fine though.
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"GPU blacklisted for HEVC Main10 due to hybrid decode");
result = false;
break;
}
default:
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
@@ -545,6 +551,8 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
d3dpp.BackBufferWidth = currentMode.Width;
d3dpp.BackBufferHeight = currentMode.Height;
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
// TODO: Need to select 10-bit color format for HDR
d3dpp.BackBufferFormat = currentMode.Format;
}
else {
@@ -680,7 +688,13 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0');
}
else {
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
}
if (!initializeDevice(params->window, params->enableVsync)) {
return false;
@@ -762,6 +776,20 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
}
}
int DXVA2Renderer::getDecoderColorspace()
{
if (isDXVideoProcessorAPIBlacklisted()) {
// StretchRect() assumes Rec 601 on Intel GPUs
return COLORSPACE_REC_601;
}
else {
// VideoProcessBlt() *should* properly handle whatever, since
// we provide colorspace information. However, AMD GPUs seem to
// always assume Rec 709, so we'll use that as our default.
return COLORSPACE_REC_709;
}
}
void DXVA2Renderer::renderFrame(AVFrame *frame)
{
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
@@ -20,6 +20,7 @@ public:
virtual bool prepareDecoderContext(AVCodecContext* context) override;
virtual void renderFrame(AVFrame* frame) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual int getDecoderColorspace() override;
private:
bool initializeDecoder();
@@ -1,11 +0,0 @@
#pragma once
#include "pacer.h"
class DisplayLinkVsyncSourceFactory
{
public:
static
IVsyncSource* createVsyncSource(Pacer* pacer);
};
@@ -1,132 +0,0 @@
#include "displaylinkvsyncsource.h"
#include <SDL_syswm.h>
#include <CoreVideo/CoreVideo.h>
#import <Cocoa/Cocoa.h>
class DisplayLinkVsyncSource : public IVsyncSource
{
public:
DisplayLinkVsyncSource(Pacer* pacer)
: m_Pacer(pacer),
m_DisplayLink(nullptr)
{
}
virtual ~DisplayLinkVsyncSource() override
{
if (m_DisplayLink != nullptr) {
CVDisplayLinkStop(m_DisplayLink);
CVDisplayLinkRelease(m_DisplayLink);
}
}
static
CGDirectDisplayID
getDisplayID(NSScreen* screen)
{
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
return [screenNumber unsignedIntValue];
}
static
CVReturn
displayLinkOutputCallback(
CVDisplayLinkRef displayLink,
const CVTimeStamp* /* now */,
const CVTimeStamp* /* vsyncTime */,
CVOptionFlags,
CVOptionFlags*,
void *displayLinkContext)
{
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
SDL_assert(displayLink == me->m_DisplayLink);
// In my testing on macOS 10.13, this callback is invoked about 24 ms
// prior to the specified v-sync time (now - vsyncTime). Since this is
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
// draw using the current host time, rather than the actual v-sync target
// time. Because the CVDisplayLink is in sync with the actual v-sync
// interval, even if many ms prior, we can safely use the current host time
// and get a consistent callback for each v-sync. This reduces video latency
// by at least 1 frame vs. rendering with the actual vsyncTime.
me->m_Pacer->vsyncCallback(500 / me->m_DisplayFps);
return kCVReturnSuccess;
}
virtual bool initialize(SDL_Window* window, int displayFps) override
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if (!SDL_GetWindowWMInfo(window, &info)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetWindowWMInfo() failed: %s",
SDL_GetError());
return false;
}
SDL_assert(info.subsystem == SDL_SYSWM_COCOA);
m_DisplayFps = displayFps;
NSScreen* screen = [info.info.cocoa.window screen];
CVReturn status;
if (screen == nullptr) {
// Window not visible on any display, so use a
// CVDisplayLink that can work with all active displays.
// When we become visible, we'll recreate ourselves
// and associate with the new screen.
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"NSWindow is not visible on any display");
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
}
else {
CGDirectDisplayID displayId;
displayId = getDisplayID(screen);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"NSWindow on display: %x",
displayId);
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
}
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create CVDisplayLink: %d",
status);
return false;
}
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CVDisplayLinkSetOutputCallback() failed: %d",
status);
return false;
}
status = CVDisplayLinkStart(m_DisplayLink);
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CVDisplayLinkStart() failed: %d",
status);
return false;
}
return true;
}
private:
Pacer* m_Pacer;
CVDisplayLinkRef m_DisplayLink;
int m_DisplayFps;
};
IVsyncSource* DisplayLinkVsyncSourceFactory::createVsyncSource(Pacer* pacer) {
return new DisplayLinkVsyncSource(pacer);
}
@@ -61,7 +61,7 @@ bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
m_Window = info.info.win.window;
m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this);
m_Thread = SDL_CreateThread(vsyncThread, "DXVsync", this);
if (m_Thread == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unable to create DX V-sync thread: %s",
@@ -18,7 +18,7 @@ NullThreadedVsyncSource::~NullThreadedVsyncSource()
bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
{
m_DisplayFps = displayFps;
m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this);
m_Thread = SDL_CreateThread(vsyncThread, "NullVsync", this);
if (m_Thread == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unable to create DX V-sync thread: %s",
@@ -3,10 +3,6 @@
#include "nullthreadedvsyncsource.h"
#ifdef Q_OS_DARWIN
#include "displaylinkvsyncsource.h"
#endif
#ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@@ -230,9 +226,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
"Frame pacing active: target %d Hz with %d FPS stream",
m_DisplayFps, m_MaxVideoFps);
#if defined(Q_OS_DARWIN)
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
#elif defined(Q_OS_WIN32)
#if defined(Q_OS_WIN32)
// Don't use D3DKMTWaitForVerticalBlankEvent() on Windows 7, because
// it blocks during other concurrent DX operations (like actually rendering).
if (IsWindows8OrGreater()) {
@@ -254,7 +248,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
}
if (m_VsyncRenderer->isRenderThreadSupported()) {
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this);
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "PacerRender", this);
}
return true;
@@ -264,7 +258,7 @@ void Pacer::renderFrame(AVFrame* frame)
{
// Count time spent in Pacer's queues
Uint32 beforeRender = SDL_GetTicks();
m_VideoStats->totalPacerTime += beforeRender - frame->pts;
m_VideoStats->totalPacerTime += beforeRender - frame->pkt_dts;
// Render it
m_VsyncRenderer->renderFrame(frame);
@@ -31,6 +31,11 @@ public:
return 0;
}
virtual int getDecoderColorspace() {
// Rec 601 is default
return COLORSPACE_REC_601;
}
virtual FramePacingConstraint getFramePacingConstraint() {
// No pacing preference
return PACING_ANY;
@@ -47,9 +52,14 @@ public:
}
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
// Planar YUV 4:2:0
SDL_assert(videoFormat != VIDEO_FORMAT_H265_MAIN10);
return AV_PIX_FMT_YUV420P;
if (videoFormat == VIDEO_FORMAT_H265_MAIN10) {
// 10-bit YUV 4:2:0
return AV_PIX_FMT_P010;
}
else {
// Planar YUV 4:2:0
return AV_PIX_FMT_YUV420P;
}
}
// IOverlayRenderer
+48 -29
View File
@@ -7,15 +7,10 @@
#include <Limelight.h>
const std::vector<int> SdlRenderer::k_SwFormats({
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_NV12
});
SdlRenderer::SdlRenderer()
: m_Renderer(nullptr),
m_Texture(nullptr),
m_SwPixelFormat(AV_PIX_FMT_YUV420P),
m_SwPixelFormat(AV_PIX_FMT_NONE),
m_FontData(Path::readDataFile("ModeSeven.ttf"))
{
SDL_assert(TTF_WasInit() == 0);
@@ -73,13 +68,6 @@ bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
return true;
}
int SdlRenderer::getDecoderCapabilities()
{
// The FFmpeg CPU decoder can handle reference frame invalidation,
// but only for H.264.
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
}
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
{
// Construct the required font to render the overlay
@@ -139,10 +127,25 @@ bool SdlRenderer::isRenderThreadSupported()
return true;
}
int SdlRenderer::getDecoderCapabilities()
{
// Slice up to 4 times for parallel decode, once slice per core
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Encoder configured for %d slices per frame",
slices);
return CAPABILITY_SLICES_PER_FRAME(slices);
}
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
{
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
// SDL doesn't support rendering YUV 10-bit textures yet
return false;
}
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
@@ -238,6 +241,18 @@ void SdlRenderer::renderFrame(AVFrame* frame)
// accelerated decoder, we'll need to read the frame
// back to render it.
// Find the native read-back format
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
m_SwPixelFormat = hwFrameCtx->sw_format;
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Selected read-back format: %d",
m_SwPixelFormat);
}
swFrame = av_frame_alloc();
if (swFrame == nullptr) {
return;
@@ -248,22 +263,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
swFrame->format = m_SwPixelFormat;
err = av_hwframe_transfer_data(swFrame, frame, 0);
if (err != 0) {
// Try to find a supported format
for (uint32_t i = 0; i < k_SwFormats.size(); i++) {
swFrame->format = k_SwFormats.at(i);
err = av_hwframe_transfer_data(swFrame, frame, 0);
if (err == 0) {
// The format was supported. Use that automatically in the future.
m_SwPixelFormat = swFrame->format;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Selected read-back format: %d",
m_SwPixelFormat);
break;
}
}
}
if (err != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"av_hwframe_transfer_data() failed: %d",
@@ -271,6 +270,11 @@ void SdlRenderer::renderFrame(AVFrame* frame)
goto Exit;
}
// av_hwframe_transfer_data() can nuke frame metadata,
// so anything other than width, height, and format must
// be set *after* calling av_hwframe_transfer_data().
swFrame->colorspace = frame->colorspace;
frame = swFrame;
}
@@ -285,11 +289,26 @@ void SdlRenderer::renderFrame(AVFrame* frame)
case AV_PIX_FMT_NV12:
sdlFormat = SDL_PIXELFORMAT_NV12;
break;
case AV_PIX_FMT_NV21:
sdlFormat = SDL_PIXELFORMAT_NV21;
break;
default:
SDL_assert(false);
goto Exit;
}
switch (frame->colorspace)
{
case AVCOL_SPC_BT709:
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
break;
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
default:
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
break;
}
m_Texture = SDL_CreateTexture(m_Renderer,
sdlFormat,
SDL_TEXTUREACCESS_STREAMING,
@@ -11,9 +11,9 @@ public:
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context) override;
virtual void renderFrame(AVFrame* frame) override;
virtual int getDecoderCapabilities() override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual bool isRenderThreadSupported() override;
virtual int getDecoderCapabilities() override;
private:
void renderOverlay(Overlay::OverlayType type);
@@ -26,7 +26,5 @@ private:
SDL_Surface* m_OverlaySurfaces[Overlay::OverlayMax];
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
static const std::vector<int> k_SwFormats;
};
+72 -3
View File
@@ -5,8 +5,12 @@
#include <SDL_syswm.h>
#include <unistd.h>
#include <fcntl.h>
VAAPIRenderer::VAAPIRenderer()
: m_HwContext(nullptr)
: m_HwContext(nullptr),
m_DrmFd(-1)
{
}
@@ -26,6 +30,10 @@ VAAPIRenderer::~VAAPIRenderer()
vaTerminate(display);
}
}
if (m_DrmFd != -1) {
close(m_DrmFd);
}
}
bool
@@ -86,6 +94,39 @@ VAAPIRenderer::initialize(PDECODER_PARAMETERS params)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Moonlight not compiled with VAAPI Wayland support!");
return false;
#endif
}
// TODO: Upstream a better solution for SDL_GetWindowWMInfo on KMSDRM
else if (strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
#ifdef HAVE_LIBVA_DRM
const char* device = SDL_getenv("DRM_DEV");
if (device == nullptr) {
device = "/dev/dri/card0";
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Opening DRM device: %s",
device);
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
if (m_DrmFd < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open DRM device: %d",
errno);
return false;
}
vaDeviceContext->display = vaGetDisplayDRM(m_DrmFd);
if (!vaDeviceContext->display) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unable to open DRM display for VAAPI");
return false;
}
#else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Moonlight not compiled with VAAPI DRM support!");
return false;
#endif
}
else {
@@ -157,7 +198,7 @@ VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using VAAPI accelerated renderer on %s",
m_WindowSystem == SDL_SYSWM_X11 ? "X11" : "Wayland");
SDL_GetCurrentVideoDriver());
return true;
}
@@ -178,6 +219,13 @@ VAAPIRenderer::isDirectRenderingSupported()
return m_WindowSystem == SDL_SYSWM_X11;
}
int VAAPIRenderer::getDecoderColorspace()
{
// Gallium drivers don't support Rec 709 yet - https://gitlab.freedesktop.org/mesa/mesa/issues/1915
// Intel-vaapi-driver defaults to Rec 601 - https://github.com/intel/intel-vaapi-driver/blob/021bcb79d1bd873bbd9fbca55f40320344bab866/src/i965_output_dri.c#L186
return COLORSPACE_REC_601;
}
void
VAAPIRenderer::renderFrame(AVFrame* frame)
{
@@ -197,6 +245,27 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
if (m_WindowSystem == SDL_SYSWM_X11) {
#ifdef HAVE_LIBVA_X11
unsigned int flags = 0;
// NB: Not all VAAPI drivers respect these flags. Many drivers
// just ignore them and do the color conversion as Rec 601.
switch (frame->colorspace) {
case AVCOL_SPC_BT709:
flags |= VA_SRC_BT709;
break;
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
flags |= VA_SRC_BT601;
break;
case AVCOL_SPC_SMPTE240M:
flags |= VA_SRC_SMPTE_240;
break;
default:
// Unknown colorspace
SDL_assert(false);
break;
}
vaPutSurface(vaDeviceContext->display,
surface,
m_XWindow,
@@ -204,7 +273,7 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
m_VideoWidth, m_VideoHeight,
dst.x, dst.y,
dst.w, dst.h,
NULL, 0, 0);
NULL, 0, flags);
#endif
}
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
@@ -22,6 +22,9 @@ extern "C" {
#ifdef HAVE_LIBVA_WAYLAND
#include <va/va_wayland.h>
#endif
#ifdef HAVE_LIBVA_DRM
#include <va/va_drm.h>
#endif
#include <libavutil/hwcontext_vaapi.h>
}
@@ -35,10 +38,12 @@ public:
virtual void renderFrame(AVFrame* frame) override;
virtual bool needsTestFrame() override;
virtual bool isDirectRenderingSupported() override;
virtual int getDecoderColorspace() override;
private:
int m_WindowSystem;
AVBufferRef* m_HwContext;
int m_DrmFd;
#ifdef HAVE_LIBVA_X11
Window m_XWindow;
+21 -3
View File
@@ -12,11 +12,16 @@
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
BAIL_ON_FAIL(status, id)
const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = {
const VdpRGBAFormat VDPAURenderer::k_OutputFormats8Bit[] = {
VDP_RGBA_FORMAT_B8G8R8A8,
VDP_RGBA_FORMAT_R8G8B8A8
};
const VdpRGBAFormat VDPAURenderer::k_OutputFormats10Bit[] = {
VDP_RGBA_FORMAT_B10G10R10A2,
VDP_RGBA_FORMAT_R10G10B10A2
};
VDPAURenderer::VDPAURenderer()
: m_HwContext(nullptr),
m_PresentationQueueTarget(0),
@@ -143,7 +148,11 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
VdpBool supported;
uint32_t maxWidth, maxHeight;
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, k_OutputFormats[i],
VdpRGBAFormat candidateFormat =
params->videoFormat == VIDEO_FORMAT_H265_MAIN10 ?
k_OutputFormats10Bit[i] : k_OutputFormats8Bit[i];
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, candidateFormat,
&supported, &maxWidth, &maxHeight);
if (status != VDP_STATUS_OK) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -154,7 +163,7 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
if (supported) {
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
m_OutputSurfaceFormat = k_OutputFormats[i];
m_OutputSurfaceFormat = candidateFormat;
foundFormat = true;
break;
}
@@ -241,6 +250,15 @@ bool VDPAURenderer::needsTestFrame()
return true;
}
int VDPAURenderer::getDecoderColorspace()
{
// VDPAU defaults to Rec 601.
// https://http.download.nvidia.com/XFree86/vdpau/doxygen/html/group___vdp_video_mixer.html#ga65580813e9045d94b739ed2bb8b62b46
//
// AMD and Nvidia GPUs both correctly process Rec 601, so let's not try our luck using a non-default colorspace.
return COLORSPACE_REC_601;
}
void VDPAURenderer::renderFrame(AVFrame* frame)
{
VdpStatus status;
+3 -1
View File
@@ -17,6 +17,7 @@ public:
virtual bool prepareDecoderContext(AVCodecContext* context) override;
virtual void renderFrame(AVFrame* frame) override;
virtual bool needsTestFrame() override;
virtual int getDecoderColorspace() override;
private:
uint32_t m_VideoWidth, m_VideoHeight;
@@ -33,7 +34,8 @@ private:
int m_NextSurfaceIndex;
#define OUTPUT_SURFACE_FORMAT_COUNT 2
static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT];
static const VdpRGBAFormat k_OutputFormats8Bit[OUTPUT_SURFACE_FORMAT_COUNT];
static const VdpRGBAFormat k_OutputFormats10Bit[OUTPUT_SURFACE_FORMAT_COUNT];
VdpGetErrorString* m_VdpGetErrorString;
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
+155 -9
View File
@@ -14,6 +14,7 @@
#import <VideoToolbox/VideoToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <dispatch/dispatch.h>
#import <Metal/Metal.h>
class VTRenderer : public IFFmpegRenderer
{
@@ -22,13 +23,29 @@ public:
: m_HwContext(nullptr),
m_DisplayLayer(nullptr),
m_FormatDesc(nullptr),
m_StreamView(nullptr)
m_StreamView(nullptr),
m_DisplayLink(nullptr),
m_VsyncMutex(nullptr),
m_VsyncPassed(nullptr)
{
SDL_zero(m_OverlayTextFields);
}
virtual ~VTRenderer() override
{
if (m_DisplayLink != nullptr) {
CVDisplayLinkStop(m_DisplayLink);
CVDisplayLinkRelease(m_DisplayLink);
}
if (m_VsyncPassed != nullptr) {
SDL_DestroyCond(m_VsyncPassed);
}
if (m_VsyncMutex != nullptr) {
SDL_DestroyMutex(m_VsyncMutex);
}
if (m_HwContext != nullptr) {
av_buffer_unref(&m_HwContext);
}
@@ -48,6 +65,78 @@ public:
}
}
static
CVReturn
displayLinkOutputCallback(
CVDisplayLinkRef displayLink,
const CVTimeStamp* /* now */,
const CVTimeStamp* /* vsyncTime */,
CVOptionFlags,
CVOptionFlags*,
void *displayLinkContext)
{
auto me = reinterpret_cast<VTRenderer*>(displayLinkContext);
SDL_assert(displayLink == me->m_DisplayLink);
SDL_LockMutex(me->m_VsyncMutex);
SDL_CondSignal(me->m_VsyncPassed);
SDL_UnlockMutex(me->m_VsyncMutex);
return kCVReturnSuccess;
}
bool initializeVsyncCallback(SDL_SysWMinfo* info)
{
NSScreen* screen = [info->info.cocoa.window screen];
CVReturn status;
if (screen == nullptr) {
// Window not visible on any display, so use a
// CVDisplayLink that can work with all active displays.
// When we become visible, we'll recreate ourselves
// and associate with the new screen.
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"NSWindow is not visible on any display");
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
}
else {
CGDirectDisplayID displayId = [[screen deviceDescription][@"NSScreenNumber"] unsignedIntValue];
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"NSWindow on display: %x",
displayId);
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
}
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create CVDisplayLink: %d",
status);
return false;
}
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CVDisplayLinkSetOutputCallback() failed: %d",
status);
return false;
}
// The CVDisplayLink callback uses these, so we must initialize them before
// starting the callbacks.
m_VsyncMutex = SDL_CreateMutex();
m_VsyncPassed = SDL_CreateCond();
status = CVDisplayLinkStart(m_DisplayLink);
if (status != kCVReturnSuccess) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CVDisplayLinkStart() failed: %d",
status);
return false;
}
return true;
}
// Caller frees frame after we return
virtual void renderFrame(AVFrame* frame) override
{
@@ -104,6 +193,16 @@ public:
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
CFRelease(sampleBuffer);
if (m_DisplayLink != nullptr) {
// Vsync is enabled, so wait for a swap before returning
SDL_LockMutex(m_VsyncMutex);
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"V-sync wait timed out after 100 ms");
}
SDL_UnlockMutex(m_VsyncMutex);
}
}
virtual bool initialize(PDECODER_PARAMETERS params) override
@@ -136,6 +235,52 @@ public:
"No HW accelerated HEVC decode via VT");
return false;
}
// HEVC Main10 requires more extensive checks because there's no
// simple API to check for Main10 hardware decoding, and if we don't
// have it, we'll silently get software decoding with horrible performance.
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
if (__builtin_available(macOS 10.14, *)) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (device == nullptr) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Unable to get default Metal device");
return false;
}
// Exclude all GPUs earlier than macOSGPUFamily2
// https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
if ([device.name containsString:@"Intel"]) {
// 500-series Intel GPUs are Skylake and don't support Main10 hardware decoding
if ([device.name containsString:@" 5"]) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No HEVC Main10 support on Skylake iGPU");
return false;
}
}
else if ([device.name containsString:@"AMD"]) {
// FirePro D, M200, and M300 series GPUs don't support Main10 hardware decoding
if ([device.name containsString:@"FirePro D"] ||
[device.name containsString:@" M2"] ||
[device.name containsString:@" M3"]) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No HEVC Main10 support on AMD GPUs until Polaris");
return false;
}
}
}
}
else
#endif
{
// Fail closed for HEVC Main10 if we're not on 10.14+
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No HEVC Main10 support on < macOS 10.14");
return false;
}
}
}
else
#endif
@@ -191,6 +336,12 @@ public:
return false;
}
if (params->enableVsync) {
if (!initializeVsyncCallback(&info)) {
return false;
}
}
return true;
}
@@ -278,20 +429,15 @@ public:
return true;
}
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 PACING_FORCE_ON;
}
private:
AVBufferRef* m_HwContext;
AVSampleBufferDisplayLayer* m_DisplayLayer;
CMVideoFormatDescriptionRef m_FormatDesc;
NSView* m_StreamView;
NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
CVDisplayLinkRef m_DisplayLink;
SDL_mutex* m_VsyncMutex;
SDL_cond* m_VsyncPassed;
};
IFFmpegRenderer* VTRendererFactory::createRenderer() {
+86 -23
View File
@@ -6,6 +6,7 @@
#include <h264_stream.h>
#include "ffmpeg-renderers/sdlvid.h"
#include "ffmpeg-renderers/cuda.h"
#ifdef Q_OS_WIN32
#include "ffmpeg-renderers/dxva2.h"
@@ -49,6 +50,11 @@ int FFmpegVideoDecoder::getDecoderCapabilities()
return m_BackendRenderer->getDecoderCapabilities();
}
int FFmpegVideoDecoder::getDecoderColorspace()
{
return m_FrontendRenderer->getDecoderColorspace();
}
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
const enum AVPixelFormat* pixFmts)
{
@@ -212,6 +218,15 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
// Report decoding errors to allow us to request a key frame
//
// With HEVC streams, FFmpeg can drop a frame (hwaccel->start_frame() fails)
// without telling us. Since we have an infinite GOP length, this causes artifacts
// on screen that persist for a long time. It's easy to cause this condition
// by using NVDEC and delaying 100 ms randomly in the render path so the decoder
// runs out of output buffers.
m_VideoDecoderCtx->err_recognition = AV_EF_EXPLODE;
// Enable slice multi-threading for software decoding
if (!isHardwareAccelerated()) {
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
@@ -254,13 +269,24 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
// now to see if things will actually work when the video stream
// comes in.
if (testFrame) {
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
switch (params->videoFormat) {
case VIDEO_FORMAT_H264:
m_Pkt.data = (uint8_t*)k_H264TestFrame;
m_Pkt.size = sizeof(k_H264TestFrame);
}
else {
m_Pkt.data = (uint8_t*)k_HEVCTestFrame;
m_Pkt.size = sizeof(k_HEVCTestFrame);
break;
case VIDEO_FORMAT_H265:
m_Pkt.data = (uint8_t*)k_HEVCMainTestFrame;
m_Pkt.size = sizeof(k_HEVCMainTestFrame);
break;
case VIDEO_FORMAT_H265_MAIN10:
m_Pkt.data = (uint8_t*)k_HEVCMain10TestFrame;
m_Pkt.size = sizeof(k_HEVCMain10TestFrame);
break;
default:
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"No test frame for format: %x",
params->videoFormat);
return false;
}
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
@@ -369,31 +395,49 @@ void FFmpegVideoDecoder::logVideoStats(VIDEO_STATS& stats, const char* title)
}
}
IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg)
IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg, int pass)
{
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
return nullptr;
}
// Look for acceleration types we support
switch (hwDecodeCfg->device_type) {
// First pass using our top-tier hwaccel implementations
if (pass == 0) {
switch (hwDecodeCfg->device_type) {
#ifdef Q_OS_WIN32
case AV_HWDEVICE_TYPE_DXVA2:
return new DXVA2Renderer();
case AV_HWDEVICE_TYPE_DXVA2:
return new DXVA2Renderer();
#endif
#ifdef Q_OS_DARWIN
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
return VTRendererFactory::createRenderer();
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
return VTRendererFactory::createRenderer();
#endif
#ifdef HAVE_LIBVA
case AV_HWDEVICE_TYPE_VAAPI:
return new VAAPIRenderer();
case AV_HWDEVICE_TYPE_VAAPI:
return new VAAPIRenderer();
#endif
#ifdef HAVE_LIBVDPAU
case AV_HWDEVICE_TYPE_VDPAU:
return new VDPAURenderer();
case AV_HWDEVICE_TYPE_VDPAU:
return new VDPAURenderer();
#endif
default:
default:
return nullptr;
}
}
// Second pass for our second-tier hwaccel implementations
else if (pass == 1) {
switch (hwDecodeCfg->device_type) {
case AV_HWDEVICE_TYPE_CUDA:
// CUDA should only be used if all other options fail, since it requires
// read-back of frames. This should only be used for the NVIDIA+Wayland case
// with VDPAU covering the NVIDIA+X11 scenario.
return new CUDARenderer();
default:
return nullptr;
}
}
else {
SDL_assert(false);
return nullptr;
}
}
@@ -469,7 +513,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
// Look for a hardware decoder first unless software-only
if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) {
// Look for the first matching hwaccel hardware decoder
// Look for the first matching hwaccel hardware decoder (pass 0)
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (!config) {
@@ -479,7 +523,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
// Initialize the hardware codec and submit a test frame if the renderer needs it
if (tryInitializeRenderer(decoder, params, config,
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config); })) {
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) {
return true;
}
}
@@ -515,6 +559,22 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
return true;
}
#endif
// Look for the first matching hwaccel hardware decoder (pass 1)
// This picks up "second-tier" hwaccels like CUDA.
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (!config) {
// No remaing hwaccel options
break;
}
// Initialize the hardware codec and submit a test frame if the renderer needs it
if (tryInitializeRenderer(decoder, params, config,
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 1); })) {
return true;
}
}
}
// Fallback to software if no matching hardware decoder was found
@@ -620,9 +680,9 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
// Add some extra space in case we need to do an SPS fixup
requiredBufferSize += MAX_SPS_EXTRA_SIZE;
}
if (requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE > m_DecodeBuffer.length()) {
m_DecodeBuffer = QByteArray(requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE, 0);
}
// Ensure the decoder buffer is large enough
m_DecodeBuffer.reserve(requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
int offset = 0;
while (entry != nullptr) {
@@ -676,8 +736,11 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
// Restore default log level after a successful decode
av_log_set_level(AV_LOG_INFO);
// Store the presentation time
frame->pts = du->presentationTimeMs;
// Capture a frame timestamp to measuring pacing delay
frame->pts = SDL_GetTicks();
frame->pkt_dts = SDL_GetTicks();
// Count time in avcodec_send_packet() and avcodec_receive_frame()
// as time spent decoding
+4 -2
View File
@@ -17,6 +17,7 @@ public:
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool isHardwareAccelerated() override;
virtual int getDecoderCapabilities() override;
virtual int getDecoderColorspace() override;
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
virtual void renderFrameOnMainThread() override;
@@ -38,7 +39,7 @@ private:
const AVCodecHWConfig* hwConfig,
std::function<IFFmpegRenderer*()> createRendererFunc);
static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg);
static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg, int pass);
void reset();
@@ -66,5 +67,6 @@ private:
bool m_TestOnly;
static const uint8_t k_H264TestFrame[];
static const uint8_t k_HEVCTestFrame[];
static const uint8_t k_HEVCMainTestFrame[];
static const uint8_t k_HEVCMain10TestFrame[];
};
+13 -2
View File
@@ -10,10 +10,21 @@ const uint8_t FFmpegVideoDecoder::k_H264TestFrame[] = {
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
};
// 720p 60 FPS HEVC with 1 reference frame
const uint8_t FFmpegVideoDecoder::k_HEVCTestFrame[] = {
// 720p 60 FPS HEVC Main with 1 reference frame
const uint8_t FFmpegVideoDecoder::k_HEVCMainTestFrame[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
};
// 720p 60 FPS HEVC Main10 with 1 reference frame
const uint8_t FFmpegVideoDecoder::k_HEVCMain10TestFrame[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x12, 0xd9, 0x6b, 0x4a, 0x42, 0x59, 0x2e, 0x30, 0x16, 0xa0, 0xc0, 0xc0, 0xd4, 0x82, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x78, 0x5f, 0x1a, 0x2d,
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf3, 0xc0, 0x4c, 0x90,
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xbc, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4,
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x26, 0x43, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x05, 0xb4,
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x2d, 0xc3, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x24,
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x36, 0x83, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4
};
+8 -1
View File
@@ -31,6 +31,12 @@ SLVideoDecoder::getDecoderCapabilities()
return 0;
}
int
SLVideoDecoder::getDecoderColorspace()
{
return COLORSPACE_REC_709;
}
bool
SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
{
@@ -59,6 +65,7 @@ SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
return false;
}
SLVideo_SetStreamVideoTransferMatrix(m_VideoStream, k_ESLVideoTransferMatrix_BT709);
SLVideo_SetStreamTargetFramerate(m_VideoStream, params->frameRate, 1);
return true;
@@ -109,7 +116,7 @@ SLVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
return DR_OK;
}
void SLVideoDecoder::slLogCallback(void *context, ESLVideoLog logLevel, const char *message)
void SLVideoDecoder::slLogCallback(void*, ESLVideoLog logLevel, const char *message)
{
SDL_LogPriority priority;
+1
View File
@@ -12,6 +12,7 @@ public:
virtual bool initialize(PDECODER_PARAMETERS params);
virtual bool isHardwareAccelerated();
virtual int getDecoderCapabilities();
virtual int getDecoderColorspace();
virtual int submitDecodeUnit(PDECODE_UNIT du);
// Unused since rendering is done directly from the decode thread
+1 -1
View File
@@ -1 +1 @@
0.10.1
1.2.0
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -162,12 +162,29 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__GNUC__) && defined(__arm__)
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__)
#if 0 /* defined(__LINUX__) || defined(__ANDROID__) */
/* Information from:
https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
The Linux kernel provides a helper function which provides the right code for a memory barrier,
hard-coded at address 0xffff0fa0
*/
typedef void (*SDL_KernelMemoryBarrierFunc)();
#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#elif 0 /* defined(__QNXNTO__) */
#include <sys/cpuinline.h>
#define SDL_MemoryBarrierRelease() __cpu_membarrier()
#define SDL_MemoryBarrierAcquire() __cpu_membarrier()
#else
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_5TE__)
#ifdef __thumb__
/* The mcr instruction isn't available in thumb mode, use real functions */
#define SDL_MEMORY_BARRIER_USES_FUNCTION
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
#else
@@ -177,6 +194,7 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
#endif /* __LINUX__ || __ANDROID__ */
#endif /* __GNUC__ && __arm__ */
#else
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -420,23 +420,56 @@ extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
/* @} *//* Pause audio functions */
/**
* This function loads a WAVE from the data source, automatically freeing
* that source if \c freesrc is non-zero. For example, to load a WAVE file,
* you could do:
* \brief Load the audio data of a WAVE file into memory
*
* Loading a WAVE file requires \c src, \c spec, \c audio_buf and \c audio_len
* to be valid pointers. The entire data portion of the file is then loaded
* into memory and decoded if necessary.
*
* If \c freesrc is non-zero, the data source gets automatically closed and
* freed before the function returns.
*
* Supported are RIFF WAVE files with the formats PCM (8, 16, 24, and 32 bits),
* IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and A-law and
* µ-law (8 bits). Other formats are currently unsupported and cause an error.
*
* If this function succeeds, the pointer returned by it is equal to \c spec
* and the pointer to the audio data allocated by the function is written to
* \c audio_buf and its length in bytes to \c audio_len. The \ref SDL_AudioSpec
* members \c freq, \c channels, and \c format are set to the values of the
* audio data in the buffer. The \c samples member is set to a sane default and
* all others are set to zero.
*
* It's necessary to use SDL_FreeWAV() to free the audio data returned in
* \c audio_buf when it is no longer used.
*
* Because of the underspecification of the Waveform format, there are many
* problematic files in the wild that cause issues with strict decoders. To
* provide compatibility with these files, this decoder is lenient in regards
* to the truncation of the file, the fact chunk, and the size of the RIFF
* chunk. The hints SDL_HINT_WAVE_RIFF_CHUNK_SIZE, SDL_HINT_WAVE_TRUNCATION,
* and SDL_HINT_WAVE_FACT_CHUNK can be used to tune the behavior of the
* loading process.
*
* Any file that is invalid (due to truncation, corruption, or wrong values in
* the headers), too big, or unsupported causes an error. Additionally, any
* critical I/O error from the data source will terminate the loading process
* with an error. The function returns NULL on error and in all cases (with the
* exception of \c src being NULL), an appropriate error message will be set.
*
* It is required that the data source supports seeking.
*
* Example:
* \code
* SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...);
* \endcode
*
* If this function succeeds, it returns the given SDL_AudioSpec,
* filled with the audio data format of the wave data, and sets
* \c *audio_buf to a malloc()'d buffer containing the audio data,
* and sets \c *audio_len to the length of that audio buffer, in bytes.
* You need to free the audio buffer with SDL_FreeWAV() when you are
* done with it.
*
* This function returns NULL and sets the SDL error message if the
* wave file cannot be opened, uses an unknown data format, or is
* corrupt. Currently raw and MS-ADPCM WAVE files are supported.
* \param src The data source with the WAVE data
* \param freesrc A integer value that makes the function close the data source if non-zero
* \param spec A pointer filled with the audio format of the audio data
* \param audio_buf A pointer filled with the audio data allocated by the function
* \param audio_len A pointer filled with the length of the audio data buffer in bytes
* \return NULL on error, or non-NULL on success.
*/
extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src,
int freesrc,
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -101,6 +101,15 @@ SDL_MostSignificantBitIndex32(Uint32 x)
#endif
}
SDL_FORCE_INLINE SDL_bool
SDL_HasExactlyOneBitSet32(Uint32 x)
{
if (x && !(x & (x - 1))) {
return SDL_TRUE;
}
return SDL_FALSE;
}
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -90,12 +90,12 @@ typedef enum
/**
* \brief Create a custom blend mode, which may or may not be supported by a given renderer
*
* \param srcColorFactor
* \param dstColorFactor
* \param colorOperation
* \param srcAlphaFactor
* \param dstAlphaFactor
* \param alphaOperation
* \param srcColorFactor source color factor
* \param dstColorFactor destination color factor
* \param colorOperation color operation
* \param srcAlphaFactor source alpha factor
* \param dstAlphaFactor destination alpha factor
* \param alphaOperation alpha operation
*
* The result of the blend mode operation will be:
* dstRGB = dstRGB * dstColorFactor colorOperation srcRGB * srcColorFactor
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -218,7 +218,7 @@
#endif
/* Enable Vulkan support */
/* Metal/MoltenVK/Vulkan only supported on 64-bit architectures with 10.11+ */
/* Metal/Vulkan Portability only supported on 64-bit architectures with 10.11+ */
#if TARGET_CPU_X86_64 && (MAC_OS_X_VERSION_MAX_ALLOWED >= 101100)
#define SDL_VIDEO_VULKAN 1
#else
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -41,24 +41,43 @@
#else
#include <intrin.h>
#ifndef _WIN64
#ifndef __MMX__
#define __MMX__
#endif
#ifndef __3dNOW__
#define __3dNOW__
#endif
#endif
#ifndef __SSE__
#define __SSE__
#endif
#ifndef __SSE2__
#define __SSE2__
#endif
#endif /* __clang__ */
#elif defined(__MINGW64_VERSION_MAJOR)
#include <intrin.h>
#else
#ifdef __ALTIVEC__
#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
/* altivec.h redefining bool causes a number of problems, see bugs 3993 and 4392, so you need to explicitly define SDL_ENABLE_ALTIVEC_H to have it included. */
#if defined(HAVE_ALTIVEC_H) && defined(__ALTIVEC__) && !defined(__APPLE_ALTIVEC__) && defined(SDL_ENABLE_ALTIVEC_H)
#include <altivec.h>
#undef pixel
#undef bool
#endif
#endif
#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
#include <arm_neon.h>
#if !defined(SDL_DISABLE_ARM_NEON_H)
# if defined(__ARM_NEON)
# include <arm_neon.h>
# elif defined(__WINDOWS__) || defined(__WINRT__)
/* Visual Studio doesn't define __ARM_ARCH, but _M_ARM (if set, always 7), and _M_ARM64 (if set, always 1). */
# if defined(_M_ARM)
# include <armintr.h>
# include <arm_neon.h>
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
# endif
# if defined (_M_ARM64)
# include <armintr.h>
# include <arm_neon.h>
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
# endif
# endif
#endif
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
#include <mm3dnow.h>
@@ -177,6 +196,69 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
*/
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
/**
* \brief Report the alignment this system needs for SIMD allocations.
*
* This will return the minimum number of bytes to which a pointer must be
* aligned to be compatible with SIMD instructions on the current machine.
* For example, if the machine supports SSE only, it will return 16, but if
* it supports AVX-512F, it'll return 64 (etc). This only reports values for
* instruction sets SDL knows about, so if your SDL build doesn't have
* SDL_HasAVX512F(), then it might return 16 for the SSE support it sees and
* not 64 for the AVX-512 instructions that exist but SDL doesn't know about.
* Plan accordingly.
*/
extern DECLSPEC size_t SDLCALL SDL_SIMDGetAlignment(void);
/**
* \brief Allocate memory in a SIMD-friendly way.
*
* This will allocate a block of memory that is suitable for use with SIMD
* instructions. Specifically, it will be properly aligned and padded for
* the system's supported vector instructions.
*
* The memory returned will be padded such that it is safe to read or write
* an incomplete vector at the end of the memory block. This can be useful
* so you don't have to drop back to a scalar fallback at the end of your
* SIMD processing loop to deal with the final elements without overflowing
* the allocated buffer.
*
* You must free this memory with SDL_FreeSIMD(), not free() or SDL_free()
* or delete[], etc.
*
* Note that SDL will only deal with SIMD instruction sets it is aware of;
* for example, SDL 2.0.8 knows that SSE wants 16-byte vectors
* (SDL_HasSSE()), and AVX2 wants 32 bytes (SDL_HasAVX2()), but doesn't
* know that AVX-512 wants 64. To be clear: if you can't decide to use an
* instruction set with an SDL_Has*() function, don't use that instruction
* set with memory allocated through here.
*
* SDL_AllocSIMD(0) will return a non-NULL pointer, assuming the system isn't
* out of memory.
*
* \param len The length, in bytes, of the block to allocated. The actual
* allocated block might be larger due to padding, etc.
* \return Pointer to newly-allocated block, NULL if out of memory.
*
* \sa SDL_SIMDAlignment
* \sa SDL_SIMDFree
*/
extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len);
/**
* \brief Deallocate memory obtained from SDL_SIMDAlloc
*
* It is not valid to use this function on a pointer from anything but
* SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc,
* SDL_malloc, memalign, new[], etc.
*
* However, SDL_SIMDFree(NULL) is a legal no-op.
*
* \sa SDL_SIMDAlloc
*/
extern DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr);
/* vi: set ts=4 sw=4 expandtab: */
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -594,6 +594,9 @@ typedef union SDL_Event
Uint8 padding[56];
} SDL_Event;
/* Make sure we haven't broken binary compatibility */
SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == 56);
/* Function prototypes */
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -315,6 +315,16 @@ extern "C" {
*/
#define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS"
/**
* \brief A variable controlling whether mouse events should generate synthetic touch events
*
* This variable can be set to the following values:
* "0" - Mouse events will not generate touch events (default for desktop platforms)
* "1" - Mouse events will generate touch events (default for mobile platforms, such as Android and iOS)
*/
#define SDL_HINT_MOUSE_TOUCH_EVENTS "SDL_MOUSE_TOUCH_EVENTS"
/**
* \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to true.
*
@@ -436,6 +446,16 @@ extern "C" {
*/
#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG"
/**
* \brief A variable that lets you provide a file with extra gamecontroller db entries.
*
* The file should contain lines of gamecontroller config data, see SDL_gamecontroller.h
*
* This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER)
* You can update mappings after the system is initialized with SDL_GameControllerMappingForGUID() and SDL_GameControllerAddMapping()
*/
#define SDL_HINT_GAMECONTROLLERCONFIG_FILE "SDL_GAMECONTROLLERCONFIG_FILE"
/**
* \brief A variable containing a list of devices to skip when scanning for game controllers.
*
@@ -836,19 +856,7 @@ extern "C" {
*/
#define SDL_HINT_IME_INTERNAL_EDITING "SDL_IME_INTERNAL_EDITING"
/**
* \brief A variable to control whether mouse and touch events are to be treated together or separately
*
* The variable can be set to the following values:
* "0" - Mouse events will be handled as touch events, and touch will raise fake mouse
* events. This is the behaviour of SDL <= 2.0.3. (default)
* "1" - Mouse events will be handled separately from pure touch events.
*
* The value of this hint is used at runtime, so it can be changed at any time.
*/
#define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
/**
/**
* \brief A variable to control whether we trap the Android back button to handle it manually.
* This is necessary for the right mouse button to work on some Android devices, or
* to be able to trap the back button for use in your code reliably. If set to true,
@@ -865,6 +873,17 @@ extern "C" {
*/
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
/**
* \brief A variable to control whether the event loop will block itself when the app is paused.
*
* The variable can be set to the following values:
* "0" - Non blocking.
* "1" - Blocking. (default)
*
* The value should be set before SDL is initialized.
*/
#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE "SDL_ANDROID_BLOCK_ON_PAUSE"
/**
* \brief A variable to control whether the return key on the soft keyboard
* should hide the soft keyboard on Android and iOS.
@@ -1043,6 +1062,118 @@ extern "C" {
*/
#define SDL_HINT_AUDIO_CATEGORY "SDL_AUDIO_CATEGORY"
/**
* \brief A variable controlling whether the 2D render API is compatible or efficient.
*
* This variable can be set to the following values:
*
* "0" - Don't use batching to make rendering more efficient.
* "1" - Use batching, but might cause problems if app makes its own direct OpenGL calls.
*
* Up to SDL 2.0.9, the render API would draw immediately when requested. Now
* it batches up draw requests and sends them all to the GPU only when forced
* to (during SDL_RenderPresent, when changing render targets, by updating a
* texture that the batch needs, etc). This is significantly more efficient,
* but it can cause problems for apps that expect to render on top of the
* render API's output. As such, SDL will disable batching if a specific
* render backend is requested (since this might indicate that the app is
* planning to use the underlying graphics API directly). This hint can
* be used to explicitly request batching in this instance. It is a contract
* that you will either never use the underlying graphics API directly, or
* if you do, you will call SDL_RenderFlush() before you do so any current
* batch goes to the GPU before your work begins. Not following this contract
* will result in undefined behavior.
*/
#define SDL_HINT_RENDER_BATCHING "SDL_RENDER_BATCHING"
/**
* \brief A variable controlling whether SDL logs all events pushed onto its internal queue.
*
* This variable can be set to the following values:
*
* "0" - Don't log any events (default)
* "1" - Log all events except mouse and finger motion, which are pretty spammy.
* "2" - Log all events.
*
* This is generally meant to be used to debug SDL itself, but can be useful
* for application developers that need better visibility into what is going
* on in the event queue. Logged events are sent through SDL_Log(), which
* means by default they appear on stdout on most platforms or maybe
* OutputDebugString() on Windows, and can be funneled by the app with
* SDL_LogSetOutputFunction(), etc.
*
* This hint can be toggled on and off at runtime, if you only need to log
* events for a small subset of program execution.
*/
#define SDL_HINT_EVENT_LOGGING "SDL_EVENT_LOGGING"
/**
* \brief Controls how the size of the RIFF chunk affects the loading of a WAVE file.
*
* The size of the RIFF chunk (which includes all the sub-chunks of the WAVE
* file) is not always reliable. In case the size is wrong, it's possible to
* just ignore it and step through the chunks until a fixed limit is reached.
*
* Note that files that have trailing data unrelated to the WAVE file or
* corrupt files may slow down the loading process without a reliable boundary.
* By default, SDL stops after 10000 chunks to prevent wasting time. Use the
* environment variable SDL_WAVE_CHUNK_LIMIT to adjust this value.
*
* This variable can be set to the following values:
*
* "force" - Always use the RIFF chunk size as a boundary for the chunk search
* "ignorezero" - Like "force", but a zero size searches up to 4 GiB (default)
* "ignore" - Ignore the RIFF chunk size and always search up to 4 GiB
* "maximum" - Search for chunks until the end of file (not recommended)
*/
#define SDL_HINT_WAVE_RIFF_CHUNK_SIZE "SDL_WAVE_RIFF_CHUNK_SIZE"
/**
* \brief Controls how a truncated WAVE file is handled.
*
* A WAVE file is considered truncated if any of the chunks are incomplete or
* the data chunk size is not a multiple of the block size. By default, SDL
* decodes until the first incomplete block, as most applications seem to do.
*
* This variable can be set to the following values:
*
* "verystrict" - Raise an error if the file is truncated
* "strict" - Like "verystrict", but the size of the RIFF chunk is ignored
* "dropframe" - Decode until the first incomplete sample frame
* "dropblock" - Decode until the first incomplete block (default)
*/
#define SDL_HINT_WAVE_TRUNCATION "SDL_WAVE_TRUNCATION"
/**
* \brief Controls how the fact chunk affects the loading of a WAVE file.
*
* The fact chunk stores information about the number of samples of a WAVE
* file. The Standards Update from Microsoft notes that this value can be used
* to 'determine the length of the data in seconds'. This is especially useful
* for compressed formats (for which this is a mandatory chunk) if they produce
* multiple sample frames per block and truncating the block is not allowed.
* The fact chunk can exactly specify how many sample frames there should be
* in this case.
*
* Unfortunately, most application seem to ignore the fact chunk and so SDL
* ignores it by default as well.
*
* This variable can be set to the following values:
*
* "truncate" - Use the number of samples to truncate the wave data if
* the fact chunk is present and valid
* "strict" - Like "truncate", but raise an error if the fact chunk
* is invalid, not present for non-PCM formats, or if the
* data chunk doesn't have that many samples
* "ignorezero" - Like "truncate", but ignore fact chunk if the number of
* samples is zero
* "ignore" - Ignore fact chunk entirely (default)
*/
#define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK"
/**
* \brief An enumeration of hint priorities
*/
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -55,6 +55,10 @@
/* On iOS SDL provides a main function that creates an application delegate
and starts the iOS application run loop.
If you link with SDL dynamically on iOS, the main function can't be in a
shared library, so you need to link with libSDLmain.a, which includes a
stub main function that calls into the shared library to start execution.
See src/video/uikit/SDL_uikitappdelegate.m for more details.
*/
#define SDL_MAIN_NEEDED
@@ -82,12 +86,6 @@
#endif
#endif /* SDL_MAIN_HANDLED */
#ifdef __cplusplus
#define C_LINKAGE "C"
#else
#define C_LINKAGE
#endif /* __cplusplus */
#ifndef SDLMAIN_DECLSPEC
#define SDLMAIN_DECLSPEC
#endif
@@ -111,17 +109,18 @@
#define main SDL_main
#endif
/**
* The prototype for the application's main() function
*/
extern C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
#include "begin_code.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* The prototype for the application's main() function
*/
typedef int (*SDL_main_func)(int argc, char *argv[]);
extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
/**
* This is called by the real SDL main function to let the rest of the
* library know that initialization was done properly.
@@ -136,8 +135,7 @@ extern DECLSPEC void SDLCALL SDL_SetMainReady(void);
/**
* This can be called to set the application class at startup
*/
extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style,
void *hInst);
extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, void *hInst);
extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
#endif /* __WIN32__ */
@@ -153,10 +151,24 @@ extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
* \return 0 on success, -1 on failure. On failure, use SDL_GetError to retrieve more
* information on the failure.
*/
extern DECLSPEC int SDLCALL SDL_WinRTRunApp(int (*mainFunction)(int, char **), void * reserved);
extern DECLSPEC int SDLCALL SDL_WinRTRunApp(SDL_main_func mainFunction, void * reserved);
#endif /* __WINRT__ */
#if defined(__IPHONEOS__)
/**
* \brief Initializes and launches an SDL application.
*
* \param argc The argc parameter from the application's main() function
* \param argv The argv parameter from the application's main() function
* \param mainFunction The SDL app's C-style main().
* \return the return value from mainFunction
*/
extern DECLSPEC int SDLCALL SDL_UIKitRunApp(int argc, char *argv[], SDL_main_func mainFunction);
#endif /* __IPHONEOS__ */
#ifdef __cplusplus
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages

Some files were not shown because too many files have changed in this diff Show More