Compare commits

...

85 Commits

Author SHA1 Message Date
loki
5ff5942258 Fix video packet corruption after error correction 2021-07-11 16:18:49 +02:00
loki
8c803e6a34 Fix lack of audio for second client when multiple clients are connected simultaniously 2021-07-11 15:27:12 +02:00
loki
a93a640d42 Merge branch 'master' into multi-block-fec 2021-07-10 23:49:56 +02:00
loki
62abd1ee19 Merge branch 'master' of github.com:loki-47-6F-64/sunshine 2021-07-10 23:49:38 +02:00
loki
d55c6ee84c Fix incorrect scaling of absolute mouse coordinates (again) 2021-07-10 23:49:04 +02:00
loki-47-6F-64
d3419697a5 Merge pull request #125 from cgutman/yuv420_linux_nvenc
Reduce CPU usage with Linux NVENC
2021-07-10 19:10:58 +02:00
Cameron Gutman
a7171d77db Reduce CPU usage with Linux NVENC
The internal assembly routines inside libswscale perform the
RGB->YUV conversion using a fully planar output format, then
have to perform an additional YUV420->NV12 conversion step.

NVENC can directly consume YUV420 input frames, so we can
completely avoid this NV12 conversion and save quite a bit of
CPU time in the process.
2021-07-10 11:24:13 -05:00
loki
fccb8d080d Merge branch 'master' into multi-block-fec 2021-07-10 17:37:47 +02:00
loki
24acfabe07 Prevent fec encoding from exceeding DATA_SHARDS_MAX 2021-07-10 17:33:25 +02:00
loki-47-6F-64
61be0eb051 Merge pull request #120 from cgutman/nvenc_linux
Add basic NVENC support on Linux
2021-07-10 17:30:51 +02:00
loki-47-6F-64
c4b4393177 Merge pull request #123 from cgutman/longer_ping_timeout
Increase default ping timeout to 10 seconds
2021-07-10 17:30:20 +02:00
Cameron Gutman
388e4696ff Increase default ping timeout to 10 seconds
During periods of poor connectivity, the ping timeout of 2 seconds
can easily be exceeded, especially with ENet's RTO backoff active.

This causes an unnecessary disconnection when the connection would
have recovered on its own in a few seconds. Increasing the timeout
to 10 seconds should prevent spurious disconnections in most cases.
2021-07-09 23:45:59 -05:00
Cameron Gutman
43dc7cf7c0 Fix video bitstream corruption when matching replacement isn't found
NVENC doesn't always include the HEVC bitstream prefix that Sunshine
is looking to patch. When this happens replace() appends a spurious
00 00 00 01 28 NALU prefix to the end of the bitstream rather than
simply doing nothing.

This causes varying degrees of malfunctioning depending on the client,
with the worst being complete video corruption on iOS.
2021-07-09 20:04:07 -05:00
Cameron Gutman
e8feb00b33 Use a valid RTP version to fix Wireshark parsing 2021-07-09 20:01:39 -05:00
Cameron Gutman
42472bec85 Enable NVENC configuration page on Linux 2021-07-09 20:01:39 -05:00
loki-47-6F-64
769c4c8f99 Merge pull request #121 from cgutman/update_gfe_version
Update GfeVersion to be consistent with AppVersion
2021-07-09 19:05:06 +02:00
Cameron Gutman
049044f768 Update GfeVersion to be consistent with AppVersion 2021-07-09 08:47:44 -05:00
Cameron Gutman
971c784f14 Add basic NVENC support on Linux
We're not offloading scaling and YUV conversion from the CPU yet,
so the gains aren't as high as one of the fully accelerated
backends like Windows NVENC or Linux VAAPI.

Still, offloading the H.264/HEVC encoding itself is an improvement
over doing everything on the CPU.
2021-07-09 08:13:05 -05:00
loki
747ba7f23a fix incorrect pix_fmt 2021-07-09 14:21:57 +02:00
loki
4a3f3622b8 Merge branch 'master' into encrypt_control_and_audio 2021-07-09 14:19:21 +02:00
loki
7d2705424a Use extended termination error code 2021-07-09 13:26:39 +02:00
loki
b228fd371c Send back encrypted termination code 2021-07-09 12:27:38 +02:00
loki
cbd2a8269c Encrypt audio when requested by Moonlight 2021-07-08 22:44:35 +02:00
loki
7f9467d759 Add support for IDR frame request for encrypted control 2021-07-07 17:10:06 +02:00
loki
9cf7c14a26 Improve speed quitting apps 2021-07-07 17:00:12 +02:00
loki
f09aea81fe Update moonlight-common-c and Simple-Web-Server 2021-07-07 16:55:52 +02:00
loki
8249d41848 Let Moonlight know it's unauthorized, rather than just resetting the connection 2021-07-07 16:39:16 +02:00
loki-47-6F-64
d33f742241 Merge pull request #119 from cgutman/remove_superkey_workaround
Remove super key workaround
2021-07-07 07:36:54 +02:00
Cameron Gutman
6d0499b0e3 Remove super key workaround
Moonlight can now pass the super key natively
2021-07-06 23:34:11 -05:00
loki
fd7a6d070b Merge branch 'master' into encrypt_control_and_audio 2021-07-06 20:54:52 +02:00
loki
3d81b0fe7a Remove redundant code 2021-07-06 20:54:29 +02:00
loki
1d37a19468 Merge branch 'master' into encrypt_control_and_audio 2021-07-06 19:31:18 +02:00
loki-47-6F-64
cc3cf60015 Merge pull request #116 from cgutman/fix_expired_certs
Allow expired or not-yet-valid client certificates
2021-07-06 19:30:08 +02:00
loki-47-6F-64
8d4e55fcbb Merge pull request #118 from cgutman/idr_frame_delay
Fix IDR frame delays after packet loss or client request
2021-07-06 19:29:47 +02:00
loki
25aa8b41a5 Don't keep reinitializing the cipher context for gcm 2021-07-06 19:11:16 +02:00
Cameron Gutman
667878d2eb Fix IDR frame delays after packet loss or client request
This can lead to spurious poor connection warnings in Moonlight.
2021-07-05 17:55:32 -05:00
Cameron Gutman
53a9f365ce Allow expired or not-yet-valid client certificates 2021-07-05 16:42:59 -05:00
loki
a587338701 Fully encrypt control data 2021-07-05 20:58:53 +02:00
loki
a52f547726 Correctly identify size of rtsp packet 2021-07-04 21:25:10 +02:00
loki
136e9c43a5 Handle RTSP packets that are broken up in more than two pieces 2021-07-04 18:57:55 +02:00
loki
616f62fb9f Add support for periodic ping 2021-07-04 17:12:41 +02:00
loki
9d10eaeef4 Merge branch 'master' into min_fec_packets 2021-07-04 16:30:40 +02:00
loki
f23fcee382 Merge branch 'audio_fec' into min_fec_packets 2021-07-04 16:29:34 +02:00
loki
21b1a4a336 If all content has been read in RTSP request, no need for waiting for next message 2021-07-04 16:06:03 +02:00
loki
b346ac2eb0 Use tcp instead of udp for RTSP setup 2021-07-04 15:50:28 +02:00
Cameron Gutman
db5a9363ba Add support for minimum required FEC packets attribute 2021-07-04 00:14:22 -05:00
Cameron Gutman
0a637b2272 Parse additional SDP options 2021-07-03 23:38:45 -05:00
Cameron Gutman
169a53b568 Increase default FEC percentage to match GFE
Also increase the range to the maximum of 255
2021-07-03 23:37:43 -05:00
Cameron Gutman
109b48a108 Implement audio FEC support 2021-07-03 18:32:33 -05:00
Cameron Gutman
118707f37a Improve audio RTP header data to better match GFE 2021-07-03 16:05:18 -05:00
loki
e169259f6f install systemd service for debian packages 2021-07-03 18:13:05 +02:00
loki
355df9a615 Listening on arbitrary ports now compatible with Moonlight 2021-07-03 13:14:23 +02:00
loki
96cfb1f368 add missing file 2021-07-01 12:17:10 +02:00
loki
545cca792b Fix inabillity to display secure desktop even when runnig as system account 2021-06-30 22:03:21 +02:00
loki
ae04c4afbb Gracefully exit when stopped by systemd or pkill 2021-06-30 17:28:58 +02:00
loki
e8fadd2848 Update version for debian package 2021-06-30 15:51:26 +02:00
loki
beb6bdfadb Allow end user to configure what ports to listen on 2021-06-30 15:25:08 +02:00
loki
8bf4ade9d8 Disable UPnP by default 2021-06-30 12:22:37 +02:00
loki
ea928c53b4 Add support for upnp 2021-06-29 22:42:06 +02:00
loki
c1697c8562 add comma's -_- 2021-06-28 21:56:21 +02:00
loki
57c4986f0e Add necessary dependencies for Debian package 2021-06-28 21:52:24 +02:00
loki
0cc7e35ed9 Automatic service discovery for Windows 2021-06-28 21:05:52 +02:00
loki
d6eceaf0dc Fix incorrect instruction in the README 2021-06-26 16:25:46 +02:00
loki
cf7eb14573 Remove redundent thread creation 2021-06-26 15:48:07 +02:00
loki
ed5de34800 Dynamically load avahi libraries if they are available 2021-06-26 15:36:56 +02:00
loki
27d4f6063f refactored publish.cpp from C code to C++ code 2021-06-26 13:32:14 +02:00
loki
62662edc8d Merge with master 2021-06-26 12:40:06 +02:00
loki
b67600962a Fix incorrect scaling for absolute mouse coordinates 2021-06-24 20:53:19 +02:00
loki
1eda45a81a Allow installation of debian package 2021-06-24 20:23:43 +02:00
loki
926e95f527 Fix absolute mouse coordinates with multiple monitors on Windows 2021-06-23 21:51:15 +02:00
loki
029194cb60 Fix incorrect abs mouse coordinates on Linux when scaling to different aspect ratio 2021-06-23 14:05:09 +02:00
loki
7e3abefc2c pass session event objects through safe::mail_t 2021-06-22 22:26:11 +02:00
loki
cf9eb961fc Pass global event objects through mail_t 2021-06-21 21:21:52 +02:00
loki
0a05c28df8 fixed incorrect colors when scaling with software 2021-06-20 16:13:34 +02:00
loki
0034438c9e fixed incorrect colors when scaling with software 2021-06-20 15:58:13 +02:00
loki
2691489dab Merge branch 'vaapi' 2021-06-20 15:30:16 +02:00
loki
9e7ecf8db2 Allow replacement of hevc headers 2021-06-20 15:29:51 +02:00
loki-47-6F-64
32e6054435 Fix incorrect instruction in the README 2021-06-20 15:16:30 +02:00
loki-47-6F-64
bd2d846557 Merge pull request #100 from TheElixZammuto/master
Fix Config Page AMD RC settings
2021-06-18 22:52:07 +02:00
Elia Zammuto
0932d8bab9 Merge pull request #5 from TheElixZammuto/fix-config-page
Fix amd rc selection on config page
2021-06-18 22:44:25 +02:00
Elia Zammuto
82ac3becd8 Fix amd rc selection on config page 2021-06-18 22:43:55 +02:00
loki
7b86ea9e87 temporary workaround for hanging when interrupting application before http server started 2021-06-18 20:02:57 +02:00
loki
63d15333f2 Allow injecting more than one type of header data into video 2021-06-18 17:27:56 +02:00
arne
3ec4bf52e1 avahi service publishing cleanup & appveyor 2021-04-13 18:36:48 +02:00
arne
ec44a4391a avahi service publishing 2021-04-13 18:15:53 +02:00
55 changed files with 3743 additions and 1211 deletions

3
.gitmodules vendored
View File

@@ -7,3 +7,6 @@
[submodule "ViGEmClient"]
path = third-party/ViGEmClient
url = https://github.com/ViGEm/ViGEmClient
[submodule "third-party/miniupnp"]
path = third-party/miniupnp
url = https://github.com/miniupnp/miniupnp

View File

@@ -6,6 +6,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(third-party/Simple-Web-Server)
set(UPNPC_BUILD_SHARED OFF CACHE BOOL "no shared libraries")
set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Don't build tests for miniupnpc")
set(UPNPC_BUILD_SAMPLE OFF CACHE BOOL "Don't build samples for miniupnpc")
set(UPNPC_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for miniupnpc")
add_subdirectory(third-party/miniupnp/miniupnpc)
include_directories(third-party/miniupnp)
if(WIN32)
# Ugly hack to compile with #include <qos2.h>
add_compile_definitions(
@@ -43,6 +50,8 @@ if(WIN32)
include_directories(third-party/ViGEmClient/include)
set(PLATFORM_TARGET_FILES
sunshine/platform/windows/publish.cpp
sunshine/platform/windows/misc.h
sunshine/platform/windows/misc.cpp
sunshine/platform/windows/input.cpp
sunshine/platform/windows/display.h
@@ -88,6 +97,7 @@ if(WIN32)
iphlpapi
d3d11 dxgi D3DCompiler
setupapi
dnsapi
)
set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650")
@@ -98,9 +108,12 @@ else()
find_package(X11 REQUIRED)
find_package(FFmpeg REQUIRED)
set(PLATFORM_TARGET_FILES
sunshine/platform/linux/publish.cpp
sunshine/platform/linux/vaapi.h
sunshine/platform/linux/vaapi.cpp
sunshine/platform/linux/misc.h
sunshine/platform/linux/misc.cpp
sunshine/platform/linux/display.cpp
sunshine/platform/linux/audio.cpp
@@ -132,7 +145,7 @@ else()
third-party/glad/include)
if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH)
set(SUNSHINE_EXECUTABLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/sunshine")
set(SUNSHINE_EXECUTABLE_PATH "sunshine")
endif()
configure_file(gen-deb.in gen-deb @ONLY)
configure_file(sunshine.service.in sunshine.service @ONLY)
@@ -150,6 +163,8 @@ set(SUNSHINE_TARGET_FILES
third-party/moonlight-common-c/src/Rtsp.h
third-party/moonlight-common-c/src/RtspParser.c
third-party/moonlight-common-c/src/Video.h
sunshine/upnp.cpp
sunshine/upnp.h
sunshine/cbs.cpp
sunshine/utility.h
sunshine/uuid.h
@@ -188,6 +203,8 @@ set(SUNSHINE_TARGET_FILES
sunshine/round_robin.h
${PLATFORM_TARGET_FILES})
set_source_files_properties(sunshine/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/third-party
@@ -217,6 +234,7 @@ list(APPEND CBS_EXTERNAL_LIBRARIES
cbs)
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
libminiupnpc-static
${CBS_EXTERNAL_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
stdc++fs

View File

@@ -35,7 +35,7 @@ sunshine needs access to uinput to create mouse and gamepad events:
- Run the following command:
`nano /etc/udev/rules.d/85-sunshine-input.rules`
- Input the following contents:
`KERNEL=="uinput", GROUP="input", mode="0660"`
`KERNEL=="uinput", GROUP="input", MODE="0660"`
- Save the file and exit
1. `CTRL+X` to start exit
2. `Y` to save modifications
@@ -65,13 +65,13 @@ sunshine needs access to uinput to create mouse and gamepad events:
### Requirements:
mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make
mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make
### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive`
- `cd sunshine && mkdir build && cd build`
- `cmake -G"Unix Makefiles" ..`
- `make`
- `mingw32-make`
### Setup:
- **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]

View File

@@ -9,7 +9,7 @@ environment:
install:
- sh: sudo apt update --ignore-missing
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
before_build:

View File

@@ -1,255 +1,271 @@
# If no external IP address is given, the local IP address is used
# external_ip = 123.456.789.12
# The private key must be 2048 bits
# pkey = /dir/pkey.pem
# The certificate must be signed with a 2048 bit key
# cert = /dir/cert.pem
# The name displayed by Moonlight
# If not specified, the PC's hostname is used
# sunshine_name = Sunshine
# The minimum log level printed to standard out
#
# none -> no logs are printed to standard out
#
# verbose = [0]
# debug = [1]
# info = [2]
# warning = [3]
# error = [4]
# fatal = [5]
# none = [6]
#
# min_log_level = info
# The origin of the remote endpoint address that is not denied for HTTP method /pin
# Could be any of the following values:
# pc|lan|wan
# pc: Only localhost may access /pin
# lan: Only those in LAN may access /pin
# wan: Anyone may access /pin
#
# origin_pin_allowed = lan
# The file where current state of Sunshine is stored
# file_state = sunshine_state.json
# The file where user credentials for the UI are stored
# By default, credentials are stored in `file_state`
# credentials_file = sunshine_state.json
# The display modes advertised by Sunshine
#
# Some versions of Moonlight, such as Moonlight-nx (Switch),
# rely on this list to ensure that the requested resolutions and fps
# are supported.
#
# fps = [10, 30, 60, 90, 120]
# resolutions = [
# 352x240,
# 480x360,
# 858x480,
# 1280x720,
# 1920x1080,
# 2560x1080,
# 3440x1440,
# 1920x1200,
# 3860x2160,
# 3840x1600,
# ]
# How long to wait in milliseconds for data from moonlight before shutting down the stream
# ping_timeout = 2000
# The file where configuration for the different applications that Sunshine can run during a stream
# file_apps = apps.json
# How much error correcting packets must be send for every video
# This is just some random number, don't know the optimal value
# The higher fec_percentage, the lower space for the actual data to send per frame there is
#
# The value must be greater than 0 and lower than or equal to 100
# fec_percentage = 10
# When multicasting, it could be usefull to have different configurations for each connected Client.
# For example:
# Clients connected through WAN and LAN have different bitrate contstraints.
# Decoders may require different settings for color
#
# Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
# Note, CPU usage increases for each distinct video stream generated
# channels = 1
# The back/select button on the controller
# On the Shield, the home and powerbutton are not passed to Moonlight
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
# back_button_timeout = 2000
# !! Windows only !!
# Control how fast keys will repeat themselves
# The initial delay in milliseconds before repeating keys
# key_repeat_delay = 500
#
# How often keys repeat every second
# This configurable option supports decimals
# key_repeat_frequency = 24.9
# The name of the audio sink used for Audio Loopback
# If you do not specify this variable, pulseaudio will select the default monitor device.
#
# You can find the name of the audio sink using the following command:
# !! Linux only !!
# pacmd list-sinks | grep "name:" if running vanilla pulseaudio
# pactl info | grep Source` if running pipewire
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
#
# !! Windows only !!
# tools\audio-info.exe
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
#
# The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine
# to stream audio, while muting the speakers.
# virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
# !! Windows only !!
# You can select the video card you want to stream:
# The appropriate values can be found using the following command:
# tools\dxgi-info.exe
# adapter_name = Radeon RX 580 Series
# output_name = \\.\DISPLAY1
# !! Linux only !!
# Set the display number to stream.
# You can find them by the following command:
# xrandr --listmonitors
# Example output: "0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1"
# ^ <-- You need this.
# output_name = 0
###############################################
# FFmpeg software encoding parameters
# Honestly, I have no idea what the optimal values would be.
# Play around with this :)
# Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality
# Higher value means more compression, but less quality
# If crf == 0, then use QP directly instead
# crf = 0
# Quantitization Parameter
# Higher value means more compression, but less quality
# If crf != 0, then this parameter is ignored
# qp = 28
# Minimum number of threads used by ffmpeg to encode the video.
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
# value that can reliably encode at your desired streaming settings on your hardware.
# min_threads = 1
# Allows the client to request HEVC Main or HEVC Main10 video streams.
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
# If set to 0 (default), Sunshine will specify support for HEVC based on encoder
# If set to 1, Sunshine will not advertise support for HEVC
# If set to 2, Sunshine will advertise support for HEVC Main profile
# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# hevc_mode = 2
# Force a specific encoder, otherwise Sunshine will use the first encoder that is available
# supported encoders:
# nvenc
# amdvce # NOTE: alpha stage. The cursor is not yet displayed
# software
#
# encoder = nvenc
##################################### Software #####################################
# See x264 --fullhelp for the different presets
# sw_preset = superfast
# sw_tune = zerolatency
#
##################################### NVENC #####################################
###### presets ###########
# default
# hp -- high performance
# hq -- high quality
# slow -- hq 2 passes
# medium -- hq 1 pass
# fast -- hp 1 pass
# bd
# ll -- low latency
# llhq
# llhp
# lossless
# losslesshp
##########################
# nv_preset = llhq
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr -- variable bitrate
# cbr -- constant bitrate
# cbr_hq -- cbr high quality
# cbr_ld_hq -- cbr low delay high quality
# vbr_hq -- vbr high quality
##########################
# nv_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# nv_coder = auto
##################################### AMD #####################################
###### presets ###########
# default
# speed
# balanced
##########################
# amd_preset = balanced
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr_latency -- Latency Constrained Variable Bitrate
# vbr_peak -- Peak Contrained Variable Bitrate
# cbr -- constant bitrate
##########################
# amd_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# amd_coder = auto
#################################### VAAPI ###################################
####### adapter ##########
# Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done
# on a different GPU.
# Run the following commands:
# 1. ls /dev/dri/renderD*
# to find all devices capable of VAAPI
# 2. vainfo --display drm --device /dev/dri/renderD129 | grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version"
# Lists the name and capabilities of the device.
# To be supported by Sunshine, it needs to have at the very minimum:
# VAProfileH264High : VAEntrypointEncSlice
# adapter_name = /dev/dri/renderD128
##############################################
# Some configurable parameters, are merely toggles for specific features
# The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc
# Here, you change the default state of any flag
#
# To set the initial state of flags -0 and -1 to on, set the following flags:
# flags = 01
#
# See: sunshine --help for all options under the header: flags
# If no external IP address is given, Sunshine will attempt to automatically detect external ip-address
# external_ip = 123.456.789.12
# Set the familly of ports used by Sunshine
# port = 47989
# The private key must be 2048 bits
# pkey = /dir/pkey.pem
# The certificate must be signed with a 2048 bit key
# cert = /dir/cert.pem
# The name displayed by Moonlight
# If not specified, the PC's hostname is used
# sunshine_name = Sunshine
# The minimum log level printed to standard out
#
# none -> no logs are printed to standard out
#
# verbose = [0]
# debug = [1]
# info = [2]
# warning = [3]
# error = [4]
# fatal = [5]
# none = [6]
#
# min_log_level = info
# The origin of the remote endpoint address that is not denied for HTTP method /pin
# Could be any of the following values:
# pc|lan|wan
# pc: Only localhost may access /pin
# lan: Only those in LAN may access /pin
# wan: Anyone may access /pin
#
# origin_pin_allowed = pc
# The origin of the remote endpoint address that is not denied for HTTPS Web UI
# Could be any of the following values:
# pc|lan|wan
# pc: Only localhost may access the Web Manager
# lan: Only those in LAN may access the Web Manager
# wan: Anyone may access the Web Manager
#
# origin_web_ui_allowed = lan
# If UPnP is enabled, Sunshine will attempt to open ports for streaming over the internet
# To enable it, uncomment the following line:
# upnp = on
# The file where current state of Sunshine is stored
# file_state = sunshine_state.json
# The file where user credentials for the UI are stored
# By default, credentials are stored in `file_state`
# credentials_file = sunshine_state.json
# The display modes advertised by Sunshine
#
# Some versions of Moonlight, such as Moonlight-nx (Switch),
# rely on this list to ensure that the requested resolutions and fps
# are supported.
#
# fps = [10, 30, 60, 90, 120]
# resolutions = [
# 352x240,
# 480x360,
# 858x480,
# 1280x720,
# 1920x1080,
# 2560x1080,
# 3440x1440,
# 1920x1200,
# 3860x2160,
# 3840x1600,
# ]
# How long to wait in milliseconds for data from moonlight before shutting down the stream
# ping_timeout = 10000
# The file where configuration for the different applications that Sunshine can run during a stream
# file_apps = apps.json
# Percentage of error correcting packets per data packet in each video frame
# Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage
# The default value of 20 is what GeForce Experience uses
#
# The value must be greater than 0 and lower than or equal to 255
# fec_percentage = 20
# When multicasting, it could be usefull to have different configurations for each connected Client.
# For example:
# Clients connected through WAN and LAN have different bitrate contstraints.
# Decoders may require different settings for color
#
# Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
# Note, CPU usage increases for each distinct video stream generated
# channels = 1
# The back/select button on the controller
# On the Shield, the home and powerbutton are not passed to Moonlight
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
# back_button_timeout = 2000
# !! Windows only !!
# Control how fast keys will repeat themselves
# The initial delay in milliseconds before repeating keys
# key_repeat_delay = 500
#
# How often keys repeat every second
# This configurable option supports decimals
# key_repeat_frequency = 24.9
# The name of the audio sink used for Audio Loopback
# If you do not specify this variable, pulseaudio will select the default monitor device.
#
# You can find the name of the audio sink using the following command:
# !! Linux only !!
# pacmd list-sinks | grep "name:" if running vanilla pulseaudio
# pactl info | grep Source` if running pipewire
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
#
# !! Windows only !!
# tools\audio-info.exe
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
#
# The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine
# to stream audio, while muting the speakers.
# virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
# !! Windows only !!
# You can select the video card you want to stream:
# The appropriate values can be found using the following command:
# tools\dxgi-info.exe
# adapter_name = Radeon RX 580 Series
# output_name = \\.\DISPLAY1
# !! Linux only !!
# Set the display number to stream.
# You can find them by the following command:
# xrandr --listmonitors
# Example output: "0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1"
# ^ <-- You need this.
# output_name = 0
###############################################
# FFmpeg software encoding parameters
# Honestly, I have no idea what the optimal values would be.
# Play around with this :)
# Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality
# Higher value means more compression, but less quality
# If crf == 0, then use QP directly instead
# crf = 0
# Quantitization Parameter
# Higher value means more compression, but less quality
# If crf != 0, then this parameter is ignored
# qp = 28
# Minimum number of threads used by ffmpeg to encode the video.
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
# value that can reliably encode at your desired streaming settings on your hardware.
# min_threads = 1
# Allows the client to request HEVC Main or HEVC Main10 video streams.
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
# If set to 0 (default), Sunshine will specify support for HEVC based on encoder
# If set to 1, Sunshine will not advertise support for HEVC
# If set to 2, Sunshine will advertise support for HEVC Main profile
# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# hevc_mode = 2
# Force a specific encoder, otherwise Sunshine will use the first encoder that is available
# supported encoders:
# nvenc
# amdvce # NOTE: alpha stage. The cursor is not yet displayed
# software
#
# encoder = nvenc
##################################### Software #####################################
# See x264 --fullhelp for the different presets
# sw_preset = superfast
# sw_tune = zerolatency
#
##################################### NVENC #####################################
###### presets ###########
# default
# hp -- high performance
# hq -- high quality
# slow -- hq 2 passes
# medium -- hq 1 pass
# fast -- hp 1 pass
# bd
# ll -- low latency
# llhq
# llhp
# lossless
# losslesshp
##########################
# nv_preset = llhq
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr -- variable bitrate
# cbr -- constant bitrate
# cbr_hq -- cbr high quality
# cbr_ld_hq -- cbr low delay high quality
# vbr_hq -- vbr high quality
##########################
# nv_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# nv_coder = auto
##################################### AMD #####################################
###### presets ###########
# default
# speed
# balanced
##########################
# amd_preset = balanced
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr_latency -- Latency Constrained Variable Bitrate
# vbr_peak -- Peak Contrained Variable Bitrate
# cbr -- constant bitrate
##########################
# amd_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# amd_coder = auto
#################################### VAAPI ###################################
####### adapter ##########
# Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done
# on a different GPU.
# Run the following commands:
# 1. ls /dev/dri/renderD*
# to find all devices capable of VAAPI
# 2. vainfo --display drm --device /dev/dri/renderD129 | grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version"
# Lists the name and capabilities of the device.
# To be supported by Sunshine, it needs to have at the very minimum:
# VAProfileH264High : VAEntrypointEncSlice
# adapter_name = /dev/dri/renderD128
##############################################
# Some configurable parameters, are merely toggles for specific features
# The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc
# Here, you change the default state of any flag
#
# To set the initial state of flags -0 and -1 to on, set the following flags:
# flags = 012
#
# See: sunshine --help for all options under the header: flags

View File

@@ -32,28 +32,30 @@
</select>
<div class="form-text">The minimum log level printed to standard out</div>
</div>
<!--Origin PIN Allowed-->
<!--Origin Web UI Allowed-->
<div class="mb-3">
<label for="origin_pin_allowed" class="form-label">Origin PIN Allowed</label>
<select id="origin_pin_allowed" class="form-select" v-model="config.origin_pin_allowed">
<option value="pc">Only localhost may access /pin and Web UI</option>
<option value="lan">Only those in LAN may access /pin and Web UI</option>
<option value="wan">Anyone may access /pin and Web UI</option>
<label for="origin_web_ui_allowed" class="form-label">Origin Web UI Allowed</label>
<select id="origin_web_ui_allowed" class="form-select" v-model="config.origin_web_ui_allowed">
<option value="pc">Only localhost may access Web UI</option>
<option value="lan">Only those in LAN may access Web UI</option>
<option value="wan">Anyone may access Web UI</option>
</select>
<div class="form-text">The origin of the remote endpoint address that is not denied for HTTP method /pin
<div class="form-text">The origin of the remote endpoint address that is not denied access to Web UI
</div>
</div>
<!--External IP-->
<!--UPnP-->
<div class="mb-3">
<label for="external_ip" class="form-label">External IP</label>
<input type="text" class="form-control" id="external_ip" placeholder="123.456.789.12"
v-model="config.external_ip">
<div class="form-text">If no external IP address is given, the local IP address is used</div>
<label for="upnp" class="form-label">UPnP</label>
<select id="upnp" class="form-select" v-model="config.upnp">
<option value="disabled">Disabled</option>
<option value="enabled">Enabled</option>
</select>
<div class="form-text">Automatically configure port forwarding</div>
</div>
<!--Ping Timeout-->
<div class="mb-3">
<label for="ping_timeout" class="form-label">Ping Timeout</label>
<input type="text" class="form-control" id="ping_timeout" placeholder="2000"
<input type="text" class="form-control" id="ping_timeout" placeholder="10000"
v-model="config.ping_timeout">
<div class="form-text">How long to wait in milliseconds for data from moonlight before shutting down the
stream</div>
@@ -236,6 +238,15 @@
</div>
</div>
<div v-if="currentTab === 'advanced'" class="config-page">
<!--Port familly-->
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" min="0" max="65529" class="form-control" id="port" placeholder="47989"
v-model="config.port">
<div class="form-text">
Set the familly of ports used by Sunshine
</div>
</div>
<!--Constant Rate Factor-->
<div class="mb-3">
<label for="crf" class="form-label">Constant Rate Factor</label>
@@ -306,12 +317,12 @@
<!--FEC Percentage-->
<div class="mb-3">
<label for="fec_percentage" class="form-label">FEC Percentage</label>
<input type="text" class="form-control" id="fec_percentage" placeholder="10"
<input type="text" class="form-control" id="fec_percentage" placeholder="20"
v-model="config.fec_percentage">
<div class="form-text">
How much error correcting packets must be send for every video.<br>
This is just some random number, don't know the optimal value.<br>
The higher fec_percentage, the lower space for the actual data to send per frame there is
Percentage of error correcting packets per data packet in each video frame.<br>
Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage.<br>
The default value of 20 is what GeForce Experience uses.
</div>
</div>
<!--Channels-->
@@ -339,6 +350,25 @@
Store Username/Password seperately from Sunshine's state file.
</div>
</div>
<!--Origin PIN Allowed-->
<div class="mb-3">
<label for="origin_pin_allowed" class="form-label">Origin PIN Allowed</label>
<select id="origin_pin_allowed" class="form-select" v-model="config.origin_pin_allowed">
<option value="pc">Only localhost may access /pin</option>
<option value="lan">Only those in LAN may access /pin</option>
<option value="wan">Anyone may access /pin</option>
</select>
<div class="form-text">The origin of the remote endpoint address that is not denied for HTTP method /pin
</div>
</div>
<!--External IP-->
<div class="mb-3">
<label for="external_ip" class="form-label">External IP</label>
<input type="text" class="form-control" id="external_ip" placeholder="123.456.789.12"
v-model="config.external_ip">
<div class="form-text">If no external IP address is given, Sunshine will automatically detect external
IP</div>
</div>
</div>
<!--Software Settings-->
<div v-if="currentTab === 'sw'" class="config-page">
@@ -405,7 +435,7 @@
</div>
<div class="mb-3">
<label for="amd_rc" class="form-label">AMD AMF Rate Control</label>
<select id="amd_rc" class="form-select">
<select id="amd_rc" class="form-select" v-model="config.amd_rc">
<option value="auto">auto -- let ffmpeg decide rate control</option>
<option value="constqp">constqp -- constant QP mode</option>
<option value="vbr_latency">vbr_latency -- Latency Constrained Variable Bitrate</option>
@@ -498,15 +528,17 @@
}
if (this.platform == "linux") {
this.tabs = this.tabs.filter(el => {
return el.id !== "nv" && el.id !== "amd";
return el.id !== "amd";
});
}
delete this.config.status;
delete this.config.platform;
//Populate default values if not present in config
this.config.upnp = this.config.upnp || 'disabled';
this.config.min_log_level = this.config.min_log_level || 2;
this.config.origin_pin_allowed = this.config.origin_pin_allowed || "lan";
this.config.origin_pin_allowed = this.config.origin_pin_allowed || "pc";
this.config.origin_web_ui_allowed = this.config.origin_web_manager_allowed || "lan";
this.config.hevc_mode = this.config.hevc_mode || 0;
this.config.encoder = this.config.encoder || '';
this.config.nv_preset = this.config.nv_preset || 'default';

View File

@@ -10,15 +10,17 @@ if [ -d package-deb ]; then
rm -rf package-deb
fi
export DEBIAN=package-deb/sunshine/DEBIAN
export RULES=package-deb/sunshine/etc/udev/rules.d
export BIN=package-deb/sunshine/usr/bin
export ASSETS=package-deb/sunshine/etc/sunshine
export DEBIAN=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/DEBIAN
export RULES=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/etc/udev/rules.d
export BIN=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/usr/bin
export SERVICE=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/usr/lib/systemd/user
export ASSETS=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/etc/sunshine
mkdir -p $DEBIAN
mkdir -p $RULES
mkdir -p $BIN
mkdir -p $ASSETS
mkdir -p $ASSETS/shaders
mkdir -p $SERVICE
if [ ! -f sunshine ]; then
echo "Error: Can't find sunshine"
@@ -35,8 +37,8 @@ Package: sunshine
Architecture: amd64
Maintainer: @loki
Priority: optional
Version: 0.7.4
Depends: libssl1.1, libavdevice58, libboost-thread1.71.0, libboost-filesystem1.71.0, libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0
Version: 0.8.1
Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0, libboost-log1.67.0 | libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2
Description: Gamestream host for Moonlight
EOF
@@ -71,11 +73,10 @@ cat << 'EOF' > $RULES/85-sunshine-rules.rules
KERNEL=="uinput", GROUP="input", MODE="0660"
EOF
mkdir -p $ASSETS/shaders
cp sunshine $BIN/sunshine
cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json $ASSETS/apps_linux.json
cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf $ASSETS/sunshine.conf
cp @CMAKE_CURRENT_BINARY_DIR@/sunshine.service $SERVICE/sunshine.service
cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/web $ASSETS/web
cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/shaders/opengl $ASSETS/shaders/opengl

View File

@@ -72,7 +72,9 @@ opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
@@ -89,7 +91,7 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) {
packet_t packet { 1024 }; // 1KB
buffer_t packet { 1024 }; // 1KB
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) {
@@ -104,7 +106,9 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
}
}
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
@@ -148,7 +152,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
}
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, packets, samples, config, channel_data };
std::thread thread { encodeThread, samples, config, channel_data };
auto fg = util::fail_guard([&]() {
samples->stop();
@@ -225,6 +229,7 @@ int start_audio_control(audio_ctx_t &ctx) {
ctx.sink = std::move(*sink);
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {

View File

@@ -37,9 +37,9 @@ struct config_t {
std::bitset<MAX_FLAGS> flags;
};
using packet_t = util::buffer_t<std::uint8_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void *, packet_t>>>;
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data);
using buffer_t = util::buffer_t<std::uint8_t>;
using packet_t = std::pair<void *, buffer_t>;
void capture(safe::mail_t mail, config_t config, void *channel_data);
} // namespace audio
#endif

View File

@@ -1,10 +1,12 @@
extern "C" {
#include <cbs/cbs_h264.h>
#include <cbs/cbs_h265.h>
#include <cbs/h264_levels.h>
#include <cbs/video_levels.h>
#include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h>
}
#include "cbs.h"
#include "main.h"
#include "utility.h"
@@ -46,19 +48,16 @@ public:
}
};
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
@@ -74,6 +73,13 @@ util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_i
return data;
}
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
@@ -162,9 +168,83 @@ util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264);
}
util::buffer_t<std::uint8_t> read_sps(const AVPacket *packet, int codec_id) {
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) {
if(ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {};
}
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps;
auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
H265RawSPS sps { *sps_p };
H265RawVPS vps { *vps_p };
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
auto &vui = sps.vui;
std::memset(&vui, 0, sizeof(vui));
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
}
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
}
@@ -178,24 +258,15 @@ util::buffer_t<std::uint8_t> read_sps(const AVPacket *packet, int codec_id) {
return {};
}
if(codec_id == AV_CODEC_ID_H264) {
auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps;
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
}
auto hevc = (H264RawNALUnitHeader *)((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
return write(hevc->nal_unit_type, (void *)hevc, AV_CODEC_ID_H265);
auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps;
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
}
util::buffer_t<std::uint8_t> make_sps(const AVCodecContext *ctx, int format) {
switch(format) {
case 0:
return make_sps_h264(ctx);
}
BOOST_LOG(warning) << "make_sps: video format ["sv << format << "] not supported"sv;
return {};
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
return h264_t {
make_sps_h264(ctx),
read_sps_h264(packet),
};
}
bool validate_sps(const AVPacket *packet, int codec_id) {
@@ -206,7 +277,7 @@ bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);

View File

@@ -5,10 +5,25 @@
struct AVPacket;
struct AVCodecContext;
namespace cbs {
util::buffer_t<std::uint8_t> read_sps(const AVPacket *packet, int codec_id);
util::buffer_t<std::uint8_t> make_sps(const AVCodecContext *ctx, int video_format);
struct nal_t {
util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old;
};
struct hevc_t {
nal_t vps;
nal_t sps;
};
struct h264_t {
nal_t sps;
};
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
/**
* Check if SPS->VUI is present

View File

@@ -176,16 +176,18 @@ video_t video {
audio_t audio {};
stream_t stream {
2s, // ping_timeout
10s, // ping_timeout
APPS_JSON_PATH,
10, // fecPercentage
20, // fecPercentage
1 // channels
};
nvhttp_t nvhttp {
"lan", // origin_pin
"pc", // origin_pin
"lan", // origin web manager
PRIVATE_KEY_FILE,
CERTIFICATE_FILE,
@@ -222,7 +224,8 @@ sunshine_t sunshine {
{}, // Password
{}, // Password Salt
SUNSHINE_ASSETS_DIR "/sunshine.conf", // config file
{} // cmd args
{}, // cmd args
47989,
};
bool endline(char ch) {
@@ -446,9 +449,12 @@ bool to_bool(std::string &boolean) {
return boolean == "true"sv ||
boolean == "yes"sv ||
boolean == "enable"sv ||
boolean == "enabled"sv ||
boolean == "on"sv ||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
}
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
std::string tmp;
string_f(vars, name, tmp);
@@ -456,7 +462,7 @@ void bool_f(std::unordered_map<std::string, std::string> &vars, const std::strin
return;
}
input = to_bool(tmp) ? 1 : 0;
input = to_bool(tmp);
}
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
@@ -549,8 +555,11 @@ int apply_flags(const char *line) {
case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip();
break;
case '2':
config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE].flip();
break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
config::sunshine.flags[config::flag::UPNP].flip();
break;
default:
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
@@ -604,6 +613,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
string_f(vars, "virtual_sink", audio.virtual_sink);
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
int to = -1;
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
@@ -614,7 +624,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
path_f(vars, "file_apps", stream.file_apps);
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 100 });
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
to = std::numeric_limits<int>::min();
int_f(vars, "back_button_timeout", to);
@@ -636,6 +646,17 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
input.key_repeat_delay = std::chrono::milliseconds { to };
}
int port = sunshine.port;
int_f(vars, "port"s, port);
sunshine.port = (std::uint16_t)port;
bool upnp = false;
bool_f(vars, "upnp"s, upnp);
if(upnp) {
config::sunshine.flags[config::flag::UPNP].flip();
}
std::string log_level_string;
string_restricted_f(vars, "min_log_level", log_level_string, { "verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv });
@@ -721,6 +742,13 @@ int parse(int argc, char *argv[]) {
return -1;
}
TUPLE_EL_REF(name, 0, *var);
auto it = cmd_vars.find(name);
if(it != std::end(cmd_vars)) {
cmd_vars.erase(it);
}
cmd_vars.emplace(std::move(*var));
}
}

View File

@@ -59,6 +59,7 @@ struct nvhttp_t {
// Could be any of the following values:
// pc|lan|wan
std::string origin_pin_allowed;
std::string origin_web_ui_allowed;
std::string pkey; // must be 2048 bits
std::string cert; // must be signed with a key of 2048 bits
@@ -80,9 +81,11 @@ struct input_t {
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
CONST_PIN, // Use "universal" pin
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
UPNP, // Try Universal Plug 'n Play
CONST_PIN, // Use "universal" pin
FLAG_SIZE
};
}
@@ -103,6 +106,8 @@ struct sunshine_t {
int argc;
char **argv;
} cmd;
std::uint16_t port;
};
extern video_t video;

View File

@@ -15,7 +15,7 @@
#include <boost/asio/ssl/context.hpp>
#include <Simple-Web-Server/crypto.hpp>
#include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include "config.h"
@@ -30,10 +30,9 @@
#include "utility.h"
#include "uuid.h"
namespace confighttp {
using namespace std::literals;
constexpr auto PORT_HTTPS = 47990;
namespace confighttp {
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
@@ -67,7 +66,7 @@ void print_req(const req_https_t &request) {
void send_unauthorized(resp_https_t response, req_https_t request) {
auto address = request->remote_endpoint_address();
BOOST_LOG(info) << '[' << address << "] -- denied"sv;
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
const SimpleWeb::CaseInsensitiveMultimap headers {
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
};
@@ -78,8 +77,8 @@ bool authenticate(resp_https_t response, req_https_t request) {
auto address = request->remote_endpoint_address();
auto ip_type = net::from_address(address);
if(ip_type > http::origin_pin_allowed) {
BOOST_LOG(info) << '[' << address << "] -- denied"sv;
if(ip_type > http::origin_web_ui_allowed) {
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- denied"sv;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
return false;
}
@@ -452,7 +451,11 @@ void savePin(resp_https_t response, req_https_t request) {
}
}
void start(std::shared_ptr<safe::signal_t> shutdown_event) {
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_https = map_port(PORT_HTTPS);
auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls);
ctx->use_certificate_chain_file(config::nvhttp.cert);
ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem);
@@ -473,14 +476,14 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
server.config.reuse_address = true;
server.config.address = "0.0.0.0"s;
server.config.port = PORT_HTTPS;
server.config.port = port_https;
try {
server.bind();
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTPS << "]";
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port_https << "]";
}
catch(boost::system::system_error &err) {
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << "]: "sv << err.what();
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_https << "]: "sv << err.what();
shutdown_event->raise(true);
return;
@@ -495,7 +498,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
return;
}
BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTPS << "]: "sv << err.what();
BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server to port ["sv << port_https << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}

View File

@@ -14,7 +14,8 @@
namespace confighttp {
void start(std::shared_ptr<safe::signal_t> shutdown_event);
}
constexpr auto PORT_HTTPS = 1;
void start();
} // namespace confighttp
#endif //SUNSHINE_CONFIGHTTP_H

View File

@@ -4,6 +4,7 @@
#include "crypto.h"
#include <openssl/pem.h>
namespace crypto {
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
//using rsa_t = util::safe_ptr<RSA, RSA_free>;
@@ -17,6 +18,26 @@ void cert_chain_t::add(x509_t &&cert) {
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
}
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch(err_code) {
//FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
return 1;
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
// This behavior also matches what GeForce Experience does.
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
return 1;
default:
return ok;
}
}
/*
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
@@ -32,6 +53,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
});
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), nullptr, nullptr);
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
X509_STORE_CTX_set_cert(_cert_ctx.get(), cert);
auto err = X509_verify_cert(_cert_ctx.get());
@@ -42,10 +64,6 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
//FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
return nullptr;
}
if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
return X509_verify_cert_error_string(err_code);
}
@@ -54,71 +72,91 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
return X509_verify_cert_error_string(err_code);
}
cipher_t::cipher_t(const crypto::aes_t &key) : ctx { EVP_CIPHER_CTX_new() }, key { key }, padding { true } {}
int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
namespace cipher {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(ctx.get());
});
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
if(!ctx) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size();
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended
if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
std::vector<std::uint8_t> &plaintext) {
auto cipher = tagged_cipher.substr(16);
auto tag = tagged_cipher.substr(0, 16);
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(ctx.get());
});
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if(EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
int size;
if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return 0;
}
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
if(!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
auto cipher = tagged_cipher.substr(tag_size);
auto tag = tagged_cipher.substr(0, tag_size);
plaintext.resize((cipher.size() + 15) / 16 * 16);
int size;
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1;
}
int len = size;
if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
@@ -126,28 +164,92 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
return 0;
}
int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
if(!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return -1;
}
auto tag = tagged_cipher;
auto cipher = tag + tag_size;
int len;
int size = round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
return -1;
}
return len + size;
}
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(ctx.get());
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
if(EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size();
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
int len;
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int)cipher.size();
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
@@ -155,6 +257,44 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_
return 0;
}
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
if(!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
int len;
int size = plaintext.size(); //round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
return size + len;
}
ecb_t::ecb_t(const aes_t &key, bool padding)
: cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
cbc_t::cbc_t(const aes_t &key, bool padding)
: cipher_t { nullptr, nullptr, key, padding } {}
gcm_t::gcm_t(const crypto::aes_t &key, bool padding)
: cipher_t { nullptr, nullptr, key, padding } {}
} // namespace cipher
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key;

View File

@@ -66,24 +66,70 @@ private:
x509_store_ctx_t _cert_ctx;
};
namespace cipher {
constexpr std::size_t tag_size = 16;
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16;
}
class cipher_t {
public:
cipher_t(const aes_t &key);
cipher_t(cipher_t &&) noexcept = default;
cipher_t &operator=(cipher_t &&) noexcept = default;
cipher_ctx_t decrypt_ctx;
cipher_ctx_t encrypt_ctx;
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
private:
cipher_ctx_t ctx;
aes_t key;
public:
bool padding;
};
class ecb_t : public cipher_t {
public:
ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default;
ecb_t &operator=(ecb_t &&) noexcept = default;
ecb_t(const aes_t &key, bool padding = true);
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
};
class gcm_t : public cipher_t {
public:
gcm_t() = default;
gcm_t(gcm_t &&) noexcept = default;
gcm_t &operator=(gcm_t &&) noexcept = default;
gcm_t(const crypto::aes_t &key, bool padding = true);
/**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
*
* return -1 on error
* return bytes written on success
*/
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
};
class cbc_t : public cipher_t {
public:
cbc_t() = default;
cbc_t(cbc_t &&) noexcept = default;
cbc_t &operator=(cbc_t &&) noexcept = default;
cbc_t(const crypto::aes_t &key, bool padding = true);
/**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
*
* return -1 on error
* return bytes written on success
*/
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
};
} // namespace cipher
} // namespace crypto
#endif //SUNSHINE_CRYPTO_H

View File

@@ -35,10 +35,13 @@ bool user_creds_exist(const std::string &file);
std::string unique_id;
net::net_e origin_pin_allowed;
net::net_e origin_web_ui_allowed;
int init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
if(clean_slate) {
unique_id = util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sushine"sv;

View File

@@ -14,5 +14,6 @@ int save_user_creds(
int reload_user_creds(const std::string &file);
extern std::string unique_id;
extern net::net_e origin_pin_allowed;
extern net::net_e origin_web_ui_allowed;
} // namespace http

View File

@@ -46,11 +46,6 @@ void free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false;
}
touch_port_event_t touch_port_event;
platf::touch_port_t touch_port {
0, 0, 0, 0
};
static util::TaskPool::task_id_t task_id {};
static std::unordered_map<short, bool> key_press {};
static std::array<std::uint8_t, 5> mouse_press {};
@@ -89,12 +84,21 @@ struct gamepad_t {
};
struct input_t {
input_t() : active_gamepad_state {}, gamepads(MAX_GAMEPADS), mouse_left_button_timeout {} {}
input_t(safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event)
: active_gamepad_state {},
gamepads(MAX_GAMEPADS),
touch_port_event { std::move(touch_port_event) },
mouse_left_button_timeout {},
touch_port { 0, 0, 0, 0, 0, 0, 1.0f } {}
std::uint16_t active_gamepad_state;
std::vector<gamepad_t> gamepads;
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
util::ThreadPool::task_id_t mouse_left_button_timeout;
input::touch_port_t touch_port;
};
using namespace std::literals;
@@ -201,6 +205,8 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET pack
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
}
auto &touch_port_event = input->touch_port_event;
auto &touch_port = input->touch_port;
if(touch_port_event->peek()) {
touch_port = *touch_port_event->pop();
}
@@ -216,13 +222,27 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET pack
return;
}
float width = util::endian::big(packet->width);
float height = util::endian::big(packet->height);
auto width = (float)util::endian::big(packet->width);
auto height = (float)util::endian::big(packet->height);
auto scale_x = (float)touch_port.width / width;
auto scale_y = (float)touch_port.height / height;
auto scalarX = touch_port.width / width;
auto scalarY = touch_port.height / height;
platf::abs_mouse(platf_input, touch_port, x * scale_x, y * scale_y);
x *= scalarX;
y *= scalarY;
auto offsetX = touch_port.client_offsetX;
auto offsetY = touch_port.client_offsetY;
std::clamp(x, offsetX, width - offsetX);
std::clamp(y, offsetY, height - offsetY);
platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y,
touch_port.env_width, touch_port.env_height
};
platf::abs_mouse(platf_input, abs_port, (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv);
}
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
@@ -552,12 +572,11 @@ void reset(std::shared_ptr<input_t> &input) {
}
void init() {
touch_port_event = std::make_unique<touch_port_event_t::element_type>();
platf_input = platf::input();
platf_input = platf::input();
}
std::shared_ptr<input_t> alloc() {
auto input = std::make_shared<input_t>();
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(mail->event<input::touch_port_t>(mail::touch_port));
// Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() {

View File

@@ -7,6 +7,7 @@
#include "platform/common.h"
#include "thread_safe.h"
namespace input {
struct input_t;
@@ -18,10 +19,16 @@ void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&in
void init();
std::shared_ptr<input_t> alloc();
std::shared_ptr<input_t> alloc(safe::mail_t mail);
using touch_port_event_t = std::unique_ptr<safe::event_t<platf::touch_port_t>>;
extern touch_port_event_t touch_port_event;
struct touch_port_t : public platf::touch_port_t {
int env_width, env_height;
// Offset x and y coordinates of the client
float client_offsetX, client_offsetY;
float scalar_inv;
};
} // namespace input
#endif //SUNSHINE_INPUT_H

View File

@@ -19,9 +19,11 @@
#include "config.h"
#include "confighttp.h"
#include "httpcommon.h"
#include "main.h"
#include "nvhttp.h"
#include "rtsp.h"
#include "thread_pool.h"
#include "upnp.h"
#include "video.h"
#include "platform/common.h"
@@ -30,6 +32,8 @@ extern "C" {
#include <rs.h>
}
safe::mail_t mail::man;
using namespace std::literals;
namespace bl = boost::log;
@@ -57,13 +61,16 @@ void print_help(const char *name) {
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
<< std::endl
<< " --help | print help"sv << std::endl
<< " --help | print help"sv << std::endl
<< " --creds username password | set user credentials for the Web manager" << std::endl
<< std::endl
<< " flags"sv << std::endl
<< " -0 | Read PIN from stdin"sv << std::endl
<< " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl;
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl
<< " -2 | Force replacement of headers in video stream" << std::endl
<< " -p | Enable/Disable UPnP" << std::endl
<< std::endl;
}
namespace help {
@@ -108,6 +115,8 @@ std::map<std::string_view, std::function<int(const char *name, int argc, char **
};
int main(int argc, char *argv[]) {
mail::man = std::make_shared<safe::mail_raw_t>();
if(config::parse(argc, argv)) {
return 0;
}
@@ -178,13 +187,53 @@ int main(int argc, char *argv[]) {
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
}
task_pool.start(1);
bool shutdown_by_interrupt = false;
util::TaskPool::task_id_t force_shutdown = nullptr;
// Create signal handler after logging has been initialized
auto shutdown_event = std::make_shared<safe::event_t<bool>>();
on_signal(SIGINT, [shutdown_event]() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
on_signal(SIGINT, [&shutdown_by_interrupt, &force_shutdown, shutdown_event]() {
BOOST_LOG(info) << "Interrupt handler called"sv;
auto task = []() {
BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv;
log_flush();
std::abort();
};
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
shutdown_by_interrupt = true;
shutdown_event->raise(true);
});
on_signal(SIGTERM, [&force_shutdown, shutdown_event]() {
BOOST_LOG(info) << "Terminate handler called"sv;
auto task = []() {
BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv;
log_flush();
std::abort();
};
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
shutdown_event->raise(true);
});
auto exit_guard = util::fail_guard([&shutdown_by_interrupt, &force_shutdown]() {
if(!shutdown_by_interrupt) {
return;
}
task_pool.cancel(force_shutdown);
std::cout << "Sunshine exited: Press enter to continue"sv << std::endl;
std::string _;
std::getline(std::cin, _);
});
proc::refresh(config::stream.file_apps);
auto deinit_guard = platf::init();
@@ -193,6 +242,7 @@ int main(int argc, char *argv[]) {
}
reed_solomon_init();
input::init();
if(video::init()) {
return 2;
}
@@ -200,11 +250,25 @@ int main(int argc, char *argv[]) {
return 3;
}
task_pool.start(1);
std::unique_ptr<platf::deinit_t> mDNS;
auto sync_mDNS = std::async(std::launch::async, [&mDNS]() {
mDNS = platf::publish::start();
});
std::thread httpThread { nvhttp::start, shutdown_event };
std::thread configThread { confighttp::start, shutdown_event };
stream::rtpThread(shutdown_event);
std::unique_ptr<platf::deinit_t> upnp_unmap;
auto sync_upnp = std::async(std::launch::async, [&upnp_unmap]() {
upnp_unmap = upnp::start();
});
//FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
if(shutdown_event->peek()) {
return 0;
}
std::thread httpThread { nvhttp::start };
std::thread configThread { confighttp::start };
stream::rtpThread();
httpThread.join();
configThread.join();
@@ -243,4 +307,8 @@ int write_file(const char *path, const std::string_view &contents) {
out << contents;
return 0;
}
std::uint16_t map_port(int port) {
return (std::uint16_t)((int)config::sunshine.port + port);
}

View File

@@ -5,7 +5,11 @@
#ifndef SUNSHINE_MAIN_H
#define SUNSHINE_MAIN_H
#include <string_view>
#include "thread_pool.h"
#include "thread_safe.h"
#include <boost/log/common.hpp>
extern util::ThreadPool task_pool;
@@ -24,4 +28,27 @@ void print_help(const char *name);
std::string read_file(const char *path);
int write_file(const char *path, const std::string_view &contents);
std::uint16_t map_port(int port);
namespace mail {
#define MAIL(x) \
constexpr auto x = std::string_view { #x }
extern safe::mail_t man;
// Global mail
MAIL(shutdown);
MAIL(broadcast_shutdown);
MAIL(video_packets);
MAIL(audio_packets);
// Local mail
MAIL(touch_port);
MAIL(idr);
#undef MAIL
} // namespace mail
#endif //SUNSHINE_MAIN_H

View File

@@ -6,8 +6,8 @@
#include "utility.h"
#include <algorithm>
namespace net {
using namespace std::literals;
namespace net {
// In the format "xxx.xxx.xxx.xxx/x"
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip);
@@ -16,7 +16,7 @@ std::vector<std::pair<std::uint32_t, std::uint32_t>> pc_ips {
};
std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
ip_block("192.168.0.0/16"sv),
ip_block("172.16.0.0/12"),
ip_block("172.16.0.0/12"sv),
ip_block("10.0.0.0/8"sv)
};

View File

@@ -30,13 +30,11 @@
#include "utility.h"
#include "uuid.h"
namespace nvhttp {
using namespace std::literals;
constexpr auto PORT_HTTP = 47989;
constexpr auto PORT_HTTPS = 47984;
namespace nvhttp {
constexpr auto VERSION = "7.1.400.0";
constexpr auto GFE_VERSION = "3.12.0.1";
constexpr auto VERSION = "7.1.431.0";
constexpr auto GFE_VERSION = "3.23.0.74";
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
@@ -224,8 +222,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar
auto encrypted_response = util::from_hex_vec(args.at("serverchallengeresp"s), true);
std::vector<uint8_t> decrypted;
crypto::cipher_t cipher(*sess.cipher_key);
cipher.padding = false;
crypto::cipher::ecb_t cipher(*sess.cipher_key, false);
cipher.decrypt(encrypted_response, decrypted);
@@ -244,8 +241,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
auto challenge = util::from_hex_vec(args.at("clientchallenge"s), true);
crypto::cipher_t cipher(*sess.cipher_key);
cipher.padding = false;
crypto::cipher::ecb_t cipher(*sess.cipher_key, false);
std::vector<uint8_t> decrypted;
cipher.decrypt(challenge, decrypted);
@@ -369,8 +365,11 @@ void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> resp
pt::write_xml(data, tree);
response->write(data.str());
*response << "HTTP/1.1 404 NOT FOUND\r\n"
<< data.str();
*response
<< "HTTP/1.1 404 NOT FOUND\r\n"
<< data.str();
response->close_connection_after_response = true;
}
template<class T>
@@ -379,6 +378,14 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
pt::ptree tree;
auto fg = util::fail_guard([&]() {
std::ostringstream data;
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
});
auto args = request->parse_query_string();
if(args.find("uniqueid"s) == std::end(args)) {
tree.put("root.<xmlattr>.status_code", 400);
@@ -402,11 +409,7 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
ptr->second.async_insert_pin.salt = std::move(args.at("salt"s));
if(config::sunshine.flags[config::flag::CONST_PIN]) {
std::string pin("6174");
getservercert(ptr->second, tree, pin);
}
else if(config::sunshine.flags[config::flag::PIN_STDIN]) {
if(config::sunshine.flags[config::flag::PIN_STDIN]) {
std::string pin;
std::cout << "Please insert pin: "sv;
@@ -417,6 +420,7 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
else {
ptr->second.async_insert_pin.response = std::move(response);
fg.disable();
return;
}
}
@@ -437,11 +441,6 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
else {
tree.put("root.<xmlattr>.status_code", 404);
}
std::ostringstream data;
pt::write_xml(data, tree);
response->write(data.str());
}
bool pin(std::string pin) {
@@ -478,10 +477,12 @@ template<class T>
void pin(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
response->close_connection_after_response = true;
auto address = request->remote_endpoint_address();
auto ip_type = net::from_address(address);
if(ip_type > http::origin_pin_allowed) {
BOOST_LOG(info) << '[' << address << "] -- denied"sv;
BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
@@ -522,6 +523,8 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
tree.put("root.appversion", VERSION);
tree.put("root.GfeVersion", GFE_VERSION);
tree.put("root.uniqueid", http::unique_id);
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
tree.put("root.ExternalPort", map_port(PORT_HTTP));
tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address()));
tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", request->local_endpoint_address());
@@ -574,6 +577,7 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
}
void applist(resp_https_t response, req_https_t request) {
@@ -586,6 +590,7 @@ void applist(resp_https_t response, req_https_t request) {
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
});
auto args = request->parse_query_string();
@@ -629,6 +634,7 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) {
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
});
if(stream::session_count() == config::stream.channels) {
@@ -675,6 +681,7 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) {
stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT)));
tree.put("root.gamesession", 1);
}
@@ -687,6 +694,7 @@ void resume(bool &host_audio, resp_https_t response, req_https_t request) {
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
});
// It is possible that due a race condition that this if-statement gives a false negative,
@@ -720,6 +728,7 @@ void resume(bool &host_audio, resp_https_t response, req_https_t request) {
stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT)));
tree.put("root.resume", 1);
}
@@ -732,6 +741,7 @@ void cancel(resp_https_t response, req_https_t request) {
pt::write_xml(data, tree);
response->write(data.str());
response->close_connection_after_response = true;
});
// It is possible that due a race condition that this if-statement gives a false positive,
@@ -756,9 +766,14 @@ void appasset(resp_https_t response, req_https_t request) {
std::ifstream in(SUNSHINE_ASSETS_DIR "/box.png");
response->write(SimpleWeb::StatusCode::success_ok, in);
response->close_connection_after_response = true;
}
void start(std::shared_ptr<safe::signal_t> shutdown_event) {
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_http = map_port(PORT_HTTP);
auto port_https = map_port(PORT_HTTPS);
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
@@ -782,22 +797,38 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
// Ugly hack for verifying certificates, see crypto::cert_chain_t::verify() for details
ctx->set_verify_callback([&cert_chain, add_cert](int verified, boost::asio::ssl::verify_context &ctx) {
ctx->set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
// To respond with an error message, a connection must be established
return 1;
});
// /resume doesn't get the parameter "localAudioPlayMode"
// /launch will store it in host_audio
bool host_audio {};
https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once };
http_server_t http_server;
// Verify certificates after establishing connection
https_server.verify = [&cert_chain, add_cert](SSL *ssl) {
auto x509 = SSL_get_peer_certificate(ssl);
if(!x509) {
BOOST_LOG(info) << "unknown -- denied"sv;
return 0;
}
int verified = 0;
auto fg = util::fail_guard([&]() {
char subject_name[256];
auto x509 = ctx.native_handle();
X509_NAME_oneline(X509_get_subject_name(X509_STORE_CTX_get_current_cert(x509)), subject_name, sizeof(subject_name));
X509_NAME_oneline(X509_get_subject_name(x509), subject_name, sizeof(subject_name));
BOOST_LOG(info) << subject_name << " -- "sv << (verified ? "verfied"sv : "denied"sv);
});
if(verified) {
return 1;
}
while(add_cert->peek()) {
char subject_name[256];
@@ -808,22 +839,32 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
cert_chain.add(std::move(cert));
}
auto err_str = cert_chain.verify(X509_STORE_CTX_get_current_cert(ctx.native_handle()));
auto err_str = cert_chain.verify(x509);
if(err_str) {
BOOST_LOG(warning) << "SSL Verification error :: "sv << err_str;
return 0;
return verified;
}
verified = 1;
return 1;
});
// /resume doesn't get the parameter "localAudioPlayMode"
// /launch will store it in host_audio
bool host_audio {};
return verified;
};
https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once };
http_server_t http_server;
https_server.on_verify_failed = [](resp_https_t resp, req_https_t req) {
pt::ptree tree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_xml(data, tree);
resp->write(data.str());
resp->close_connection_after_response = true;
});
tree.put("root.<xmlattr>.status_code"s, 401);
tree.put("root.<xmlattr>.query"s, req->path);
tree.put("root.<xmlattr>.status_message"s, "The client is not authorized. Certificate verification failed."s);
};
https_server.default_resource = not_found<SimpleWeb::HTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
@@ -837,7 +878,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
https_server.config.reuse_address = true;
https_server.config.address = "0.0.0.0"s;
https_server.config.port = PORT_HTTPS;
https_server.config.port = port_https;
http_server.default_resource = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
@@ -846,14 +887,14 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
http_server.config.reuse_address = true;
http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP;
http_server.config.port = port_http;
try {
https_server.bind();
http_server.bind();
}
catch(boost::system::system_error &err) {
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_http << ", "sv << port_http << "]: "sv << err.what();
shutdown_event->raise(true);
return;
@@ -869,7 +910,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
return;
}
BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << port_https << ", "sv << port_https << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}

View File

@@ -6,13 +6,13 @@
#define SUNSHINE_NVHTTP_H
#include "thread_safe.h"
#include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.hpp>
#include <functional>
#include <string>
namespace nvhttp {
void start(std::shared_ptr<safe::signal_t> shutdown_event);
constexpr auto PORT_HTTP = 0;
constexpr auto PORT_HTTPS = -5;
void start();
bool pin(std::string pin);
} // namespace nvhttp

View File

@@ -75,6 +75,7 @@ enum class mem_type_e {
system,
vaapi,
dxgi,
cuda,
unknown
};
@@ -105,13 +106,8 @@ inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
// Dimensions for touchscreen input
struct touch_port_t {
std::uint32_t offset_x, offset_y;
std::uint32_t width, height;
constexpr touch_port_t(
std::uint32_t offset_x, std::uint32_t offset_y,
std::uint32_t width, std::uint32_t height) noexcept : offset_x { offset_x }, offset_y { offset_y },
width { width }, height { height } {};
int offset_x, offset_y;
int width, height;
};
struct gamepad_state_t {
@@ -202,6 +198,7 @@ public:
// Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x, offset_y;
int env_width, env_height;
int width, height;
};
@@ -249,6 +246,13 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
int alloc_gamepad(input_t &input, int nr);
void free_gamepad(input_t &input, int nr);
#define SERVICE_NAME "Sunshine"
#define SERVICE_TYPE "_nvstream._tcp"
namespace publish {
[[nodiscard]] std::unique_ptr<deinit_t> start();
}
[[nodiscard]] std::unique_ptr<deinit_t> init();
} // namespace platf

View File

@@ -147,7 +147,7 @@ struct x11_attr_t : public display_t {
* Last X (NOT the streamed monitor!) size.
* This way we can trigger reinitialization if the dimensions changed while streaming
*/
int lastWidth, lastHeight;
// int env_width, env_height;
x11_attr_t(mem_type_e mem_type) : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } {
XInitThreads();
@@ -204,8 +204,8 @@ struct x11_attr_t : public display_t {
height = xattr.height;
}
lastWidth = xattr.width;
lastHeight = xattr.height;
env_width = xattr.width;
env_height = xattr.height;
return 0;
}
@@ -221,7 +221,7 @@ struct x11_attr_t : public display_t {
refresh();
//The whole X server changed, so we gotta reinit everything
if(xattr.width != lastWidth || xattr.height != lastHeight) {
if(xattr.width != env_width || xattr.height != env_height) {
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
return capture_e::reinit;
}
@@ -289,7 +289,7 @@ struct shm_attr_t : public x11_attr_t {
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
//The whole X server changed, so we gotta reinit everything
if(xattr.width != lastWidth || xattr.height != lastHeight) {
if(xattr.width != env_width || xattr.height != env_height) {
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
return capture_e::reinit;
}
@@ -372,7 +372,7 @@ struct shm_attr_t : public x11_attr_t {
};
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type) {
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi) {
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}

View File

@@ -290,8 +290,8 @@ uint16_t keysym(uint16_t modcode) {
return XK_Control_R;
case 0xA4:
return XK_Alt_L;
case 0xA5: /* return XK_Alt_R; */
return XK_Super_L;
case 0xA5:
return XK_Alt_R;
case 0x5B:
return XK_Super_L;
case 0x5C:

View File

@@ -1,13 +1,21 @@
#include "sunshine/platform/common.h"
#include <fstream>
#include <arpa/inet.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <pwd.h>
#include <unistd.h>
#include <fstream>
#include "misc.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
#ifdef __GNUC__
#define SUNSHINE_GNUC_EXTENSION __extension__
#else
#define SUNSHINE_GNUC_EXTENSION
#endif
using namespace std::literals;
namespace fs = std::filesystem;
@@ -85,4 +93,46 @@ std::string get_mac_address(const std::string_view &address) {
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
} // namespace platf
} // namespace platf
namespace dyn {
void *handle(const std::vector<const char *> &libs) {
void *handle;
for(auto lib : libs) {
handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if(handle) {
return handle;
}
}
std::stringstream ss;
ss << "Couldn't find any of the following libraries: ["sv << libs.front();
std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) {
ss << ", "sv << lib;
});
ss << ']';
BOOST_LOG(error) << ss.str();
return nullptr;
}
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0;
for(auto &func : funcs) {
TUPLE_2D_REF(fn, name, func);
*fn = SUNSHINE_GNUC_EXTENSION(apiproc) dlsym(handle, name);
if(!*fn && strict) {
BOOST_LOG(error) << "Couldn't find function: "sv << name;
err = -1;
}
}
return err;
}
} // namespace dyn

View File

@@ -0,0 +1,14 @@
#ifndef SUNSHINE_PLATFORM_MISC_H
#define SUNSHINE_PLATFORM_MISC_H
#include <vector>
namespace dyn {
typedef void (*apiproc)(void);
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
void *handle(const std::vector<const char *> &libs);
} // namespace dyn
#endif

View File

@@ -0,0 +1,429 @@
// adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
#include <thread>
#include "misc.h"
#include "sunshine/main.h"
#include "sunshine/nvhttp.h"
#include "sunshine/platform/common.h"
#include "sunshine/utility.h"
using namespace std::literals;
namespace avahi {
/** Error codes used by avahi */
enum err_e {
OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */
ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
ERR_MAX = -54
};
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
};
enum ServerState {
SERVER_INVALID, /**< Invalid state (initial) */
SERVER_REGISTERING, /**< Host RRs are being registered */
SERVER_RUNNING, /**< All host RRs have been established */
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
};
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
};
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
};
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
};
/** Some flags for publishing functions */
enum PublishFlags {
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
/** \cond fulldocs */
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
/** \endcond */
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
/** \cond fulldocs */
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
/** \endcond */
};
using IfIndex = int;
using Protocol = int;
struct EntryGroup;
struct Poll;
struct SimplePoll;
struct Client;
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
typedef void (*free_fn)(void *);
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
typedef void (*client_free_fn)(Client *);
typedef char *(*alternative_service_name_fn)(char *);
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
Protocol protocol,
PublishFlags flags,
const char *name,
const char *type,
const char *domain,
const char *host,
uint16_t port,
...);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
typedef int (*entry_group_commit_fn)(EntryGroup *);
typedef char *(*strdup_fn)(const char *);
typedef char *(*strerror_fn)(int);
typedef int (*client_errno_fn)(Client *);
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
typedef int (*simple_poll_loop_fn)(SimplePoll *);
typedef void (*simple_poll_quit_fn)(SimplePoll *);
typedef SimplePoll *(*simple_poll_new_fn)();
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int init_common() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *)&free, "avahi_free" },
{ (dyn::apiproc *)&strdup, "avahi_strdup" },
{ (dyn::apiproc *)&strerror, "avahi_strerror" },
{ (dyn::apiproc *)&simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *)&simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *)&simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *)&simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *)&simple_poll_free, "avahi_simple_poll_free" },
};
if(dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
int init_client() {
if(init_common()) {
return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&client_new, "avahi_client_new" },
{ (dyn::apiproc *)&client_free, "avahi_client_free" },
{ (dyn::apiproc *)&entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *)&entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *)&entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *)&entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *)&entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *)&entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *)&client_errno, "avahi_client_errno" },
};
if(dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
namespace platf::publish {
template<class T>
void free(T *p) {
avahi::free(p);
}
template<class T>
using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
avahi::EntryGroup *group = nullptr;
poll_t poll;
client_t client;
ptr_t<char> name;
void create_services(avahi::Client *c);
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch(state) {
case avahi::ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
break;
case avahi::ENTRY_GROUP_COLLISION:
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
create_services(avahi::entry_group_get_client(g));
break;
case avahi::ENTRY_GROUP_FAILURE:
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
avahi::simple_poll_quit(poll.get());
break;
case avahi::ENTRY_GROUP_UNCOMMITED:
case avahi::ENTRY_GROUP_REGISTERING:;
}
}
void create_services(avahi::Client *c) {
int ret;
auto fg = util::fail_guard([]() {
avahi::simple_poll_quit(poll.get());
});
if(!group) {
if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return;
}
}
if(avahi::entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
ret = avahi::entry_group_add_service(
group,
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
map_port(nvhttp::PORT_HTTP),
nullptr);
if(ret < 0) {
if(ret == avahi::ERR_COLLISION) {
// A service name collision with a local service happened. Let's pick a new name
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get();
avahi::entry_group_reset(group);
create_services(c);
fg.disable();
return;
}
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
return;
}
ret = avahi::entry_group_commit(group);
if(ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
}
}
fg.disable();
}
void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch(state) {
case avahi::CLIENT_S_RUNNING:
create_services(c);
break;
case avahi::CLIENT_FAILURE:
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
avahi::simple_poll_quit(poll.get());
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if(group)
avahi::entry_group_reset(group);
break;
case avahi::CLIENT_CONNECTING:;
}
}
class deinit_t : public ::platf::deinit_t {
public:
std::thread poll_thread;
deinit_t(std::thread poll_thread) : poll_thread { std::move(poll_thread) } {}
~deinit_t() override {
if(avahi::simple_poll_quit && poll) {
avahi::simple_poll_quit(poll.get());
}
if(poll_thread.joinable()) {
poll_thread.join();
}
}
};
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
if(avahi::init_client()) {
return nullptr;
}
int avhi_error;
poll.reset(avahi::simple_poll_new());
if(!poll) {
BOOST_LOG(error) << "Failed to create simple poll object."sv;
return nullptr;
}
name.reset(avahi::strdup(SERVICE_NAME));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
if(!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
}
}; // namespace platf::publish

View File

@@ -1,16 +1,16 @@
#include <sstream>
#include <string>
#include <fcntl.h>
#include <glad/egl.h>
#include <glad/gl.h>
#include <dlfcn.h>
#include <fcntl.h>
extern "C" {
#include <libavcodec/avcodec.h>
}
#include "misc.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
@@ -41,48 +41,6 @@ static void free_frame(AVFrame *frame) {
using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace dyn {
void *handle(const std::vector<const char *> &libs) {
void *handle;
for(auto lib : libs) {
handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if(handle) {
return handle;
}
}
std::stringstream ss;
ss << "Couldn't find any of the following libraries: ["sv << libs.front();
std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) {
ss << ", "sv << lib;
});
ss << ']';
BOOST_LOG(error) << ss.str();
return nullptr;
}
int load(void *handle, std::vector<std::tuple<GLADapiproc *, const char *>> &funcs, bool strict = true) {
for(auto &func : funcs) {
TUPLE_2D_REF(fn, name, func);
*fn = GLAD_GNUC_EXTENSION(GLADapiproc) dlsym(handle, name);
if(!*fn && strict) {
BOOST_LOG(error) << "Couldn't find function: "sv << name;
return -1;
}
}
return 0;
}
} // namespace dyn
namespace va {
constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000;
constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002;
@@ -174,14 +132,14 @@ int init() {
}
}
std::vector<std::tuple<GLADapiproc *, const char *>> funcs {
{ (GLADapiproc *)&terminate, "vaTerminate" },
{ (GLADapiproc *)&initialize, "vaInitialize" },
{ (GLADapiproc *)&errorStr, "vaErrorStr" },
{ (GLADapiproc *)&setErrorCallback, "vaSetErrorCallback" },
{ (GLADapiproc *)&setInfoCallback, "vaSetInfoCallback" },
{ (GLADapiproc *)&queryVendorString, "vaQueryVendorString" },
{ (GLADapiproc *)&exportSurfaceHandle, "vaExportSurfaceHandle" },
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&terminate, "vaTerminate" },
{ (dyn::apiproc *)&initialize, "vaInitialize" },
{ (dyn::apiproc *)&errorStr, "vaErrorStr" },
{ (dyn::apiproc *)&setErrorCallback, "vaSetErrorCallback" },
{ (dyn::apiproc *)&setInfoCallback, "vaSetInfoCallback" },
{ (dyn::apiproc *)&queryVendorString, "vaQueryVendorString" },
{ (dyn::apiproc *)&exportSurfaceHandle, "vaExportSurfaceHandle" },
};
if(dyn::load(handle, funcs)) {
@@ -209,8 +167,8 @@ int init_drm() {
}
}
std::vector<std::tuple<GLADapiproc *, const char *>> funcs {
{ (GLADapiproc *)&getDisplayDRM, "vaGetDisplayDRM" },
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&getDisplayDRM, "vaGetDisplayDRM" },
};
if(dyn::load(handle, funcs)) {

View File

@@ -4,12 +4,12 @@
#include <codecvt>
#include "display.h"
#include "misc.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
#include "display.h"
namespace platf {
using namespace std::literals;
}
@@ -91,6 +91,13 @@ int display_base_t::init() {
});
*/
// Ensure we can duplicate the current display
syncThreadDesktop();
// Get rectangle of full desktop for absolute mouse coordinates
env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
env_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
HRESULT status;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
@@ -133,6 +140,11 @@ int display_base_t::init() {
offset_y = desc.DesktopCoordinates.top;
width = desc.DesktopCoordinates.right - offset_x;
height = desc.DesktopCoordinates.bottom - offset_y;
// left and bottom may be negative, yet absolute mouse coordinates start at 0x0
// Ensure offset starts at 0x0
offset_x -= GetSystemMetrics(SM_XVIRTUALSCREEN);
offset_y -= GetSystemMetrics(SM_YVIRTUALSCREEN);
}
}
@@ -195,7 +207,9 @@ int display_base_t::init() {
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl
<< "Capture size : "sv << width << 'x' << height;
<< "Capture size : "sv << width << 'x' << height << std::endl
<< "Offset : "sv << offset_x << 'x' << offset_y << std::endl
<< "Virtual Desktop : "sv << env_width << 'x' << env_height;
// Bump up thread priority
{

View File

@@ -4,20 +4,20 @@
#include <ViGEm/Client.h>
#include "misc.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
namespace platf {
using namespace std::literals;
volatile HDESK _lastKnownInputDesktop = NULL;
thread_local HDESK _lastKnownInputDesktop = nullptr;
constexpr touch_port_t target_touch_port {
0, 0,
65535, 65535
};
HDESK pairInputDesktop();
class vigem_t {
public:
using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>;
@@ -102,12 +102,12 @@ void send_input(INPUT &i) {
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
auto hDesk = syncThreadDesktop();
if(_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send input"sv;
BOOST_LOG(error) << "Couldn't send input"sv;
}
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
@@ -201,10 +201,6 @@ void scroll(input_t &input, int distance) {
}
void keyboard(input_t &input, uint16_t modcode, bool release) {
if(modcode == VK_RMENU) {
modcode = VK_LWIN;
}
INPUT i {};
i.type = INPUT_KEYBOARD;
auto &ki = i.ki;
@@ -281,29 +277,6 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
}
}
int thread_priority() {
return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1;
}
HDESK pairInputDesktop() {
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
if(NULL == hDesk) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
else {
BOOST_LOG(info) << std::endl
<< "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
if(!SetThreadDesktop(hDesk)) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
CloseDesktop(hDesk);
}
return hDesk;
}
void freeInput(void *p) {
auto vigem = (vigem_t *)p;

View File

@@ -87,4 +87,37 @@ std::string get_mac_address(const std::string_view &address) {
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
HDESK syncThreadDesktop() {
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
if(!hDesk) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']';
return nullptr;
}
if(!SetThreadDesktop(hDesk)) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']';
}
CloseDesktop(hDesk);
return hDesk;
}
void print_status(const std::string_view &prefix, HRESULT status) {
char err_string[1024];
DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
status,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
err_string,
sizeof(err_string),
nullptr);
BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes };
}
} // namespace platf

View File

@@ -0,0 +1,13 @@
#ifndef SUNSHINE_WINDOWS_MISC_H
#define SUNSHINE_WINDOWS_MISC_H
#include <windows.h>
#include <winnt.h>
#include <string_view>
namespace platf {
void print_status(const std::string_view &prefix, HRESULT status);
HDESK syncThreadDesktop();
}
#endif

View File

@@ -0,0 +1,180 @@
#include <winsock2.h>
#include <windows.h>
#include <windns.h>
#include <winerror.h>
#include <boost/asio/ip/host_name.hpp>
#include "misc.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/network.h"
#include "sunshine/nvhttp.h"
#include "sunshine/platform/common.h"
#include "sunshine/thread_safe.h"
using namespace std::literals;
#define __SV(quote) L##quote##sv
#define SV(quote) __SV(quote)
extern "C" {
constexpr auto DNS_REQUEST_PENDING = 9506L;
constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1;
constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
#define SERVICE_DOMAIN "local"
constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN);
constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN);
typedef struct _DNS_SERVICE_INSTANCE {
LPWSTR pszInstanceName;
LPWSTR pszHostName;
IP4_ADDRESS *ip4Address;
IP6_ADDRESS *ip6Address;
WORD wPort;
WORD wPriority;
WORD wWeight;
// Property list
DWORD dwPropertyCount;
PWSTR *keys;
PWSTR *values;
DWORD dwInterfaceIndex;
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE(
_In_ DWORD Status,
_In_ PVOID pQueryContext,
_In_ PDNS_SERVICE_INSTANCE pInstance);
typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE;
typedef struct _DNS_SERVICE_CANCEL {
PVOID reserved;
} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL;
typedef struct _DNS_SERVICE_REGISTER_REQUEST {
ULONG Version;
ULONG InterfaceIndex;
PDNS_SERVICE_INSTANCE pServiceInstance;
PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback;
PVOID pQueryContext;
HANDLE hCredentials;
BOOL unicastEnabled;
} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST;
VOID DnsServiceFreeInstance(
_In_ PDNS_SERVICE_INSTANCE pInstance);
DWORD DnsServiceDeRegister(
_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest,
_Inout_opt_ PDNS_SERVICE_CANCEL pCancel);
DWORD DnsServiceRegister(
_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest,
_Inout_opt_ PDNS_SERVICE_CANCEL pCancel);
PDNS_SERVICE_INSTANCE DnsServiceConstructInstance(
_In_ PCWSTR pServiceName,
_In_ PCWSTR pHostName,
_In_opt_ PIP4_ADDRESS pIp4,
_In_opt_ PIP6_ADDRESS pIp6,
_In_ WORD wPort,
_In_ WORD wPriority,
_In_ WORD wWeight,
_In_ DWORD dwPropertiesCount,
_In_reads_(dwPropertiesCount) PCWSTR *keys,
_In_reads_(dwPropertiesCount) PCWSTR *values);
} /* extern "C" */
namespace platf::publish {
VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
auto alarm = (safe::alarm_t<DNS_STATUS>::element_type *)pQueryContext;
auto fg = util::fail_guard([&]() {
if(pInstance) {
DnsServiceFreeInstance(pInstance);
}
});
if(status) {
print_status("register_cb()"sv, status);
alarm->ring(-1);
return;
}
alarm->ring(0);
}
static int service(bool enable) {
auto alarm = safe::make_alarm<DNS_STATUS>();
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local");
DNS_SERVICE_INSTANCE instance {};
instance.pszInstanceName = name.data();
instance.wPort = map_port(nvhttp::PORT_HTTP);
instance.pszHostName = host.data();
DNS_SERVICE_REGISTER_REQUEST req {};
req.Version = DNS_QUERY_REQUEST_VERSION1;
req.pQueryContext = alarm.get();
req.pServiceInstance = &instance;
req.pRegisterCompletionCallback = register_cb;
DNS_STATUS status {};
if(enable) {
status = DnsServiceRegister(&req, nullptr);
}
else {
status = DnsServiceDeRegister(&req, nullptr);
}
alarm->wait();
status = *alarm->status();
if(status) {
BOOST_LOG(error) << "No mDNS service"sv;
return -1;
}
return 0;
}
class deinit_t : public ::platf::deinit_t {
public:
~deinit_t() override {
if(service(false)) {
std::abort();
}
BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv;
}
};
std::unique_ptr<::platf::deinit_t> start() {
if(service(true)) {
return nullptr;
}
BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv;
return std::make_unique<deinit_t>();
}
} // namespace platf::publish

View File

@@ -2,10 +2,18 @@
// Created by loki on 2/2/20.
//
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
extern "C" {
#include <moonlight-common-c/src/Rtsp.h>
}
#include <array>
#include <cctype>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include "config.h"
#include "input.h"
#include "main.h"
@@ -22,17 +30,6 @@ using asio::ip::udp;
using namespace std::literals;
namespace stream {
//FIXME: Quick and dirty workaround for bug in MinGW 9.3 causing a linker error when using std::to_string
template<class T>
std::string to_string(T &&t) {
std::stringstream ss;
ss << std::forward<T>(t);
return ss.str();
}
constexpr auto RTSP_SETUP_PORT = 48010;
void free_msg(PRTSP_MESSAGE msg) {
freeMessage(msg);
@@ -42,20 +39,180 @@ void free_msg(PRTSP_MESSAGE msg) {
class rtsp_server_t;
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>;
using cmd_func_t = std::function<void(rtsp_server_t *, net::peer_t, msg_t &&)>;
using cmd_func_t = std::function<void(rtsp_server_t *server, tcp::socket &, msg_t &&)>;
void print_msg(PRTSP_MESSAGE msg);
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req);
void cmd_not_found(tcp::socket &sock, msg_t &&req);
void respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload);
class socket_t : public std::enable_shared_from_this<socket_t> {
public:
socket_t(boost::asio::io_service &ios, std::function<void(tcp::socket &sock, msg_t &&)> &&handle_data_fn)
: handle_data_fn { std::move(handle_data_fn) }, sock { ios } {}
void read() {
if(begin == std::end(msg_buf)) {
BOOST_LOG(error) << "RTSP: read(): Exceeded maximum rtsp packet size: "sv << msg_buf.size();
respond(sock, nullptr, 400, "BAD REQUEST", 0, {});
sock.close();
return;
}
sock.async_read_some(
boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)),
boost::bind(
&socket_t::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void read_payload() {
if(begin == std::end(msg_buf)) {
BOOST_LOG(error) << "RTSP: read_payload(): Exceeded maximum rtsp packet size: "sv << msg_buf.size();
respond(sock, nullptr, 400, "BAD REQUEST", 0, {});
sock.close();
return;
}
sock.async_read_some(
boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)),
boost::bind(
&socket_t::handle_payload, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
static void handle_payload(std::shared_ptr<socket_t> &socket, const boost::system::error_code &ec, std::size_t bytes) {
BOOST_LOG(debug) << "handle_payload(): Handle read of size: "sv << bytes << " bytes"sv;
auto sock_close = util::fail_guard([&socket]() {
boost::system::error_code ec;
socket->sock.close(ec);
if(ec) {
BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't close tcp socket: "sv << ec.message();
}
});
if(ec) {
BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't read from tcp socket: "sv << ec.message();
return;
}
auto end = socket->begin + bytes;
msg_t req { new msg_t::element_type {} };
if(auto status = parseRtspMessage(req.get(), socket->msg_buf.data(), (std::size_t)(end - socket->msg_buf.data()))) {
BOOST_LOG(error) << "Malformed RTSP message: ["sv << status << ']';
respond(socket->sock, nullptr, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
}
sock_close.disable();
auto fg = util::fail_guard([&socket]() {
socket->read_payload();
});
auto content_lenght = 0;
for(auto option = req->options; option != nullptr; option = option->next) {
if("Content-length"sv == option->option) {
BOOST_LOG(debug) << "Found Content-Length: "sv << option->content << " bytes"sv;
// If content_length > bytes read, then we need to store current data read,
// to be appended by the next read.
std::string_view content { option->content };
auto begin = std::find_if(std::begin(content), std::end(content), [](auto ch) { return (bool)std::isdigit(ch); });
content_lenght = util::from_chars(begin, std::end(content));
break;
}
}
if(end - socket->crlf >= content_lenght) {
if(end - socket->crlf > content_lenght) {
BOOST_LOG(warning) << "(end - socket->crlf) > content_lenght -- "sv << (std::size_t)(end - socket->crlf) << " > "sv << content_lenght;
}
fg.disable();
print_msg(req.get());
socket->handle_data(std::move(req));
}
socket->begin = end;
}
static void handle_read(std::shared_ptr<socket_t> &socket, const boost::system::error_code &ec, std::size_t bytes) {
BOOST_LOG(debug) << "handle_read(): Handle read of size: "sv << bytes << " bytes"sv;
if(ec) {
BOOST_LOG(error) << "RTSP: handle_read(): Couldn't read from tcp socket: "sv << ec.message();
boost::system::error_code ec;
socket->sock.close(ec);
if(ec) {
BOOST_LOG(error) << "RTSP: handle_read(): Couldn't close tcp socket: "sv << ec.message();
}
return;
}
auto fg = util::fail_guard([&socket]() {
socket->read();
});
auto begin = std::max(socket->begin - 4, socket->begin);
auto buf_size = bytes + (begin - socket->begin);
auto end = begin + buf_size;
constexpr auto needle = "\r\n\r\n"sv;
auto it = std::search(begin, begin + buf_size, std::begin(needle), std::end(needle));
if(it == end) {
socket->begin = end;
return;
}
// Emulate read completion for payload data
socket->begin = it + needle.size();
socket->crlf = socket->begin;
buf_size = end - socket->begin;
fg.disable();
handle_payload(socket, ec, buf_size);
}
void handle_data(msg_t &&req) {
handle_data_fn(sock, std::move(req));
}
std::function<void(tcp::socket &sock, msg_t &&)> handle_data_fn;
tcp::socket sock;
std::array<char, 2048> msg_buf;
char *crlf;
char *begin = msg_buf.data();
};
class rtsp_server_t {
public:
~rtsp_server_t() {
if(_host) {
clear();
}
clear();
}
int bind(std::uint16_t port) {
int bind(std::uint16_t port, boost::system::error_code &ec) {
{
auto lg = _session_slots.lock();
@@ -63,9 +220,72 @@ public:
_slot_count = config::stream.channels;
}
_host = net::host_create(_addr, 1, port);
acceptor.open(tcp::v4(), ec);
if(ec) {
return -1;
}
return !(bool)_host;
acceptor.set_option(boost::asio::socket_base::reuse_address { true });
acceptor.bind(tcp::endpoint(tcp::v4(), port), ec);
if(ec) {
return -1;
}
acceptor.listen(4096, ec);
if(ec) {
return -1;
}
next_socket = std::make_shared<socket_t>(ios, [this](tcp::socket &sock, msg_t &&msg) {
handle_msg(sock, std::move(msg));
});
acceptor.async_accept(next_socket->sock, [this](const auto &ec) {
handle_accept(ec);
});
return 0;
}
template<class T, class X>
void iterate(std::chrono::duration<T, X> timeout) {
ios.run_one_for(timeout);
}
void handle_msg(tcp::socket &sock, msg_t &&req) {
auto func = _map_cmd_cb.find(req->message.request.command);
if(func != std::end(_map_cmd_cb)) {
func->second(this, sock, std::move(req));
}
else {
cmd_not_found(sock, std::move(req));
}
}
void handle_accept(const boost::system::error_code &ec) {
if(ec) {
BOOST_LOG(error) << "Couldn't accept incoming connections: "sv << ec.message();
//Stop server
clear();
return;
}
auto socket = std::move(next_socket);
socket->read();
next_socket = std::make_shared<socket_t>(ios, [this](tcp::socket &sock, msg_t &&msg) {
handle_msg(sock, std::move(msg));
});
acceptor.async_accept(next_socket->sock, [this](const auto &ec) {
handle_accept(ec);
});
}
void map(const std::string_view &type, cmd_func_t cb) {
_map_cmd_cb.emplace(type, std::move(cb));
}
void session_raise(launch_session_t launch_session) {
@@ -78,73 +298,7 @@ public:
return config::stream.channels - _slot_count;
}
template<class T, class X>
void iterate(std::chrono::duration<T, X> timeout) {
ENetEvent event;
auto res = enet_host_service(_host.get(), &event, std::chrono::floor<std::chrono::milliseconds>(timeout).count());
if(res > 0) {
switch(event.type) {
case ENET_EVENT_TYPE_RECEIVE: {
net::packet_t packet { event.packet };
net::peer_t peer { event.peer };
msg_t req { new msg_t::element_type };
//TODO: compare addresses of the peers
if(_queue_packet.second == nullptr) {
parseRtspMessage(req.get(), (char *)packet->data, packet->dataLength);
for(auto option = req->options; option != nullptr; option = option->next) {
if("Content-length"sv == option->option) {
_queue_packet = std::make_pair(peer, std::move(packet));
return;
}
}
}
else {
std::vector<char> full_payload;
auto old_msg = std::move(_queue_packet);
auto &old_packet = old_msg.second;
std::string_view new_payload { (char *)packet->data, packet->dataLength };
std::string_view old_payload { (char *)old_packet->data, old_packet->dataLength };
full_payload.resize(new_payload.size() + old_payload.size());
std::copy(std::begin(old_payload), std::end(old_payload), std::begin(full_payload));
std::copy(std::begin(new_payload), std::end(new_payload), std::begin(full_payload) + old_payload.size());
parseRtspMessage(req.get(), full_payload.data(), full_payload.size());
}
print_msg(req.get());
msg_t resp;
auto func = _map_cmd_cb.find(req->message.request.command);
if(func != std::end(_map_cmd_cb)) {
func->second(this, peer, std::move(req));
}
else {
cmd_not_found(host(), peer, std::move(req));
}
return;
}
case ENET_EVENT_TYPE_CONNECT:
BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv;
break;
case ENET_EVENT_TYPE_DISCONNECT:
BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv;
break;
case ENET_EVENT_TYPE_NONE:
break;
}
}
}
void map(const std::string_view &type, cmd_func_t cb) {
_map_cmd_cb.emplace(type, std::move(cb));
}
safe::event_t<launch_session_t> launch_event;
void clear(bool all = true) {
auto lg = _session_slots.lock();
@@ -160,10 +314,8 @@ public:
}
}
if(all) {
std::for_each(_host->peers, _host->peers + _host->peerCount, [](auto &peer) {
enet_peer_disconnect_now(&peer, 0);
});
if(all && !ios.stopped()) {
ios.stop();
}
}
@@ -188,24 +340,17 @@ public:
return nullptr;
}
net::host_t::pointer host() const {
return _host.get();
}
safe::event_t<launch_session_t> launch_event;
private:
// named _queue_packet because I want to make it an actual queue
// It's like this for convenience sake
std::pair<net::peer_t, net::packet_t> _queue_packet;
std::unordered_map<std::string_view, cmd_func_t> _map_cmd_cb;
util::sync_t<std::vector<std::shared_ptr<session_t>>> _session_slots;
int _slot_count;
ENetAddress _addr;
net::host_t _host;
boost::asio::io_service ios;
tcp::acceptor acceptor { ios };
std::shared_ptr<socket_t> next_socket;
};
rtsp_server_t server;
@@ -221,9 +366,26 @@ int session_count() {
return server.session_count();
}
void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
int send(tcp::socket &sock, const std::string_view &sv) {
std::size_t bytes_send = 0;
while(bytes_send != sv.size()) {
boost::system::error_code ec;
bytes_send += sock.send(boost::asio::buffer(sv.substr(bytes_send)), 0, ec);
if(ec) {
BOOST_LOG(error) << "RTSP: Couldn't send data over tcp socket: "sv << ec.message();
return -1;
}
}
return 0;
}
void respond(tcp::socket &sock, msg_t &resp) {
auto payload = std::make_pair(resp->payload, resp->payloadLength);
// Restore response message for proper destruction
auto lg = util::fail_guard([&]() {
resp->payload = payload.first;
resp->payloadLength = payload.second;
@@ -241,57 +403,44 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
<< "---End Response---"sv << std::endl;
std::string_view tmp_resp { raw_resp.get(), (size_t)serialized_len };
{
auto packet = enet_packet_create(tmp_resp.data(), tmp_resp.size(), ENET_PACKET_FLAG_RELIABLE);
if(enet_peer_send(peer, 0, packet)) {
enet_packet_destroy(packet);
return;
}
enet_host_flush(host);
if(send(sock, tmp_resp)) {
return;
}
if(payload.second > 0) {
auto packet = enet_packet_create(payload.first, payload.second, ENET_PACKET_FLAG_RELIABLE);
if(enet_peer_send(peer, 0, packet)) {
enet_packet_destroy(packet);
return;
}
enet_host_flush(host);
}
send(sock, std::string_view { payload.first, (std::size_t)payload.second });
}
void respond(net::host_t::pointer host, net::peer_t peer, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) {
void respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) {
msg_t resp { new msg_t::element_type };
createRtspResponse(resp.get(), nullptr, 0, const_cast<char *>("RTSP/1.0"), statuscode, const_cast<char *>(status_msg), seqn, options, const_cast<char *>(payload.data()), (int)payload.size());
respond(host, peer, resp);
respond(sock, resp);
}
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req) {
respond(host, peer, nullptr, 404, "NOT FOUND", req->sequenceNumber, {});
void cmd_not_found(tcp::socket &sock, msg_t &&req) {
respond(sock, nullptr, 404, "NOT FOUND", req->sequenceNumber, {});
}
void cmd_option(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
void cmd_option(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
auto seqn_str = std::to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
}
void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
void cmd_describe(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
auto seqn_str = std::to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
std::stringstream ss;
@@ -326,18 +475,19 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
ss << std::endl;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, ss.str());
respond(sock, &option, 200, "OK", req->sequenceNumber, ss.str());
}
void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM options[2] {};
void cmd_setup(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
OPTION_ITEM options[3] {};
auto &seqn = options[0];
auto &session_option = options[1];
auto &port_option = options[2];
seqn.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
auto seqn_str = std::to_string(req->sequenceNumber);
seqn.content = const_cast<char *>(seqn_str.c_str());
std::string_view target { req->message.request.target };
@@ -345,34 +495,52 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
auto end = std::find(begin, std::end(target), '/');
std::string_view type { begin, (size_t)std::distance(begin, end) };
std::uint16_t port;
if(type == "audio"sv) {
seqn.next = &session_option;
session_option.option = const_cast<char *>("Session");
session_option.content = const_cast<char *>("DEADBEEFCAFE;timeout = 90");
port = map_port(stream::AUDIO_STREAM_PORT);
}
else if(type != "video"sv && type != "control"sv) {
cmd_not_found(server->host(), peer, std::move(req));
else if(type == "video"sv) {
port = map_port(stream::VIDEO_STREAM_PORT);
}
else if(type == "control"sv) {
port = map_port(stream::CONTROL_PORT);
}
else {
cmd_not_found(sock, std::move(req));
return;
}
respond(server->host(), peer, &seqn, 200, "OK", req->sequenceNumber, {});
seqn.next = &session_option;
session_option.option = const_cast<char *>("Session");
session_option.content = const_cast<char *>("DEADBEEFCAFE;timeout = 90");
session_option.next = &port_option;
// Moonlight merely requires 'server_port=<port>'
auto port_value = "server_port=" + std::to_string(port);
port_option.option = const_cast<char *>("Transport");
port_option.content = port_value.data();
respond(sock, &seqn, 200, "OK", req->sequenceNumber, {});
}
void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
auto seqn_str = std::to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
if(!server->launch_event.peek()) {
// /launch has not been used
respond(server->host(), peer, &option, 503, "Service Unavailable", req->sequenceNumber, {});
respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {});
return;
}
auto launch_session { server->launch_event.pop() };
@@ -424,6 +592,9 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
args.try_emplace("x-nv-vqos[0].bitStreamFormat"sv, "0"sv);
args.try_emplace("x-nv-video[0].dynamicRangeMode"sv, "0"sv);
args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv);
args.try_emplace("x-nv-general.useReliableUdp"sv, "1"sv);
args.try_emplace("x-nv-vqos[0].fec.minRequiredFecPackets"sv, "0"sv);
args.try_emplace("x-nv-general.featureFlags"sv, "135"sv);
config_t config;
@@ -436,7 +607,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
config.audio.flags[audio::config_t::HIGH_QUALITY] =
util::from_view(args.at("x-nv-audio.surround.AudioQuality"sv));
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
config.controlProtocolType = util::from_view(args.at("x-nv-general.useReliableUdp"sv));
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
config.minRequiredFecPackets = util::from_view(args.at("x-nv-vqos[0].fec.minRequiredFecPackets"sv));
config.featureFlags = util::from_view(args.at("x-nv-general.featureFlags"sv));
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv));
@@ -450,14 +624,14 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
}
catch(std::out_of_range &) {
respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
}
if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 1) {
BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
}
@@ -467,34 +641,37 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
if(!slot) {
BOOST_LOG(info) << "Ran out of slots for client from ["sv << ']';
respond(server->host(), peer, &option, 503, "Service Unavailable", req->sequenceNumber, {});
respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {});
return;
}
if(session::start(*session, platf::from_sockaddr((sockaddr *)&peer->address.address))) {
if(session::start(*session, sock.remote_endpoint().address().to_string())) {
BOOST_LOG(error) << "Failed to start a streaming session"sv;
server->clear(slot);
respond(server->host(), peer, &option, 500, "Internal Server Error", req->sequenceNumber, {});
respond(sock, &option, 500, "Internal Server Error", req->sequenceNumber, {});
return;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
}
void cmd_play(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
void cmd_play(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
auto seqn_str = std::to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
}
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event) {
void rtpThread() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
server.map("OPTIONS"sv, &cmd_option);
server.map("DESCRIBE"sv, &cmd_describe);
server.map("SETUP"sv, &cmd_setup);
@@ -502,8 +679,9 @@ void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event) {
server.map("PLAY"sv, &cmd_play);
if(server.bind(RTSP_SETUP_PORT)) {
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << RTSP_SETUP_PORT << "], likely another process already bound to the port"sv;
boost::system::error_code ec;
if(server.bind(map_port(RTSP_SETUP_PORT), ec)) {
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(RTSP_SETUP_PORT) << "], " << ec.message();
shutdown_event->raise(true);
return;
@@ -512,7 +690,7 @@ void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event) {
while(!shutdown_event->peek()) {
server.iterate(std::min(500ms, config::stream.ping_timeout));
if(broadcast_shutdown_event.peek()) {
if(broadcast_shutdown_event->peek()) {
server.clear();
}
else {

View File

@@ -11,6 +11,8 @@
#include "thread_safe.h"
namespace stream {
constexpr auto RTSP_SETUP_PORT = 21;
struct launch_session_t {
crypto::aes_t gcm_key;
crypto::aes_t iv;
@@ -21,7 +23,7 @@ struct launch_session_t {
void launch_session_raise(launch_session_t launch_session);
int session_count();
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event);
void rtpThread();
} // namespace stream

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,19 @@
#include "video.h"
namespace stream {
constexpr auto VIDEO_STREAM_PORT = 9;
constexpr auto CONTROL_PORT = 10;
constexpr auto AUDIO_STREAM_PORT = 11;
struct session_t;
struct config_t {
audio::config_t audio;
video::config_t monitor;
int packetsize;
int minRequiredFecPackets;
int featureFlags;
int controlProtocolType;
std::optional<int> gcmap;
};
@@ -35,8 +43,6 @@ void stop(session_t &session);
void join(session_t &session);
state_e state(session_t &session);
} // namespace session
extern safe::signal_t broadcast_shutdown_event;
} // namespace stream
#endif //SUNSHINE_STREAM_H

View File

@@ -8,6 +8,7 @@
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <mutex>
#include <vector>
@@ -97,8 +98,6 @@ public:
}
bool peek() {
std::lock_guard lg { _lock };
return _continue && (bool)_status;
}
@@ -242,8 +241,6 @@ public:
}
bool peek() {
std::lock_guard lg { _lock };
return _continue && !_queue.empty();
}
@@ -426,6 +423,89 @@ auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
}
using signal_t = event_t<bool>;
class mail_raw_t;
using mail_t = std::shared_ptr<mail_raw_t>;
void cleanup(mail_raw_t *);
template<class T>
class post_t : public T {
public:
template<class... Args>
post_t(mail_t mail, Args &&...args) : T(std::forward<Args>(args)...), mail { std::move(mail) } {}
mail_t mail;
~post_t() {
cleanup(mail.get());
}
};
template<class T>
inline auto lock(const std::weak_ptr<void> &wp) {
return std::reinterpret_pointer_cast<typename T::element_type>(wp.lock());
}
class mail_raw_t : public std::enable_shared_from_this<mail_raw_t> {
public:
template<class T>
using event_t = std::shared_ptr<post_t<event_t<T>>>;
template<class T>
using queue_t = std::shared_ptr<post_t<queue_t<T>>>;
template<class T>
event_t<T> event(const std::string_view &id) {
std::lock_guard lg { mutex };
auto it = id_to_post.find(id);
if(it != std::end(id_to_post)) {
return lock<event_t<T>>(it->second);
}
auto post = std::make_shared<typename event_t<T>::element_type>(shared_from_this());
id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> { std::string { id }, post });
return post;
}
template<class T>
queue_t<T> queue(const std::string_view &id) {
std::lock_guard lg { mutex };
auto it = id_to_post.find(id);
if(it != std::end(id_to_post)) {
return lock<queue_t<T>>(it->second);
}
auto post = std::make_shared<typename queue_t<T>::element_type>(shared_from_this(), 32);
id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> { std::string { id }, post });
return post;
}
void cleanup() {
std::lock_guard lg { mutex };
for(auto it = std::begin(id_to_post); it != std::end(id_to_post); ++it) {
auto &weak = it->second;
if(weak.expired()) {
id_to_post.erase(it);
return;
}
}
}
std::mutex mutex;
std::map<std::string, std::weak_ptr<void>, std::less<>> id_to_post;
};
inline void cleanup(mail_raw_t *mail) {
mail->cleanup();
}
} // namespace safe
#endif //SUNSHINE_THREAD_SAFE_H

184
sunshine/upnp.cpp Normal file
View File

@@ -0,0 +1,184 @@
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#include "config.h"
#include "confighttp.h"
#include "main.h"
#include "network.h"
#include "nvhttp.h"
#include "rtsp.h"
#include "stream.h"
#include "upnp.h"
#include "utility.h"
using namespace std::literals;
namespace upnp {
constexpr auto INET6_ADDRESS_STRLEN = 46;
constexpr auto IPv4 = 0;
constexpr auto IPv6 = 1;
using device_t = util::safe_ptr<UPNPDev, freeUPNPDevlist>;
KITTY_USING_MOVE_T(urls_t, UPNPUrls, , {
FreeUPNPUrls(&el);
});
struct mapping_t {
struct {
std::string wan;
std::string lan;
} port;
std::string description;
bool tcp;
};
void unmap(
const urls_t &urls,
const IGDdatas &data,
std::vector<mapping_t>::const_reverse_iterator begin,
std::vector<mapping_t>::const_reverse_iterator end) {
BOOST_LOG(debug) << "Unmapping UPNP ports"sv;
for(auto it = begin; it != end; ++it) {
auto status = UPNP_DeletePortMapping(
urls->controlURL,
data.first.servicetype,
it->port.wan.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr);
if(status) {
BOOST_LOG(warning) << "Failed to unmap port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
break;
}
}
}
class deinit_t : public platf::deinit_t {
public:
using iter_t = std::vector<mapping_t>::const_reverse_iterator;
deinit_t(urls_t &&urls, IGDdatas data, std::vector<mapping_t> &&mapping)
: urls { std::move(urls) }, data { data }, mapping { std::move(mapping) } {}
~deinit_t() {
BOOST_LOG(info) << "Unmapping UPNP ports..."sv;
unmap(urls, data, std::rbegin(mapping), std::rend(mapping));
}
urls_t urls;
IGDdatas data;
std::vector<mapping_t> mapping;
};
static std::string_view status_string(int status) {
switch(status) {
case 0:
return "No IGD device found"sv;
case 1:
return "Valid IGD device found"sv;
case 2:
return "A UPnP device has been found, but it wasn't recognized as an IGD"sv;
}
return "Unknown status"sv;
}
std::unique_ptr<platf::deinit_t> start() {
int err {};
device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) };
if(!device || err) {
BOOST_LOG(error) << "Couldn't discover any UPNP devices"sv;
return nullptr;
}
for(auto dev = device.get(); dev != nullptr; dev = dev->pNext) {
BOOST_LOG(debug) << "Found device: "sv << dev->descURL;
}
std::array<char, INET6_ADDRESS_STRLEN> lan_addr;
std::array<char, INET6_ADDRESS_STRLEN> wan_addr;
urls_t urls;
IGDdatas data;
auto status = UPNP_GetValidIGD(device.get(), &urls.el, &data, lan_addr.data(), lan_addr.size());
if(status != 1) {
BOOST_LOG(error) << status_string(status);
return nullptr;
}
BOOST_LOG(debug) << "Found valid IGD device: "sv << urls->rootdescURL;
if(UPNP_GetExternalIPAddress(urls->controlURL, data.first.servicetype, wan_addr.data())) {
BOOST_LOG(warning) << "Could not get external ip"sv;
}
else {
BOOST_LOG(debug) << "Found external ip: "sv << wan_addr.data();
if(config::nvhttp.external_ip.empty()) {
config::nvhttp.external_ip = wan_addr.data();
}
}
if(!config::sunshine.flags[config::flag::UPNP]) {
return nullptr;
}
auto rtsp = std::to_string(map_port(stream::RTSP_SETUP_PORT));
auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT));
auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT));
auto control = std::to_string(map_port(stream::CONTROL_PORT));
auto gs_http = std::to_string(map_port(nvhttp::PORT_HTTP));
auto gs_https = std::to_string(map_port(nvhttp::PORT_HTTPS));
auto wm_http = std::to_string(map_port(confighttp::PORT_HTTPS));
std::vector<mapping_t> mappings {
{ rtsp, rtsp, "RTSP setup port"s, true },
{ video, video, "Video stream port"s, false },
{ audio, audio, "Control stream port"s, false },
{ control, control, "Audio stream port"s, false },
{ gs_http, gs_http, "Gamestream http port"s, true },
{ gs_https, gs_https, "Gamestream https port"s, true },
};
// Only map port for the Web Manager if it is configured to accept connection from WAN
if(net::from_enum_string(config::nvhttp.origin_web_ui_allowed) > net::LAN) {
mappings.emplace_back(mapping_t { wm_http, wm_http, "Sunshine Web UI port"s, true });
}
auto it = std::begin(mappings);
status = 0;
for(; it != std::end(mappings); ++it) {
status = UPNP_AddPortMapping(
urls->controlURL,
data.first.servicetype,
it->port.wan.c_str(),
it->port.lan.c_str(),
lan_addr.data(),
it->description.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr,
"86400");
if(status) {
BOOST_LOG(error) << "Failed to map port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
break;
}
}
if(status) {
unmap(urls, data, std::make_reverse_iterator(it), std::rend(mappings));
return nullptr;
}
return std::make_unique<deinit_t>(std::move(urls), data, std::move(mappings));
}
} // namespace upnp

10
sunshine/upnp.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SUNSHINE_UPNP_H
#define SUNSHINE_UPNP_H
#include "platform/common.h"
namespace upnp {
[[nodiscard]] std::unique_ptr<platf::deinit_t> start();
}
#endif

View File

@@ -881,7 +881,7 @@ struct endian_helper<T, std::enable_if_t<
static inline T big(T x) {
if(!x) return x;
if constexpr(endianness<T>::big) {
if constexpr(endianness<T>::little) {
auto *data = reinterpret_cast<uint8_t *>(&*x);
std::reverse(data, data + sizeof(*x));

View File

@@ -12,6 +12,7 @@ extern "C" {
#include "cbs.h"
#include "config.h"
#include "input.h"
#include "main.h"
#include "platform/common.h"
#include "round_robin.h"
@@ -24,8 +25,11 @@ extern "C" {
}
#endif
namespace video {
using namespace std::literals;
namespace video {
constexpr auto hevc_nalu = "\000\000\000\001("sv;
constexpr auto h264_nalu = "\000\000\000\001e"sv;
void free_ctx(AVCodecContext *ctx) {
avcodec_free_context(&ctx);
@@ -67,6 +71,7 @@ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt);
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
util::Either<buffer_t, int> vaapi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
util::Either<buffer_t, int> cuda_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format);
@@ -81,14 +86,14 @@ public:
std::uint8_t *data[4];
data[0] = sw_frame->data[0] + offset;
data[0] = sw_frame->data[0] + offsetY;
if(sw_frame->format == AV_PIX_FMT_NV12) {
data[1] = sw_frame->data[1] + offset;
data[1] = sw_frame->data[1] + offsetUV;
data[2] = nullptr;
}
else {
data[1] = sw_frame->data[1] + offset / 2;
data[2] = sw_frame->data[2] + offset / 2;
data[1] = sw_frame->data[1] + offsetUV;
data[2] = sw_frame->data[2] + offsetUV;
data[3] = nullptr;
}
@@ -204,9 +209,10 @@ public:
out_height = in_height * scalar;
// result is always positive
auto offsetX = (frame->width - out_width) / 2;
auto offsetY = (frame->height - out_height) / 2;
offset = offsetX + offsetY * frame->width;
auto offsetW = (frame->width - out_width) / 2;
auto offsetH = (frame->height - out_height) / 2;
offsetUV = (offsetW + offsetH * frame->width / 2) / 2;
offsetY = offsetW + offsetH * frame->width;
sws.reset(sws_getContext(
in_width, in_height, AV_PIX_FMT_BGR0,
@@ -226,7 +232,8 @@ public:
sws_t sws;
// offset of input image to output frame in pixels
int offset;
int offsetUV;
int offsetY;
};
enum flag_e {
@@ -245,6 +252,7 @@ struct encoder_t {
SLICE, // Allow frame to be partitioned into multiple slices
DYNAMIC_RANGE, // hdr
VUI_PARAMETERS, // AMD encoder with VAAPI doesn't add VUI parameters to SPS
NALU_PREFIX_5b, // libx264/libx265 have a 3-byte nalu prefix instead of 4-byte nalu prefix
MAX_FLAGS
};
@@ -259,6 +267,7 @@ struct encoder_t {
_CONVERT(SLICE);
_CONVERT(DYNAMIC_RANGE);
_CONVERT(VUI_PARAMETERS);
_CONVERT(NALU_PREFIX_5b);
_CONVERT(MAX_FLAGS);
}
#undef _CONVERT
@@ -312,16 +321,19 @@ struct encoder_t {
class session_t {
public:
session_t() = default;
session_t(ctx_t &&ctx, util::wrap_ptr<platf::hwdevice_t> &&device, util::buffer_t<std::uint8_t> &&sps) : ctx { std::move(ctx) }, device { std::move(device) }, sps { std::move(sps) } {}
session_t(ctx_t &&ctx, util::wrap_ptr<platf::hwdevice_t> &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {}
session_t(session_t &&other) noexcept : ctx { std::move(other.ctx) }, device { std::move(other.device) }, sps { std::move(sps) }, sps_old { std::move(sps_old) } {}
session_t(session_t &&other) noexcept = default;
// Ensure objects are destroyed in the correct order
session_t &operator=(session_t &&other) {
device = std::move(other.device);
ctx = std::move(other.ctx);
sps = std::move(other.sps);
sps_old = std::move(other.sps_old);
device = std::move(other.device);
ctx = std::move(other.ctx);
replacements = std::move(other.replacements);
sps = std::move(other.sps);
vps = std::move(other.vps);
inject = other.inject;
return *this;
}
@@ -329,18 +341,24 @@ public:
ctx_t ctx;
util::wrap_ptr<platf::hwdevice_t> device;
util::buffer_t<std::uint8_t> sps;
util::buffer_t<std::uint8_t> sps_old;
std::vector<packet_raw_t::replace_t> replacements;
cbs::nal_t sps;
cbs::nal_t vps;
// inject sps/vps data into idr pictures
int inject;
};
struct sync_session_ctx_t {
safe::signal_t *shutdown_event;
safe::signal_t *join_event;
packet_queue_t packets;
idr_event_t idr_events;
safe::mail_raw_t::event_t<bool> shutdown_event;
safe::mail_raw_t::queue_t<packet_t> packets;
safe::mail_raw_t::event_t<bool> idr_events;
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_events;
config_t config;
int frame_nr;
int key_frame_nr;
void *channel_data;
};
@@ -385,13 +403,19 @@ void end_capture_async(capture_thread_async_ctx_t &ctx);
auto capture_thread_async = safe::make_shared<capture_thread_async_ctx_t>(start_capture_async, end_capture_async);
auto capture_thread_sync = safe::make_shared<capture_thread_sync_ctx_t>(start_capture_sync, end_capture_sync);
#ifdef _WIN32
static encoder_t nvenc {
"nvenc"sv,
{ (int)nv::profile_h264_e::high, (int)nv::profile_hevc_e::main, (int)nv::profile_hevc_e::main_10 },
#ifdef _WIN32
AV_HWDEVICE_TYPE_D3D11VA,
AV_PIX_FMT_D3D11,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
#else
AV_HWDEVICE_TYPE_CUDA,
AV_PIX_FMT_CUDA,
// Fully planar YUV formats are more efficient for sws_scale()
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
#endif
{
{
{ "forced-idr"s, 1 },
@@ -415,10 +439,16 @@ static encoder_t nvenc {
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
"h264_nvenc"s,
},
#ifdef _WIN32
DEFAULT,
dxgi_make_hwdevice_ctx
#else
SYSTEM_MEMORY,
cuda_make_hwdevice_ctx
#endif
};
#ifdef _WIN32
static encoder_t amdvce {
"amdvce"sv,
{ FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN },
@@ -520,8 +550,8 @@ static encoder_t vaapi {
#endif
static std::vector<encoder_t> encoders {
#ifdef _WIN32
nvenc,
#ifdef _WIN32
amdvce,
#endif
#ifdef __linux__
@@ -679,13 +709,13 @@ void captureThread(
}
}
int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_queue_t &packets, void *channel_data) {
int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::mail_raw_t::queue_t<packet_t> &packets, void *channel_data) {
frame->pts = frame_nr;
auto &ctx = session.ctx;
auto &sps = session.sps;
auto &sps_old = session.sps_old;
auto &sps = session.sps;
auto &vps = session.vps;
/* send the frame to the encoder */
auto ret = avcodec_send_frame(ctx.get(), frame);
@@ -707,12 +737,33 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_
return ret;
}
if(sps.size() && !sps_old.size()) {
sps_old = cbs::read_sps(packet.get(), AV_CODEC_ID_H264);
if(session.inject) {
if(session.inject == 1) {
auto h264 = cbs::make_sps_h264(ctx.get(), packet.get());
sps = std::move(h264.sps);
}
else {
auto hevc = cbs::make_sps_hevc(ctx.get(), packet.get());
sps = std::move(hevc.sps);
vps = std::move(hevc.vps);
session.replacements.emplace_back(
std::string_view((char *)std::begin(vps.old), vps.old.size()),
std::string_view((char *)std::begin(vps._new), vps._new.size()));
}
session.inject = 0;
session.replacements.emplace_back(
std::string_view((char *)std::begin(sps.old), sps.old.size()),
std::string_view((char *)std::begin(sps._new), sps._new.size()));
}
packet->sps.old = std::string_view((char *)std::begin(sps_old), sps_old.size());
packet->sps.replacement = std::string_view((char *)std::begin(sps), sps.size());
packet->channel_data = channel_data;
packet->replacements = &session.replacements;
packet->channel_data = channel_data;
packets->raise(std::move(packet));
}
@@ -819,6 +870,9 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
sw_fmt = encoder.dynamic_pix_fmt;
}
// Used by cbs::make_sps_hevc
ctx->sw_pix_fmt = sw_fmt;
buffer_t hwdevice_ctx;
if(hardware) {
ctx->pix_fmt = encoder.dev_pix_fmt;
@@ -926,24 +980,26 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
device->set_colorspace(sws_color_space, ctx->color_range);
if(video_format[encoder_t::VUI_PARAMETERS]) {
return std::make_optional<session_t>(
std::move(ctx),
std::move(device),
util::buffer_t<std::uint8_t> {});
}
return std::make_optional<session_t>(
session_t session {
std::move(ctx),
std::move(device),
cbs::make_sps(ctx.get(), config.videoFormat));
// 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc
(1 - (int)video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat),
};
if(!video_format[encoder_t::NALU_PREFIX_5b]) {
auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu;
session.replacements.emplace_back(nalu_prefix.substr(1), nalu_prefix);
}
return std::make_optional(std::move(session));
}
void encode_run(
int &frame_nr, int &key_frame_nr, // Store progress of the frame number
safe::signal_t *shutdown_event, // Signal for shutdown event of the session
packet_queue_t packets,
idr_event_t idr_events,
int &frame_nr, // Store progress of the frame number
safe::mail_t mail,
img_event_t images,
config_t config,
int width, int height,
@@ -962,6 +1018,11 @@ void encode_run(
auto next_frame = std::chrono::steady_clock::now();
auto frame = session->device->frame;
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto packets = mail::man->queue<packet_t>(mail::video_packets);
auto idr_events = mail->event<bool>(mail::idr);
while(true) {
if(shutdown_event->peek() || reinit_event.peek() || !images->running()) {
break;
@@ -971,27 +1032,14 @@ void encode_run(
frame->pict_type = AV_PICTURE_TYPE_I;
frame->key_frame = 1;
auto event = idr_events->pop();
if(!event) {
return;
}
auto end = event->second;
frame_nr = end;
key_frame_nr = end + config.framerate;
}
else if(frame_nr == key_frame_nr) {
auto frame = session->device->frame;
frame->pict_type = AV_PICTURE_TYPE_I;
frame->key_frame = 1;
idr_events->pop();
}
std::this_thread::sleep_until(next_frame);
next_frame += delay;
// When Moonlight request an IDR frame, send frames even if there is no new captured frame
if(frame_nr > (key_frame_nr + config.framerate) || images->peek()) {
if(!frame->key_frame || images->peek()) {
if(auto img = images->pop(delay)) {
session->device->convert(*img);
}
@@ -1013,6 +1061,34 @@ void encode_run(
}
}
input::touch_port_t make_port(platf::display_t *display, const config_t &config) {
float wd = display->width;
float hd = display->height;
float wt = config.width;
float ht = config.height;
auto scalar = std::fminf(wt / wd, ht / hd);
auto w2 = scalar * wd;
auto h2 = scalar * hd;
auto offsetX = (config.width - w2) * 0.5f;
auto offsetY = (config.height - h2) * 0.5f;
return input::touch_port_t {
display->offset_x,
display->offset_y,
config.width,
config.height,
display->env_width,
display->env_height,
offsetX,
offsetY,
1.0f / scalar,
};
}
std::optional<sync_session_t> make_synced_session(platf::display_t *disp, const encoder_t &encoder, platf::img_t &img, sync_session_ctx_t &ctx) {
sync_session_t encode_session;
@@ -1027,6 +1103,9 @@ std::optional<sync_session_t> make_synced_session(platf::display_t *disp, const
return std::nullopt;
}
// absolute mouse coordinates require that the dimensions of the screen are known
ctx.touch_port_events->raise(make_port(disp, ctx.config));
auto session = make_session(encoder, ctx.config, img.width, img.height, hwdevice.get());
if(!session) {
return std::nullopt;
@@ -1064,9 +1143,6 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
return encode_e::error;
}
// absolute mouse coordinates require that the dimensions of the screen are known
input::touch_port_event->raise(disp->offset_x, disp->offset_y, disp->width, disp->height);
std::vector<sync_session_t> synced_sessions;
for(auto &ctx : synced_session_ctxs) {
auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx);
@@ -1137,15 +1213,7 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
frame->pict_type = AV_PICTURE_TYPE_I;
frame->key_frame = 1;
auto event = ctx->idr_events->pop();
auto end = event->second;
ctx->frame_nr = end;
ctx->key_frame_nr = end + ctx->config.framerate;
}
else if(ctx->frame_nr == ctx->key_frame_nr) {
frame->pict_type = AV_PICTURE_TYPE_I;
frame->key_frame = 1;
ctx->idr_events->pop();
}
if(img_tmp) {
@@ -1217,12 +1285,12 @@ void captureThreadSync() {
}
void capture_async(
safe::signal_t *shutdown_event,
packet_queue_t &packets,
idr_event_t &idr_events,
safe::mail_t mail,
config_t &config,
void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto images = std::make_shared<img_event_t::element_type>();
auto lg = util::fail_guard([&]() {
images->stop();
@@ -1242,8 +1310,9 @@ void capture_async(
return;
}
int frame_nr = 1;
int key_frame_nr = 1;
int frame_nr = 1;
auto touch_port_event = mail->event<input::touch_port_t>(mail::touch_port);
while(!shutdown_event->peek() && images->running()) {
// Wait for the main capture event when the display is being reinitialized
@@ -1262,7 +1331,8 @@ void capture_async(
display = ref->display_wp->lock();
}
auto pix_fmt = config.dynamicRange == 0 ? platf::pix_fmt_e::yuv420p : platf::pix_fmt_e::yuv420p10;
auto &encoder = encoders.front();
auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt);
auto hwdevice = display->make_hwdevice(pix_fmt);
if(!hwdevice) {
return;
@@ -1276,12 +1346,11 @@ void capture_async(
images->raise(std::move(dummy_img));
// absolute mouse coordinates require that the dimensions of the screen are known
input::touch_port_event->raise(display->offset_x, display->offset_y, display->width, display->height);
touch_port_event->raise(make_port(display.get(), config));
encode_run(
frame_nr, key_frame_nr,
shutdown_event,
packets, idr_events, images,
frame_nr,
mail, images,
config, display->width, display->height,
hwdevice.get(),
ref->reinit_event, *ref->encoder_p,
@@ -1290,60 +1359,73 @@ void capture_async(
}
void capture(
safe::signal_t *shutdown_event,
packet_queue_t packets,
idr_event_t idr_events,
safe::mail_t mail,
config_t config,
void *channel_data) {
idr_events->raise(std::make_pair(0, 1));
auto idr_events = mail->event<bool>(mail::idr);
idr_events->raise(true);
if(encoders.front().flags & SYSTEM_MEMORY) {
capture_async(shutdown_event, packets, idr_events, config, channel_data);
capture_async(std::move(mail), config, channel_data);
}
else {
safe::signal_t join_event;
auto ref = capture_thread_sync.ref();
ref->encode_session_ctx_queue.raise(sync_session_ctx_t {
shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data });
&join_event,
mail->event<bool>(mail::shutdown),
mail::man->queue<packet_t>(mail::video_packets),
std::move(idr_events),
mail->event<input::touch_port_t>(mail::touch_port),
config,
1,
channel_data,
});
// Wait for join signal
join_event.view();
}
}
enum validate_flag_e {
VUI_PARAMS = 0x01,
NALU_PREFIX_5b = 0x02,
};
int validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &encoder, const config_t &config) {
reset_display(disp, encoder.dev_type);
if(!disp) {
return 0;
return -1;
}
auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt);
auto hwdevice = disp->make_hwdevice(pix_fmt);
if(!hwdevice) {
return 0;
return -1;
}
auto session = make_session(encoder, config, disp->width, disp->height, hwdevice.get());
if(!session) {
return 0;
return -1;
}
auto img = disp->alloc_img();
if(disp->dummy_img(img.get())) {
return 0;
return -1;
}
if(session->device->convert(*img)) {
return 0;
return -1;
}
auto frame = session->device->frame;
frame->pict_type = AV_PICTURE_TYPE_I;
auto packets = std::make_shared<packet_queue_t::element_type>(30);
auto packets = mail::man->queue<packet_t>(mail::video_packets);
while(!packets->peek()) {
if(encode(1, *session, frame, packets, nullptr)) {
return false;
return -1;
}
}
@@ -1351,14 +1433,21 @@ int validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &en
if(!(packet->flags & AV_PKT_FLAG_KEY)) {
BOOST_LOG(error) << "First packet type is not an IDR frame"sv;
return 0;
return -1;
}
int flag = 0;
if(cbs::validate_sps(&*packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) {
return 1;
flag |= VUI_PARAMS;
}
return -1;
auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu;
std::string_view payload { (char *)packet->data, (std::size_t)packet->size };
if(std::search(std::begin(payload), std::end(payload), std::begin(nalu_prefix), std::end(nalu_prefix)) != std::end(payload)) {
flag |= NALU_PREFIX_5b;
}
return flag;
}
bool validate_encoder(encoder_t &encoder) {
@@ -1384,16 +1473,21 @@ bool validate_encoder(encoder_t &encoder) {
auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames);
auto autoselect_h264 = validate_config(disp, encoder, config_autoselect);
if(!max_ref_frames_h264 && !autoselect_h264) {
if(max_ref_frames_h264 < 0 && autoselect_h264 < 0) {
return false;
}
if(max_ref_frames_h264 < 0 || autoselect_h264 < 0) {
encoder.h264[encoder_t::VUI_PARAMETERS] = false;
std::vector<std::pair<validate_flag_e, encoder_t::flag_e>> packet_deficiencies {
{ VUI_PARAMS, encoder_t::VUI_PARAMETERS },
{ NALU_PREFIX_5b, encoder_t::NALU_PREFIX_5b },
};
for(auto [validate_flag, encoder_flag] : packet_deficiencies) {
encoder.h264[encoder_flag] = (max_ref_frames_h264 & validate_flag && autoselect_h264 & validate_flag);
}
encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264;
encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264;
encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264 >= 0;
encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264 >= 0;
encoder.h264[encoder_t::PASSED] = true;
encoder.h264[encoder_t::SLICE] = validate_config(disp, encoder, config_max_ref_frames);
@@ -1405,18 +1499,18 @@ bool validate_encoder(encoder_t &encoder) {
auto autoselect_hevc = validate_config(disp, encoder, config_autoselect);
// If HEVC must be supported, but it is not supported
if(force_hevc && !max_ref_frames_hevc && !autoselect_hevc) {
if(force_hevc && max_ref_frames_hevc < 0 && autoselect_hevc < 0) {
return false;
}
if(max_ref_frames_h264 < 0 || autoselect_h264 < 0) {
encoder.hevc[encoder_t::VUI_PARAMETERS] = false;
for(auto [validate_flag, encoder_flag] : packet_deficiencies) {
encoder.hevc[encoder_flag] = (max_ref_frames_hevc & validate_flag && autoselect_hevc & validate_flag);
}
encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc;
encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc;
encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc >= 0;
encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc >= 0;
encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc || autoselect_hevc;
encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc >= 0 || autoselect_hevc >= 0;
}
std::vector<std::pair<encoder_t::flag_e, config_t>> configs {
@@ -1430,12 +1524,15 @@ bool validate_encoder(encoder_t &encoder) {
h264.videoFormat = 0;
hevc.videoFormat = 1;
encoder.h264[flag] = validate_config(disp, encoder, h264);
encoder.h264[flag] = validate_config(disp, encoder, h264) >= 0;
if(encoder.hevc[encoder_t::PASSED]) {
encoder.hevc[flag] = validate_config(disp, encoder, hevc);
encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0;
}
}
encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
encoder.hevc[encoder_t::VUI_PARAMETERS] = encoder.hevc[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
if(!encoder.h264[encoder_t::VUI_PARAMETERS]) {
BOOST_LOG(warning) << encoder.name << ": h264 missing sps->vui parameters"sv;
}
@@ -1443,14 +1540,18 @@ bool validate_encoder(encoder_t &encoder) {
BOOST_LOG(warning) << encoder.name << ": hevc missing sps->vui parameters"sv;
}
if(!encoder.h264[encoder_t::NALU_PREFIX_5b]) {
BOOST_LOG(warning) << encoder.name << ": h264: replacing nalu prefix data"sv;
}
if(encoder.hevc[encoder_t::PASSED] && !encoder.hevc[encoder_t::NALU_PREFIX_5b]) {
BOOST_LOG(warning) << encoder.name << ": hevc: replacing nalu prefix data"sv;
}
fg.disable();
return true;
}
int init() {
// video depends on input for input::touch_port_event
input::init();
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv;
BOOST_LOG(info) << "// //"sv;
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. //"sv;
@@ -1566,6 +1667,19 @@ util::Either<buffer_t, int> vaapi_make_hwdevice_ctx(platf::hwdevice_t *base) {
return hw_device_buf;
}
util::Either<buffer_t, int> cuda_make_hwdevice_ctx(platf::hwdevice_t *base) {
buffer_t hw_device_buf;
auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 0);
if(status < 0) {
char string[AV_ERROR_MAX_STRING_SIZE];
BOOST_LOG(error) << "Failed to create a CUDA device: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status);
return -1;
}
return hw_device_buf;
}
#ifdef _WIN32
}
@@ -1633,7 +1747,9 @@ platf::mem_type_e map_dev_type(AVHWDeviceType type) {
return platf::mem_type_e::dxgi;
case AV_HWDEVICE_TYPE_VAAPI:
return platf::mem_type_e::vaapi;
case AV_PICTURE_TYPE_NONE:
case AV_HWDEVICE_TYPE_CUDA:
return platf::mem_type_e::cuda;
case AV_HWDEVICE_TYPE_NONE:
return platf::mem_type_e::system;
default:
return platf::mem_type_e::unknown;
@@ -1685,4 +1801,4 @@ color_t colors[] {
make_color_matrix(0.2126f, 0.0722f, 0.436f, 0.615f, 0.0625, 0.5f, { 16.0f, 235.0f }, { 16.0f, 240.0f }), // BT701 MPEG
make_color_matrix(0.2126f, 0.0722f, 0.5f, 0.5f, 0.0f, 0.5f, { 0.0f, 255.0f }, { 0.0f, 255.0f }), // BT701 JPEG
};
}
} // namespace video

View File

@@ -42,18 +42,21 @@ struct packet_raw_t : public AVPacket {
av_packet_unref(this);
}
struct {
struct replace_t {
std::string_view old;
std::string_view replacement;
} sps;
std::string_view _new;
KITTY_DEFAULT_CONSTR(replace_t)
replace_t(std::string_view old, std::string_view _new) noexcept : old { std::move(old) }, _new { std::move(_new) } {}
};
std::vector<replace_t> *replacements;
void *channel_data;
};
using packet_t = std::unique_ptr<packet_raw_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<packet_t>>;
using idr_event_t = std::shared_ptr<safe::event_t<std::pair<int64_t, int64_t>>>;
using img_event_t = std::shared_ptr<safe::event_t<std::shared_ptr<platf::img_t>>>;
using packet_t = std::unique_ptr<packet_raw_t>;
struct config_t {
int width;
@@ -82,9 +85,7 @@ struct __attribute__((__aligned__(16))) color_t {
extern color_t colors[4];
void capture(
safe::signal_t *shutdown_event,
packet_queue_t packets,
idr_event_t idr_events,
safe::mail_t mail,
config_t config,
void *channel_data);

View File

@@ -18,7 +18,7 @@ include/cbs/h2645_parse.h
include/cbs/h264.h
include/cbs/hevc.h
include/cbs/sei.h
include/cbs/h264_levels.h
include/cbs/video_levels.h
cbs.c
cbs_h2645.c
@@ -28,7 +28,7 @@ cbs_mpeg2.c
cbs_jpeg.c
cbs_sei.c
h2645_parse.c
h264_levels.c
video_levels.c
bytestream.h
cbs_internal.h

View File

@@ -1,121 +0,0 @@
/*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <libavcodec/avcodec.h>
#include "include/cbs/h264_levels.h"
// H.264 table A-1.
static const H264LevelDescriptor h264_levels[] = {
// Name MaxMBPS MaxBR MinCR
// | level_idc | MaxFS | MaxCPB | MaxMvsPer2Mb
// | | cs3f | | MaxDpbMbs | | MaxVmvR | |
{ "1", 10, 0, 1485, 99, 396, 64, 175, 64, 2, 0 },
{ "1b", 11, 1, 1485, 99, 396, 128, 350, 64, 2, 0 },
{ "1b", 9, 0, 1485, 99, 396, 128, 350, 64, 2, 0 },
{ "1.1", 11, 0, 3000, 396, 900, 192, 500, 128, 2, 0 },
{ "1.2", 12, 0, 6000, 396, 2376, 384, 1000, 128, 2, 0 },
{ "1.3", 13, 0, 11880, 396, 2376, 768, 2000, 128, 2, 0 },
{ "2", 20, 0, 11880, 396, 2376, 2000, 2000, 128, 2, 0 },
{ "2.1", 21, 0, 19800, 792, 4752, 4000, 4000, 256, 2, 0 },
{ "2.2", 22, 0, 20250, 1620, 8100, 4000, 4000, 256, 2, 0 },
{ "3", 30, 0, 40500, 1620, 8100, 10000, 10000, 256, 2, 32 },
{ "3.1", 31, 0, 108000, 3600, 18000, 14000, 14000, 512, 4, 16 },
{ "3.2", 32, 0, 216000, 5120, 20480, 20000, 20000, 512, 4, 16 },
{ "4", 40, 0, 245760, 8192, 32768, 20000, 25000, 512, 4, 16 },
{ "4.1", 41, 0, 245760, 8192, 32768, 50000, 62500, 512, 2, 16 },
{ "4.2", 42, 0, 522240, 8704, 34816, 50000, 62500, 512, 2, 16 },
{ "5", 50, 0, 589824, 22080, 110400, 135000, 135000, 512, 2, 16 },
{ "5.1", 51, 0, 983040, 36864, 184320, 240000, 240000, 512, 2, 16 },
{ "5.2", 52, 0, 2073600, 36864, 184320, 240000, 240000, 512, 2, 16 },
{ "6", 60, 0, 4177920, 139264, 696320, 240000, 240000, 8192, 2, 16 },
{ "6.1", 61, 0, 8355840, 139264, 696320, 480000, 480000, 8192, 2, 16 },
{ "6.2", 62, 0, 16711680, 139264, 696320, 800000, 800000, 8192, 2, 16 },
};
// H.264 table A-2 plus values from A-1.
static const struct {
int profile_idc;
int cpb_br_vcl_factor;
int cpb_br_nal_factor;
} h264_br_factors[] = {
{ 66, 1000, 1200 },
{ 77, 1000, 1200 },
{ 88, 1000, 1200 },
{ 100, 1250, 1500 },
{ 110, 3000, 3600 },
{ 122, 4000, 4800 },
{ 244, 4000, 4800 },
{ 44, 4000, 4800 },
};
// We are only ever interested in the NAL bitrate factor.
static int h264_get_br_factor(int profile_idc) {
int i;
for(i = 0; i < FF_ARRAY_ELEMS(h264_br_factors); i++) {
if(h264_br_factors[i].profile_idc == profile_idc)
return h264_br_factors[i].cpb_br_nal_factor;
}
// Default to the non-high profile value if not specified.
return 1200;
}
const H264LevelDescriptor *ff_h264_guess_level(int profile_idc,
int64_t bitrate,
int framerate,
int width, int height,
int max_dec_frame_buffering) {
int width_mbs = (width + 15) / 16;
int height_mbs = (height + 15) / 16;
int no_cs3f = !(profile_idc == 66 ||
profile_idc == 77 ||
profile_idc == 88);
int i;
for(i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) {
const H264LevelDescriptor *level = &h264_levels[i];
if(level->constraint_set3_flag && no_cs3f)
continue;
if(bitrate > (int64_t)level->max_br * h264_get_br_factor(profile_idc))
continue;
if(width_mbs * height_mbs > level->max_fs)
continue;
if(width_mbs * width_mbs > 8 * level->max_fs)
continue;
if(height_mbs * height_mbs > 8 * level->max_fs)
continue;
if(width_mbs && height_mbs) {
int max_dpb_frames =
FFMIN(level->max_dpb_mbs / (width_mbs * height_mbs), 16);
if(max_dec_frame_buffering > max_dpb_frames)
continue;
if(framerate > (level->max_mbps / (width_mbs * height_mbs)))
continue;
}
return level;
}
// No usable levels found - frame is too big or bitrate is too high.
return NULL;
}

View File

@@ -22,6 +22,53 @@
#include <stdint.h>
#include "cbs_h265.h"
typedef struct H265LevelDescriptor {
const char *name;
uint8_t level_idc;
// Table A.6.
uint32_t max_luma_ps;
uint32_t max_cpb_main;
uint32_t max_cpb_high;
uint16_t max_slice_segments_per_picture;
uint8_t max_tile_rows;
uint8_t max_tile_cols;
// Table A.7.
uint32_t max_luma_sr;
uint32_t max_br_main;
uint32_t max_br_high;
uint8_t min_cr_base_main;
uint8_t min_cr_base_high;
} H265LevelDescriptor;
typedef struct H265ProfileDescriptor {
const char *name;
uint8_t profile_idc;
uint8_t high_throughput;
// Tables A.2, A.3 and A.5.
uint8_t max_14bit;
uint8_t max_12bit;
uint8_t max_10bit;
uint8_t max_8bit;
uint8_t max_422chroma;
uint8_t max_420chroma;
uint8_t max_monochrome;
uint8_t intra;
uint8_t one_picture_only;
uint8_t lower_bit_rate;
// Table A.8.
uint16_t cpb_vcl_factor;
uint16_t cpb_nal_factor;
float format_capability_factor;
float min_cr_scale_factor;
uint8_t max_dpb_pic_buf;
} H265ProfileDescriptor;
typedef struct H264LevelDescriptor {
const char *name;
uint8_t level_idc;
@@ -36,6 +83,20 @@ typedef struct H264LevelDescriptor {
uint8_t max_mvs_per_2mb;
} H264LevelDescriptor;
const H265ProfileDescriptor *ff_h265_get_profile(const H265RawProfileTierLevel *ptl);
/**
* Guess the level of a stream from some parameters.
*
* Unknown parameters may be zero, in which case they are ignored.
*/
const H265LevelDescriptor *ff_h265_guess_level(const H265RawProfileTierLevel *ptl,
int64_t bitrate,
int width, int height,
int slice_segments,
int tile_rows, int tile_cols,
int max_dec_pic_buffering);
/**
* Guess the level of a stream from some parameters.
*

349
third-party/cbs/video_levels.c vendored Normal file
View File

@@ -0,0 +1,349 @@
/*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <libavcodec/avcodec.h>
#include "include/cbs/video_levels.h"
// H.264 table A-1.
static const H264LevelDescriptor h264_levels[] = {
// Name MaxMBPS MaxBR MinCR
// | level_idc | MaxFS | MaxCPB | MaxMvsPer2Mb
// | | cs3f | | MaxDpbMbs | | MaxVmvR | |
{ "1", 10, 0, 1485, 99, 396, 64, 175, 64, 2, 0 },
{ "1b", 11, 1, 1485, 99, 396, 128, 350, 64, 2, 0 },
{ "1b", 9, 0, 1485, 99, 396, 128, 350, 64, 2, 0 },
{ "1.1", 11, 0, 3000, 396, 900, 192, 500, 128, 2, 0 },
{ "1.2", 12, 0, 6000, 396, 2376, 384, 1000, 128, 2, 0 },
{ "1.3", 13, 0, 11880, 396, 2376, 768, 2000, 128, 2, 0 },
{ "2", 20, 0, 11880, 396, 2376, 2000, 2000, 128, 2, 0 },
{ "2.1", 21, 0, 19800, 792, 4752, 4000, 4000, 256, 2, 0 },
{ "2.2", 22, 0, 20250, 1620, 8100, 4000, 4000, 256, 2, 0 },
{ "3", 30, 0, 40500, 1620, 8100, 10000, 10000, 256, 2, 32 },
{ "3.1", 31, 0, 108000, 3600, 18000, 14000, 14000, 512, 4, 16 },
{ "3.2", 32, 0, 216000, 5120, 20480, 20000, 20000, 512, 4, 16 },
{ "4", 40, 0, 245760, 8192, 32768, 20000, 25000, 512, 4, 16 },
{ "4.1", 41, 0, 245760, 8192, 32768, 50000, 62500, 512, 2, 16 },
{ "4.2", 42, 0, 522240, 8704, 34816, 50000, 62500, 512, 2, 16 },
{ "5", 50, 0, 589824, 22080, 110400, 135000, 135000, 512, 2, 16 },
{ "5.1", 51, 0, 983040, 36864, 184320, 240000, 240000, 512, 2, 16 },
{ "5.2", 52, 0, 2073600, 36864, 184320, 240000, 240000, 512, 2, 16 },
{ "6", 60, 0, 4177920, 139264, 696320, 240000, 240000, 8192, 2, 16 },
{ "6.1", 61, 0, 8355840, 139264, 696320, 480000, 480000, 8192, 2, 16 },
{ "6.2", 62, 0, 16711680, 139264, 696320, 800000, 800000, 8192, 2, 16 },
};
// H.264 table A-2 plus values from A-1.
static const struct {
int profile_idc;
int cpb_br_vcl_factor;
int cpb_br_nal_factor;
} h264_br_factors[] = {
{ 66, 1000, 1200 },
{ 77, 1000, 1200 },
{ 88, 1000, 1200 },
{ 100, 1250, 1500 },
{ 110, 3000, 3600 },
{ 122, 4000, 4800 },
{ 244, 4000, 4800 },
{ 44, 4000, 4800 },
};
// We are only ever interested in the NAL bitrate factor.
static int h264_get_br_factor(int profile_idc) {
int i;
for(i = 0; i < FF_ARRAY_ELEMS(h264_br_factors); i++) {
if(h264_br_factors[i].profile_idc == profile_idc)
return h264_br_factors[i].cpb_br_nal_factor;
}
// Default to the non-high profile value if not specified.
return 1200;
}
const H264LevelDescriptor *ff_h264_guess_level(int profile_idc,
int64_t bitrate,
int framerate,
int width, int height,
int max_dec_frame_buffering) {
int width_mbs = (width + 15) / 16;
int height_mbs = (height + 15) / 16;
int no_cs3f = !(profile_idc == 66 ||
profile_idc == 77 ||
profile_idc == 88);
int i;
for(i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) {
const H264LevelDescriptor *level = &h264_levels[i];
if(level->constraint_set3_flag && no_cs3f)
continue;
if(bitrate > (int64_t)level->max_br * h264_get_br_factor(profile_idc))
continue;
if(width_mbs * height_mbs > level->max_fs)
continue;
if(width_mbs * width_mbs > 8 * level->max_fs)
continue;
if(height_mbs * height_mbs > 8 * level->max_fs)
continue;
if(width_mbs && height_mbs) {
int max_dpb_frames =
FFMIN(level->max_dpb_mbs / (width_mbs * height_mbs), 16);
if(max_dec_frame_buffering > max_dpb_frames)
continue;
if(framerate > (level->max_mbps / (width_mbs * height_mbs)))
continue;
}
return level;
}
// No usable levels found - frame is too big or bitrate is too high.
return NULL;
}
static const H265LevelDescriptor h265_levels[] = {
// Name CpbFactor-Main MaxSliceSegmentsPerPicture
// | level_idc | CpbFactor-High MaxLumaSr BrFactor-High
// | | MaxLumaPs | | | MaxTileRows | BrFactor-Main | MinCr-Main
// | | | | | | | MaxTileCols | | | MinCr-High
{ "1", 30, 36864, 350, 0, 16, 1, 1, 552960, 128, 0, 2, 2 },
{ "2", 60, 122880, 1500, 0, 16, 1, 1, 3686400, 1500, 0, 2, 2 },
{ "2.1", 63, 245760, 3000, 0, 20, 1, 1, 7372800, 3000, 0, 2, 2 },
{ "3", 90, 552960, 6000, 0, 30, 2, 2, 16588800, 6000, 0, 2, 2 },
{ "3.1", 93, 983040, 10000, 0, 40, 3, 3, 33177600, 10000, 0, 2, 2 },
{ "4", 120, 2228224, 12000, 30000, 75, 5, 5, 66846720, 12000, 30000, 4, 4 },
{ "4.1", 123, 2228224, 20000, 50000, 75, 5, 5, 133693440, 20000, 50000, 4, 4 },
{ "5", 150, 8912896, 25000, 100000, 200, 11, 10, 267386880, 25000, 100000, 6, 4 },
{ "5.1", 153, 8912896, 40000, 160000, 200, 11, 10, 534773760, 40000, 160000, 8, 4 },
{ "5.2", 156, 8912896, 60000, 240000, 200, 11, 10, 1069547520, 60000, 240000, 8, 4 },
{ "6", 180, 35651584, 60000, 240000, 600, 22, 20, 1069547520, 60000, 240000, 8, 4 },
{ "6.1", 183, 35651584, 120000, 480000, 600, 22, 20, 2139095040, 120000, 480000, 8, 4 },
{ "6.2", 186, 35651584, 240000, 800000, 600, 22, 20, 4278190080, 240000, 800000, 6, 4 },
};
static const H265ProfileDescriptor h265_profiles[] = {
// profile_idc 8bit one-picture
// HT-profile | 422chroma | lower-bit-rate
// | 14bit | | 420chroma | | CpbVclFactor MinCrScaleFactor
// | | 12bit | | | monochrome| | CpbNalFactor | maxDpbPicBuf
// | | | 10bit | | | intra | | | FormatCapabilityFactor
{ "Monochrome", // | | | | | | | | | | |
4, 0, 2, 1, 1, 1, 1, 1, 1, 0, 0, 1, 667, 733, 1.000, 1.0, 6 },
{ "Monochrome 10",
4, 0, 2, 1, 1, 0, 1, 1, 1, 0, 0, 1, 833, 917, 1.250, 1.0, 6 },
{ "Monochrome 12",
4, 0, 2, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1000, 1100, 1.500, 1.0, 6 },
{ "Monochrome 16",
4, 0, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1333, 1467, 2.000, 1.0, 6 },
{ "Main",
1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1000, 1100, 1.500, 1.0, 6 },
{ "Screen-Extended Main",
9, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1000, 1100, 1.500, 1.0, 7 },
{ "Main 10",
2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 1000, 1100, 1.875, 1.0, 6 },
{ "Screen-Extended Main 10",
9, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1000, 1100, 1.875, 1.0, 7 },
{ "Main 12",
4, 0, 2, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1500, 1650, 2.250, 1.0, 6 },
{ "Main Still Picture",
3, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1000, 1100, 1.500, 1.0, 6 },
{ "Main 10 Still Picture",
2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1000, 1100, 1.875, 1.0, 6 },
{ "Main 4:2:2 10",
4, 0, 2, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1667, 1833, 2.500, 0.5, 6 },
{ "Main 4:2:2 12",
4, 0, 2, 1, 0, 0, 1, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 },
{ "Main 4:4:4",
4, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 },
{ "High Throughput 4:4:4",
5, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 },
{ "Screen-Extended Main 4:4:4",
9, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 7 },
{ "Screen-Extended High Throughput 4:4:4",
9, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 7 },
{ "Main 4:4:4 10",
4, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 6 },
{ "High Throughput 4:4:4 10",
5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 6 },
{ "Screen-Extended Main 4:4:4 10",
9, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 7 },
{ "Screen-Extended High Throughput 4:4:4 10",
9, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 7 },
{ "Main 4:4:4 12",
4, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 3300, 4.500, 0.5, 6 },
{ "High Throughput 4:4:4 14",
5, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3500, 3850, 5.250, 0.5, 6 },
{ "Screen-Extended High Throughput 4:4:4 14",
9, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3500, 3850, 5.250, 0.5, 7 },
{ "Main Intra",
4, 0, 2, 1, 1, 1, 1, 1, 0, 1, 0, 2, 1000, 1100, 1.500, 1.0, 6 },
{ "Main 10 Intra",
4, 0, 2, 1, 1, 0, 1, 1, 0, 1, 0, 2, 1000, 1100, 1.875, 1.0, 6 },
{ "Main 12 Intra",
4, 0, 2, 1, 0, 0, 1, 1, 0, 1, 0, 2, 1500, 1650, 2.250, 1.0, 6 },
{ "Main 4:2:2 10 Intra",
4, 0, 2, 1, 1, 0, 1, 0, 0, 1, 0, 2, 1667, 1833, 2.500, 0.5, 6 },
{ "Main 4:2:2 12 Intra",
4, 0, 2, 1, 0, 0, 1, 0, 0, 1, 0, 2, 2000, 2200, 3.000, 0.5, 6 },
{ "Main 4:4:4 Intra",
4, 0, 2, 1, 1, 1, 0, 0, 0, 1, 0, 2, 2000, 2200, 3.000, 0.5, 6 },
{ "Main 4:4:4 10 Intra",
4, 0, 2, 1, 1, 0, 0, 0, 0, 1, 0, 2, 2500, 2750, 3.750, 0.5, 6 },
{ "Main 4:4:4 12 Intra",
4, 0, 2, 1, 0, 0, 0, 0, 0, 1, 0, 2, 3000, 3300, 4.500, 0.5, 6 },
{ "Main 4:4:4 16 Intra",
4, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4000, 4400, 6.000, 0.5, 6 },
{ "Main 4:4:4 Still Picture",
4, 0, 2, 1, 1, 1, 0, 0, 0, 1, 1, 2, 2000, 2200, 3.000, 0.5, 6 },
{ "Main 4:4:4 16 Still Picture",
4, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, 2, 4000, 4400, 6.000, 0.5, 6 },
{ "High Throughput 4:4:4 16 Intra",
5, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4000, 4400, 6.000, 0.5, 6 },
};
const H265ProfileDescriptor *ff_h265_get_profile(const H265RawProfileTierLevel *ptl) {
int i;
if(ptl->general_profile_space)
return NULL;
for(i = 0; i < FF_ARRAY_ELEMS(h265_profiles); i++) {
const H265ProfileDescriptor *profile = &h265_profiles[i];
if(ptl->general_profile_idc &&
ptl->general_profile_idc != profile->profile_idc)
continue;
if(!ptl->general_profile_compatibility_flag[profile->profile_idc])
continue;
#define check_flag(name) \
if(profile->name < 2) { \
if(profile->name != ptl->general_##name##_constraint_flag) \
continue; \
}
check_flag(max_14bit);
check_flag(max_12bit);
check_flag(max_10bit);
check_flag(max_8bit);
check_flag(max_422chroma);
check_flag(max_420chroma);
check_flag(max_monochrome);
check_flag(intra);
check_flag(one_picture_only);
check_flag(lower_bit_rate);
#undef check_flag
return profile;
}
return NULL;
}
const H265LevelDescriptor *ff_h265_guess_level(const H265RawProfileTierLevel *ptl,
int64_t bitrate,
int width, int height,
int slice_segments,
int tile_rows, int tile_cols,
int max_dec_pic_buffering) {
const H265ProfileDescriptor *profile;
int pic_size, tier_flag, lbr_flag, hbr_factor;
int i;
if(ptl)
profile = ff_h265_get_profile(ptl);
else
profile = NULL;
if(!profile) {
// Default to using multiplication factors for Main profile.
profile = &h265_profiles[4];
}
pic_size = width * height;
if(ptl) {
tier_flag = ptl->general_tier_flag;
lbr_flag = ptl->general_lower_bit_rate_constraint_flag;
}
else {
tier_flag = 0;
lbr_flag = profile->lower_bit_rate > 0;
}
if(profile->profile_idc == 1 || profile->profile_idc == 2) {
hbr_factor = 1;
}
else if(profile->high_throughput) {
if(profile->intra)
hbr_factor = 24 - 12 * lbr_flag;
else
hbr_factor = 6;
}
else {
hbr_factor = 2 - lbr_flag;
}
for(i = 0; i < FF_ARRAY_ELEMS(h265_levels); i++) {
const H265LevelDescriptor *level = &h265_levels[i];
int max_br, max_dpb_size;
if(tier_flag && !level->max_br_high)
continue;
if(pic_size > level->max_luma_ps)
continue;
if(width * width > 8 * level->max_luma_ps)
continue;
if(height * height > 8 * level->max_luma_ps)
continue;
if(slice_segments > level->max_slice_segments_per_picture)
continue;
if(tile_rows > level->max_tile_rows)
continue;
if(tile_cols > level->max_tile_cols)
continue;
if(tier_flag)
max_br = level->max_br_high;
else
max_br = level->max_br_main;
if(!max_br)
continue;
if(bitrate > (int64_t)profile->cpb_nal_factor * hbr_factor * max_br)
continue;
if(pic_size <= (level->max_luma_ps >> 2))
max_dpb_size = FFMIN(4 * profile->max_dpb_pic_buf, 16);
else if(pic_size <= (level->max_luma_ps >> 1))
max_dpb_size = FFMIN(2 * profile->max_dpb_pic_buf, 16);
else if(pic_size <= (3 * level->max_luma_ps >> 2))
max_dpb_size = FFMIN(4 * profile->max_dpb_pic_buf / 3, 16);
else
max_dpb_size = profile->max_dpb_pic_buf;
if(max_dec_pic_buffering > max_dpb_size)
continue;
return level;
}
return NULL;
}

1
third-party/miniupnp vendored Submodule

Submodule third-party/miniupnp added at 6f848ae082