mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ff5942258 | ||
|
|
8c803e6a34 | ||
|
|
a93a640d42 | ||
|
|
62abd1ee19 | ||
|
|
d55c6ee84c | ||
|
|
d3419697a5 | ||
|
|
a7171d77db | ||
|
|
fccb8d080d | ||
|
|
24acfabe07 | ||
|
|
61be0eb051 | ||
|
|
c4b4393177 | ||
|
|
388e4696ff | ||
|
|
43dc7cf7c0 | ||
|
|
e8feb00b33 | ||
|
|
42472bec85 | ||
|
|
769c4c8f99 | ||
|
|
049044f768 | ||
|
|
971c784f14 | ||
|
|
747ba7f23a | ||
|
|
4a3f3622b8 | ||
|
|
7d2705424a | ||
|
|
b228fd371c | ||
|
|
cbd2a8269c | ||
|
|
7f9467d759 | ||
|
|
9cf7c14a26 | ||
|
|
f09aea81fe | ||
|
|
8249d41848 | ||
|
|
d33f742241 | ||
|
|
6d0499b0e3 | ||
|
|
fd7a6d070b | ||
|
|
3d81b0fe7a | ||
|
|
1d37a19468 | ||
|
|
cc3cf60015 | ||
|
|
8d4e55fcbb | ||
|
|
25aa8b41a5 | ||
|
|
667878d2eb | ||
|
|
53a9f365ce | ||
|
|
a587338701 | ||
|
|
a52f547726 | ||
|
|
136e9c43a5 | ||
|
|
616f62fb9f | ||
|
|
9d10eaeef4 | ||
|
|
f23fcee382 | ||
|
|
21b1a4a336 | ||
|
|
b346ac2eb0 | ||
|
|
db5a9363ba | ||
|
|
0a637b2272 | ||
|
|
169a53b568 | ||
|
|
109b48a108 | ||
|
|
118707f37a | ||
|
|
e169259f6f | ||
|
|
355df9a615 | ||
|
|
96cfb1f368 | ||
|
|
545cca792b | ||
|
|
ae04c4afbb | ||
|
|
e8fadd2848 | ||
|
|
beb6bdfadb | ||
|
|
8bf4ade9d8 | ||
|
|
ea928c53b4 | ||
|
|
c1697c8562 | ||
|
|
57c4986f0e | ||
|
|
0cc7e35ed9 | ||
|
|
d6eceaf0dc | ||
|
|
cf7eb14573 | ||
|
|
ed5de34800 | ||
|
|
27d4f6063f | ||
|
|
62662edc8d | ||
|
|
b67600962a | ||
|
|
1eda45a81a | ||
|
|
926e95f527 | ||
|
|
029194cb60 | ||
|
|
7e3abefc2c | ||
|
|
cf9eb961fc | ||
|
|
0a05c28df8 | ||
|
|
0034438c9e | ||
|
|
2691489dab | ||
|
|
9e7ecf8db2 | ||
|
|
32e6054435 | ||
|
|
bd2d846557 | ||
|
|
0932d8bab9 | ||
|
|
82ac3becd8 | ||
|
|
7b86ea9e87 | ||
|
|
63d15333f2 | ||
|
|
3ec4bf52e1 | ||
|
|
ec44a4391a |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
19
gen-deb.in
19
gen-deb.in
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
121
sunshine/cbs.cpp
121
sunshine/cbs.cpp
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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([]() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
14
sunshine/platform/linux/misc.h
Normal file
14
sunshine/platform/linux/misc.h
Normal 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
|
||||
429
sunshine/platform/linux/publish.cpp
Normal file
429
sunshine/platform/linux/publish.cpp
Normal 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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
13
sunshine/platform/windows/misc.h
Normal file
13
sunshine/platform/windows/misc.h
Normal 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
|
||||
180
sunshine/platform/windows/publish.cpp
Normal file
180
sunshine/platform/windows/publish.cpp
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
184
sunshine/upnp.cpp
Normal 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
10
sunshine/upnp.h
Normal 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
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
2
third-party/Simple-Web-Server
vendored
2
third-party/Simple-Web-Server
vendored
Submodule third-party/Simple-Web-Server updated: f37a41d48b...3ae451038f
4
third-party/cbs/CMakeLists.txt
vendored
4
third-party/cbs/CMakeLists.txt
vendored
@@ -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
|
||||
|
||||
121
third-party/cbs/h264_levels.c
vendored
121
third-party/cbs/h264_levels.c
vendored
@@ -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;
|
||||
}
|
||||
@@ -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
349
third-party/cbs/video_levels.c
vendored
Normal 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
1
third-party/miniupnp
vendored
Submodule
Submodule third-party/miniupnp added at 6f848ae082
2
third-party/moonlight-common-c
vendored
2
third-party/moonlight-common-c
vendored
Submodule third-party/moonlight-common-c updated: a13eeddacf...3b9d8a3176
Reference in New Issue
Block a user