Merge with master

This commit is contained in:
loki 2021-06-26 12:40:06 +02:00
commit 62662edc8d
140 changed files with 37900 additions and 3073 deletions

67
.clang-format Normal file
View file

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: true
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 2
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Never
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 2
Cpp11BracedListStyle: false
UseTab: Never

8
.gitignore vendored
View file

@ -1,8 +1,10 @@
build build
cmake-build-* cmake-build*
.DS_Store .DS_Store
.vscode
.vs
*.swp *.swp
*.kdev4 *.kdev4
.idea .cache
.idea

9
.gitmodules vendored
View file

@ -1,12 +1,9 @@
[submodule "moonlight-common-c"] [submodule "moonlight-common-c"]
path = moonlight-common-c path = third-party/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git url = https://github.com/moonlight-stream/moonlight-common-c.git
[submodule "Simple-Web-Server"] [submodule "Simple-Web-Server"]
path = Simple-Web-Server path = third-party/Simple-Web-Server
url = https://github.com/loki-47-6F-64/Simple-Web-Server.git url = https://github.com/loki-47-6F-64/Simple-Web-Server.git
[submodule "ViGEmClient"] [submodule "ViGEmClient"]
path = ViGEmClient path = third-party/ViGEmClient
url = https://github.com/ViGEm/ViGEmClient url = https://github.com/ViGEm/ViGEmClient
[submodule "pre-compiled"]
path = pre-compiled
url = https://bitbucket.org/Loki-47-6F-64/pre-compiled.git

View file

@ -1,101 +1,128 @@
cmake_minimum_required(VERSION 2.8) cmake_minimum_required(VERSION 3.0)
project(Sunshine) project(Sunshine)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# On MSYS2, building a stand-alone binary that links with ffmpeg is not possible, add_subdirectory(third-party/Simple-Web-Server)
# Therefore, ffmpeg, libx264 and libx265 must be build from source
if(WIN32) if(WIN32)
option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF) # Ugly hack to compile with #include <qos2.h>
if(SUNSHINE_STANDALONE) add_compile_definitions(
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") QOS_FLOWID=UINT32
PQOS_FLOWID=UINT32*
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) QOS_NON_ADAPTIVE_FLOW=2)
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows")
endif()
list(PREPEND PLATFORM_LIBRARIES
C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a
C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a
)
set(FFMPEG_INCLUDE_DIRS
${SUNSHINE_PREPARED_BINARIES}/include)
set(FFMPEG_LIBRARIES
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a)
endif()
else()
set(SUNSHINE_STANDALONE OFF)
endif() endif()
add_subdirectory(third-party/moonlight-common-c/enet)
add_subdirectory(Simple-Web-Server)
add_subdirectory(moonlight-common-c/enet)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
if(NOT SUNSHINE_STANDALONE)
find_package(FFmpeg REQUIRED)
endif()
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare) list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
if(WIN32) if(WIN32)
file(
DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
TIMEOUT 60
EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6)
file(ARCHIVE_EXTRACT
INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows")
endif()
add_compile_definitions(SUNSHINE_PLATFORM="windows")
add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json")
include_directories(
ViGEmClient/include) include_directories(third-party/ViGEmClient/include)
set(PLATFORM_TARGET_FILES set(PLATFORM_TARGET_FILES
sunshine/platform/windows/misc.cpp
sunshine/platform/windows/input.cpp sunshine/platform/windows/input.cpp
sunshine/platform/windows/display.h sunshine/platform/windows/display.h
sunshine/platform/windows/display_base.cpp sunshine/platform/windows/display_base.cpp
sunshine/platform/windows/display_vram.cpp sunshine/platform/windows/display_vram.cpp
sunshine/platform/windows/display_ram.cpp sunshine/platform/windows/display_ram.cpp
sunshine/platform/windows/audio.cpp sunshine/platform/windows/audio.cpp
ViGEmClient/src/ViGEmClient.cpp third-party/ViGEmClient/src/ViGEmClient.cpp
ViGEmClient/include/ViGEm/Client.h third-party/ViGEmClient/include/ViGEm/Client.h
ViGEmClient/include/ViGEm/Common.h third-party/ViGEmClient/include/ViGEm/Common.h
ViGEmClient/include/ViGEm/Util.h third-party/ViGEmClient/include/ViGEm/Util.h
ViGEmClient/include/ViGEm/km/BusShared.h) third-party/ViGEmClient/include/ViGEm/km/BusShared.h)
set(OPENSSL_LIBRARIES
libssl.a
libcrypto.a)
set(FFMPEG_INCLUDE_DIRS
${SUNSHINE_PREPARED_BINARIES}/include)
set(FFMPEG_LIBRARIES
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a
z lzma bcrypt libiconv.a)
list(PREPEND PLATFORM_LIBRARIES list(PREPEND PLATFORM_LIBRARIES
libstdc++.a
libwinpthread.a
libssp.a
Qwave
winmm winmm
ksuser ksuser
wsock32 wsock32
ws2_32 ws2_32
iphlpapi iphlpapi
d3d11 dxgi d3d11 dxgi D3DCompiler
setupapi setupapi
) )
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650")
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess")
else() else()
add_compile_definitions(SUNSHINE_PLATFORM="linux")
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json")
find_package(X11 REQUIRED) find_package(X11 REQUIRED)
pkg_check_modules(AVAHI REQUIRED avahi-client) pkg_check_modules(AVAHI REQUIRED avahi-client)
find_package(FFmpeg REQUIRED)
set(PLATFORM_TARGET_FILES set(PLATFORM_TARGET_FILES
sunshine/platform/linux/vaapi.h
sunshine/platform/linux/vaapi.cpp
sunshine/platform/linux/misc.cpp
sunshine/platform/linux/display.cpp sunshine/platform/linux/display.cpp
sunshine/platform/linux/input.cpp) sunshine/platform/linux/audio.cpp
sunshine/platform/linux/input.cpp
third-party/glad/src/egl.c
third-party/glad/src/gl.c
third-party/glad/include/EGL/eglplatform.h
third-party/glad/include/KHR/khrplatform.h
third-party/glad/include/glad/gl.h
third-party/glad/include/glad/egl.h)
set(PLATFORM_LIBRARIES set(PLATFORM_LIBRARIES
Xfixes Xfixes
Xtst Xtst
xcb xcb
xcb-shm xcb-shm
xcb-xfixes xcb-xfixes
Xrandr
${X11_LIBRARIES} ${X11_LIBRARIES}
dl
evdev evdev
pulse pulse
pulse-simple pulse-simple
@ -105,7 +132,8 @@ else()
set(PLATFORM_INCLUDE_DIRS set(PLATFORM_INCLUDE_DIRS
${X11_INCLUDE_DIR} ${X11_INCLUDE_DIR}
${AVAHI_INCLUDE_DIRS} ${AVAHI_INCLUDE_DIRS}
/usr/include/libevdev-1.0) /usr/include/libevdev-1.0
third-party/glad/include)
if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH) if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH)
set(SUNSHINE_EXECUTABLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/sunshine") set(SUNSHINE_EXECUTABLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/sunshine")
@ -114,16 +142,19 @@ else()
configure_file(sunshine.service.in sunshine.service @ONLY) configure_file(sunshine.service.in sunshine.service @ONLY)
endif() endif()
add_subdirectory(third-party/cbs)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
find_package(Boost COMPONENTS log filesystem REQUIRED) find_package(Boost COMPONENTS log filesystem REQUIRED)
set(SUNSHINE_TARGET_FILES set(SUNSHINE_TARGET_FILES
moonlight-common-c/reedsolomon/rs.c third-party/moonlight-common-c/reedsolomon/rs.c
moonlight-common-c/reedsolomon/rs.h third-party/moonlight-common-c/reedsolomon/rs.h
moonlight-common-c/src/Input.h third-party/moonlight-common-c/src/Input.h
moonlight-common-c/src/Rtsp.h third-party/moonlight-common-c/src/Rtsp.h
moonlight-common-c/src/RtspParser.c third-party/moonlight-common-c/src/RtspParser.c
moonlight-common-c/src/Video.h third-party/moonlight-common-c/src/Video.h
sunshine/cbs.cpp
sunshine/utility.h sunshine/utility.h
sunshine/uuid.h sunshine/uuid.h
sunshine/config.h sunshine/config.h
@ -134,6 +165,10 @@ set(SUNSHINE_TARGET_FILES
sunshine/crypto.h sunshine/crypto.h
sunshine/nvhttp.cpp sunshine/nvhttp.cpp
sunshine/nvhttp.h sunshine/nvhttp.h
sunshine/httpcommon.cpp
sunshine/httpcommon.h
sunshine/confighttp.cpp
sunshine/confighttp.h
sunshine/rtsp.cpp sunshine/rtsp.cpp
sunshine/rtsp.h sunshine/rtsp.h
sunshine/stream.cpp sunshine/stream.cpp
@ -161,9 +196,10 @@ set(SUNSHINE_TARGET_FILES
include_directories( include_directories(
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/Simple-Web-Server ${CMAKE_CURRENT_SOURCE_DIR}/third-party
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/enet/include ${CMAKE_CURRENT_SOURCE_DIR}/third-party/cbs/include
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/reedsolomon ${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/reedsolomon
${FFMPEG_INCLUDE_DIRS} ${FFMPEG_INCLUDE_DIRS}
${PLATFORM_INCLUDE_DIRS} ${PLATFORM_INCLUDE_DIRS}
) )
@ -183,13 +219,11 @@ if(NOT SUNSHINE_ASSETS_DIR)
set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
endif() endif()
if(SUNSHINE_STANDALONE) list(APPEND CBS_EXTERNAL_LIBRARIES
set(OPENSSL_LIBRARIES cbs)
C:/msys64/mingw64/lib/libssl.a
C:/msys64/mingw64/lib/libcrypto.a)
endif()
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CBS_EXTERNAL_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
stdc++fs stdc++fs
enet enet

View file

@ -1,6 +1,9 @@
# Introduction # Introduction
Sunshine is a Gamestream host for Moonlight Sunshine is a Gamestream host for Moonlight
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master)
[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases)
- [Building](README.md#building) - [Building](README.md#building)
- [Credits](README.md#credits) - [Credits](README.md#credits)
@ -12,48 +15,60 @@ Sunshine is a Gamestream host for Moonlight
### Requirements: ### Requirements:
Ubuntu 20.04: Ubuntu 20.04:
Install the following
sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libavahi-client-dev ```
sudo apt install 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 libavahi-client-dev
```
### Compilation: ### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` - `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
- `cd sunshine && mkdir build && cd build` - `cd sunshine && mkdir build && cd build`
- `cmake ..` - `cmake ..`
- `make`: It is suggested to use the `-j C#` flags with this command, `C#` being the number of cores your PC has - `make -j ${nproc}`
### Setup: ### Setup:
sunshine needs access to uinput to create mouse and gamepad events: sunshine needs access to uinput to create mouse and gamepad events:
- Add user to group 'input': "usermod -a -G input username - Add user to group 'input':
- Create a file: "/etc/udev/rules.d/85-sunshine-input.rules" `usermod -a -G input $USER`
- The contents of the file is as follows: - Create udev rules:
KERNEL=="uinput", GROUP="input", mode="0660" - Run the following command:
- assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf" `nano /etc/udev/rules.d/85-sunshine-input.rules`
- path/to/build/dir/sunshine.service is used to start sunshine in the background: - Input the following contents:
- `cp sunshine.service $HOME/.config/systemd/user/` `KERNEL=="uinput", GROUP="input", MODE="0660"`
- Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable - Save the file and exit
- `systemctl --user start sunshine` 1. `CTRL+X` to start exit
2. `Y` to save modifications
- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running:
`sunshine path/to/sunshine.conf`
- Configure autostart service
`path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following:
1. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/`
2. Starting
- Onetime:
`systemctl --user start sunshine`
- Always on boot:
`systemctl --user enable sunshine`
- assets/apps.json is an [example](README.md#application-list) of a list of applications that are started just before running a stream - `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream
### Trouleshooting: ### Trouleshooting:
* If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": - If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
* groups - `groups $USER`
* If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
* pacmd list-sources | grep "name:" - If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
* Copy the name to the configuration option "audio_sink" 1. `$ pacmd list-sources | grep "name:"` or `$ pactl info | grep Source` if running pipewire.
* restart sunshine 2. Copy the name to the configuration option "audio_sink"
3. restart sunshine
## Windows 10 ## Windows 10
### Requirements: ### Requirements:
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost 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
### Compilation: ### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` - `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive`
- `cd sunshine && mkdir build && cd build` - `cd sunshine && mkdir build && cd build`
- `cmake -G"Unix Makefiles" ..` - `cmake -G"Unix Makefiles" ..`
- `make` - `make`
@ -61,28 +76,20 @@ sunshine needs access to uinput to create mouse and gamepad events:
### Setup: ### Setup:
- **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases] - **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]
### Static build
#### Requirements:
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost git-lfs
#### Compilation:
- `git lfs install`
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
- `cd sunshine && mkdir build && cd build`
- `cmake -DSUNSHINE_STANDALONE=ON -G"Unix Makefiles" ..`
- `make`
# Common # Common
## Usage: ## Usage:
- run "sunshine path/to/sunshine.conf" - run "sunshine path/to/sunshine.conf"
- If running for the first time, make sure to note the username and password Sunshine showed to you, since you **cannot get back later**!
- In Moonlight: Add PC manually - In Moonlight: Add PC manually
- When Moonlight request you insert the correct pin on sunshine, either: - When Moonlight request you insert the correct pin on sunshine:
- Type in the URL bar of your browser: `xxx.xxx.xxx.xxx:47989/pin/####` - Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer
- `wget xxx.xxx.xxx.xxx:47989/pin/####` - Ignore any warning given by your browser about "insecure website"
- The x's are the IP of your instance, `####` is the pin - Type in the username and password shown the first time you run Sunshine
- Go to "PIN" in the Header
- Type in your PIN and press Enter, you should get a Success Message
- Click on one of the Applications listed - Click on one of the Applications listed
- Have fun :) - Have fun :)
@ -101,8 +108,10 @@ sunshine needs access to uinput to create mouse and gamepad events:
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server) - [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
- [Moonlight](https://github.com/moonlight-stream) - [Moonlight](https://github.com/moonlight-stream)
- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :) - [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :)
- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically)
## Application List: ## Application List:
**Note:** You can change the Application List in the "Apps" section of the User Interface `https://xxx.xxx.xxx.xxx:47990/`
- You can use Environment variables in place of values - You can use Environment variables in place of values
- $(HOME) will be replaced by the value of $HOME - $(HOME) will be replaced by the value of $HOME
- $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME) - $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME)
@ -116,15 +125,20 @@ sunshine needs access to uinput to create mouse and gamepad events:
"cmd":"command to open app", "cmd":"command to open app",
"prep-cmd":[ "prep-cmd":[
{ {
"do":"somecommand", "do":"some-command",
"undo":"undothatcommand" "undo":"undo-that-command"
} }
],
"detached":[
"some-command",
"another-command"
] ]
} }
``` ```
- name: Self explanatory - name: Self explanatory
- output <optional>: The file where the output of the command is stored - output <optional>: The file where the output of the command is stored
- If it is not specified, the output is ignored - If it is not specified, the output is ignored
- detached: A list of commands to be run and forgotten about
- prep-cmd: A list of commands to be run before/after the application - prep-cmd: A list of commands to be run before/after the application
- If any of the prep-commands fail, starting the application is aborted - If any of the prep-commands fail, starting the application is aborted
- do: Run before the application - do: Run before the application

View file

@ -1,5 +1,5 @@
image: image:
- Ubuntu - Ubuntu2004
- Visual Studio 2019 - Visual Studio 2019
environment: environment:
@ -8,8 +8,8 @@ environment:
- BUILD_TYPE: Release - BUILD_TYPE: Release
install: install:
- sh: sudo apt update - sh: sudo apt update --ignore-missing
- sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libavahi-client-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 libavahi-client-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" - 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: before_build:
@ -20,8 +20,8 @@ before_build:
build_script: build_script:
- cmd: set OLDPATH=%PATH% - cmd: set OLDPATH=%PATH%
- cmd: set PATH=C:\msys64\mingw64\bin - cmd: set PATH=C:\msys64\mingw64\bin
- sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine .. - sh: cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
- sh: make -j$(nproc) - sh: make -j$(nproc)
- cmd: mingw32-make -j2 - cmd: mingw32-make -j2
- cmd: set PATH=%OLDPATH% - cmd: set PATH=%OLDPATH%

View file

@ -13,7 +13,7 @@
"name":"Steam BigPicture", "name":"Steam BigPicture",
"output":"steam.txt", "output":"steam.txt",
"cmd":"steam -bigpicture" "detached":["setsid steam steam://open/bigpicture"]
} }
] ]
} }

View file

@ -7,9 +7,7 @@
"name":"Steam BigPicture", "name":"Steam BigPicture",
"output":"steam.txt", "output":"steam.txt",
"prep-cmd":[ "detached":["steam steam://open/bigpicture"]
{"do":"steam \"steam://open/bigpicture\""}
]
} }
] ]
} }

View file

@ -0,0 +1,33 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
struct FragTexWide {
float3 uuv : TEXCOORD0;
};
cbuffer ColorMatrix : register(b0) {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float2 main_ps(FragTexWide input) : SV_Target
{
float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb;
float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb;
float3 rgb = (rgb_left + rgb_right) * 0.5;
float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w;
u = u * range_uv.x + range_uv.y;
v = v * range_uv.x + range_uv.y;
return float2(u, v * 224.0f/256.0f + 0.0625);
}

View file

@ -0,0 +1,29 @@
struct VertTexPosWide {
float3 uuv : TEXCOORD;
float4 pos : SV_POSITION;
};
cbuffer info : register(b0) {
float width_i;
};
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VertTexPosWide main_vs(uint vI : SV_VERTEXID)
{
float idHigh = float(vI >> 1);
float idLow = float(vI & uint(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u_right = idHigh * 2.0;
float u_left = u_right - width_i;
float v = 1.0 - idLow * 2.0;
VertTexPosWide vert_out;
vert_out.uuv = float3(u_left, u_right, v);
vert_out.pos = float4(x, y, 0.0, 1.0);
return vert_out;
}

View file

@ -0,0 +1,25 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
cbuffer ColorMatrix : register(b0) {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
float main_ps(PS_INPUT frag_in) : SV_Target
{
float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb;
float y = dot(color_vec_y.xyz, rgb);
return y * range_y.x + range_y.y;
}

View file

@ -0,0 +1,14 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
float4 main_ps(PS_INPUT frag_in) : SV_Target
{
return image.Sample(def_sampler, frag_in.tex, 0);
}

View file

@ -0,0 +1,22 @@
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
PS_INPUT main_vs(uint vI : SV_VERTEXID)
{
float idHigh = float(vI >> 1);
float idLow = float(vI & uint(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u = idHigh * 2.0;
float v = 1.0 - idLow * 2.0;
PS_INPUT vert_out;
vert_out.pos = float4(x, y, 0.0, 1.0);
vert_out.tex = float2(u, v);
return vert_out;
}

View file

@ -0,0 +1,35 @@
#version 300 es
#ifdef GL_ES
precision lowp float;
#endif
uniform sampler2D image;
layout(shared) uniform ColorMatrix {
vec4 color_vec_y;
vec4 color_vec_u;
vec4 color_vec_v;
vec2 range_y;
vec2 range_uv;
};
in vec3 uuv;
layout(location = 0) out vec2 color;
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
void main() {
vec3 rgb_left = texture(image, uuv.xz).rgb;
vec3 rgb_right = texture(image, uuv.yz).rgb;
vec3 rgb = (rgb_left + rgb_right) * 0.5;
float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w;
u = u * range_uv.x + range_uv.y;
v = v * range_uv.x + range_uv.y;
color = vec2(u, v * 224.0f / 256.0f + 0.0625);
}

View file

@ -0,0 +1,27 @@
#version 300 es
#ifdef GL_ES
precision mediump float;
#endif
uniform float width_i;
out vec3 uuv;
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
void main()
{
float idHigh = float(gl_VertexID >> 1);
float idLow = float(gl_VertexID & int(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u_right = idHigh * 2.0;
float u_left = u_right - width_i;
float v = idLow * 2.0;
uuv = vec3(u_left, u_right, v);
gl_Position = vec4(x, y, 0.0, 1.0);
}

View file

@ -0,0 +1,26 @@
#version 300 es
#ifdef GL_ES
precision lowp float;
#endif
uniform sampler2D image;
layout(shared) uniform ColorMatrix {
vec4 color_vec_y;
vec4 color_vec_u;
vec4 color_vec_v;
vec2 range_y;
vec2 range_uv;
};
in vec2 tex;
layout(location = 0) out float color;
void main()
{
vec3 rgb = texture(image, tex).rgb;
float y = dot(color_vec_y.xyz, rgb);
color = y * range_y.x + range_y.y;
}

View file

@ -0,0 +1,14 @@
#version 300 es
#ifdef GL_ES
precision lowp float;
#endif
uniform sampler2D image;
in vec2 tex;
layout(location = 0) out vec4 color;
void main()
{
color = texture(image, tex);
}

View file

@ -0,0 +1,22 @@
#version 300 es
#ifdef GL_ES
precision mediump float;
#endif
out vec2 tex;
void main()
{
float idHigh = float(gl_VertexID >> 1);
float idLow = float(gl_VertexID & int(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u = idHigh * 2.0;
float v = idLow * 2.0;
gl_Position = vec4(x, y, 0.0, 1.0);
tex = vec2(u, v);
}

View file

@ -37,6 +37,30 @@
# The file where current state of Sunshine is stored # The file where current state of Sunshine is stored
# file_state = sunshine_state.json # 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 # How long to wait in milliseconds for data from moonlight before shutting down the stream
# ping_timeout = 2000 # ping_timeout = 2000
@ -79,12 +103,17 @@
# #
# You can find the name of the audio sink using the following command: # You can find the name of the audio sink using the following command:
# !! Linux only !! # !! Linux only !!
# pacmd list-sources | grep "name:" # pacmd list-sinks | grep "name:" if running vanilla pulseaudio
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor # pactl info | grep Source` if running pipewire
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
# #
# !! Windows only !! # !! Windows only !!
# tools\audio-info.exe # tools\audio-info.exe
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} # 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 !! # !! Windows only !!
# You can select the video card you want to stream: # You can select the video card you want to stream:
@ -93,6 +122,13 @@
# adapter_name = Radeon RX 580 Series # adapter_name = Radeon RX 580 Series
# output_name = \\.\DISPLAY1 # 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 # FFmpeg software encoding parameters
@ -121,11 +157,12 @@
# If set to 1, Sunshine will not advertise support for HEVC # 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 2, Sunshine will advertise support for HEVC Main profile
# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles # If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# hevc_mode = 0 # hevc_mode = 2
# Force a specific encoder, otherwise Sunshine will use the first encoder that is available # Force a specific encoder, otherwise Sunshine will use the first encoder that is available
# supported encoders: # supported encoders:
# nvenc # nvenc
# amdvce # NOTE: alpha stage. The cursor is not yet displayed
# software # software
# #
# encoder = nvenc # encoder = nvenc
@ -170,6 +207,42 @@
########################## ##########################
# nv_coder = auto # 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 # Some configurable parameters, are merely toggles for specific features
@ -177,6 +250,6 @@
# Here, you change the default state of any flag # 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: # To set the initial state of flags -0 and -1 to on, set the following flags:
# flags = 01 # flags = 012
# #
# See: sunshine --help for all options under the header: flags # See: sunshine --help for all options under the header: flags

162
assets/web/apps.html Normal file
View file

@ -0,0 +1,162 @@
<div id="app" class="container">
<div class="my-4">
<h1>Applications</h1>
<div>Applications are refreshed only when Client is restarted</div>
</div>
<div class="card p-4">
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(app,i) in apps" :key="i">
<td>{{app.name}}</td>
<td><button class="btn btn-primary" @click="editApp(i)">Edit</button>
<button class="btn btn-danger" @click="showDeleteForm(i)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="edit-form card mt-2" v-if="showEditForm">
<div class="p-4">
<!--name-->
<div class="mb-3">
<label for="appName" class="form-label">Application Name</label>
<input type="text" class="form-control" id="appName" aria-describedby="appNameHelp" v-model="editForm.name">
<div id="appNameHelp" class="form-text">Application Name, as shown on Moonlight</div>
</div>
<!--output-->
<div class="mb-3">
<label for="appOutput" class="form-label">Output</label>
<input type="text" class="form-control monospace" id="appOutput" aria-describedby="appOutputHelp"
v-model="editForm.output">
<div id="appOutputHelp" class="form-text">The file where the output of the command is stored, if it is not
specified, the output is ignored</div>
</div>
<!--prep-cmd-->
<div class="mb-3 d-flex flex-column">
<label for="appName" class="form-label">Command Preparations</label>
<div class="form-text">A list of commands to be run before/after the application. <br> If any of the
prep-commands fail, starting the application is aborted</div>
<table v-if="editForm['prep-cmd'].length > 0">
<thead>
<th class="precmd-head">Do</th>
<th class="precmd-head">Undo</th>
<th style="width: 48px;"></th>
</thead>
<tbody>
<tr v-for="(c,i) in editForm['prep-cmd']">
<td><input type="text" class="form-control monospace" v-model="c.do"></td>
<td><input type="text" class="form-control monospace" v-model="c.undo"></td>
<td><button class="btn btn-danger" @click="editForm['prep-cmd'].splice(i,1)">&times;</button></td>
</tr>
</tbody>
</table>
<button class="mt-2 btn btn-success" style="margin: 0 auto;" @click="addPrepCmd">&plus; Add</button>
</div>
<!--detatched-->
<div class="mb-3">
<label for="appName" class="form-label">Detached Commands</label>
<div v-for="(c,i) in editForm.detached" class="d-flex justify-content-between my-2">
<pre>{{c}}</pre>
<button class="btn btn-danger mx-2" @click="editForm.detached.splice(i,1)">&times;</button>
</div>
<div class="d-flex justify-content-between">
<input type="text" class="form-control monospace" v-model="detachedCmd">
<button class="btn btn-success mx-2" @click="editForm.detached.push(detachedCmd);detachedCmd = '';">+</button>
</div>
<div class="form-text">A list of commands to be run and forgotten about</div>
</div>
<!--command-->
<div class="mb-3">
<label for="appCmd" class="form-label">Command</label>
<input type="text" class="form-control monospace" id="appCmd" aria-describedby="appCmdHelp"
v-model="editForm.cmd">
<div id="appCmdHelp" class="form-text">The main application, if it is not specified, a processs is started that
sleeps indefinitely</div>
</div>
<!--buttons-->
<div class="d-flex">
<button @click="showEditForm = false" class="btn btn-secondary m-2">Cancel</button>
<button class="btn btn-primary m-2" @click="save">Save</button>
</div>
</div>
</div>
<div class="mt-2" v-else>
<button class="btn btn-primary" @click="newApp">+ Add New</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
apps: [],
showEditForm: false,
editForm: null,
detachedCmd: '',
}
},
created() {
fetch("/api/apps").then(r => r.json()).then((r) => {
console.log(r);
this.apps = r.apps;
})
},
methods: {
newApp() {
this.editForm = {
name: '',
output: '',
cmd: [],
index: -1,
"prep-cmd": [],
"detached": []
};
this.editForm.index = -1;
this.showEditForm = true;
},
editApp(id) {
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
this.$set(this.editForm, "index", id);
if (this.editForm["prep-cmd"] === undefined) this.$set(this.editForm, "prep-cmd", []);
if (this.editForm["detached"] === undefined) this.$set(this.editForm, "detached", []);
this.showEditForm = true;
},
showDeleteForm(id) {
let resp = confirm("Are you sure to delete " + this.apps[id].name + "?");
if (resp) {
fetch("/api/apps/" + id, { method: "DELETE" }).then((r) => {
if (r.status == 200) document.location.reload();
});
}
},
addPrepCmd() {
this.editForm['prep-cmd'].push({
do: '',
undo: '',
});
},
save() {
fetch("/api/apps", { method: "POST", body: JSON.stringify(this.editForm) }).then((r) => {
if (r.status == 200) document.location.reload();
});
}
}
})
</script>
<style>
.precmd-head {
width: 200px;
}
.monospace {
font-family: monospace;
}
</style>

3
assets/web/clients.html Normal file
View file

@ -0,0 +1,3 @@
<div id="content" class="container">
<h1>Clients</h1>
</div>

560
assets/web/config.html Normal file
View file

@ -0,0 +1,560 @@
<div id="app" class="container">
<h1 class="my-4">Configuration</h1>
<div class="form" v-if="config">
<!--Header-->
<ul class="nav nav-tabs">
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
@click="currentTab = tab.id">{{tab.name}}</a>
</li>
</ul>
<!--General Tab-->
<div v-if="currentTab === 'general'" class="config-page">
<!--Sunshine Name-->
<div class="mb-3">
<label for="sunshine_name" class="form-label">Sunshine Name</label>
<input type="text" class="form-control" id="sunshine_name" placeholder="Sunshine"
v-model="config.sunshine_name">
<div class="form-text">The name displayed by Moonlight. If not specified, the PC's hostname is used
</div>
</div>
<!--Log Level-->
<div class="mb-3">
<label for="min_log_level" class="form-label">Log Level</label>
<select id="min_log_level" class="form-select" v-model="config.min_log_level">
<option :value="0">Verbose</option>
<option :value="1">Debug</option>
<option :value="2">Info</option>
<option :value="3">Warning</option>
<option :value="4">Error</option>
<option :value="5">Fatal</option>
<option :value="6">None</option>
</select>
<div class="form-text">The minimum log level printed to standard out</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 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>
</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, the local IP address is used</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"
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>
</div>
<!--Advertised FPS and Resolutions-->
<div class="mb-3">
<label for="ping_timeout" class="form-label">Advertised Resolutions and FPS</label>
<div class="resolutions-container">
<label>Resolutions</label>
<div class="resolutions d-flex flex-wrap">
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(r,i) in resolutions"
:key="r">
<span class="px-2">{{r}}</span>
<span style="cursor: pointer;" @click="resolutions.splice(i,1)">&times;</span>
</div>
<form @submit.prevent="resolutions.push(resIn);resIn = '';" class="d-flex align-items-center">
<input type="text" v-model="resIn" required pattern="[0-9]+x[0-9]+"
style="border-top-right-radius: 0;border-bottom-right-radius: 0;" class="form-control">
<button style="border-top-left-radius: 0;border-bottom-left-radius: 0;"
class="btn btn-success">+</button>
</form>
</div>
</div>
<div class="fps-container">
<label>FPS</label>
<div class="fps d-flex flex-wrap">
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(f,i) in fps" :key="f">
<span class="px-2">{{f}}</span>
<span style="cursor: pointer;" @click="fps.splice(i,1)">&times;</span>
</div>
<form @submit.prevent="fps.push(fpsIn);fpsIn = '';" class="d-flex align-items-center">
<input type="text" v-model="fpsIn" required pattern="[0-9]+"
style="width: 6ch;border-top-right-radius: 0;border-bottom-right-radius: 0;"
class="form-control">
<button style="border-top-left-radius: 0;border-bottom-left-radius: 0;"
class="btn btn-success">+</button>
</form>
</div>
</div>
<div class="form-text">
The display modes advertised by Sunshine<br>
Some versions of Moonlight, such as Moonlight-nx (Switch),
rely on this list to ensure that the requested resolutions and fps
are supported.
</div>
</div>
</div>
<!--Files Tab-->
<div v-if="currentTab === 'files'" class="config-page">
<!--Private Key-->
<div class="mb-3">
<label for="pkey" class="form-label">Private Key</label>
<input type="text" class="form-control" id="pkey" placeholder="/dir/pkey.pem" v-model="config.pkey">
<div class="form-text">The private key must be 2048 bits</div>
</div>
<!--Cert-->
<div class="mb-3">
<label for="cert" class="form-label">Cert</label>
<input type="text" class="form-control" id="cert" placeholder="/dir/cert.pem" v-model="config.cert">
<div class="form-text">The certificate must be signed with a 2048 bit key</div>
</div>
<!--State File-->
<div class="mb-3">
<label for="file_state" class="form-label">State File</label>
<input type="text" class="form-control" id="file_state" placeholder="sunshine_state.json"
v-model="config.file_state">
<div class="form-text">The file where current state of Sunshine is stored</div>
</div>
<!--Apps File-->
<div class="mb-3">
<label for="file_apps" class="form-label">Apps File</label>
<input type="text" class="form-control" id="file_apps" placeholder="apps.json"
v-model="config.file_apps">
<div class="form-text">The file where current apps of Sunshine are stored</div>
</div>
</div>
<div v-if="currentTab === 'input'" class="config-page">
<!--Back Button Timeout-->
<div class="mb-3">
<label for="back_button_timeout" class="form-label">Back Button Timeout</label>
<input type="text" class="form-control" id="back_button_timeout" placeholder="2000"
v-model="config.back_button_timeout">
<div class="form-text">
The back/select button on the controller.<br>
On the Shield, the home and powerbutton are not passed to Moonlight.<br>
If, after the timeout, the back button is still pressed down, Home/Guide button press is
emulated.<br>
If back_button_timeout &lt; 0, then the Home/Guide button will not be emulated<br>
</div>
</div>
<!-- Key Repeat Delay-->
<div class="mb-3" v-if="platform === 'windows'">
<label for="key_repeat_delay" class="form-label">Key Repeat Delay</label>
<input type="text" class="form-control" id="key_repeat_delay" placeholder="500"
v-model="config.key_repeat_delay">
<div class="form-text">
Control how fast keys will repeat themselves<br>
The initial delay in milliseconds before repeating keys
</div>
</div>
<!-- Key Repeat Frequency-->
<div class="mb-3" v-if="platform === 'windows'">
<label for="key_repeat_frequency" class="form-label">Key Repeat Frequency</label>
<input type="text" class="form-control" id="key_repeat_frequency" placeholder="24.9"
v-model="config.key_repeat_frequency">
<div class="form-text">
How often keys repeat every second<br>
This configurable option supports decimals
</div>
</div>
</div>
<!--Files Tab-->
<div v-if="currentTab === 'av'" class="config-page">
<!--Audio Sink-->
<div class="mb-3" v-if="platform === 'windows'">
<label for="audio_sink" class="form-label">Audio Sink</label>
<input type="text" class="form-control" id="audio_sink"
placeholder="{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}" v-model="config.audio_sink">
<div class="form-text">
The name of the audio sink used for Audio Loopback<br>
You can find the name of the audio sink using the following command:<br>
<pre>tools\audio-info.exe</pre>
</div>
</div>
<div class="mb-3" v-if="platform === 'linux'">
<label for="audio_sink" class="form-label">Audio Sink</label>
<input type="text" class="form-control" id="audio_sink"
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo" v-model="config.audio_sink">
<div class="form-text">
The name of the audio sink used for Audio Loopback<br>
If you do not specify this variable, pulseaudio will select the default monitor device.<br>
<br>
You can find the name of the audio sink using either command:<br>
<pre>pacmd list-sinks | grep "name:"</pre>
<pre>pactl info | grep Source</pre><br>
</div>
</div>
<!--Virtual Sink-->
<div class="mb-3" v-if="platform === 'windows'">
<label for="virtual_sink" class="form-label">Virtual Sink</label>
<input type="text" class="form-control" id="virtual_sink"
placeholder="{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}" v-model="config.virtual_sink">
<div class="form-text">
The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows
Sunshine
to stream audio, while muting the speakers.
</div>
</div>
<!--Adapter Name -->
<div class="mb-3" v-if="platform === 'windows'">
<label for="adapter_name" class="form-label">Adapter Name</label>
<input type="text" class="form-control" id="adapter_name" placeholder="Radeon RX 580 Series"
v-model="config.adapter_name">
<div class="form-text" v-if="platform === 'windows'">
You can select the video card you want to stream:<br>
The appropriate values can be found using the following command:<br>
<pre>tools\dxgi-info.exe</pre>
</div>
</div>
<!--Output Name -->
<div class="mb-3" class="config-page" v-if="platform === 'windows'">
<label for="output_name" class="form-label">Output Name</label>
<input type="text" class="form-control" id="output_name" placeholder="\\.\DISPLAY1"
v-model="config.output_name">
<div class="form-text">
You can select the video card you want to stream:<br>
The appropriate values can be found using the following command:<br>
tools\dxgi-info.exe<br><br>
</div>
</div>
<div class="mb-3" class="config-page" v-if="platform === 'linux'">
<label for="output_name" class="form-label">Monitor number</label>
<input type="text" class="form-control" id="output_name" placeholder="0" v-model="config.output_name">
<div class="form-text">
xrandr --listmonitors<br>
Example output:
<pre> 0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1</pre>
</div>
</div>
</div>
<div v-if="currentTab === 'advanced'" class="config-page">
<!--Constant Rate Factor-->
<div class="mb-3">
<label for="crf" class="form-label">Constant Rate Factor</label>
<input type="number" min="0" max="52" class="form-control" id="crf" placeholder="0"
v-model="config.crf">
<div class="form-text">
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<br>
Higher value means more compression, but less quality<br>
If crf == 0, then use QP directly instead
</div>
</div>
<!-- Quantization Parameter -->
<div class="mb-3">
<label for="qp" class="form-label">Quantitization Parameter</label>
<input type="number" class="form-control" id="qp" placeholder="28" v-model="config.qp">
<div class="form-text">
Quantitization Parameter<br>
Higher value means more compression, but less quality<br>
If crf != 0, then this parameter is ignored
</div>
</div>
<!-- Min Threads -->
<div class="mb-3">
<label for="min_threads" class="form-label">Minimum number of threads used by ffmpeg to encode the
video.</label>
<input type="number" min="1" class="form-control" id="min_threads" placeholder="1"
v-model="config.min_threads">
<div class="form-text">
Minimum number of threads used by ffmpeg to encode the video.<br>
Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually<br>
worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest<br>
value that can reliably encode at your desired streaming settings on your hardware.
</div>
</div>
<!--HEVC Suppport -->
<div class="mb-3">
<label for="hevc_mode" class="form-label">HEVC Support</label>
<select id="hevc_mode" class="form-select" v-model="config.hevc_mode">
<option value="0">Sunshine will specify support for HEVC based on encoder</option>
<option value="1">Sunshine will not advertise support for HEVC</option>
<option value="2">Sunshine will advertise support for HEVC Main profile</option>
<option value="3">Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
</option>
</select>
<div class="form-text">
Allows the client to request HEVC Main or HEVC Main10 video streams.<br>
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using
software
encoding.
</div>
</div>
<!--Encoder -->
<div class="mb-3">
<label for="encoder" class="form-label">Force a Specific Encoder</label>
<select id="encoder" class="form-select" v-model="config.encoder">
<option :value="''">Autodetect</option>
<option value="nvenc">nVidia NVENC</option>
<option value="amdvce">AMD AMF/VCE</option>
<option value="vaapi">VA-API</option>
<option value="software">Software</option>
</select>
<div class="form-text">
Force a specific encoder, otherwise Sunshine will use the first encoder that is available
</div>
</div>
<!--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"
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
</div>
</div>
<!--Channels-->
<div class="mb-3">
<label for="channels" class="form-label">Channels</label>
<input type="text" class="form-control" id="channels" placeholder="1" v-model="config.channels">
<div class="form-text">
When multicasting, it could be useful to have different configurations for each connected
Client.
For example:
<ul>
<li>Clients connected through WAN and LAN have different bitrate contstraints.</li>
<li>Decoders may require different settings for color</li>
</ul>
Unlike simply broadcasting to multiple Client, this will generate distinct video streams.<br>
Note, CPU usage increases for each distinct video stream generated
</div>
</div>
<!--Credentials File-->
<div class="mb-3">
<label for="credentials_file" class="form-label">Web Manager Credentials File</label>
<input type="text" class="form-control" id="credentials_file" placeholder="sunshine_state.json"
v-model="config.credentials_file">
<div class="form-text">
Store Username/Password seperately from Sunshine's state file.
</div>
</div>
</div>
<!--Software Settings-->
<div v-if="currentTab === 'sw'" class="config-page">
<div class="mb-3">
<label for="sw_preset" class="form-label">SW Presets</label>
<input class="form-control" id="sw_preset" placeholder="superfast" v-model="config.sw_preset">
</div>
<div class="mb-3">
<label for="sw_tune" class="form-label">SW Tune</label>
<input class="form-control" id="sw_tune" placeholder="zerolatency" v-model="config.sw_tune">
</div>
</div>
<!--Nvidia Encoder Settings-->
<div v-if="currentTab === 'nv'" class="config-page">
<!--NVENC SETTINGS-->
<div class="mb-3">
<label for="nv_preset" class="form-label">NVEnc Preset</label>
<select id="nv_preset" class="form-select" v-model="config.nv_preset">
<option value="default">Default</option>
<option value="hp">High Performance</option>
<option value="hq">High Quality</option>
<option value="slow">Slow - hq 2 passes</option>
<option value="medium">medium -- hq 1 pass</option>
<option value="fast">fast -- hp 1 pass</option>
<option value="bd">bd</option>
<option value="ll">ll -- low latency</option>
<option value="llhq">llhq</option>
<option value="llhp">llhp</option>
<option value="lossless">lossless</option>
<option value="losslesshp">losslesshp</option>
</select>
</div>
<div class="mb-3">
<label for="nv_rc" class="form-label">NVEnc Rate Control</label>
<select id="nv_rc" class="form-select" v-model="config.nv_rc">
<option value="auto">auto -- let ffmpeg decide rate control</option>
<option value="constqp">constqp -- constant QP mode</option>
<option value="vbr">vbr -- variable bitrate</option>
<option value="cbr">cbr -- constant bitrate</option>
<option value="cbr_hq">cbr_hq -- cbr high quality</option>
<option value="cbr_ld_hq">cbr_ld_hq -- cbr low delay high quality</option>
<option value="vbr_hq">vbr_hq -- vbr high quality</option>
</select>
</div>
<div class="mb-3">
<label for="nv_coder" class="form-label">NVEnc Coder</label>
<select id="nv_coder" class="form-select" v-model="config.nv_coder">
<option value="auto">auto</option>
<option value="cabac">cabac</option>
<option value="cavlc">cavlc</option>
</select>
</div>
</div>
<!--AMD Encoder Settings-->
<div v-if="currentTab === 'amd'" class="config-page">
<!--Presets-->
<div class="mb-3">
<label for="amd_quality" class="form-label">AMD AMF Quality</label>
<select id="amd_quality" class="form-select" v-model="config.amd_quality">
<option value="default">Default</option>
<option value="speed">Speed</option>
<option value="balanced">Balanced</option>
</select>
</div>
<div class="mb-3">
<label for="amd_rc" class="form-label">AMD AMF Rate Control</label>
<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>
<option value="vbr_peak">vbr_peak -- Peak Contrained Variable Bitrate</option>
<option value="cbr">cbr -- constant bitrate</option>
</select>
</div>
<div class="mb-3">
<label for="amd_coder" class="form-label">AMD AMF Rate Control</label>
<select id="amd_coder" class="form-select" v-model="config.amd_coder">
<option value="auto">auto</option>
<option value="cabac">cabac</option>
<option value="cavlc">cavlc</option>
</select>
</div>
</div>
<div v-if="currentTab === 'va-api'" class="config-page">
<input class="form-control" id="adapter_name" placeholder="/dev/dri/renderD128"
v-model="config.adapter_name">
</div>
</div>
<div class="alert alert-success my-4" v-if="success"><b>Success!</b> Restart Sunshine to apply changes</div>
<div class="mb-3 buttons">
<button class="btn btn-primary" @click="save">Save</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
platform: '',
success: false,
config: null,
fps: [],
resolutions: [],
currentTab: 'general',
resIn: '',
fpsIn: '',
tabs: [{
id: 'general',
name: "General"
},
{
id: 'files',
name: "Files"
},
{
id: 'input',
name: "Input"
},
{
id: 'av',
name: "Audio/Video"
},
{
id: 'advanced',
name: "Advanced"
},
{
id: "sw",
name: "Software Encoder"
},
{
id: "nv",
name: "NVENC Encoder"
},
{
id: "amd",
name: "AMF Encoder"
},
{
id: "va-api",
name: "VA-API encoder"
}
]
}
},
created() {
fetch("/api/config").then(r => r.json()).then((r) => {
this.config = r;
this.platform = this.config.platform;
var app = document.getElementById("app");
if (this.platform == "windows") {
this.tabs = this.tabs.filter(el => {
return el.id !== "va-api";
});
}
if (this.platform == "linux") {
this.tabs = this.tabs.filter(el => {
return el.id !== "nv" && el.id !== "amd";
});
}
delete this.config.status;
delete this.config.platform;
//Populate default values if not present in config
this.config.min_log_level = this.config.min_log_level || 2;
this.config.origin_pin_allowed = this.config.origin_pin_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';
this.config.nv_rc = this.config.nv_rc || 'auto';
this.config.nv_coder = this.config.nv_coder || 'auto';
this.config.amd_quality = this.config.amd_quality || 'default';
this.config.amd_rc = this.config.amd_rc || 'auto';
this.config.fps = this.config.fps || '[10, 30, 60, 90, 120]';
this.config.resolutions = this.config.resolutions || '[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3860x2160,3840x1600]';
this.fps = JSON.parse(this.config.fps);
//Resolutions should be fixed because are not valid JSON
let res = this.config.resolutions.substring(1, this.config.resolutions.length - 1);
let resolutions = [];
res.split(",").forEach(r => resolutions.push(r.trim()));
this.resolutions = resolutions;
})
},
methods: {
save() {
this.success = false;
let nl = this.config === 'windows' ? "\r\n" : "\n";
this.config.resolutions = "[" + nl + " " + this.resolutions.join("," + nl + " ") + nl + "]";
this.config.fps = JSON.stringify(this.fps);
fetch("/api/config", {
method: "POST",
body: JSON.stringify(this.config)
}).then((r) => {
if (r.status == 200) this.success = true;
});
}
}
})
</script>
<style>
.config-page {
padding: 1em;
border: 1px solid #dee2e6;
border-top: none;
}
.buttons {
padding: 1em 0;
}
.ms-item {
background-color: #CCC;
font-size: 12px;
font-weight: bold;
}
</style>

45
assets/web/header.html Normal file
View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sunshine</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400;">
<div class="container-fluid">
<span class="navbar-brand">Sunshine</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pin">PIN</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/apps">Applications</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/password">Change Password</a>
</li>
</ul>
</div>
</nav>

9
assets/web/index.html Normal file
View file

@ -0,0 +1,9 @@
<div id="content" class="container">
<div class="row">
<div class="col-md-6 py-4" style="margin: 0 auto;">
<h1>Hello, Sunshine!</h1>
<p>Sunshine is a Gamestream host for Moonlight</p>
<a href="https://github.com/loki-47-6F-64/sunshine">Official GitHub Repository</a>
</div>
</div>
</div>

97
assets/web/password.html Normal file
View file

@ -0,0 +1,97 @@
<div id="app" class="container">
<h1 class="my-4">Password Change</h1>
<form @submit.prevent="save">
<div class="card d-flex p-4 flex-row">
<div class="col-md-6 px-4">
<h4>Current Credentials</h4>
<div class="mb-3">
<label for="currentUsername" class="form-label">Username</label>
<input required type="text" class="form-control" id="currentUsername" v-model="passwordData.currentUsername">
<div class="form-text">&nbsp;</div>
</div>
<div class="mb-3">
<label for="currentPassword" class="form-label">Password</label>
<input autocomplete="current-password" type="password" class="form-control" id="currentPassword" v-model="passwordData.currentPassword">
</div>
</div>
<div class="col-md-6 px-4">
<h4>New Credentials</h4>
<div class="mb-3">
<label for="newUsername" class="form-label">New Username</label>
<input type="text" class="form-control" id="newUsername" v-model="passwordData.newUsername">
<div class="form-text">If not specified, the username will not change
</div>
</div>
<div class="mb-3">
<label for="newPassword" class="form-label">Password</label>
<input autocomplete="new-password" required type="password" class="form-control" id="newPassword" v-model="passwordData.newPassword">
</div>
<div class="mb-3">
<label for="confirmNewPassword" class="form-label">Confirm Password</label>
<input autocomplete="new-password" required type="password" class="form-control" id="confirmNewPassword" v-model="passwordData.confirmNewPassword">
</div>
</div>
</div>
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
<div class="alert alert-success" v-if="success"><b>Success! </b>This page will reload soon, your browser will ask you for the new credentials</div>
<div class="mb-3 buttons">
<button class="btn btn-primary">Save</button>
</div>
</form>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
error: null,
success: false,
passwordData: {
currentUsername: '',
currentPassword: '',
newUsername: '',
newPassword: '',
confirmNewPassword: ''
}
}
},
methods: {
save() {
this.error = null;
fetch("/api/password", {
method: "POST",
body: JSON.stringify(this.passwordData)
}).then((r) => {
if (r.status == 200){
r.json().then((rj) => {
if(rj.status.toString() === "true"){
this.success = true;
setTimeout(()=>{
document.location.reload();
},5000);
} else {
this.error = rj.error;
}
})
}
else {
this.error = "Internal Server Error"
}
});
}
}
})
</script>
<style>
.config-page {
padding: 1em;
border: 1px solid #dee2e6;
border-top: none;
}
.buttons {
padding: 1em 0;
}
</style>

31
assets/web/pin.html Normal file
View file

@ -0,0 +1,31 @@
<div id="content" class="container">
<h1 class="my-4">PIN Pairing</h1>
<form action="" class="form d-flex flex-column align-items-center" id="form">
<div class="card flex-column d-flex p-4 mb-4">
<input type="number" placeholder="PIN" id="pin-input" class="form-control my-4">
<button class="btn btn-primary">Send</button>
</div>
<div class="alert alert-warning">
<b>Warning!</b> Make sure you have access to the client you are pairing with.<br>
This software can give total control to your computer, so be careful!
</div>
<div id="status"></div>
</form>
</div>
<script>
document.querySelector("#form").addEventListener("submit", (e) => {
e.preventDefault();
let pin = document.querySelector("#pin-input").value;
document.querySelector("#status").innerHTML = "";
let b = JSON.stringify({pin: pin});
fetch("/api/pin",{method: "POST",body: b}).then((response) => response.json()).then((response)=>{
if(response.status){
document.querySelector("#status").innerHTML = `<div class="alert alert-success" role="alert">Success! Please check Moonlight to continue</div>`;
} else {
document.querySelector("#status").innerHTML = `<div class="alert alert-danger" role="alert">PIN does not match, please check if it's typed correctly</div>`;
}
})
})
</script>

View file

@ -35,8 +35,8 @@ Package: sunshine
Architecture: amd64 Architecture: amd64
Maintainer: @loki Maintainer: @loki
Priority: optional Priority: optional
Version: 0.2.1 Version: 0.7.7
Depends: libssl1.1, libavdevice58, libboost-thread1.71.0, libboost-filesystem1.71.0, libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0 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
Description: Gamestream host for Moonlight Description: Gamestream host for Moonlight
EOF EOF
@ -54,19 +54,36 @@ if [ -f /etc/group ]; then
else else
echo "Warning: /etc/group not found" echo "Warning: /etc/group not found"
fi fi
# Update permissions on config files for Web Manager
if [ -f /etc/sunshine/apps_linux.json ]; then
echo "chmod 666 /etc/sunshine/apps_linux.json"
chmod 666 /etc/sunshine/apps_linux.json
fi
if [ -f /etc/sunshine/sunshine.conf ]; then
echo "chmod 666 /etc/sunshine/sunshine.conf"
chmod 666 /etc/sunshine/sunshine.conf
fi
EOF EOF
cat << 'EOF' > $RULES/85-sunshine-rules.rules cat << 'EOF' > $RULES/85-sunshine-rules.rules
KERNEL=="uinput", GROUP="input", MODE="0660" KERNEL=="uinput", GROUP="input", MODE="0660"
EOF EOF
mkdir -p $ASSETS/shaders
cp sunshine $BIN/sunshine cp sunshine $BIN/sunshine
cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json $ASSETS/apps_linux.json 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_SOURCE_DIR@/assets/sunshine.conf $ASSETS/sunshine.conf
cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/web $ASSETS/web
cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/shaders/opengl $ASSETS/shaders/opengl
chmod 755 $DEBIAN/postinst chmod 755 $DEBIAN/postinst
chmod 755 $BIN/sunshine chmod 755 $BIN/sunshine
chmod 644 $RULES/85-sunshine-rules.rules chmod 644 $RULES/85-sunshine-rules.rules
chmod 666 $ASSETS/apps_linux.json
chmod 666 $ASSETS/sunshine.conf
cd package-deb cd package-deb
if fakeroot dpkg-deb --build sunshine; then if fakeroot dpkg-deb --build sunshine; then

@ -1 +0,0 @@
Subproject commit cfeb0ffd90992ee0fa4b6b4a179652e3e2786873

@ -1 +0,0 @@
Subproject commit afd9a9bbfc6ee1a064b0c1f9210bc20b2170c416

View file

@ -2,12 +2,7 @@
Description=Sunshine Gamestream Server for Moonlight Description=Sunshine Gamestream Server for Moonlight
[Service] [Service]
WorkingDirectory=/home/%u
Environment="DISPLAY=:0"
Type=simple
# wait for Xorg
ExecStartPre=/bin/sh -c 'while ! pgrep Xorg; do sleep 2; done'
ExecStart=@SUNSHINE_EXECUTABLE_PATH@ ExecStart=@SUNSHINE_EXECUTABLE_PATH@
[Install] [Install]
WantedBy=default.target WantedBy=graphical-session.target

View file

@ -4,55 +4,80 @@
#include "platform/common.h" #include "platform/common.h"
#include "utility.h"
#include "thread_safe.h"
#include "audio.h" #include "audio.h"
#include "config.h"
#include "main.h" #include "main.h"
#include "thread_safe.h"
#include "utility.h"
namespace audio { namespace audio {
using namespace std::literals; using namespace std::literals;
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>; using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>; using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
struct opus_stream_config_t { struct audio_ctx_t {
std::int32_t sampleRate; // We want to change the sink for the first stream only
int channelCount; std::unique_ptr<std::atomic_bool> sink_flag;
int streams;
int coupledStreams; std::unique_ptr<platf::audio_control_t> control;
const std::uint8_t *mapping;
bool restore_sink;
platf::sink_t sink;
}; };
constexpr std::uint8_t map_stereo[] { 0, 1 }; static int start_audio_control(audio_ctx_t &ctx);
constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3}; static void stop_audio_control(audio_ctx_t &);
constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5};
int map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000; constexpr auto SAMPLE_RATE = 48000;
static opus_stream_config_t stereo = {
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE, SAMPLE_RATE,
2, 2,
1, 1,
1, 1,
map_stereo platf::speaker::map_stereo,
}; },
{
static opus_stream_config_t Surround51 = {
SAMPLE_RATE, SAMPLE_RATE,
6, 6,
4, 4,
2, 2,
map_surround51 platf::speaker::map_surround51,
}; },
{
static opus_stream_config_t HighSurround51 = {
SAMPLE_RATE, SAMPLE_RATE,
6, 6,
6, 6,
0, 0,
map_high_surround51 platf::speaker::map_surround51,
},
{
SAMPLE_RATE,
8,
5,
3,
platf::speaker::map_surround71,
},
{
SAMPLE_RATE,
8,
8,
0,
platf::speaker::map_surround71,
},
}; };
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) { auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
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 //FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stereo; auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
opus_t opus { opus_multistream_encoder_create( opus_t opus { opus_multistream_encoder_create(
stream->sampleRate, stream->sampleRate,
stream->channelCount, stream->channelCount,
@ -60,16 +85,17 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
stream->coupledStreams, stream->coupledStreams,
stream->mapping, stream->mapping,
OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_AUDIO,
nullptr) nullptr) };
};
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
auto frame_size = config.packetDuration * stream->sampleRate / 1000; auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) { while(auto sample = samples->pop()) {
packet_t packet { 16*1024 }; // 16KB buffer_t packet { 1024 }; // 1KB
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size()); int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) { if(bytes < 0) {
BOOST_LOG(error) << opus_strerror(bytes); BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes);
packets->stop(); packets->stop();
return; return;
@ -80,9 +106,53 @@ 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])];
auto ref = control_shared.ref();
if(!ref) {
return;
}
auto &control = ref->control;
if(!control) {
BOOST_LOG(error) << "Couldn't create audio control"sv;
return;
}
std::string *sink =
config::audio.sink.empty() ? &ref->sink.host : &config::audio.sink;
if(ref->sink.null) {
auto &null = *ref->sink.null;
switch(stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the client requests audio on the host, don't change the default sink
if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
auto samples = std::make_shared<sample_queue_t::element_type>(30); 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([&]() { auto fg = util::fail_guard([&]() {
samples->stop(); samples->stop();
@ -91,43 +161,85 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
shutdown_event->view(); shutdown_event->view();
}); });
//FIXME: Pick correct opus_stream_config_t based on config.channels auto frame_size = config.packetDuration * stream->sampleRate / 1000;
auto stream = &stereo; int samples_per_frame = frame_size * stream->channelCount;
auto mic = platf::microphone(stream->sampleRate); auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) { if(!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv ; BOOST_LOG(error) << "Couldn't create audio input"sv;
return; return;
} }
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
while(!shutdown_event->peek()) { while(!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer; std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame); sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer); auto status = mic->sample(sample_buffer);
switch(status) { switch(status) {
case platf::capture_e::ok: case platf::capture_e::ok:
break; break;
case platf::capture_e::timeout: case platf::capture_e::timeout:
continue; continue;
case platf::capture_e::reinit: case platf::capture_e::reinit:
mic.reset(); mic.reset();
mic = platf::microphone(stream->sampleRate); mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) { if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv ; BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return;
}
return;
default:
return; return;
}
return;
default:
return;
} }
samples->raise(std::move(sample_buffer)); samples->raise(std::move(sample_buffer));
} }
} }
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch(channels) {
case 2:
return STEREO;
case 6:
return SURROUND51 + shift;
case 8:
return SURROUND71 + shift;
}
return STEREO;
} }
int start_audio_control(audio_ctx_t &ctx) {
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
if(!(ctx.control = platf::audio_control())) {
return -1;
}
auto sink = ctx.control->sink_info();
if(!sink) {
return -1;
}
// The default sink has not been replaced yet.
ctx.restore_sink = false;
ctx.sink = std::move(*sink);
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {
return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if(!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
} // namespace audio

View file

@ -1,18 +1,45 @@
#ifndef SUNSHINE_AUDIO_H #ifndef SUNSHINE_AUDIO_H
#define SUNSHINE_AUDIO_H #define SUNSHINE_AUDIO_H
#include "utility.h"
#include "thread_safe.h" #include "thread_safe.h"
#include "utility.h"
namespace audio { namespace audio {
enum stream_config_e : int {
STEREO,
SURROUND51,
HIGH_SURROUND51,
SURROUND71,
HIGH_SURROUND71,
MAX_STREAM_CONFIG
};
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
struct config_t { struct config_t {
enum flags_e : int {
HIGH_QUALITY,
HOST_AUDIO,
MAX_FLAGS
};
int packetDuration; int packetDuration;
int channels; int channels;
int mask; int mask;
std::bitset<MAX_FLAGS> flags;
}; };
using packet_t = util::buffer_t<std::uint8_t>; using buffer_t = util::buffer_t<std::uint8_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void*, packet_t>>>; using packet_t = std::pair<void *, buffer_t>;
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);
} } // namespace audio
#endif #endif

300
sunshine/cbs.cpp Normal file
View file

@ -0,0 +1,300 @@
extern "C" {
#include <cbs/cbs_h264.h>
#include <cbs/cbs_h265.h>
#include <cbs/video_levels.h>
#include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h>
}
#include "cbs.h"
#include "main.h"
#include "utility.h"
using namespace std::literals;
namespace cbs {
void close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
class frag_t : public CodedBitstreamFragment {
public:
frag_t(frag_t &&o) {
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
o.data = nullptr;
o.units = nullptr;
};
frag_t() {
std::fill_n((std::uint8_t *)this, sizeof(*this), 0);
}
frag_t &operator=(frag_t &&o) {
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
o.data = nullptr;
o.units = nullptr;
return *this;
};
~frag_t() {
if(data || units) {
ff_cbs_fragment_free(this);
}
}
};
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 insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
util::buffer_t<std::uint8_t> data { frag.data_size };
std::copy_n(frag.data, frag.data_size, std::begin(data));
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 {};
/* b_per_p == ctx->max_b_frames for h264 */
/* desired_b_depth == avoption("b_depth") == 1 */
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
auto max_b_depth = 1;
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
sps.nal_unit_header.nal_ref_idc = 3;
sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
sps.constraint_set1_flag = 1;
if(ctx->level != FF_LEVEL_UNKNOWN) {
sps.level_idc = ctx->level;
}
else {
auto framerate = ctx->framerate;
auto level = ff_h264_guess_level(
sps.profile_idc,
ctx->bit_rate,
framerate.num / framerate.den,
mb_width,
mb_height,
dpb_frame);
if(!level) {
BOOST_LOG(error) << "Could not guess h264 level"sv;
return {};
}
sps.level_idc = level->level_idc;
}
sps.seq_parameter_set_id = 0;
sps.chroma_format_idc = 1;
sps.log2_max_frame_num_minus4 = 3; //4;
sps.pic_order_cnt_type = 0;
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; //4;
sps.max_num_ref_frames = dpb_frame;
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
sps.frame_mbs_only_flag = 1;
sps.direct_8x8_inference_flag = 1;
if(ctx->width != mb_width || ctx->height != mb_height) {
sps.frame_cropping_flag = 1;
sps.frame_crop_left_offset = 0;
sps.frame_crop_top_offset = 0;
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
}
sps.vui_parameters_present_flag = 1;
auto &vui = sps.vui;
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = ctx->color_primaries;
vui.transfer_characteristics = ctx->color_trc;
vui.matrix_coefficients = ctx->colorspace;
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
vui.max_num_reorder_frames = max_b_depth;
vui.max_dec_frame_buffering = max_b_depth + 1;
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264);
}
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
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 {};
}
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 h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps;
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
}
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) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) {
return false;
}
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 false;
}
if(codec_id == AV_CODEC_ID_H264) {
auto h264 = (CodedBitstreamH264Context *)ctx->priv_data;
if(!h264->active_sps->vui_parameters_present_flag) {
return false;
}
return true;
}
return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag;
}
} // namespace cbs

34
sunshine/cbs.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef SUNSHINE_CBS_H
#define SUNSHINE_CBS_H
#include "utility.h"
struct AVPacket;
struct AVCodecContext;
namespace cbs {
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
*/
bool validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs
#endif

View file

@ -1,12 +1,20 @@
#include <algorithm>
#include <filesystem>
#include <fstream> #include <fstream>
#include <iostream>
#include <functional> #include <functional>
#include <iostream>
#include <unordered_map> #include <unordered_map>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "utility.h"
#include "config.h" #include "config.h"
#include "main.h"
#include "utility.h"
#include "platform/common.h"
namespace fs = std::filesystem;
using namespace std::literals;
#define CA_DIR "credentials" #define CA_DIR "credentials"
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
@ -14,7 +22,6 @@
#define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON #define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON
namespace config { namespace config {
using namespace std::literals;
namespace nv { namespace nv {
enum preset_e : int { enum preset_e : int {
@ -33,12 +40,12 @@ enum preset_e : int {
}; };
enum rc_e : int { enum rc_e : int {
constqp = 0x0, /**< Constant QP mode */ constqp = 0x0, /**< Constant QP mode */
vbr = 0x1, /**< Variable bitrate mode */ vbr = 0x1, /**< Variable bitrate mode */
cbr = 0x2, /**< Constant bitrate mode */ cbr = 0x2, /**< Constant bitrate mode */
cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */
cbr_hq = 0x10, /**< CBR, high quality (slower) */ cbr_hq = 0x10, /**< CBR, high quality (slower) */
vbr_hq = 0x20 /**< VBR, high quality (slower) */ vbr_hq = 0x20 /**< VBR, high quality (slower) */
}; };
enum coder_e : int { enum coder_e : int {
@ -48,7 +55,8 @@ enum coder_e : int {
}; };
std::optional<preset_e> preset_from_view(const std::string_view &preset) { std::optional<preset_e> preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) if(preset == #x##sv) return x #define _CONVERT_(x) \
if(preset == #x##sv) return x
_CONVERT_(slow); _CONVERT_(slow);
_CONVERT_(medium); _CONVERT_(medium);
_CONVERT_(fast); _CONVERT_(fast);
@ -65,7 +73,8 @@ std::optional<preset_e> preset_from_view(const std::string_view &preset) {
} }
std::optional<rc_e> rc_from_view(const std::string_view &rc) { std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) if(rc == #x##sv) return x #define _CONVERT_(x) \
if(rc == #x##sv) return x
_CONVERT_(constqp); _CONVERT_(constqp);
_CONVERT_(vbr); _CONVERT_(vbr);
_CONVERT_(cbr); _CONVERT_(cbr);
@ -78,34 +87,90 @@ std::optional<rc_e> rc_from_view(const std::string_view &rc) {
int coder_from_view(const std::string_view &coder) { int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto; if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac; if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1; return -1;
} }
} // namespace nv
namespace amd {
enum quality_e : int {
_default = 0,
speed,
balanced,
//quality2,
};
enum rc_e : int {
constqp, /**< Constant QP mode */
vbr_latency, /**< Latency Constrained Variable Bitrate */
vbr_peak, /**< Peak Contrained Variable Bitrate */
cbr, /**< Constant bitrate mode */
};
enum coder_e : int {
_auto = 0,
cabac,
cavlc
};
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
#define _CONVERT_(x) \
if(quality == #x##sv) return x
_CONVERT_(speed);
_CONVERT_(balanced);
//_CONVERT_(quality2);
if(quality == "default"sv) return _default;
#undef _CONVERT_
return std::nullopt;
} }
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) \
if(rc == #x##sv) return x
_CONVERT_(constqp);
_CONVERT_(vbr_latency);
_CONVERT_(vbr_peak);
_CONVERT_(cbr);
#undef _CONVERT_
return std::nullopt;
}
int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1;
}
} // namespace amd
video_t video { video_t video {
0, // crf 0, // crf
28, // qp 28, // qp
0, // hevc_mode 0, // hevc_mode
1, // min_threads 1, // min_threads
{ {
"superfast"s, // preset "superfast"s, // preset
"zerolatency"s, // tune "zerolatency"s, // tune
}, // software }, // software
{ {
nv::llhq, nv::llhq,
std::nullopt, std::nullopt,
-1 -1 }, // nv
}, // nv
{
amd::balanced,
std::nullopt,
-1 }, // amd
{}, // encoder {}, // encoder
{}, // adapter_name {}, // adapter_name
{} // output_name {}, // output_name
}; };
audio_t audio {}; audio_t audio {};
@ -116,7 +181,7 @@ stream_t stream {
APPS_JSON_PATH, APPS_JSON_PATH,
10, // fecPercentage 10, // fecPercentage
1 // channels 1 // channels
}; };
nvhttp_t nvhttp { nvhttp_t nvhttp {
@ -125,55 +190,136 @@ nvhttp_t nvhttp {
CERTIFICATE_FILE, CERTIFICATE_FILE,
boost::asio::ip::host_name(), // sunshine_name, boost::asio::ip::host_name(), // sunshine_name,
"sunshine_state.json"s // file_state "sunshine_state.json"s, // file_state
{}, // external_ip
{
"352x240"s,
"480x360"s,
"858x480"s,
"1280x720"s,
"1920x1080"s,
"2560x1080"s,
"3440x1440"s
"1920x1200"s,
"3860x2160"s,
"3840x1600"s,
}, // supported resolutions
{ 10, 30, 60, 90, 120 }, // supported fps
}; };
input_t input { input_t input {
2s, // back_button_timeout 2s, // back_button_timeout
500ms, // key_repeat_delay 500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period
}; };
sunshine_t sunshine { sunshine_t sunshine {
2, // min_log_level 2, // min_log_level
0 // flags 0, // flags
{}, // User file
{}, // Username
{}, // Password
{}, // Password Salt
SUNSHINE_ASSETS_DIR "/sunshine.conf", // config file
{} // cmd args
}; };
bool whitespace(char ch) { bool endline(char ch) {
return ch == '\r' || ch == '\n';
}
bool space_tab(char ch) {
return ch == ' ' || ch == '\t'; return ch == ' ' || ch == '\t';
} }
std::string to_string(const char *begin, const char *end) { bool whitespace(char ch) {
return { begin, (std::size_t)(end - begin) }; return space_tab(ch) || endline(ch);
} }
std::optional<std::pair<std::string, std::string>> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) { std::string to_string(const char *begin, const char *end) {
begin = std::find_if(begin, end, std::not_fn(whitespace)); std::string result;
end = std::find(begin, end, '#');
end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
auto eq = std::find(begin, end, '='); KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
if(eq == end || eq == begin) { auto comment = std::find(pos, end, '#');
return std::nullopt; auto endl = std::find_if(comment, end, endline);
result.append(pos, comment);
pos = endl;
})
return result;
}
template<class It>
It skip_list(It skipper, It end) {
int stack = 1;
while(skipper != end && stack) {
if(*skipper == '[') {
++stack;
}
if(*skipper == ']') {
--stack;
}
++skipper;
} }
auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base(); return skipper;
auto begin_val = std::find_if(eq + 1, end, std::not_fn(whitespace));
return std::pair { to_string(begin, end_name), to_string(begin_val, end) };
} }
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content) { std::pair<
std::string_view::const_iterator,
std::optional<std::pair<std::string, std::string>>>
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
begin = std::find_if_not(begin, end, whitespace);
auto endl = std::find_if(begin, end, endline);
auto endc = std::find(begin, endl, '#');
endc = std::find_if(std::make_reverse_iterator(endc), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
auto eq = std::find(begin, endc, '=');
if(eq == endc || eq == begin) {
return std::make_pair(endl, std::nullopt);
}
auto end_name = std::find_if_not(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), space_tab).base();
auto begin_val = std::find_if_not(eq + 1, endc, space_tab);
if(begin_val == endl) {
return std::make_pair(endl, std::nullopt);
}
// Lists might contain newlines
if(*begin_val == '[') {
endl = skip_list(begin_val + 1, end);
if(endl == end) {
std::cout << "Warning: Config option ["sv << to_string(begin, end_name) << "] Missing ']'"sv;
return std::make_pair(endl, std::nullopt);
}
}
return std::make_pair(
endl,
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
}
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars; std::unordered_map<std::string, std::string> vars;
auto pos = std::begin(file_content); auto pos = std::begin(file_content);
auto end = std::end(file_content); auto end = std::end(file_content);
while(pos < end) { while(pos < end) {
auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; }); // auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; });
auto var = parse_line(pos, newline); TUPLE_2D(endl, var, parse_option(pos, end));
pos = endl;
if(pos != end) {
pos += (*pos == '\r') ? 2 : 1;
}
pos = (*newline == '\r') ? newline + 2 : newline + 1;
if(!var) { if(!var) {
continue; continue;
} }
@ -207,6 +353,38 @@ void string_restricted_f(std::unordered_map<std::string, std::string> &vars, con
} }
} }
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
// appdata needs to be retrieved once only
static auto appdata = platf::appdata();
std::string temp;
string_f(vars, name, temp);
if(!temp.empty()) {
input = temp;
}
if(input.is_relative()) {
input = appdata / input;
}
auto dir = input;
dir.remove_filename();
// Ensure the directories exists
if(!fs::exists(dir)) {
fs::create_directories(dir);
}
}
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
fs::path temp = input;
path_f(vars, name, temp);
input = temp.string();
}
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) { void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
auto it = vars.find(name); auto it = vars.find(name);
@ -215,7 +393,7 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
} }
auto &val = it->second; auto &val = it->second;
input = util::from_chars(&val[0], &val[0] + val.size()); input = util::from_chars(&val[0], &val[0] + val.size());
vars.erase(it); vars.erase(it);
} }
@ -228,7 +406,7 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
} }
auto &val = it->second; auto &val = it->second;
input = util::from_chars(&val[0], &val[0] + val.size()); input = util::from_chars(&val[0], &val[0] + val.size());
vars.erase(it); vars.erase(it);
} }
@ -263,15 +441,14 @@ void int_between_f(std::unordered_map<std::string, std::string> &vars, const std
} }
bool to_bool(std::string &boolean) { bool to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); }); std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); });
return return boolean == "true"sv ||
boolean == "true"sv || boolean == "yes"sv ||
boolean == "yes"sv || boolean == "enable"sv ||
boolean == "enable"sv || (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
(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, int &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@ -282,7 +459,7 @@ void bool_f(std::unordered_map<std::string, std::string> &vars, const std::strin
input = to_bool(tmp) ? 1 : 0; input = to_bool(tmp) ? 1 : 0;
} }
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) { void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@ -311,30 +488,76 @@ void double_between_f(std::unordered_map<std::string, std::string> &vars, const
} }
} }
void print_help(const char *name) { void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::cout << std::string string;
"Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl << string_f(vars, name, string);
" Any configurable option can be overwritten with: \"name=value\""sv << std::endl << std::endl <<
" --help | print help"sv << std::endl << std::endl << if(string.empty()) {
" flags"sv << std::endl << return;
" -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; input.clear();
auto begin = std::cbegin(string);
if(*begin == '[') {
++begin;
}
begin = std::find_if_not(begin, std::cend(string), whitespace);
if(begin == std::cend(string)) {
return;
}
auto pos = begin;
while(pos < std::cend(string)) {
if(*pos == '[') {
pos = skip_list(pos + 1, std::cend(string)) + 1;
}
else if(*pos == ']') {
break;
}
else if(*pos == ',') {
input.emplace_back(begin, pos);
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
}
else {
++pos;
}
}
if(pos != begin) {
input.emplace_back(begin, pos);
}
}
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list;
list_string_f(vars, name, list);
for(auto &el : list) {
input.emplace_back(util::from_view(el));
}
} }
int apply_flags(const char *line) { int apply_flags(const char *line) {
int ret = 0; int ret = 0;
while(*line != '\0') { while(*line != '\0') {
switch(*line) { switch(*line) {
case '0': case '0':
config::sunshine.flags[config::flag::PIN_STDIN].flip(); config::sunshine.flags[config::flag::PIN_STDIN].flip();
break; break;
case '1': case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip(); config::sunshine.flags[config::flag::FRESH_STATE].flip();
break; break;
default: case '2':
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE].flip();
ret = -1; break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
break;
default:
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
ret = -1;
} }
++line; ++line;
@ -351,46 +574,50 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
int_f(vars, "crf", video.crf); int_f(vars, "crf", video.crf);
int_f(vars, "qp", video.qp); int_f(vars, "qp", video.qp);
int_f(vars, "min_threads", video.min_threads); int_f(vars, "min_threads", video.min_threads);
int_between_f(vars, "hevc_mode", video.hevc_mode, { int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
0, 3
});
string_f(vars, "sw_preset", video.sw.preset); string_f(vars, "sw_preset", video.sw.preset);
string_f(vars, "sw_tune", video.sw.tune); string_f(vars, "sw_tune", video.sw.tune);
int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view);
int_f(vars, "nv_rc", video.nv.preset, nv::rc_from_view); int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view);
int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view); int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view);
int_f(vars, "amd_quality", video.amd.quality, amd::quality_from_view);
int_f(vars, "amd_rc", video.amd.rc, amd::rc_from_view);
int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view);
string_f(vars, "encoder", video.encoder); string_f(vars, "encoder", video.encoder);
string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "adapter_name", video.adapter_name);
string_f(vars, "output_name", video.output_name); string_f(vars, "output_name", video.output_name);
string_f(vars, "pkey", nvhttp.pkey); path_f(vars, "pkey", nvhttp.pkey);
string_f(vars, "cert", nvhttp.cert); path_f(vars, "cert", nvhttp.cert);
string_f(vars, "sunshine_name", nvhttp.sunshine_name); string_f(vars, "sunshine_name", nvhttp.sunshine_name);
string_f(vars, "file_state", nvhttp.file_state);
path_f(vars, "file_state", nvhttp.file_state);
// Must be run after "file_state"
config::sunshine.credentials_file = config::nvhttp.file_state;
path_f(vars, "credentials_file", config::sunshine.credentials_file);
string_f(vars, "external_ip", nvhttp.external_ip); string_f(vars, "external_ip", nvhttp.external_ip);
list_string_f(vars, "resolutions"s, nvhttp.resolutions);
list_int_f(vars, "fps"s, nvhttp.fps);
string_f(vars, "audio_sink", audio.sink); string_f(vars, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink);
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
"pc"sv, "lan"sv, "wan"sv
});
int to = -1; int to = -1;
int_between_f(vars, "ping_timeout", to, { int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
-1, std::numeric_limits<int>::max()
});
if(to != -1) { if(to != -1) {
stream.ping_timeout = std::chrono::milliseconds(to); stream.ping_timeout = std::chrono::milliseconds(to);
} }
int_between_f(vars, "channels", stream.channels, { int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
1, std::numeric_limits<int>::max()
});
string_f(vars, "file_apps", stream.file_apps); path_f(vars, "file_apps", stream.file_apps);
int_between_f(vars, "fec_percentage", stream.fec_percentage, { int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 100 });
1, 100
});
to = std::numeric_limits<int>::min(); to = std::numeric_limits<int>::min();
int_f(vars, "back_button_timeout", to); int_f(vars, "back_button_timeout", to);
@ -400,12 +627,10 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
} }
double repeat_frequency { 0 }; double repeat_frequency { 0 };
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
0, std::numeric_limits<double>::max()
});
if(repeat_frequency > 0) { if(repeat_frequency > 0) {
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency }; config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
} }
to = -1; to = -1;
@ -415,9 +640,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
} }
std::string log_level_string; std::string log_level_string;
string_restricted_f(vars, "min_log_level", 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 });
"verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv
});
if(!log_level_string.empty()) { if(!log_level_string.empty()) {
if(log_level_string == "verbose"sv) { if(log_level_string == "verbose"sv) {
@ -441,6 +664,13 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
else if(log_level_string == "none"sv) { else if(log_level_string == "none"sv) {
sunshine.min_log_level = 6; sunshine.min_log_level = 6;
} }
else {
// accept digit directly
auto val = log_level_string[0];
if(val >= '0' && val < '7') {
sunshine.min_log_level = val - '0';
}
}
} }
auto it = vars.find("flags"s); auto it = vars.find("flags"s);
@ -451,18 +681,16 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
} }
if(sunshine.min_log_level <= 3) { if(sunshine.min_log_level <= 3) {
for(auto &[var,_] : vars) { for(auto &[var, _] : vars) {
std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl; std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl;
} }
} }
} }
int parse(int argc, char *argv[]) { int parse(int argc, char *argv[]) {
const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf";
std::unordered_map<std::string, std::string> cmd_vars; std::unordered_map<std::string, std::string> cmd_vars;
for(auto x = argc -1; x > 0; --x) { for(auto x = 1; x < argc; ++x) {
auto line = argv[x]; auto line = argv[x];
if(line == "--help"sv) { if(line == "--help"sv) {
@ -470,6 +698,13 @@ int parse(int argc, char *argv[]) {
return 1; return 1;
} }
else if(*line == '-') { else if(*line == '-') {
if(*(line + 1) == '-') {
sunshine.cmd.name = line + 2;
sunshine.cmd.argc = argc - x - 1;
sunshine.cmd.argv = argv + x + 1;
break;
}
if(apply_flags(line + 1)) { if(apply_flags(line + 1)) {
print_help(*argv); print_help(*argv);
return -1; return -1;
@ -480,35 +715,30 @@ int parse(int argc, char *argv[]) {
auto pos = std::find(line, line_end, '='); auto pos = std::find(line, line_end, '=');
if(pos == line_end) { if(pos == line_end) {
config_file = line; sunshine.config_file = line;
} }
else { else {
auto var = parse_line(line, line_end); TUPLE_EL(var, 1, parse_option(line, line_end));
if(!var) { if(!var) {
print_help(*argv); print_help(*argv);
return -1; 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)); cmd_vars.emplace(std::move(*var));
} }
} }
} }
std::ifstream in { config_file }; auto vars = parse_config(read_file(sunshine.config_file.c_str()));
if(!in.is_open()) { for(auto &[name, value] : cmd_vars) {
std::cout << "Error: Couldn't open "sv << config_file << std::endl;
return -1;
}
auto vars = parse_config(std::string {
// Quick and dirty
std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>()
});
for(auto &[name,value] : cmd_vars) {
vars.insert_or_assign(std::move(name), std::move(value)); vars.insert_or_assign(std::move(name), std::move(value));
} }
@ -516,4 +746,4 @@ int parse(int argc, char *argv[]) {
return 0; return 0;
} }
} } // namespace config

View file

@ -1,16 +1,18 @@
#ifndef SUNSHINE_CONFIG_H #ifndef SUNSHINE_CONFIG_H
#define SUNSHINE_CONFIG_H #define SUNSHINE_CONFIG_H
#include <chrono>
#include <string>
#include <bitset> #include <bitset>
#include <chrono>
#include <optional> #include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace config { namespace config {
struct video_t { struct video_t {
// ffmpeg params // ffmpeg params
int crf; // higher == more compression and less quality int crf; // higher == more compression and less quality
int qp; // higher == more compression and less quality, ignored if crf != 0 int qp; // higher == more compression and less quality, ignored if crf != 0
int hevc_mode; int hevc_mode;
@ -26,6 +28,12 @@ struct video_t {
int coder; int coder;
} nv; } nv;
struct {
std::optional<int> quality;
std::optional<int> rc;
int coder;
} amd;
std::string encoder; std::string encoder;
std::string adapter_name; std::string adapter_name;
std::string output_name; std::string output_name;
@ -33,6 +41,7 @@ struct video_t {
struct audio_t { struct audio_t {
std::string sink; std::string sink;
std::string virtual_sink;
}; };
struct stream_t { struct stream_t {
@ -59,6 +68,8 @@ struct nvhttp_t {
std::string file_state; std::string file_state;
std::string external_ip; std::string external_ip;
std::vector<std::string> resolutions;
std::vector<int> fps;
}; };
struct input_t { struct input_t {
@ -69,16 +80,30 @@ struct input_t {
namespace flag { namespace flag {
enum flag_e : std::size_t { enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
CONST_PIN, // Use "universal" pin
FLAG_SIZE FLAG_SIZE
}; };
} }
struct sunshine_t { struct sunshine_t {
int min_log_level; int min_log_level;
std::bitset<flag::FLAG_SIZE> flags; std::bitset<flag::FLAG_SIZE> flags;
std::string credentials_file;
std::string username;
std::string password;
std::string salt;
std::string config_file;
struct cmd_t {
std::string name;
int argc;
char **argv;
} cmd;
}; };
extern video_t video; extern video_t video;
@ -89,6 +114,6 @@ extern input_t input;
extern sunshine_t sunshine; extern sunshine_t sunshine;
int parse(int argc, char *argv[]); int parse(int argc, char *argv[]);
} std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
} // namespace config
#endif #endif

514
sunshine/confighttp.cpp Normal file
View file

@ -0,0 +1,514 @@
//
// Created by TheElixZammuto on 2021-05-09.
// TODO: Authentication, better handling of routes common to nvhttp, cleanup
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
#include <filesystem>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/asio/ssl/context.hpp>
#include <Simple-Web-Server/crypto.hpp>
#include <Simple-Web-Server/server_http.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include "config.h"
#include "confighttp.h"
#include "crypto.h"
#include "httpcommon.h"
#include "main.h"
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "rtsp.h"
#include "utility.h"
#include "uuid.h"
namespace confighttp {
using namespace std::literals;
constexpr auto PORT_HTTPS = 47990;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
using args_t = SimpleWeb::CaseInsensitiveMultimap;
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
enum class op_e {
ADD,
REMOVE
};
void print_req(const req_https_t &request) {
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
for(auto &[name, val] : request->header) {
BOOST_LOG(debug) << name << " -- " << val;
}
BOOST_LOG(debug) << " [--] "sv;
for(auto &[name, val] : request->parse_query_string()) {
BOOST_LOG(debug) << name << " -- " << val;
}
BOOST_LOG(debug) << " [--] "sv;
}
void send_unauthorized(resp_https_t response, req_https_t request) {
auto address = request->remote_endpoint_address();
BOOST_LOG(info) << '[' << address << "] -- denied"sv;
const SimpleWeb::CaseInsensitiveMultimap headers {
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
};
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
}
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;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
return false;
}
auto fg = util::fail_guard([&]() {
send_unauthorized(response, request);
});
auto auth = request->header.find("authorization");
if(auth == request->header.end()) {
return false;
}
auto &rawAuth = auth->second;
auto authData = SimpleWeb::Crypto::Base64::decode(rawAuth.substr("Basic "sv.length()));
int index = authData.find(':');
if(index >= authData.size() - 1) {
return false;
}
auto username = authData.substr(0, index);
auto password = authData.substr(index + 1);
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
if(username != config::sunshine.username || hash != config::sunshine.password) {
return false;
}
fg.disable();
return true;
}
void not_found(resp_https_t response, req_https_t request) {
pt::ptree tree;
tree.put("root.<xmlattr>.status_code", 404);
std::ostringstream data;
pt::write_xml(data, tree);
response->write(data.str());
*response << "HTTP/1.1 404 NOT FOUND\r\n"
<< data.str();
}
void getIndexPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "index.html");
response->write(header + content);
}
void getPinPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "pin.html");
response->write(header + content);
}
void getAppsPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "apps.html");
response->write(header + content);
}
void getClientsPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "clients.html");
response->write(header + content);
}
void getConfigPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "config.html");
response->write(header + content);
}
void getPasswordPage(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "password.html");
response->write(header + content);
}
void getApps(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::string content = read_file(config::stream.file_apps.c_str());
response->write(content);
}
void saveApp(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::stringstream ss;
ss << request->content.rdbuf();
pt::ptree outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
pt::ptree inputTree, fileTree;
BOOST_LOG(fatal) << config::stream.file_apps;
try {
//TODO: Input Validation
pt::read_json(ss, inputTree);
pt::read_json(config::stream.file_apps, fileTree);
if(inputTree.get_child("prep-cmd").empty()) {
inputTree.erase("prep-cmd");
}
if(inputTree.get_child("detached").empty()) {
inputTree.erase("detached");
}
auto &apps_node = fileTree.get_child("apps"s);
int index = inputTree.get<int>("index");
inputTree.erase("index");
if(index == -1) {
apps_node.push_back(std::make_pair("", inputTree));
}
else {
//Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick
pt::ptree newApps;
int i = 0;
for(const auto &kv : apps_node) {
if(i == index) {
newApps.push_back(std::make_pair("", inputTree));
}
else {
newApps.push_back(std::make_pair("", kv.second));
}
i++;
}
fileTree.erase("apps");
fileTree.push_back(std::make_pair("apps", newApps));
}
pt::write_json(config::stream.file_apps, fileTree);
}
catch(std::exception &e) {
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
outputTree.put("status", "false");
outputTree.put("error", "Invalid Input JSON");
return;
}
outputTree.put("status", "true");
proc::refresh(config::stream.file_apps);
}
void deleteApp(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
pt::ptree outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
pt::ptree fileTree;
try {
pt::read_json(config::stream.file_apps, fileTree);
auto &apps_node = fileTree.get_child("apps"s);
int index = stoi(request->path_match[1]);
if(index < 0) {
outputTree.put("status", "false");
outputTree.put("error", "Invalid Index");
return;
}
else {
//Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick
pt::ptree newApps;
int i = 0;
for(const auto &kv : apps_node) {
if(i++ != index) {
newApps.push_back(std::make_pair("", kv.second));
}
}
fileTree.erase("apps");
fileTree.push_back(std::make_pair("apps", newApps));
}
pt::write_json(config::stream.file_apps, fileTree);
}
catch(std::exception &e) {
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
outputTree.put("status", "false");
outputTree.put("error", "Invalid File JSON");
return;
}
outputTree.put("status", "true");
proc::refresh(config::stream.file_apps);
}
void getConfig(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
pt::ptree outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
outputTree.put("status", "true");
outputTree.put("platform", SUNSHINE_PLATFORM);
auto vars = config::parse_config(read_file(config::sunshine.config_file.c_str()));
for(auto &[name, value] : vars) {
outputTree.put(std::move(name), std::move(value));
}
}
void saveConfig(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::stringstream ss;
std::stringstream configStream;
ss << request->content.rdbuf();
pt::ptree outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
pt::ptree inputTree;
try {
//TODO: Input Validation
pt::read_json(ss, inputTree);
for(const auto &kv : inputTree) {
std::string value = inputTree.get<std::string>(kv.first);
if(value.length() == 0 || value.compare("null") == 0) continue;
configStream << kv.first << " = " << value << std::endl;
}
write_file(config::sunshine.config_file.c_str(), configStream.str());
}
catch(std::exception &e) {
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
outputTree.put("status", "false");
outputTree.put("error", e.what());
return;
}
}
void savePassword(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::stringstream ss;
std::stringstream configStream;
ss << request->content.rdbuf();
pt::ptree inputTree, outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
try {
//TODO: Input Validation
pt::read_json(ss, inputTree);
auto username = inputTree.get<std::string>("currentUsername");
auto newUsername = inputTree.get<std::string>("newUsername");
auto password = inputTree.get<std::string>("currentPassword");
auto newPassword = inputTree.get<std::string>("newPassword");
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
if(newUsername.length() == 0) newUsername = username;
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
if(username == config::sunshine.username && hash == config::sunshine.password) {
if(newPassword != confirmPassword) {
outputTree.put("status", false);
outputTree.put("error", "Password Mismatch");
}
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
http::reload_user_creds(config::sunshine.credentials_file);
outputTree.put("status", true);
}
else {
outputTree.put("status", false);
outputTree.put("error", "Invalid Current Credentials");
}
}
catch(std::exception &e) {
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
outputTree.put("status", false);
outputTree.put("error", e.what());
return;
}
}
void savePin(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
print_req(request);
std::stringstream ss;
ss << request->content.rdbuf();
pt::ptree inputTree, outputTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
try {
//TODO: Input Validation
pt::read_json(ss, inputTree);
std::string pin = inputTree.get<std::string>("pin");
outputTree.put("status", nvhttp::pin(pin));
}
catch(std::exception &e) {
BOOST_LOG(warning) << "SavePin: "sv << e.what();
outputTree.put("status", false);
outputTree.put("error", e.what());
return;
}
}
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
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);
https_server_t server { ctx, 0 };
server.default_resource = not_found;
server.resource["^/$"]["GET"] = getIndexPage;
server.resource["^/pin$"]["GET"] = getPinPage;
server.resource["^/apps$"]["GET"] = getAppsPage;
server.resource["^/clients$"]["GET"] = getClientsPage;
server.resource["^/config$"]["GET"] = getConfigPage;
server.resource["^/password$"]["GET"] = getPasswordPage;
server.resource["^/api/pin"]["POST"] = savePin;
server.resource["^/api/apps$"]["GET"] = getApps;
server.resource["^/api/apps$"]["POST"] = saveApp;
server.resource["^/api/config$"]["GET"] = getConfig;
server.resource["^/api/config$"]["POST"] = saveConfig;
server.resource["^/api/password$"]["POST"] = savePassword;
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;
try {
server.bind();
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();
shutdown_event->raise(true);
return;
}
auto accept_and_run = [&](auto *server) {
try {
server->accept_and_run();
}
catch(boost::system::system_error &err) {
// It's possible the exception gets thrown after calling server->stop() from a different thread
if(shutdown_event->peek()) {
return;
}
BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTPS << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}
};
std::thread tcp { accept_and_run, &server };
// Wait for any event
shutdown_event->view();
server.stop();
tcp.join();
}
} // namespace confighttp

20
sunshine/confighttp.h Normal file
View file

@ -0,0 +1,20 @@
//
// Created by loki on 6/3/19.
//
#ifndef SUNSHINE_CONFIGHTTP_H
#define SUNSHINE_CONFIGHTTP_H
#include <functional>
#include <string>
#include "thread_safe.h"
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
namespace confighttp {
void start();
}
#endif //SUNSHINE_CONFIGHTTP_H

View file

@ -2,14 +2,14 @@
// Created by loki on 5/31/19. // Created by loki on 5/31/19.
// //
#include <openssl/pem.h>
#include "crypto.h" #include "crypto.h"
#include <openssl/pem.h>
namespace crypto { namespace crypto {
using big_num_t = util::safe_ptr<BIGNUM, BN_free>; using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
//using rsa_t = util::safe_ptr<RSA, RSA_free>; //using rsa_t = util::safe_ptr<RSA, RSA_free>;
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>; using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx {X509_STORE_CTX_new() } {} cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
void cert_chain_t::add(x509_t &&cert) { void cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store { X509_STORE_new() }; x509_store_t x509_store { X509_STORE_new() };
@ -26,7 +26,7 @@ void cert_chain_t::add(x509_t &&cert) {
*/ */
const char *cert_chain_t::verify(x509_t::element_type *cert) { const char *cert_chain_t::verify(x509_t::element_type *cert) {
int err_code = 0; int err_code = 0;
for(auto &[_,x509_store] : _certs) { for(auto &[_, x509_store] : _certs) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
X509_STORE_CTX_cleanup(_cert_ctx.get()); X509_STORE_CTX_cleanup(_cert_ctx.get());
}); });
@ -36,7 +36,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
auto err = X509_verify_cert(_cert_ctx.get()); auto err = X509_verify_cert(_cert_ctx.get());
if (err == 1) { if(err == 1) {
return nullptr; return nullptr;
} }
@ -46,7 +46,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) { if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
return nullptr; return nullptr;
} }
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) { 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); return X509_verify_cert_error_string(err_code);
} }
} }
@ -63,7 +63,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
}); });
// Gen 7 servers use 128-bit AES ECB // Gen 7 servers use 128-bit AES ECB
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1; return -1;
} }
@ -72,11 +72,11 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
plaintext.resize((cipher.size() + 15) / 16 * 16); plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size(); auto size = (int)plaintext.size();
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended // 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) { if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1; return -1;
} }
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) { if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
return -1; return -1;
} }
@ -85,7 +85,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
} }
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
std::vector<std::uint8_t> &plaintext) { std::vector<std::uint8_t> &plaintext) {
auto cipher = tagged_cipher.substr(16); auto cipher = tagged_cipher.substr(16);
auto tag = tagged_cipher.substr(0, 16); auto tag = tagged_cipher.substr(0, 16);
@ -93,15 +93,15 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
EVP_CIPHER_CTX_reset(ctx.get()); EVP_CIPHER_CTX_reset(ctx.get());
}); });
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) { if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -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; 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; return -1;
} }
@ -109,16 +109,16 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
plaintext.resize((cipher.size() + 15) / 16 * 16); plaintext.resize((cipher.size() + 15) / 16 * 16);
int size; int size;
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) { if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1; return -1;
} }
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char*>(tag.data())) != 1) { if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1; return -1;
} }
int len = size; int len = size;
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) { if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
return -1; return -1;
} }
@ -134,7 +134,7 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_
}); });
// Gen 7 servers use 128-bit AES ECB // 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_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1; return -1;
} }
@ -143,11 +143,11 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_
cipher.resize((plaintext.size() + 15) / 16 * 16); cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int)cipher.size(); auto size = (int)cipher.size();
// Encrypt into the caller's buffer // 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(ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1; return -1;
} }
if (EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) { if(EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
return -1; return -1;
} }
@ -187,10 +187,10 @@ x509_t x509(const std::string_view &x) {
BIO_write(io.get(), x.data(), x.size()); BIO_write(io.get(), x.data(), x.size());
X509 *p = nullptr; x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr); PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return x509_t { p }; return p;
} }
pkey_t pkey(const std::string_view &k) { pkey_t pkey(const std::string_view &k) {
@ -198,10 +198,10 @@ pkey_t pkey(const std::string_view &k) {
BIO_write(io.get(), k.data(), k.size()); BIO_write(io.get(), k.data(), k.size());
EVP_PKEY *p = nullptr; pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr); PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return pkey_t { p }; return p;
} }
std::string pem(x509_t &x509) { std::string pem(x509_t &x509) {
@ -230,14 +230,14 @@ std::string_view signature(const x509_t &x) {
const ASN1_BIT_STRING *asn1 = nullptr; const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get()); X509_get0_signature(&asn1, nullptr, x.get());
return { (const char*)asn1->data, (std::size_t)asn1->length }; return { (const char *)asn1->data, (std::size_t)asn1->length };
} }
std::string rand(std::size_t bytes) { std::string rand(std::size_t bytes) {
std::string r; std::string r;
r.resize(bytes); r.resize(bytes);
RAND_bytes((uint8_t*)r.data(), r.size()); RAND_bytes((uint8_t *)r.data(), r.size());
return r; return r;
} }
@ -297,8 +297,8 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
X509_set_pubkey(x509.get(), pkey.get()); X509_set_pubkey(x509.get(), pkey.get());
auto name = X509_get_subject_name(x509.get()); auto name = X509_get_subject_name(x509.get());
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(const std::uint8_t*)cn.data(), cn.size(), (const std::uint8_t *)cn.data(), cn.size(),
-1, 0); -1, 0);
X509_set_issuer_name(x509.get(), name); X509_set_issuer_name(x509.get(), name);
@ -324,7 +324,7 @@ bool verify(const x509_t &x509, const std::string_view &data, const std::string_
return false; return false;
} }
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) { if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) {
return false; return false;
} }
@ -338,4 +338,14 @@ bool verify256(const x509_t &x509, const std::string_view &data, const std::stri
void md_ctx_destroy(EVP_MD_CTX *ctx) { void md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx); EVP_MD_CTX_destroy(ctx);
} }
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes);
for(std::size_t i = 0; i != value.size(); ++i) {
value[i] = alphabet[value[i] % alphabet.length()];
}
return value;
} }
} // namespace crypto

View file

@ -5,12 +5,11 @@
#ifndef SUNSHINE_CRYPTO_H #ifndef SUNSHINE_CRYPTO_H
#define SUNSHINE_CRYPTO_H #define SUNSHINE_CRYPTO_H
#include <cassert>
#include <array> #include <array>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/rand.h>
#include "utility.h" #include "utility.h"
@ -25,16 +24,17 @@ void md_ctx_destroy(EVP_MD_CTX *);
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>; using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
using aes_t = std::array<std::uint8_t, 16>; using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>; using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>; using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>; using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>; using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>; using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>; using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>; using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
sha256_t hash(const std::string_view &plaintext); sha256_t hash(const std::string_view &plaintext);
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin); aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
x509_t x509(const std::string_view &x); x509_t x509(const std::string_view &x);
@ -50,6 +50,8 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
std::string_view signature(const x509_t &x); std::string_view signature(const x509_t &x);
std::string rand(std::size_t bytes); std::string rand(std::size_t bytes);
std::string rand_alphabet(std::size_t bytes,
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
class cert_chain_t { class cert_chain_t {
public: public:
@ -58,6 +60,7 @@ public:
void add(x509_t &&cert); void add(x509_t &&cert);
const char *verify(x509_t::element_type *cert); const char *verify(x509_t::element_type *cert);
private: private:
std::vector<std::pair<x509_t, x509_store_t>> _certs; std::vector<std::pair<x509_t, x509_store_t>> _certs;
x509_store_ctx_t _cert_ctx; x509_store_ctx_t _cert_ctx;
@ -66,13 +69,14 @@ private:
class cipher_t { class cipher_t {
public: public:
cipher_t(const aes_t &key); cipher_t(const aes_t &key);
cipher_t(cipher_t&&) noexcept = default; cipher_t(cipher_t &&) noexcept = default;
cipher_t &operator=(cipher_t&&) noexcept = default; cipher_t &operator=(cipher_t &&) noexcept = default;
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher); 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_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); int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
private: private:
cipher_ctx_t ctx; cipher_ctx_t ctx;
aes_t key; aes_t key;
@ -80,6 +84,6 @@ private:
public: public:
bool padding; bool padding;
}; };
} } // namespace crypto
#endif //SUNSHINE_CRYPTO_H #endif //SUNSHINE_CRYPTO_H

189
sunshine/httpcommon.cpp Normal file
View file

@ -0,0 +1,189 @@
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
#include <filesystem>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/asio/ssl/context.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"
#include "crypto.h"
#include "httpcommon.h"
#include "main.h"
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "rtsp.h"
#include "utility.h"
#include "uuid.h"
namespace http {
using namespace std::literals;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
int reload_user_creds(const std::string &file);
bool user_creds_exist(const std::string &file);
std::string unique_id;
net::net_e origin_pin_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);
if(clean_slate) {
unique_id = util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sushine"sv;
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
}
if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1;
}
}
if(!user_creds_exist(config::sunshine.credentials_file)) {
if(save_user_creds(config::sunshine.credentials_file, "sunshine"s, crypto::rand_alphabet(16), true)) {
return -1;
}
}
if(reload_user_creds(config::sunshine.credentials_file)) {
return -1;
}
return 0;
}
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree;
if(fs::exists(file)) {
try {
pt::read_json(file, outputTree);
}
catch(std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1;
}
}
auto salt = crypto::rand_alphabet(16);
outputTree.put("username", username);
outputTree.put("salt", salt);
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try {
pt::write_json(file, outputTree);
}
catch(std::exception &e) {
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
return -1;
}
BOOST_LOG(info) << "New credentials have been created"sv;
if(run_our_mouth) {
BOOST_LOG(info) << "Username: "sv << username;
BOOST_LOG(info) << "Password: "sv << password;
}
return 0;
}
bool user_creds_exist(const std::string &file) {
if(!fs::exists(file)) {
return false;
}
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found();
}
catch(std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
}
return false;
}
int reload_user_creds(const std::string &file) {
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
config::sunshine.username = inputTree.get<std::string>("username");
config::sunshine.password = inputTree.get<std::string>("password");
config::sunshine.salt = inputTree.get<std::string>("salt");
}
catch(std::exception &e) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
}
int create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
auto pkey_dir = pkey_path;
auto cert_dir = cert_path;
pkey_dir.remove_filename();
cert_dir.remove_filename();
std::error_code err_code {};
fs::create_directories(pkey_dir, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
return -1;
}
fs::create_directories(cert_dir, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
return -1;
}
if(write_file(pkey.c_str(), creds.pkey)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
return -1;
}
if(write_file(cert.c_str(), creds.x509)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']';
return -1;
}
fs::permissions(pkey_path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1;
}
fs::permissions(cert_path,
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
}
} // namespace http

18
sunshine/httpcommon.h Normal file
View file

@ -0,0 +1,18 @@
#include "network.h"
#include "thread_safe.h"
namespace http {
int init();
int create_creds(const std::string &pkey, const std::string &cert);
int save_user_creds(
const std::string &file,
const std::string &username,
const std::string &password,
bool run_our_mouth = false);
int reload_user_creds(const std::string &file);
extern std::string unique_id;
extern net::net_e origin_pin_allowed;
} // namespace http

View file

@ -2,21 +2,27 @@
// Created by loki on 6/20/19. // Created by loki on 6/20/19.
// //
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint>
extern "C" { extern "C" {
#include <moonlight-common-c/src/Input.h> #include <moonlight-common-c/src/Input.h>
} }
#include <bitset> #include <bitset>
#include "main.h"
#include "config.h" #include "config.h"
#include "utility.h" #include "input.h"
#include "main.h"
#include "platform/common.h" #include "platform/common.h"
#include "thread_pool.h" #include "thread_pool.h"
#include "utility.h"
namespace input { namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t)*8); constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
#define DISABLE_LEFT_BUTTON_DELAY ((util::ThreadPool::task_id_t)0x01)
#define ENABLE_LEFT_BUTTON_DELAY nullptr
enum class button_state_e { enum class button_state_e {
NONE, NONE,
DOWN, DOWN,
@ -48,7 +54,7 @@ static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {}; static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
void free_gamepad(platf::input_t &platf_input, int id) { void free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad(platf_input, id, platf::gamepad_state_t{}); platf::gamepad(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id); platf::free_gamepad(platf_input, id);
free_id(gamepadMask, id); free_id(gamepadMask, id);
@ -57,7 +63,7 @@ struct gamepad_t {
gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {} gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
~gamepad_t() { ~gamepad_t() {
if(id >= 0) { if(id >= 0) {
task_pool.push([id=this->id]() { task_pool.push([id = this->id]() {
free_gamepad(platf_input, id); free_gamepad(platf_input, id);
}); });
} }
@ -78,20 +84,41 @@ struct gamepad_t {
}; };
struct input_t { struct input_t {
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { } 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::uint16_t active_gamepad_state;
std::vector<gamepad_t> gamepads; 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; using namespace std::literals;
void print(PNV_MOUSE_MOVE_PACKET packet) { void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse move packet--"sv << std::endl << "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl << "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
<< "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl << "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl
<< "--end mouse move packet--"sv; << "--end relative mouse move packet--"sv;
}
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
<< "y ["sv << util::endian::big(packet->y) << ']' << std::endl
<< "width ["sv << util::endian::big(packet->width) << ']' << std::endl
<< "height ["sv << util::endian::big(packet->height) << ']' << std::endl
<< "--end absolute mouse move packet--"sv;
} }
void print(PNV_MOUSE_BUTTON_PACKET packet) { void print(PNV_MOUSE_BUTTON_PACKET packet) {
@ -135,50 +162,147 @@ void print(PNV_MULTI_CONTROLLER_PACKET packet) {
constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL; constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL;
void print(void *input) { void print(void *input) {
int input_type = util::endian::big(*(int*)input); int input_type = util::endian::big(*(int *)input);
switch(input_type) { switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE: case PACKET_TYPE_REL_MOUSE_MOVE:
print((PNV_MOUSE_MOVE_PACKET)input); print((PNV_REL_MOUSE_MOVE_PACKET)input);
break; break;
case PACKET_TYPE_MOUSE_BUTTON: case PACKET_TYPE_ABS_MOUSE_MOVE:
print((PNV_MOUSE_BUTTON_PACKET)input); print((PNV_ABS_MOUSE_MOVE_PACKET)input);
break; break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD: case PACKET_TYPE_MOUSE_BUTTON:
{ print((PNV_MOUSE_BUTTON_PACKET)input);
char *tmp_input = (char*)input + 4; break;
if(tmp_input[0] == 0x0A) { case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
print((PNV_SCROLL_PACKET)input); char *tmp_input = (char *)input + 4;
} if(tmp_input[0] == 0x0A) {
else { print((PNV_SCROLL_PACKET)input);
print((PNV_KEYBOARD_PACKET)input);
}
break;
} }
case PACKET_TYPE_MULTI_CONTROLLER: else {
print((PNV_MULTI_CONTROLLER_PACKET)input); print((PNV_KEYBOARD_PACKET)input);
break; }
break;
}
case PACKET_TYPE_MULTI_CONTROLLER:
print((PNV_MULTI_CONTROLLER_PACKET)input);
break;
} }
} }
void passthrough(platf::input_t &input, PNV_MOUSE_MOVE_PACKET packet) { void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
display_cursor = true; display_cursor = true;
platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY)); input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
}
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
if(input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
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();
}
float x = util::endian::big(packet->x);
float y = util::endian::big(packet->y);
// Prevent divide by zero
// Don't expect it to happen, but just in case
if(!packet->width || !packet->height) {
BOOST_LOG(warning) << "Moonlight passed invalid dimensions"sv;
return;
}
int width = util::endian::big(packet->width);
int height = util::endian::big(packet->height);
auto offsetX = (width - (float)touch_port.width) * 0.5f;
auto offsetY = (height - (float)touch_port.height) * 0.5f;
std::clamp(x, offsetX, width - offsetX);
std::clamp(y, offsetX, 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) { void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x09; auto constexpr BUTTON_RELEASED = 0x09;
auto constexpr BUTTON_LEFT = 0x01;
auto constexpr BUTTON_RIGHT = 0x03;
display_cursor = true; display_cursor = true;
auto release = packet->action == BUTTON_RELEASED;
auto button = util::endian::big(packet->button); auto button = util::endian::big(packet->button);
if(button > 0 && button < mouse_press.size()) { if(button > 0 && button < mouse_press.size()) {
mouse_press[button] = packet->action != BUTTON_RELEASED; if(mouse_press[button] != release) {
} // button state is already what we want
return;
}
platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED); mouse_press[button] = !release;
}
///////////////////////////////////
/*/
* When Moonlight sends mouse input through absolute coordinates,
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
* As a result, Sunshine will left click on hyperlinks in the browser before right clicking
*
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
* absolute mouse coordinates have been send.
*
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
*
* input->mouse_left_button_timeout can only be nullptr
* when the last mouse coordinates were absolute
/*/
if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
auto f = [=]() {
auto left_released = mouse_press[BUTTON_LEFT];
if(left_released) {
// Already released left button
return;
}
platf::button_mouse(platf_input, BUTTON_LEFT, release);
mouse_press[BUTTON_LEFT] = false;
input->mouse_left_button_timeout = nullptr;
};
input->mouse_left_button_timeout = task_pool.pushDelayed(std::move(f), 10ms).task_id;
return;
}
if(
button == BUTTON_RIGHT && !release &&
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
mouse_press[BUTTON_RIGHT] = false;
return;
}
///////////////////////////////////
platf::button_mouse(platf_input, button, release);
} }
void repeat_key(short key_code) { void repeat_key(short key_code) {
@ -193,6 +317,20 @@ void repeat_key(short key_code) {
task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id; task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id;
} }
short map_keycode(short keycode) {
keycode &= 0x00FF;
switch(keycode) {
case 0x10:
return 0xA0;
case 0x11:
return 0xA2;
case 0x12:
return 0xA4;
}
return keycode;
}
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) { void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x04; auto constexpr BUTTON_RELEASED = 0x04;
@ -220,18 +358,19 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
} }
pressed = !release; pressed = !release;
platf::keyboard(platf_input, packet->keyCode & 0x00FF, release);
platf::keyboard(platf_input, map_keycode(packet->keyCode), release);
} }
void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) { void passthrough(PNV_SCROLL_PACKET packet) {
display_cursor = true; display_cursor = true;
platf::scroll(input, util::endian::big(packet->scrollAmt1)); platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
} }
int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) { int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) {
auto xorGamepadMask = old_state ^ new_state; auto xorGamepadMask = old_state ^ new_state;
if (!xorGamepadMask) { if(!xorGamepadMask) {
return 0; return 0;
} }
@ -240,7 +379,7 @@ int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std
auto &gamepad = gamepads[x]; auto &gamepad = gamepads[x];
if((old_state >> x) & 1) { if((old_state >> x) & 1) {
if (gamepad.id < 0) { if(gamepad.id < 0) {
return -1; return -1;
} }
@ -300,7 +439,7 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
display_cursor = false; display_cursor = false;
std::uint16_t bf = packet->buttonFlags; std::uint16_t bf = packet->buttonFlags;
platf::gamepad_state_t gamepad_state{ platf::gamepad_state_t gamepad_state {
bf, bf,
packet->leftTrigger, packet->leftTrigger,
packet->rightTrigger, packet->rightTrigger,
@ -312,30 +451,30 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
auto bf_new = gamepad_state.buttonFlags; auto bf_new = gamepad_state.buttonFlags;
switch(gamepad.back_button_state) { switch(gamepad.back_button_state) {
case button_state_e::UP: case button_state_e::UP:
if(!(platf::BACK & bf_new)) { if(!(platf::BACK & bf_new)) {
gamepad.back_button_state = button_state_e::NONE; gamepad.back_button_state = button_state_e::NONE;
} }
gamepad_state.buttonFlags &= ~platf::BACK; gamepad_state.buttonFlags &= ~platf::BACK;
break; break;
case button_state_e::DOWN: case button_state_e::DOWN:
if(platf::BACK & bf_new) { if(platf::BACK & bf_new) {
gamepad.back_button_state = button_state_e::NONE; gamepad.back_button_state = button_state_e::NONE;
} }
gamepad_state.buttonFlags |= platf::BACK; gamepad_state.buttonFlags |= platf::BACK;
break; break;
case button_state_e::NONE: case button_state_e::NONE:
break; break;
} }
bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags; bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags;
bf_new = gamepad_state.buttonFlags; bf_new = gamepad_state.buttonFlags;
if (platf::BACK & bf) { if(platf::BACK & bf) {
if (platf::BACK & bf_new) { if(platf::BACK & bf_new) {
// Don't emulate home button if timeout < 0 // Don't emulate home button if timeout < 0
if(config::input.back_button_timeout >= 0ms) { if(config::input.back_button_timeout >= 0ms) {
gamepad.back_timeout_id = task_pool.pushDelayed([input, controller=packet->controllerNumber]() { auto f = [input, controller = packet->controllerNumber]() {
auto &gamepad = input->gamepads[controller]; auto &gamepad = input->gamepads[controller];
auto &state = gamepad.gamepad_state; auto &state = gamepad.gamepad_state;
@ -354,10 +493,12 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
platf::gamepad(platf_input, gamepad.id, state); platf::gamepad(platf_input, gamepad.id, state);
gamepad.back_timeout_id = nullptr; gamepad.back_timeout_id = nullptr;
}, config::input.back_button_timeout).task_id; };
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
} }
} }
else if (gamepad.back_timeout_id) { else if(gamepad.back_timeout_id) {
task_pool.cancel(gamepad.back_timeout_id); task_pool.cancel(gamepad.back_timeout_id);
gamepad.back_timeout_id = nullptr; gamepad.back_timeout_id = nullptr;
} }
@ -371,30 +512,32 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) { void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
void *payload = input_data.data(); void *payload = input_data.data();
int input_type = util::endian::big(*(int*)payload); int input_type = util::endian::big(*(int *)payload);
switch(input_type) { switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE: case PACKET_TYPE_REL_MOUSE_MOVE:
passthrough(platf_input, (PNV_MOUSE_MOVE_PACKET)payload); passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
break; break;
case PACKET_TYPE_MOUSE_BUTTON: case PACKET_TYPE_ABS_MOUSE_MOVE:
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload); passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
break; break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD: case PACKET_TYPE_MOUSE_BUTTON:
{ passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
char *tmp_input = (char*)payload + 4; break;
if(tmp_input[0] == 0x0A) { case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
passthrough(platf_input, (PNV_SCROLL_PACKET)payload); char *tmp_input = (char *)payload + 4;
} if(tmp_input[0] == 0x0A) {
else { passthrough((PNV_SCROLL_PACKET)payload);
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
}
break;
} }
case PACKET_TYPE_MULTI_CONTROLLER: else {
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload); passthrough(input, (PNV_KEYBOARD_PACKET)payload);
break; }
break;
}
case PACKET_TYPE_MULTI_CONTROLLER:
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload);
break;
} }
} }
@ -402,19 +545,40 @@ void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&in
task_pool.push(passthrough_helper, input, util::cmove(input_data)); task_pool.push(passthrough_helper, input, util::cmove(input_data));
} }
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(task_id);
task_pool.cancel(input->mouse_left_button_timeout);
// Ensure input is synchronous, by using the task_pool
task_pool.push([]() {
for(int x = 0; x < mouse_press.size(); ++x) {
if(mouse_press[x]) {
platf::button_mouse(platf_input, x, true);
mouse_press[x] = false;
}
}
for(auto &kp : key_press) {
platf::keyboard(platf_input, kp.first & 0x00FF, true);
key_press[kp.first] = false;
}
});
}
void init() { void init() {
platf_input = platf::input(); platf_input = platf::input();
} }
std::shared_ptr<input_t> alloc() { std::shared_ptr<input_t> alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(); 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 // Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() { task_pool.pushDelayed([]() {
platf::move_mouse(platf_input, 1, 1); platf::move_mouse(platf_input, 1, 1);
platf::move_mouse(platf_input, -1, -1); platf::move_mouse(platf_input, -1, -1);
}, 100ms); },
100ms);
return input; return input;
} }
} } // namespace input

View file

@ -5,17 +5,28 @@
#ifndef SUNSHINE_INPUT_H #ifndef SUNSHINE_INPUT_H
#define SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H
#include "platform/common.h"
#include "thread_safe.h"
namespace input { namespace input {
struct input_t; struct input_t;
void print(void *input); void print(void *input);
void reset(std::shared_ptr<input_t> &input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data); void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
void init(); void init();
std::shared_ptr<input_t> alloc(); std::shared_ptr<input_t> alloc(safe::mail_t mail);
}
struct touch_port_t : public platf::touch_port_t {
int env_width, env_height;
// inverse of scalar used for aspect ratio
float scalar_inv;
};
} // namespace input
#endif //SUNSHINE_INPUT_H #endif //SUNSHINE_INPUT_H

View file

@ -4,30 +4,36 @@
#include "process.h" #include "process.h"
#include <thread>
#include <iostream>
#include <csignal> #include <csignal>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <thread>
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/attributes/clock.hpp> #include <boost/log/attributes/clock.hpp>
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include "video.h"
#include "input.h"
#include "nvhttp.h"
#include "rtsp.h"
#include "config.h" #include "config.h"
#include "thread_pool.h" #include "confighttp.h"
#include "httpcommon.h"
#include "main.h"
#include "nvhttp.h"
#include "publish.h" #include "publish.h"
#include "rtsp.h"
#include "thread_pool.h"
#include "video.h"
#include "platform/common.h" #include "platform/common.h"
extern "C" { extern "C" {
#include <rs.h>
#include <libavutil/log.h> #include <libavutil/log.h>
#include <rs.h>
} }
safe::mail_t mail::man;
using namespace std::literals; using namespace std::literals;
namespace bl = boost::log; namespace bl = boost::log;
@ -50,6 +56,28 @@ struct NoDelete {
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
void print_help(const char *name) {
std::cout
<< "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
<< " --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
<< " -2 | Force replacement of headers in video stream" << std::endl;
}
namespace help {
int entry(const char *name, int argc, char *argv[]) {
print_help(name);
return 0;
}
} // namespace help
void log_flush() { void log_flush() {
sink->flush(); sink->flush();
} }
@ -66,14 +94,37 @@ void on_signal(int sig, FN &&fn) {
std::signal(sig, on_signal_forwarder); std::signal(sig, on_signal_forwarder);
} }
namespace gen_creds {
int entry(const char *name, int argc, char *argv[]) {
if(argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
print_help(name);
return 0;
}
http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]);
return 0;
}
} // namespace gen_creds
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{ "creds"sv, gen_creds::entry },
{ "help"sv, help::entry }
};
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
mail::man = std::make_shared<safe::mail_raw_t>();
if(config::parse(argc, argv)) { if(config::parse(argc, argv)) {
return 0; return 0;
} }
if(config::sunshine.min_log_level >= 2) { if(config::sunshine.min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET); av_log_set_level(AV_LOG_QUIET);
} }
else {
av_log_set_level(AV_LOG_DEBUG);
}
sink = boost::make_shared<text_sink>(); sink = boost::make_shared<text_sink>();
@ -81,31 +132,31 @@ int main(int argc, char *argv[]) {
sink->locked_backend()->add_stream(stream); sink->locked_backend()->add_stream(stream);
sink->set_filter(severity >= config::sunshine.min_log_level); sink->set_filter(severity >= config::sunshine.min_log_level);
sink->set_formatter([message="Message"s, severity="Severity"s](const bl::record_view &view, bl::formatting_ostream &os) { sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
constexpr int DATE_BUFFER_SIZE = 21 +2 +1; // Full string plus ": \0" constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0"
auto log_level = view.attribute_values()[severity].extract<int>().get(); auto log_level = view.attribute_values()[severity].extract<int>().get();
std::string_view log_type; std::string_view log_type;
switch(log_level) { switch(log_level) {
case 0: case 0:
log_type = "Verbose: "sv; log_type = "Verbose: "sv;
break; break;
case 1: case 1:
log_type = "Debug: "sv; log_type = "Debug: "sv;
break; break;
case 2: case 2:
log_type = "Info: "sv; log_type = "Info: "sv;
break; break;
case 3: case 3:
log_type = "Warning: "sv; log_type = "Warning: "sv;
break; break;
case 4: case 4:
log_type = "Error: "sv; log_type = "Error: "sv;
break; break;
case 5: case 5:
log_type = "Fatal: "sv; log_type = "Fatal: "sv;
break; break;
}; };
char _date[DATE_BUFFER_SIZE]; char _date[DATE_BUFFER_SIZE];
@ -118,40 +169,94 @@ int main(int argc, char *argv[]) {
bl::core::get()->add_sink(sink); bl::core::get()->add_sink(sink);
auto fg = util::fail_guard(log_flush); auto fg = util::fail_guard(log_flush);
if(!config::sunshine.cmd.name.empty()) {
auto fn = cmd_to_func.find(config::sunshine.cmd.name);
if(fn == std::end(cmd_to_func)) {
BOOST_LOG(fatal) << "Unknown command: "sv << config::sunshine.cmd.name;
BOOST_LOG(info) << "Possible commands:"sv;
for(auto &[key, _] : cmd_to_func) {
BOOST_LOG(info) << '\t' << key;
}
return 7;
}
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
}
// Create signal handler after logging has been initialized // Create signal handler after logging has been initialized
auto shutdown_event = std::make_shared<safe::event_t<bool>>(); auto shutdown_event = mail::man->event<bool>(mail::shutdown);
on_signal(SIGINT, [shutdown_event]() { on_signal(SIGINT, [shutdown_event]() {
BOOST_LOG(info) << "Interrupt handler called"sv; BOOST_LOG(info) << "Interrupt handler called"sv;
shutdown_event->raise(true); shutdown_event->raise(true);
}); });
auto proc_opt = proc::parse(config::stream.file_apps); proc::refresh(config::stream.file_apps);
if(!proc_opt) {
return 7;
}
{
proc::ctx_t ctx;
ctx.name = "Desktop"s;
proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx));
}
proc::proc = std::move(*proc_opt);
auto deinit_guard = platf::init(); auto deinit_guard = platf::init();
input::init(); if(!deinit_guard) {
return 4;
}
reed_solomon_init(); reed_solomon_init();
input::init();
if(video::init()) { if(video::init()) {
return 2; return 2;
} }
if(http::init()) {
return 3;
}
//FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
if(shutdown_event->peek()) {
return 0;
}
task_pool.start(1); task_pool.start(1);
std::thread httpThread { nvhttp::start, shutdown_event }; std::thread publishThread { publish::start };
std::thread publishThread { publish::start, shutdown_event }; std::thread httpThread { nvhttp::start };
stream::rtpThread(shutdown_event); std::thread configThread { confighttp::start };
stream::rtpThread();
publishThread.join();
httpThread.join(); httpThread.join();
configThread.join();
task_pool.stop();
task_pool.join();
return 0; return 0;
} }
std::string read_file(const char *path) {
if(!std::filesystem::exists(path)) {
return {};
}
std::ifstream in(path);
std::string input;
std::string base64_cert;
while(!in.eof()) {
std::getline(in, input);
base64_cert += input + '\n';
}
return base64_cert;
}
int write_file(const char *path, const std::string_view &contents) {
std::ofstream out(path);
if(!out.is_open()) {
return -1;
}
out << contents;
return 0;
}

View file

@ -5,8 +5,12 @@
#ifndef SUNSHINE_MAIN_H #ifndef SUNSHINE_MAIN_H
#define SUNSHINE_MAIN_H #define SUNSHINE_MAIN_H
#include <boost/log/common.hpp> #include <string_view>
#include "thread_pool.h" #include "thread_pool.h"
#include "thread_safe.h"
#include <boost/log/common.hpp>
extern util::ThreadPool task_pool; extern util::ThreadPool task_pool;
extern bool display_cursor; extern bool display_cursor;
@ -19,4 +23,30 @@ extern boost::log::sources::severity_logger<int> error;
extern boost::log::sources::severity_logger<int> fatal; extern boost::log::sources::severity_logger<int> fatal;
void log_flush(); void log_flush();
void print_help(const char *name);
std::string read_file(const char *path);
int write_file(const char *path, const std::string_view &contents);
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 #endif //SUNSHINE_MAIN_H

View file

@ -11,23 +11,24 @@ template<class T>
class MoveByCopy { class MoveByCopy {
public: public:
typedef T move_type; typedef T move_type;
private: private:
move_type _to_move; move_type _to_move;
public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { } public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {}
MoveByCopy(MoveByCopy &&other) = default; MoveByCopy(MoveByCopy &&other) = default;
MoveByCopy(const MoveByCopy &other) { MoveByCopy(const MoveByCopy &other) {
*this = other; *this = other;
} }
MoveByCopy& operator=(MoveByCopy &&other) = default; MoveByCopy &operator=(MoveByCopy &&other) = default;
MoveByCopy& operator=(const MoveByCopy &other) { MoveByCopy &operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy&>(other)._to_move); this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this; return *this;
} }
@ -44,7 +45,7 @@ MoveByCopy<T> cmove(T &movable) {
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template<class T> template<class T>
MoveByCopy<T> const_cmove(const T &movable) { MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T&>(movable))); return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
}
} }
} // namespace util
#endif #endif

View file

@ -2,9 +2,9 @@
// Created by loki on 12/27/19. // Created by loki on 12/27/19.
// //
#include <algorithm>
#include "network.h" #include "network.h"
#include "utility.h" #include "utility.h"
#include <algorithm>
namespace net { namespace net {
using namespace std::literals; using namespace std::literals;
@ -21,17 +21,17 @@ std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
}; };
std::uint32_t ip(const std::string_view &ip_str) { std::uint32_t ip(const std::string_view &ip_str) {
auto begin = std::begin(ip_str); auto begin = std::begin(ip_str);
auto end = std::end(ip_str); auto end = std::end(ip_str);
auto temp_end = std::find(begin, end, '.'); auto temp_end = std::find(begin, end, '.');
std::uint32_t ip = 0; std::uint32_t ip = 0;
auto shift = 24; auto shift = 24;
while(temp_end != end) { while(temp_end != end) {
ip += (util::from_chars(begin, temp_end) << shift); ip += (util::from_chars(begin, temp_end) << shift);
shift -= 8; shift -= 8;
begin = temp_end + 1; begin = temp_end + 1;
temp_end = std::find(begin, end, '.'); temp_end = std::find(begin, end, '.');
} }
@ -43,7 +43,7 @@ std::uint32_t ip(const std::string_view &ip_str) {
// In the format "xxx.xxx.xxx.xxx/x" // In the format "xxx.xxx.xxx.xxx/x"
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip_str) { std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip_str) {
auto begin = std::begin(ip_str); auto begin = std::begin(ip_str);
auto end = std::find(begin, std::end(ip_str), '/'); auto end = std::find(begin, std::end(ip_str), '/');
auto addr = ip({ begin, (std::size_t)(end - begin) }); auto addr = ip({ begin, (std::size_t)(end - begin) });
@ -82,12 +82,12 @@ net_e from_address(const std::string_view &view) {
std::string_view to_enum_string(net_e net) { std::string_view to_enum_string(net_e net) {
switch(net) { switch(net) {
case PC: case PC:
return "pc"sv; return "pc"sv;
case LAN: case LAN:
return "lan"sv; return "lan"sv;
case WAN: case WAN:
return "wan"sv; return "wan"sv;
} }
// avoid warning // avoid warning
@ -112,4 +112,4 @@ void free_host(ENetHost *host) {
enet_host_destroy(host); enet_host_destroy(host);
} }
} } // namespace net

View file

@ -14,8 +14,8 @@
namespace net { namespace net {
void free_host(ENetHost *host); void free_host(ENetHost *host);
using host_t = util::safe_ptr<ENetHost, free_host>; using host_t = util::safe_ptr<ENetHost, free_host>;
using peer_t = ENetPeer*; using peer_t = ENetPeer *;
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>; using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
enum net_e : int { enum net_e : int {
@ -30,6 +30,6 @@ std::string_view to_enum_string(net_e net);
net_e from_address(const std::string_view &view); net_e from_address(const std::string_view &view);
host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port); host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port);
} } // namespace net
#endif //SUNSHINE_NETWORK_H #endif //SUNSHINE_NETWORK_H

View file

@ -2,13 +2,15 @@
// Created by loki on 6/3/19. // Created by loki on 6/3/19.
// //
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h" #include "process.h"
#include <filesystem> #include <filesystem>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/asio/ssl/context.hpp> #include <boost/asio/ssl/context.hpp>
@ -16,16 +18,17 @@
#include <Simple-Web-Server/server_https.hpp> #include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context_base.hpp> #include <boost/asio/ssl/context_base.hpp>
#include "config.h" #include "config.h"
#include "utility.h"
#include "rtsp.h"
#include "crypto.h" #include "crypto.h"
#include "httpcommon.h"
#include "main.h"
#include "network.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h" #include "platform/common.h"
#include "network.h" #include "rtsp.h"
#include "utility.h"
#include "uuid.h" #include "uuid.h"
#include "main.h"
namespace nvhttp { namespace nvhttp {
using namespace std::literals; using namespace std::literals;
@ -36,9 +39,6 @@ constexpr auto GFE_VERSION = "3.12.0.1";
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
std::string read_file(const char *path);
int write_file(const char *path, const std::string_view &contents);
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>; using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
using http_server_t = SimpleWeb::Server<SimpleWeb::HTTP>; using http_server_t = SimpleWeb::Server<SimpleWeb::HTTP>;
@ -67,8 +67,8 @@ struct pair_session_t {
struct { struct {
util::Either< util::Either<
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>, std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>,
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response> std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>>
> response; response;
std::string salt; std::string salt;
} async_insert_pin; } async_insert_pin;
}; };
@ -76,14 +76,12 @@ struct pair_session_t {
// uniqueID, session // uniqueID, session
std::unordered_map<std::string, pair_session_t> map_id_sess; std::unordered_map<std::string, pair_session_t> map_id_sess;
std::unordered_map<std::string, client_t> map_id_client; std::unordered_map<std::string, client_t> map_id_client;
std::string unique_id;
net::net_e origin_pin_allowed;
using args_t = SimpleWeb::CaseInsensitiveMultimap; using args_t = SimpleWeb::CaseInsensitiveMultimap;
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>; using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>; using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>; using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>; using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>;
enum class op_e { enum class op_e {
ADD, ADD,
@ -93,9 +91,21 @@ enum class op_e {
void save_state() { void save_state() {
pt::ptree root; pt::ptree root;
root.put("root.uniqueid", unique_id); if(fs::exists(config::nvhttp.file_state)) {
try {
pt::read_json(config::nvhttp.file_state, root);
}
catch(std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
}
root.erase("root"s);
root.put("root.uniqueid", http::unique_id);
auto &nodes = root.add_child("root.devices", pt::ptree {}); auto &nodes = root.add_child("root.devices", pt::ptree {});
for(auto &[_,client] : map_id_client) { for(auto &[_, client] : map_id_client) {
pt::ptree node; pt::ptree node;
node.put("uniqueid"s, client.uniqueID); node.put("uniqueid"s, client.uniqueID);
@ -111,31 +121,44 @@ void save_state() {
nodes.push_back(std::make_pair(""s, node)); nodes.push_back(std::make_pair(""s, node));
} }
pt::write_json(config::nvhttp.file_state, root); try {
pt::write_json(config::nvhttp.file_state, root);
}
catch(std::exception &e) {
BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
} }
void load_state() { void load_state() {
auto file_state = fs::current_path() / config::nvhttp.file_state; if(!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "DOENST EXIST"sv;
if(!fs::exists(file_state)) { http::unique_id = util::uuid_t::generate().string();
unique_id = util::uuid_t::generate().string();
return; return;
} }
pt::ptree root; pt::ptree root;
try { try {
pt::read_json(config::nvhttp.file_state, root); pt::read_json(config::nvhttp.file_state, root);
} catch (std::exception &e) { }
BOOST_LOG(warning) << e.what(); catch(std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
unique_id = root.get<std::string>("root.uniqueid"); auto unique_id_p = root.get_optional<std::string>("root.uniqueid");
if(!unique_id_p) {
// This file doesn't contain moonlight credentials
http::unique_id = util::uuid_t::generate().string();
return;
}
http::unique_id = std::move(*unique_id_p);
auto device_nodes = root.get_child("root.devices"); auto device_nodes = root.get_child("root.devices");
for(auto &[_,device_node] : device_nodes) { for(auto &[_, device_node] : device_nodes) {
auto uniqID = device_node.get<std::string>("uniqueid"); auto uniqID = device_node.get<std::string>("uniqueid");
auto &client = map_id_client.emplace(uniqID, client_t {}).first->second; auto &client = map_id_client.emplace(uniqID, client_t {}).first->second;
client.uniqueID = uniqID; client.uniqueID = uniqID;
@ -148,16 +171,14 @@ void load_state() {
void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) { void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) {
switch(op) { switch(op) {
case op_e::ADD: case op_e::ADD: {
{ auto &client = map_id_client[uniqueID];
auto &client = map_id_client[uniqueID]; client.certs.emplace_back(std::move(cert));
client.certs.emplace_back(std::move(cert)); client.uniqueID = uniqueID;
client.uniqueID = uniqueID; } break;
} case op_e::REMOVE:
break; map_id_client.erase(uniqueID);
case op_e::REMOVE: break;
map_id_client.erase(uniqueID);
break;
} }
if(!config::sunshine.flags[config::flag::FRESH_STATE]) { if(!config::sunshine.flags[config::flag::FRESH_STATE]) {
@ -165,6 +186,20 @@ void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op)
} }
} }
stream::launch_session_t make_launch_session(bool host_audio, const args_t &args) {
stream::launch_session_t launch_session;
launch_session.host_audio = host_audio;
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
auto prepend_iv_p = (uint8_t *)&prepend_iv;
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv));
std::fill(next, std::end(launch_session.iv), 0);
return launch_session;
}
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) { void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if(sess.async_insert_pin.salt.size() < 32) { if(sess.async_insert_pin.salt.size() < 32) {
tree.put("root.paired", 0); tree.put("root.paired", 0);
@ -173,10 +208,10 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin
} }
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 }; std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true); auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
auto key = crypto::gen_aes_key(*salt, pin); auto key = crypto::gen_aes_key(*salt, pin);
sess.cipher_key = std::make_unique<crypto::aes_t>(key); sess.cipher_key = std::make_unique<crypto::aes_t>(key);
tree.put("root.paired", 1); tree.put("root.paired", 1);
@ -195,7 +230,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar
sess.clienthash = std::move(decrypted); sess.clienthash = std::move(decrypted);
auto serversecret = sess.serversecret; auto serversecret = sess.serversecret;
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret); auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign)); serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign));
@ -213,14 +248,14 @@ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args)
std::vector<uint8_t> decrypted; std::vector<uint8_t> decrypted;
cipher.decrypt(challenge, decrypted); cipher.decrypt(challenge, decrypted);
auto x509 = crypto::x509(conf_intern.servercert); auto x509 = crypto::x509(conf_intern.servercert);
auto sign = crypto::signature(x509); auto sign = crypto::signature(x509);
auto serversecret = crypto::rand(16); auto serversecret = crypto::rand(16);
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign)); decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret)); decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
auto hash = crypto::hash({ (char*)decrypted.data(), decrypted.size() }); auto hash = crypto::hash({ (char *)decrypted.data(), decrypted.size() });
auto serverchallenge = crypto::rand(16); auto serverchallenge = crypto::rand(16);
std::string plaintext; std::string plaintext;
@ -250,7 +285,7 @@ void clientpairingsecret(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cer
assert((secret.size() + sign.size()) == pairingsecret.size()); assert((secret.size() + sign.size()) == pairingsecret.size());
auto x509 = crypto::x509(client.cert); auto x509 = crypto::x509(client.cert);
auto x509_sign = crypto::signature(x509); auto x509_sign = crypto::signature(x509);
std::string data; std::string data;
@ -304,8 +339,8 @@ template<class T>
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) { void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string; BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method; BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
for(auto &[name, val] : request->header) { for(auto &[name, val] : request->header) {
BOOST_LOG(debug) << name << " -- " << val; BOOST_LOG(debug) << name << " -- " << val;
@ -332,33 +367,44 @@ void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> resp
pt::write_xml(data, tree); pt::write_xml(data, tree);
response->write(data.str()); 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();
} }
template<class T> template<class T>
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) { void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
pt::ptree tree;
auto args = request->parse_query_string(); auto args = request->parse_query_string();
if(args.find("uniqueid"s) == std::end(args)) {
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto uniqID { std::move(args.at("uniqueid"s)) }; auto uniqID { std::move(args.at("uniqueid"s)) };
auto sess_it = map_id_sess.find(uniqID); auto sess_it = map_id_sess.find(uniqID);
pt::ptree tree;
args_t::const_iterator it; args_t::const_iterator it;
if(it = args.find("phrase"); it != std::end(args)) { if(it = args.find("phrase"); it != std::end(args)) {
if(it->second == "getservercert"sv) { if(it->second == "getservercert"sv) {
pair_session_t sess; pair_session_t sess;
sess.client.uniqueID = std::move(uniqID); sess.client.uniqueID = std::move(uniqID);
sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true); sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true);
BOOST_LOG(debug) << sess.client.cert; BOOST_LOG(debug) << sess.client.cert;
auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first;
ptr->second.async_insert_pin.salt = std::move(args.at("salt"s)); ptr->second.async_insert_pin.salt = std::move(args.at("salt"s));
if(config::sunshine.flags[config::flag::PIN_STDIN]) { 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]) {
std::string pin; std::string pin;
std::cout << "Please insert pin: "sv; std::cout << "Please insert pin: "sv;
@ -396,30 +442,14 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
response->write(data.str()); response->write(data.str());
} }
template<class T> bool pin(std::string pin) {
void pin(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
auto address = request->remote_endpoint_address();
auto ip_type = net::from_address(address);
if(ip_type > origin_pin_allowed) {
BOOST_LOG(info) << '[' << address << "] -- denied"sv;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
return;
}
pt::ptree tree; pt::ptree tree;
if(map_id_sess.empty()) { if(map_id_sess.empty()) {
response->write(SimpleWeb::StatusCode::client_error_im_a_teapot); return false;
return;
} }
auto &sess = std::begin(map_id_sess)->second; auto &sess = std::begin(map_id_sess)->second;
getservercert(sess, tree, request->path_match[1]); getservercert(sess, tree, pin);
// response to the request for pin // response to the request for pin
std::ostringstream data; std::ostringstream data;
@ -429,19 +459,40 @@ void pin(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response,
if(async_response.has_left() && async_response.left()) { if(async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str()); async_response.left()->write(data.str());
} }
else if(async_response.has_right() && async_response.right()){ else if(async_response.has_right() && async_response.right()) {
async_response.right()->write(data.str()); async_response.right()->write(data.str());
} }
else { else {
response->write(SimpleWeb::StatusCode::client_error_im_a_teapot); return false;
return;
} }
// reset async_response // reset async_response
async_response = std::decay_t<decltype(async_response.left())>(); async_response = std::decay_t<decltype(async_response.left())>();
// response to the current request // response to the current request
response->write(SimpleWeb::StatusCode::success_ok); return true;
}
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);
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;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
return;
}
bool pinResponse = pin(request->path_match[1]);
if(pinResponse) {
response->write(SimpleWeb::StatusCode::success_ok);
}
else {
response->write(SimpleWeb::StatusCode::client_error_im_a_teapot);
}
} }
template<class T> template<class T>
@ -449,13 +500,13 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
print_req<T>(request); print_req<T>(request);
int pair_status = 0; int pair_status = 0;
if constexpr (std::is_same_v<SimpleWeb::HTTPS, T>) { if constexpr(std::is_same_v<SimpleWeb::HTTPS, T>) {
auto args = request->parse_query_string(); auto args = request->parse_query_string();
auto clientID = args.find("uniqueid"s); auto clientID = args.find("uniqueid"s);
if(clientID != std::end(args)) { if(clientID != std::end(args)) {
if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) { if(auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) {
pair_status = 1; pair_status = 1;
} }
} }
@ -468,7 +519,7 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
tree.put("root.appversion", VERSION); tree.put("root.appversion", VERSION);
tree.put("root.GfeVersion", GFE_VERSION); tree.put("root.GfeVersion", GFE_VERSION);
tree.put("root.uniqueid", unique_id); tree.put("root.uniqueid", http::unique_id);
tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); 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.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", request->local_endpoint_address()); tree.put("root.LocalIP", request->local_endpoint_address());
@ -487,10 +538,35 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
tree.put("root.ExternalIP", config::nvhttp.external_ip); tree.put("root.ExternalIP", config::nvhttp.external_ip);
} }
pt::ptree display_nodes;
for(auto &resolution : config::nvhttp.resolutions) {
auto pred = [](auto ch) { return ch == ' ' || ch == '\t' || ch == 'x'; };
auto middle = std::find_if(std::begin(resolution), std::end(resolution), pred);
if(middle == std::end(resolution)) {
BOOST_LOG(warning) << resolution << " is not in the proper format for a resolution: WIDTHxHEIGHT"sv;
continue;
}
auto width = util::from_chars(&*std::begin(resolution), &*middle);
auto height = util::from_chars(&*(middle + 1), &*std::end(resolution));
for(auto fps : config::nvhttp.fps) {
pt::ptree display_node;
display_node.put("Width", width);
display_node.put("Height", height);
display_node.put("RefreshRate", fps);
display_nodes.add_child("DisplayMode", display_node);
}
}
if(!config::nvhttp.resolutions.empty()) {
tree.add_child("root.SupportedDisplayMode", display_nodes);
}
auto current_appid = proc::proc.running(); auto current_appid = proc::proc.running();
tree.put("root.PairStatus", pair_status); tree.put("root.PairStatus", pair_status);
tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0); tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0);
tree.put("root.state", current_appid >= 0 ? "_SERVER_BUSY" : "_SERVER_FREE"); tree.put("root.state", current_appid >= 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE");
std::ostringstream data; std::ostringstream data;
@ -501,9 +577,6 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
void applist(resp_https_t response, req_https_t request) { void applist(resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request); print_req<SimpleWeb::HTTPS>(request);
auto args = request->parse_query_string();
auto clientID = args.at("uniqueid"s);
pt::ptree tree; pt::ptree tree;
auto g = util::fail_guard([&]() { auto g = util::fail_guard([&]() {
@ -513,6 +586,15 @@ void applist(resp_https_t response, req_https_t request) {
response->write(data.str()); response->write(data.str());
}); });
auto args = request->parse_query_string();
if(args.find("uniqueid"s) == std::end(args)) {
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto clientID = args.at("uniqueid"s);
auto client = map_id_client.find(clientID); auto client = map_id_client.find(clientID);
if(client == std::end(map_id_client)) { if(client == std::end(map_id_client)) {
tree.put("root.<xmlattr>.status_code", 501); tree.put("root.<xmlattr>.status_code", 501);
@ -536,7 +618,7 @@ void applist(resp_https_t response, req_https_t request) {
} }
} }
void launch(resp_https_t response, req_https_t request) { void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request); print_req<SimpleWeb::HTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -555,7 +637,19 @@ void launch(resp_https_t response, req_https_t request) {
} }
auto args = request->parse_query_string(); auto args = request->parse_query_string();
auto appid = util::from_view(args.at("appid")) -1; if(
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"s) == std::end(args) ||
args.find("appid"s) == std::end(args)) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto appid = util::from_view(args.at("appid")) - 1;
auto current_appid = proc::proc.running(); auto current_appid = proc::proc.running();
if(current_appid != -1) { if(current_appid != -1) {
@ -575,23 +669,14 @@ void launch(resp_https_t response, req_https_t request) {
} }
} }
stream::launch_session_t launch_session; host_audio = util::from_view(args.at("localAudioPlayMode"));
stream::launch_session_raise(make_launch_session(host_audio, args));
auto clientID = args.at("uniqueid"s);
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
auto prepend_iv_p = (uint8_t*)&prepend_iv;
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv));
std::fill(next, std::end(launch_session.iv), 0);
stream::launch_session_raise(launch_session);
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.gamesession", 1); tree.put("root.gamesession", 1);
} }
void resume(resp_https_t response, req_https_t request) { void resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request); print_req<SimpleWeb::HTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -619,18 +704,18 @@ void resume(resp_https_t response, req_https_t request) {
return; return;
} }
stream::launch_session_t launch_session;
auto args = request->parse_query_string(); auto args = request->parse_query_string();
auto clientID = args.at("uniqueid"s); if(
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true); args.find("rikey"s) == std::end(args) ||
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s))); args.find("rikeyid"s) == std::end(args)) {
auto prepend_iv_p = (uint8_t*)&prepend_iv;
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv)); tree.put("root.resume", 0);
std::fill(next, std::end(launch_session.iv), 0); tree.put("root.<xmlattr>.status_code", 400);
stream::launch_session_raise(launch_session); return;
}
stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.resume", 1); tree.put("root.resume", 1);
@ -671,87 +756,16 @@ void appasset(resp_https_t response, req_https_t request) {
response->write(SimpleWeb::StatusCode::success_ok, in); response->write(SimpleWeb::StatusCode::success_ok, in);
} }
int create_creds(const std::string &pkey, const std::string &cert) { void start() {
fs::path pkey_path = pkey; auto shutdown_event = mail::man->event<bool>(mail::shutdown);
fs::path cert_path = cert;
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
auto pkey_dir = pkey_path;
auto cert_dir = cert_path;
pkey_dir.remove_filename();
cert_dir.remove_filename();
std::error_code err_code{};
fs::create_directories(pkey_dir, err_code);
if (err_code) {
BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
return -1;
}
fs::create_directories(cert_dir, err_code);
if (err_code) {
BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
return -1;
}
if (write_file(pkey.c_str(), creds.pkey)) {
BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
return -1;
}
if (write_file(cert.c_str(), creds.x509)) {
BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']';
return -1;
}
fs::permissions(pkey_path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1;
}
fs::permissions(cert_path,
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
}
void start(std::shared_ptr<safe::signal_t> shutdown_event) {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
if(clean_slate) {
unique_id = util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sushine"sv;
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
}
if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
shutdown_event->raise(true);
return;
}
}
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
if(!clean_slate) { if(!clean_slate) {
load_state(); load_state();
} }
conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); conf_intern.pkey = read_file(config::nvhttp.pkey.c_str());
conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); conf_intern.servercert = read_file(config::nvhttp.cert.c_str());
auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls); auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls);
@ -759,7 +773,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem);
crypto::cert_chain_t cert_chain; crypto::cert_chain_t cert_chain;
for(auto &[_,client] : map_id_client) { for(auto &[_, client] : map_id_client) {
for(auto &cert : client.certs) { for(auto &cert : client.certs) {
cert_chain.add(crypto::x509(cert)); cert_chain.add(crypto::x509(cert));
} }
@ -803,45 +817,64 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
return 1; 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 }; 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; http_server_t http_server;
https_server.default_resource = not_found<SimpleWeb::HTTPS>; https_server.default_resource = not_found<SimpleWeb::HTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>; https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTPS>(add_cert, resp, req); }; https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTPS>(add_cert, resp, req); };
https_server.resource["^/applist$"]["GET"] = applist; https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset; https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = launch; https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
https_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTPS>; https_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTPS>;
https_server.resource["^/resume$"]["GET"] = resume; https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
https_server.resource["^/cancel$"]["GET"] = cancel; https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.config.reuse_address = true; https_server.config.reuse_address = true;
https_server.config.address = "0.0.0.0"s; 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.default_resource = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>; http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); }; http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>; http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>;
http_server.config.reuse_address = true; http_server.config.reuse_address = true;
http_server.config.address = "0.0.0.0"s; http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP; http_server.config.port = PORT_HTTP;
try { try {
https_server.bind(); https_server.bind();
http_server.bind(); http_server.bind();
} catch(boost::system::system_error &err) { }
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_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
shutdown_event->raise(true); shutdown_event->raise(true);
return; return;
} }
std::thread ssl { &https_server_t::accept_and_run, &https_server }; auto accept_and_run = [&](auto *http_server) {
std::thread tcp { &http_server_t::accept_and_run, &http_server }; try {
http_server->accept_and_run();
}
catch(boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
if(shutdown_event->peek()) {
return;
}
BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}
};
std::thread ssl { accept_and_run, &https_server };
std::thread tcp { accept_and_run, &http_server };
// Wait for any event // Wait for any event
shutdown_event->view(); shutdown_event->view();
@ -852,31 +885,4 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
ssl.join(); ssl.join();
tcp.join(); tcp.join();
} }
} // namespace nvhttp
int write_file(const char *path, const std::string_view &contents) {
std::ofstream out(path);
if(!out.is_open()) {
return -1;
}
out << contents;
return 0;
}
std::string read_file(const char *path) {
std::ifstream in(path);
std::string input;
std::string base64_cert;
//FIXME: Being unable to read file could result in infinite loop
while(!in.eof()) {
std::getline(in, input);
base64_cert += input + '\n';
}
return base64_cert;
}
}

View file

@ -5,19 +5,18 @@
#ifndef SUNSHINE_NVHTTP_H #ifndef SUNSHINE_NVHTTP_H
#define SUNSHINE_NVHTTP_H #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 <functional>
#include <string> #include <string>
#include "thread_safe.h"
#define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA"
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
namespace nvhttp { namespace nvhttp {
constexpr auto PORT_HTTP = 47989; constexpr auto PORT_HTTP = 47989;
constexpr auto PORT_HTTPS = 47984; constexpr auto PORT_HTTPS = 47984;
void start(std::shared_ptr<safe::signal_t> shutdown_event);
} void start();
bool pin(std::string pin);
} // namespace nvhttp
#endif //SUNSHINE_NVHTTP_H #endif //SUNSHINE_NVHTTP_H

View file

@ -5,11 +5,16 @@
#ifndef SUNSHINE_COMMON_H #ifndef SUNSHINE_COMMON_H
#define SUNSHINE_COMMON_H #define SUNSHINE_COMMON_H
#include <string> #include <bitset>
#include <filesystem>
#include <mutex> #include <mutex>
#include <string>
#include "sunshine/utility.h" #include "sunshine/utility.h"
struct sockaddr; struct sockaddr;
struct AVFrame;
namespace platf { namespace platf {
constexpr auto MAX_GAMEPADS = 32; constexpr auto MAX_GAMEPADS = 32;
@ -29,8 +34,46 @@ constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000; constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000; constexpr std::uint16_t Y = 0x8000;
enum class dev_type_e { namespace speaker {
none, enum speaker_e {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};
constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT
};
constexpr std::uint8_t map_surround51[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
};
constexpr std::uint8_t map_surround71[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
};
} // namespace speaker
enum class mem_type_e {
system,
vaapi,
dxgi, dxgi,
unknown unknown
}; };
@ -43,6 +86,29 @@ enum class pix_fmt_e {
unknown unknown
}; };
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) \
case pix_fmt_e::x: \
return #x##sv
switch(pix_fmt) {
_CONVERT(yuv420p);
_CONVERT(yuv420p10);
_CONVERT(nv12);
_CONVERT(p010);
_CONVERT(unknown);
}
#undef _CONVERT
return "unknown"sv;
}
// Dimensions for touchscreen input
struct touch_port_t {
int offset_x, offset_y;
int width, height;
};
struct gamepad_state_t { struct gamepad_state_t {
std::uint16_t buttonFlags; std::uint16_t buttonFlags;
std::uint8_t lt; std::uint8_t lt;
@ -60,27 +126,49 @@ public:
struct img_t { struct img_t {
public: public:
std::uint8_t *data {}; std::uint8_t *data {};
std::int32_t width {}; std::int32_t width {};
std::int32_t height {}; std::int32_t height {};
std::int32_t pixel_pitch {}; std::int32_t pixel_pitch {};
std::int32_t row_pitch {}; std::int32_t row_pitch {};
img_t() = default; img_t() = default;
img_t(const img_t&) = delete; img_t(const img_t &) = delete;
img_t(img_t&&) = delete; img_t(img_t &&) = delete;
virtual ~img_t() = default; virtual ~img_t() = default;
}; };
struct sink_t {
// Play on host PC
std::string host;
// On Windows, it is not possible to create a virtual sink
// Therefore, it is optional
struct null_t {
std::string stereo;
std::string surround51;
std::string surround71;
};
std::optional<null_t> null;
};
struct hwdevice_t { struct hwdevice_t {
void *data {}; void *data {};
platf::img_t *img {}; AVFrame *frame {};
virtual int convert(platf::img_t &img) { virtual int convert(platf::img_t &img) {
return -1; return -1;
} }
/**
* implementations must take ownership of 'frame'
*/
virtual int set_frame(AVFrame *frame) {
std::abort(); // ^ This function must never be called
return -1;
};
virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {}; virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {};
virtual ~hwdevice_t() = default; virtual ~hwdevice_t() = default;
@ -95,17 +183,22 @@ enum class capture_e : int {
class display_t { class display_t {
public: public:
display_t() noexcept : offset_x { 0 }, offset_y { 0 } {}
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0; virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0; virtual std::shared_ptr<img_t> alloc_img() = 0;
virtual int dummy_img(img_t *img) = 0; virtual int dummy_img(img_t *img) = 0;
virtual std::shared_ptr<hwdevice_t> make_hwdevice(int width, int height, pix_fmt_e pix_fmt) { virtual std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) {
return std::make_shared<hwdevice_t>(); return std::make_shared<hwdevice_t>();
} }
virtual ~display_t() = default; virtual ~display_t() = default;
// 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; int width, height;
}; };
@ -116,21 +209,34 @@ public:
virtual ~mic_t() = default; virtual ~mic_t() = default;
}; };
class audio_control_t {
public:
virtual int set_sink(const std::string &sink) = 0;
void freeInput(void*); virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::optional<sink_t> sink_info() = 0;
virtual ~audio_control_t() = default;
};
void freeInput(void *);
using input_t = util::safe_ptr<void, freeInput>; using input_t = util::safe_ptr<void, freeInput>;
std::filesystem::path appdata();
std::string get_mac_address(const std::string_view &address); std::string get_mac_address(const std::string_view &address);
std::string from_sockaddr(const sockaddr *const); std::string from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const); std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate); std::unique_ptr<audio_control_t> audio_control();
std::shared_ptr<display_t> display(dev_type_e hwdevice_type); std::shared_ptr<display_t> display(mem_type_e hwdevice_type);
input_t input(); input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY); void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void button_mouse(input_t &input, int button, bool release); void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance); void scroll(input_t &input, int distance);
void keyboard(input_t &input, uint16_t modcode, bool release); void keyboard(input_t &input, uint16_t modcode, bool release);
@ -140,6 +246,6 @@ int alloc_gamepad(input_t &input, int nr);
void free_gamepad(input_t &input, int nr); void free_gamepad(input_t &input, int nr);
[[nodiscard]] std::unique_ptr<deinit_t> init(); [[nodiscard]] std::unique_ptr<deinit_t> init();
} } // namespace platf
#endif //SUNSHINE_COMMON_H #endif //SUNSHINE_COMMON_H

View file

@ -0,0 +1,437 @@
//
// Created by loki on 5/16/21.
//
#include <bitset>
#include <sstream>
#include <boost/regex.hpp>
#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include "sunshine/platform/common.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/thread_safe.h"
namespace platf {
using namespace std::literals;
constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER,
PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT,
PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT,
PA_CHANNEL_POSITION_SIDE_RIGHT,
};
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=s16le channels="sv << channels << " channel_map="sv;
std::for_each_n(mapping, channels - 1, [&ss](std::uint8_t pos) {
ss << pa_channel_position_to_string(position_mapping[pos]) << ',';
});
ss << pa_channel_position_to_string(position_mapping[mapping[channels - 1]]);
ss << " sink_properties=device.description="sv << name;
auto result = ss.str();
BOOST_LOG(debug) << "null-sink args: "sv << result;
return result;
}
struct mic_attr_t : public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels };
pa_channel_map pa_map;
pa_map.channels = channels;
std::for_each_n(pa_map.map, pa_map.channels, [mapping](auto &channel) mutable {
channel = position_mapping[*mapping++];
});
int status;
const char *audio_sink = "@DEFAULT_MONITOR@";
if(!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
}
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
"sunshine-record", &ss, &pa_map, nullptr, &status));
if(!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
}
return mic;
}
namespace pa {
template<bool B, class T>
struct add_const_helper;
template<class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
template<class T>
struct add_const_helper<false, T> {
using type = const T *;
};
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template<class T>
void pa_free(T *p) {
pa_xfree(p);
}
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *)userdata;
// For some reason, pulseaudio calls this callback after disconnecting
if(i && eol) {
return;
}
f(ctx, i, eol);
}
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(i);
}
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *)userdata;
f(ctx);
}
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(status ? 0 : 1);
}
class server_t : public audio_control_t {
enum ctx_event_e : int {
ready,
terminated,
failed
};
public:
loop_t loop;
ctx_t ctx;
struct {
std::uint32_t stereo = PA_INVALID_INDEX;
std::uint32_t surround51 = PA_INVALID_INDEX;
std::uint32_t surround71 = PA_INVALID_INDEX;
} index;
std::unique_ptr<safe::event_t<ctx_event_e>> events;
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker;
int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
events_cb = std::make_unique<std::function<void(ctx_t::pointer)>>([this](ctx_t::pointer ctx) {
switch(pa_context_get_state(ctx)) {
case PA_CONTEXT_READY:
events->raise(ready);
break;
case PA_CONTEXT_TERMINATED:
BOOST_LOG(debug) << "Pulseadio context terminated"sv;
events->raise(terminated);
break;
case PA_CONTEXT_FAILED:
BOOST_LOG(debug) << "Pulseadio context failed"sv;
events->raise(failed);
break;
case PA_CONTEXT_CONNECTING:
BOOST_LOG(debug) << "Connecting to pulseaudio"sv;
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
}
});
pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get());
auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr);
if(status) {
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return -1;
}
worker = std::thread {
[](loop_t::pointer loop) {
int retval;
auto status = pa_mainloop_run(loop, &retval);
if(status < 0) {
BOOST_LOG(fatal) << "Couldn't run pulseaudio main loop"sv;
log_flush();
std::abort();
}
},
loop.get()
};
auto event = events->pop();
if(event == failed) {
return -1;
}
return 0;
}
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_load_module(
ctx.get(),
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
};
alarm->wait();
return *alarm->status();
}
int unload_null(std::uint32_t i) {
if(i == PA_INVALID_INDEX) {
return 0;
}
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_unload_module(ctx.get(), i, success_cb, alarm.get())
};
alarm->wait();
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't unload null-sink with index ["sv << i << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
}
std::optional<sink_t> sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71";
auto alarm = safe::make_alarm<int>();
sink_t sink;
// If hardware sink with more channels found, set that as host
int channels = 0;
// Count of all virtual sinks that are created by us
int nullcount = 0;
cb_t<pa_sink_info *> f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) {
if(!sink_info) {
if(!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
}
alarm->ring(0);
return;
}
if(sink_info->active_port != nullptr) {
sink.host = sink_info->name;
channels = sink_info->channel_map.channels;
}
// Ensure Sunshine won't create a sink that already exists.
if(!std::strcmp(sink_info->name, stereo)) {
index.stereo = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module;
++nullcount;
}
};
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
if(!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return std::nullopt;
}
alarm->wait();
if(*alarm->status()) {
return std::nullopt;
}
if(!channels) {
BOOST_LOG(warning) << "Couldn't find an active sink"sv;
}
if(index.stereo == PA_INVALID_INDEX) {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if(index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(index.surround51 == PA_INVALID_INDEX) {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if(index.surround51 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(index.surround71 == PA_INVALID_INDEX) {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if(index.surround71 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
}
return std::make_optional(std::move(sink));
}
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
return ::platf::microphone(mapping, channels, sample_rate);
}
int set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()),
};
if(!op) {
BOOST_LOG(error) << "Couldn't create set default-sink operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
alarm->wait();
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
}
~server_t() override {
unload_null(index.stereo);
unload_null(index.surround51);
unload_null(index.surround71);
if(worker.joinable()) {
pa_context_disconnect(ctx.get());
KITTY_WHILE_LOOP(auto event = events->pop(), event != terminated && event != failed, {
event = events->pop();
})
pa_mainloop_quit(loop.get(), 0);
worker.join();
}
}
};
} // namespace pa
std::unique_ptr<audio_control_t> audio_control() {
auto audio = std::make_unique<pa::server_t>();
if(audio->init()) {
return nullptr;
}
return audio;
}
} // namespace platf

View file

@ -5,46 +5,45 @@
#include "sunshine/platform/common.h" #include "sunshine/platform/common.h"
#include <fstream> #include <fstream>
#include <bitset>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <X11/X.h> #include <X11/X.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h> #include <X11/extensions/Xfixes.h>
#include <xcb/shm.h> #include <X11/extensions/Xrandr.h>
#include <xcb/xfixes.h>
#include <sys/ipc.h> #include <sys/ipc.h>
#include <sys/shm.h> #include <sys/shm.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "sunshine/task_pool.h"
#include "sunshine/config.h" #include "sunshine/config.h"
#include "sunshine/main.h" #include "sunshine/main.h"
#include "sunshine/task_pool.h"
#include "vaapi.h"
using namespace std::literals;
namespace platf { namespace platf {
using namespace std::literals;
void freeImage(XImage *); void freeImage(XImage *);
void freeX(XFixesCursorImage *); void freeX(XFixesCursorImage *);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>; using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>; using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
using xcb_cursor_img = util::c_ptr<xcb_xfixes_get_cursor_image_reply_t>;
using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>; using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
using ximg_t = util::safe_ptr<XImage, freeImage>; using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>; using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>;
using output_info_t = util::safe_ptr<_XRROutputInfo, XRRFreeOutputInfo>;
using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>;
class shm_id_t { class shm_id_t {
public: public:
shm_id_t() : id { -1 } {} shm_id_t() : id { -1 } {}
shm_id_t(int id) : id {id } {} shm_id_t(int id) : id { id } {}
shm_id_t(shm_id_t &&other) noexcept : id(other.id) { shm_id_t(shm_id_t &&other) noexcept : id(other.id) {
other.id = -1; other.id = -1;
} }
@ -60,17 +59,16 @@ public:
class shm_data_t { class shm_data_t {
public: public:
shm_data_t() : data {(void*)-1 } {} shm_data_t() : data { (void *)-1 } {}
shm_data_t(void *data) : data {data } {} shm_data_t(void *data) : data { data } {}
shm_data_t(shm_data_t &&other) noexcept : data(other.data) { shm_data_t(shm_data_t &&other) noexcept : data(other.data) {
other.data = (void*)-1; other.data = (void *)-1;
} }
~shm_data_t() { ~shm_data_t() {
if((std::uintptr_t)data != -1) { if((std::uintptr_t)data != -1) {
shmdt(data); shmdt(data);
data = (void*)-1;
} }
} }
@ -88,7 +86,7 @@ struct shm_img_t : public img_t {
} }
}; };
void blend_cursor(Display *display, img_t &img) { void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
xcursor_t overlay { XFixesGetCursorImage(display) }; xcursor_t overlay { XFixesGetCursorImage(display) };
if(!overlay) { if(!overlay) {
@ -99,86 +97,146 @@ void blend_cursor(Display *display, img_t &img) {
overlay->x -= overlay->xhot; overlay->x -= overlay->xhot;
overlay->y -= overlay->yhot; overlay->y -= overlay->yhot;
overlay->x -= offsetX;
overlay->y -= offsetY;
overlay->x = std::max((short)0, overlay->x); overlay->x = std::max((short)0, overlay->x);
overlay->y = std::max((short)0, overlay->y); overlay->y = std::max((short)0, overlay->y);
auto pixels = (int*)img.data; auto pixels = (int *)img.data;
auto screen_height = img.height; auto screen_height = img.height;
auto screen_width = img.width; auto screen_width = img.width;
auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y)); auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y));
auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x)); auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x));
for(auto y = 0; y < delta_height; ++y) { for(auto y = 0; y < delta_height; ++y) {
auto overlay_begin = &overlay->pixels[y * overlay->width]; auto overlay_begin = &overlay->pixels[y * overlay->width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x]; auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x];
std::for_each(overlay_begin, overlay_end, [&](long pixel) { std::for_each(overlay_begin, overlay_end, [&](long pixel) {
int *pixel_p = (int*)&pixel; int *pixel_p = (int *)&pixel;
auto colors_in = (uint8_t*)pixels_begin; auto colors_in = (uint8_t *)pixels_begin;
auto alpha = (*(uint*)pixel_p) >> 24u; auto alpha = (*(uint *)pixel_p) >> 24u;
if(alpha == 255) { if(alpha == 255) {
*pixels_begin = *pixel_p; *pixels_begin = *pixel_p;
} }
else { else {
auto colors_out = (uint8_t*)pixel_p; auto colors_out = (uint8_t *)pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
} }
++pixels_begin; ++pixels_begin;
}); });
} }
} }
struct x11_attr_t : public display_t { struct x11_attr_t : public display_t {
x11_attr_t() : xdisplay {XOpenDisplay(nullptr) }, xwindow { }, xattr {} { xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
mem_type_e mem_type;
/*
* Last X (NOT the streamed monitor!) size.
* This way we can trigger reinitialization if the dimensions changed while streaming
*/
// int env_width, env_height;
x11_attr_t(mem_type_e mem_type) : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } {
XInitThreads();
}
int init() {
if(!xdisplay) { if(!xdisplay) {
BOOST_LOG(fatal) << "Could not open x11 display"sv; BOOST_LOG(error) << "Could not open X11 display"sv;
log_flush(); return -1;
std::abort();
} }
xwindow = DefaultRootWindow(xdisplay.get()); xwindow = DefaultRootWindow(xdisplay.get());
refresh(); refresh();
width = xattr.width; int streamedMonitor = -1;
height = xattr.height; if(!config::video.output_name.empty()) {
streamedMonitor = (int)util::from_view(config::video.output_name);
}
if(streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv;
screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) };
int output = screenr->noutput;
output_info_t result;
int monitor = 0;
for(int x = 0; x < output; ++x) {
output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
if(out_info && out_info->connection == RR_Connected) {
if(monitor++ == streamedMonitor) {
result = std::move(out_info);
break;
}
}
}
if(!result) {
BOOST_LOG(error) << "Could not stream display number ["sv << streamedMonitor << "], there are only ["sv << monitor << "] displays."sv;
return -1;
}
crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) };
BOOST_LOG(info)
<< "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
width = crt_info->width;
height = crt_info->height;
offset_x = crt_info->x;
offset_y = crt_info->y;
}
else {
width = xattr.width;
height = xattr.height;
}
env_width = xattr.width;
env_height = xattr.height;
return 0;
} }
/**
* Called when the display attributes should change.
*/
void refresh() { void refresh() {
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
} }
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override { capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override {
refresh(); refresh();
if(width != xattr.width || height != xattr.height) { //The whole X server changed, so we gotta reinit everything
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; return capture_e::reinit;
} }
XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
XImage *img { XGetImage( auto img_out = (x11_img_t *)img_out_base;
xdisplay.get(), img_out->width = img->width;
xwindow, img_out->height = img->height;
0, 0, img_out->data = (uint8_t *)img->data;
xattr.width, xattr.height, img_out->row_pitch = img->bytes_per_line;
AllPlanes, ZPixmap)
};
auto img_out = (x11_img_t*)img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t*)img->data;
img_out->row_pitch = img->bytes_per_line;
img_out->pixel_pitch = img->bits_per_pixel / 8; img_out->pixel_pitch = img->bits_per_pixel / 8;
img_out->img.reset(img); img_out->img.reset(img);
if(cursor) { if(cursor) {
blend_cursor(xdisplay.get(), *img_out_base); blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
} }
return capture_e::ok; return capture_e::ok;
@ -188,14 +246,18 @@ struct x11_attr_t : public display_t {
return std::make_shared<x11_img_t>(); return std::make_shared<x11_img_t>();
} }
std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override {
if(mem_type == mem_type_e::vaapi) {
return egl::make_hwdevice(width, height);
}
return std::make_shared<hwdevice_t>();
}
int dummy_img(img_t *img) override { int dummy_img(img_t *img) override {
snapshot(img, 0s, true); snapshot(img, 0s, true);
return 0; return 0;
} }
xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
}; };
struct shm_attr_t : public x11_attr_t { struct shm_attr_t : public x11_attr_t {
@ -209,58 +271,54 @@ struct shm_attr_t : public x11_attr_t {
shm_data_t data; shm_data_t data;
util::TaskPool::task_id_t refresh_task_id; util::TaskPool::task_id_t refresh_task_id;
void delayed_refresh() { void delayed_refresh() {
refresh(); refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
} }
shm_attr_t() : x11_attr_t(), shm_xdisplay {XOpenDisplay(nullptr) } { shm_attr_t(mem_type_e mem_type) : x11_attr_t(mem_type), shm_xdisplay { XOpenDisplay(nullptr) } {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
} }
~shm_attr_t() override { ~shm_attr_t() override {
while(!task_pool.cancel(refresh_task_id)); while(!task_pool.cancel(refresh_task_id))
;
} }
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
if(width != xattr.width || height != xattr.height) { //The whole X server changed, so we gotta reinit everything
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; return capture_e::reinit;
} }
else {
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
auto img_cookie = xcb_shm_get_image_unchecked( xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
xcb.get(), if(!img_reply) {
display->root, BOOST_LOG(error) << "Could not get image reply"sv;
0, 0, return capture_e::reinit;
width, height, }
~0,
XCB_IMAGE_FORMAT_Z_PIXMAP,
seg,
0
);
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; std::copy_n((std::uint8_t *)data.data, frame_size(), img->data);
if(!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv; if(cursor) {
return capture_e::reinit; blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
}
return capture_e::ok;
} }
std::copy_n((std::uint8_t*)data.data, frame_size(), img->data);
if(cursor) {
blend_cursor(shm_xdisplay.get(), *img);
}
return capture_e::ok;
} }
std::shared_ptr<img_t> alloc_img() override { std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<shm_img_t>(); auto img = std::make_shared<shm_img_t>();
img->width = width; img->width = width;
img->height = height; img->height = height;
img->pixel_pitch = 4; img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width; img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch]; img->data = new std::uint8_t[height * img->row_pitch];
return img; return img;
} }
@ -270,6 +328,10 @@ struct shm_attr_t : public x11_attr_t {
} }
int init() { int init() {
if(x11_attr_t::init()) {
return 1;
}
shm_xdisplay.reset(XOpenDisplay(nullptr)); shm_xdisplay.reset(XOpenDisplay(nullptr));
xcb.reset(xcb_connect(nullptr, nullptr)); xcb.reset(xcb_connect(nullptr, nullptr));
if(xcb_connection_has_error(xcb.get())) { if(xcb_connection_has_error(xcb.get())) {
@ -283,8 +345,8 @@ struct shm_attr_t : public x11_attr_t {
} }
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
display = iter.data; display = iter.data;
seg = xcb_generate_id(xcb.get()); seg = xcb_generate_id(xcb.get());
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
if(shm_id.id == -1) { if(shm_id.id == -1) {
@ -295,15 +357,12 @@ struct shm_attr_t : public x11_attr_t {
xcb_shm_attach(xcb.get(), seg, shm_id.id, false); xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
data.data = shmat(shm_id.id, nullptr, 0); data.data = shmat(shm_id.id, nullptr, 0);
if ((uintptr_t)data.data == -1) { if((uintptr_t)data.data == -1) {
BOOST_LOG(error) << "shmat failed"sv; BOOST_LOG(error) << "shmat failed"sv;
return -1; return -1;
} }
width = display->width_in_pixels;
height = display->height_in_pixels;
return 0; return 0;
} }
@ -312,130 +371,32 @@ struct shm_attr_t : public x11_attr_t {
} }
}; };
struct mic_attr_t : public mic_t { std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type) {
pa_sample_spec ss; if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi) {
util::safe_ptr<pa_simple, pa_simple_free> mic; BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {}
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::shared_ptr<display_t> shm_display() {
auto shm = std::make_shared<shm_attr_t>();
if(shm->init()) {
return nullptr; return nullptr;
} }
return shm; // Attempt to use shared memory X11 to avoid copying the frame
} auto shm_disp = std::make_shared<shm_attr_t>(hwdevice_type);
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) { auto status = shm_disp->init();
if(hwdevice_type != platf::dev_type_e::none) { if(status > 0) {
// x11_attr_t::init() failed, don't bother trying again.
return nullptr; return nullptr;
} }
auto shm_disp = shm_display(); if(status == 0) {
return shm_disp;
if(!shm_disp) {
return std::make_shared<x11_attr_t>();
} }
return shm_disp; // Fallback
} auto x11_disp = std::make_shared<x11_attr_t>(hwdevice_type);
if(x11_disp->init()) {
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) { return nullptr;
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
int status;
const char *audio_sink = "@DEFAULT_MONITOR@";
if(!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
} }
mic->mic.reset( return x11_disp;
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, "sunshine-record", &mic->ss, nullptr, nullptr, &status)
);
if(!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
}
return mic;
}
ifaddr_t get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
}
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
std::uint16_t port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
port = ((sockaddr_in6*)ip_addr)->sin6_port;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in*)ip_addr)->sin_port;
}
return { port, std::string { data } };
}
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address");
if(mac_file.good()) {
std::string mac_address;
std::getline(mac_file, mac_address);
return mac_address;
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
} }
void freeImage(XImage *p) { void freeImage(XImage *p) {
@ -444,4 +405,4 @@ void freeImage(XImage *p) {
void freeX(XFixesCursorImage *p) { void freeX(XFixesCursorImage *p) {
XFree(p); XFree(p);
} }
} } // namespace platf

View file

@ -1,16 +1,17 @@
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h> #include <libevdev/libevdev-uinput.h>
#include <libevdev/libevdev.h>
#include <X11/X.h> #include <X11/X.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/XTest.h> #include <X11/extensions/XTest.h>
#include <cmath>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
#include "sunshine/platform/common.h"
#include "sunshine/main.h" #include "sunshine/main.h"
#include "sunshine/platform/common.h"
#include "sunshine/utility.h" #include "sunshine/utility.h"
// Support older versions // Support older versions
@ -24,13 +25,27 @@
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
using evdev_t = util::safe_ptr<libevdev, libevdev_free>; using evdev_t = util::safe_ptr<libevdev, libevdev_free>;
using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>; using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>;
using keyboard_t = util::safe_ptr_v2<Display, int, XCloseDisplay>; using keyboard_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
constexpr touch_port_t target_touch_port {
0, 0,
19200, 12000
};
struct input_raw_t { struct input_raw_t {
public: public:
void clear_touchscreen() {
std::filesystem::path touch_path { "sunshine_touchscreen"sv };
if(std::filesystem::is_symlink(touch_path)) {
std::filesystem::remove(touch_path);
}
touch_input.reset();
}
void clear_mouse() { void clear_mouse() {
std::filesystem::path mouse_path { "sunshine_mouse"sv }; std::filesystem::path mouse_path { "sunshine_mouse"sv };
@ -51,13 +66,11 @@ public:
std::filesystem::remove(gamepad_path); std::filesystem::remove(gamepad_path);
} }
gamepads[nr] = std::make_pair(uinput_t{}, gamepad_state_t {}); gamepads[nr] = std::make_pair(uinput_t {}, gamepad_state_t {});
} }
int create_mouse() { int create_mouse() {
libevdev_uinput *buf {}; int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input);
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
mouse_input.reset(buf);
if(err) { if(err) {
BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err); BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err);
@ -69,13 +82,24 @@ public:
return 0; return 0;
} }
int create_touchscreen() {
int err = libevdev_uinput_create_from_device(touch_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &touch_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Touchscreen: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(touch_input.get()), "sunshine_touchscreen"sv);
return 0;
}
int alloc_gamepad(int nr) { int alloc_gamepad(int nr) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]); TUPLE_2D_REF(input, gamepad_state, gamepads[nr]);
libevdev_uinput *buf; int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
input.reset(buf);
gamepad_state = gamepad_state_t {}; gamepad_state = gamepad_state_t {};
if(err) { if(err) {
@ -96,6 +120,7 @@ public:
} }
void clear() { void clear() {
clear_touchscreen();
clear_mouse(); clear_mouse();
for(int x = 0; x < gamepads.size(); ++x) { for(int x = 0; x < gamepads.size(); ++x) {
clear_gamepad(x); clear_gamepad(x);
@ -106,18 +131,33 @@ public:
clear(); clear();
} }
evdev_t gamepad_dev;
std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads; std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads;
evdev_t mouse_dev;
uinput_t mouse_input; uinput_t mouse_input;
uinput_t touch_input;
evdev_t gamepad_dev;
evdev_t touch_dev;
evdev_t mouse_dev;
keyboard_t keyboard; keyboard_t keyboard;
}; };
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto touchscreen = ((input_raw_t *)input.get())->touch_input.get();
auto scaled_x = (int)std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = (int)std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x);
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchscreen, EV_SYN, SYN_REPORT, 0);
}
void move_mouse(input_t &input, int deltaX, int deltaY) { void move_mouse(input_t &input, int deltaX, int deltaY) {
auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
if(deltaX) { if(deltaX) {
libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX); libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX);
@ -136,26 +176,26 @@ void button_mouse(input_t &input, int button, bool release) {
if(button == 1) { if(button == 1) {
btn_type = BTN_LEFT; btn_type = BTN_LEFT;
scan = 90001; scan = 90001;
} }
else if(button == 2) { else if(button == 2) {
btn_type = BTN_MIDDLE; btn_type = BTN_MIDDLE;
scan = 90003; scan = 90003;
} }
else if(button == 3) { else if(button == 3) {
btn_type = BTN_RIGHT; btn_type = BTN_RIGHT;
scan = 90002; scan = 90002;
} }
else if(button == 4) { else if(button == 4) {
btn_type = BTN_SIDE; btn_type = BTN_SIDE;
scan = 90004; scan = 90004;
} }
else { else {
btn_type = BTN_EXTRA; btn_type = BTN_EXTRA;
scan = 90005; scan = 90005;
} }
auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan); libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan);
libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1); libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
@ -164,7 +204,7 @@ void button_mouse(input_t &input, int button, bool release) {
void scroll(input_t &input, int high_res_distance) { void scroll(input_t &input, int high_res_distance) {
int distance = high_res_distance / 120; int distance = high_res_distance / 120;
auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance);
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
@ -172,7 +212,7 @@ void scroll(input_t &input, int high_res_distance) {
uint16_t keysym(uint16_t modcode) { uint16_t keysym(uint16_t modcode) {
constexpr auto VK_NUMPAD = 0x60; constexpr auto VK_NUMPAD = 0x60;
constexpr auto VK_F1 = 0x70; constexpr auto VK_F1 = 0x70;
if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) { if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) {
return XK_KP_0 + (modcode - VK_NUMPAD); return XK_KP_0 + (modcode - VK_NUMPAD);
@ -184,108 +224,108 @@ uint16_t keysym(uint16_t modcode) {
switch(modcode) { switch(modcode) {
case 0x08: case 0x08:
return XK_BackSpace; return XK_BackSpace;
case 0x09: case 0x09:
return XK_Tab; return XK_Tab;
case 0x0D: case 0x0D:
return XK_Return; return XK_Return;
case 0x13: case 0x13:
return XK_Pause; return XK_Pause;
case 0x14: case 0x14:
return XK_Caps_Lock; return XK_Caps_Lock;
case 0x1B: case 0x1B:
return XK_Escape; return XK_Escape;
case 0x21: case 0x21:
return XK_Page_Up; return XK_Page_Up;
case 0x22: case 0x22:
return XK_Page_Down; return XK_Page_Down;
case 0x23: case 0x23:
return XK_End; return XK_End;
case 0x24: case 0x24:
return XK_Home; return XK_Home;
case 0x25: case 0x25:
return XK_Left; return XK_Left;
case 0x26: case 0x26:
return XK_Up; return XK_Up;
case 0x27: case 0x27:
return XK_Right; return XK_Right;
case 0x28: case 0x28:
return XK_Down; return XK_Down;
case 0x29: case 0x29:
return XK_Select; return XK_Select;
case 0x2B: case 0x2B:
return XK_Execute; return XK_Execute;
case 0x2C: case 0x2C:
return XK_Print; return XK_Print;
case 0x2D: case 0x2D:
return XK_Insert; return XK_Insert;
case 0x2E: case 0x2E:
return XK_Delete; return XK_Delete;
case 0x2F: case 0x2F:
return XK_Help; return XK_Help;
case 0x6A: case 0x6A:
return XK_KP_Multiply; return XK_KP_Multiply;
case 0x6B: case 0x6B:
return XK_KP_Add; return XK_KP_Add;
case 0x6C: case 0x6C:
return XK_KP_Separator; return XK_KP_Separator;
case 0x6D: case 0x6D:
return XK_KP_Subtract; return XK_KP_Subtract;
case 0x6E: case 0x6E:
return XK_KP_Decimal; return XK_KP_Decimal;
case 0x6F: case 0x6F:
return XK_KP_Divide; return XK_KP_Divide;
case 0x90: case 0x90:
return XK_Num_Lock; return XK_Num_Lock;
case 0x91: case 0x91:
return XK_Scroll_Lock; return XK_Scroll_Lock;
case 0xA0: case 0xA0:
return XK_Shift_L; return XK_Shift_L;
case 0xA1: case 0xA1:
return XK_Shift_R; return XK_Shift_R;
case 0xA2: case 0xA2:
return XK_Control_L; return XK_Control_L;
case 0xA3: case 0xA3:
return XK_Control_R; return XK_Control_R;
case 0xA4: case 0xA4:
return XK_Alt_L; return XK_Alt_L;
case 0xA5: /* return XK_Alt_R; */ case 0xA5: /* return XK_Alt_R; */
return XK_Super_L; return XK_Super_L;
case 0x5B: case 0x5B:
return XK_Super_L; return XK_Super_L;
case 0x5C: case 0x5C:
return XK_Super_R; return XK_Super_R;
case 0xBA: case 0xBA:
return XK_semicolon; return XK_semicolon;
case 0xBB: case 0xBB:
return XK_equal; return XK_equal;
case 0xBC: case 0xBC:
return XK_comma; return XK_comma;
case 0xBD: case 0xBD:
return XK_minus; return XK_minus;
case 0xBE: case 0xBE:
return XK_period; return XK_period;
case 0xBF: case 0xBF:
return XK_slash; return XK_slash;
case 0xC0: case 0xC0:
return XK_grave; return XK_grave;
case 0xDB: case 0xDB:
return XK_bracketleft; return XK_bracketleft;
case 0xDC: case 0xDC:
return XK_backslash; return XK_backslash;
case 0xDD: case 0xDD:
return XK_bracketright; return XK_bracketright;
case 0xDE: case 0xDE:
return XK_apostrophe; return XK_apostrophe;
} }
return modcode; return modcode;
} }
void keyboard(input_t &input, uint16_t modcode, bool release) { void keyboard(input_t &input, uint16_t modcode, bool release) {
auto &keyboard = ((input_raw_t*)input.get())->keyboard; auto &keyboard = ((input_raw_t *)input.get())->keyboard;
KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode)); KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode));
if(!kc) { if(!kc) {
return; return;
@ -298,18 +338,18 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
} }
int alloc_gamepad(input_t &input, int nr) { int alloc_gamepad(input_t &input, int nr) {
return ((input_raw_t*)input.get())->alloc_gamepad(nr); return ((input_raw_t *)input.get())->alloc_gamepad(nr);
} }
void free_gamepad(input_t &input, int nr) { void free_gamepad(input_t &input, int nr) {
((input_raw_t*)input.get())->clear_gamepad(nr); ((input_raw_t *)input.get())->clear_gamepad(nr);
} }
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t*)input.get())->gamepads[nr]); TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t *)input.get())->gamepads[nr]);
auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags; auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags;
auto bf_new = gamepad_state.buttonFlags; auto bf_new = gamepad_state.buttonFlags;
if(bf) { if(bf) {
@ -326,17 +366,17 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state); libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state);
} }
if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0); if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0);
if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0); if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0); if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0); if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0); if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0); if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0); if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0); if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0); if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0); if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0); if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
} }
if(gamepad_state_old.lt != gamepad_state.lt) { if(gamepad_state_old.lt != gamepad_state.lt) {
@ -368,7 +408,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
} }
evdev_t mouse() { evdev_t mouse() {
evdev_t dev { libevdev_new() }; evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Mouse"); libevdev_set_uniq(dev.get(), "Sunshine Mouse");
libevdev_set_id_product(dev.get(), 0x4038); libevdev_set_id_product(dev.get(), 0x4038);
@ -409,6 +449,48 @@ evdev_t mouse() {
return dev; return dev;
} }
evdev_t touchscreen() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Touch");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Touchscreen passthrough");
libevdev_enable_property(dev.get(), INPUT_PROP_DIRECT);
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_PEN, nullptr); // Needed to be enabled for BTN_TOOL_FINGER to work.
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_FINGER, nullptr);
input_absinfo absx {
0,
0,
target_touch_port.width,
1,
0,
28
};
input_absinfo absy {
0,
0,
target_touch_port.height,
1,
0,
28
};
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &absx);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &absy);
return dev;
}
evdev_t x360() { evdev_t x360() {
evdev_t dev { libevdev_new() }; evdev_t dev { libevdev_new() };
@ -471,7 +553,7 @@ evdev_t x360() {
input_t input() { input_t input() {
input_t result { new input_raw_t() }; input_t result { new input_raw_t() };
auto &gp = *(input_raw_t*)result.get(); auto &gp = *(input_raw_t *)result.get();
gp.keyboard.reset(XOpenDisplay(nullptr)); gp.keyboard.reset(XOpenDisplay(nullptr));
@ -486,10 +568,11 @@ input_t input() {
// Ensure starting from clean slate // Ensure starting from clean slate
gp.clear(); gp.clear();
gp.mouse_dev = mouse(); gp.touch_dev = touchscreen();
gp.mouse_dev = mouse();
gp.gamepad_dev = x360(); gp.gamepad_dev = x360();
if(gp.create_mouse()) { if(gp.create_mouse() || gp.create_touchscreen()) {
log_flush(); log_flush();
std::abort(); std::abort();
} }
@ -498,9 +581,7 @@ input_t input() {
} }
void freeInput(void *p) { void freeInput(void *p) {
auto *input = (input_raw_t*)p; auto *input = (input_raw_t *)p;
delete input; delete input;
} }
} // namespace platf
std::unique_ptr<deinit_t> init() { return nullptr; }
}

View file

@ -0,0 +1,88 @@
#include "sunshine/platform/common.h"
#include <fstream>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <pwd.h>
#include <unistd.h>
#include "sunshine/main.h"
using namespace std::literals;
namespace fs = std::filesystem;
namespace platf {
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
ifaddr_t get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
}
fs::path appdata() {
const char *homedir;
if((homedir = getenv("HOME")) == nullptr) {
homedir = getpwuid(geteuid())->pw_dir;
}
return fs::path { homedir } / ".config/sunshine"sv;
}
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
std::uint16_t port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *)ip_addr)->sin6_port;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *)ip_addr)->sin_port;
}
return { port, std::string { data } };
}
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address");
if(mac_file.good()) {
std::string mac_address;
std::getline(mac_file, mac_address);
return mac_address;
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
} // namespace platf

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
#ifndef SUNSHINE_DISPLAY_H
#define SUNSHINE_DISPLAY_H
#include "sunshine/platform/common.h"
namespace platf::egl {
std::shared_ptr<hwdevice_t> make_hwdevice(int width, int height);
} // namespace platf::egl
#endif

View file

@ -0,0 +1,164 @@
// ----------------------------------------------------------------------------
// PolicyConfig.h
// Undocumented COM-interface IPolicyConfig.
// Use for set default audio render endpoint
// @author EreTIk
// http://eretik.omegahg.com/
// ----------------------------------------------------------------------------
#pragma once
#ifdef __MINGW32__
#undef DEFINE_GUID
#ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#else
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
#endif
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
//
// interface IPolicyConfig
// {f8679f50-850a-41cf-9c72-430f290290c8}
//
// Query interface:
// CComPtr<IPolicyConfig> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown {
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64);
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64);
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT);
};
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigVistaClient
// {294935CE-F637-4E7C-A41B-AB255460B862}
//
// interface IPolicyConfigVista
// {568b9108-44bf-40b4-9006-86afe5b5a620}
//
// Query interface:
// CComPtr<IPolicyConfigVista> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown {
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT); // not available on Windows 7, use method from IPolicyConfig
};
// ----------------------------------------------------------------------------

File diff suppressed because it is too large Load diff

View file

@ -5,14 +5,14 @@
#ifndef SUNSHINE_DISPLAY_H #ifndef SUNSHINE_DISPLAY_H
#define SUNSHINE_DISPLAY_H #define SUNSHINE_DISPLAY_H
#include <dxgi.h>
#include <d3d11.h> #include <d3d11.h>
#include <d3d11_4.h> #include <d3d11_4.h>
#include <d3dcommon.h> #include <d3dcommon.h>
#include <dxgi.h>
#include <dxgi1_2.h> #include <dxgi1_2.h>
#include "sunshine/utility.h"
#include "sunshine/platform/common.h" #include "sunshine/platform/common.h"
#include "sunshine/utility.h"
namespace platf::dxgi { namespace platf::dxgi {
extern const char *format_str[]; extern const char *format_str[];
@ -32,6 +32,7 @@ using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>; using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>; using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>; using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>; using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>; using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
@ -42,7 +43,7 @@ using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11Vide
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>; using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>; using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>; using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
} } // namespace video
class hwdevice_t; class hwdevice_t;
struct cursor_t { struct cursor_t {
@ -84,6 +85,17 @@ public:
DXGI_FORMAT format; DXGI_FORMAT format;
D3D_FEATURE_LEVEL feature_level; D3D_FEATURE_LEVEL feature_level;
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
} D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
}; };
class display_ram_t : public display_base_t { class display_ram_t : public display_base_t {
@ -106,11 +118,11 @@ public:
std::shared_ptr<img_t> alloc_img() override; std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img_base) override; int dummy_img(img_t *img_base) override;
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, pix_fmt_e pix_fmt) override; std::shared_ptr<platf::hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override;
gpu_cursor_t cursor; gpu_cursor_t cursor;
std::vector<hwdevice_t*> hwdevices; std::vector<hwdevice_t *> hwdevices;
}; };
} } // namespace platf::dxgi
#endif #endif

View file

@ -23,18 +23,18 @@ capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::ch
auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p); auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p);
switch(status) { switch(status) {
case S_OK: case S_OK:
has_frame = true; has_frame = true;
return capture_e::ok; return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT: case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout; return capture_e::timeout;
case WAIT_ABANDONED: case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST: case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED: case DXGI_ERROR_ACCESS_DENIED:
return capture_e::reinit; return capture_e::reinit;
default: default:
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error; return capture_e::error;
} }
} }
@ -52,20 +52,20 @@ capture_e duplication_t::release_frame() {
} }
auto status = dup->ReleaseFrame(); auto status = dup->ReleaseFrame();
switch (status) { switch(status) {
case S_OK: case S_OK:
has_frame = false; has_frame = false;
return capture_e::ok; return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT: case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout; return capture_e::timeout;
case WAIT_ABANDONED: case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST: case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED: case DXGI_ERROR_ACCESS_DENIED:
has_frame = false; has_frame = false;
return capture_e::reinit; return capture_e::reinit;
default: default:
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error; return capture_e::error;
} }
} }
@ -74,7 +74,7 @@ duplication_t::~duplication_t() {
} }
int display_base_t::init() { int display_base_t::init() {
/* Uncomment when use of IDXGIOutput5 is implemented /* Uncomment when use of IDXGIOutput5 is implemented
std::call_once(windows_cpp_once_flag, []() { std::call_once(windows_cpp_once_flag, []() {
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4);
@ -90,16 +90,14 @@ int display_base_t::init() {
FreeLibrary(user32); FreeLibrary(user32);
}); });
*/ */
dxgi::factory1_t::pointer factory_p {};
dxgi::adapter_t::pointer adapter_p {}; // Get rectangle of full desktop for absolute mouse coordinates
dxgi::output_t::pointer output_p {}; env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
dxgi::device_t::pointer device_p {}; env_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
dxgi::device_ctx_t::pointer device_ctx_p {};
HRESULT status; HRESULT status;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
factory.reset(factory_p);
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
return -1; return -1;
@ -108,9 +106,10 @@ int display_base_t::init() {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto adapter_name = converter.from_bytes(config::video.adapter_name); auto adapter_name = converter.from_bytes(config::video.adapter_name);
auto output_name = converter.from_bytes(config::video.output_name); auto output_name = converter.from_bytes(config::video.output_name);
for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { adapter_t::pointer adapter_p;
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
dxgi::adapter_t adapter_tmp { adapter_p }; dxgi::adapter_t adapter_tmp { adapter_p };
DXGI_ADAPTER_DESC1 adapter_desc; DXGI_ADAPTER_DESC1 adapter_desc;
@ -120,8 +119,9 @@ int display_base_t::init() {
continue; continue;
} }
dxgi::output_t::pointer output_p;
for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output_tmp {output_p }; dxgi::output_t output_tmp { output_p };
DXGI_OUTPUT_DESC desc; DXGI_OUTPUT_DESC desc;
output_tmp->GetDesc(&desc); output_tmp->GetDesc(&desc);
@ -133,8 +133,15 @@ int display_base_t::init() {
if(desc.AttachedToDesktop) { if(desc.AttachedToDesktop) {
output = std::move(output_tmp); output = std::move(output_tmp);
width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; offset_x = desc.DesktopCoordinates.left;
height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; 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);
} }
} }
@ -150,8 +157,6 @@ int display_base_t::init() {
} }
D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_1,
@ -161,10 +166,9 @@ int display_base_t::init() {
D3D_FEATURE_LEVEL_9_1 D3D_FEATURE_LEVEL_9_1
}; };
status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); status = adapter->QueryInterface(IID_IDXGIAdapter, (void **)&adapter_p);
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
return -1; return -1;
} }
@ -175,14 +179,12 @@ int display_base_t::init() {
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&device_p, &device,
&feature_level, &feature_level,
&device_ctx_p); &device_ctx);
adapter_p->Release(); adapter_p->Release();
device.reset(device_p);
device_ctx.reset(device_ctx_p);
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']';
@ -202,16 +204,46 @@ int display_base_t::init() {
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl << "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 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 << "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 // Bump up thread priority
{ {
dxgi::dxgi_t::pointer dxgi_p {}; const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); TOKEN_PRIVILEGES tp;
dxgi::dxgi_t dxgi { dxgi_p }; HANDLE token;
LUID val;
if(OpenProcessToken(GetCurrentProcess(), flags, &token) &&
!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = val;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
BOOST_LOG(warning) << "Could not set privilege to increase GPU priority";
}
}
CloseHandle(token);
HMODULE gdi32 = GetModuleHandleA("GDI32");
if(gdi32) {
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if(fn) {
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance.";
}
}
}
dxgi::dxgi_t dxgi;
status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1; return -1;
} }
@ -220,25 +252,24 @@ int display_base_t::init() {
// Try to reduce latency // Try to reduce latency
{ {
dxgi::dxgi1_t::pointer dxgi_p {}; dxgi::dxgi1_t dxgi {};
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
dxgi::dxgi1_t dxgi { dxgi_p };
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1; return -1;
} }
dxgi->SetMaximumFrameLatency(1); status = dxgi->SetMaximumFrameLatency(1);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to set maximum frame latency [0x"sv << util::hex(status).to_string_view() << ']';
}
} }
//FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
//TODO: Use IDXGIOutput5 for improved performance //TODO: Use IDXGIOutput5 for improved performance
{ {
dxgi::output1_t::pointer output1_p {}; dxgi::output1_t output1 {};
status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p); status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
dxgi::output1_t output1 {output1_p };
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
return -1; return -1;
@ -246,10 +277,8 @@ int display_base_t::init() {
// We try this twice, in case we still get an error on reinitialization // We try this twice, in case we still get an error on reinitialization
for(int x = 0; x < 2; ++x) { for(int x = 0; x < 2; ++x) {
dxgi::dup_t::pointer dup_p {}; status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup);
status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p);
if(SUCCEEDED(status)) { if(SUCCEEDED(status)) {
dup.reset(dup_p);
break; break;
} }
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
@ -396,18 +425,18 @@ const char *format_str[] = {
"DXGI_FORMAT_V408" "DXGI_FORMAT_V408"
}; };
} } // namespace platf::dxgi
namespace platf { namespace platf {
std::shared_ptr<display_t> display(dev_type_e hwdevice_type) { std::shared_ptr<display_t> display(mem_type_e hwdevice_type) {
if(hwdevice_type == dev_type_e::dxgi) { if(hwdevice_type == mem_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_vram_t>(); auto disp = std::make_shared<dxgi::display_vram_t>();
if(!disp->init()) { if(!disp->init()) {
return disp; return disp;
} }
} }
else if(hwdevice_type == dev_type_e::none) { else if(hwdevice_type == mem_type_e::system) {
auto disp = std::make_shared<dxgi::display_ram_t>(); auto disp = std::make_shared<dxgi::display_ram_t>();
if(!disp->init()) { if(!disp->init()) {
@ -417,4 +446,4 @@ std::shared_ptr<display_t> display(dev_type_e hwdevice_type) {
return nullptr; return nullptr;
} }
} } // namespace platf

View file

@ -1,12 +1,12 @@
#include "sunshine/main.h"
#include "display.h" #include "display.h"
#include "sunshine/main.h"
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
} }
namespace platf::dxgi { namespace platf::dxgi {
struct img_t : public ::platf::img_t { struct img_t : public ::platf::img_t {
~img_t() override { ~img_t() override {
delete[] data; delete[] data;
data = nullptr; data = nullptr;
@ -22,29 +22,29 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
auto cursor_skip_y = -std::min(0, cursor.y); auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x); auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height); auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width); auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x; auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y; auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) { if(cursor_height > height || cursor_width > width) {
return; return;
} }
auto img_skip_y = std::max(0, cursor.y); auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x); auto img_skip_x = std::max(0, cursor.x);
auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch;
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto pixels_per_byte = width / pitch; auto pixels_per_byte = width / pitch;
auto bytes_per_row = delta_width / pixels_per_byte; auto bytes_per_row = delta_width / pixels_per_byte;
auto img_data = (int*)img.data; auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) { for(int i = 0; i < delta_height; ++i) {
auto and_mask = &cursor_img_data[i * pitch]; auto and_mask = &cursor_img_data[i * pitch];
auto xor_mask = &cursor_img_data[(i + height) * pitch]; auto xor_mask = &cursor_img_data[(i + height) * pitch];
@ -76,8 +76,8 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
} }
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t*)&cursor_pixel; auto colors_out = (std::uint8_t *)&cursor_pixel;
auto colors_in = (std::uint8_t*)img_pixel_p; auto colors_in = (std::uint8_t *)img_pixel_p;
//TODO: When use of IDXGIOutput5 is implemented, support different color formats //TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = colors_out[3]; auto alpha = colors_out[3];
@ -85,15 +85,15 @@ void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
*img_pixel_p = cursor_pixel; *img_pixel_p = cursor_pixel;
} }
else { else {
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
} }
} }
void apply_color_masked(int *img_pixel_p, int cursor_pixel) { void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
//TODO: When use of IDXGIOutput5 is implemented, support different color formats //TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t*)&cursor_pixel)[3]; auto alpha = ((std::uint8_t *)&cursor_pixel)[3];
if(alpha == 0xFF) { if(alpha == 0xFF) {
*img_pixel_p ^= cursor_pixel; *img_pixel_p ^= cursor_pixel;
} }
@ -111,30 +111,30 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
auto cursor_skip_y = -std::min(0, cursor.y); auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x); auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height); auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width); auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto img_skip_y = std::max(0, cursor.y); auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x); auto img_skip_x = std::max(0, cursor.x);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x; auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y; auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) { if(cursor_height > height || cursor_width > width) {
return; return;
} }
auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch]; auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch];
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto img_data = (int*)img.data; auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) { for(int i = 0; i < delta_height; ++i) {
auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x];
auto cursor_end = &cursor_begin[delta_width]; auto cursor_end = &cursor_begin[delta_width];
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
@ -151,22 +151,22 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
void blend_cursor(const cursor_t &cursor, img_t &img) { void blend_cursor(const cursor_t &cursor, img_t &img) {
switch(cursor.shape_info.Type) { switch(cursor.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false); blend_cursor_color(cursor, img, false);
break; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img); blend_cursor_monochrome(cursor, img);
break; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
blend_cursor_color(cursor, img, true); blend_cursor_color(cursor, img, true);
break; break;
default: default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
} }
} }
capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
auto img = (img_t*)img_base; auto img = (img_t *)img_base;
HRESULT status; HRESULT status;
@ -174,9 +174,9 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
resource_t::pointer res_p {}; resource_t::pointer res_p {};
auto capture_status = dup.next_frame(frame_info, timeout, &res_p); auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
resource_t res{res_p}; resource_t res { res_p };
if (capture_status != capture_e::ok) { if(capture_status != capture_e::ok) {
return capture_status; return capture_status;
} }
@ -187,7 +187,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
UINT dummy; UINT dummy;
status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info);
if (FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error; return capture_e::error;
@ -195,19 +195,18 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
} }
if(frame_info.LastMouseUpdateTime.QuadPart) { if(frame_info.LastMouseUpdateTime.QuadPart) {
cursor.x = frame_info.PointerPosition.Position.x; cursor.x = frame_info.PointerPosition.Position.x;
cursor.y = frame_info.PointerPosition.Position.y; cursor.y = frame_info.PointerPosition.Position.y;
cursor.visible = frame_info.PointerPosition.Visible; cursor.visible = frame_info.PointerPosition.Visible;
} }
// If frame has been updated // If frame has been updated
if (frame_info.LastPresentTime.QuadPart != 0) { if(frame_info.LastPresentTime.QuadPart != 0) {
{ {
texture2d_t::pointer src_p {}; texture2d_t src {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
texture2d_t src{src_p};
if (FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error; return capture_e::error;
} }
@ -222,14 +221,14 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
} }
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if (FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error; return capture_e::error;
} }
} }
const bool mouse_update = const bool mouse_update =
(frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) &&
(cursor_visible && cursor.visible); (cursor_visible && cursor.visible);
@ -239,7 +238,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
return capture_e::timeout; return capture_e::timeout;
} }
std::copy_n((std::uint8_t*)img_info.pData, height * img_info.RowPitch, (std::uint8_t*)img->data); std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);
if(cursor_visible && cursor.visible) { if(cursor_visible && cursor.visible) {
blend_cursor(cursor, *img); blend_cursor(cursor, *img);
@ -251,11 +250,11 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() { std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>(); auto img = std::make_shared<img_t>();
img->pixel_pitch = 4; img->pixel_pitch = 4;
img->row_pitch = img_info.RowPitch; img->row_pitch = img_info.RowPitch;
img->width = width; img->width = width;
img->height = height; img->height = height;
img->data = new std::uint8_t[img->row_pitch * height]; img->data = new std::uint8_t[img->row_pitch * height];
return img; return img;
} }
@ -270,19 +269,16 @@ int display_ram_t::init() {
} }
D3D11_TEXTURE2D_DESC t {}; D3D11_TEXTURE2D_DESC t {};
t.Width = width; t.Width = width;
t.Height = height; t.Height = height;
t.MipLevels = 1; t.MipLevels = 1;
t.ArraySize = 1; t.ArraySize = 1;
t.SampleDesc.Count = 1; t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING; t.Usage = D3D11_USAGE_STAGING;
t.Format = format; t.Format = format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, nullptr, &texture);
auto status = device->CreateTexture2D(&t, nullptr, &tex_p);
texture.reset(tex_p);
if(FAILED(status)) { if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']';
@ -298,4 +294,4 @@ int display_ram_t::init() {
return 0; return 0;
} }
} } // namespace platf::dxgi

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,6 @@
#include <sstream>
#include <iomanip>
#include <ws2tcpip.h>
#include <winsock2.h>
#include <windows.h> #include <windows.h>
#include <winuser.h>
#include <iphlpapi.h> #include <cmath>
#include <ViGEm/Client.h> #include <ViGEm/Client.h>
@ -15,7 +10,13 @@
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>; volatile HDESK _lastKnownInputDesktop = NULL;
constexpr touch_port_t target_touch_port {
0, 0,
65535, 65535
};
HDESK pairInputDesktop();
class vigem_t { class vigem_t {
public: public:
@ -86,75 +87,10 @@ public:
client_t client; client_t client;
}; };
std::string from_sockaddr(const sockaddr *const socket_address) {
char data[INET6_ADDRSTRLEN];
auto family = socket_address->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)socket_address)->sin_addr, data, INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
std::uint16_t port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
port = ((sockaddr_in6*)ip_addr)->sin6_port;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in*)ip_addr)->sin_port;
}
return { port, std::string { data } };
}
adapteraddrs_t get_adapteraddrs() {
adapteraddrs_t info { nullptr };
ULONG size = 0;
while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) {
info.reset((PIP_ADAPTER_ADDRESSES)malloc(size));
}
return info;
}
std::string get_mac_address(const std::string_view &address) {
adapteraddrs_t info = get_adapteraddrs();
for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) {
for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) {
if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) {
std::stringstream mac_addr;
mac_addr << std::hex;
for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) {
if(i > 0) {
mac_addr << ':';
}
mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i];
}
return mac_addr.str();
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
input_t input() { input_t input() {
input_t result { new vigem_t {} }; input_t result { new vigem_t {} };
auto vigem = (vigem_t*)result.get(); auto vigem = (vigem_t *)result.get();
if(vigem->init()) { if(vigem->init()) {
return nullptr; return nullptr;
} }
@ -162,55 +98,86 @@ input_t input() {
return result; return result;
} }
void send_input(INPUT &i) {
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if(_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send input"sv;
}
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
INPUT i {};
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags =
MOUSEEVENTF_MOVE |
MOUSEEVENTF_ABSOLUTE |
// MOUSEEVENTF_VIRTUALDESK maps to the entirety of the desktop rather than the primary desktop
MOUSEEVENTF_VIRTUALDESK;
auto scaled_x = std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
mi.dx = scaled_x;
mi.dy = scaled_y;
send_input(i);
}
void move_mouse(input_t &input, int deltaX, int deltaY) { void move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
auto &mi = i.mi; auto &mi = i.mi;
mi.dwFlags = MOUSEEVENTF_MOVE; mi.dwFlags = MOUSEEVENTF_MOVE;
mi.dx = deltaX; mi.dx = deltaX;
mi.dy = deltaY; mi.dy = deltaY;
auto send = SendInput(1, &i, sizeof(INPUT)); send_input(i);
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send mouse movement input"sv;
}
} }
void button_mouse(input_t &input, int button, bool release) { void button_mouse(input_t &input, int button, bool release) {
constexpr SHORT KEY_STATE_DOWN = 0x8000; constexpr auto KEY_STATE_DOWN = (SHORT)0x8000;
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
auto &mi = i.mi; auto &mi = i.mi;
int mouse_button; int mouse_button;
if(button == 1) { if(button == 1) {
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN; mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
mouse_button = VK_LBUTTON; mouse_button = VK_LBUTTON;
} }
else if(button == 2) { else if(button == 2) {
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN; mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
mouse_button = VK_MBUTTON; mouse_button = VK_MBUTTON;
} }
else if(button == 3) { else if(button == 3) {
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN; mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
mouse_button = VK_RBUTTON; mouse_button = VK_RBUTTON;
} }
else if(button == 4) { else if(button == 4) {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON1; mi.mouseData = XBUTTON1;
mouse_button = VK_XBUTTON1; mouse_button = VK_XBUTTON1;
} }
else { else {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON2; mi.mouseData = XBUTTON2;
mouse_button = VK_XBUTTON2; mouse_button = VK_XBUTTON2;
} }
auto key_state = GetAsyncKeyState(mouse_button); auto key_state = GetAsyncKeyState(mouse_button);
bool key_state_down = (key_state & KEY_STATE_DOWN) != 0; bool key_state_down = (key_state & KEY_STATE_DOWN) != 0;
if(key_state_down != release) { if(key_state_down != release) {
BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv; BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv;
@ -218,25 +185,19 @@ void button_mouse(input_t &input, int button, bool release) {
return; return;
} }
auto send = SendInput(1, &i, sizeof(INPUT)); send_input(i);
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send mouse button input"sv;
}
} }
void scroll(input_t &input, int distance) { void scroll(input_t &input, int distance) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
auto &mi = i.mi; auto &mi = i.mi;
mi.dwFlags = MOUSEEVENTF_WHEEL; mi.dwFlags = MOUSEEVENTF_WHEEL;
mi.mouseData = distance; mi.mouseData = distance;
auto send = SendInput(1, &i, sizeof(INPUT)); send_input(i);
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send moue movement input"sv;
}
} }
void keyboard(input_t &input, uint16_t modcode, bool release) { void keyboard(input_t &input, uint16_t modcode, bool release) {
@ -245,12 +206,12 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
} }
INPUT i {}; INPUT i {};
i.type = INPUT_KEYBOARD; i.type = INPUT_KEYBOARD;
auto &ki = i.ki; auto &ki = i.ki;
// For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/ // For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) { if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC); ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
ki.dwFlags = KEYEVENTF_SCANCODE; ki.dwFlags = KEYEVENTF_SCANCODE;
} }
else { else {
@ -259,33 +220,30 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
// https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags // https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags
switch(modcode) { switch(modcode) {
case VK_RMENU: case VK_RMENU:
case VK_RCONTROL: case VK_RCONTROL:
case VK_INSERT: case VK_INSERT:
case VK_DELETE: case VK_DELETE:
case VK_HOME: case VK_HOME:
case VK_END: case VK_END:
case VK_PRIOR: case VK_PRIOR:
case VK_NEXT: case VK_NEXT:
case VK_UP: case VK_UP:
case VK_DOWN: case VK_DOWN:
case VK_LEFT: case VK_LEFT:
case VK_RIGHT: case VK_RIGHT:
case VK_DIVIDE: case VK_DIVIDE:
ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
break; break;
default: default:
break; break;
} }
if(release) { if(release) {
ki.dwFlags |= KEYEVENTF_KEYUP; ki.dwFlags |= KEYEVENTF_KEYUP;
} }
auto send = SendInput(1, &i, sizeof(INPUT)); send_input(i);
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send moue movement input"sv;
}
} }
int alloc_gamepad(input_t &input, int nr) { int alloc_gamepad(input_t &input, int nr) {
@ -293,7 +251,7 @@ int alloc_gamepad(input_t &input, int nr) {
return 0; return 0;
} }
return ((vigem_t*)input.get())->alloc_x360(nr); return ((vigem_t *)input.get())->alloc_x360(nr);
} }
void free_gamepad(input_t &input, int nr) { void free_gamepad(input_t &input, int nr) {
@ -301,7 +259,7 @@ void free_gamepad(input_t &input, int nr) {
return; return;
} }
((vigem_t*)input.get())->free_target(nr); ((vigem_t *)input.get())->free_target(nr);
} }
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
// If there is no gamepad support // If there is no gamepad support
@ -309,7 +267,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
return; return;
} }
auto vigem = (vigem_t*)input.get(); auto vigem = (vigem_t *)input.get();
auto &xusb = *(PXUSB_REPORT)&gamepad_state; auto &xusb = *(PXUSB_REPORT)&gamepad_state;
auto &x360 = vigem->x360s[nr]; auto &x360 = vigem->x360s[nr];
@ -323,13 +281,32 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
} }
} }
int thread_priority() { int thread_priority() {
return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1; 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) { void freeInput(void *p) {
auto vigem = (vigem_t*)p; auto vigem = (vigem_t *)p;
delete vigem; delete vigem;
} }
} } // namespace platf

View file

@ -0,0 +1,90 @@
#include <filesystem>
#include <iomanip>
#include <sstream>
// prevent clang format from "optimizing" the header include order
// clang-format off
#include <winsock2.h>
#include <iphlpapi.h>
#include <windows.h>
#include <winuser.h>
#include <ws2tcpip.h>
// clang-format on
#include "sunshine/main.h"
#include "sunshine/utility.h"
using namespace std::literals;
namespace platf {
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
std::filesystem::path appdata() {
return L"."sv;
}
std::string from_sockaddr(const sockaddr *const socket_address) {
char data[INET6_ADDRSTRLEN];
auto family = socket_address->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
std::uint16_t port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *)ip_addr)->sin6_port;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in *)ip_addr)->sin_port;
}
return { port, std::string { data } };
}
adapteraddrs_t get_adapteraddrs() {
adapteraddrs_t info { nullptr };
ULONG size = 0;
while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) {
info.reset((PIP_ADAPTER_ADDRESSES)malloc(size));
}
return info;
}
std::string get_mac_address(const std::string_view &address) {
adapteraddrs_t info = get_adapteraddrs();
for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) {
for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) {
if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) {
std::stringstream mac_addr;
mac_addr << std::hex;
for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) {
if(i > 0) {
mac_addr << ':';
}
mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i];
}
return mac_addr.str();
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
} // namespace platf

View file

@ -2,16 +2,18 @@
// Created by loki on 12/14/19. // Created by loki on 12/14/19.
// //
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h" #include "process.h"
#include <vector>
#include <string> #include <string>
#include <vector>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include "utility.h"
#include "main.h" #include "main.h"
#include "utility.h"
namespace proc { namespace proc {
using namespace std::literals; using namespace std::literals;
@ -57,11 +59,11 @@ int proc_t::execute(int app_id) {
// Ensure starting from a clean slate // Ensure starting from a clean slate
terminate(); terminate();
_app_id = app_id; _app_id = app_id;
auto &proc = _apps[app_id]; auto &proc = _apps[app_id];
_undo_begin = std::begin(proc.prep_cmds); _undo_begin = std::begin(proc.prep_cmds);
_undo_it = _undo_begin; _undo_it = _undo_begin;
if(!proc.output.empty() && proc.output != "null"sv) { if(!proc.output.empty() && proc.output != "null"sv) {
_pipe.reset(fopen(proc.output.c_str(), "a")); _pipe.reset(fopen(proc.output.c_str(), "a"));
@ -80,17 +82,31 @@ int proc_t::execute(int app_id) {
auto ret = exe(cmd, _env, _pipe, ec); auto ret = exe(cmd, _env, _pipe, ec);
if(ec) { if(ec) {
BOOST_LOG(error) << "System: "sv << ec.message(); BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
return -1; return -1;
} }
if(ret != 0) { if(ret != 0) {
BOOST_LOG(error) << "Return code ["sv << ret << ']'; BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']';
return -1; return -1;
} }
} }
BOOST_LOG(debug) << "Starting ["sv << proc.cmd << ']'; for(auto &cmd : proc.detached) {
BOOST_LOG(info) << "Spawning ["sv << cmd << ']';
if(proc.output.empty()) {
bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
bp::spawn(cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
}
if(ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
}
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
if(proc.cmd.empty()) { if(proc.cmd.empty()) {
placebo = true; placebo = true;
} }
@ -102,7 +118,7 @@ int proc_t::execute(int app_id) {
} }
if(ec) { if(ec) {
BOOST_LOG(info) << "System: "sv << ec.message(); BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
return -1; return -1;
} }
@ -133,7 +149,7 @@ void proc_t::terminate() {
std::abort(); std::abort();
} }
for(;_undo_it != _undo_begin; --_undo_it) { for(; _undo_it != _undo_begin; --_undo_it) {
auto &cmd = (_undo_it - 1)->undo_cmd; auto &cmd = (_undo_it - 1)->undo_cmd;
if(cmd.empty()) { if(cmd.empty()) {
@ -178,9 +194,11 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str
do { do {
++begin; ++begin;
switch(*begin) { switch(*begin) {
case '(': ++stack; case '(':
++stack;
break; break;
case ')': --stack; case ')':
--stack;
} }
} while(begin != end && stack != 0); } while(begin != end && stack != 0);
@ -191,7 +209,7 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str
} }
std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) { std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) {
auto pos = std::begin(val_raw); auto pos = std::begin(val_raw);
auto dollar = std::find(pos, std::end(val_raw), '$'); auto dollar = std::find(pos, std::end(val_raw), '$');
std::stringstream ss; std::stringstream ss;
@ -200,23 +218,23 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v
auto next = dollar + 1; auto next = dollar + 1;
if(next != std::end(val_raw)) { if(next != std::end(val_raw)) {
switch(*next) { switch(*next) {
case '(': { case '(': {
ss.write(pos, (dollar - pos)); ss.write(pos, (dollar - pos));
auto var_begin = next + 1; auto var_begin = next + 1;
auto var_end = find_match(next, std::end(val_raw)); auto var_end = find_match(next, std::end(val_raw));
ss << env[std::string { var_begin, var_end }].to_string(); ss << env[std::string { var_begin, var_end }].to_string();
pos = var_end + 1; pos = var_end + 1;
next = var_end; next = var_end;
break; break;
} }
case '$': case '$':
ss.write(pos, (next - pos)); ss.write(pos, (next - pos));
pos = next + 1; pos = next + 1;
++next; ++next;
break; break;
} }
dollar = std::find(next, std::end(val_raw), '$'); dollar = std::find(next, std::end(val_raw), '$');
@ -231,7 +249,7 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v
return ss.str(); return ss.str();
} }
std::optional<proc::proc_t> parse(const std::string& file_name) { std::optional<proc::proc_t> parse(const std::string &file_name) {
pt::ptree tree; pt::ptree tree;
try { try {
@ -242,27 +260,27 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
auto this_env = boost::this_process::environment(); auto this_env = boost::this_process::environment();
for(auto &[name,val] : env_vars) { for(auto &[name, val] : env_vars) {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>()); this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
} }
std::vector<proc::ctx_t> apps; std::vector<proc::ctx_t> apps;
for(auto &[_,app_node] : apps_node) { for(auto &[_, app_node] : apps_node) {
proc::ctx_t ctx; proc::ctx_t ctx;
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s); auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
auto output = app_node.get_optional<std::string>("output"s); auto detached_nodes_opt = app_node.get_child_optional("detached"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s)); auto output = app_node.get_optional<std::string>("output"s);
auto cmd = app_node.get_optional<std::string>("cmd"s); auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
std::vector<proc::cmd_t> prep_cmds; std::vector<proc::cmd_t> prep_cmds;
if(prep_nodes_opt) { if(prep_nodes_opt) {
auto &prep_nodes = *prep_nodes_opt; auto &prep_nodes = *prep_nodes_opt;
prep_cmds.reserve(prep_nodes.size()); prep_cmds.reserve(prep_nodes.size());
for(auto &[_, prep_node] : prep_nodes) { for(auto &[_, prep_node] : prep_nodes) {
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s)); auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s); auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
if(undo_cmd) { if(undo_cmd) {
@ -274,6 +292,16 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
} }
} }
std::vector<std::string> detached;
if(detached_nodes_opt) {
auto &detached_nodes = *detached_nodes_opt;
detached.reserve(detached_nodes.size());
for(auto &[_, detached_val] : detached_nodes) {
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
}
}
if(output) { if(output) {
ctx.output = parse_env_val(this_env, *output); ctx.output = parse_env_val(this_env, *output);
} }
@ -282,8 +310,9 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
ctx.cmd = parse_env_val(this_env, *cmd); ctx.cmd = parse_env_val(this_env, *cmd);
} }
ctx.name = std::move(name); ctx.name = std::move(name);
ctx.prep_cmds = std::move(prep_cmds); ctx.prep_cmds = std::move(prep_cmds);
ctx.detached = std::move(detached);
apps.emplace_back(std::move(ctx)); apps.emplace_back(std::move(ctx));
} }
@ -291,7 +320,8 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
return proc::proc_t { return proc::proc_t {
std::move(this_env), std::move(apps) std::move(this_env), std::move(apps)
}; };
} catch (std::exception &e) { }
catch(std::exception &e) {
BOOST_LOG(error) << e.what(); BOOST_LOG(error) << e.what();
} }
@ -302,7 +332,12 @@ void refresh(const std::string &file_name) {
auto proc_opt = proc::parse(file_name); auto proc_opt = proc::parse(file_name);
if(proc_opt) { if(proc_opt) {
{
proc::ctx_t ctx;
ctx.name = "Desktop"s;
proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx));
}
proc = std::move(*proc_opt); proc = std::move(*proc_opt);
} }
} }
} } // namespace proc

View file

@ -9,8 +9,8 @@
#define __kernel_entry #define __kernel_entry
#endif #endif
#include <unordered_map>
#include <optional> #include <optional>
#include <unordered_map>
#include <boost/process.hpp> #include <boost/process.hpp>
@ -30,6 +30,7 @@ struct cmd_t {
}; };
/* /*
* pre_cmds -- guaranteed to be executed unless any of the commands fail. * pre_cmds -- guaranteed to be executed unless any of the commands fail.
* detached -- commands detached from Sunshine
* cmd -- Runs indefinitely until: * cmd -- Runs indefinitely until:
* No session is running and a different set of commands it to be executed * No session is running and a different set of commands it to be executed
* Command exits * Command exits
@ -41,6 +42,14 @@ struct cmd_t {
struct ctx_t { struct ctx_t {
std::vector<cmd_t> prep_cmds; std::vector<cmd_t> prep_cmds;
/**
* Some applications, such as Steam,
* either exit quickly, or keep running indefinitely.
* Steam.exe is one such application.
* That is why some applications need be run and forgotten about
*/
std::vector<std::string> detached;
std::string name; std::string name;
std::string cmd; std::string cmd;
std::string output; std::string output;
@ -52,10 +61,9 @@ public:
proc_t( proc_t(
boost::process::environment &&env, boost::process::environment &&env,
std::vector<ctx_t> &&apps) : std::vector<ctx_t> &&apps) : _app_id(-1),
_app_id(-1), _env(std::move(env)),
_env(std::move(env)), _apps(std::move(apps)) {}
_apps(std::move(apps)) {}
int execute(int app_id); int execute(int app_id);
@ -68,7 +76,7 @@ public:
const std::vector<ctx_t> &get_apps() const; const std::vector<ctx_t> &get_apps() const;
std::vector<ctx_t> &get_apps(); std::vector<ctx_t> &get_apps();
void terminate(); void terminate();
private: private:
@ -89,8 +97,8 @@ private:
}; };
void refresh(const std::string &file_name); void refresh(const std::string &file_name);
std::optional<proc::proc_t> parse(const std::string& file_name); std::optional<proc::proc_t> parse(const std::string &file_name);
extern proc_t proc; extern proc_t proc;
} } // namespace proc
#endif //SUNSHINE_PROCESS_H #endif //SUNSHINE_PROCESS_H

View file

@ -9,20 +9,20 @@
#include <avahi-common/malloc.h> #include <avahi-common/malloc.h>
#include <avahi-common/simple-watch.h> #include <avahi-common/simple-watch.h>
#include "publish.h"
#include "nvhttp.h"
#include "main.h" #include "main.h"
#include "nvhttp.h"
#include "publish.h"
namespace publish { namespace publish {
AvahiEntryGroup *group = NULL; AvahiEntryGroup *group = NULL;
AvahiSimplePoll *simple_poll = NULL; AvahiSimplePoll *simple_poll = NULL;
char *name = NULL; char *name = NULL;
void create_services(AvahiClient *c); void create_services(AvahiClient *c);
void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
assert(g == group || group == NULL); assert(g == group || group == NULL);
group = g; group = g;
switch (state) { switch(state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED: case AVAHI_ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name << " successfully established."; BOOST_LOG(info) << "Avahi service " << name << " successfully established.";
break; break;
@ -39,8 +39,7 @@ void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_
avahi_simple_poll_quit(simple_poll); avahi_simple_poll_quit(simple_poll);
break; break;
case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING: case AVAHI_ENTRY_GROUP_REGISTERING:;
;
} }
} }
@ -48,22 +47,22 @@ void create_services(AvahiClient *c) {
char *n; char *n;
int ret; int ret;
assert(c); assert(c);
if (!group) { if(!group) {
if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) { if(!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) {
BOOST_LOG(error) << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)); BOOST_LOG(error) << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c));
goto fail; goto fail;
} }
} }
if (avahi_entry_group_is_empty(group)) { if(avahi_entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service " << name; BOOST_LOG(info) << "Adding avahi service " << name;
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, SERVICE_TYPE, NULL, NULL, nvhttp::PORT_HTTP, NULL)) < 0) { if((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, SERVICE_TYPE, NULL, NULL, nvhttp::PORT_HTTP, NULL)) < 0) {
if (ret == AVAHI_ERR_COLLISION) if(ret == AVAHI_ERR_COLLISION)
goto collision; goto collision;
BOOST_LOG(error) << "Failed to add " << SERVICE_TYPE << " service: " << avahi_strerror(ret); BOOST_LOG(error) << "Failed to add " << SERVICE_TYPE << " service: " << avahi_strerror(ret);
goto fail; goto fail;
} }
if ((ret = avahi_entry_group_commit(group)) < 0) { if((ret = avahi_entry_group_commit(group)) < 0) {
BOOST_LOG(error) << "Failed to commit entry group: " << avahi_strerror(ret); BOOST_LOG(error) << "Failed to commit entry group: " << avahi_strerror(ret);
goto fail; goto fail;
} }
@ -74,7 +73,7 @@ collision:
n = avahi_alternative_service_name(name); n = avahi_alternative_service_name(name);
avahi_free(name); avahi_free(name);
name = n; name = n;
BOOST_LOG(info) << "Service name collision, renaming service to " << name; BOOST_LOG(info) << "Service name collision, renaming service to " << name;
avahi_entry_group_reset(group); avahi_entry_group_reset(group);
create_services(c); create_services(c);
return; return;
@ -84,7 +83,7 @@ fail:
void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata) { void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata) {
assert(c); assert(c);
switch (state) { switch(state) {
case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_RUNNING:
create_services(c); create_services(c);
break; break;
@ -94,24 +93,25 @@ void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED vo
break; break;
case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_COLLISION:
case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_REGISTERING:
if (group) if(group)
avahi_entry_group_reset(group); avahi_entry_group_reset(group);
break; break;
case AVAHI_CLIENT_CONNECTING: case AVAHI_CLIENT_CONNECTING:;
;
} }
} }
void start(std::shared_ptr<safe::signal_t> shutdown_event) { void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
AvahiClient *client = NULL; AvahiClient *client = NULL;
int avhi_error; int avhi_error;
if (!(simple_poll = avahi_simple_poll_new())) { if(!(simple_poll = avahi_simple_poll_new())) {
BOOST_LOG(error) << "Failed to create simple poll object."; BOOST_LOG(error) << "Failed to create simple poll object.";
return; return;
} }
name = avahi_strdup(SERVICE_NAME); name = avahi_strdup(SERVICE_NAME);
client = avahi_client_new(avahi_simple_poll_get(simple_poll), AvahiClientFlags(0), client_callback, NULL, &avhi_error); client = avahi_client_new(avahi_simple_poll_get(simple_poll), AvahiClientFlags(0), client_callback, NULL, &avhi_error);
if (!client) { if(!client) {
BOOST_LOG(error) << "Failed to create client: " << avahi_strerror(avhi_error); BOOST_LOG(error) << "Failed to create client: " << avahi_strerror(avhi_error);
avahi_simple_poll_free(simple_poll); avahi_simple_poll_free(simple_poll);
return; return;
@ -125,7 +125,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
avahi_simple_poll_quit(simple_poll); avahi_simple_poll_quit(simple_poll);
poll_thread.join(); poll_thread.join();
avahi_client_free(client); avahi_client_free(client);
avahi_simple_poll_free(simple_poll); avahi_simple_poll_free(simple_poll);
avahi_free(name); avahi_free(name);

View file

@ -7,7 +7,7 @@
#define SERVICE_TYPE "_nvstream._tcp" #define SERVICE_TYPE "_nvstream._tcp"
namespace publish { namespace publish {
void start(std::shared_ptr<safe::signal_t> shutdown_event); void start();
} }
#endif //SUNSHINE_PUBLISH_H #endif //SUNSHINE_PUBLISH_H

View file

@ -10,12 +10,12 @@ public:
typedef T iterator; typedef T iterator;
typedef typename std::iterator<std::random_access_iterator_tag, V>::value_type class_t; typedef typename std::iterator<std::random_access_iterator_tag, V>::value_type class_t;
typedef class_t& reference; typedef class_t &reference;
typedef class_t* pointer; typedef class_t *pointer;
typedef std::ptrdiff_t diff_t; typedef std::ptrdiff_t diff_t;
iterator operator += (diff_t step) { iterator operator+=(diff_t step) {
while(step-- > 0) { while(step-- > 0) {
++_this(); ++_this();
} }
@ -23,7 +23,7 @@ public:
return _this(); return _this();
} }
iterator operator -= (diff_t step) { iterator operator-=(diff_t step) {
while(step-- > 0) { while(step-- > 0) {
--_this(); --_this();
} }
@ -31,19 +31,19 @@ public:
return _this(); return _this();
} }
iterator operator +(diff_t step) { iterator operator+(diff_t step) {
iterator new_ = _this(); iterator new_ = _this();
return new_ += step; return new_ += step;
} }
iterator operator -(diff_t step) { iterator operator-(diff_t step) {
iterator new_ = _this(); iterator new_ = _this();
return new_ -= step; return new_ -= step;
} }
diff_t operator -(iterator first) { diff_t operator-(iterator first) {
diff_t step = 0; diff_t step = 0;
while(first != _this()) { while(first != _this()) {
++step; ++step;
@ -53,8 +53,14 @@ public:
return step; return step;
} }
iterator operator++() { _this().inc(); return _this(); } iterator operator++() {
iterator operator--() { _this().dec(); return _this(); } _this().inc();
return _this();
}
iterator operator--() {
_this().dec();
return _this();
}
iterator operator++(int) { iterator operator++(int) {
iterator new_ = _this(); iterator new_ = _this();
@ -78,35 +84,35 @@ public:
pointer operator->() { return &*_this(); } pointer operator->() { return &*_this(); }
const pointer operator->() const { return &*_this(); } const pointer operator->() const { return &*_this(); }
bool operator != (const iterator &other) const { bool operator!=(const iterator &other) const {
return !(_this() == other); return !(_this() == other);
} }
bool operator < (const iterator &other) const { bool operator<(const iterator &other) const {
return !(_this() >= other); return !(_this() >= other);
} }
bool operator >= (const iterator &other) const { bool operator>=(const iterator &other) const {
return _this() == other || _this() > other; return _this() == other || _this() > other;
} }
bool operator <= (const iterator &other) const { bool operator<=(const iterator &other) const {
return _this() == other || _this() < other; return _this() == other || _this() < other;
} }
bool operator == (const iterator &other) const { return _this().eq(other); }; bool operator==(const iterator &other) const { return _this().eq(other); };
bool operator > (const iterator &other) const { return _this().gt(other); } bool operator>(const iterator &other) const { return _this().gt(other); }
private:
iterator &_this() { return *static_cast<iterator*>(this); } private:
const iterator &_this() const { return *static_cast<const iterator*>(this); } iterator &_this() { return *static_cast<iterator *>(this); }
const iterator &_this() const { return *static_cast<const iterator *>(this); }
}; };
template<class V, class It> template<class V, class It>
class round_robin_t : public it_wrap_t<V, round_robin_t<V, It>> { class round_robin_t : public it_wrap_t<V, round_robin_t<V, It>> {
public: public:
using iterator = It; using iterator = It;
using pointer = V*; using pointer = V *;
round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {} round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {}
@ -119,10 +125,10 @@ public:
} }
void dec() { void dec() {
if(_pos == _begin) { if(_pos == _begin) {
_pos = _end; _pos = _end;
} }
--_pos; --_pos;
} }
@ -133,6 +139,7 @@ public:
pointer get() const { pointer get() const {
return &*_pos; return &*_pos;
} }
private: private:
It _begin; It _begin;
It _end; It _end;
@ -144,6 +151,6 @@ template<class V, class It>
round_robin_t<V, It> make_round_robin(It begin, It end) { round_robin_t<V, It> make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end); return round_robin_t<V, It>(begin, end);
} }
} } // namespace util
#endif #endif

View file

@ -7,10 +7,10 @@ extern "C" {
} }
#include "config.h" #include "config.h"
#include "input.h"
#include "main.h" #include "main.h"
#include "network.h" #include "network.h"
#include "rtsp.h" #include "rtsp.h"
#include "input.h"
#include "stream.h" #include "stream.h"
#include "sync.h" #include "sync.h"
@ -25,7 +25,7 @@ namespace stream {
//FIXME: Quick and dirty workaround for bug in MinGW 9.3 causing a linker error when using std::to_string //FIXME: Quick and dirty workaround for bug in MinGW 9.3 causing a linker error when using std::to_string
template<class T> template<class T>
std::string to_string(T && t) { std::string to_string(T &&t) {
std::stringstream ss; std::stringstream ss;
ss << std::forward<T>(t); ss << std::forward<T>(t);
return ss.str(); return ss.str();
@ -41,11 +41,11 @@ void free_msg(PRTSP_MESSAGE msg) {
class rtsp_server_t; class rtsp_server_t;
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>; 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 *, net::peer_t, msg_t &&)>;
void print_msg(PRTSP_MESSAGE msg); 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(net::host_t::pointer host, net::peer_t peer, msg_t &&req);
class rtsp_server_t { class rtsp_server_t {
public: public:
@ -69,6 +69,7 @@ public:
} }
void session_raise(launch_session_t launch_session) { void session_raise(launch_session_t launch_session) {
//FIXME: If client abandons us at this stage, _slot_count won't be raised again.
--_slot_count; --_slot_count;
launch_event.raise(launch_session); launch_event.raise(launch_session);
} }
@ -82,61 +83,61 @@ public:
ENetEvent event; ENetEvent event;
auto res = enet_host_service(_host.get(), &event, std::chrono::floor<std::chrono::milliseconds>(timeout).count()); auto res = enet_host_service(_host.get(), &event, std::chrono::floor<std::chrono::milliseconds>(timeout).count());
if (res > 0) { if(res > 0) {
switch (event.type) { switch(event.type) {
case ENET_EVENT_TYPE_RECEIVE: { case ENET_EVENT_TYPE_RECEIVE: {
net::packet_t packet{event.packet}; net::packet_t packet { event.packet };
net::peer_t peer{event.peer}; net::peer_t peer { event.peer };
msg_t req { new msg_t::element_type }; msg_t req { new msg_t::element_type };
//TODO: compare addresses of the peers //TODO: compare addresses of the peers
if (_queue_packet.second == nullptr) { if(_queue_packet.second == nullptr) {
parseRtspMessage(req.get(), (char *) packet->data, packet->dataLength); parseRtspMessage(req.get(), (char *)packet->data, packet->dataLength);
for (auto option = req->options; option != nullptr; option = option->next) { for(auto option = req->options; option != nullptr; option = option->next) {
if ("Content-length"sv == option->option) { if("Content-length"sv == option->option) {
_queue_packet = std::make_pair(peer, std::move(packet)); _queue_packet = std::make_pair(peer, std::move(packet));
return; 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: else {
BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv; std::vector<char> full_payload;
break;
case ENET_EVENT_TYPE_DISCONNECT: auto old_msg = std::move(_queue_packet);
BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv; auto &old_packet = old_msg.second;
break;
case ENET_EVENT_TYPE_NONE: std::string_view new_payload { (char *)packet->data, packet->dataLength };
break; 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;
} }
} }
} }
@ -149,7 +150,7 @@ public:
auto lg = _session_slots.lock(); auto lg = _session_slots.lock();
for(auto &slot : *_session_slots) { for(auto &slot : *_session_slots) {
if (slot && (all || session::state(*slot) == session::state_e::STOPPING)) { if(slot && (all || session::state(*slot) == session::state_e::STOPPING)) {
session::stop(*slot); session::stop(*slot);
session::join(*slot); session::join(*slot);
@ -194,7 +195,6 @@ public:
safe::event_t<launch_session_t> launch_event; safe::event_t<launch_session_t> launch_event;
private: private:
// named _queue_packet because I want to make it an actual queue // named _queue_packet because I want to make it an actual queue
// It's like this for convenience sake // It's like this for convenience sake
std::pair<net::peer_t, net::packet_t> _queue_packet; std::pair<net::peer_t, net::packet_t> _queue_packet;
@ -225,11 +225,11 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
auto payload = std::make_pair(resp->payload, resp->payloadLength); auto payload = std::make_pair(resp->payload, resp->payloadLength);
auto lg = util::fail_guard([&]() { auto lg = util::fail_guard([&]() {
resp->payload = payload.first; resp->payload = payload.first;
resp->payloadLength = payload.second; resp->payloadLength = payload.second;
}); });
resp->payload = nullptr; resp->payload = nullptr;
resp->payloadLength = 0; resp->payloadLength = 0;
int serialized_len; int serialized_len;
@ -264,45 +264,69 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
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(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) {
msg_t resp { new msg_t::element_type }; 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()); 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(host, peer, resp);
} }
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t&& req) { 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, {}); respond(host, peer, 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, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {}; OPTION_ITEM option {};
// I know these string literals will not be modified // I know these string literals will not be modified
option.option = const_cast<char*>("CSeq"); option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber); auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str()); option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {}); respond(server->host(), peer, &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, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {}; OPTION_ITEM option {};
// I know these string literals will not be modified // I know these string literals will not be modified
option.option = const_cast<char*>("CSeq"); option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber); auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str()); option.content = const_cast<char *>(seqn_str.c_str());
std::string_view payload; std::stringstream ss;
if(config::video.hevc_mode == 1) { if(config::video.hevc_mode != 1) {
payload = "surround-params=NONE"sv; ss << "sprop-parameter-sets=AAAAAU"sv << std::endl;
}
else {
payload = "sprop-parameter-sets=AAAAAU;surround-params=NONE"sv;
} }
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, payload); for(int x = 0; x < audio::MAX_STREAM_CONFIG; ++x) {
auto &stream_config = audio::stream_configs[x];
std::uint8_t mapping[platf::speaker::MAX_SPEAKERS];
auto mapping_p = stream_config.mapping;
/**
* GFE advertises incorrect mapping for normal quality configurations,
* as a result, Moonlight rotates all channels from index '3' to the right
* To work around this, rotate channels to the left from index '3'
*/
if(x == audio::SURROUND51 || x == audio::SURROUND71) {
std::copy_n(mapping_p, stream_config.channelCount, mapping);
std::rotate(mapping + 3, mapping + 4, mapping + audio::MAX_STREAM_CONFIG);
mapping_p = mapping;
}
ss << "a=fmtp:97 surround-params="sv << stream_config.channelCount << stream_config.streams << stream_config.coupledStreams;
std::for_each_n(mapping_p, stream_config.channelCount, [&ss](std::uint8_t digit) {
ss << (char)(digit + '0');
});
ss << std::endl;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, ss.str());
} }
void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
@ -311,10 +335,10 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
auto &seqn = options[0]; auto &seqn = options[0];
auto &session_option = options[1]; auto &session_option = options[1];
seqn.option = const_cast<char*>("CSeq"); seqn.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber); auto seqn_str = to_string(req->sequenceNumber);
seqn.content = const_cast<char*>(seqn_str.c_str()); seqn.content = const_cast<char *>(seqn_str.c_str());
std::string_view target { req->message.request.target }; std::string_view target { req->message.request.target };
auto begin = std::find(std::begin(target), std::end(target), '=') + 1; auto begin = std::find(std::begin(target), std::end(target), '=') + 1;
@ -324,8 +348,8 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
if(type == "audio"sv) { if(type == "audio"sv) {
seqn.next = &session_option; seqn.next = &session_option;
session_option.option = const_cast<char*>("Session"); session_option.option = const_cast<char *>("Session");
session_option.content = const_cast<char*>("DEADBEEFCAFE;timeout = 90"); session_option.content = const_cast<char *>("DEADBEEFCAFE;timeout = 90");
} }
else if(type != "video"sv && type != "control"sv) { else if(type != "video"sv && type != "control"sv) {
cmd_not_found(server->host(), peer, std::move(req)); cmd_not_found(server->host(), peer, std::move(req));
@ -340,10 +364,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {}; OPTION_ITEM option {};
// I know these string literals will not be modified // I know these string literals will not be modified
option.option = const_cast<char*>("CSeq"); option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber); auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str()); option.content = const_cast<char *>(seqn_str.c_str());
if(!server->launch_event.peek()) { if(!server->launch_event.peek()) {
// /launch has not been used // /launch has not been used
@ -362,10 +386,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
}; };
{ {
auto pos = std::begin(payload); auto pos = std::begin(payload);
auto begin = pos; auto begin = pos;
while (pos != std::end(payload)) { while(pos != std::end(payload)) {
if (whitespace(*pos++)) { if(whitespace(*pos++)) {
lines.emplace_back(begin, pos - begin - 1); lines.emplace_back(begin, pos - begin - 1);
while(pos != std::end(payload) && whitespace(*pos)) { ++pos; } while(pos != std::end(payload) && whitespace(*pos)) { ++pos; }
@ -386,10 +410,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
auto pos = line.find(':'); auto pos = line.find(':');
auto name = line.substr(2, pos - 2); auto name = line.substr(2, pos - 2);
auto val = line.substr(pos + 1); auto val = line.substr(pos + 1);
if(val[val.size() -1] == ' ') { if(val[val.size() - 1] == ' ') {
val = val.substr(0, val.size() -1); val = val.substr(0, val.size() - 1);
} }
args.emplace(name, val); args.emplace(name, val);
} }
@ -402,11 +426,16 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv); args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv);
config_t config; config_t config;
config.audio.flags[audio::config_t::HOST_AUDIO] = launch_session->host_audio;
try { try {
config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv)); config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv));
config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv)); config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv));
config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv)); config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv));
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.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv)); config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
@ -418,8 +447,8 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv)); config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv));
config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv)); config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv));
config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv)); config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv));
}
} catch(std::out_of_range &) { catch(std::out_of_range &) {
respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return; return;
@ -442,7 +471,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
return; return;
} }
if(session::start(*session, platf::from_sockaddr((sockaddr*)&peer->address.address))) { if(session::start(*session, platf::from_sockaddr((sockaddr *)&peer->address.address))) {
BOOST_LOG(error) << "Failed to start a streaming session"sv; BOOST_LOG(error) << "Failed to start a streaming session"sv;
server->clear(slot); server->clear(slot);
@ -457,15 +486,18 @@ void cmd_play(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {}; OPTION_ITEM option {};
// I know these string literals will not be modified // I know these string literals will not be modified
option.option = const_cast<char*>("CSeq"); option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber); auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str()); option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {}); respond(server->host(), peer, &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("OPTIONS"sv, &cmd_option);
server.map("DESCRIBE"sv, &cmd_describe); server.map("DESCRIBE"sv, &cmd_describe);
server.map("SETUP"sv, &cmd_setup); server.map("SETUP"sv, &cmd_setup);
@ -483,7 +515,7 @@ void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event) {
while(!shutdown_event->peek()) { while(!shutdown_event->peek()) {
server.iterate(std::min(500ms, config::stream.ping_timeout)); server.iterate(std::min(500ms, config::stream.ping_timeout));
if(broadcast_shutdown_event.peek()) { if(broadcast_shutdown_event->peek()) {
server.clear(); server.clear();
} }
else { else {
@ -518,7 +550,7 @@ void print_msg(PRTSP_MESSAGE msg) {
BOOST_LOG(debug) << "status :: "sv << status; BOOST_LOG(debug) << "status :: "sv << status;
} }
else { else {
auto& req = msg->message.request; auto &req = msg->message.request;
std::string_view command { req.command }; std::string_view command { req.command };
std::string_view target { req.target }; std::string_view target { req.target };
@ -534,6 +566,8 @@ void print_msg(PRTSP_MESSAGE msg) {
BOOST_LOG(debug) << name << " :: "sv << content; BOOST_LOG(debug) << name << " :: "sv << content;
} }
BOOST_LOG(debug) << "---Begin MessageBuffer---"sv << std::endl << messageBuffer << std::endl << "---End MessageBuffer---"sv << std::endl; BOOST_LOG(debug) << "---Begin MessageBuffer---"sv << std::endl
} << messageBuffer << std::endl
<< "---End MessageBuffer---"sv << std::endl;
} }
} // namespace stream

View file

@ -14,13 +14,15 @@ namespace stream {
struct launch_session_t { struct launch_session_t {
crypto::aes_t gcm_key; crypto::aes_t gcm_key;
crypto::aes_t iv; crypto::aes_t iv;
bool host_audio;
}; };
void launch_session_raise(launch_session_t launch_session); void launch_session_raise(launch_session_t launch_session);
int session_count(); int session_count();
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event); void rtpThread();
} } // namespace stream
#endif //SUNSHINE_RTSP_H #endif //SUNSHINE_RTSP_H

View file

@ -4,8 +4,8 @@
#include "process.h" #include "process.h"
#include <queue>
#include <future> #include <future>
#include <queue>
#include <fstream> #include <fstream>
#include <openssl/err.h> #include <openssl/err.h>
@ -15,14 +15,14 @@ extern "C" {
#include <rs.h> #include <rs.h>
} }
#include "network.h"
#include "config.h" #include "config.h"
#include "utility.h"
#include "stream.h"
#include "thread_safe.h"
#include "sync.h"
#include "input.h" #include "input.h"
#include "main.h" #include "main.h"
#include "network.h"
#include "stream.h"
#include "sync.h"
#include "thread_safe.h"
#include "utility.h"
#define IDX_START_A 0 #define IDX_START_A 0
#define IDX_REQUEST_IDR_FRAME 0 #define IDX_REQUEST_IDR_FRAME 0
@ -45,7 +45,7 @@ static const short packetTypes[] = {
}; };
constexpr auto VIDEO_STREAM_PORT = 47998; constexpr auto VIDEO_STREAM_PORT = 47998;
constexpr auto CONTROL_PORT = 47999; constexpr auto CONTROL_PORT = 47999;
constexpr auto AUDIO_STREAM_PORT = 48000; constexpr auto AUDIO_STREAM_PORT = 48000;
namespace asio = boost::asio; namespace asio = boost::asio;
@ -89,7 +89,7 @@ using rh_t = util::safe_ptr<reed_solomon, reed_solomon_release>;
using video_packet_t = util::c_ptr<video_packet_raw_t>; using video_packet_t = util::c_ptr<video_packet_raw_t>;
using audio_packet_t = util::c_ptr<audio_packet_raw_t>; using audio_packet_t = util::c_ptr<audio_packet_raw_t>;
using message_queue_t = std::shared_ptr<safe::queue_t<std::pair<std::uint16_t, std::string>>>; using message_queue_t = std::shared_ptr<safe::queue_t<std::pair<std::uint16_t, std::string>>>;
using message_queue_queue_t = std::shared_ptr<safe::queue_t<std::tuple<socket_e, asio::ip::address, message_queue_t>>>; using message_queue_queue_t = std::shared_ptr<safe::queue_t<std::tuple<socket_e, asio::ip::address, message_queue_t>>>;
static inline void while_starting_do_nothing(std::atomic<session::state_e> &state) { static inline void while_starting_do_nothing(std::atomic<session::state_e> &state) {
@ -124,7 +124,7 @@ public:
// Therefore, iterate is implemented further down the source file // Therefore, iterate is implemented further down the source file
void iterate(std::chrono::milliseconds timeout); void iterate(std::chrono::milliseconds timeout);
void map(uint16_t type, std::function<void(session_t *, const std::string_view&)> cb) { void map(uint16_t type, std::function<void(session_t *, const std::string_view &)> cb) {
_map_type_cb.emplace(type, std::move(cb)); _map_type_cb.emplace(type, std::move(cb));
} }
@ -140,19 +140,16 @@ public:
} }
// Callbacks // Callbacks
std::unordered_map<std::uint16_t, std::function<void(session_t *, const std::string_view&)>> _map_type_cb; std::unordered_map<std::uint16_t, std::function<void(session_t *, const std::string_view &)>> _map_type_cb;
// Mapping ip:port to session // Mapping ip:port to session
util::sync_t<std::unordered_multimap<std::string, std::pair<std::uint16_t, session_t*>>> _map_addr_session; util::sync_t<std::unordered_multimap<std::string, std::pair<std::uint16_t, session_t *>>> _map_addr_session;
ENetAddress _addr; ENetAddress _addr;
net::host_t _host; net::host_t _host;
}; };
struct broadcast_ctx_t { struct broadcast_ctx_t {
video::packet_queue_t video_packets;
audio::packet_queue_t audio_packets;
message_queue_queue_t message_queue_queue; message_queue_queue_t message_queue_queue;
std::thread recv_thread; std::thread recv_thread;
@ -169,6 +166,9 @@ struct broadcast_ctx_t {
struct session_t { struct session_t {
config_t config; config_t config;
safe::mail_t mail;
std::shared_ptr<input::input_t> input; std::shared_ptr<input::input_t> input;
std::thread audioThread; std::thread audioThread;
@ -181,7 +181,7 @@ struct session_t {
struct { struct {
int lowseq; int lowseq;
udp::endpoint peer; udp::endpoint peer;
video::idr_event_t idr_events; safe::mail_raw_t::event_t<video::idr_t> idr_events;
} video; } video;
struct { struct {
@ -196,7 +196,7 @@ struct session_t {
crypto::aes_t gcm_key; crypto::aes_t gcm_key;
crypto::aes_t iv; crypto::aes_t iv;
safe::signal_t shutdown_event; safe::mail_raw_t::event_t<bool> shutdown_event;
safe::signal_t controlEnd; safe::signal_t controlEnd;
std::atomic<session::state_e> state; std::atomic<session::state_e> state;
@ -207,10 +207,9 @@ void end_broadcast(broadcast_ctx_t &ctx);
static auto broadcast = safe::make_shared<broadcast_ctx_t>(start_broadcast, end_broadcast); static auto broadcast = safe::make_shared<broadcast_ctx_t>(start_broadcast, end_broadcast);
safe::signal_t broadcast_shutdown_event;
session_t *control_server_t::get_session(const net::peer_t peer) { session_t *control_server_t::get_session(const net::peer_t peer) {
TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr*)&peer->address.address)); TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr *)&peer->address.address));
auto lg = _map_addr_session.lock(); auto lg = _map_addr_session.lock();
TUPLE_2D(begin, end, _map_addr_session->equal_range(addr_string)); TUPLE_2D(begin, end, _map_addr_session->equal_range(addr_string));
@ -231,7 +230,7 @@ session_t *control_server_t::get_session(const net::peer_t peer) {
TUPLE_2D_REF(session_port, session_p, it->second); TUPLE_2D_REF(session_port, session_p, it->second);
session_p->control.peer = peer; session_p->control.peer = peer;
session_port = port; session_port = port;
return session_p; return session_p;
} }
@ -246,7 +245,7 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) {
if(res > 0) { if(res > 0) {
auto session = get_session(event.peer); auto session = get_session(event.peer);
if(!session) { if(!session) {
BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr*)&event.peer->address.address) << "]: it's not properly set up"sv; BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr *)&event.peer->address.address) << "]: it's not properly set up"sv;
enet_peer_disconnect_now(event.peer, 0); enet_peer_disconnect_now(event.peer, 0);
return; return;
@ -255,37 +254,37 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) {
session->pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout; session->pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
switch(event.type) { switch(event.type) {
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE: {
{ net::packet_t packet { event.packet };
net::packet_t packet { event.packet };
auto type = (std::uint16_t *)packet->data; auto type = (std::uint16_t *)packet->data;
std::string_view payload { (char*)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) }; std::string_view payload { (char *)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) };
auto cb = _map_type_cb.find(*type); auto cb = _map_type_cb.find(*type);
if(cb == std::end(_map_type_cb)) { if(cb == std::end(_map_type_cb)) {
BOOST_LOG(warning) BOOST_LOG(warning)
<< "type [Unknown] { "sv << util::hex(*type).to_string_view() << " }"sv << std::endl << "type [Unknown] { "sv << util::hex(*type).to_string_view() << " }"sv << std::endl
<< "---data---"sv << std::endl << util::hex_vec(payload) << std::endl << "---end data---"sv; << "---data---"sv << std::endl
} << util::hex_vec(payload) << std::endl
<< "---end data---"sv;
else {
cb->second(session, payload);
}
} }
break;
case ENET_EVENT_TYPE_CONNECT: else {
BOOST_LOG(info) << "CLIENT CONNECTED"sv; cb->second(session, payload);
break; }
case ENET_EVENT_TYPE_DISCONNECT: } break;
BOOST_LOG(info) << "CLIENT DISCONNECTED"sv; case ENET_EVENT_TYPE_CONNECT:
// No more clients to send video data to ^_^ BOOST_LOG(info) << "CLIENT CONNECTED"sv;
if(session->state == session::state_e::RUNNING) { break;
session::stop(*session); case ENET_EVENT_TYPE_DISCONNECT:
} BOOST_LOG(info) << "CLIENT DISCONNECTED"sv;
break; // No more clients to send video data to ^_^
case ENET_EVENT_TYPE_NONE: if(session->state == session::state_e::RUNNING) {
break; session::stop(*session);
}
break;
case ENET_EVENT_TYPE_NONE:
break;
} }
} }
} }
@ -302,11 +301,11 @@ struct fec_t {
util::buffer_t<char> shards; util::buffer_t<char> shards;
char *data(size_t el) { char *data(size_t el) {
return &shards[el*blocksize]; return &shards[el * blocksize];
} }
std::string_view operator[](size_t el) const { std::string_view operator[](size_t el) const {
return { &shards[el*blocksize], blocksize }; return { &shards[el * blocksize], blocksize };
} }
size_t size() const { size_t size() const {
@ -314,38 +313,42 @@ struct fec_t {
} }
}; };
fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage) { static fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage) {
auto payload_size = payload.size(); auto payload_size = payload.size();
auto pad = payload_size % blocksize != 0; auto pad = payload_size % blocksize != 0;
auto data_shards = payload_size / blocksize + (pad ? 1 : 0); auto data_shards = payload_size / blocksize + (pad ? 1 : 0);
auto parity_shards = (data_shards * fecpercentage + 99) / 100; auto parity_shards = (data_shards * fecpercentage + 99) / 100;
auto nr_shards = data_shards + parity_shards; auto nr_shards = data_shards + parity_shards;
if(nr_shards > DATA_SHARDS_MAX) { if(nr_shards > DATA_SHARDS_MAX) {
BOOST_LOG(error) BOOST_LOG(warning)
<< "Number of fragments for reed solomon exceeds DATA_SHARDS_MAX"sv << std::endl << "Number of fragments for reed solomon exceeds DATA_SHARDS_MAX"sv << std::endl
<< nr_shards << " > "sv << DATA_SHARDS_MAX; << nr_shards << " > "sv << DATA_SHARDS_MAX
<< ", skipping error correction"sv;
return { 0 }; nr_shards = data_shards;
fecpercentage = 0;
} }
util::buffer_t<char> shards { nr_shards * blocksize }; util::buffer_t<char> shards { nr_shards * blocksize };
util::buffer_t<uint8_t*> shards_p { nr_shards }; util::buffer_t<uint8_t *> shards_p { nr_shards };
// copy payload + padding // copy payload + padding
auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards)); auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards));
std::fill(next, std::end(shards), 0); // padding with zero std::fill(next, std::end(shards), 0); // padding with zero
for(auto x = 0; x < nr_shards; ++x) { for(auto x = 0; x < nr_shards; ++x) {
shards_p[x] = (uint8_t*)&shards[x * blocksize]; shards_p[x] = (uint8_t *)&shards[x * blocksize];
} }
// packets = parity_shards + data_shards if(data_shards + parity_shards <= DATA_SHARDS_MAX) {
rs_t rs { reed_solomon_new(data_shards, parity_shards) }; // packets = parity_shards + data_shards
rs_t rs { reed_solomon_new(data_shards, parity_shards) };
reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize); reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize);
}
return { return {
data_shards, data_shards,
@ -355,11 +358,11 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen
std::move(shards) std::move(shards)
}; };
} }
} } // namespace fec
template<class F> template<class F>
std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data, F &&f) { std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data, F &&f) {
auto pad = data.size() % slice_size != 0; auto pad = data.size() % slice_size != 0;
auto elements = data.size() / slice_size + (pad ? 1 : 0); auto elements = data.size() / slice_size + (pad ? 1 : 0);
std::vector<uint8_t> result; std::vector<uint8_t> result;
@ -367,20 +370,20 @@ std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std
auto next = std::begin(data); auto next = std::begin(data);
for(auto x = 0; x < elements - 1; ++x) { for(auto x = 0; x < elements - 1; ++x) {
void *p = &result[x*(insert_size + slice_size)]; void *p = &result[x * (insert_size + slice_size)];
f(p, x, elements); f(p, x, elements);
std::copy(next, next + slice_size, (char*)p + insert_size); std::copy(next, next + slice_size, (char *)p + insert_size);
next += slice_size; next += slice_size;
} }
auto x = elements - 1; auto x = elements - 1;
void *p = &result[x*(insert_size + slice_size)]; void *p = &result[x * (insert_size + slice_size)];
f(p, x, elements); f(p, x, elements);
std::copy(next, std::end(data), (char*)p + insert_size); std::copy(next, std::end(data), (char *)p + insert_size);
return result; return result;
} }
@ -389,7 +392,7 @@ std::vector<uint8_t> replace(const std::string_view &original, const std::string
std::vector<uint8_t> replaced; std::vector<uint8_t> replaced;
auto begin = std::begin(original); auto begin = std::begin(original);
auto next = std::search(begin, std::end(original), std::begin(old), std::end(old)); auto next = std::search(begin, std::end(original), std::begin(old), std::end(old));
std::copy(begin, next, std::back_inserter(replaced)); std::copy(begin, next, std::back_inserter(replaced));
std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced)); std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced));
@ -398,7 +401,7 @@ std::vector<uint8_t> replace(const std::string_view &original, const std::string
return replaced; return replaced;
} }
void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *server) { void controlBroadcastThread(control_server_t *server) {
server->map(packetTypes[IDX_START_A], [&](session_t *session, const std::string_view &payload) { server->map(packetTypes[IDX_START_A], [&](session_t *session, const std::string_view &payload) {
BOOST_LOG(debug) << "type [IDX_START_A]"sv; BOOST_LOG(debug) << "type [IDX_START_A]"sv;
}); });
@ -408,8 +411,8 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
}); });
server->map(packetTypes[IDX_LOSS_STATS], [&](session_t *session, const std::string_view &payload) { server->map(packetTypes[IDX_LOSS_STATS], [&](session_t *session, const std::string_view &payload) {
int32_t *stats = (int32_t*)payload.data(); int32_t *stats = (int32_t *)payload.data();
auto count = stats[0]; auto count = stats[0];
std::chrono::milliseconds t { stats[1] }; std::chrono::milliseconds t { stats[1] };
auto lastGoodFrame = stats[3]; auto lastGoodFrame = stats[3];
@ -424,9 +427,9 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
}); });
server->map(packetTypes[IDX_INVALIDATE_REF_FRAMES], [&](session_t *session, const std::string_view &payload) { server->map(packetTypes[IDX_INVALIDATE_REF_FRAMES], [&](session_t *session, const std::string_view &payload) {
auto frames = (std::int64_t *)payload.data(); auto frames = (std::int64_t *)payload.data();
auto firstFrame = frames[0]; auto firstFrame = frames[0];
auto lastFrame = frames[1]; auto lastFrame = frames[1];
BOOST_LOG(debug) BOOST_LOG(debug)
<< "type [IDX_INVALIDATE_REF_FRAMES]"sv << std::endl << "type [IDX_INVALIDATE_REF_FRAMES]"sv << std::endl
@ -439,7 +442,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) { server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) {
BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv; BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv;
int32_t tagged_cipher_length = util::endian::big(*(int32_t*)payload.data()); int32_t tagged_cipher_length = util::endian::big(*(int32_t *)payload.data());
std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length }; std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length };
crypto::cipher_t cipher { session->gcm_key }; crypto::cipher_t cipher { session->gcm_key };
@ -462,6 +465,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
input::passthrough(session->input, std::move(plaintext)); input::passthrough(session->input, std::move(plaintext));
}); });
auto shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
while(!shutdown_event->peek()) { while(!shutdown_event->peek()) {
{ {
auto lg = server->_map_addr_session.lock(); auto lg = server->_map_addr_session.lock();
@ -498,12 +502,12 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
payload[0] = packetTypes[IDX_TERMINATION]; payload[0] = packetTypes[IDX_TERMINATION];
payload[1] = reason; payload[1] = reason;
server->send(std::string_view {(char*)payload.data(), payload.size()}); server->send(std::string_view { (char *)payload.data(), payload.size() });
auto lg = server->_map_addr_session.lock(); auto lg = server->_map_addr_session.lock();
for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) { for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) {
auto session = pos->second.second; auto session = pos->second.second;
session->shutdown_event.raise(true); session->shutdown_event->raise(true);
} }
} }
@ -518,7 +522,9 @@ void recvThread(broadcast_ctx_t &ctx) {
auto &video_sock = ctx.video_sock; auto &video_sock = ctx.video_sock;
auto &audio_sock = ctx.audio_sock; auto &audio_sock = ctx.audio_sock;
auto &message_queue_queue = ctx.message_queue_queue; auto &message_queue_queue = ctx.message_queue_queue;
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
auto &io = ctx.io; auto &io = ctx.io;
udp::endpoint peer; udp::endpoint peer;
@ -532,34 +538,34 @@ void recvThread(broadcast_ctx_t &ctx) {
TUPLE_3D_REF(socket_type, addr, message_queue, *message_queue_opt); TUPLE_3D_REF(socket_type, addr, message_queue, *message_queue_opt);
switch(socket_type) { switch(socket_type) {
case socket_e::video: case socket_e::video:
if(message_queue) { if(message_queue) {
peer_to_video_session.emplace(addr, message_queue); peer_to_video_session.emplace(addr, message_queue);
} }
else { else {
peer_to_video_session.erase(addr); peer_to_video_session.erase(addr);
} }
break; break;
case socket_e::audio: case socket_e::audio:
if(message_queue) { if(message_queue) {
peer_to_audio_session.emplace(addr, message_queue); peer_to_audio_session.emplace(addr, message_queue);
} }
else { else {
peer_to_audio_session.erase(addr); peer_to_audio_session.erase(addr);
} }
break; break;
} }
} }
}; };
auto recv_func_init = [&](udp::socket &sock, int buf_elem, std::map<asio::ip::address, message_queue_t> &peer_to_session) { auto recv_func_init = [&](udp::socket &sock, int buf_elem, std::map<asio::ip::address, message_queue_t> &peer_to_session) {
recv_func[buf_elem] = [&,buf_elem](const boost::system::error_code &ec, size_t bytes) { recv_func[buf_elem] = [&, buf_elem](const boost::system::error_code &ec, size_t bytes) {
auto fg = util::fail_guard([&]() { auto fg = util::fail_guard([&]() {
sock.async_receive_from(asio::buffer(buf[buf_elem]), peer, 0, recv_func[buf_elem]); sock.async_receive_from(asio::buffer(buf[buf_elem]), peer, 0, recv_func[buf_elem]);
}); });
auto type_str = buf_elem ? "AUDIO"sv : "VIDEO"sv; auto type_str = buf_elem ? "AUDIO"sv : "VIDEO"sv;
BOOST_LOG(debug) << "Recv: "sv << peer.address().to_string() << ":"sv << peer.port() << " :: " << type_str; BOOST_LOG(verbose) << "Recv: "sv << peer.address().to_string() << ':' << peer.port() << " :: " << type_str;
populate_peer_to_session(); populate_peer_to_session();
@ -590,61 +596,58 @@ void recvThread(broadcast_ctx_t &ctx) {
video_sock.async_receive_from(asio::buffer(buf[0]), peer, 0, recv_func[0]); video_sock.async_receive_from(asio::buffer(buf[0]), peer, 0, recv_func[0]);
audio_sock.async_receive_from(asio::buffer(buf[1]), peer, 0, recv_func[1]); audio_sock.async_receive_from(asio::buffer(buf[1]), peer, 0, recv_func[1]);
while(!broadcast_shutdown_event.peek()) { while(!broadcast_shutdown_event->peek()) {
io.run(); io.run();
} }
} }
void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, video::packet_queue_t packets) { void videoBroadcastThread(udp::socket &sock) {
auto shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
auto packets = mail::man->queue<video::packet_t>(mail::video_packets);
while(auto packet = packets->pop()) { while(auto packet = packets->pop()) {
if(shutdown_event->peek()) { if(shutdown_event->peek()) {
break; break;
} }
auto session = (session_t*)packet->channel_data; auto session = (session_t *)packet->channel_data;
auto lowseq = session->video.lowseq; auto lowseq = session->video.lowseq;
std::string_view payload{(char *) packet->data, (size_t) packet->size}; std::string_view payload { (char *)packet->data, (size_t)packet->size };
std::vector<uint8_t> payload_new; std::vector<uint8_t> payload_new;
auto nv_packet_header = "\0017charss"sv; auto nv_packet_header = "\0017charss"sv;
std::copy(std::begin(nv_packet_header), std::end(nv_packet_header), std::back_inserter(payload_new)); std::copy(std::begin(nv_packet_header), std::end(nv_packet_header), std::back_inserter(payload_new));
std::copy(std::begin(payload), std::end(payload), std::back_inserter(payload_new)); std::copy(std::begin(payload), std::end(payload), std::back_inserter(payload_new));
payload = {(char *) payload_new.data(), payload_new.size()}; payload = { (char *)payload_new.data(), payload_new.size() };
// make sure moonlight recognizes the nalu code for IDR frames if(packet->flags & AV_PKT_FLAG_KEY) {
if (packet->flags & AV_PKT_FLAG_KEY) { for(auto &replacement : *packet->replacements) {
// TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix auto frame_old = replacement.old;
std::string_view frame_old = "\000\000\001e"sv; auto frame_new = replacement._new;
std::string_view frame_new = "\000\000\000\001e"sv;
if(session->config.monitor.videoFormat != 0) { payload_new = replace(payload, frame_old, frame_new);
frame_old = "\000\000\001("sv; payload = { (char *)payload_new.data(), payload_new.size() };
frame_new = "\000\000\000\001("sv;
} }
payload_new = replace(payload, frame_old, frame_new);
payload = {(char *) payload_new.data(), payload_new.size()};
} }
// insert packet headers // insert packet headers
auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE; auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE;
auto payload_blocksize = blocksize - sizeof(video_packet_raw_t); auto payload_blocksize = blocksize - sizeof(video_packet_raw_t);
auto fecPercentage = config::stream.fec_percentage; auto fecPercentage = config::stream.fec_percentage;
payload_new = insert(sizeof(video_packet_raw_t), payload_blocksize, payload_new = insert(sizeof(video_packet_raw_t), payload_blocksize,
payload, [&](void *p, int fecIndex, int end) { payload, [&](void *p, int fecIndex, int end) {
video_packet_raw_t *video_packet = (video_packet_raw_t *)p; video_packet_raw_t *video_packet = (video_packet_raw_t *)p;
video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA; video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA;
video_packet->packet.frameIndex = packet->pts; video_packet->packet.frameIndex = packet->pts;
video_packet->packet.streamPacketIndex = ((uint32_t)lowseq + fecIndex) << 8; video_packet->packet.streamPacketIndex = ((uint32_t)lowseq + fecIndex) << 8;
video_packet->packet.fecInfo = ( video_packet->packet.fecInfo = (fecIndex << 12 |
fecIndex << 12 | end << 22 |
end << 22 | fecPercentage << 4);
fecPercentage << 4
);
if(fecIndex == 0) { if(fecIndex == 0) {
video_packet->packet.flags |= FLAG_SOF; video_packet->packet.flags |= FLAG_SOF;
@ -654,11 +657,11 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
video_packet->packet.flags |= FLAG_EOF; video_packet->packet.flags |= FLAG_EOF;
} }
video_packet->rtp.header = FLAG_EXTENSION; video_packet->rtp.header = FLAG_EXTENSION;
video_packet->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + fecIndex); video_packet->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + fecIndex);
}); });
payload = {(char *) payload_new.data(), payload_new.size()}; payload = { (char *)payload_new.data(), payload_new.size() };
auto shards = fec::encode(payload, blocksize, fecPercentage); auto shards = fec::encode(payload, blocksize, fecPercentage);
if(shards.data_shards == 0) { if(shards.data_shards == 0) {
@ -666,17 +669,15 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
continue; continue;
} }
for (auto x = shards.data_shards; x < shards.size(); ++x) { for(auto x = shards.data_shards; x < shards.size(); ++x) {
auto *inspect = (video_packet_raw_t *)shards.data(x); auto *inspect = (video_packet_raw_t *)shards.data(x);
inspect->packet.frameIndex = packet->pts; inspect->packet.frameIndex = packet->pts;
inspect->packet.fecInfo = ( inspect->packet.fecInfo = (x << 12 |
x << 12 | shards.data_shards << 22 |
shards.data_shards << 22 | shards.percentage << 4);
fecPercentage << 4
);
inspect->rtp.header = FLAG_EXTENSION; inspect->rtp.header = FLAG_EXTENSION;
inspect->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + x); inspect->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + x);
} }
@ -697,28 +698,31 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
shutdown_event->raise(true); shutdown_event->raise(true);
} }
void audioBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, audio::packet_queue_t packets) { void audioBroadcastThread(udp::socket &sock) {
while (auto packet = packets->pop()) { auto shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
auto packets = mail::man->queue<audio::packet_t>(mail::audio_packets);
while(auto packet = packets->pop()) {
if(shutdown_event->peek()) { if(shutdown_event->peek()) {
break; break;
} }
TUPLE_2D_REF(channel_data, packet_data, *packet); TUPLE_2D_REF(channel_data, packet_data, *packet);
auto session = (session_t*)channel_data; auto session = (session_t *)channel_data;
auto frame = session->audio.frame++; auto frame = session->audio.frame++;
audio_packet_t audio_packet { (audio_packet_raw_t*)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) }; audio_packet_t audio_packet { (audio_packet_raw_t *)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) };
audio_packet->rtp.header = 0; audio_packet->rtp.header = 0;
audio_packet->rtp.packetType = 97; audio_packet->rtp.packetType = 97;
audio_packet->rtp.sequenceNumber = util::endian::big(frame); audio_packet->rtp.sequenceNumber = util::endian::big(frame);
audio_packet->rtp.timestamp = 0; audio_packet->rtp.timestamp = 0;
audio_packet->rtp.ssrc = 0; audio_packet->rtp.ssrc = 0;
std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload()); std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload());
sock.send_to(asio::buffer((char*)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer); sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer);
BOOST_LOG(verbose) << "Audio ["sv << frame << "] :: send..."sv; BOOST_LOG(verbose) << "Audio ["sv << frame << "] :: send..."sv;
} }
@ -761,13 +765,11 @@ int start_broadcast(broadcast_ctx_t &ctx) {
return -1; return -1;
} }
ctx.video_packets = std::make_shared<video::packet_queue_t::element_type>(30);
ctx.audio_packets = std::make_shared<audio::packet_queue_t::element_type>(30);
ctx.message_queue_queue = std::make_shared<message_queue_queue_t::element_type>(30); ctx.message_queue_queue = std::make_shared<message_queue_queue_t::element_type>(30);
ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets }; ctx.video_thread = std::thread { videoBroadcastThread, std::ref(ctx.video_sock) };
ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets }; ctx.audio_thread = std::thread { audioBroadcastThread, std::ref(ctx.audio_sock) };
ctx.control_thread = std::thread { controlBroadcastThread, &broadcast_shutdown_event, &ctx.control_server }; ctx.control_thread = std::thread { controlBroadcastThread, &ctx.control_server };
ctx.recv_thread = std::thread { recvThread, std::ref(ctx) }; ctx.recv_thread = std::thread { recvThread, std::ref(ctx) };
@ -775,11 +777,16 @@ int start_broadcast(broadcast_ctx_t &ctx) {
} }
void end_broadcast(broadcast_ctx_t &ctx) { void end_broadcast(broadcast_ctx_t &ctx) {
broadcast_shutdown_event.raise(true); auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
broadcast_shutdown_event->raise(true);
auto video_packets = mail::man->queue<video::packet_t>(mail::video_packets);
auto audio_packets = mail::man->queue<audio::packet_t>(mail::audio_packets);
// Minimize delay stopping video/audio threads // Minimize delay stopping video/audio threads
ctx.video_packets->stop(); video_packets->stop();
ctx.audio_packets->stop(); audio_packets->stop();
ctx.message_queue_queue->stop(); ctx.message_queue_queue->stop();
ctx.io.stop(); ctx.io.stop();
@ -787,8 +794,8 @@ void end_broadcast(broadcast_ctx_t &ctx) {
ctx.video_sock.close(); ctx.video_sock.close();
ctx.audio_sock.close(); ctx.audio_sock.close();
ctx.video_packets.reset(); video_packets.reset();
ctx.audio_packets.reset(); audio_packets.reset();
BOOST_LOG(debug) << "Waiting for main listening thread to end..."sv; BOOST_LOG(debug) << "Waiting for main listening thread to end..."sv;
ctx.recv_thread.join(); ctx.recv_thread.join();
@ -800,7 +807,7 @@ void end_broadcast(broadcast_ctx_t &ctx) {
ctx.control_thread.join(); ctx.control_thread.join();
BOOST_LOG(debug) << "All broadcasting threads ended"sv; BOOST_LOG(debug) << "All broadcasting threads ended"sv;
broadcast_shutdown_event.reset(); broadcast_shutdown_event->reset();
} }
int recv_ping(decltype(broadcast)::ptr_t ref, socket_e type, asio::ip::address &addr, std::chrono::milliseconds timeout) { int recv_ping(decltype(broadcast)::ptr_t ref, socket_e type, asio::ip::address &addr, std::chrono::milliseconds timeout) {
@ -842,7 +849,7 @@ void videoThread(session_t *session, std::string addr_str) {
while_starting_do_nothing(session->state); while_starting_do_nothing(session->state);
auto addr = asio::ip::make_address(addr_str); auto addr = asio::ip::make_address(addr_str);
auto ref = broadcast.ref(); auto ref = broadcast.ref();
auto port = recv_ping(ref, socket_e::video, addr, config::stream.ping_timeout); auto port = recv_ping(ref, socket_e::video, addr, config::stream.ping_timeout);
if(port < 0) { if(port < 0) {
return; return;
@ -852,7 +859,7 @@ void videoThread(session_t *session, std::string addr_str) {
session->video.peer.port(port); session->video.peer.port(port);
BOOST_LOG(debug) << "Start capturing Video"sv; BOOST_LOG(debug) << "Start capturing Video"sv;
video::capture(&session->shutdown_event, ref->video_packets, session->video.idr_events, session->config.monitor, session); video::capture(session->mail, session->config.monitor, session);
} }
void audioThread(session_t *session, std::string addr_str) { void audioThread(session_t *session, std::string addr_str) {
@ -864,7 +871,7 @@ void audioThread(session_t *session, std::string addr_str) {
auto addr = asio::ip::make_address(addr_str); auto addr = asio::ip::make_address(addr_str);
auto ref = broadcast.ref(); auto ref = broadcast.ref();
auto port = recv_ping(ref, socket_e::audio, addr, config::stream.ping_timeout); auto port = recv_ping(ref, socket_e::audio, addr, config::stream.ping_timeout);
if(port < 0) { if(port < 0) {
return; return;
@ -874,7 +881,7 @@ void audioThread(session_t *session, std::string addr_str) {
session->audio.peer.port(port); session->audio.peer.port(port);
BOOST_LOG(debug) << "Start capturing Audio"sv; BOOST_LOG(debug) << "Start capturing Audio"sv;
audio::capture(&session->shutdown_event, ref->audio_packets, session->config.audio, session); audio::capture(session->mail, session->config.audio, session);
} }
namespace session { namespace session {
@ -884,14 +891,13 @@ state_e state(session_t &session) {
void stop(session_t &session) { void stop(session_t &session) {
while_starting_do_nothing(session.state); while_starting_do_nothing(session.state);
auto expected = state_e::RUNNING;
auto expected = state_e::RUNNING;
auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING); auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING);
if(already_stopping) { if(already_stopping) {
return; return;
} }
session.shutdown_event.raise(true); session.shutdown_event->raise(true);
} }
void join(session_t &session) { void join(session_t &session) {
@ -901,11 +907,14 @@ void join(session_t &session) {
session.audioThread.join(); session.audioThread.join();
BOOST_LOG(debug) << "Waiting for control to end..."sv; BOOST_LOG(debug) << "Waiting for control to end..."sv;
session.controlEnd.view(); session.controlEnd.view();
//Reset input on session stop to avoid stuck repeated keys
BOOST_LOG(debug) << "Resetting Input..."sv;
input::reset(session.input);
BOOST_LOG(debug) << "Session ended"sv; BOOST_LOG(debug) << "Session ended"sv;
} }
int start(session_t &session, const std::string &addr_string) { int start(session_t &session, const std::string &addr_string) {
session.input = input::alloc(); session.input = input::alloc(session.mail);
session.broadcast_ref = broadcast.ref(); session.broadcast_ref = broadcast.ref();
if(!session.broadcast_ref) { if(!session.broadcast_ref) {
@ -916,8 +925,8 @@ int start(session_t &session, const std::string &addr_string) {
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout; session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
session.audioThread = std::thread {audioThread, &session, addr_string}; session.audioThread = std::thread { audioThread, &session, addr_string };
session.videoThread = std::thread {videoThread, &session, addr_string}; session.videoThread = std::thread { videoThread, &session, addr_string };
session.state.store(state_e::RUNNING, std::memory_order_relaxed); session.state.store(state_e::RUNNING, std::memory_order_relaxed);
@ -927,19 +936,25 @@ int start(session_t &session, const std::string &addr_string) {
std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv) { std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv) {
auto session = std::make_shared<session_t>(); auto session = std::make_shared<session_t>();
session->config = config; auto mail = std::make_shared<safe::mail_raw_t>();
session->gcm_key = gcm_key;
session->iv = iv;
session->video.idr_events = std::make_shared<video::idr_event_t::element_type>(); session->shutdown_event = mail->event<bool>(mail::shutdown);
session->video.lowseq = 0;
session->config = config;
session->gcm_key = gcm_key;
session->iv = iv;
session->video.idr_events = mail->event<video::idr_t>(mail::idr);
session->video.lowseq = 0;
session->audio.frame = 1; session->audio.frame = 1;
session->control.peer = nullptr; session->control.peer = nullptr;
session->state.store(state_e::STOPPED, std::memory_order_relaxed); session->state.store(state_e::STOPPED, std::memory_order_relaxed);
session->mail = std::move(mail);
return session; return session;
} }
} } // namespace session
} } // namespace stream

View file

@ -7,9 +7,9 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "video.h"
#include "audio.h" #include "audio.h"
#include "crypto.h" #include "crypto.h"
#include "video.h"
namespace stream { namespace stream {
struct session_t; struct session_t;
@ -18,7 +18,6 @@ struct config_t {
video::config_t monitor; video::config_t monitor;
int packetsize; int packetsize;
bool sops;
std::optional<int> gcmap; std::optional<int> gcmap;
}; };
@ -35,9 +34,7 @@ int start(session_t &session, const std::string &addr_string);
void stop(session_t &session); void stop(session_t &session);
void join(session_t &session); void join(session_t &session);
state_e state(session_t &session); state_e state(session_t &session);
} } // namespace session
} // namespace stream
extern safe::signal_t broadcast_shutdown_event;
}
#endif //SUNSHINE_STREAM_H #endif //SUNSHINE_STREAM_H

View file

@ -5,9 +5,9 @@
#ifndef SUNSHINE_SYNC_H #ifndef SUNSHINE_SYNC_H
#define SUNSHINE_SYNC_H #define SUNSHINE_SYNC_H
#include <utility>
#include <mutex>
#include <array> #include <array>
#include <mutex>
#include <utility>
namespace util { namespace util {
@ -21,8 +21,8 @@ public:
return std::lock_guard { _lock }; return std::lock_guard { _lock };
} }
template<class ...Args> template<class... Args>
sync_t(Args&&... args) : raw {std::forward<Args>(args)... } {} sync_t(Args &&...args) : raw { std::forward<Args>(args)... } {}
sync_t &operator=(sync_t &&other) noexcept { sync_t &operator=(sync_t &&other) noexcept {
std::lock(_lock, other._lock); std::lock(_lock, other._lock);
@ -84,11 +84,12 @@ public:
} }
value_t raw; value_t raw;
private: private:
mutex_t _lock; mutex_t _lock;
}; };
} } // namespace util
#endif //T_MAN_SYNC_H #endif //T_MAN_SYNC_H

View file

@ -1,18 +1,18 @@
#ifndef KITTY_TASK_POOL_H #ifndef KITTY_TASK_POOL_H
#define KITTY_TASK_POOL_H #define KITTY_TASK_POOL_H
#include <deque>
#include <vector>
#include <future>
#include <chrono> #include <chrono>
#include <utility> #include <deque>
#include <functional> #include <functional>
#include <future>
#include <mutex> #include <mutex>
#include <type_traits>
#include <optional> #include <optional>
#include <type_traits>
#include <utility>
#include <vector>
#include "utility.h"
#include "move_by_copy.h" #include "move_by_copy.h"
#include "utility.h"
namespace util { namespace util {
class _ImplBase { class _ImplBase {
@ -29,8 +29,7 @@ class _Impl : public _ImplBase {
Function _func; Function _func;
public: public:
_Impl(Function &&f) : _func(std::forward<Function>(f)) {}
_Impl(Function&& f) : _func(std::forward<Function>(f)) { }
void run() override { void run() override {
_func(); _func();
@ -40,7 +39,7 @@ public:
class TaskPool { class TaskPool {
public: public:
typedef std::unique_ptr<_ImplBase> __task; typedef std::unique_ptr<_ImplBase> __task;
typedef _ImplBase* task_id_t; typedef _ImplBase *task_id_t;
typedef std::chrono::steady_clock::time_point __time_point; typedef std::chrono::steady_clock::time_point __time_point;
@ -53,9 +52,10 @@ public:
timer_task_t(task_id_t task_id, std::future<R> &future) : task_id { task_id }, future { std::move(future) } {} timer_task_t(task_id_t task_id, std::future<R> &future) : task_id { task_id }, future { std::move(future) } {}
}; };
protected: protected:
std::deque<__task> _tasks; std::deque<__task> _tasks;
std::vector<std::pair<__time_point, __task>> _timer_tasks; std::vector<std::pair<__time_point, __task>> _timer_tasks;
std::mutex _task_mutex; std::mutex _task_mutex;
public: public:
@ -70,8 +70,8 @@ public:
} }
template<class Function, class... Args> template<class Function, class... Args>
auto push(Function && newTask, Args &&... args) { auto push(Function &&newTask, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function"); static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>; using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>; using task_t = std::packaged_task<__return()>;
@ -81,12 +81,12 @@ public:
}; };
task_t task(std::move(bind)); task_t task(std::move(bind));
auto future = task.get_future(); auto future = task.get_future();
std::lock_guard<std::mutex> lg(_task_mutex); std::lock_guard<std::mutex> lg(_task_mutex);
_tasks.emplace_back(toRunnable(std::move(task))); _tasks.emplace_back(toRunnable(std::move(task)));
return future; return future;
} }
@ -107,14 +107,14 @@ public:
* @return an id to potentially delay the task * @return an id to potentially delay the task
*/ */
template<class Function, class X, class Y, class... Args> template<class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) { auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function"); static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>; using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>; using task_t = std::packaged_task<__return()>;
__time_point time_point; __time_point time_point;
if constexpr (std::is_floating_point_v<X>) { if constexpr(std::is_floating_point_v<X>) {
time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration); time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
} }
else { else {
@ -127,7 +127,7 @@ public:
task_t task(std::move(bind)); task_t task(std::move(bind));
auto future = task.get_future(); auto future = task.get_future();
auto runnable = toRunnable(std::move(task)); auto runnable = toRunnable(std::move(task));
task_id_t task_id = &*runnable; task_id_t task_id = &*runnable;
@ -160,13 +160,14 @@ public:
} }
// smaller time goes to the back // smaller time goes to the back
auto prev = it -1; auto prev = it - 1;
while(it > _timer_tasks.cbegin()) { while(it > _timer_tasks.cbegin()) {
if(std::get<0>(*it) > std::get<0>(*prev)) { if(std::get<0>(*it) > std::get<0>(*prev)) {
std::swap(*it, *prev); std::swap(*it, *prev);
} }
--prev; --it; --prev;
--it;
} }
} }
@ -201,20 +202,20 @@ public:
std::optional<__task> pop() { std::optional<__task> pop() {
std::lock_guard lg(_task_mutex); std::lock_guard lg(_task_mutex);
if(!_tasks.empty()) { if(!_tasks.empty()) {
__task task = std::move(_tasks.front()); __task task = std::move(_tasks.front());
_tasks.pop_front(); _tasks.pop_front();
return std::move(task); return std::move(task);
} }
if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) { if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
__task task = std::move(std::get<1>(_timer_tasks.back())); __task task = std::move(std::get<1>(_timer_tasks.back()));
_timer_tasks.pop_back(); _timer_tasks.pop_back();
return std::move(task); return std::move(task);
} }
return std::nullopt; return std::nullopt;
} }
@ -233,12 +234,12 @@ public:
return std::get<0>(_timer_tasks.back()); return std::get<0>(_timer_tasks.back());
} }
private:
private:
template<class Function> template<class Function>
std::unique_ptr<_ImplBase> toRunnable(Function &&f) { std::unique_ptr<_ImplBase> toRunnable(Function &&f) {
return std::make_unique<_Impl<Function>>(std::forward<Function&&>(f)); return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
} }
}; };
} } // namespace util
#endif #endif

View file

@ -1,8 +1,8 @@
#ifndef KITTY_THREAD_POOL_H #ifndef KITTY_THREAD_POOL_H
#define KITTY_THREAD_POOL_H #define KITTY_THREAD_POOL_H
#include <thread>
#include "task_pool.h" #include "task_pool.h"
#include <thread>
namespace util { namespace util {
/* /*
@ -12,32 +12,33 @@ namespace util {
class ThreadPool : public TaskPool { class ThreadPool : public TaskPool {
public: public:
typedef TaskPool::__task __task; typedef TaskPool::__task __task;
private: private:
std::vector<std::thread> _thread; std::vector<std::thread> _thread;
std::condition_variable _cv; std::condition_variable _cv;
std::mutex _lock; std::mutex _lock;
bool _continue; bool _continue;
public: public:
ThreadPool() : _continue { false } {} ThreadPool() : _continue { false } {}
explicit ThreadPool(int threads) : _thread(threads), _continue { true } { explicit ThreadPool(int threads) : _thread(threads), _continue { true } {
for (auto &t : _thread) { for(auto &t : _thread) {
t = std::thread(&ThreadPool::_main, this); t = std::thread(&ThreadPool::_main, this);
} }
} }
~ThreadPool() noexcept { ~ThreadPool() noexcept {
if (!_continue) return; if(!_continue) return;
stop(); stop();
join(); join();
} }
template<class Function, class... Args> template<class Function, class... Args>
auto push(Function && newTask, Args &&... args) { auto push(Function &&newTask, Args &&...args) {
std::lock_guard lg(_lock); std::lock_guard lg(_lock);
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...); auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
@ -52,7 +53,7 @@ public:
} }
template<class Function, class X, class Y, class... Args> template<class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) { auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
std::lock_guard lg(_lock); std::lock_guard lg(_lock);
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...); auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
@ -79,15 +80,14 @@ public:
} }
void join() { void join() {
for (auto & t : _thread) { for(auto &t : _thread) {
t.join(); t.join();
} }
} }
public: public:
void _main() { void _main() {
while (_continue) { while(_continue) {
if(auto task = this->pop()) { if(auto task = this->pop()) {
(*task)->run(); (*task)->run();
} }
@ -117,5 +117,5 @@ public:
} }
} }
}; };
} } // namespace util
#endif #endif

View file

@ -5,28 +5,29 @@
#ifndef SUNSHINE_THREAD_SAFE_H #ifndef SUNSHINE_THREAD_SAFE_H
#define SUNSHINE_THREAD_SAFE_H #define SUNSHINE_THREAD_SAFE_H
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic> #include <atomic>
#include <condition_variable>
#include <functional> #include <functional>
#include <map>
#include <mutex>
#include <vector>
#include "utility.h" #include "utility.h"
namespace safe { namespace safe {
template<class T> template<class T>
class event_t { class event_t {
public:
using status_t = util::optional_t<T>; using status_t = util::optional_t<T>;
public: template<class... Args>
template<class...Args>
void raise(Args &&...args) { void raise(Args &&...args) {
std::lock_guard lg { _lock }; std::lock_guard lg { _lock };
if(!_continue) { if(!_continue) {
return; return;
} }
if constexpr (std::is_same_v<std::optional<T>, status_t>) { if constexpr(std::is_same_v<std::optional<T>, status_t>) {
_status = std::make_optional<T>(std::forward<Args>(args)...); _status = std::make_optional<T>(std::forward<Args>(args)...);
} }
else { else {
@ -38,42 +39,42 @@ public:
// pop and view shoud not be used interchangebly // pop and view shoud not be used interchangebly
status_t pop() { status_t pop() {
std::unique_lock ul{ _lock }; std::unique_lock ul { _lock };
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
while (!_status) { while(!_status) {
_cv.wait(ul); _cv.wait(ul);
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
} }
auto val = std::move(_status); auto val = std::move(_status);
_status = util::false_v<status_t>; _status = util::false_v<status_t>;
return val; return val;
} }
// pop and view shoud not be used interchangebly // pop and view shoud not be used interchangebly
template<class Rep, class Period> template<class Rep, class Period>
status_t pop(std::chrono::duration<Rep, Period> delay) { status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul{ _lock }; std::unique_lock ul { _lock };
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
while (!_status) { while(!_status) {
if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
} }
auto val = std::move(_status); auto val = std::move(_status);
_status = util::false_v<status_t>; _status = util::false_v<status_t>;
return val; return val;
} }
@ -81,14 +82,14 @@ public:
const status_t &view() { const status_t &view() {
std::unique_lock ul { _lock }; std::unique_lock ul { _lock };
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
while (!_status) { while(!_status) {
_cv.wait(ul); _cv.wait(ul);
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
} }
@ -97,13 +98,11 @@ public:
} }
bool peek() { bool peek() {
std::lock_guard lg { _lock };
return _continue && (bool)_status; return _continue && (bool)_status;
} }
void stop() { void stop() {
std::lock_guard lg{ _lock }; std::lock_guard lg { _lock };
_continue = false; _continue = false;
@ -111,7 +110,7 @@ public:
} }
void reset() { void reset() {
std::lock_guard lg{ _lock }; std::lock_guard lg { _lock };
_continue = true; _continue = true;
@ -121,8 +120,8 @@ public:
[[nodiscard]] bool running() const { [[nodiscard]] bool running() const {
return _continue; return _continue;
} }
private:
private:
bool _continue { true }; bool _continue { true };
status_t _status { util::false_v<status_t> }; status_t _status { util::false_v<status_t> };
@ -131,14 +130,101 @@ private:
}; };
template<class T> template<class T>
class queue_t { class alarm_raw_t {
public:
using status_t = util::optional_t<T>; using status_t = util::optional_t<T>;
alarm_raw_t() : _status { util::false_v<status_t> } {}
void ring(const status_t &status) {
std::lock_guard lg(_lock);
_status = status;
_cv.notify_one();
}
void ring(status_t &&status) {
std::lock_guard lg(_lock);
_status = std::move(status);
_cv.notify_one();
}
template<class Rep, class Period>
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time) {
std::unique_lock ul(_lock);
return _cv.wait_for(ul, rel_time, [this]() { return (bool)status(); });
}
template<class Rep, class Period, class Pred>
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
std::unique_lock ul(_lock);
return _cv.wait_for(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
}
template<class Rep, class Period>
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time) {
std::unique_lock ul(_lock);
return _cv.wait_until(ul, rel_time, [this]() { return (bool)status(); });
}
template<class Rep, class Period, class Pred>
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
std::unique_lock ul(_lock);
return _cv.wait_until(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
}
auto wait() {
std::unique_lock ul(_lock);
_cv.wait(ul, [this]() { return (bool)status(); });
}
template<class Pred>
auto wait(Pred &&pred) {
std::unique_lock ul(_lock);
_cv.wait(ul, [this, &pred]() { return (bool)status() || pred(); });
}
const status_t &status() const {
return _status;
}
status_t &status() {
return _status;
}
void reset() {
_status = status_t {};
}
private:
std::mutex _lock;
std::condition_variable _cv;
status_t _status;
};
template<class T>
using alarm_t = std::shared_ptr<alarm_raw_t<T>>;
template<class T>
alarm_t<T> make_alarm() {
return std::make_shared<alarm_raw_t<T>>();
}
template<class T>
class queue_t {
public: public:
using status_t = util::optional_t<T>;
queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {} queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {}
template<class ...Args> template<class... Args>
void raise(Args &&... args) { void raise(Args &&...args) {
std::lock_guard ul { _lock }; std::lock_guard ul { _lock };
if(!_continue) { if(!_continue) {
@ -155,8 +241,6 @@ public:
} }
bool peek() { bool peek() {
std::lock_guard lg { _lock };
return _continue && !_queue.empty(); return _continue && !_queue.empty();
} }
@ -164,12 +248,12 @@ public:
status_t pop(std::chrono::duration<Rep, Period> delay) { status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul { _lock }; std::unique_lock ul { _lock };
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
while (_queue.empty()) { while(_queue.empty()) {
if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
} }
@ -183,14 +267,14 @@ public:
status_t pop() { status_t pop() {
std::unique_lock ul { _lock }; std::unique_lock ul { _lock };
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
while (_queue.empty()) { while(_queue.empty()) {
_cv.wait(ul); _cv.wait(ul);
if (!_continue) { if(!_continue) {
return util::false_v<status_t>; return util::false_v<status_t>;
} }
} }
@ -202,7 +286,6 @@ public:
} }
std::vector<T> &unsafe() { std::vector<T> &unsafe() {
std::lock_guard { _lock };
return _queue; return _queue;
} }
@ -219,7 +302,6 @@ public:
} }
private: private:
bool _continue { true }; bool _continue { true };
std::uint32_t _max_elements; std::uint32_t _max_elements;
@ -235,7 +317,7 @@ public:
using element_type = T; using element_type = T;
using construct_f = std::function<int(element_type &)>; using construct_f = std::function<int(element_type &)>;
using destruct_f = std::function<void(element_type &)>; using destruct_f = std::function<void(element_type &)>;
struct ptr_t { struct ptr_t {
shared_t *owner; shared_t *owner;
@ -252,7 +334,7 @@ public:
return; return;
} }
auto tmp = ptr.owner->ref(); auto tmp = ptr.owner->ref();
tmp.owner = nullptr; tmp.owner = nullptr;
} }
@ -282,7 +364,7 @@ public:
} }
} }
operator bool () const { operator bool() const {
return owner != nullptr; return owner != nullptr;
} }
@ -298,22 +380,22 @@ public:
} }
element_type *get() const { element_type *get() const {
return reinterpret_cast<element_type*>(owner->_object_buf.data()); return reinterpret_cast<element_type *>(owner->_object_buf.data());
} }
element_type *operator->() { element_type *operator->() {
return reinterpret_cast<element_type*>(owner->_object_buf.data()); return reinterpret_cast<element_type *>(owner->_object_buf.data());
} }
}; };
template<class FC, class FD> template<class FC, class FD>
shared_t(FC && fc, FD &&fd) : _construct { std::forward<FC>(fc) }, _destruct { std::forward<FD>(fd) } {} shared_t(FC &&fc, FD &&fd) : _construct { std::forward<FC>(fc) }, _destruct { std::forward<FD>(fd) } {}
[[nodiscard]] ptr_t ref() { [[nodiscard]] ptr_t ref() {
std::lock_guard lg { _lock }; std::lock_guard lg { _lock };
if(!_count) { if(!_count) {
new(_object_buf.data()) element_type; new(_object_buf.data()) element_type;
if(_construct(*reinterpret_cast<element_type*>(_object_buf.data()))) { if(_construct(*reinterpret_cast<element_type *>(_object_buf.data()))) {
return ptr_t { nullptr }; return ptr_t { nullptr };
} }
} }
@ -322,6 +404,7 @@ public:
return ptr_t { this }; return ptr_t { this };
} }
private: private:
construct_f _construct; construct_f _construct;
destruct_f _destruct; destruct_f _destruct;
@ -340,6 +423,89 @@ auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
} }
using signal_t = event_t<bool>; 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 #endif //SUNSHINE_THREAD_SAFE_H

View file

@ -1,63 +1,109 @@
#ifndef UTILITY_H #ifndef UTILITY_H
#define UTILITY_H #define UTILITY_H
#include <algorithm>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <optional>
#include <string_view>
#include <type_traits>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include <memory>
#include <type_traits>
#include <algorithm>
#include <optional>
#include <mutex>
#include <condition_variable>
#include <string_view>
#define KITTY_WHILE_LOOP(x, y, z) { x;while(y) z } #define KITTY_WHILE_LOOP(x, y, z) \
#define KITTY_DECL_CONSTR(x)\ { \
x(x&&) noexcept = default;\ x; \
x&operator=(x&&) noexcept = default;\ while(y) z \
}
template<typename T>
struct argument_type;
template<typename T, typename U>
struct argument_type<T(U)> { typedef U type; };
#define KITTY_USING_MOVE_T(move_t, t, init_val, z) \
class move_t { \
public: \
using element_type = typename argument_type<void(t)>::type; \
\
move_t() : el { init_val } {} \
template<class... Args> \
move_t(Args &&...args) : el { std::forward<Args>(args)... } {} \
move_t(const move_t &) = delete; \
\
move_t(move_t &&other) noexcept : el { std::move(other.el) } { \
other.el = element_type { init_val }; \
} \
\
move_t &operator=(const move_t &) = delete; \
\
move_t &operator=(move_t &&other) { \
std::swap(el, other.el); \
return *this; \
} \
element_type *operator->() { return &el; } \
const element_type *operator->() const { return &el; } \
\
~move_t() z \
\
element_type el; \
}
#define KITTY_DECL_CONSTR(x) \
x(x &&) noexcept = default; \
x &operator=(x &&) noexcept = default; \
x(); x();
#define KITTY_DEFAULT_CONSTR(x)\ #define KITTY_DEFAULT_CONSTR(x) \
x(x&&) noexcept = default;\ x(x &&) noexcept = default; \
x&operator=(x&&) noexcept = default;\ x &operator=(x &&) noexcept = default; \
x() = default; x() = default;
#define KITTY_DEFAULT_CONSTR_THROW(x)\ #define KITTY_DEFAULT_CONSTR_THROW(x) \
x(x&&) = default;\ x(x &&) = default; \
x&operator=(x&&) = default;\ x &operator=(x &&) = default; \
x() = default; x() = default;
#define TUPLE_2D(a,b, expr)\ #define TUPLE_2D(a, b, expr) \
decltype(expr) a##_##b = expr;\ decltype(expr) a##_##b = expr; \
auto &a = std::get<0>(a##_##b);\ auto &a = std::get<0>(a##_##b); \
auto &b = std::get<1>(a##_##b) auto &b = std::get<1>(a##_##b)
#define TUPLE_2D_REF(a,b, expr)\ #define TUPLE_2D_REF(a, b, expr) \
auto &a##_##b = expr;\ auto &a##_##b = expr; \
auto &a = std::get<0>(a##_##b);\ auto &a = std::get<0>(a##_##b); \
auto &b = std::get<1>(a##_##b) auto &b = std::get<1>(a##_##b)
#define TUPLE_3D(a,b,c, expr)\ #define TUPLE_3D(a, b, c, expr) \
decltype(expr) a##_##b##_##c = expr;\ decltype(expr) a##_##b##_##c = expr; \
auto &a = std::get<0>(a##_##b##_##c);\ auto &a = std::get<0>(a##_##b##_##c); \
auto &b = std::get<1>(a##_##b##_##c);\ auto &b = std::get<1>(a##_##b##_##c); \
auto &c = std::get<2>(a##_##b##_##c) auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_3D_REF(a,b,c, expr)\ #define TUPLE_3D_REF(a, b, c, expr) \
auto &a##_##b##_##c = expr;\ auto &a##_##b##_##c = expr; \
auto &a = std::get<0>(a##_##b##_##c);\ auto &a = std::get<0>(a##_##b##_##c); \
auto &b = std::get<1>(a##_##b##_##c);\ auto &b = std::get<1>(a##_##b##_##c); \
auto &c = std::get<2>(a##_##b##_##c) auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_EL(a, b, expr) \
decltype(expr) a##_ = expr; \
auto &a = std::get<b>(a##_)
#define TUPLE_EL_REF(a, b, expr) \
auto &a = std::get<b>(expr)
namespace util { namespace util {
template<template<typename...> class X, class...Y> template<template<typename...> class X, class... Y>
struct __instantiation_of : public std::false_type {}; struct __instantiation_of : public std::false_type {};
template<template<typename...> class X, class... Y> template<template<typename...> class X, class... Y>
struct __instantiation_of<X, X<Y...>> : public std::true_type {}; struct __instantiation_of<X, X<Y...>> : public std::true_type {};
template<template<typename...> class X, class T, class...Y> template<template<typename...> class X, class T, class... Y>
static constexpr auto instantiation_of_v = __instantiation_of<X, T, Y...>::value; static constexpr auto instantiation_of_v = __instantiation_of<X, T, Y...>::value;
template<bool V, class X, class Y> template<bool V, class X, class Y>
@ -76,45 +122,16 @@ struct __either<false, X, Y> {
template<bool V, class X, class Y> template<bool V, class X, class Y>
using either_t = typename __either<V, X, Y>::type; using either_t = typename __either<V, X, Y>::type;
template<class T, class V = void> template<class... Ts>
struct __false_v; struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
template<class T> overloaded(Ts...) -> overloaded<Ts...>;
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
static constexpr std::nullopt_t value = std::nullopt;
};
template<class T>
struct __false_v<T, std::enable_if_t<
(std::is_pointer_v<T> || instantiation_of_v<std::unique_ptr, T> || instantiation_of_v<std::shared_ptr, T>)
>> {
static constexpr std::nullptr_t value = nullptr;
};
template<class T>
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
static constexpr bool value = false;
};
template<class T>
static constexpr auto false_v = __false_v<T>::value;
template<class T>
using optional_t = either_t<
(std::is_same_v<T, bool> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
std::is_pointer_v<T>),
T, std::optional<T>>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class T> template<class T>
class FailGuard { class FailGuard {
public: public:
FailGuard() = delete; FailGuard() = delete;
FailGuard(T && f) noexcept : _func { std::forward<T>(f) } {} FailGuard(T &&f) noexcept : _func { std::forward<T>(f) } {}
FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } { FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } {
this->failure = other.failure; this->failure = other.failure;
@ -134,12 +151,13 @@ public:
void disable() { failure = false; } void disable() { failure = false; }
bool failure { true }; bool failure { true };
private: private:
T _func; T _func;
}; };
template<class T> template<class T>
[[nodiscard]] auto fail_guard(T && f) { [[nodiscard]] auto fail_guard(T &&f) {
return FailGuard<T> { std::forward<T>(f) }; return FailGuard<T> { std::forward<T>(f) };
} }
@ -149,9 +167,9 @@ void append_struct(std::vector<uint8_t> &buf, const T &_struct) {
buf.reserve(data_len); buf.reserve(data_len);
auto *data = (uint8_t *) & _struct; auto *data = (uint8_t *)&_struct;
for (size_t x = 0; x < data_len; ++x) { for(size_t x = 0; x < data_len; ++x) {
buf.push_back(data[x]); buf.push_back(data[x]);
} }
} }
@ -160,24 +178,26 @@ template<class T>
class Hex { class Hex {
public: public:
typedef T elem_type; typedef T elem_type;
private: private:
const char _bits[16] { const char _bits[16] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
}; };
char _hex[sizeof(elem_type) * 2]; char _hex[sizeof(elem_type) * 2];
public: public:
Hex(const elem_type &elem, bool rev) { Hex(const elem_type &elem, bool rev) {
if(!rev) { if(!rev) {
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem) + sizeof(elem_type) - 1; const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem) + sizeof(elem_type) - 1;
for (auto it = begin(); it < cend();) { for(auto it = begin(); it < cend();) {
*it++ = _bits[*data / 16]; *it++ = _bits[*data / 16];
*it++ = _bits[*data-- % 16]; *it++ = _bits[*data-- % 16];
} }
} }
else { else {
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem); const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem);
for (auto it = begin(); it < cend();) { for(auto it = begin(); it < cend();) {
*it++ = _bits[*data / 16]; *it++ = _bits[*data / 16];
*it++ = _bits[*data++ % 16]; *it++ = _bits[*data++ % 16];
} }
@ -209,7 +229,7 @@ Hex<T> hex(const T &elem, bool rev = false) {
template<class It> template<class It>
std::string hex_vec(It begin, It end, bool rev = false) { std::string hex_vec(It begin, It end, bool rev = false) {
auto str_size = 2*std::distance(begin, end); auto str_size = 2 * std::distance(begin, end);
std::string hex; std::string hex;
@ -220,14 +240,14 @@ std::string hex_vec(It begin, It end, bool rev = false) {
}; };
if(rev) { if(rev) {
for (auto it = std::begin(hex); it < std::end(hex);) { for(auto it = std::begin(hex); it < std::end(hex);) {
*it++ = _bits[((uint8_t)*begin) / 16]; *it++ = _bits[((uint8_t)*begin) / 16];
*it++ = _bits[((uint8_t)*begin++) % 16]; *it++ = _bits[((uint8_t)*begin++) % 16];
} }
} }
else { else {
--end; --end;
for (auto it = std::begin(hex); it < std::end(hex);) { for(auto it = std::begin(hex); it < std::end(hex);) {
*it++ = _bits[((uint8_t)*end) / 16]; *it++ = _bits[((uint8_t)*end) / 16];
*it++ = _bits[((uint8_t)*end--) % 16]; *it++ = _bits[((uint8_t)*end--) % 16];
} }
@ -238,7 +258,7 @@ std::string hex_vec(It begin, It end, bool rev = false) {
} }
template<class C> template<class C>
std::string hex_vec(C&& c, bool rev = false) { std::string hex_vec(C &&c, bool rev = false) {
return hex_vec(std::begin(c), std::end(c), rev); return hex_vec(std::begin(c), std::end(c), rev);
} }
@ -247,7 +267,7 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
std::uint8_t buf[sizeof(T)]; std::uint8_t buf[sizeof(T)];
static char constexpr shift_bit = 'a' - 'A'; static char constexpr shift_bit = 'a' - 'A';
auto is_convertable = [] (char ch) -> bool { auto is_convertable = [](char ch) -> bool {
if(isdigit(ch)) { if(isdigit(ch)) {
return true; return true;
} }
@ -266,9 +286,9 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
return std::nullopt; return std::nullopt;
} }
const char *data = hex.data() + hex.size() -1; const char *data = hex.data() + hex.size() - 1;
auto convert = [] (char ch) -> std::uint8_t { auto convert = [](char ch) -> std::uint8_t {
if(ch >= '0' && ch <= '9') { if(ch >= '0' && ch <= '9') {
return (std::uint8_t)ch - '0'; return (std::uint8_t)ch - '0';
} }
@ -297,7 +317,7 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
std::string buf; std::string buf;
static char constexpr shift_bit = 'a' - 'A'; static char constexpr shift_bit = 'a' - 'A';
auto is_convertable = [] (char ch) -> bool { auto is_convertable = [](char ch) -> bool {
if(isdigit(ch)) { if(isdigit(ch)) {
return true; return true;
} }
@ -314,9 +334,9 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2; auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
buf.resize(buf_size); buf.resize(buf_size);
const char *data = hex.data() + hex.size() -1; const char *data = hex.data() + hex.size() - 1;
auto convert = [] (char ch) -> std::uint8_t { auto convert = [](char ch) -> std::uint8_t {
if(ch >= '0' && ch <= '9') { if(ch >= '0' && ch <= '9') {
return (std::uint8_t)ch - '0'; return (std::uint8_t)ch - '0';
} }
@ -348,49 +368,20 @@ public:
std::size_t operator()(const value_type &value) const { std::size_t operator()(const value_type &value) const {
const auto *p = reinterpret_cast<const char *>(&value); const auto *p = reinterpret_cast<const char *>(&value);
return std::hash<std::string_view>{}(std::string_view { p, sizeof(value_type) }); return std::hash<std::string_view> {}(std::string_view { p, sizeof(value_type) });
} }
}; };
template<class T> template<class T>
auto enm(const T& val) -> const std::underlying_type_t<T>& { auto enm(const T &val) -> const std::underlying_type_t<T> & {
return *reinterpret_cast<const std::underlying_type_t<T>*>(&val); return *reinterpret_cast<const std::underlying_type_t<T> *>(&val);
} }
template<class T> template<class T>
auto enm(T& val) -> std::underlying_type_t<T>& { auto enm(T &val) -> std::underlying_type_t<T> & {
return *reinterpret_cast<std::underlying_type_t<T>*>(&val); return *reinterpret_cast<std::underlying_type_t<T> *>(&val);
} }
template<class ReturnType, class ...Args>
struct Function {
typedef ReturnType (*type)(Args...);
};
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
struct Destroy {
typedef T pointer;
void operator()(pointer p) {
function(p);
}
};
template<class T, typename Function<void, T*>::type function>
using safe_ptr = std::unique_ptr<T, Destroy<T*, void, function>>;
// You cannot specialize an alias
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
using safe_ptr_v2 = std::unique_ptr<T, Destroy<T*, ReturnType, function>>;
template<class T>
void c_free(T *p) {
free(p);
}
template<class T>
using c_ptr = safe_ptr<T, c_free<T>>;
inline std::int64_t from_chars(const char *begin, const char *end) { inline std::int64_t from_chars(const char *begin, const char *end) {
std::int64_t res {}; std::int64_t res {};
std::int64_t mul = 1; std::int64_t mul = 1;
@ -436,13 +427,171 @@ public:
} }
}; };
// Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself
template<typename T, typename D = std::default_delete<T>>
class uniq_ptr {
public:
using element_type = T;
using pointer = element_type *;
using deleter_type = D;
constexpr uniq_ptr() noexcept : _p { nullptr } {}
constexpr uniq_ptr(std::nullptr_t) noexcept : _p { nullptr } {}
uniq_ptr(const uniq_ptr &other) noexcept = delete;
uniq_ptr &operator=(const uniq_ptr &other) noexcept = delete;
template<class V>
uniq_ptr(V *p) noexcept : _p { p } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<element_type, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr(std::unique_ptr<V, deleter_type> &&uniq) noexcept : _p { uniq.release() } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr(uniq_ptr<V, deleter_type> &&other) noexcept : _p { other.release() } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr &operator=(uniq_ptr<V, deleter_type> &&other) noexcept {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
reset(other.release());
return *this;
}
template<class V>
uniq_ptr &operator=(std::unique_ptr<V, deleter_type> &&uniq) noexcept {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
reset(uniq.release());
return *this;
}
~uniq_ptr() {
reset();
}
void reset(pointer p = pointer()) {
if(_p) {
_deleter(_p);
}
_p = p;
}
pointer release() {
auto tmp = _p;
_p = nullptr;
return tmp;
}
pointer get() {
return _p;
}
const pointer get() const {
return _p;
}
const std::add_lvalue_reference_t<element_type> operator*() const {
return *_p;
}
std::add_lvalue_reference_t<element_type> operator*() {
return *_p;
}
const pointer operator->() const {
return _p;
}
pointer operator->() {
return _p;
}
pointer *operator&() const {
return &_p;
}
pointer *operator&() {
return &_p;
}
deleter_type &get_deleter() {
return _deleter;
}
const deleter_type &get_deleter() const {
return _deleter;
}
explicit operator bool() const {
return _p != nullptr;
}
protected:
pointer _p;
deleter_type _deleter;
};
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const std::unique_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const std::unique_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1> &x, const std::unique_ptr<T1, D1> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1> &x, const std::unique_ptr<T1, D1> &y) {
return x.get() != y.get();
}
template<class T, class D>
bool operator==(const uniq_ptr<T, D> &x, std::nullptr_t) {
return !(bool)x;
}
template<class T, class D>
bool operator!=(const uniq_ptr<T, D> &x, std::nullptr_t) {
return (bool)x;
}
template<class T, class D>
bool operator==(std::nullptr_t, const uniq_ptr<T, D> &y) {
return !(bool)y;
}
template<class T, class D>
bool operator!=(std::nullptr_t, const uniq_ptr<T, D> &y) {
return (bool)y;
}
template<class T> template<class T>
class wrap_ptr { class wrap_ptr {
public: public:
using element_type = T; using element_type = T;
using pointer = element_type*; using pointer = element_type *;
using reference = element_type&; using reference = element_type &;
wrap_ptr() : _own_ptr { false }, _p { nullptr } {} wrap_ptr() : _own_ptr { false }, _p { nullptr } {}
wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {} wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {}
@ -458,7 +607,7 @@ public:
_p = other._p; _p = other._p;
_own_ptr = other._own_ptr; _own_ptr = other._own_ptr;
other._own_ptr = false; other._own_ptr = false;
return *this; return *this;
@ -468,7 +617,7 @@ public:
wrap_ptr &operator=(std::unique_ptr<V> &&uniq_ptr) { wrap_ptr &operator=(std::unique_ptr<V> &&uniq_ptr) {
static_assert(std::is_base_of_v<element_type, V>, "element_type must be base class of V"); static_assert(std::is_base_of_v<element_type, V>, "element_type must be base class of V");
_own_ptr = true; _own_ptr = true;
_p = uniq_ptr.release(); _p = uniq_ptr.release();
return *this; return *this;
} }
@ -478,7 +627,7 @@ public:
delete _p; delete _p;
} }
_p = p; _p = p;
_own_ptr = false; _own_ptr = false;
return *this; return *this;
@ -510,12 +659,52 @@ private:
pointer _p; pointer _p;
}; };
template<class T>
constexpr bool is_pointer_v =
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T> ||
std::is_pointer_v<T>;
template<class T, class V = void>
struct __false_v;
template<class T>
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
static constexpr std::nullopt_t value = std::nullopt;
};
template<class T>
struct __false_v<T, std::enable_if_t<is_pointer_v<T>>> {
static constexpr std::nullptr_t value = nullptr;
};
template<class T>
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
static constexpr bool value = false;
};
template<class T>
static constexpr auto false_v = __false_v<T>::value;
template<class T>
using optional_t = either_t<
(std::is_same_v<T, bool> || is_pointer_v<T>),
T, std::optional<T>>;
template<class T> template<class T>
class buffer_t { class buffer_t {
public: public:
buffer_t() : _els { 0 } {}; buffer_t() : _els { 0 } {};
buffer_t(buffer_t&&) noexcept = default; buffer_t(buffer_t &&o) noexcept : _els { o._els }, _buf { std::move(o._buf) } {
buffer_t &operator=(buffer_t&& other) noexcept = default; o._els = 0;
}
buffer_t &operator=(buffer_t &&o) noexcept {
std::swap(_els, o._els);
std::swap(_buf, o._buf);
return *this;
};
explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {} explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {}
explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } { explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {
@ -559,7 +748,6 @@ private:
std::unique_ptr<T[]> _buf; std::unique_ptr<T[]> _buf;
}; };
template<class T> template<class T>
T either(std::optional<T> &&l, T &&r) { T either(std::optional<T> &&l, T &&r) {
if(l) { if(l) {
@ -569,27 +757,77 @@ T either(std::optional<T> &&l, T &&r) {
return std::forward<T>(r); return std::forward<T>(r);
} }
template<class ReturnType, class... Args>
struct Function {
typedef ReturnType (*type)(Args...);
};
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
struct Destroy {
typedef T pointer;
void operator()(pointer p) {
function(p);
}
};
template<class T, typename Function<void, T *>::type function>
using safe_ptr = uniq_ptr<T, Destroy<T *, void, function>>;
// You cannot specialize an alias
template<class T, class ReturnType, typename Function<ReturnType, T *>::type function>
using safe_ptr_v2 = uniq_ptr<T, Destroy<T *, ReturnType, function>>;
template<class T>
void c_free(T *p) {
free(p);
}
template<class T, class ReturnType, ReturnType (**function)(T *)>
void dynamic(T *p) {
(*function)(p);
}
template<class T, void (**function)(T *)>
using dyn_safe_ptr = safe_ptr<T, dynamic<T, void, function>>;
template<class T, class ReturnType, ReturnType (**function)(T *)>
using dyn_safe_ptr_v2 = safe_ptr<T, dynamic<T, ReturnType, function>>;
template<class T>
using c_ptr = safe_ptr<T, c_free<T>>;
template<class It>
std::string_view view(It begin, It end) {
return std::string_view { (const char *)begin, (std::size_t)(end - begin) };
}
template<class T>
std::string_view view(const T &data) {
return std::string_view((const char *)&data, sizeof(T));
}
namespace endian { namespace endian {
template<class T = void> template<class T = void>
struct endianness { struct endianness {
enum : bool { enum : bool {
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
defined(__BIG_ENDIAN__) || \ defined(__BIG_ENDIAN__) || \
defined(__ARMEB__) || \ defined(__ARMEB__) || \
defined(__THUMBEB__) || \ defined(__THUMBEB__) || \
defined(__AARCH64EB__) || \ defined(__AARCH64EB__) || \
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
// It's a big-endian target architecture // It's a big-endian target architecture
little = false, little = false,
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ #elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
defined(__LITTLE_ENDIAN__) || \ defined(__LITTLE_ENDIAN__) || \
defined(__ARMEL__) || \ defined(__ARMEL__) || \
defined(__THUMBEL__) || \ defined(__THUMBEL__) || \
defined(__AARCH64EL__) || \ defined(__AARCH64EL__) || \
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \
defined(_WIN32) defined(_WIN32)
// It's a little-endian target architecture // It's a little-endian target architecture
little = true, little = true,
#else #else
#error "Unknown Endianness" #error "Unknown Endianness"
#endif #endif
@ -598,15 +836,14 @@ struct endianness {
}; };
template<class T, class S = void> template<class T, class S = void>
struct endian_helper { }; struct endian_helper {};
template<class T> template<class T>
struct endian_helper<T, std::enable_if_t< struct endian_helper<T, std::enable_if_t<
!(instantiation_of_v<std::optional, T>) !(instantiation_of_v<std::optional, T>)>> {
>> {
static inline T big(T x) { static inline T big(T x) {
if constexpr (endianness<T>::little) { if constexpr(endianness<T>::little) {
uint8_t *data = reinterpret_cast<uint8_t*>(&x); uint8_t *data = reinterpret_cast<uint8_t *>(&x);
std::reverse(data, data + sizeof(x)); std::reverse(data, data + sizeof(x));
} }
@ -615,8 +852,8 @@ struct endian_helper<T, std::enable_if_t<
} }
static inline T little(T x) { static inline T little(T x) {
if constexpr (endianness<T>::big) { if constexpr(endianness<T>::big) {
uint8_t *data = reinterpret_cast<uint8_t*>(&x); uint8_t *data = reinterpret_cast<uint8_t *>(&x);
std::reverse(data, data + sizeof(x)); std::reverse(data, data + sizeof(x));
} }
@ -627,32 +864,31 @@ struct endian_helper<T, std::enable_if_t<
template<class T> template<class T>
struct endian_helper<T, std::enable_if_t< struct endian_helper<T, std::enable_if_t<
instantiation_of_v<std::optional, T> instantiation_of_v<std::optional, T>>> {
>> { static inline T little(T x) {
static inline T little(T x) { if(!x) return x;
if(!x) return x;
if constexpr (endianness<T>::big) { if constexpr(endianness<T>::big) {
auto *data = reinterpret_cast<uint8_t*>(&*x); auto *data = reinterpret_cast<uint8_t *>(&*x);
std::reverse(data, data + sizeof(*x)); std::reverse(data, data + sizeof(*x));
}
return x;
} }
return x;
}
static inline T big(T x) {
if(!x) return x;
static inline T big(T x) { if constexpr(endianness<T>::big) {
if(!x) return x; auto *data = reinterpret_cast<uint8_t *>(&*x);
if constexpr (endianness<T>::big) { std::reverse(data, data + sizeof(*x));
auto *data = reinterpret_cast<uint8_t*>(&*x); }
std::reverse(data, data + sizeof(*x)); return x;
} }
return x;
}
}; };
template<class T> template<class T>
@ -660,7 +896,6 @@ inline auto little(T x) { return endian_helper<T>::little(x); }
template<class T> template<class T>
inline auto big(T x) { return endian_helper<T>::big(x); } inline auto big(T x) { return endian_helper<T>::big(x); }
} /* endian */ } // namespace endian
} // namespace util
} /* util */
#endif #endif

View file

@ -18,12 +18,12 @@ union uuid_t {
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max()); std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max());
uuid_t buf; uuid_t buf;
for (auto &el : buf.b8) { for(auto &el : buf.b8) {
el = dist(engine); el = dist(engine);
} }
buf.b8[7] &= (std::uint8_t) 0b00101111; buf.b8[7] &= (std::uint8_t)0b00101111;
buf.b8[9] &= (std::uint8_t) 0b10011111; buf.b8[9] &= (std::uint8_t)0b10011111;
return buf; return buf;
} }
@ -31,7 +31,7 @@ union uuid_t {
static uuid_t generate() { static uuid_t generate() {
std::random_device r; std::random_device r;
std::default_random_engine engine{r()}; std::default_random_engine engine { r() };
return generate(engine); return generate(engine);
} }
@ -41,7 +41,7 @@ union uuid_t {
result.reserve(sizeof(uuid_t) * 2 + 4); result.reserve(sizeof(uuid_t) * 2 + 4);
auto hex = util::hex(*this, true); auto hex = util::hex(*this, true);
auto hex_view = hex.to_string_view(); auto hex_view = hex.to_string_view();
std::string_view slices[] = { std::string_view slices[] = {
@ -75,5 +75,5 @@ union uuid_t {
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1])); return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1]));
} }
}; };
} } // namespace util
#endif //T_MAN_UUID_H #endif //T_MAN_UUID_H

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,9 @@
#ifndef SUNSHINE_VIDEO_H #ifndef SUNSHINE_VIDEO_H
#define SUNSHINE_VIDEO_H #define SUNSHINE_VIDEO_H
#include "thread_safe.h" #include "input.h"
#include "platform/common.h" #include "platform/common.h"
#include "thread_safe.h"
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
@ -14,29 +15,49 @@ extern "C" {
struct AVPacket; struct AVPacket;
namespace video { namespace video {
void free_packet(AVPacket *packet);
struct packet_raw_t : public AVPacket { struct packet_raw_t : public AVPacket {
template<class P> void init_packet() {
explicit packet_raw_t(P *user_data) : channel_data { user_data } { pts = AV_NOPTS_VALUE;
av_init_packet(this); dts = AV_NOPTS_VALUE;
pos = -1;
duration = 0;
flags = 0;
stream_index = 0;
buf = nullptr;
side_data = nullptr;
side_data_elems = 0;
} }
explicit packet_raw_t(std::nullptr_t null) : channel_data { nullptr } { template<class P>
av_init_packet(this); explicit packet_raw_t(P *user_data) : channel_data { user_data } {
init_packet();
}
explicit packet_raw_t(std::nullptr_t) : channel_data { nullptr } {
init_packet();
} }
~packet_raw_t() { ~packet_raw_t() {
av_packet_unref(this); av_packet_unref(this);
} }
struct replace_t {
std::string_view old;
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; void *channel_data;
}; };
using packet_t = std::unique_ptr<packet_raw_t>; using packet_t = std::unique_ptr<packet_raw_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<packet_t>>; using idr_t = std::pair<int64_t, int64_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>>>;
struct config_t { struct config_t {
int width; int width;
@ -50,14 +71,26 @@ struct config_t {
int dynamicRange; int dynamicRange;
}; };
using float4 = float[4];
using float3 = float[3];
using float2 = float[2];
struct __attribute__((__aligned__(16))) color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
extern color_t colors[4];
void capture( void capture(
safe::signal_t *shutdown_event, safe::mail_t mail,
packet_queue_t packets,
idr_event_t idr_events,
config_t config, config_t config,
void *channel_data); void *channel_data);
int init(); int init();
} } // namespace video
#endif //SUNSHINE_VIDEO_H #endif //SUNSHINE_VIDEO_H

69
third-party/cbs/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.0)
project(CBS)
SET(CBS_SOURCE_FILES
include/cbs/av1.h
include/cbs/cbs_av1.h
include/cbs/cbs_bsf.h
include/cbs/cbs.h
include/cbs/cbs_h2645.h
include/cbs/cbs_h264.h
include/cbs/cbs_h265.h
include/cbs/cbs_jpeg.h
include/cbs/cbs_mpeg2.h
include/cbs/cbs_sei.h
include/cbs/cbs_vp9.h
include/cbs/h2645_parse.h
include/cbs/h264.h
include/cbs/hevc.h
include/cbs/sei.h
include/cbs/video_levels.h
cbs.c
cbs_h2645.c
cbs_av1.c
cbs_vp9.c
cbs_mpeg2.c
cbs_jpeg.c
cbs_sei.c
h2645_parse.c
video_levels.c
bytestream.h
cbs_internal.h
defs.h
get_bits.h
h264_ps.h
h264_sei.h
hevc_sei.h
intmath.h
mathops.h
put_bits.h
vlc.h
config.h
)
include_directories(include)
if(DEFINED FFMPEG_INCLUDE_DIRS)
include_directories(${FFMPEG_INCLUDE_DIRS})
endif()
add_compile_definitions(
HAVE_THREADS=1
HAVE_FAST_UNALIGNED
PIC=1
CONFIG_CBS_AV1=1
CONFIG_CBS_H264=1
CONFIG_CBS_H265=1
CONFIG_CBS_JPEG=1
CONFIG_CBS_MPEG2=1
CONFIG_CBS_VP9=1
)
add_library(cbs ${CBS_SOURCE_FILES})
target_compile_options(cbs PRIVATE -Wall -Wno-incompatible-pointer-types -Wno-maybe-uninitialized -Wno-format -Wno-format-extra-args)

351
third-party/cbs/bytestream.h vendored Normal file
View file

@ -0,0 +1,351 @@
/*
* Bytestream functions
* copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@free.fr>
* Copyright (c) 2012 Aneesh Dogra (lionaneesh) <lionaneesh@gmail.com>
*
* 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
*/
#ifndef AVCODEC_BYTESTREAM_H
#define AVCODEC_BYTESTREAM_H
#include "config.h"
#include <stdint.h>
#include <string.h>
#include <libavutil/avassert.h>
#include <libavutil/common.h>
#include <libavutil/intreadwrite.h>
typedef struct GetByteContext {
const uint8_t *buffer, *buffer_end, *buffer_start;
} GetByteContext;
typedef struct PutByteContext {
uint8_t *buffer, *buffer_end, *buffer_start;
int eof;
} PutByteContext;
#define DEF(type, name, bytes, read, write) \
static av_always_inline type bytestream_get_##name(const uint8_t **b) { \
(*b) += bytes; \
return read(*b - bytes); \
} \
static av_always_inline void bytestream_put_##name(uint8_t **b, \
const type value) { \
write(*b, value); \
(*b) += bytes; \
} \
static av_always_inline void bytestream2_put_##name##u(PutByteContext *p, \
const type value) { \
bytestream_put_##name(&p->buffer, value); \
} \
static av_always_inline void bytestream2_put_##name(PutByteContext *p, \
const type value) { \
if(!p->eof && (p->buffer_end - p->buffer >= bytes)) { \
write(p->buffer, value); \
p->buffer += bytes; \
} \
else \
p->eof = 1; \
} \
static av_always_inline type bytestream2_get_##name##u(GetByteContext *g) { \
return bytestream_get_##name(&g->buffer); \
} \
static av_always_inline type bytestream2_get_##name(GetByteContext *g) { \
if(g->buffer_end - g->buffer < bytes) { \
g->buffer = g->buffer_end; \
return 0; \
} \
return bytestream2_get_##name##u(g); \
} \
static av_always_inline type bytestream2_peek_##name##u(GetByteContext *g) { \
return read(g->buffer); \
} \
static av_always_inline type bytestream2_peek_##name(GetByteContext *g) { \
if(g->buffer_end - g->buffer < bytes) \
return 0; \
return bytestream2_peek_##name##u(g); \
}
DEF(uint64_t, le64, 8, AV_RL64, AV_WL64)
DEF(unsigned int, le32, 4, AV_RL32, AV_WL32)
DEF(unsigned int, le24, 3, AV_RL24, AV_WL24)
DEF(unsigned int, le16, 2, AV_RL16, AV_WL16)
DEF(uint64_t, be64, 8, AV_RB64, AV_WB64)
DEF(unsigned int, be32, 4, AV_RB32, AV_WB32)
DEF(unsigned int, be24, 3, AV_RB24, AV_WB24)
DEF(unsigned int, be16, 2, AV_RB16, AV_WB16)
DEF(unsigned int, byte, 1, AV_RB8, AV_WB8)
#if AV_HAVE_BIGENDIAN
#define bytestream2_get_ne16 bytestream2_get_be16
#define bytestream2_get_ne24 bytestream2_get_be24
#define bytestream2_get_ne32 bytestream2_get_be32
#define bytestream2_get_ne64 bytestream2_get_be64
#define bytestream2_get_ne16u bytestream2_get_be16u
#define bytestream2_get_ne24u bytestream2_get_be24u
#define bytestream2_get_ne32u bytestream2_get_be32u
#define bytestream2_get_ne64u bytestream2_get_be64u
#define bytestream2_put_ne16 bytestream2_put_be16
#define bytestream2_put_ne24 bytestream2_put_be24
#define bytestream2_put_ne32 bytestream2_put_be32
#define bytestream2_put_ne64 bytestream2_put_be64
#define bytestream2_peek_ne16 bytestream2_peek_be16
#define bytestream2_peek_ne24 bytestream2_peek_be24
#define bytestream2_peek_ne32 bytestream2_peek_be32
#define bytestream2_peek_ne64 bytestream2_peek_be64
#else
#define bytestream2_get_ne16 bytestream2_get_le16
#define bytestream2_get_ne24 bytestream2_get_le24
#define bytestream2_get_ne32 bytestream2_get_le32
#define bytestream2_get_ne64 bytestream2_get_le64
#define bytestream2_get_ne16u bytestream2_get_le16u
#define bytestream2_get_ne24u bytestream2_get_le24u
#define bytestream2_get_ne32u bytestream2_get_le32u
#define bytestream2_get_ne64u bytestream2_get_le64u
#define bytestream2_put_ne16 bytestream2_put_le16
#define bytestream2_put_ne24 bytestream2_put_le24
#define bytestream2_put_ne32 bytestream2_put_le32
#define bytestream2_put_ne64 bytestream2_put_le64
#define bytestream2_peek_ne16 bytestream2_peek_le16
#define bytestream2_peek_ne24 bytestream2_peek_le24
#define bytestream2_peek_ne32 bytestream2_peek_le32
#define bytestream2_peek_ne64 bytestream2_peek_le64
#endif
static av_always_inline void bytestream2_init(GetByteContext *g,
const uint8_t *buf,
int buf_size) {
av_assert0(buf_size >= 0);
g->buffer = buf;
g->buffer_start = buf;
g->buffer_end = buf + buf_size;
}
static av_always_inline void bytestream2_init_writer(PutByteContext *p,
uint8_t *buf,
int buf_size) {
av_assert0(buf_size >= 0);
p->buffer = buf;
p->buffer_start = buf;
p->buffer_end = buf + buf_size;
p->eof = 0;
}
static av_always_inline int bytestream2_get_bytes_left(GetByteContext *g) {
return g->buffer_end - g->buffer;
}
static av_always_inline int bytestream2_get_bytes_left_p(PutByteContext *p) {
return p->buffer_end - p->buffer;
}
static av_always_inline void bytestream2_skip(GetByteContext *g,
unsigned int size) {
g->buffer += FFMIN(g->buffer_end - g->buffer, size);
}
static av_always_inline void bytestream2_skipu(GetByteContext *g,
unsigned int size) {
g->buffer += size;
}
static av_always_inline void bytestream2_skip_p(PutByteContext *p,
unsigned int size) {
int size2;
if(p->eof)
return;
size2 = FFMIN(p->buffer_end - p->buffer, size);
if(size2 != size)
p->eof = 1;
p->buffer += size2;
}
static av_always_inline int bytestream2_tell(GetByteContext *g) {
return (int)(g->buffer - g->buffer_start);
}
static av_always_inline int bytestream2_tell_p(PutByteContext *p) {
return (int)(p->buffer - p->buffer_start);
}
static av_always_inline int bytestream2_size(GetByteContext *g) {
return (int)(g->buffer_end - g->buffer_start);
}
static av_always_inline int bytestream2_size_p(PutByteContext *p) {
return (int)(p->buffer_end - p->buffer_start);
}
static av_always_inline int bytestream2_seek(GetByteContext *g,
int offset,
int whence) {
switch(whence) {
case SEEK_CUR:
offset = av_clip(offset, -(g->buffer - g->buffer_start),
g->buffer_end - g->buffer);
g->buffer += offset;
break;
case SEEK_END:
offset = av_clip(offset, -(g->buffer_end - g->buffer_start), 0);
g->buffer = g->buffer_end + offset;
break;
case SEEK_SET:
offset = av_clip(offset, 0, g->buffer_end - g->buffer_start);
g->buffer = g->buffer_start + offset;
break;
default:
return AVERROR(EINVAL);
}
return bytestream2_tell(g);
}
static av_always_inline int bytestream2_seek_p(PutByteContext *p,
int offset,
int whence) {
p->eof = 0;
switch(whence) {
case SEEK_CUR:
if(p->buffer_end - p->buffer < offset)
p->eof = 1;
offset = av_clip(offset, -(p->buffer - p->buffer_start),
p->buffer_end - p->buffer);
p->buffer += offset;
break;
case SEEK_END:
if(offset > 0)
p->eof = 1;
offset = av_clip(offset, -(p->buffer_end - p->buffer_start), 0);
p->buffer = p->buffer_end + offset;
break;
case SEEK_SET:
if(p->buffer_end - p->buffer_start < offset)
p->eof = 1;
offset = av_clip(offset, 0, p->buffer_end - p->buffer_start);
p->buffer = p->buffer_start + offset;
break;
default:
return AVERROR(EINVAL);
}
return bytestream2_tell_p(p);
}
static av_always_inline unsigned int bytestream2_get_buffer(GetByteContext *g,
uint8_t *dst,
unsigned int size) {
int size2 = FFMIN(g->buffer_end - g->buffer, size);
memcpy(dst, g->buffer, size2);
g->buffer += size2;
return size2;
}
static av_always_inline unsigned int bytestream2_get_bufferu(GetByteContext *g,
uint8_t *dst,
unsigned int size) {
memcpy(dst, g->buffer, size);
g->buffer += size;
return size;
}
static av_always_inline unsigned int bytestream2_put_buffer(PutByteContext *p,
const uint8_t *src,
unsigned int size) {
int size2;
if(p->eof)
return 0;
size2 = FFMIN(p->buffer_end - p->buffer, size);
if(size2 != size)
p->eof = 1;
memcpy(p->buffer, src, size2);
p->buffer += size2;
return size2;
}
static av_always_inline unsigned int bytestream2_put_bufferu(PutByteContext *p,
const uint8_t *src,
unsigned int size) {
memcpy(p->buffer, src, size);
p->buffer += size;
return size;
}
static av_always_inline void bytestream2_set_buffer(PutByteContext *p,
const uint8_t c,
unsigned int size) {
int size2;
if(p->eof)
return;
size2 = FFMIN(p->buffer_end - p->buffer, size);
if(size2 != size)
p->eof = 1;
memset(p->buffer, c, size2);
p->buffer += size2;
}
static av_always_inline void bytestream2_set_bufferu(PutByteContext *p,
const uint8_t c,
unsigned int size) {
memset(p->buffer, c, size);
p->buffer += size;
}
static av_always_inline unsigned int bytestream2_get_eof(PutByteContext *p) {
return p->eof;
}
static av_always_inline unsigned int bytestream2_copy_bufferu(PutByteContext *p,
GetByteContext *g,
unsigned int size) {
memcpy(p->buffer, g->buffer, size);
p->buffer += size;
g->buffer += size;
return size;
}
static av_always_inline unsigned int bytestream2_copy_buffer(PutByteContext *p,
GetByteContext *g,
unsigned int size) {
int size2;
if(p->eof)
return 0;
size = FFMIN(g->buffer_end - g->buffer, size);
size2 = FFMIN(p->buffer_end - p->buffer, size);
if(size2 != size)
p->eof = 1;
return bytestream2_copy_bufferu(p, g, size2);
}
static av_always_inline unsigned int bytestream_get_buffer(const uint8_t **b,
uint8_t *dst,
unsigned int size) {
memcpy(dst, *b, size);
(*b) += size;
return size;
}
static av_always_inline void bytestream_put_buffer(uint8_t **b,
const uint8_t *src,
unsigned int size) {
memcpy(*b, src, size);
(*b) += size;
}
#endif /* AVCODEC_BYTESTREAM_H */

1050
third-party/cbs/cbs.c vendored Normal file

File diff suppressed because it is too large Load diff

1323
third-party/cbs/cbs_av1.c vendored Normal file

File diff suppressed because it is too large Load diff

2063
third-party/cbs/cbs_av1_syntax_template.c vendored Normal file

File diff suppressed because it is too large Load diff

1632
third-party/cbs/cbs_h2645.c vendored Normal file

File diff suppressed because it is too large Load diff

1175
third-party/cbs/cbs_h264_syntax_template.c vendored Normal file

File diff suppressed because it is too large Load diff

2038
third-party/cbs/cbs_h265_syntax_template.c vendored Normal file

File diff suppressed because it is too large Load diff

220
third-party/cbs/cbs_internal.h vendored Normal file
View file

@ -0,0 +1,220 @@
/*
* 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
*/
#ifndef AVCODEC_CBS_INTERNAL_H
#define AVCODEC_CBS_INTERNAL_H
#include <stdint.h>
#include <libavutil/buffer.h>
#include <libavutil/log.h>
#include "cbs/cbs.h"
#include "get_bits.h"
#include "put_bits.h"
enum CBSContentType {
// Unit content is a simple structure.
CBS_CONTENT_TYPE_POD,
// Unit content contains some references to other structures, but all
// managed via buffer reference counting. The descriptor defines the
// structure offsets of every buffer reference.
CBS_CONTENT_TYPE_INTERNAL_REFS,
// Unit content is something more complex. The descriptor defines
// special functions to manage the content.
CBS_CONTENT_TYPE_COMPLEX,
};
enum {
// Maximum number of unit types described by the same unit type
// descriptor.
CBS_MAX_UNIT_TYPES = 3,
// Maximum number of reference buffer offsets in any one unit.
CBS_MAX_REF_OFFSETS = 2,
// Special value used in a unit type descriptor to indicate that it
// applies to a large range of types rather than a set of discrete
// values.
CBS_UNIT_TYPE_RANGE = -1,
};
typedef const struct CodedBitstreamUnitTypeDescriptor {
// Number of entries in the unit_types array, or the special value
// CBS_UNIT_TYPE_RANGE to indicate that the range fields should be
// used instead.
int nb_unit_types;
// Array of unit types that this entry describes.
const CodedBitstreamUnitType unit_types[CBS_MAX_UNIT_TYPES];
// Start and end of unit type range, used if nb_unit_types is
// CBS_UNIT_TYPE_RANGE.
const CodedBitstreamUnitType unit_type_range_start;
const CodedBitstreamUnitType unit_type_range_end;
// The type of content described.
enum CBSContentType content_type;
// The size of the structure which should be allocated to contain
// the decomposed content of this type of unit.
size_t content_size;
// Number of entries in the ref_offsets array. Only used if the
// content_type is CBS_CONTENT_TYPE_INTERNAL_REFS.
int nb_ref_offsets;
// The structure must contain two adjacent elements:
// type *field;
// AVBufferRef *field_ref;
// where field points to something in the buffer referred to by
// field_ref. This offset is then set to offsetof(struct, field).
size_t ref_offsets[CBS_MAX_REF_OFFSETS];
void (*content_free)(void *opaque, uint8_t *data);
int (*content_clone)(AVBufferRef **ref, CodedBitstreamUnit *unit);
} CodedBitstreamUnitTypeDescriptor;
typedef struct CodedBitstreamType {
enum AVCodecID codec_id;
// A class for the private data, used to declare private AVOptions.
// This field is NULL for types that do not declare any options.
// If this field is non-NULL, the first member of the filter private data
// must be a pointer to AVClass.
const AVClass *priv_class;
size_t priv_data_size;
// List of unit type descriptors for this codec.
// Terminated by a descriptor with nb_unit_types equal to zero.
const CodedBitstreamUnitTypeDescriptor *unit_types;
// Split frag->data into coded bitstream units, creating the
// frag->units array. Fill data but not content on each unit.
// The header argument should be set if the fragment came from
// a header block, which may require different parsing for some
// codecs (e.g. the AVCC header in H.264).
int (*split_fragment)(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag,
int header);
// Read the unit->data bitstream and decompose it, creating
// unit->content.
int (*read_unit)(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit);
// Write the data bitstream from unit->content into pbc.
// Return value AVERROR(ENOSPC) indicates that pbc was too small.
int (*write_unit)(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc);
// Read the data from all of frag->units and assemble it into
// a bitstream for the whole fragment.
int (*assemble_fragment)(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag);
// Reset the codec internal state.
void (*flush)(CodedBitstreamContext *ctx);
// Free the codec internal state.
void (*close)(CodedBitstreamContext *ctx);
} CodedBitstreamType;
// Helper functions for trace output.
void ff_cbs_trace_header(CodedBitstreamContext *ctx,
const char *name);
void ff_cbs_trace_syntax_element(CodedBitstreamContext *ctx, int position,
const char *name, const int *subscripts,
const char *bitstring, int64_t value);
// Helper functions for read/write of common bitstream elements, including
// generation of trace output.
int ff_cbs_read_unsigned(CodedBitstreamContext *ctx, GetBitContext *gbc,
int width, const char *name,
const int *subscripts, uint32_t *write_to,
uint32_t range_min, uint32_t range_max);
int ff_cbs_write_unsigned(CodedBitstreamContext *ctx, PutBitContext *pbc,
int width, const char *name,
const int *subscripts, uint32_t value,
uint32_t range_min, uint32_t range_max);
int ff_cbs_read_signed(CodedBitstreamContext *ctx, GetBitContext *gbc,
int width, const char *name,
const int *subscripts, int32_t *write_to,
int32_t range_min, int32_t range_max);
int ff_cbs_write_signed(CodedBitstreamContext *ctx, PutBitContext *pbc,
int width, const char *name,
const int *subscripts, int32_t value,
int32_t range_min, int32_t range_max);
// The largest unsigned value representable in N bits, suitable for use as
// range_max in the above functions.
#define MAX_UINT_BITS(length) ((UINT64_C(1) << (length)) - 1)
// The largest signed value representable in N bits, suitable for use as
// range_max in the above functions.
#define MAX_INT_BITS(length) ((INT64_C(1) << ((length)-1)) - 1)
// The smallest signed value representable in N bits, suitable for use as
// range_min in the above functions.
#define MIN_INT_BITS(length) (-(INT64_C(1) << ((length)-1)))
#define CBS_UNIT_TYPE_POD(type, structure) \
{ \
.nb_unit_types = 1, \
.unit_types = { type }, \
.content_type = CBS_CONTENT_TYPE_POD, \
.content_size = sizeof(structure), \
}
#define CBS_UNIT_TYPE_INTERNAL_REF(type, structure, ref_field) \
{ \
.nb_unit_types = 1, \
.unit_types = { type }, \
.content_type = CBS_CONTENT_TYPE_INTERNAL_REFS, \
.content_size = sizeof(structure), \
.nb_ref_offsets = 1, \
.ref_offsets = { offsetof(structure, ref_field) }, \
}
#define CBS_UNIT_TYPE_COMPLEX(type, structure, free_func) \
{ \
.nb_unit_types = 1, \
.unit_types = { type }, \
.content_type = CBS_CONTENT_TYPE_COMPLEX, \
.content_size = sizeof(structure), \
.content_free = free_func, \
}
#define CBS_UNIT_TYPE_END_OF_LIST \
{ .nb_unit_types = 0 }
extern const CodedBitstreamType ff_cbs_type_av1;
extern const CodedBitstreamType ff_cbs_type_h264;
extern const CodedBitstreamType ff_cbs_type_h265;
extern const CodedBitstreamType ff_cbs_type_jpeg;
extern const CodedBitstreamType ff_cbs_type_mpeg2;
extern const CodedBitstreamType ff_cbs_type_vp9;
#endif /* AVCODEC_CBS_INTERNAL_H */

482
third-party/cbs/cbs_jpeg.c vendored Normal file
View file

@ -0,0 +1,482 @@
/*
* 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 "cbs/cbs_jpeg.h"
#include "cbs/cbs.h"
#include "cbs_internal.h"
#define HEADER(name) \
do { \
ff_cbs_trace_header(ctx, name); \
} while(0)
#define CHECK(call) \
do { \
err = (call); \
if(err < 0) \
return err; \
} while(0)
#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]) { subs, __VA_ARGS__ }) : NULL)
#define u(width, name, range_min, range_max) \
xu(width, name, range_min, range_max, 0, )
#define us(width, name, sub, range_min, range_max) \
xu(width, name, range_min, range_max, 1, sub)
#define READ
#define READWRITE read
#define RWContext GetBitContext
#define FUNC(name) cbs_jpeg_read_##name
#define xu(width, name, range_min, range_max, subs, ...) \
do { \
uint32_t value; \
CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
SUBSCRIPTS(subs, __VA_ARGS__), \
&value, range_min, range_max)); \
current->name = value; \
} while(0)
#include "cbs_jpeg_syntax_template.c"
#undef READ
#undef READWRITE
#undef RWContext
#undef FUNC
#undef xu
#define WRITE
#define READWRITE write
#define RWContext PutBitContext
#define FUNC(name) cbs_jpeg_write_##name
#define xu(width, name, range_min, range_max, subs, ...) \
do { \
uint32_t value = current->name; \
CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
SUBSCRIPTS(subs, __VA_ARGS__), \
value, range_min, range_max)); \
} while(0)
#include "cbs_jpeg_syntax_template.c"
#undef WRITE
#undef READWRITE
#undef RWContext
#undef FUNC
#undef xu
static void cbs_jpeg_free_application_data(void *opaque, uint8_t *content) {
JPEGRawApplicationData *ad = (JPEGRawApplicationData *)content;
av_buffer_unref(&ad->Ap_ref);
av_freep(&content);
}
static void cbs_jpeg_free_comment(void *opaque, uint8_t *content) {
JPEGRawComment *comment = (JPEGRawComment *)content;
av_buffer_unref(&comment->Cm_ref);
av_freep(&content);
}
static void cbs_jpeg_free_scan(void *opaque, uint8_t *content) {
JPEGRawScan *scan = (JPEGRawScan *)content;
av_buffer_unref(&scan->data_ref);
av_freep(&content);
}
static int cbs_jpeg_split_fragment(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag,
int header) {
AVBufferRef *data_ref;
uint8_t *data;
size_t data_size;
int unit, start, end, marker, next_start, next_marker;
int err, i, j, length;
if(frag->data_size < 4) {
// Definitely too short to be meaningful.
return AVERROR_INVALIDDATA;
}
for(i = 0; i + 1 < frag->data_size && frag->data[i] != 0xff; i++)
;
if(i > 0) {
av_log(ctx->log_ctx, AV_LOG_WARNING, "Discarding %d bytes at "
"beginning of image.\n",
i);
}
for(++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++)
;
if(i + 1 >= frag->data_size && frag->data[i]) {
av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
"no SOI marker found.\n");
return AVERROR_INVALIDDATA;
}
marker = frag->data[i];
if(marker != JPEG_MARKER_SOI) {
av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: first "
"marker is %02x, should be SOI.\n",
marker);
return AVERROR_INVALIDDATA;
}
for(++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++)
;
if(i + 1 >= frag->data_size) {
av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
"no image content found.\n");
return AVERROR_INVALIDDATA;
}
marker = frag->data[i];
start = i + 1;
for(unit = 0;; unit++) {
if(marker == JPEG_MARKER_EOI) {
break;
}
else if(marker == JPEG_MARKER_SOS) {
next_marker = -1;
end = start;
for(i = start; i + 1 < frag->data_size; i++) {
if(frag->data[i] != 0xff)
continue;
end = i;
for(++i; i + 1 < frag->data_size &&
frag->data[i] == 0xff;
i++)
;
if(i + 1 < frag->data_size) {
if(frag->data[i] == 0x00)
continue;
next_marker = frag->data[i];
next_start = i + 1;
}
break;
}
}
else {
i = start;
if(i + 2 > frag->data_size) {
av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
"truncated at %02x marker.\n",
marker);
return AVERROR_INVALIDDATA;
}
length = AV_RB16(frag->data + i);
if(i + length > frag->data_size) {
av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
"truncated at %02x marker segment.\n",
marker);
return AVERROR_INVALIDDATA;
}
end = start + length;
i = end;
if(frag->data[i] != 0xff) {
next_marker = -1;
}
else {
for(++i; i + 1 < frag->data_size &&
frag->data[i] == 0xff;
i++)
;
if(i + 1 >= frag->data_size) {
next_marker = -1;
}
else {
next_marker = frag->data[i];
next_start = i + 1;
}
}
}
if(marker == JPEG_MARKER_SOS) {
length = AV_RB16(frag->data + start);
if(length > end - start)
return AVERROR_INVALIDDATA;
data_ref = NULL;
data = av_malloc(end - start +
AV_INPUT_BUFFER_PADDING_SIZE);
if(!data)
return AVERROR(ENOMEM);
memcpy(data, frag->data + start, length);
for(i = start + length, j = length; i < end; i++, j++) {
if(frag->data[i] == 0xff) {
while(frag->data[i] == 0xff)
++i;
data[j] = 0xff;
}
else {
data[j] = frag->data[i];
}
}
data_size = j;
memset(data + data_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}
else {
data = frag->data + start;
data_size = end - start;
data_ref = frag->data_ref;
}
err = ff_cbs_insert_unit_data(frag, unit, marker,
data, data_size, data_ref);
if(err < 0)
return err;
if(next_marker == -1)
break;
marker = next_marker;
start = next_start;
}
return 0;
}
static int cbs_jpeg_read_unit(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit) {
GetBitContext gbc;
int err;
err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
if(err < 0)
return err;
if(unit->type >= JPEG_MARKER_SOF0 &&
unit->type <= JPEG_MARKER_SOF3) {
err = ff_cbs_alloc_unit_content(unit,
sizeof(JPEGRawFrameHeader),
NULL);
if(err < 0)
return err;
err = cbs_jpeg_read_frame_header(ctx, &gbc, unit->content);
if(err < 0)
return err;
}
else if(unit->type >= JPEG_MARKER_APPN &&
unit->type <= JPEG_MARKER_APPN + 15) {
err = ff_cbs_alloc_unit_content(unit,
sizeof(JPEGRawApplicationData),
&cbs_jpeg_free_application_data);
if(err < 0)
return err;
err = cbs_jpeg_read_application_data(ctx, &gbc, unit->content);
if(err < 0)
return err;
}
else if(unit->type == JPEG_MARKER_SOS) {
JPEGRawScan *scan;
int pos;
err = ff_cbs_alloc_unit_content(unit,
sizeof(JPEGRawScan),
&cbs_jpeg_free_scan);
if(err < 0)
return err;
scan = unit->content;
err = cbs_jpeg_read_scan_header(ctx, &gbc, &scan->header);
if(err < 0)
return err;
pos = get_bits_count(&gbc);
av_assert0(pos % 8 == 0);
if(pos > 0) {
scan->data_size = unit->data_size - pos / 8;
scan->data_ref = av_buffer_ref(unit->data_ref);
if(!scan->data_ref)
return AVERROR(ENOMEM);
scan->data = unit->data + pos / 8;
}
}
else {
switch(unit->type) {
#define SEGMENT(marker, type, func, free) \
case JPEG_MARKER_##marker: { \
err = ff_cbs_alloc_unit_content(unit, \
sizeof(type), free); \
if(err < 0) \
return err; \
err = cbs_jpeg_read_##func(ctx, &gbc, unit->content); \
if(err < 0) \
return err; \
} break
SEGMENT(DQT, JPEGRawQuantisationTableSpecification, dqt, NULL);
SEGMENT(DHT, JPEGRawHuffmanTableSpecification, dht, NULL);
SEGMENT(COM, JPEGRawComment, comment, &cbs_jpeg_free_comment);
#undef SEGMENT
default:
return AVERROR(ENOSYS);
}
}
return 0;
}
static int cbs_jpeg_write_scan(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
JPEGRawScan *scan = unit->content;
int err;
err = cbs_jpeg_write_scan_header(ctx, pbc, &scan->header);
if(err < 0)
return err;
if(scan->data) {
if(scan->data_size * 8 > put_bits_left(pbc))
return AVERROR(ENOSPC);
av_assert0(put_bits_count(pbc) % 8 == 0);
flush_put_bits(pbc);
memcpy(put_bits_ptr(pbc), scan->data, scan->data_size);
skip_put_bytes(pbc, scan->data_size);
}
return 0;
}
static int cbs_jpeg_write_segment(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
int err;
if(unit->type >= JPEG_MARKER_SOF0 &&
unit->type <= JPEG_MARKER_SOF3) {
err = cbs_jpeg_write_frame_header(ctx, pbc, unit->content);
}
else if(unit->type >= JPEG_MARKER_APPN &&
unit->type <= JPEG_MARKER_APPN + 15) {
err = cbs_jpeg_write_application_data(ctx, pbc, unit->content);
}
else {
switch(unit->type) {
#define SEGMENT(marker, func) \
case JPEG_MARKER_##marker: \
err = cbs_jpeg_write_##func(ctx, pbc, unit->content); \
break;
SEGMENT(DQT, dqt);
SEGMENT(DHT, dht);
SEGMENT(COM, comment);
default:
return AVERROR_PATCHWELCOME;
}
}
return err;
}
static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
if(unit->type == JPEG_MARKER_SOS)
return cbs_jpeg_write_scan(ctx, unit, pbc);
else
return cbs_jpeg_write_segment(ctx, unit, pbc);
}
static int cbs_jpeg_assemble_fragment(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag) {
const CodedBitstreamUnit *unit;
uint8_t *data;
size_t size, dp, sp;
int i;
size = 4; // SOI + EOI.
for(i = 0; i < frag->nb_units; i++) {
unit = &frag->units[i];
size += 2 + unit->data_size;
if(unit->type == JPEG_MARKER_SOS) {
for(sp = 0; sp < unit->data_size; sp++) {
if(unit->data[sp] == 0xff)
++size;
}
}
}
frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
if(!frag->data_ref)
return AVERROR(ENOMEM);
data = frag->data_ref->data;
dp = 0;
data[dp++] = 0xff;
data[dp++] = JPEG_MARKER_SOI;
for(i = 0; i < frag->nb_units; i++) {
unit = &frag->units[i];
data[dp++] = 0xff;
data[dp++] = unit->type;
if(unit->type != JPEG_MARKER_SOS) {
memcpy(data + dp, unit->data, unit->data_size);
dp += unit->data_size;
}
else {
sp = AV_RB16(unit->data);
av_assert0(sp <= unit->data_size);
memcpy(data + dp, unit->data, sp);
dp += sp;
for(; sp < unit->data_size; sp++) {
if(unit->data[sp] == 0xff) {
data[dp++] = 0xff;
data[dp++] = 0x00;
}
else {
data[dp++] = unit->data[sp];
}
}
}
}
data[dp++] = 0xff;
data[dp++] = JPEG_MARKER_EOI;
av_assert0(dp == size);
memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
frag->data = data;
frag->data_size = size;
return 0;
}
const CodedBitstreamType ff_cbs_type_jpeg = {
.codec_id = AV_CODEC_ID_MJPEG,
.split_fragment = &cbs_jpeg_split_fragment,
.read_unit = &cbs_jpeg_read_unit,
.write_unit = &cbs_jpeg_write_unit,
.assemble_fragment = &cbs_jpeg_assemble_fragment,
};

View file

@ -0,0 +1,189 @@
/*
* 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
*/
static int FUNC(frame_header)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawFrameHeader *current) {
int err, i;
HEADER("Frame Header");
u(16, Lf, 8, 8 + 3 * JPEG_MAX_COMPONENTS);
u(8, P, 2, 16);
u(16, Y, 0, JPEG_MAX_HEIGHT);
u(16, X, 1, JPEG_MAX_WIDTH);
u(8, Nf, 1, JPEG_MAX_COMPONENTS);
for(i = 0; i < current->Nf; i++) {
us(8, C[i], i, 0, JPEG_MAX_COMPONENTS);
us(4, H[i], i, 1, 4);
us(4, V[i], i, 1, 4);
us(8, Tq[i], i, 0, 3);
}
return 0;
}
static int FUNC(quantisation_table)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawQuantisationTable *current) {
int err, i;
u(4, Pq, 0, 1);
u(4, Tq, 0, 3);
if(current->Pq) {
for(i = 0; i < 64; i++)
us(16, Q[i], i, 1, 255);
}
else {
for(i = 0; i < 64; i++)
us(8, Q[i], i, 1, 255);
}
return 0;
}
static int FUNC(dqt)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawQuantisationTableSpecification *current) {
int err, i, n;
HEADER("Quantisation Tables");
u(16, Lq, 2, 2 + 4 * 65);
n = current->Lq / 65;
for(i = 0; i < n; i++)
CHECK(FUNC(quantisation_table)(ctx, rw, &current->table[i]));
return 0;
}
static int FUNC(huffman_table)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawHuffmanTable *current) {
int err, i, j, ij;
u(4, Tc, 0, 1);
u(4, Th, 0, 3);
for(i = 0; i < 16; i++)
us(8, L[i], i, 0, 224);
ij = 0;
for(i = 0; i < 16; i++) {
for(j = 0; j < current->L[i]; j++) {
if(ij >= 224)
return AVERROR_INVALIDDATA;
us(8, V[ij], ij, 0, 255);
++ij;
}
}
return 0;
}
static int FUNC(dht)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawHuffmanTableSpecification *current) {
int err, i, j, n;
HEADER("Huffman Tables");
u(16, Lh, 2, 2 + 8 * (1 + 16 + 256));
n = 2;
for(i = 0; n < current->Lh; i++) {
if(i >= 8)
return AVERROR_INVALIDDATA;
CHECK(FUNC(huffman_table)(ctx, rw, &current->table[i]));
++n;
for(j = 0; j < 16; j++)
n += 1 + current->table[i].L[j];
}
return 0;
}
static int FUNC(scan_header)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawScanHeader *current) {
int err, j;
HEADER("Scan");
u(16, Ls, 6, 6 + 2 * JPEG_MAX_COMPONENTS);
u(8, Ns, 1, 4);
for(j = 0; j < current->Ns; j++) {
us(8, Cs[j], j, 0, JPEG_MAX_COMPONENTS);
us(4, Td[j], j, 0, 3);
us(4, Ta[j], j, 0, 3);
}
u(8, Ss, 0, 63);
u(8, Se, 0, 63);
u(4, Ah, 0, 13);
u(4, Al, 0, 15);
return 0;
}
static int FUNC(application_data)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawApplicationData *current) {
int err, i;
HEADER("Application Data");
u(16, Lp, 2, 65535);
if(current->Lp > 2) {
#ifdef READ
current->Ap_ref = av_buffer_alloc(current->Lp - 2);
if(!current->Ap_ref)
return AVERROR(ENOMEM);
current->Ap = current->Ap_ref->data;
#endif
for(i = 0; i < current->Lp - 2; i++)
us(8, Ap[i], i, 0, 255);
}
return 0;
}
static int FUNC(comment)(CodedBitstreamContext *ctx, RWContext *rw,
JPEGRawComment *current) {
int err, i;
HEADER("Comment");
u(16, Lc, 2, 65535);
if(current->Lc > 2) {
#ifdef READ
current->Cm_ref = av_buffer_alloc(current->Lc - 2);
if(!current->Cm_ref)
return AVERROR(ENOMEM);
current->Cm = current->Cm_ref->data;
#endif
for(i = 0; i < current->Lc - 2; i++)
us(8, Cm[i], i, 0, 255);
}
return 0;
}

469
third-party/cbs/cbs_mpeg2.c vendored Normal file
View file

@ -0,0 +1,469 @@
/*
* 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 <libavutil/avassert.h>
#include "cbs/cbs.h"
#include "cbs/cbs_mpeg2.h"
#include "cbs_internal.h"
#define HEADER(name) \
do { \
ff_cbs_trace_header(ctx, name); \
} while(0)
#define CHECK(call) \
do { \
err = (call); \
if(err < 0) \
return err; \
} while(0)
#define FUNC_NAME(rw, codec, name) cbs_##codec##_##rw##_##name
#define FUNC_MPEG2(rw, name) FUNC_NAME(rw, mpeg2, name)
#define FUNC(name) FUNC_MPEG2(READWRITE, name)
#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]) { subs, __VA_ARGS__ }) : NULL)
#define ui(width, name) \
xui(width, name, current->name, 0, MAX_UINT_BITS(width), 0, )
#define uir(width, name) \
xui(width, name, current->name, 1, MAX_UINT_BITS(width), 0, )
#define uis(width, name, subs, ...) \
xui(width, name, current->name, 0, MAX_UINT_BITS(width), subs, __VA_ARGS__)
#define uirs(width, name, subs, ...) \
xui(width, name, current->name, 1, MAX_UINT_BITS(width), subs, __VA_ARGS__)
#define xui(width, name, var, range_min, range_max, subs, ...) \
xuia(width, #name, var, range_min, range_max, subs, __VA_ARGS__)
#define sis(width, name, subs, ...) \
xsi(width, name, current->name, subs, __VA_ARGS__)
#define marker_bit() \
bit("marker_bit", 1)
#define bit(string, value) \
do { \
av_unused uint32_t bit = value; \
xuia(1, string, bit, value, value, 0, ); \
} while(0)
#define READ
#define READWRITE read
#define RWContext GetBitContext
#define xuia(width, string, var, range_min, range_max, subs, ...) \
do { \
uint32_t value; \
CHECK(ff_cbs_read_unsigned(ctx, rw, width, string, \
SUBSCRIPTS(subs, __VA_ARGS__), \
&value, range_min, range_max)); \
var = value; \
} while(0)
#define xsi(width, name, var, subs, ...) \
do { \
int32_t value; \
CHECK(ff_cbs_read_signed(ctx, rw, width, #name, \
SUBSCRIPTS(subs, __VA_ARGS__), &value, \
MIN_INT_BITS(width), \
MAX_INT_BITS(width))); \
var = value; \
} while(0)
#define nextbits(width, compare, var) \
(get_bits_left(rw) >= width && \
(var = show_bits(rw, width)) == (compare))
#define infer(name, value) \
do { \
current->name = value; \
} while(0)
#include "cbs_mpeg2_syntax_template.c"
#undef READ
#undef READWRITE
#undef RWContext
#undef xuia
#undef xsi
#undef nextbits
#undef infer
#define WRITE
#define READWRITE write
#define RWContext PutBitContext
#define xuia(width, string, var, range_min, range_max, subs, ...) \
do { \
CHECK(ff_cbs_write_unsigned(ctx, rw, width, string, \
SUBSCRIPTS(subs, __VA_ARGS__), \
var, range_min, range_max)); \
} while(0)
#define xsi(width, name, var, subs, ...) \
do { \
CHECK(ff_cbs_write_signed(ctx, rw, width, #name, \
SUBSCRIPTS(subs, __VA_ARGS__), var, \
MIN_INT_BITS(width), \
MAX_INT_BITS(width))); \
} while(0)
#define nextbits(width, compare, var) (var)
#define infer(name, value) \
do { \
if(current->name != (value)) { \
av_log(ctx->log_ctx, AV_LOG_WARNING, "Warning: " \
"%s does not match inferred value: " \
"%" PRId64 ", but should be %" PRId64 ".\n", \
#name, (int64_t)current->name, (int64_t)(value)); \
} \
} while(0)
#include "cbs_mpeg2_syntax_template.c"
#undef WRITE
#undef READWRITE
#undef RWContext
#undef xuia
#undef xsi
#undef nextbits
#undef infer
static const uint8_t *avpriv_find_start_code(const uint8_t *restrict p,
const uint8_t *end,
uint32_t *restrict state) {
int i;
av_assert0(p <= end);
if(p >= end)
return end;
for(i = 0; i < 3; i++) {
uint32_t tmp = *state << 8;
*state = tmp + *(p++);
if(tmp == 0x100 || p == end)
return p;
}
while(p < end) {
if(p[-1] > 1) p += 3;
else if(p[-2])
p += 2;
else if(p[-3] | (p[-1] - 1))
p++;
else {
p++;
break;
}
}
p = FFMIN(p, end) - 4;
*state = AV_RB32(p);
return p + 4;
}
static int cbs_mpeg2_split_fragment(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag,
int header) {
const uint8_t *start, *end;
CodedBitstreamUnitType unit_type;
uint32_t start_code = -1;
size_t unit_size;
int err, i, final = 0;
start = avpriv_find_start_code(frag->data, frag->data + frag->data_size,
&start_code);
if(start_code >> 8 != 0x000001) {
// No start code found.
return AVERROR_INVALIDDATA;
}
for(i = 0;; i++) {
unit_type = start_code & 0xff;
if(start == frag->data + frag->data_size) {
// The last four bytes form a start code which constitutes
// a unit of its own. In this situation avpriv_find_start_code
// won't modify start_code at all so modify start_code so that
// the next unit will be treated as the last unit.
start_code = 0;
}
end = avpriv_find_start_code(start--, frag->data + frag->data_size,
&start_code);
// start points to the byte containing the start_code_identifier
// (may be the last byte of fragment->data); end points to the byte
// following the byte containing the start code identifier (or to
// the end of fragment->data).
if(start_code >> 8 == 0x000001) {
// Unit runs from start to the beginning of the start code
// pointed to by end (including any padding zeroes).
unit_size = (end - 4) - start;
}
else {
// We didn't find a start code, so this is the final unit.
unit_size = end - start;
final = 1;
}
err = ff_cbs_insert_unit_data(frag, i, unit_type, (uint8_t *)start,
unit_size, frag->data_ref);
if(err < 0)
return err;
if(final)
break;
start = end;
}
return 0;
}
static int cbs_mpeg2_read_unit(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit) {
GetBitContext gbc;
int err;
err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
if(err < 0)
return err;
err = ff_cbs_alloc_unit_content2(ctx, unit);
if(err < 0)
return err;
if(MPEG2_START_IS_SLICE(unit->type)) {
MPEG2RawSlice *slice = unit->content;
int pos, len;
err = cbs_mpeg2_read_slice_header(ctx, &gbc, &slice->header);
if(err < 0)
return err;
if(!get_bits_left(&gbc))
return AVERROR_INVALIDDATA;
pos = get_bits_count(&gbc);
len = unit->data_size;
slice->data_size = len - pos / 8;
slice->data_ref = av_buffer_ref(unit->data_ref);
if(!slice->data_ref)
return AVERROR(ENOMEM);
slice->data = unit->data + pos / 8;
slice->data_bit_start = pos % 8;
}
else {
switch(unit->type) {
#define START(start_code, type, read_func, free_func) \
case start_code: { \
type *header = unit->content; \
err = cbs_mpeg2_read_##read_func(ctx, &gbc, header); \
if(err < 0) \
return err; \
} break;
START(MPEG2_START_PICTURE, MPEG2RawPictureHeader,
picture_header, &cbs_mpeg2_free_picture_header);
START(MPEG2_START_USER_DATA, MPEG2RawUserData,
user_data, &cbs_mpeg2_free_user_data);
START(MPEG2_START_SEQUENCE_HEADER, MPEG2RawSequenceHeader,
sequence_header, NULL);
START(MPEG2_START_EXTENSION, MPEG2RawExtensionData,
extension_data, NULL);
START(MPEG2_START_GROUP, MPEG2RawGroupOfPicturesHeader,
group_of_pictures_header, NULL);
START(MPEG2_START_SEQUENCE_END, MPEG2RawSequenceEnd,
sequence_end, NULL);
#undef START
default:
return AVERROR(ENOSYS);
}
}
return 0;
}
static int cbs_mpeg2_write_header(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
int err;
switch(unit->type) {
#define START(start_code, type, func) \
case start_code: \
err = cbs_mpeg2_write_##func(ctx, pbc, unit->content); \
break;
START(MPEG2_START_PICTURE, MPEG2RawPictureHeader, picture_header);
START(MPEG2_START_USER_DATA, MPEG2RawUserData, user_data);
START(MPEG2_START_SEQUENCE_HEADER, MPEG2RawSequenceHeader, sequence_header);
START(MPEG2_START_EXTENSION, MPEG2RawExtensionData, extension_data);
START(MPEG2_START_GROUP, MPEG2RawGroupOfPicturesHeader,
group_of_pictures_header);
START(MPEG2_START_SEQUENCE_END, MPEG2RawSequenceEnd, sequence_end);
#undef START
default:
av_log(ctx->log_ctx, AV_LOG_ERROR, "Write unimplemented for start "
"code %02" PRIx32 ".\n",
unit->type);
return AVERROR_PATCHWELCOME;
}
return err;
}
static int cbs_mpeg2_write_slice(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
MPEG2RawSlice *slice = unit->content;
int err;
err = cbs_mpeg2_write_slice_header(ctx, pbc, &slice->header);
if(err < 0)
return err;
if(slice->data) {
size_t rest = slice->data_size - (slice->data_bit_start + 7) / 8;
uint8_t *pos = slice->data + slice->data_bit_start / 8;
av_assert0(slice->data_bit_start >= 0 &&
slice->data_size > slice->data_bit_start / 8);
if(slice->data_size * 8 + 8 > put_bits_left(pbc))
return AVERROR(ENOSPC);
// First copy the remaining bits of the first byte
if(slice->data_bit_start % 8)
put_bits(pbc, 8 - slice->data_bit_start % 8,
*pos++ & MAX_UINT_BITS(8 - slice->data_bit_start % 8));
if(put_bits_count(pbc) % 8 == 0) {
// If the writer is aligned at this point,
// memcpy can be used to improve performance.
// This is the normal case.
flush_put_bits(pbc);
memcpy(put_bits_ptr(pbc), pos, rest);
skip_put_bytes(pbc, rest);
}
else {
// If not, we have to copy manually:
for(; rest > 3; rest -= 4, pos += 4)
put_bits32(pbc, AV_RB32(pos));
for(; rest; rest--, pos++)
put_bits(pbc, 8, *pos);
// Align with zeros
put_bits(pbc, 8 - put_bits_count(pbc) % 8, 0);
}
}
return 0;
}
static int cbs_mpeg2_write_unit(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
PutBitContext *pbc) {
if(MPEG2_START_IS_SLICE(unit->type))
return cbs_mpeg2_write_slice(ctx, unit, pbc);
else
return cbs_mpeg2_write_header(ctx, unit, pbc);
}
static int cbs_mpeg2_assemble_fragment(CodedBitstreamContext *ctx,
CodedBitstreamFragment *frag) {
uint8_t *data;
size_t size, dp;
int i;
size = 0;
for(i = 0; i < frag->nb_units; i++)
size += 3 + frag->units[i].data_size;
frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
if(!frag->data_ref)
return AVERROR(ENOMEM);
data = frag->data_ref->data;
dp = 0;
for(i = 0; i < frag->nb_units; i++) {
CodedBitstreamUnit *unit = &frag->units[i];
data[dp++] = 0;
data[dp++] = 0;
data[dp++] = 1;
memcpy(data + dp, unit->data, unit->data_size);
dp += unit->data_size;
}
av_assert0(dp == size);
memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
frag->data = data;
frag->data_size = size;
return 0;
}
static const CodedBitstreamUnitTypeDescriptor cbs_mpeg2_unit_types[] = {
CBS_UNIT_TYPE_INTERNAL_REF(MPEG2_START_PICTURE, MPEG2RawPictureHeader,
extra_information_picture.extra_information),
{
.nb_unit_types = CBS_UNIT_TYPE_RANGE,
.unit_type_range_start = 0x01,
.unit_type_range_end = 0xaf,
.content_type = CBS_CONTENT_TYPE_INTERNAL_REFS,
.content_size = sizeof(MPEG2RawSlice),
.nb_ref_offsets = 2,
.ref_offsets = { offsetof(MPEG2RawSlice, header.extra_information_slice.extra_information),
offsetof(MPEG2RawSlice, data) },
},
CBS_UNIT_TYPE_INTERNAL_REF(MPEG2_START_USER_DATA, MPEG2RawUserData,
user_data),
CBS_UNIT_TYPE_POD(MPEG2_START_SEQUENCE_HEADER, MPEG2RawSequenceHeader),
CBS_UNIT_TYPE_POD(MPEG2_START_EXTENSION, MPEG2RawExtensionData),
CBS_UNIT_TYPE_POD(MPEG2_START_SEQUENCE_END, MPEG2RawSequenceEnd),
CBS_UNIT_TYPE_POD(MPEG2_START_GROUP, MPEG2RawGroupOfPicturesHeader),
CBS_UNIT_TYPE_END_OF_LIST
};
const CodedBitstreamType ff_cbs_type_mpeg2 = {
.codec_id = AV_CODEC_ID_MPEG2VIDEO,
.priv_data_size = sizeof(CodedBitstreamMPEG2Context),
.unit_types = cbs_mpeg2_unit_types,
.split_fragment = &cbs_mpeg2_split_fragment,
.read_unit = &cbs_mpeg2_read_unit,
.write_unit = &cbs_mpeg2_write_unit,
.assemble_fragment = &cbs_mpeg2_assemble_fragment,
};

View file

@ -0,0 +1,413 @@
/*
* 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
*/
static int FUNC(sequence_header)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawSequenceHeader *current) {
CodedBitstreamMPEG2Context *mpeg2 = ctx->priv_data;
int err, i;
HEADER("Sequence Header");
ui(8, sequence_header_code);
uir(12, horizontal_size_value);
uir(12, vertical_size_value);
mpeg2->horizontal_size = current->horizontal_size_value;
mpeg2->vertical_size = current->vertical_size_value;
uir(4, aspect_ratio_information);
uir(4, frame_rate_code);
ui(18, bit_rate_value);
marker_bit();
ui(10, vbv_buffer_size_value);
ui(1, constrained_parameters_flag);
ui(1, load_intra_quantiser_matrix);
if(current->load_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, intra_quantiser_matrix[i], 1, i);
}
ui(1, load_non_intra_quantiser_matrix);
if(current->load_non_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, non_intra_quantiser_matrix[i], 1, i);
}
return 0;
}
static int FUNC(user_data)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawUserData *current) {
size_t k;
int err;
HEADER("User Data");
ui(8, user_data_start_code);
#ifdef READ
k = get_bits_left(rw);
av_assert0(k % 8 == 0);
current->user_data_length = k /= 8;
if(k > 0) {
current->user_data_ref = av_buffer_allocz(k + AV_INPUT_BUFFER_PADDING_SIZE);
if(!current->user_data_ref)
return AVERROR(ENOMEM);
current->user_data = current->user_data_ref->data;
}
#endif
for(k = 0; k < current->user_data_length; k++)
uis(8, user_data[k], 1, k);
return 0;
}
static int FUNC(sequence_extension)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawSequenceExtension *current) {
CodedBitstreamMPEG2Context *mpeg2 = ctx->priv_data;
int err;
HEADER("Sequence Extension");
ui(8, profile_and_level_indication);
ui(1, progressive_sequence);
ui(2, chroma_format);
ui(2, horizontal_size_extension);
ui(2, vertical_size_extension);
mpeg2->horizontal_size = (mpeg2->horizontal_size & 0xfff) |
current->horizontal_size_extension << 12;
mpeg2->vertical_size = (mpeg2->vertical_size & 0xfff) |
current->vertical_size_extension << 12;
mpeg2->progressive_sequence = current->progressive_sequence;
ui(12, bit_rate_extension);
marker_bit();
ui(8, vbv_buffer_size_extension);
ui(1, low_delay);
ui(2, frame_rate_extension_n);
ui(5, frame_rate_extension_d);
return 0;
}
static int FUNC(sequence_display_extension)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawSequenceDisplayExtension *current) {
int err;
HEADER("Sequence Display Extension");
ui(3, video_format);
ui(1, colour_description);
if(current->colour_description) {
#ifdef READ
#define READ_AND_PATCH(name) \
do { \
ui(8, name); \
if(current->name == 0) { \
current->name = 2; \
av_log(ctx->log_ctx, AV_LOG_WARNING, "%s in a sequence display " \
"extension had the invalid value 0. Setting it to 2 " \
"(meaning unknown) instead.\n", \
#name); \
} \
} while(0)
READ_AND_PATCH(colour_primaries);
READ_AND_PATCH(transfer_characteristics);
READ_AND_PATCH(matrix_coefficients);
#undef READ_AND_PATCH
#else
uir(8, colour_primaries);
uir(8, transfer_characteristics);
uir(8, matrix_coefficients);
#endif
}
else {
infer(colour_primaries, 2);
infer(transfer_characteristics, 2);
infer(matrix_coefficients, 2);
}
ui(14, display_horizontal_size);
marker_bit();
ui(14, display_vertical_size);
return 0;
}
static int FUNC(group_of_pictures_header)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawGroupOfPicturesHeader *current) {
int err;
HEADER("Group of Pictures Header");
ui(8, group_start_code);
ui(25, time_code);
ui(1, closed_gop);
ui(1, broken_link);
return 0;
}
static int FUNC(extra_information)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawExtraInformation *current,
const char *element_name, const char *marker_name) {
int err;
size_t k;
#ifdef READ
GetBitContext start = *rw;
uint8_t bit;
for(k = 0; nextbits(1, 1, bit); k++)
skip_bits(rw, 1 + 8);
current->extra_information_length = k;
if(k > 0) {
*rw = start;
current->extra_information_ref =
av_buffer_allocz(k + AV_INPUT_BUFFER_PADDING_SIZE);
if(!current->extra_information_ref)
return AVERROR(ENOMEM);
current->extra_information = current->extra_information_ref->data;
}
#endif
for(k = 0; k < current->extra_information_length; k++) {
bit(marker_name, 1);
xuia(8, element_name,
current->extra_information[k], 0, 255, 1, k);
}
bit(marker_name, 0);
return 0;
}
static int FUNC(picture_header)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawPictureHeader *current) {
int err;
HEADER("Picture Header");
ui(8, picture_start_code);
ui(10, temporal_reference);
uir(3, picture_coding_type);
ui(16, vbv_delay);
if(current->picture_coding_type == 2 ||
current->picture_coding_type == 3) {
ui(1, full_pel_forward_vector);
ui(3, forward_f_code);
}
if(current->picture_coding_type == 3) {
ui(1, full_pel_backward_vector);
ui(3, backward_f_code);
}
CHECK(FUNC(extra_information)(ctx, rw, &current->extra_information_picture,
"extra_information_picture[k]", "extra_bit_picture"));
return 0;
}
static int FUNC(picture_coding_extension)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawPictureCodingExtension *current) {
CodedBitstreamMPEG2Context *mpeg2 = ctx->priv_data;
int err;
HEADER("Picture Coding Extension");
uir(4, f_code[0][0]);
uir(4, f_code[0][1]);
uir(4, f_code[1][0]);
uir(4, f_code[1][1]);
ui(2, intra_dc_precision);
ui(2, picture_structure);
ui(1, top_field_first);
ui(1, frame_pred_frame_dct);
ui(1, concealment_motion_vectors);
ui(1, q_scale_type);
ui(1, intra_vlc_format);
ui(1, alternate_scan);
ui(1, repeat_first_field);
ui(1, chroma_420_type);
ui(1, progressive_frame);
if(mpeg2->progressive_sequence) {
if(current->repeat_first_field) {
if(current->top_field_first)
mpeg2->number_of_frame_centre_offsets = 3;
else
mpeg2->number_of_frame_centre_offsets = 2;
}
else {
mpeg2->number_of_frame_centre_offsets = 1;
}
}
else {
if(current->picture_structure == 1 || // Top field.
current->picture_structure == 2) { // Bottom field.
mpeg2->number_of_frame_centre_offsets = 1;
}
else {
if(current->repeat_first_field)
mpeg2->number_of_frame_centre_offsets = 3;
else
mpeg2->number_of_frame_centre_offsets = 2;
}
}
ui(1, composite_display_flag);
if(current->composite_display_flag) {
ui(1, v_axis);
ui(3, field_sequence);
ui(1, sub_carrier);
ui(7, burst_amplitude);
ui(8, sub_carrier_phase);
}
return 0;
}
static int FUNC(quant_matrix_extension)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawQuantMatrixExtension *current) {
int err, i;
HEADER("Quant Matrix Extension");
ui(1, load_intra_quantiser_matrix);
if(current->load_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, intra_quantiser_matrix[i], 1, i);
}
ui(1, load_non_intra_quantiser_matrix);
if(current->load_non_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, non_intra_quantiser_matrix[i], 1, i);
}
ui(1, load_chroma_intra_quantiser_matrix);
if(current->load_chroma_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, intra_quantiser_matrix[i], 1, i);
}
ui(1, load_chroma_non_intra_quantiser_matrix);
if(current->load_chroma_non_intra_quantiser_matrix) {
for(i = 0; i < 64; i++)
uirs(8, chroma_non_intra_quantiser_matrix[i], 1, i);
}
return 0;
}
static int FUNC(picture_display_extension)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawPictureDisplayExtension *current) {
CodedBitstreamMPEG2Context *mpeg2 = ctx->priv_data;
int err, i;
HEADER("Picture Display Extension");
for(i = 0; i < mpeg2->number_of_frame_centre_offsets; i++) {
sis(16, frame_centre_horizontal_offset[i], 1, i);
marker_bit();
sis(16, frame_centre_vertical_offset[i], 1, i);
marker_bit();
}
return 0;
}
static int FUNC(extension_data)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawExtensionData *current) {
int err;
HEADER("Extension Data");
ui(8, extension_start_code);
ui(4, extension_start_code_identifier);
switch(current->extension_start_code_identifier) {
case MPEG2_EXTENSION_SEQUENCE:
return FUNC(sequence_extension)(ctx, rw, &current->data.sequence);
case MPEG2_EXTENSION_SEQUENCE_DISPLAY:
return FUNC(sequence_display_extension)(ctx, rw, &current->data.sequence_display);
case MPEG2_EXTENSION_QUANT_MATRIX:
return FUNC(quant_matrix_extension)(ctx, rw, &current->data.quant_matrix);
case MPEG2_EXTENSION_PICTURE_DISPLAY:
return FUNC(picture_display_extension)(ctx, rw, &current->data.picture_display);
case MPEG2_EXTENSION_PICTURE_CODING:
return FUNC(picture_coding_extension)(ctx, rw, &current->data.picture_coding);
default:
av_log(ctx->log_ctx, AV_LOG_ERROR, "Extension ID %d not supported.\n",
current->extension_start_code_identifier);
return AVERROR_PATCHWELCOME;
}
}
static int FUNC(slice_header)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawSliceHeader *current) {
CodedBitstreamMPEG2Context *mpeg2 = ctx->priv_data;
int err;
HEADER("Slice Header");
ui(8, slice_vertical_position);
if(mpeg2->vertical_size > 2800)
ui(3, slice_vertical_position_extension);
if(mpeg2->scalable) {
if(mpeg2->scalable_mode == 0)
ui(7, priority_breakpoint);
}
uir(5, quantiser_scale_code);
if(nextbits(1, 1, current->slice_extension_flag)) {
ui(1, slice_extension_flag);
ui(1, intra_slice);
ui(1, slice_picture_id_enable);
ui(6, slice_picture_id);
}
CHECK(FUNC(extra_information)(ctx, rw, &current->extra_information_slice,
"extra_information_slice[k]", "extra_bit_slice"));
return 0;
}
static int FUNC(sequence_end)(CodedBitstreamContext *ctx, RWContext *rw,
MPEG2RawSequenceEnd *current) {
int err;
HEADER("Sequence End");
ui(8, sequence_end_code);
return 0;
}

355
third-party/cbs/cbs_sei.c vendored Normal file
View file

@ -0,0 +1,355 @@
/*
* 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 "cbs/cbs_sei.h"
#include "cbs/cbs.h"
#include "cbs/cbs_h264.h"
#include "cbs/cbs_h265.h"
#include "cbs_internal.h"
static void cbs_free_user_data_registered(void *opaque, uint8_t *data) {
SEIRawUserDataRegistered *udr = (SEIRawUserDataRegistered *)data;
av_buffer_unref(&udr->data_ref);
av_free(udr);
}
static void cbs_free_user_data_unregistered(void *opaque, uint8_t *data) {
SEIRawUserDataUnregistered *udu = (SEIRawUserDataUnregistered *)data;
av_buffer_unref(&udu->data_ref);
av_free(udu);
}
int ff_cbs_sei_alloc_message_payload(SEIRawMessage *message,
const SEIMessageTypeDescriptor *desc) {
void (*free_func)(void *, uint8_t *);
av_assert0(message->payload == NULL &&
message->payload_ref == NULL);
message->payload_type = desc->type;
if(desc->type == SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35)
free_func = &cbs_free_user_data_registered;
else if(desc->type == SEI_TYPE_USER_DATA_UNREGISTERED)
free_func = &cbs_free_user_data_unregistered;
else
free_func = NULL;
if(free_func) {
message->payload = av_mallocz(desc->size);
if(!message->payload)
return AVERROR(ENOMEM);
message->payload_ref =
av_buffer_create(message->payload, desc->size,
free_func, NULL, 0);
}
else {
message->payload_ref = av_buffer_alloc(desc->size);
}
if(!message->payload_ref) {
av_freep(&message->payload);
return AVERROR(ENOMEM);
}
message->payload = message->payload_ref->data;
return 0;
}
int ff_cbs_sei_list_add(SEIRawMessageList *list) {
void *ptr;
int old_count = list->nb_messages_allocated;
av_assert0(list->nb_messages <= old_count);
if(list->nb_messages + 1 > old_count) {
int new_count = 2 * old_count + 1;
ptr = av_realloc_array(list->messages,
new_count, sizeof(*list->messages));
if(!ptr)
return AVERROR(ENOMEM);
list->messages = ptr;
list->nb_messages_allocated = new_count;
// Zero the newly-added entries.
memset(list->messages + old_count, 0,
(new_count - old_count) * sizeof(*list->messages));
}
++list->nb_messages;
return 0;
}
void ff_cbs_sei_free_message_list(SEIRawMessageList *list) {
for(int i = 0; i < list->nb_messages; i++) {
SEIRawMessage *message = &list->messages[i];
av_buffer_unref(&message->payload_ref);
av_buffer_unref(&message->extension_data_ref);
}
av_free(list->messages);
}
static int cbs_sei_get_unit(CodedBitstreamContext *ctx,
CodedBitstreamFragment *au,
int prefix,
CodedBitstreamUnit **sei_unit) {
CodedBitstreamUnit *unit;
int sei_type, highest_vcl_type, err, i, position;
switch(ctx->codec->codec_id) {
case AV_CODEC_ID_H264:
// (We can ignore auxiliary slices because we only have prefix
// SEI in H.264 and an auxiliary picture must always follow a
// primary picture.)
highest_vcl_type = H264_NAL_IDR_SLICE;
if(prefix)
sei_type = H264_NAL_SEI;
else
return AVERROR(EINVAL);
break;
case AV_CODEC_ID_H265:
highest_vcl_type = HEVC_NAL_RSV_VCL31;
if(prefix)
sei_type = HEVC_NAL_SEI_PREFIX;
else
sei_type = HEVC_NAL_SEI_SUFFIX;
break;
default:
return AVERROR(EINVAL);
}
// Find an existing SEI NAL unit of the right type.
unit = NULL;
for(i = 0; i < au->nb_units; i++) {
if(au->units[i].type == sei_type) {
unit = &au->units[i];
break;
}
}
if(unit) {
*sei_unit = unit;
return 0;
}
// Need to add a new SEI NAL unit ...
if(prefix) {
// ... before the first VCL NAL unit.
for(i = 0; i < au->nb_units; i++) {
if(au->units[i].type < highest_vcl_type)
break;
}
position = i;
}
else {
// ... after the last VCL NAL unit.
for(i = au->nb_units - 1; i >= 0; i--) {
if(au->units[i].type < highest_vcl_type)
break;
}
if(i < 0) {
// No VCL units; just put it at the end.
position = au->nb_units;
}
else {
position = i + 1;
}
}
err = ff_cbs_insert_unit_content(au, position, sei_type,
NULL, NULL);
if(err < 0)
return err;
unit = &au->units[position];
unit->type = sei_type;
err = ff_cbs_alloc_unit_content2(ctx, unit);
if(err < 0)
return err;
switch(ctx->codec->codec_id) {
case AV_CODEC_ID_H264: {
H264RawSEI sei = {
.nal_unit_header = {
.nal_ref_idc = 0,
.nal_unit_type = sei_type,
},
};
memcpy(unit->content, &sei, sizeof(sei));
} break;
case AV_CODEC_ID_H265: {
H265RawSEI sei = {
.nal_unit_header = {
.nal_unit_type = sei_type,
.nuh_layer_id = 0,
.nuh_temporal_id_plus1 = 1,
},
};
memcpy(unit->content, &sei, sizeof(sei));
} break;
default:
av_assert0(0);
}
*sei_unit = unit;
return 0;
}
static int cbs_sei_get_message_list(CodedBitstreamContext *ctx,
CodedBitstreamUnit *unit,
SEIRawMessageList **list) {
switch(ctx->codec->codec_id) {
case AV_CODEC_ID_H264: {
H264RawSEI *sei = unit->content;
if(unit->type != H264_NAL_SEI)
return AVERROR(EINVAL);
*list = &sei->message_list;
} break;
case AV_CODEC_ID_H265: {
H265RawSEI *sei = unit->content;
if(unit->type != HEVC_NAL_SEI_PREFIX &&
unit->type != HEVC_NAL_SEI_SUFFIX)
return AVERROR(EINVAL);
*list = &sei->message_list;
} break;
default:
return AVERROR(EINVAL);
}
return 0;
}
int ff_cbs_sei_add_message(CodedBitstreamContext *ctx,
CodedBitstreamFragment *au,
int prefix,
uint32_t payload_type,
void *payload_data,
AVBufferRef *payload_buf) {
const SEIMessageTypeDescriptor *desc;
CodedBitstreamUnit *unit;
SEIRawMessageList *list;
SEIRawMessage *message;
AVBufferRef *payload_ref;
int err;
desc = ff_cbs_sei_find_type(ctx, payload_type);
if(!desc)
return AVERROR(EINVAL);
// Find an existing SEI unit or make a new one to add to.
err = cbs_sei_get_unit(ctx, au, prefix, &unit);
if(err < 0)
return err;
// Find the message list inside the codec-dependent unit.
err = cbs_sei_get_message_list(ctx, unit, &list);
if(err < 0)
return err;
// Add a new message to the message list.
err = ff_cbs_sei_list_add(list);
if(err < 0)
return err;
if(payload_buf) {
payload_ref = av_buffer_ref(payload_buf);
if(!payload_ref)
return AVERROR(ENOMEM);
}
else {
payload_ref = NULL;
}
message = &list->messages[list->nb_messages - 1];
message->payload_type = payload_type;
message->payload = payload_data;
message->payload_ref = payload_ref;
return 0;
}
int ff_cbs_sei_find_message(CodedBitstreamContext *ctx,
CodedBitstreamFragment *au,
uint32_t payload_type,
SEIRawMessage **iter) {
int err, i, j, found;
found = 0;
for(i = 0; i < au->nb_units; i++) {
CodedBitstreamUnit *unit = &au->units[i];
SEIRawMessageList *list;
err = cbs_sei_get_message_list(ctx, unit, &list);
if(err < 0)
continue;
for(j = 0; j < list->nb_messages; j++) {
SEIRawMessage *message = &list->messages[j];
if(message->payload_type == payload_type) {
if(!*iter || found) {
*iter = message;
return 0;
}
if(message == *iter)
found = 1;
}
}
}
return AVERROR(ENOENT);
}
static void cbs_sei_delete_message(SEIRawMessageList *list,
int position) {
SEIRawMessage *message;
av_assert0(0 <= position && position < list->nb_messages);
message = &list->messages[position];
av_buffer_unref(&message->payload_ref);
av_buffer_unref(&message->extension_data_ref);
--list->nb_messages;
if(list->nb_messages > 0) {
memmove(list->messages + position,
list->messages + position + 1,
(list->nb_messages - position) * sizeof(*list->messages));
}
}
void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx,
CodedBitstreamFragment *au,
uint32_t payload_type) {
int err, i, j;
for(i = 0; i < au->nb_units; i++) {
CodedBitstreamUnit *unit = &au->units[i];
SEIRawMessageList *list;
err = cbs_sei_get_message_list(ctx, unit, &list);
if(err < 0)
continue;
for(j = list->nb_messages - 1; j >= 0; j--) {
if(list->messages[j].payload_type == payload_type)
cbs_sei_delete_message(list, j);
}
}
}

View file

@ -0,0 +1,310 @@
/*
* 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
*/
static int FUNC(filler_payload)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawFillerPayload *current, SEIMessageState *state) {
int err, i;
HEADER("Filler Payload");
#ifdef READ
current->payload_size = state->payload_size;
#endif
for(i = 0; i < current->payload_size; i++)
fixed(8, ff_byte, 0xff);
return 0;
}
static int FUNC(user_data_registered)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawUserDataRegistered *current, SEIMessageState *state) {
int err, i, j;
HEADER("User Data Registered ITU-T T.35");
u(8, itu_t_t35_country_code, 0x00, 0xff);
if(current->itu_t_t35_country_code != 0xff)
i = 1;
else {
u(8, itu_t_t35_country_code_extension_byte, 0x00, 0xff);
i = 2;
}
#ifdef READ
if(state->payload_size < i) {
av_log(ctx->log_ctx, AV_LOG_ERROR,
"Invalid SEI user data registered payload.\n");
return AVERROR_INVALIDDATA;
}
current->data_length = state->payload_size - i;
#endif
allocate(current->data, current->data_length);
for(j = 0; j < current->data_length; j++)
xu(8, itu_t_t35_payload_byte[], current->data[j], 0x00, 0xff, 1, i + j);
return 0;
}
static int FUNC(user_data_unregistered)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawUserDataUnregistered *current, SEIMessageState *state) {
int err, i;
HEADER("User Data Unregistered");
#ifdef READ
if(state->payload_size < 16) {
av_log(ctx->log_ctx, AV_LOG_ERROR,
"Invalid SEI user data unregistered payload.\n");
return AVERROR_INVALIDDATA;
}
current->data_length = state->payload_size - 16;
#endif
for(i = 0; i < 16; i++)
us(8, uuid_iso_iec_11578[i], 0x00, 0xff, 1, i);
allocate(current->data, current->data_length);
for(i = 0; i < current->data_length; i++)
xu(8, user_data_payload_byte[i], current->data[i], 0x00, 0xff, 1, i);
return 0;
}
static int FUNC(mastering_display_colour_volume)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawMasteringDisplayColourVolume *current, SEIMessageState *state) {
int err, c;
HEADER("Mastering Display Colour Volume");
for(c = 0; c < 3; c++) {
ubs(16, display_primaries_x[c], 1, c);
ubs(16, display_primaries_y[c], 1, c);
}
ub(16, white_point_x);
ub(16, white_point_y);
ub(32, max_display_mastering_luminance);
ub(32, min_display_mastering_luminance);
return 0;
}
static int FUNC(content_light_level_info)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawContentLightLevelInfo *current, SEIMessageState *state) {
int err;
HEADER("Content Light Level Information");
ub(16, max_content_light_level);
ub(16, max_pic_average_light_level);
return 0;
}
static int FUNC(alternative_transfer_characteristics)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawAlternativeTransferCharacteristics *current,
SEIMessageState *state) {
int err;
HEADER("Alternative Transfer Characteristics");
ub(8, preferred_transfer_characteristics);
return 0;
}
static int FUNC(message)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawMessage *current) {
const SEIMessageTypeDescriptor *desc;
int err, i;
desc = ff_cbs_sei_find_type(ctx, current->payload_type);
if(desc) {
SEIMessageState state = {
.payload_type = current->payload_type,
.payload_size = current->payload_size,
.extension_present = current->extension_bit_length > 0,
};
int start_position, current_position, bits_written;
#ifdef READ
CHECK(ff_cbs_sei_alloc_message_payload(current, desc));
#endif
start_position = bit_position(rw);
CHECK(desc->READWRITE(ctx, rw, current->payload, &state));
current_position = bit_position(rw);
bits_written = current_position - start_position;
if(byte_alignment(rw) || state.extension_present ||
bits_written < 8 * current->payload_size) {
size_t bits_left;
#ifdef READ
GetBitContext tmp = *rw;
int trailing_bits, trailing_zero_bits;
bits_left = 8 * current->payload_size - bits_written;
if(bits_left > 8)
skip_bits_long(&tmp, bits_left - 8);
trailing_bits = get_bits(&tmp, FFMIN(bits_left, 8));
if(trailing_bits == 0) {
// The trailing bits must contain a bit_equal_to_one, so
// they can't all be zero.
return AVERROR_INVALIDDATA;
}
trailing_zero_bits = ff_ctz(trailing_bits);
current->extension_bit_length =
bits_left - 1 - trailing_zero_bits;
#endif
if(current->extension_bit_length > 0) {
allocate(current->extension_data,
(current->extension_bit_length + 7) / 8);
bits_left = current->extension_bit_length;
for(i = 0; bits_left > 0; i++) {
int length = FFMIN(bits_left, 8);
xu(length, reserved_payload_extension_data,
current->extension_data[i],
0, MAX_UINT_BITS(length), 0);
bits_left -= length;
}
}
fixed(1, bit_equal_to_one, 1);
while(byte_alignment(rw))
fixed(1, bit_equal_to_zero, 0);
}
#ifdef WRITE
current->payload_size = (put_bits_count(rw) - start_position) / 8;
#endif
}
else {
uint8_t *data;
allocate(current->payload, current->payload_size);
data = current->payload;
for(i = 0; i < current->payload_size; i++)
xu(8, payload_byte[i], data[i], 0, 255, 1, i);
}
return 0;
}
static int FUNC(message_list)(CodedBitstreamContext *ctx, RWContext *rw,
SEIRawMessageList *current, int prefix) {
SEIRawMessage *message;
int err, k;
#ifdef READ
for(k = 0;; k++) {
uint32_t payload_type = 0;
uint32_t payload_size = 0;
uint32_t tmp;
GetBitContext payload_gbc;
while(show_bits(rw, 8) == 0xff) {
fixed(8, ff_byte, 0xff);
payload_type += 255;
}
xu(8, last_payload_type_byte, tmp, 0, 254, 0);
payload_type += tmp;
while(show_bits(rw, 8) == 0xff) {
fixed(8, ff_byte, 0xff);
payload_size += 255;
}
xu(8, last_payload_size_byte, tmp, 0, 254, 0);
payload_size += tmp;
// There must be space remaining for both the payload and
// the trailing bits on the SEI NAL unit.
if(payload_size + 1 > get_bits_left(rw) / 8) {
av_log(ctx->log_ctx, AV_LOG_ERROR,
"Invalid SEI message: payload_size too large "
"(%" PRIu32 " bytes).\n",
payload_size);
return AVERROR_INVALIDDATA;
}
CHECK(init_get_bits(&payload_gbc, rw->buffer,
get_bits_count(rw) + 8 * payload_size));
skip_bits_long(&payload_gbc, get_bits_count(rw));
CHECK(ff_cbs_sei_list_add(current));
message = &current->messages[k];
message->payload_type = payload_type;
message->payload_size = payload_size;
CHECK(FUNC(message)(ctx, &payload_gbc, message));
skip_bits_long(rw, 8 * payload_size);
if(!cbs_h2645_read_more_rbsp_data(rw))
break;
}
#else
for(k = 0; k < current->nb_messages; k++) {
PutBitContext start_state;
uint32_t tmp;
int trace, i;
message = &current->messages[k];
// We write the payload twice in order to find the size. Trace
// output is switched off for the first write.
trace = ctx->trace_enable;
ctx->trace_enable = 0;
start_state = *rw;
for(i = 0; i < 2; i++) {
*rw = start_state;
tmp = message->payload_type;
while(tmp >= 255) {
fixed(8, ff_byte, 0xff);
tmp -= 255;
}
xu(8, last_payload_type_byte, tmp, 0, 254, 0);
tmp = message->payload_size;
while(tmp >= 255) {
fixed(8, ff_byte, 0xff);
tmp -= 255;
}
xu(8, last_payload_size_byte, tmp, 0, 254, 0);
err = FUNC(message)(ctx, rw, message);
ctx->trace_enable = trace;
if(err < 0)
return err;
}
}
#endif
return 0;
}

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