Merge with master

This commit is contained in:
loki
2021-06-26 12:40:06 +02:00
140 changed files with 37900 additions and 3073 deletions
+67
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
+4 -2
View File
@@ -1,8 +1,10 @@
build build
cmake-build-* cmake-build*
.DS_Store .DS_Store
.vscode
.vs
*.swp *.swp
*.kdev4 *.kdev4
.cache
.idea .idea
+3 -6
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
+99 -65
View File
@@ -1,93 +1,118 @@
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
@@ -95,7 +120,9 @@ else()
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
+53 -39
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:"
* Copy the name to the configuration option "audio_sink"
* restart sunshine
- If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
1. `$ pacmd list-sources | grep "name:"` or `$ pactl info | grep Source` if running pipewire.
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
+5 -5
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%
+1 -1
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"]
} }
] ]
} }
+1 -3
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\""}
]
} }
] ]
} }
+33
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);
}
+29
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;
}
+25
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;
}
+14
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);
}
+22
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;
}
+35
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);
}
+27
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);
}
+26
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;
}
+14
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);
}
+22
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);
}
+78 -5
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
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
View File
@@ -0,0 +1,3 @@
<div id="content" class="container">
<h1>Clients</h1>
</div>
+560
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
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
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
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
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>
+19 -2
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
Submodule pre-compiled deleted from afd9a9bbfc
+1 -6
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
+162 -50
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
+32 -5
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
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
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
+350 -120
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
+33 -8
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
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
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
+37 -27
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
+16 -12
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
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
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
+250 -86
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
+13 -2
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
+155 -50
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;
}
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; return 0;
} }
+31 -1
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
+8 -7
View File
@@ -11,11 +11,12 @@ 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;
@@ -23,10 +24,10 @@ public:
*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
+13 -13
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
+3 -3
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
+234 -228
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);
@@ -176,7 +211,7 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin
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;
}
}
+7 -8
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
+121 -15
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
+437
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
+169 -208
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
+223 -142
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; }
}
+88
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
+8
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
+164
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
+18 -6
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
+98 -69
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
+58 -62
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;
@@ -26,25 +26,25 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
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;
} }
@@ -115,26 +115,26 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
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,7 +221,7 @@ 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;
@@ -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
+105 -128
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
+90
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
+74 -39
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
+15 -7
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);
@@ -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
+22 -22
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;
+1 -1
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
+28 -21
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,7 +125,7 @@ public:
} }
void dec() { void dec() {
if(_pos == _begin) { if(_pos == _begin) {
_pos = _end; _pos = _end;
} }
@@ -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
+134 -100
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
+4 -2
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
+182 -167
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
+3 -6
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
+6 -5
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
+21 -20
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,6 +52,7 @@ 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;
@@ -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()>;
@@ -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;
} }
} }
@@ -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
+9 -9
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 {
/* /*
@@ -20,24 +20,25 @@ private:
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
+210 -44
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
+404 -169
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
+6 -6
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
+801 -338
View File
File diff suppressed because it is too large Load Diff
+48 -15
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
View File
+69
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
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
View File
File diff suppressed because it is too large Load Diff
+1323
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1632
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+220
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
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,
};
+189
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
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,
};
+413
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
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);
}
}
}
+310
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