Merge branch 'master' of https://github.com/loki-47-6F-64/sunshine into loki-47-6F-64-master
This commit is contained in:
67
.clang-format
Normal file
67
.clang-format
Normal 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
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,5 @@ cmake-build*
|
|||||||
*.swp
|
*.swp
|
||||||
*.kdev4
|
*.kdev4
|
||||||
|
|
||||||
|
.cache
|
||||||
.idea
|
.idea
|
||||||
@@ -5,6 +5,14 @@ project(Sunshine)
|
|||||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
add_subdirectory(Simple-Web-Server)
|
add_subdirectory(Simple-Web-Server)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
# Ugly hack to compile with #include <qos2.h>
|
||||||
|
add_compile_definitions(
|
||||||
|
QOS_FLOWID=UINT32
|
||||||
|
PQOS_FLOWID=UINT32*
|
||||||
|
QOS_NON_ADAPTIVE_FLOW=2)
|
||||||
|
endif()
|
||||||
add_subdirectory(moonlight-common-c/enet)
|
add_subdirectory(moonlight-common-c/enet)
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
@@ -71,6 +79,7 @@ if(WIN32)
|
|||||||
libstdc++.a
|
libstdc++.a
|
||||||
libwinpthread.a
|
libwinpthread.a
|
||||||
libssp.a
|
libssp.a
|
||||||
|
Qwave
|
||||||
winmm
|
winmm
|
||||||
ksuser
|
ksuser
|
||||||
wsock32
|
wsock32
|
||||||
@@ -90,6 +99,7 @@ else()
|
|||||||
find_package(FFmpeg REQUIRED)
|
find_package(FFmpeg REQUIRED)
|
||||||
set(PLATFORM_TARGET_FILES
|
set(PLATFORM_TARGET_FILES
|
||||||
sunshine/platform/linux/display.cpp
|
sunshine/platform/linux/display.cpp
|
||||||
|
sunshine/platform/linux/audio.cpp
|
||||||
sunshine/platform/linux/input.cpp)
|
sunshine/platform/linux/input.cpp)
|
||||||
|
|
||||||
set(PLATFORM_LIBRARIES
|
set(PLATFORM_LIBRARIES
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -1,6 +1,9 @@
|
|||||||
# Introduction
|
# Introduction
|
||||||
Sunshine is a Gamestream host for Moonlight
|
Sunshine is a Gamestream host for Moonlight
|
||||||
|
|
||||||
|
[](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master)
|
||||||
|
[](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,39 +15,51 @@ 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
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
### 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:"
|
||||||
|
2. Copy the name to the configuration option "audio_sink"
|
||||||
|
3. restart sunshine
|
||||||
|
|
||||||
## Windows 10
|
## Windows 10
|
||||||
|
|
||||||
@@ -90,6 +105,7 @@ 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:
|
||||||
- You can use Environment variables in place of values
|
- You can use Environment variables in place of values
|
||||||
@@ -105,15 +121,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
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ environment:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- sh: sudo apt update --ignore-missing
|
- 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 libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||||
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
|
- 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,7 +20,7 @@ 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_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
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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\""}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,26 @@
|
|||||||
# 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 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 +99,16 @@
|
|||||||
#
|
#
|
||||||
# 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:"
|
||||||
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor
|
# 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:
|
||||||
@@ -94,8 +118,8 @@
|
|||||||
# output_name = \\.\DISPLAY1
|
# output_name = \\.\DISPLAY1
|
||||||
|
|
||||||
# !! Linux only !!
|
# !! Linux only !!
|
||||||
# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually.
|
# Set the display number to stream. I have no idea how they are numbered. They start from 0, usually.
|
||||||
# output_name = 1
|
# output_name = 0
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
# FFmpeg software encoding parameters
|
# FFmpeg software encoding parameters
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Package: sunshine
|
|||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Maintainer: @loki
|
Maintainer: @loki
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Version: 0.2.1
|
Version: 0.4.1
|
||||||
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.71.0, libboost-filesystem1.71.0, libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0
|
||||||
Description: Gamestream host for Moonlight
|
Description: Gamestream host for Moonlight
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
Submodule moonlight-common-c updated: cfeb0ffd90...5d09d43b08
@@ -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
|
||||||
|
|||||||
@@ -4,55 +4,78 @@
|
|||||||
|
|
||||||
#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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
|
||||||
|
|
||||||
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
|
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
|
||||||
//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,8 +83,7 @@ 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) };
|
||||||
};
|
|
||||||
|
|
||||||
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()) {
|
||||||
@@ -81,6 +103,48 @@ 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::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
|
||||||
|
//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, packets, samples, config, channel_data };
|
||||||
|
|
||||||
@@ -91,19 +155,16 @@ 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);
|
||||||
@@ -116,7 +177,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
|
|||||||
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;
|
||||||
|
|
||||||
@@ -130,4 +191,48 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
|
|||||||
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
|
||||||
|
|||||||
@@ -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 packet_t = util::buffer_t<std::uint8_t>;
|
||||||
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void *, packet_t>>>;
|
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void *, packet_t>>>;
|
||||||
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data);
|
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data);
|
||||||
}
|
} // namespace audio
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
#include "utility.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
#define CA_DIR "credentials"
|
#define CA_DIR "credentials"
|
||||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||||
@@ -47,7 +48,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);
|
||||||
@@ -64,7 +66,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);
|
||||||
@@ -82,7 +85,7 @@ int coder_from_view(const std::string_view &coder) {
|
|||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
} // namespace nv
|
||||||
|
|
||||||
namespace amd {
|
namespace amd {
|
||||||
enum quality_e : int {
|
enum quality_e : int {
|
||||||
@@ -106,7 +109,8 @@ enum coder_e : int {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
|
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
|
||||||
#define _CONVERT_(x) if(quality == #x##sv) return x
|
#define _CONVERT_(x) \
|
||||||
|
if(quality == #x##sv) return x
|
||||||
_CONVERT_(speed);
|
_CONVERT_(speed);
|
||||||
_CONVERT_(balanced);
|
_CONVERT_(balanced);
|
||||||
//_CONVERT_(quality2);
|
//_CONVERT_(quality2);
|
||||||
@@ -116,7 +120,8 @@ std::optional<quality_e> quality_from_view(const std::string_view &quality) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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_latency);
|
_CONVERT_(vbr_latency);
|
||||||
_CONVERT_(vbr_peak);
|
_CONVERT_(vbr_peak);
|
||||||
@@ -132,7 +137,7 @@ int coder_from_view(const std::string_view &coder) {
|
|||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
} // namespace amd
|
||||||
|
|
||||||
video_t video {
|
video_t video {
|
||||||
0, // crf
|
0, // crf
|
||||||
@@ -149,14 +154,12 @@ video_t video {
|
|||||||
{
|
{
|
||||||
nv::llhq,
|
nv::llhq,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
-1
|
-1 }, // nv
|
||||||
}, // nv
|
|
||||||
|
|
||||||
{
|
{
|
||||||
amd::balanced,
|
amd::balanced,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
-1
|
-1 }, // amd
|
||||||
}, // amd
|
|
||||||
|
|
||||||
{}, // encoder
|
{}, // encoder
|
||||||
{}, // adapter_name
|
{}, // adapter_name
|
||||||
@@ -180,7 +183,22 @@ 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 {
|
||||||
@@ -194,28 +212,84 @@ sunshine_t sunshine {
|
|||||||
0 // flags
|
0 // flags
|
||||||
};
|
};
|
||||||
|
|
||||||
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool whitespace(char ch) {
|
||||||
|
return space_tab(ch) || endline(ch);
|
||||||
|
}
|
||||||
|
|
||||||
std::string to_string(const char *begin, const char *end) {
|
std::string to_string(const char *begin, const char *end) {
|
||||||
return { begin, (std::size_t)(end - begin) };
|
std::string result;
|
||||||
|
|
||||||
|
KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
|
||||||
|
auto comment = std::find(pos, end, '#');
|
||||||
|
auto endl = std::find_if(comment, end, endline);
|
||||||
|
|
||||||
|
result.append(pos, comment);
|
||||||
|
|
||||||
|
pos = endl;
|
||||||
|
})
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<std::string, std::string>> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
template<class It>
|
||||||
begin = std::find_if(begin, end, std::not_fn(whitespace));
|
It skip_list(It skipper, It end) {
|
||||||
end = std::find(begin, end, '#');
|
int stack = 1;
|
||||||
end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
while(skipper != end && stack) {
|
||||||
|
if(*skipper == '[') {
|
||||||
auto eq = std::find(begin, end, '=');
|
++stack;
|
||||||
if(eq == end || eq == begin) {
|
}
|
||||||
return std::nullopt;
|
if(*skipper == ']') {
|
||||||
|
--stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
++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) };
|
return skipper;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(std::string_view file_content) {
|
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content) {
|
||||||
@@ -225,10 +299,14 @@ std::unordered_map<std::string, std::string> parse_config(std::string_view file_
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -320,8 +398,7 @@ 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));
|
||||||
@@ -366,15 +443,68 @@ void double_between_f(std::unordered_map<std::string, std::string> &vars, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||||
|
std::string string;
|
||||||
|
string_f(vars, name, string);
|
||||||
|
|
||||||
|
if(string.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void print_help(const char *name) {
|
void print_help(const char *name) {
|
||||||
std::cout <<
|
std::cout
|
||||||
"Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl <<
|
<< "Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl
|
||||||
" Any configurable option can be overwritten with: \"name=value\""sv << std::endl << std::endl <<
|
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
|
||||||
" --help | print help"sv << std::endl << std::endl <<
|
<< std::endl
|
||||||
" flags"sv << std::endl <<
|
<< " --help | print help"sv << std::endl
|
||||||
" -0 | Read PIN from stdin"sv << std::endl <<
|
<< std::endl
|
||||||
" -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl <<
|
<< " flags"sv << std::endl
|
||||||
" | Effectively starting as if for the first time without overwriting any pairings with your devices"sv;
|
<< " -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
int apply_flags(const char *line) {
|
int apply_flags(const char *line) {
|
||||||
@@ -409,9 +539,7 @@ 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);
|
||||||
@@ -431,29 +559,24 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
|||||||
string_f(vars, "sunshine_name", nvhttp.sunshine_name);
|
string_f(vars, "sunshine_name", nvhttp.sunshine_name);
|
||||||
string_f(vars, "file_state", nvhttp.file_state);
|
string_f(vars, "file_state", nvhttp.file_state);
|
||||||
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);
|
string_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);
|
||||||
@@ -463,9 +586,7 @@ 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 };
|
||||||
@@ -478,9 +599,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) {
|
||||||
@@ -546,7 +665,7 @@ int parse(int argc, char *argv[]) {
|
|||||||
config_file = line;
|
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;
|
||||||
@@ -568,8 +687,7 @@ int parse(int argc, char *argv[]) {
|
|||||||
auto vars = parse_config(std::string {
|
auto vars = parse_config(std::string {
|
||||||
// Quick and dirty
|
// Quick and dirty
|
||||||
std::istreambuf_iterator<char>(in),
|
std::istreambuf_iterator<char>(in),
|
||||||
std::istreambuf_iterator<char>()
|
std::istreambuf_iterator<char>() });
|
||||||
});
|
|
||||||
|
|
||||||
for(auto &[name, value] : cmd_vars) {
|
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));
|
||||||
@@ -579,4 +697,4 @@ int parse(int argc, char *argv[]) {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
} // namespace config
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#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 <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace config {
|
namespace config {
|
||||||
struct video_t {
|
struct video_t {
|
||||||
@@ -40,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 {
|
||||||
@@ -66,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 {
|
||||||
@@ -78,8 +82,8 @@ 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
|
||||||
FLAG_SIZE,
|
CONST_PIN = 4, // Use "universal" pin
|
||||||
CONST_PIN= 4 // Use "universal" pin
|
FLAG_SIZE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +102,4 @@ 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(std::string_view file_content);
|
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content);
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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>;
|
||||||
@@ -338,4 +338,4 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
} // namespace crypto
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
#ifndef SUNSHINE_CRYPTO_H
|
#ifndef SUNSHINE_CRYPTO_H
|
||||||
#define SUNSHINE_CRYPTO_H
|
#define SUNSHINE_CRYPTO_H
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
#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"
|
||||||
|
|
||||||
@@ -58,6 +58,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;
|
||||||
@@ -73,6 +74,7 @@ public:
|
|||||||
|
|
||||||
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 +82,6 @@ private:
|
|||||||
public:
|
public:
|
||||||
bool padding;
|
bool padding;
|
||||||
};
|
};
|
||||||
}
|
} // namespace crypto
|
||||||
|
|
||||||
#endif //SUNSHINE_CRYPTO_H
|
#endif //SUNSHINE_CRYPTO_H
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -40,6 +46,11 @@ void free_id(std::bitset<N> &gamepad_mask, int id) {
|
|||||||
gamepad_mask[id] = false;
|
gamepad_mask[id] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_port_event_t touch_port_event;
|
||||||
|
platf::touch_port_t touch_port {
|
||||||
|
0, 0, 0, 0
|
||||||
|
};
|
||||||
|
|
||||||
static util::TaskPool::task_id_t task_id {};
|
static util::TaskPool::task_id_t task_id {};
|
||||||
static std::unordered_map<short, bool> key_press {};
|
static std::unordered_map<short, bool> key_press {};
|
||||||
static std::array<std::uint8_t, 5> mouse_press {};
|
static std::array<std::uint8_t, 5> mouse_press {};
|
||||||
@@ -78,20 +89,32 @@ struct gamepad_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct input_t {
|
struct input_t {
|
||||||
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { }
|
input_t() : active_gamepad_state {}, gamepads(MAX_GAMEPADS), mouse_left_button_timeout {} {}
|
||||||
|
|
||||||
std::uint16_t active_gamepad_state;
|
std::uint16_t active_gamepad_state;
|
||||||
std::vector<gamepad_t> gamepads;
|
std::vector<gamepad_t> gamepads;
|
||||||
|
|
||||||
|
util::ThreadPool::task_id_t mouse_left_button_timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
||||||
@@ -138,14 +161,16 @@ 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;
|
||||||
|
case PACKET_TYPE_ABS_MOUSE_MOVE:
|
||||||
|
print((PNV_ABS_MOUSE_MOVE_PACKET)input);
|
||||||
break;
|
break;
|
||||||
case PACKET_TYPE_MOUSE_BUTTON:
|
case PACKET_TYPE_MOUSE_BUTTON:
|
||||||
print((PNV_MOUSE_BUTTON_PACKET)input);
|
print((PNV_MOUSE_BUTTON_PACKET)input);
|
||||||
break;
|
break;
|
||||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
|
||||||
{
|
|
||||||
char *tmp_input = (char *)input + 4;
|
char *tmp_input = (char *)input + 4;
|
||||||
if(tmp_input[0] == 0x0A) {
|
if(tmp_input[0] == 0x0A) {
|
||||||
print((PNV_SCROLL_PACKET)input);
|
print((PNV_SCROLL_PACKET)input);
|
||||||
@@ -162,23 +187,108 @@ void print(void *input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
float width = util::endian::big(packet->width);
|
||||||
|
float height = util::endian::big(packet->height);
|
||||||
|
|
||||||
|
auto scale_x = (float)touch_port.width / width;
|
||||||
|
auto scale_y = (float)touch_port.height / height;
|
||||||
|
|
||||||
|
platf::abs_mouse(platf_input, touch_port, x * scale_x, y * scale_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -223,10 +333,10 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
|||||||
platf::keyboard(platf_input, packet->keyCode & 0x00FF, release);
|
platf::keyboard(platf_input, packet->keyCode & 0x00FF, 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) {
|
||||||
@@ -335,7 +445,7 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
|
|||||||
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,7 +464,9 @@ 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) {
|
||||||
@@ -374,17 +486,19 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
|
|||||||
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;
|
||||||
|
case PACKET_TYPE_ABS_MOUSE_MOVE:
|
||||||
|
passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
|
||||||
break;
|
break;
|
||||||
case PACKET_TYPE_MOUSE_BUTTON:
|
case PACKET_TYPE_MOUSE_BUTTON:
|
||||||
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
||||||
break;
|
break;
|
||||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
|
||||||
{
|
|
||||||
char *tmp_input = (char *)payload + 4;
|
char *tmp_input = (char *)payload + 4;
|
||||||
if(tmp_input[0] == 0x0A) {
|
if(tmp_input[0] == 0x0A) {
|
||||||
passthrough(platf_input, (PNV_SCROLL_PACKET)payload);
|
passthrough((PNV_SCROLL_PACKET)payload);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
|
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
|
||||||
@@ -402,13 +516,19 @@ 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() {
|
void reset(std::shared_ptr<input_t> &input) {
|
||||||
if(task_id) {
|
|
||||||
task_pool.cancel(task_id);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure input is synchronous
|
|
||||||
task_pool.push([]() {
|
|
||||||
for(auto &kp : key_press) {
|
for(auto &kp : key_press) {
|
||||||
platf::keyboard(platf_input, kp.first & 0x00FF, true);
|
platf::keyboard(platf_input, kp.first & 0x00FF, true);
|
||||||
key_press[kp.first] = false;
|
key_press[kp.first] = false;
|
||||||
@@ -417,6 +537,7 @@ void reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
|
touch_port_event = std::make_unique<touch_port_event_t::element_type>();
|
||||||
platf_input = platf::input();
|
platf_input = platf::input();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,8 +548,9 @@ std::shared_ptr<input_t> alloc() {
|
|||||||
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
|
||||||
|
|||||||
@@ -5,18 +5,23 @@
|
|||||||
#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();
|
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();
|
||||||
}
|
|
||||||
|
using touch_port_event_t = std::unique_ptr<safe::event_t<platf::touch_port_t>>;
|
||||||
|
extern touch_port_event_t touch_port_event;
|
||||||
|
} // namespace input
|
||||||
|
|
||||||
#endif //SUNSHINE_INPUT_H
|
#endif //SUNSHINE_INPUT_H
|
||||||
|
|||||||
@@ -4,29 +4,28 @@
|
|||||||
|
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <iostream>
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#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 "config.h"
|
||||||
#include "input.h"
|
|
||||||
#include "nvhttp.h"
|
#include "nvhttp.h"
|
||||||
#include "httpcommon.h"
|
#include "httpcommon.h"
|
||||||
#include "confighttp.h"
|
#include "confighttp.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
#include "config.h"
|
|
||||||
#include "thread_pool.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>
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
@@ -133,7 +132,6 @@ int main(int argc, char *argv[]) {
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
input::init();
|
|
||||||
reed_solomon_init();
|
reed_solomon_init();
|
||||||
if(video::init()) {
|
if(video::init()) {
|
||||||
return 2;
|
return 2;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
#ifndef SUNSHINE_MAIN_H
|
#ifndef SUNSHINE_MAIN_H
|
||||||
#define SUNSHINE_MAIN_H
|
#define SUNSHINE_MAIN_H
|
||||||
|
|
||||||
#include <boost/log/common.hpp>
|
|
||||||
#include "thread_pool.h"
|
#include "thread_pool.h"
|
||||||
|
#include <boost/log/common.hpp>
|
||||||
|
|
||||||
extern util::ThreadPool task_pool;
|
extern util::ThreadPool task_pool;
|
||||||
extern bool display_cursor;
|
extern bool display_cursor;
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ 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:
|
|
||||||
|
|
||||||
|
public:
|
||||||
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {}
|
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {}
|
||||||
|
|
||||||
MoveByCopy(MoveByCopy &&other) = default;
|
MoveByCopy(MoveByCopy &&other) = default;
|
||||||
@@ -46,5 +47,5 @@ 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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -112,4 +112,4 @@ void free_host(ENetHost *host) {
|
|||||||
|
|
||||||
enet_host_destroy(host);
|
enet_host_destroy(host);
|
||||||
}
|
}
|
||||||
}
|
} // namespace net
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -17,17 +19,19 @@
|
|||||||
#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 "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"
|
#include "main.h"
|
||||||
#include "httpcommon.h"
|
#include "httpcommon.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace nvhttp {
|
namespace nvhttp {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
constexpr auto PORT_HTTP = 47989;
|
constexpr auto PORT_HTTP = 47989;
|
||||||
@@ -67,8 +71,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;
|
||||||
};
|
};
|
||||||
@@ -114,7 +118,6 @@ void save_state() {
|
|||||||
|
|
||||||
void load_state() {
|
void load_state() {
|
||||||
auto file_state = fs::current_path() / config::nvhttp.file_state;
|
auto file_state = fs::current_path() / config::nvhttp.file_state;
|
||||||
|
|
||||||
if(!fs::exists(file_state)) {
|
if(!fs::exists(file_state)) {
|
||||||
http::unique_id = util::uuid_t::generate().string();
|
http::unique_id = util::uuid_t::generate().string();
|
||||||
return;
|
return;
|
||||||
@@ -123,7 +126,8 @@ void load_state() {
|
|||||||
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) {
|
}
|
||||||
|
catch(std::exception &e) {
|
||||||
BOOST_LOG(warning) << e.what();
|
BOOST_LOG(warning) << e.what();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -146,13 +150,11 @@ 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;
|
||||||
break;
|
|
||||||
case op_e::REMOVE:
|
case op_e::REMOVE:
|
||||||
map_id_client.erase(uniqueID);
|
map_id_client.erase(uniqueID);
|
||||||
break;
|
break;
|
||||||
@@ -163,6 +165,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);
|
||||||
@@ -330,19 +346,26 @@ 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) {
|
||||||
@@ -489,10 +512,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;
|
||||||
|
|
||||||
@@ -503,9 +551,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([&]() {
|
||||||
@@ -515,6 +560,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);
|
||||||
@@ -538,7 +592,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;
|
||||||
@@ -557,6 +611,18 @@ void launch(resp_https_t response, req_https_t request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto args = request->parse_query_string();
|
auto args = request->parse_query_string();
|
||||||
|
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 appid = util::from_view(args.at("appid")) - 1;
|
||||||
|
|
||||||
auto current_appid = proc::proc.running();
|
auto current_appid = proc::proc.running();
|
||||||
@@ -577,23 +643,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;
|
||||||
@@ -621,18 +678,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);
|
||||||
@@ -733,6 +790,9 @@ 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;
|
||||||
@@ -742,9 +802,9 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
|
|||||||
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;
|
||||||
@@ -763,7 +823,8 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
|
|||||||
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);
|
||||||
@@ -773,7 +834,8 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
|
|||||||
auto accept_and_run = [&](auto *http_server) {
|
auto accept_and_run = [&](auto *http_server) {
|
||||||
try {
|
try {
|
||||||
http_server->accept_and_run();
|
http_server->accept_and_run();
|
||||||
} catch(boost::system::system_error &err) {
|
}
|
||||||
|
catch(boost::system::system_error &err) {
|
||||||
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
|
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
|
||||||
if(shutdown_event->peek()) {
|
if(shutdown_event->peek()) {
|
||||||
return;
|
return;
|
||||||
@@ -796,4 +858,3 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
|
|||||||
ssl.join();
|
ssl.join();
|
||||||
tcp.join();
|
tcp.join();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
#ifndef SUNSHINE_COMMON_H
|
#ifndef SUNSHINE_COMMON_H
|
||||||
#define SUNSHINE_COMMON_H
|
#define SUNSHINE_COMMON_H
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <mutex>
|
|
||||||
#include "sunshine/utility.h"
|
#include "sunshine/utility.h"
|
||||||
|
#include <bitset>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
struct sockaddr;
|
struct sockaddr;
|
||||||
namespace platf {
|
namespace platf {
|
||||||
@@ -29,6 +30,43 @@ 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;
|
||||||
|
|
||||||
|
namespace speaker {
|
||||||
|
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 dev_type_e {
|
enum class dev_type_e {
|
||||||
none,
|
none,
|
||||||
dxgi,
|
dxgi,
|
||||||
@@ -45,7 +83,9 @@ enum class pix_fmt_e {
|
|||||||
|
|
||||||
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
|
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
#define _CONVERT(x) case pix_fmt_e:: x : return #x ## sv
|
#define _CONVERT(x) \
|
||||||
|
case pix_fmt_e::x: \
|
||||||
|
return #x##sv
|
||||||
switch(pix_fmt) {
|
switch(pix_fmt) {
|
||||||
_CONVERT(yuv420p);
|
_CONVERT(yuv420p);
|
||||||
_CONVERT(yuv420p10);
|
_CONVERT(yuv420p10);
|
||||||
@@ -58,6 +98,17 @@ using namespace std::literals;
|
|||||||
return "unknown"sv;
|
return "unknown"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dimensions for touchscreen input
|
||||||
|
struct touch_port_t {
|
||||||
|
std::uint32_t offset_x, offset_y;
|
||||||
|
std::uint32_t width, height;
|
||||||
|
|
||||||
|
constexpr touch_port_t(
|
||||||
|
std::uint32_t offset_x, std::uint32_t offset_y,
|
||||||
|
std::uint32_t width, std::uint32_t height) noexcept : offset_x { offset_x }, offset_y { offset_y },
|
||||||
|
width { width }, height { height } {};
|
||||||
|
};
|
||||||
|
|
||||||
struct gamepad_state_t {
|
struct gamepad_state_t {
|
||||||
std::uint16_t buttonFlags;
|
std::uint16_t buttonFlags;
|
||||||
std::uint8_t lt;
|
std::uint8_t lt;
|
||||||
@@ -88,6 +139,20 @@ public:
|
|||||||
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 {};
|
platf::img_t *img {};
|
||||||
@@ -110,6 +175,7 @@ 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;
|
||||||
|
|
||||||
@@ -121,6 +187,9 @@ public:
|
|||||||
|
|
||||||
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 width, height;
|
int width, height;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +200,16 @@ public:
|
|||||||
virtual ~mic_t() = default;
|
virtual ~mic_t() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class audio_control_t {
|
||||||
|
public:
|
||||||
|
virtual int set_sink(const std::string &sink) = 0;
|
||||||
|
|
||||||
|
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 *);
|
void freeInput(void *);
|
||||||
|
|
||||||
@@ -141,11 +220,12 @@ 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(dev_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);
|
||||||
@@ -155,6 +235,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
|
||||||
|
|||||||
441
sunshine/platform/linux/audio.cpp
Normal file
441
sunshine/platform/linux/audio.cpp
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
//
|
||||||
|
// Created by loki on 5/16/21.
|
||||||
|
//
|
||||||
|
#include <bitset>
|
||||||
|
#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->flags & PA_SINK_HARDWARE &&
|
||||||
|
sink_info->channel_map.channels > channels) {
|
||||||
|
|
||||||
|
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 hardware 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<deinit_t> init() {
|
||||||
|
return std::make_unique<deinit_t>();
|
||||||
|
}
|
||||||
|
} // namespace platf
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
#include "sunshine/platform/common.h"
|
#include "sunshine/platform/common.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <bitset>
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <ifaddrs.h>
|
#include <ifaddrs.h>
|
||||||
@@ -15,20 +14,16 @@
|
|||||||
#include <X11/Xutil.h>
|
#include <X11/Xutil.h>
|
||||||
#include <X11/extensions/Xfixes.h>
|
#include <X11/extensions/Xfixes.h>
|
||||||
#include <X11/extensions/Xrandr.h>
|
#include <X11/extensions/Xrandr.h>
|
||||||
#include <xcb/shm.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"
|
||||||
|
|
||||||
namespace platf
|
namespace platf {
|
||||||
{
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
void freeImage(XImage *);
|
void freeImage(XImage *);
|
||||||
@@ -37,12 +32,15 @@ void freeX(XFixesCursorImage*);
|
|||||||
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
|
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 } {}
|
||||||
@@ -62,24 +60,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +81,7 @@ struct x11_img_t: public img_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct shm_img_t : public img_t {
|
struct shm_img_t : public img_t {
|
||||||
~shm_img_t() override
|
~shm_img_t() override {
|
||||||
{
|
|
||||||
delete[] data;
|
delete[] data;
|
||||||
data = nullptr;
|
data = nullptr;
|
||||||
}
|
}
|
||||||
@@ -102,8 +91,7 @@ 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) {
|
||||||
BOOST_LOG(error)
|
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
|
||||||
<< "Couldn't get cursor from XFixesGetCursorImage"sv;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,31 +136,25 @@ void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct x11_attr_t: public display_t
|
struct x11_attr_t : public display_t {
|
||||||
{
|
|
||||||
xdisplay_t xdisplay;
|
xdisplay_t xdisplay;
|
||||||
Window xwindow;
|
Window xwindow;
|
||||||
XWindowAttributes xattr;
|
XWindowAttributes xattr;
|
||||||
|
|
||||||
Display* displayDisplay;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming
|
* Last X (NOT the streamed monitor!) size.
|
||||||
|
* This way we can trigger reinitialization if the dimensions changed while streaming
|
||||||
*/
|
*/
|
||||||
int lastWidth, lastHeight;
|
int lastWidth, lastHeight;
|
||||||
|
|
||||||
/*
|
x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {} {
|
||||||
* Offsets for when streaming a specific monitor. By default, they are 0.
|
|
||||||
*/
|
|
||||||
int displayOffsetX,displayOffsetY;
|
|
||||||
|
|
||||||
x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { }
|
|
||||||
{
|
|
||||||
XInitThreads();
|
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());
|
||||||
@@ -184,38 +166,30 @@ struct x11_attr_t: public display_t
|
|||||||
streamedMonitor = (int)util::from_view(config::video.output_name);
|
streamedMonitor = (int)util::from_view(config::video.output_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the value has been set at all
|
|
||||||
if(streamedMonitor != -1) {
|
if(streamedMonitor != -1) {
|
||||||
|
BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv;
|
||||||
BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv;
|
screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) };
|
||||||
XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow);
|
|
||||||
// This is the key right here. Use XRRScreenResources::noutput
|
|
||||||
int output = screenr->noutput;
|
int output = screenr->noutput;
|
||||||
|
|
||||||
if(streamedMonitor >= output) {
|
if(streamedMonitor >= output) {
|
||||||
BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv;
|
BOOST_LOG(error) << "Could not stream display number ["sv << streamedMonitor << "], there are only ["sv << output << "] displays."sv;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]);
|
output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[streamedMonitor]) };
|
||||||
if (NULL == out_info || out_info->connection != RR_Connected)
|
if(!out_info || out_info->connection != RR_Connected) {
|
||||||
{
|
|
||||||
BOOST_LOG(error) << "Could not stream selected display because it doesn't seem to be connected"sv;
|
BOOST_LOG(error) << "Could not stream selected display because it doesn't seem to be connected"sv;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), out_info->crtc) };
|
||||||
XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc);
|
BOOST_LOG(info)
|
||||||
BOOST_LOG(info)<<"Streaming display: "<<out_info->name<<" with res "<<crt_info->width<<" x "<<crt_info->height<<+" offset by "<<crt_info->x<<" x "<<crt_info->y<<"."sv;
|
<< "Streaming display: "sv << out_info->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;
|
width = crt_info->width;
|
||||||
height = crt_info->height;
|
height = crt_info->height;
|
||||||
displayOffsetX = crt_info -> x;
|
offset_x = crt_info->x;
|
||||||
displayOffsetY = crt_info -> y;
|
offset_y = crt_info->y;
|
||||||
|
|
||||||
XRRFreeCrtcInfo(crt_info);
|
|
||||||
}
|
|
||||||
XRRFreeOutputInfo(out_info);
|
|
||||||
}
|
|
||||||
XRRFreeScreenResources(screenr);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
width = xattr.width;
|
width = xattr.width;
|
||||||
@@ -224,13 +198,14 @@ struct x11_attr_t: public display_t
|
|||||||
|
|
||||||
lastWidth = xattr.width;
|
lastWidth = xattr.width;
|
||||||
lastHeight = xattr.height;
|
lastHeight = xattr.height;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the display attributes should change.
|
* Called when the display attributes should change.
|
||||||
*/
|
*/
|
||||||
void refresh()
|
void refresh() {
|
||||||
{
|
|
||||||
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
|
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +217,7 @@ struct x11_attr_t: public display_t
|
|||||||
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
|
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, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) };
|
XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
|
||||||
|
|
||||||
auto img_out = (x11_img_t *)img_out_base;
|
auto img_out = (x11_img_t *)img_out_base;
|
||||||
img_out->width = img->width;
|
img_out->width = img->width;
|
||||||
@@ -253,7 +228,7 @@ struct x11_attr_t: public display_t
|
|||||||
img_out->img.reset(img);
|
img_out->img.reset(img);
|
||||||
|
|
||||||
if(cursor) {
|
if(cursor) {
|
||||||
blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY);
|
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
return capture_e::ok;
|
return capture_e::ok;
|
||||||
@@ -292,7 +267,8 @@ struct shm_attr_t: public x11_attr_t {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~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 {
|
||||||
@@ -302,19 +278,18 @@ struct shm_attr_t: public x11_attr_t {
|
|||||||
return capture_e::reinit;
|
return capture_e::reinit;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root,displayOffsetX, displayOffsetY, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
|
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);
|
||||||
|
|
||||||
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
|
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
|
||||||
if(!img_reply) {
|
if(!img_reply) {
|
||||||
BOOST_LOG(error)
|
BOOST_LOG(error) << "Could not get image reply"sv;
|
||||||
<< "Could not get image reply"sv;
|
|
||||||
return capture_e::reinit;
|
return capture_e::reinit;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::copy_n((std::uint8_t *)data.data, frame_size(), img->data);
|
std::copy_n((std::uint8_t *)data.data, frame_size(), img->data);
|
||||||
|
|
||||||
if(cursor) {
|
if(cursor) {
|
||||||
blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY);
|
blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
return capture_e::ok;
|
return capture_e::ok;
|
||||||
@@ -337,17 +312,18 @@ 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())) {
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present)
|
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) {
|
||||||
{
|
BOOST_LOG(error) << "Missing SHM extension"sv;
|
||||||
BOOST_LOG(error)
|
|
||||||
<< "Missing SHM extension"sv;
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -357,30 +333,20 @@ struct shm_attr_t: public x11_attr_t {
|
|||||||
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) {
|
||||||
{
|
BOOST_LOG(error) << "shmget failed"sv;
|
||||||
BOOST_LOG(error)
|
|
||||||
<< "shmget failed"sv;
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Commented out resetting of the sizes when intializing X in SHM mode. It might be wrong. Expect issues. This is the default mode, so poisoning those variables is not desired
|
|
||||||
*/
|
|
||||||
// width = display->width_in_pixels;
|
|
||||||
// height = display->height_in_pixels;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,79 +355,32 @@ struct shm_attr_t: public x11_attr_t {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mic_attr_t: public mic_t {
|
|
||||||
pa_sample_spec ss;
|
|
||||||
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
|
||||||
|
|
||||||
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 shm;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
|
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
|
||||||
if(hwdevice_type != platf::dev_type_e::none) {
|
if(hwdevice_type != platf::dev_type_e::none) {
|
||||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame
|
// Attempt to use shared memory X11 to avoid copying the frame
|
||||||
|
auto shm_disp = std::make_shared<shm_attr_t>();
|
||||||
|
|
||||||
if (!shm_disp) {
|
auto status = shm_disp->init();
|
||||||
return std::make_shared<x11_attr_t>(); //Fallback to standard way if else
|
if(status > 0) {
|
||||||
|
// x11_attr_t::init() failed, don't bother trying again.
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(status == 0) {
|
||||||
return shm_disp;
|
return shm_disp;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
|
// Fallback
|
||||||
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
|
auto x11_disp = std::make_shared<x11_attr_t>();
|
||||||
|
if(x11_disp->init()) {
|
||||||
int status;
|
return nullptr;
|
||||||
|
|
||||||
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() {
|
ifaddr_t get_ifaddrs() {
|
||||||
@@ -506,8 +425,7 @@ std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_
|
|||||||
port = ((sockaddr_in *)ip_addr)->sin_port;
|
port = ((sockaddr_in *)ip_addr)->sin_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return { port, std::string { data } };
|
||||||
{ port, std::string {data}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_mac_address(const std::string_view &address) {
|
std::string get_mac_address(const std::string_view &address) {
|
||||||
@@ -522,8 +440,8 @@ std::string get_mac_address(const std::string_view &address) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BOOST_LOG(warning)
|
|
||||||
<< "Unable to find MAC address for "sv << address;
|
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
|
||||||
return "00:00:00:00:00:00"s;
|
return "00:00:00:00:00:00"s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,4 +451,4 @@ void freeImage(XImage *p) {
|
|||||||
void freeX(XFixesCursorImage *p) {
|
void freeX(XFixesCursorImage *p) {
|
||||||
XFree(p);
|
XFree(p);
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,8 +30,22 @@ 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 };
|
||||||
|
|
||||||
@@ -55,9 +70,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,16 +131,31 @@ 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();
|
||||||
|
|
||||||
@@ -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() };
|
||||||
|
|
||||||
@@ -486,10 +568,11 @@ input_t input() {
|
|||||||
|
|
||||||
// Ensure starting from clean slate
|
// Ensure starting from clean slate
|
||||||
gp.clear();
|
gp.clear();
|
||||||
|
gp.touch_dev = touchscreen();
|
||||||
gp.mouse_dev = mouse();
|
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();
|
||||||
}
|
}
|
||||||
@@ -501,6 +584,4 @@ 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 std::make_unique<deinit_t>(); }
|
|
||||||
}
|
|
||||||
|
|||||||
164
sunshine/platform/windows/PolicyConfig.h
Normal file
164
sunshine/platform/windows/PolicyConfig.h
Normal 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
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
@@ -2,18 +2,31 @@
|
|||||||
// Created by loki on 1/12/20.
|
// Created by loki on 1/12/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <roapi.h>
|
|
||||||
#include <mmdeviceapi.h>
|
|
||||||
#include <audioclient.h>
|
#include <audioclient.h>
|
||||||
|
#include <mmdeviceapi.h>
|
||||||
|
#include <roapi.h>
|
||||||
|
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
|
||||||
#include <synchapi.h>
|
#include <synchapi.h>
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
#include <propkeydef.h>
|
||||||
|
#undef INITGUID
|
||||||
|
|
||||||
#include "sunshine/config.h"
|
#include "sunshine/config.h"
|
||||||
#include "sunshine/main.h"
|
#include "sunshine/main.h"
|
||||||
#include "sunshine/platform/common.h"
|
#include "sunshine/platform/common.h"
|
||||||
|
|
||||||
|
// Must be the last included file
|
||||||
|
// clang-format off
|
||||||
|
#include "PolicyConfig.h"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
|
||||||
|
|
||||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||||
@@ -21,6 +34,8 @@ const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
|||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace platf::audio {
|
namespace platf::audio {
|
||||||
|
constexpr auto SAMPLE_RATE = 48000;
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void Release(T *p) {
|
void Release(T *p) {
|
||||||
p->Release();
|
p->Release();
|
||||||
@@ -33,10 +48,14 @@ void co_task_free(T *p) {
|
|||||||
|
|
||||||
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
||||||
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
||||||
|
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
|
||||||
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
||||||
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
||||||
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
||||||
|
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
|
||||||
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
||||||
|
using policy_t = util::safe_ptr<IPolicyConfig, Release<IPolicyConfig>>;
|
||||||
|
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
|
||||||
|
|
||||||
class co_init_t : public deinit_t {
|
class co_init_t : public deinit_t {
|
||||||
public:
|
public:
|
||||||
@@ -49,10 +68,454 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class prop_var_t {
|
||||||
|
public:
|
||||||
|
prop_var_t() {
|
||||||
|
PropVariantInit(&prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
~prop_var_t() {
|
||||||
|
PropVariantClear(&prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROPVARIANT prop;
|
||||||
|
};
|
||||||
|
|
||||||
|
class audio_pipe_t {
|
||||||
|
public:
|
||||||
|
static constexpr auto stereo = 2;
|
||||||
|
static constexpr auto channels51 = 6;
|
||||||
|
static constexpr auto channels71 = 8;
|
||||||
|
|
||||||
|
using samples_t = std::vector<std::int16_t>;
|
||||||
|
using buf_t = util::buffer_t<std::int16_t>;
|
||||||
|
|
||||||
|
virtual void to_stereo(samples_t &out, const buf_t &in) = 0;
|
||||||
|
virtual void to_51(samples_t &out, const buf_t &in) = 0;
|
||||||
|
virtual void to_71(samples_t &out, const buf_t &in) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class mono_t : public audio_pipe_t {
|
||||||
|
public:
|
||||||
|
void to_stereo(samples_t &out, const buf_t &in) override {
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) {
|
||||||
|
*sample_out_p++ = *sample_in_pos * 7 / 10;
|
||||||
|
*sample_out_p++ = *sample_in_pos++ * 7 / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_51(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||||
|
int left = *sample_in_pos++;
|
||||||
|
|
||||||
|
auto fl = (left * 7 / 10);
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = fl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = fl;
|
||||||
|
sample_out_p[FRONT_CENTER] = fl * 6;
|
||||||
|
sample_out_p[LOW_FREQUENCY] = fl / 10;
|
||||||
|
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||||
|
sample_out_p[BACK_RIGHT] = left * 4 / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_71(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||||
|
int left = *sample_in_pos++;
|
||||||
|
|
||||||
|
auto fl = (left * 7 / 10);
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = fl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = fl;
|
||||||
|
sample_out_p[FRONT_CENTER] = fl * 6;
|
||||||
|
sample_out_p[LOW_FREQUENCY] = fl / 10;
|
||||||
|
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||||
|
sample_out_p[BACK_RIGHT] = left * 4 / 10;
|
||||||
|
sample_out_p[SIDE_LEFT] = left * 5 / 10;
|
||||||
|
sample_out_p[SIDE_RIGHT] = left * 5 / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class stereo_t : public audio_pipe_t {
|
||||||
|
public:
|
||||||
|
void to_stereo(samples_t &out, const buf_t &in) override {
|
||||||
|
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_51(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||||
|
int left = sample_in_pos[speaker::FRONT_LEFT];
|
||||||
|
int right = sample_in_pos[speaker::FRONT_RIGHT];
|
||||||
|
|
||||||
|
sample_in_pos += 2;
|
||||||
|
|
||||||
|
auto fl = (left * 7 / 10);
|
||||||
|
auto fr = (right * 7 / 10);
|
||||||
|
|
||||||
|
auto mix = (fl + fr) / 2;
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = fl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = fr;
|
||||||
|
sample_out_p[FRONT_CENTER] = mix;
|
||||||
|
sample_out_p[LOW_FREQUENCY] = mix / 2;
|
||||||
|
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||||
|
sample_out_p[BACK_RIGHT] = right * 4 / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_71(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||||
|
int left = sample_in_pos[speaker::FRONT_LEFT];
|
||||||
|
int right = sample_in_pos[speaker::FRONT_RIGHT];
|
||||||
|
|
||||||
|
sample_in_pos += 2;
|
||||||
|
|
||||||
|
auto fl = (left * 7 / 10);
|
||||||
|
auto fr = (right * 7 / 10);
|
||||||
|
|
||||||
|
auto mix = (fl + fr) / 2;
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = fl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = fr;
|
||||||
|
sample_out_p[FRONT_CENTER] = mix;
|
||||||
|
sample_out_p[LOW_FREQUENCY] = mix / 2;
|
||||||
|
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||||
|
sample_out_p[BACK_RIGHT] = right * 4 / 10;
|
||||||
|
sample_out_p[SIDE_LEFT] = left * 5 / 10;
|
||||||
|
sample_out_p[SIDE_RIGHT] = right * 5 / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class surr51_t : public audio_pipe_t {
|
||||||
|
public:
|
||||||
|
void to_stereo(samples_t &out, const buf_t &in) {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) {
|
||||||
|
int left {}, right {};
|
||||||
|
|
||||||
|
left += sample_in_pos[FRONT_LEFT];
|
||||||
|
left += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||||
|
left += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||||
|
left += sample_in_pos[BACK_LEFT] * 7 / 10;
|
||||||
|
left += sample_in_pos[BACK_RIGHT] * 3 / 10;
|
||||||
|
|
||||||
|
right += sample_in_pos[FRONT_RIGHT];
|
||||||
|
right += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||||
|
right += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||||
|
right += sample_in_pos[BACK_LEFT] * 3 / 10;
|
||||||
|
right += sample_in_pos[BACK_RIGHT] * 7 / 10;
|
||||||
|
|
||||||
|
sample_out_p[0] = left;
|
||||||
|
sample_out_p[1] = right;
|
||||||
|
|
||||||
|
sample_in_pos += channels51;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_51(samples_t &out, const buf_t &in) override {
|
||||||
|
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_71(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||||
|
int fl = sample_in_pos[FRONT_LEFT];
|
||||||
|
int fr = sample_in_pos[FRONT_RIGHT];
|
||||||
|
int bl = sample_in_pos[BACK_LEFT];
|
||||||
|
int br = sample_in_pos[BACK_RIGHT];
|
||||||
|
|
||||||
|
auto mix_l = (fl + bl) / 2;
|
||||||
|
auto mix_r = (bl + br) / 2;
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = fl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = fr;
|
||||||
|
sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER];
|
||||||
|
sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY];
|
||||||
|
sample_out_p[BACK_LEFT] = bl;
|
||||||
|
sample_out_p[BACK_RIGHT] = br;
|
||||||
|
sample_out_p[SIDE_LEFT] = mix_l;
|
||||||
|
sample_out_p[SIDE_RIGHT] = mix_r;
|
||||||
|
|
||||||
|
sample_in_pos += channels51;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class surr71_t : public audio_pipe_t {
|
||||||
|
public:
|
||||||
|
void to_stereo(samples_t &out, const buf_t &in) {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) {
|
||||||
|
int left {}, right {};
|
||||||
|
|
||||||
|
left += sample_in_pos[FRONT_LEFT];
|
||||||
|
left += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||||
|
left += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||||
|
left += sample_in_pos[BACK_LEFT] * 7 / 10;
|
||||||
|
left += sample_in_pos[BACK_RIGHT] * 3 / 10;
|
||||||
|
left += sample_in_pos[SIDE_LEFT];
|
||||||
|
|
||||||
|
right += sample_in_pos[FRONT_RIGHT];
|
||||||
|
right += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||||
|
right += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||||
|
right += sample_in_pos[BACK_LEFT] * 3 / 10;
|
||||||
|
right += sample_in_pos[BACK_RIGHT] * 7 / 10;
|
||||||
|
right += sample_in_pos[SIDE_RIGHT];
|
||||||
|
|
||||||
|
sample_out_p[0] = left;
|
||||||
|
sample_out_p[1] = right;
|
||||||
|
|
||||||
|
sample_in_pos += channels71;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_51(samples_t &out, const buf_t &in) override {
|
||||||
|
using namespace speaker;
|
||||||
|
|
||||||
|
auto sample_in_pos = std::begin(in);
|
||||||
|
auto sample_end = std::begin(out) + out.size();
|
||||||
|
|
||||||
|
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||||
|
auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10;
|
||||||
|
auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10;
|
||||||
|
|
||||||
|
sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl;
|
||||||
|
sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr;
|
||||||
|
sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER];
|
||||||
|
sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY];
|
||||||
|
sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl;
|
||||||
|
sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr;
|
||||||
|
|
||||||
|
sample_in_pos += channels71;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_71(samples_t &out, const buf_t &in) override {
|
||||||
|
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||||
|
struct format_t {
|
||||||
|
enum type_e : int {
|
||||||
|
none,
|
||||||
|
mono,
|
||||||
|
stereo,
|
||||||
|
surr51,
|
||||||
|
surr71,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
std::string_view name;
|
||||||
|
int channels;
|
||||||
|
int channel_mask;
|
||||||
|
} formats[] {
|
||||||
|
{
|
||||||
|
format_t::mono,
|
||||||
|
"Mono"sv,
|
||||||
|
1,
|
||||||
|
SPEAKER_FRONT_CENTER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format_t::stereo,
|
||||||
|
"Stereo"sv,
|
||||||
|
2,
|
||||||
|
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format_t::surr51,
|
||||||
|
"Surround 5.1"sv,
|
||||||
|
6,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format_t::surr71,
|
||||||
|
"Surround 7.1"sv,
|
||||||
|
8,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT |
|
||||||
|
SPEAKER_SIDE_LEFT |
|
||||||
|
SPEAKER_SIDE_RIGHT,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static format_t surround_51_side_speakers {
|
||||||
|
format_t::surr51,
|
||||||
|
"Surround 5.1"sv,
|
||||||
|
6,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_SIDE_LEFT |
|
||||||
|
SPEAKER_SIDE_RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
||||||
|
wave_format->nChannels = format.channels;
|
||||||
|
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
|
||||||
|
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
|
||||||
|
|
||||||
|
if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||||
|
((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) {
|
||||||
|
wave_format->wBitsPerSample = 16;
|
||||||
|
wave_format->nSamplesPerSec = sample_rate;
|
||||||
|
switch(wave_format->wFormatTag) {
|
||||||
|
case WAVE_FORMAT_PCM:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_IEEE_FLOAT:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_EXTENSIBLE: {
|
||||||
|
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
|
||||||
|
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
||||||
|
wave_ex->Samples.wValidBitsPerSample = 16;
|
||||||
|
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']';
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']';
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) {
|
||||||
|
audio_client_t audio_client;
|
||||||
|
auto status = device->Activate(
|
||||||
|
IID_IAudioClient,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
nullptr,
|
||||||
|
(void **)&audio_client);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_format_t wave_format;
|
||||||
|
status = audio_client->GetMixFormat(&wave_format);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(init_wave_format(wave_format, sample_rate)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
set_wave_format(wave_format, format);
|
||||||
|
|
||||||
|
status = audio_client->Initialize(
|
||||||
|
AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
0, 0,
|
||||||
|
wave_format.get(),
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if(status) {
|
||||||
|
BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return audio_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t *no_null(const wchar_t *str) {
|
||||||
|
return str ? str : L"Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
format_t::type_e validate_device(device_t &device, int sample_rate) {
|
||||||
|
for(const auto &format : formats) {
|
||||||
|
// Ensure WaveFromat is compatible
|
||||||
|
auto audio_client = make_audio_client(device, format, sample_rate);
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv);
|
||||||
|
|
||||||
|
if(audio_client) {
|
||||||
|
return format.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return format_t::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_t default_device(device_enum_t &device_enum) {
|
||||||
|
device_t device;
|
||||||
|
HRESULT status;
|
||||||
|
status = device_enum->GetDefaultAudioEndpoint(
|
||||||
|
eRender,
|
||||||
|
eConsole,
|
||||||
|
&device);
|
||||||
|
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
class mic_wasapi_t : public mic_t {
|
class mic_wasapi_t : public mic_t {
|
||||||
public:
|
public:
|
||||||
capture_e sample(std::vector<std::int16_t> &sample_in) override {
|
capture_e sample(std::vector<std::int16_t> &sample_out) override {
|
||||||
while(sample_buf_pos - std::begin(sample_buf) < sample_in.size()) {
|
auto sample_size = sample_out.size() / channels_out * channels_in;
|
||||||
|
while(sample_buf_pos - std::begin(sample_buf) < sample_size) {
|
||||||
//FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples
|
//FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples
|
||||||
auto capture_result = _fill_buffer();
|
auto capture_result = _fill_buffer();
|
||||||
|
|
||||||
@@ -61,17 +524,30 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::copy_n(std::begin(sample_buf), sample_in.size(), std::begin(sample_in));
|
switch(channels_out) {
|
||||||
|
case 2:
|
||||||
|
pipe->to_stereo(sample_out, sample_buf);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
pipe->to_51(sample_out, sample_buf);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
pipe->to_71(sample_out, sample_buf);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv;
|
||||||
|
return capture_e::error;
|
||||||
|
}
|
||||||
|
|
||||||
// The excess samples should be in front of the queue
|
// The excess samples should be in front of the queue
|
||||||
std::move(&sample_buf[sample_in.size()], sample_buf_pos, std::begin(sample_buf));
|
std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf));
|
||||||
sample_buf_pos -= sample_in.size();
|
sample_buf_pos -= sample_size;
|
||||||
|
|
||||||
return capture_e::ok;
|
return capture_e::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int init(std::uint32_t sample_rate) {
|
int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
|
||||||
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
|
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
|
||||||
if(!audio_event) {
|
if(!audio_event) {
|
||||||
BOOST_LOG(error) << "Couldn't create Event handle"sv;
|
BOOST_LOG(error) << "Couldn't create Event handle"sv;
|
||||||
@@ -94,89 +570,50 @@ public:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config::audio.sink.empty()) {
|
auto device = default_device(device_enum);
|
||||||
status = device_enum->GetDefaultAudioEndpoint(
|
if(!device) {
|
||||||
eRender,
|
|
||||||
eConsole,
|
|
||||||
&device);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
|
||||||
auto wstring_device_id = converter.from_bytes(config::audio.sink);
|
|
||||||
|
|
||||||
status = device_enum->GetDevice(wstring_device_id.c_str(), &device);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(FAILED(status)) {
|
|
||||||
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = device->Activate(
|
for(auto &format : formats) {
|
||||||
IID_IAudioClient,
|
BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']';
|
||||||
CLSCTX_ALL,
|
audio_client = make_audio_client(device, format, sample_rate);
|
||||||
nullptr,
|
|
||||||
(void **) &audio_client);
|
|
||||||
|
|
||||||
if (FAILED(status)) {
|
if(audio_client) {
|
||||||
BOOST_LOG(error) << "Couldn't activate audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']';
|
||||||
|
channels_in = format.channels;
|
||||||
|
this->channels_out = channels_out;
|
||||||
|
|
||||||
return -1;
|
switch(channels_in) {
|
||||||
}
|
case 1:
|
||||||
|
pipe = std::make_unique<mono_t>();
|
||||||
status = audio_client->GetMixFormat(&wave_format);
|
|
||||||
if(FAILED(status)) {
|
|
||||||
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
wave_format->nChannels = 2;
|
|
||||||
wave_format->wBitsPerSample = 16;
|
|
||||||
wave_format->nSamplesPerSec = sample_rate;
|
|
||||||
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
|
|
||||||
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
|
|
||||||
|
|
||||||
switch(wave_format->wFormatTag) {
|
|
||||||
case WAVE_FORMAT_PCM:
|
|
||||||
break;
|
break;
|
||||||
case WAVE_FORMAT_IEEE_FLOAT:
|
case 2:
|
||||||
wave_format->wFormatTag = WAVE_FORMAT_PCM;
|
pipe = std::make_unique<stereo_t>();
|
||||||
break;
|
break;
|
||||||
case WAVE_FORMAT_EXTENSIBLE: {
|
case 6:
|
||||||
auto wave_ex = (PWAVEFORMATEXTENSIBLE) wave_format.get();
|
pipe = std::make_unique<surr51_t>();
|
||||||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
break;
|
||||||
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
case 8:
|
||||||
wave_ex->Samples.wValidBitsPerSample = 16;
|
pipe = std::make_unique<surr71_t>();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']';
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']';
|
BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv;
|
||||||
return -1;
|
return -1;
|
||||||
};
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!audio_client) {
|
||||||
|
BOOST_LOG(error) << "Couldn't find supported format for audio"sv;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
REFERENCE_TIME default_latency;
|
REFERENCE_TIME default_latency;
|
||||||
audio_client->GetDevicePeriod(&default_latency, nullptr);
|
audio_client->GetDevicePeriod(&default_latency, nullptr);
|
||||||
default_latency_ms = default_latency / 1000;
|
default_latency_ms = default_latency / 1000;
|
||||||
|
|
||||||
status = audio_client->Initialize(
|
|
||||||
AUDCLNT_SHAREMODE_SHARED,
|
|
||||||
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
|
||||||
0, 0,
|
|
||||||
wave_format.get(),
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
if (FAILED(status)) {
|
|
||||||
BOOST_LOG(error) << "Couldn't initialize audio client [0x"sv << util::hex(status).to_string_view() << ']';
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t frames;
|
std::uint32_t frames;
|
||||||
status = audio_client->GetBufferSize(&frames);
|
status = audio_client->GetBufferSize(&frames);
|
||||||
if(FAILED(status)) {
|
if(FAILED(status)) {
|
||||||
@@ -185,7 +622,8 @@ public:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sample_buf = util::buffer_t<std::int16_t> { frames };
|
// *2 --> needs to fit double
|
||||||
|
sample_buf = util::buffer_t<std::int16_t> { std::max(frames, frame_size) * 2 * channels_in };
|
||||||
sample_buf_pos = std::begin(sample_buf);
|
sample_buf_pos = std::begin(sample_buf);
|
||||||
|
|
||||||
status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture);
|
status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture);
|
||||||
@@ -217,6 +655,7 @@ public:
|
|||||||
audio_client->Stop();
|
audio_client->Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
capture_e _fill_buffer() {
|
capture_e _fill_buffer() {
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
@@ -247,8 +686,7 @@ private:
|
|||||||
for(
|
for(
|
||||||
status = audio_capture->GetNextPacketSize(&packet_size);
|
status = audio_capture->GetNextPacketSize(&packet_size);
|
||||||
SUCCEEDED(status) && packet_size > 0;
|
SUCCEEDED(status) && packet_size > 0;
|
||||||
status = audio_capture->GetNextPacketSize(&packet_size)
|
status = audio_capture->GetNextPacketSize(&packet_size)) {
|
||||||
) {
|
|
||||||
DWORD buffer_flags;
|
DWORD buffer_flags;
|
||||||
status = audio_capture->GetBuffer(
|
status = audio_capture->GetBuffer(
|
||||||
(BYTE **)&sample_aligned.samples,
|
(BYTE **)&sample_aligned.samples,
|
||||||
@@ -267,11 +705,12 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos;
|
sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos;
|
||||||
auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * wave_format->nChannels);
|
auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in);
|
||||||
|
|
||||||
if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) {
|
if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) {
|
||||||
std::fill_n(sample_buf_pos, n, 0);
|
std::fill_n(sample_buf_pos, n, 0);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
std::copy_n(sample_aligned.samples, n, sample_buf_pos);
|
std::copy_n(sample_aligned.samples, n, sample_buf_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,22 +729,239 @@ private:
|
|||||||
|
|
||||||
return capture_e::ok;
|
return capture_e::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
handle_t audio_event;
|
handle_t audio_event;
|
||||||
|
|
||||||
device_enum_t device_enum;
|
device_enum_t device_enum;
|
||||||
device_t device;
|
device_t device;
|
||||||
audio_client_t audio_client;
|
audio_client_t audio_client;
|
||||||
wave_format_t wave_format;
|
|
||||||
audio_capture_t audio_capture;
|
audio_capture_t audio_capture;
|
||||||
|
|
||||||
REFERENCE_TIME default_latency_ms;
|
REFERENCE_TIME default_latency_ms;
|
||||||
|
|
||||||
util::buffer_t<std::int16_t> sample_buf;
|
util::buffer_t<std::int16_t> sample_buf;
|
||||||
std::int16_t *sample_buf_pos;
|
std::int16_t *sample_buf_pos;
|
||||||
|
|
||||||
|
// out --> our audio output
|
||||||
|
int channels_out;
|
||||||
|
// in --> our wasapi input
|
||||||
|
int channels_in;
|
||||||
|
|
||||||
|
std::unique_ptr<audio_pipe_t> pipe;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class audio_control_t : public ::platf::audio_control_t {
|
||||||
|
public:
|
||||||
|
std::optional<sink_t> sink_info() override {
|
||||||
|
auto virtual_adapter_name = L"Steam Streaming Speakers"sv;
|
||||||
|
|
||||||
|
sink_t sink;
|
||||||
|
|
||||||
|
audio::device_enum_t device_enum;
|
||||||
|
auto status = CoCreateInstance(
|
||||||
|
CLSID_MMDeviceEnumerator,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
IID_IMMDeviceEnumerator,
|
||||||
|
(void **)&device_enum);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto device = default_device(device_enum);
|
||||||
|
if(!device) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::wstring_t wstring;
|
||||||
|
device->GetId(&wstring);
|
||||||
|
|
||||||
|
sink.host = converter.to_bytes(wstring.get());
|
||||||
|
|
||||||
|
collection_t collection;
|
||||||
|
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT count;
|
||||||
|
collection->GetCount(&count);
|
||||||
|
|
||||||
|
std::string virtual_device_id = config::audio.virtual_sink;
|
||||||
|
for(auto x = 0; x < count; ++x) {
|
||||||
|
audio::device_t device;
|
||||||
|
collection->Item(x, &device);
|
||||||
|
|
||||||
|
auto type = validate_device(device, SAMPLE_RATE);
|
||||||
|
if(type == format_t::none) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::wstring_t wstring;
|
||||||
|
device->GetId(&wstring);
|
||||||
|
|
||||||
|
audio::prop_t prop;
|
||||||
|
device->OpenPropertyStore(STGM_READ, &prop);
|
||||||
|
|
||||||
|
prop_var_t adapter_friendly_name;
|
||||||
|
prop_var_t device_friendly_name;
|
||||||
|
prop_var_t device_desc;
|
||||||
|
|
||||||
|
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
|
||||||
|
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
|
||||||
|
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
|
||||||
|
|
||||||
|
auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal);
|
||||||
|
BOOST_LOG(verbose)
|
||||||
|
<< L"===== Device ====="sv << std::endl
|
||||||
|
<< L"Device ID : "sv << wstring.get() << std::endl
|
||||||
|
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
|
||||||
|
<< L"Adapter name : "sv << adapter_name << std::endl
|
||||||
|
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) {
|
||||||
|
virtual_device_id = converter.to_bytes(wstring.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!virtual_device_id.empty()) {
|
||||||
|
sink.null = std::make_optional(sink_t::null_t {
|
||||||
|
"virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id,
|
||||||
|
"virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id,
|
||||||
|
"virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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 {
|
||||||
|
auto mic = std::make_unique<mic_wasapi_t>();
|
||||||
|
|
||||||
|
if(mic->init(sample_rate, frame_size, channels)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the requested sink is a virtual sink, meaning no speakers attached to
|
||||||
|
* the host, then we can seamlessly set the format to stereo and surround sound.
|
||||||
|
*
|
||||||
|
* Any virtual sink detected will be prefixed by:
|
||||||
|
* virtual-(format name)
|
||||||
|
* If it doesn't contain that prefix, then the format will not be changed
|
||||||
|
*/
|
||||||
|
std::optional<std::wstring> set_format(const std::string &sink) {
|
||||||
|
std::string_view sv { sink.c_str(), sink.size() };
|
||||||
|
|
||||||
|
format_t::type_e type = format_t::none;
|
||||||
|
// sink format:
|
||||||
|
// [virtual-(format name)]device_id
|
||||||
|
auto prefix = "virtual-"sv;
|
||||||
|
if(sv.find(prefix) == 0) {
|
||||||
|
sv = sv.substr(prefix.size(), sv.size() - prefix.size());
|
||||||
|
|
||||||
|
for(auto &format : formats) {
|
||||||
|
auto &name = format.name;
|
||||||
|
if(sv.find(name) == 0) {
|
||||||
|
type = format.type;
|
||||||
|
sv = sv.substr(name.size(), sv.size() - name.size());
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto wstring_device_id = converter.from_bytes(sv.data());
|
||||||
|
|
||||||
|
if(type == format_t::none) {
|
||||||
|
// wstring_device_id does not contain virtual-(format name)
|
||||||
|
// It's a simple deviceId, just pass it back
|
||||||
|
return std::make_optional(std::move(wstring_device_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_format_t wave_format;
|
||||||
|
auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(init_wave_format(wave_format, SAMPLE_RATE)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
set_wave_format(wave_format, formats[(int)type - 1]);
|
||||||
|
|
||||||
|
WAVEFORMATEXTENSIBLE p {};
|
||||||
|
status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p);
|
||||||
|
|
||||||
|
// Surround 5.1 might contain side-{left, right} instead of speaker in the back
|
||||||
|
// Try again with different speaker mask.
|
||||||
|
if(status == 0x88890008 && type == format_t::surr51) {
|
||||||
|
set_wave_format(wave_format, surround_51_side_speakers);
|
||||||
|
status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p);
|
||||||
|
}
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_optional(std::move(wstring_device_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_sink(const std::string &sink) override {
|
||||||
|
auto wstring_device_id = set_format(sink);
|
||||||
|
if(!wstring_device_id) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int failure {};
|
||||||
|
for(int x = 0; x < (int)ERole_enum_count; ++x) {
|
||||||
|
auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x);
|
||||||
|
if(status) {
|
||||||
|
BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']';
|
||||||
|
|
||||||
|
++failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
int init() {
|
||||||
|
auto status = CoCreateInstance(
|
||||||
|
CLSID_CPolicyConfigClient,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
IID_IPolicyConfig,
|
||||||
|
(void **)&policy);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~audio_control_t() override {}
|
||||||
|
|
||||||
|
policy_t policy;
|
||||||
|
};
|
||||||
|
} // namespace platf::audio
|
||||||
|
|
||||||
namespace platf {
|
namespace platf {
|
||||||
|
|
||||||
// It's not big enough to justify it's own source file :/
|
// It's not big enough to justify it's own source file :/
|
||||||
@@ -313,14 +969,14 @@ namespace dxgi {
|
|||||||
int init();
|
int init();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
|
std::unique_ptr<audio_control_t> audio_control() {
|
||||||
auto mic = std::make_unique<audio::mic_wasapi_t>();
|
auto control = std::make_unique<audio::audio_control_t>();
|
||||||
|
|
||||||
if(mic->init(sample_rate)) {
|
if(control->init()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mic;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<deinit_t> init() {
|
std::unique_ptr<deinit_t> init() {
|
||||||
@@ -329,4 +985,4 @@ std::unique_ptr<deinit_t> init() {
|
|||||||
}
|
}
|
||||||
return std::make_unique<platf::audio::co_init_t>();
|
return std::make_unique<platf::audio::co_init_t>();
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf
|
||||||
|
|||||||
@@ -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[];
|
||||||
@@ -43,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 {
|
||||||
@@ -86,16 +86,14 @@ public:
|
|||||||
DXGI_FORMAT format;
|
DXGI_FORMAT format;
|
||||||
D3D_FEATURE_LEVEL feature_level;
|
D3D_FEATURE_LEVEL feature_level;
|
||||||
|
|
||||||
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
|
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
|
||||||
{
|
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
|
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
|
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
|
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
|
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
|
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
|
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
|
||||||
}
|
} D3DKMT_SCHEDULINGPRIORITYCLASS;
|
||||||
D3DKMT_SCHEDULINGPRIORITYCLASS;
|
|
||||||
|
|
||||||
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
||||||
};
|
};
|
||||||
@@ -125,6 +123,6 @@ public:
|
|||||||
gpu_cursor_t cursor;
|
gpu_cursor_t cursor;
|
||||||
std::vector<hwdevice_t *> hwdevices;
|
std::vector<hwdevice_t *> hwdevices;
|
||||||
};
|
};
|
||||||
}
|
} // namespace platf::dxgi
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -129,8 +129,10 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +414,7 @@ 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(dev_type_e hwdevice_type) {
|
||||||
@@ -433,4 +435,4 @@ std::shared_ptr<display_t> display(dev_type_e hwdevice_type) {
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#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;
|
||||||
@@ -294,4 +294,4 @@ int display_ram_t::init() {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf::dxgi
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
|
||||||
#include <d3dcompiler.h>
|
#include <d3dcompiler.h>
|
||||||
#include <directxmath.h>
|
#include <directxmath.h>
|
||||||
|
|
||||||
#include "sunshine/main.h"
|
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
|
#include "sunshine/main.h"
|
||||||
|
|
||||||
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders"
|
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders"
|
||||||
namespace platf {
|
namespace platf {
|
||||||
@@ -12,8 +14,6 @@ using namespace std::literals;
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace platf::dxgi {
|
namespace platf::dxgi {
|
||||||
constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f };
|
|
||||||
|
|
||||||
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
|
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
|
||||||
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
|
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
|
||||||
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
|
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
|
||||||
@@ -329,19 +329,32 @@ public:
|
|||||||
input_res_p = scene_sr.get();
|
input_res_p = scene_sr.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
_init_view_port(out_width, out_height);
|
_init_view_port(this->img.width, this->img.height);
|
||||||
device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
|
device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
|
||||||
device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0);
|
device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0);
|
||||||
device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0);
|
device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0);
|
||||||
|
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
|
||||||
|
device_ctx_p->Draw(3, 0);
|
||||||
|
|
||||||
|
device_ctx_p->RSSetViewports(1, &outY_view);
|
||||||
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
|
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
|
||||||
device_ctx_p->Draw(3, 0);
|
device_ctx_p->Draw(3, 0);
|
||||||
|
|
||||||
_init_view_port(out_width / 2, out_height / 2);
|
// Artifacts start appearing on the rendered image if Sunshine doesn't flush
|
||||||
|
// before rendering on the UV part of the image.
|
||||||
|
device_ctx_p->Flush();
|
||||||
|
|
||||||
|
_init_view_port(this->img.width / 2, this->img.height / 2);
|
||||||
device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
|
device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
|
||||||
device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0);
|
device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0);
|
||||||
device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0);
|
device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0);
|
||||||
|
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
|
||||||
|
device_ctx_p->Draw(3, 0);
|
||||||
|
|
||||||
|
device_ctx_p->RSSetViewports(1, &outUV_view);
|
||||||
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
|
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
|
||||||
device_ctx_p->Draw(3, 0);
|
device_ctx_p->Draw(3, 0);
|
||||||
|
device_ctx_p->Flush();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -377,9 +390,8 @@ public:
|
|||||||
|
|
||||||
int init(
|
int init(
|
||||||
std::shared_ptr<platf::display_t> display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p,
|
std::shared_ptr<platf::display_t> display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p,
|
||||||
int in_width, int in_height, int out_width, int out_height,
|
int out_width, int out_height,
|
||||||
pix_fmt_e pix_fmt
|
pix_fmt_e pix_fmt) {
|
||||||
) {
|
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
|
|
||||||
device_p->AddRef();
|
device_p->AddRef();
|
||||||
@@ -393,8 +405,20 @@ public:
|
|||||||
|
|
||||||
platf::hwdevice_t::img = &img;
|
platf::hwdevice_t::img = &img;
|
||||||
|
|
||||||
this->out_width = out_width;
|
float in_width = display->width;
|
||||||
this->out_height = out_height;
|
float in_height = display->height;
|
||||||
|
|
||||||
|
// // Ensure aspect ratio is maintained
|
||||||
|
auto scalar = std::fminf(out_width / in_width, out_height / in_height);
|
||||||
|
auto out_width_f = in_width * scalar;
|
||||||
|
auto out_height_f = in_height * scalar;
|
||||||
|
|
||||||
|
// result is always positive
|
||||||
|
auto offsetX = (out_width - out_width_f) / 2;
|
||||||
|
auto offsetY = (out_height - out_height_f) / 2;
|
||||||
|
|
||||||
|
outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f };
|
||||||
|
outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f };
|
||||||
|
|
||||||
status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs);
|
status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs);
|
||||||
if(status) {
|
if(status) {
|
||||||
@@ -515,6 +539,25 @@ public:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Color the background black, so that the padding for keeping the aspect ratio
|
||||||
|
// is black
|
||||||
|
if(img.display->dummy_img(&back_img)) {
|
||||||
|
BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
|
||||||
|
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
D3D11_SRV_DIMENSION_TEXTURE2D
|
||||||
|
};
|
||||||
|
desc.Texture2D.MipLevels = 1;
|
||||||
|
|
||||||
|
status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
|
device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
|
||||||
device_ctx_p->PSSetSamplers(0, 1, &sampler_linear);
|
device_ctx_p->PSSetSamplers(0, 1, &sampler_linear);
|
||||||
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
|
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
|
||||||
@@ -535,6 +578,7 @@ public:
|
|||||||
hwdevices_p->erase(it);
|
hwdevices_p->erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void _init_view_port(float x, float y, float width, float height) {
|
void _init_view_port(float x, float y, float width, float height) {
|
||||||
D3D11_VIEWPORT view {
|
D3D11_VIEWPORT view {
|
||||||
@@ -619,17 +663,21 @@ public:
|
|||||||
|
|
||||||
img_d3d_t img;
|
img_d3d_t img;
|
||||||
|
|
||||||
|
// Clear nv12 render target to black
|
||||||
|
img_d3d_t back_img;
|
||||||
|
|
||||||
vs_t convert_UV_vs;
|
vs_t convert_UV_vs;
|
||||||
ps_t convert_UV_ps;
|
ps_t convert_UV_ps;
|
||||||
ps_t convert_Y_ps;
|
ps_t convert_Y_ps;
|
||||||
ps_t scene_ps;
|
ps_t scene_ps;
|
||||||
vs_t scene_vs;
|
vs_t scene_vs;
|
||||||
|
|
||||||
|
D3D11_VIEWPORT outY_view;
|
||||||
|
D3D11_VIEWPORT outUV_view;
|
||||||
|
|
||||||
D3D11_VIEWPORT cursor_view;
|
D3D11_VIEWPORT cursor_view;
|
||||||
bool cursor_visible;
|
bool cursor_visible;
|
||||||
|
|
||||||
float out_width, out_height;
|
|
||||||
|
|
||||||
device_ctx_t::pointer device_ctx_p;
|
device_ctx_t::pointer device_ctx_p;
|
||||||
|
|
||||||
// The destructor will remove itself from the list of hardware devices, this is done synchronously
|
// The destructor will remove itself from the list of hardware devices, this is done synchronously
|
||||||
@@ -768,6 +816,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
|
|||||||
dummy_data.get(),
|
dummy_data.get(),
|
||||||
(UINT)img->row_pitch
|
(UINT)img->row_pitch
|
||||||
};
|
};
|
||||||
|
std::fill_n(dummy_data.get(), width * height, 0);
|
||||||
|
|
||||||
D3D11_TEXTURE2D_DESC t {};
|
D3D11_TEXTURE2D_DESC t {};
|
||||||
t.Width = width;
|
t.Width = width;
|
||||||
@@ -808,7 +857,6 @@ std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(int width, int
|
|||||||
shared_from_this(),
|
shared_from_this(),
|
||||||
device.get(),
|
device.get(),
|
||||||
device_ctx.get(),
|
device_ctx.get(),
|
||||||
this->width, this->height,
|
|
||||||
width, height,
|
width, height,
|
||||||
pix_fmt);
|
pix_fmt);
|
||||||
|
|
||||||
@@ -864,4 +912,4 @@ int init() {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf::dxgi
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
#include <sstream>
|
#include <cmath>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include <ws2tcpip.h>
|
// prevent clang format from "optimizing" the header include order
|
||||||
|
// clang-format off
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winuser.h>
|
#include <winuser.h>
|
||||||
#include <iphlpapi.h>
|
#include <ws2tcpip.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
#include <ViGEm/Client.h>
|
#include <ViGEm/Client.h>
|
||||||
|
|
||||||
@@ -18,6 +22,11 @@ using namespace std::literals;
|
|||||||
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||||
|
|
||||||
volatile HDESK _lastKnownInputDesktop = NULL;
|
volatile HDESK _lastKnownInputDesktop = NULL;
|
||||||
|
constexpr touch_port_t target_touch_port {
|
||||||
|
0, 0,
|
||||||
|
65535, 65535
|
||||||
|
};
|
||||||
|
|
||||||
HDESK pairInputDesktop();
|
HDESK pairInputDesktop();
|
||||||
|
|
||||||
class vigem_t {
|
class vigem_t {
|
||||||
@@ -165,6 +174,40 @@ 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 {};
|
||||||
|
|
||||||
@@ -175,16 +218,7 @@ void move_mouse(input_t &input, int deltaX, int deltaY) {
|
|||||||
mi.dx = deltaX;
|
mi.dx = deltaX;
|
||||||
mi.dy = deltaY;
|
mi.dy = deltaY;
|
||||||
|
|
||||||
retry:
|
send_input(i);
|
||||||
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 mouse movement input"sv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void button_mouse(input_t &input, int button, bool release) {
|
void button_mouse(input_t &input, int button, bool release) {
|
||||||
@@ -227,16 +261,7 @@ void button_mouse(input_t &input, int button, bool release) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry:
|
send_input(i);
|
||||||
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 mouse button input"sv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void scroll(input_t &input, int distance) {
|
void scroll(input_t &input, int distance) {
|
||||||
@@ -248,16 +273,7 @@ void scroll(input_t &input, int distance) {
|
|||||||
mi.dwFlags = MOUSEEVENTF_WHEEL;
|
mi.dwFlags = MOUSEEVENTF_WHEEL;
|
||||||
mi.mouseData = distance;
|
mi.mouseData = distance;
|
||||||
|
|
||||||
retry:
|
send_input(i);
|
||||||
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 mouse scroll input"sv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyboard(input_t &input, uint16_t modcode, bool release) {
|
void keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||||
@@ -303,16 +319,7 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
|
|||||||
ki.dwFlags |= KEYEVENTF_KEYUP;
|
ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry:
|
send_input(i);
|
||||||
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 keyboard input"sv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int alloc_gamepad(input_t &input, int nr) {
|
int alloc_gamepad(input_t &input, int nr) {
|
||||||
@@ -361,7 +368,8 @@ HDESK pairInputDesktop() {
|
|||||||
BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']';
|
BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
|
BOOST_LOG(info) << std::endl
|
||||||
|
<< "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
|
||||||
if(!SetThreadDesktop(hDesk)) {
|
if(!SetThreadDesktop(hDesk)) {
|
||||||
auto err = GetLastError();
|
auto err = GetLastError();
|
||||||
BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']';
|
BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']';
|
||||||
@@ -377,4 +385,4 @@ void freeInput(void *p) {
|
|||||||
|
|
||||||
delete vigem;
|
delete vigem;
|
||||||
}
|
}
|
||||||
}
|
} // namespace platf
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -251,12 +269,12 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
|
|||||||
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 detached_nodes_opt = app_node.get_child_optional("detached"s);
|
||||||
auto output = app_node.get_optional<std::string>("output"s);
|
auto output = app_node.get_optional<std::string>("output"s);
|
||||||
auto name = parse_env_val(this_env, app_node.get<std::string>("name"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);
|
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;
|
||||||
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -284,6 +312,7 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,4 +340,4 @@ void refresh(const std::string &file_name) {
|
|||||||
proc = std::move(*proc_opt);
|
proc = std::move(*proc_opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // namespace proc
|
||||||
|
|||||||
@@ -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,8 +61,7 @@ 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)) {}
|
||||||
|
|
||||||
@@ -92,5 +100,5 @@ 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
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -96,8 +102,8 @@ public:
|
|||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
|
private:
|
||||||
iterator &_this() { return *static_cast<iterator *>(this); }
|
iterator &_this() { return *static_cast<iterator *>(this); }
|
||||||
const iterator &_this() const { return *static_cast<const iterator *>(this); }
|
const iterator &_this() const { return *static_cast<const iterator *>(this); }
|
||||||
};
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -294,15 +294,39 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) {
|
|||||||
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) {
|
||||||
@@ -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;
|
||||||
@@ -534,6 +563,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
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ 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);
|
||||||
@@ -21,6 +23,6 @@ int session_count();
|
|||||||
|
|
||||||
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event);
|
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event);
|
||||||
|
|
||||||
}
|
} // namespace stream
|
||||||
|
|
||||||
#endif //SUNSHINE_RTSP_H
|
#endif //SUNSHINE_RTSP_H
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -255,8 +255,7 @@ 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;
|
||||||
@@ -266,14 +265,15 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) {
|
|||||||
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 {
|
else {
|
||||||
cb->second(session, payload);
|
cb->second(session, payload);
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
case ENET_EVENT_TYPE_CONNECT:
|
case ENET_EVENT_TYPE_CONNECT:
|
||||||
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
|
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
|
||||||
break;
|
break;
|
||||||
@@ -314,7 +314,7 @@ 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;
|
||||||
@@ -324,11 +324,13 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen
|
|||||||
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 };
|
||||||
@@ -342,10 +344,12 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen
|
|||||||
shards_p[x] = (uint8_t *)&shards[x * blocksize];
|
shards_p[x] = (uint8_t *)&shards[x * blocksize];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(data_shards + parity_shards <= DATA_SHARDS_MAX) {
|
||||||
// packets = parity_shards + data_shards
|
// packets = parity_shards + data_shards
|
||||||
rs_t rs { reed_solomon_new(data_shards, parity_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,7 +359,7 @@ 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) {
|
||||||
@@ -640,11 +644,9 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
|
|||||||
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;
|
||||||
@@ -670,11 +672,9 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
|
|||||||
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 |
|
||||||
fecPercentage << 4
|
shards.percentage << 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);
|
||||||
@@ -902,7 +902,7 @@ void join(session_t &session) {
|
|||||||
session.controlEnd.view();
|
session.controlEnd.view();
|
||||||
//Reset input on session stop to avoid stuck repeated keys
|
//Reset input on session stop to avoid stuck repeated keys
|
||||||
BOOST_LOG(debug) << "Resetting Input..."sv;
|
BOOST_LOG(debug) << "Resetting Input..."sv;
|
||||||
input::reset();
|
input::reset(session.input);
|
||||||
BOOST_LOG(debug) << "Session ended"sv;
|
BOOST_LOG(debug) << "Session ended"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,5 +943,5 @@ std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypt
|
|||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
} // namespace session
|
||||||
}
|
} // namespace stream
|
||||||
|
|||||||
@@ -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,9 @@ 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
|
||||||
|
|
||||||
extern safe::signal_t broadcast_shutdown_event;
|
extern safe::signal_t broadcast_shutdown_event;
|
||||||
}
|
} // namespace stream
|
||||||
|
|
||||||
#endif //SUNSHINE_STREAM_H
|
#endif //SUNSHINE_STREAM_H
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,7 +29,6 @@ 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 {
|
||||||
@@ -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;
|
||||||
@@ -166,7 +166,8 @@ public:
|
|||||||
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
|
||||||
|
|||||||
@@ -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,6 +20,7 @@ private:
|
|||||||
std::mutex _lock;
|
std::mutex _lock;
|
||||||
|
|
||||||
bool _continue;
|
bool _continue;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThreadPool() : _continue { false } {}
|
ThreadPool() : _continue { false } {}
|
||||||
|
|
||||||
@@ -85,7 +86,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void _main() {
|
void _main() {
|
||||||
while(_continue) {
|
while(_continue) {
|
||||||
if(auto task = this->pop()) {
|
if(auto task = this->pop()) {
|
||||||
@@ -117,5 +117,5 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace util
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,20 +5,20 @@
|
|||||||
#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 <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 };
|
||||||
@@ -121,8 +121,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,10 +131,97 @@ 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>
|
||||||
@@ -202,7 +289,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<T> &unsafe() {
|
std::vector<T> &unsafe() {
|
||||||
std::lock_guard { _lock };
|
|
||||||
return _queue;
|
return _queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +305,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool _continue { true };
|
bool _continue { true };
|
||||||
std::uint32_t _max_elements;
|
std::uint32_t _max_elements;
|
||||||
|
|
||||||
@@ -322,6 +407,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 +426,6 @@ auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
using signal_t = event_t<bool>;
|
using signal_t = event_t<bool>;
|
||||||
}
|
} // namespace safe
|
||||||
|
|
||||||
#endif //SUNSHINE_THREAD_SAFE_H
|
#endif //SUNSHINE_THREAD_SAFE_H
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
#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) \
|
||||||
|
{ \
|
||||||
|
x; \
|
||||||
|
while(y) z \
|
||||||
|
}
|
||||||
#define KITTY_DECL_CONSTR(x) \
|
#define KITTY_DECL_CONSTR(x) \
|
||||||
x(x &&) noexcept = default; \
|
x(x &&) noexcept = default; \
|
||||||
x &operator=(x &&) noexcept = default; \
|
x &operator=(x &&) noexcept = default; \
|
||||||
@@ -49,6 +53,10 @@
|
|||||||
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##_)
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
template<template<typename...> class X, class... Y>
|
template<template<typename...> class X, class... Y>
|
||||||
@@ -76,8 +84,10 @@ 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... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
template<class... Ts>
|
||||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
|
template<class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class FailGuard {
|
class FailGuard {
|
||||||
@@ -103,6 +113,7 @@ public:
|
|||||||
|
|
||||||
void disable() { failure = false; }
|
void disable() { failure = false; }
|
||||||
bool failure { true };
|
bool failure { true };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T _func;
|
T _func;
|
||||||
};
|
};
|
||||||
@@ -129,12 +140,14 @@ 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) {
|
||||||
@@ -479,6 +492,7 @@ public:
|
|||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return _p != nullptr;
|
return _p != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
pointer _p;
|
pointer _p;
|
||||||
deleter_type _deleter;
|
deleter_type _deleter;
|
||||||
@@ -621,9 +635,7 @@ struct __false_v<T, std::enable_if_t<
|
|||||||
std::is_pointer_v<T> ||
|
std::is_pointer_v<T> ||
|
||||||
instantiation_of_v<std::unique_ptr, T> ||
|
instantiation_of_v<std::unique_ptr, T> ||
|
||||||
instantiation_of_v<std::shared_ptr, T> ||
|
instantiation_of_v<std::shared_ptr, T> ||
|
||||||
instantiation_of_v<uniq_ptr, T>
|
instantiation_of_v<uniq_ptr, T>)>> {
|
||||||
)
|
|
||||||
>> {
|
|
||||||
static constexpr std::nullptr_t value = nullptr;
|
static constexpr std::nullptr_t value = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -765,8 +777,7 @@ 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);
|
||||||
@@ -790,8 +801,7 @@ 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;
|
||||||
|
|
||||||
@@ -823,7 +833,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libswscale/swscale.h>
|
#include <libswscale/swscale.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "main.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "round_robin.h"
|
#include "round_robin.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
#include "config.h"
|
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
#include "main.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -38,10 +38,6 @@ void free_buffer(AVBufferRef *ref) {
|
|||||||
av_buffer_unref(&ref);
|
av_buffer_unref(&ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_packet(AVPacket *packet) {
|
|
||||||
av_packet_free(&packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace nv {
|
namespace nv {
|
||||||
|
|
||||||
enum class profile_h264_e : int {
|
enum class profile_h264_e : int {
|
||||||
@@ -56,7 +52,7 @@ enum class profile_hevc_e : int {
|
|||||||
main_10,
|
main_10,
|
||||||
rext,
|
rext,
|
||||||
};
|
};
|
||||||
}
|
} // namespace nv
|
||||||
|
|
||||||
using ctx_t = util::safe_ptr<AVCodecContext, free_ctx>;
|
using ctx_t = util::safe_ptr<AVCodecContext, free_ctx>;
|
||||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||||
@@ -70,8 +66,6 @@ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt);
|
|||||||
void sw_img_to_frame(const platf::img_t &img, frame_t &frame);
|
void sw_img_to_frame(const platf::img_t &img, frame_t &frame);
|
||||||
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame);
|
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame);
|
||||||
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||||
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame);
|
|
||||||
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
|
||||||
|
|
||||||
util::Either<buffer_t, int> make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx);
|
util::Either<buffer_t, int> make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx);
|
||||||
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format);
|
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format);
|
||||||
@@ -87,9 +81,16 @@ public:
|
|||||||
img.row_pitch, 0
|
img.row_pitch, 0
|
||||||
};
|
};
|
||||||
|
|
||||||
int ret = sws_scale(sws.get(), (std::uint8_t*const*)&img.data, linesizes, 0, img.height, frame->data, frame->linesize);
|
std::uint8_t *const data[] {
|
||||||
|
frame->data[0] + offset,
|
||||||
|
frame->data[1] + offset / 2,
|
||||||
|
frame->data[2] + offset / 2,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
int ret = sws_scale(sws.get(), (std::uint8_t *const *)&img.data, linesizes, 0, img.height, data, frame->linesize);
|
||||||
if(ret <= 0) {
|
if(ret <= 0) {
|
||||||
BOOST_LOG(fatal) << "Couldn't convert image to required format and/or size"sv;
|
BOOST_LOG(error) << "Couldn't convert image to required format and/or size"sv;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -101,17 +102,71 @@ public:
|
|||||||
sws_setColorspaceDetails(sws.get(),
|
sws_setColorspaceDetails(sws.get(),
|
||||||
sws_getCoefficients(SWS_CS_DEFAULT), 0,
|
sws_getCoefficients(SWS_CS_DEFAULT), 0,
|
||||||
sws_getCoefficients(colorspace), color_range - 1,
|
sws_getCoefficients(colorspace), color_range - 1,
|
||||||
0, 1 << 16, 1 << 16
|
0, 1 << 16, 1 << 16);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int init(int in_width, int in_height, int out_width, int out_height, AVFrame *frame, AVPixelFormat format) {
|
/**
|
||||||
|
* When preserving aspect ratio, ensure that padding is black
|
||||||
|
*/
|
||||||
|
int prefill(AVFrame *frame, AVPixelFormat format) {
|
||||||
|
auto width = frame->width;
|
||||||
|
auto height = frame->height;
|
||||||
|
|
||||||
|
sws_t sws {
|
||||||
|
sws_getContext(
|
||||||
|
width, height, AV_PIX_FMT_BGR0,
|
||||||
|
width, height, format,
|
||||||
|
SWS_LANCZOS | SWS_ACCURATE_RND,
|
||||||
|
nullptr, nullptr, nullptr)
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!sws) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
util::buffer_t<std::uint32_t> img { (std::size_t)(width * height) };
|
||||||
|
std::fill(std::begin(img), std::end(img), 0);
|
||||||
|
|
||||||
|
const int linesizes[2] {
|
||||||
|
width, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
av_frame_make_writable(frame);
|
||||||
|
|
||||||
|
auto data = img.begin();
|
||||||
|
int ret = sws_scale(sws.get(), (std::uint8_t *const *)&data, linesizes, 0, height, frame->data, frame->linesize);
|
||||||
|
if(ret <= 0) {
|
||||||
|
BOOST_LOG(error) << "Couldn't convert image to required format and/or size"sv;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format) {
|
||||||
|
if(prefill(frame, format)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto out_width = frame->width;
|
||||||
|
auto out_height = frame->height;
|
||||||
|
|
||||||
|
// Ensure aspect ratio is maintained
|
||||||
|
auto scalar = std::fminf((float)out_width / in_width, (float)out_height / in_height);
|
||||||
|
out_width = in_width * scalar;
|
||||||
|
out_height = in_height * scalar;
|
||||||
|
|
||||||
|
// result is always positive
|
||||||
|
auto offsetX = (frame->width - out_width) / 2;
|
||||||
|
auto offsetY = (frame->height - out_height) / 2;
|
||||||
|
offset = offsetX + offsetY * frame->width;
|
||||||
|
|
||||||
sws.reset(sws_getContext(
|
sws.reset(sws_getContext(
|
||||||
in_width, in_height, AV_PIX_FMT_BGR0,
|
in_width, in_height, AV_PIX_FMT_BGR0,
|
||||||
out_width, out_height, format,
|
out_width, out_height, format,
|
||||||
SWS_LANCZOS | SWS_ACCURATE_RND,
|
SWS_LANCZOS | SWS_ACCURATE_RND,
|
||||||
nullptr, nullptr, nullptr
|
nullptr, nullptr, nullptr));
|
||||||
));
|
|
||||||
data = frame;
|
data = frame;
|
||||||
|
|
||||||
return sws ? 0 : -1;
|
return sws ? 0 : -1;
|
||||||
@@ -120,6 +175,9 @@ public:
|
|||||||
~swdevice_t() override {}
|
~swdevice_t() override {}
|
||||||
|
|
||||||
sws_t sws;
|
sws_t sws;
|
||||||
|
|
||||||
|
// offset of input image to output frame in pixels
|
||||||
|
int offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct encoder_t {
|
struct encoder_t {
|
||||||
@@ -180,11 +238,9 @@ struct encoder_t {
|
|||||||
class session_t {
|
class session_t {
|
||||||
public:
|
public:
|
||||||
session_t() = default;
|
session_t() = default;
|
||||||
session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr<platf::hwdevice_t> &&device) :
|
session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr<platf::hwdevice_t> &&device) : ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {}
|
||||||
ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {}
|
|
||||||
|
|
||||||
session_t(session_t &&other) :
|
session_t(session_t &&other) noexcept : ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {}
|
||||||
ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {}
|
|
||||||
|
|
||||||
// Ensure objects are destroyed in the correct order
|
// Ensure objects are destroyed in the correct order
|
||||||
session_t &operator=(session_t &&other) {
|
session_t &operator=(session_t &&other) {
|
||||||
@@ -260,26 +316,21 @@ static encoder_t nvenc {
|
|||||||
AV_PIX_FMT_D3D11,
|
AV_PIX_FMT_D3D11,
|
||||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||||
{
|
{
|
||||||
{
|
{ { "forced-idr"s, 1 },
|
||||||
{ "forced-idr"s, 1 },
|
|
||||||
{ "zerolatency"s, 1 },
|
{ "zerolatency"s, 1 },
|
||||||
{ "preset"s, &config::video.nv.preset },
|
{ "preset"s, &config::video.nv.preset },
|
||||||
{ "rc"s, &config::video.nv.rc }
|
{ "rc"s, &config::video.nv.rc } },
|
||||||
},
|
std::nullopt,
|
||||||
std::nullopt, std::nullopt,
|
std::nullopt,
|
||||||
"hevc_nvenc"s,
|
"hevc_nvenc"s,
|
||||||
},
|
},
|
||||||
{
|
{ { { "forced-idr"s, 1 },
|
||||||
{
|
|
||||||
{ "forced-idr"s, 1 },
|
|
||||||
{ "zerolatency"s, 1 },
|
{ "zerolatency"s, 1 },
|
||||||
{ "preset"s, &config::video.nv.preset },
|
{ "preset"s, &config::video.nv.preset },
|
||||||
{ "rc"s, &config::video.nv.rc },
|
{ "rc"s, &config::video.nv.rc },
|
||||||
{ "coder"s, &config::video.nv.coder }
|
{ "coder"s, &config::video.nv.coder } },
|
||||||
},
|
|
||||||
std::nullopt, std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
std::nullopt, std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||||
"h264_nvenc"s
|
"h264_nvenc"s },
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
|
||||||
@@ -294,26 +345,23 @@ static encoder_t amdvce {
|
|||||||
AV_PIX_FMT_D3D11,
|
AV_PIX_FMT_D3D11,
|
||||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||||
{
|
{
|
||||||
{
|
{ { "header_insertion_mode"s, "idr"s },
|
||||||
{ "header_insertion_mode"s, "idr"s },
|
|
||||||
{ "gops_per_idr"s, 30 },
|
{ "gops_per_idr"s, 30 },
|
||||||
{ "usage"s, "ultralowlatency"s },
|
{ "usage"s, "ultralowlatency"s },
|
||||||
{ "quality"s, &config::video.amd.quality },
|
{ "quality"s, &config::video.amd.quality },
|
||||||
{ "rc"s, &config::video.amd.rc }
|
{ "rc"s, &config::video.amd.rc } },
|
||||||
},
|
std::nullopt,
|
||||||
std::nullopt, std::make_optional<encoder_t::option_t>({"qp"s, &config::video.qp}),
|
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||||
"hevc_amf"s,
|
"hevc_amf"s,
|
||||||
},
|
},
|
||||||
{
|
{ {
|
||||||
{
|
|
||||||
{ "usage"s, "ultralowlatency"s },
|
{ "usage"s, "ultralowlatency"s },
|
||||||
{ "quality"s, &config::video.amd.quality },
|
{ "quality"s, &config::video.amd.quality },
|
||||||
{ "rc"s, &config::video.amd.rc },
|
{ "rc"s, &config::video.amd.rc },
|
||||||
{ "log_to_dbg"s, "1"s },
|
{ "log_to_dbg"s, "1"s },
|
||||||
},
|
},
|
||||||
std::nullopt, std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
std::nullopt, std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||||
"h264_amf"s
|
"h264_amf"s },
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
|
||||||
@@ -328,27 +376,21 @@ static encoder_t software {
|
|||||||
AV_HWDEVICE_TYPE_NONE,
|
AV_HWDEVICE_TYPE_NONE,
|
||||||
AV_PIX_FMT_NONE,
|
AV_PIX_FMT_NONE,
|
||||||
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
|
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
|
||||||
{
|
{ // x265's Info SEI is so long that it causes the IDR picture data to be
|
||||||
// x265's Info SEI is so long that it causes the IDR picture data to be
|
|
||||||
// kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic.
|
// kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic.
|
||||||
// It also looks like gop_size isn't passed on to x265, so we have to set
|
// It also looks like gop_size isn't passed on to x265, so we have to set
|
||||||
// 'keyint=-1' in the parameters ourselves.
|
// 'keyint=-1' in the parameters ourselves.
|
||||||
{
|
{
|
||||||
|
{ "forced-idr"s, 1 },
|
||||||
{ "x265-params"s, "info=0:keyint=-1"s },
|
{ "x265-params"s, "info=0:keyint=-1"s },
|
||||||
{ "preset"s, &config::video.sw.preset },
|
{ "preset"s, &config::video.sw.preset },
|
||||||
{ "tune"s, &config::video.sw.tune }
|
{ "tune"s, &config::video.sw.tune } },
|
||||||
},
|
|
||||||
std::make_optional<encoder_t::option_t>("crf"s, &config::video.crf), std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
|
std::make_optional<encoder_t::option_t>("crf"s, &config::video.crf), std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
|
||||||
"libx265"s
|
"libx265"s },
|
||||||
},
|
{ { { "preset"s, &config::video.sw.preset },
|
||||||
{
|
{ "tune"s, &config::video.sw.tune } },
|
||||||
{
|
|
||||||
{ "preset"s, &config::video.sw.preset },
|
|
||||||
{ "tune"s, &config::video.sw.tune }
|
|
||||||
},
|
|
||||||
std::make_optional<encoder_t::option_t>("crf"s, &config::video.crf), std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
|
std::make_optional<encoder_t::option_t>("crf"s, &config::video.crf), std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
|
||||||
"libx264"s
|
"libx264"s },
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
|
||||||
@@ -381,8 +423,7 @@ void captureThread(
|
|||||||
std::shared_ptr<safe::queue_t<capture_ctx_t>> capture_ctx_queue,
|
std::shared_ptr<safe::queue_t<capture_ctx_t>> capture_ctx_queue,
|
||||||
util::sync_t<std::weak_ptr<platf::display_t>> &display_wp,
|
util::sync_t<std::weak_ptr<platf::display_t>> &display_wp,
|
||||||
safe::signal_t &reinit_event,
|
safe::signal_t &reinit_event,
|
||||||
const encoder_t &encoder
|
const encoder_t &encoder) {
|
||||||
) {
|
|
||||||
std::vector<capture_ctx_t> capture_ctxs;
|
std::vector<capture_ctx_t> capture_ctxs;
|
||||||
|
|
||||||
auto fg = util::fail_guard([&]() {
|
auto fg = util::fail_guard([&]() {
|
||||||
@@ -671,13 +712,14 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
|||||||
|
|
||||||
AVDictionary *options { nullptr };
|
AVDictionary *options { nullptr };
|
||||||
auto handle_option = [&options](const encoder_t::option_t &option) {
|
auto handle_option = [&options](const encoder_t::option_t &option) {
|
||||||
std::visit(util::overloaded {
|
std::visit(
|
||||||
|
util::overloaded {
|
||||||
[&](int v) { av_dict_set_int(&options, option.name.c_str(), v, 0); },
|
[&](int v) { av_dict_set_int(&options, option.name.c_str(), v, 0); },
|
||||||
[&](int *v) { av_dict_set_int(&options, option.name.c_str(), *v, 0); },
|
[&](int *v) { av_dict_set_int(&options, option.name.c_str(), *v, 0); },
|
||||||
[&](std::optional<int> *v) { if(*v) av_dict_set_int(&options, option.name.c_str(), **v, 0); },
|
[&](std::optional<int> *v) { if(*v) av_dict_set_int(&options, option.name.c_str(), **v, 0); },
|
||||||
[&](const std::string &v) { av_dict_set(&options, option.name.c_str(), v.c_str(), 0); },
|
[&](const std::string &v) { av_dict_set(&options, option.name.c_str(), v.c_str(), 0); },
|
||||||
[&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); }
|
[&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } },
|
||||||
}, option.value);
|
option.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
for(auto &option : video_format.options) {
|
for(auto &option : video_format.options) {
|
||||||
@@ -722,7 +764,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
|||||||
if(!hwdevice->data) {
|
if(!hwdevice->data) {
|
||||||
auto device_tmp = std::make_unique<swdevice_t>();
|
auto device_tmp = std::make_unique<swdevice_t>();
|
||||||
|
|
||||||
if(device_tmp->init(width, height, config.width, config.height, frame.get(), sw_fmt)) {
|
if(device_tmp->init(width, height, frame.get(), sw_fmt)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,8 +778,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
|||||||
return std::make_optional(session_t {
|
return std::make_optional(session_t {
|
||||||
std::move(ctx),
|
std::move(ctx),
|
||||||
std::move(frame),
|
std::move(frame),
|
||||||
std::move(device)
|
std::move(device) });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode_run(
|
void encode_run(
|
||||||
@@ -862,6 +903,9 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
|||||||
return encode_e::error;
|
return encode_e::error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||||
|
input::touch_port_event->raise(disp->offset_x, disp->offset_y, disp->width, disp->height);
|
||||||
|
|
||||||
std::vector<sync_session_t> synced_sessions;
|
std::vector<sync_session_t> synced_sessions;
|
||||||
for(auto &ctx : synced_session_ctxs) {
|
for(auto &ctx : synced_session_ctxs) {
|
||||||
auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx);
|
auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx);
|
||||||
@@ -1009,7 +1053,8 @@ void captureThreadSync() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit);
|
while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void capture_async(
|
void capture_async(
|
||||||
@@ -1032,8 +1077,7 @@ void capture_async(
|
|||||||
|
|
||||||
auto delay = std::chrono::floor<std::chrono::nanoseconds>(1s) / config.framerate;
|
auto delay = std::chrono::floor<std::chrono::nanoseconds>(1s) / config.framerate;
|
||||||
ref->capture_ctx_queue->raise(capture_ctx_t {
|
ref->capture_ctx_queue->raise(capture_ctx_t {
|
||||||
images, delay
|
images, delay });
|
||||||
});
|
|
||||||
|
|
||||||
if(!ref->capture_ctx_queue->running()) {
|
if(!ref->capture_ctx_queue->running()) {
|
||||||
return;
|
return;
|
||||||
@@ -1069,8 +1113,12 @@ void capture_async(
|
|||||||
if(display->dummy_img(dummy_img.get())) {
|
if(display->dummy_img(dummy_img.get())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
images->raise(std::move(dummy_img));
|
images->raise(std::move(dummy_img));
|
||||||
|
|
||||||
|
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||||
|
input::touch_port_event->raise(display->offset_x, display->offset_y, display->width, display->height);
|
||||||
|
|
||||||
encode_run(
|
encode_run(
|
||||||
frame_nr, key_frame_nr,
|
frame_nr, key_frame_nr,
|
||||||
shutdown_event,
|
shutdown_event,
|
||||||
@@ -1097,8 +1145,7 @@ void capture(
|
|||||||
safe::signal_t join_event;
|
safe::signal_t join_event;
|
||||||
auto ref = capture_thread_sync.ref();
|
auto ref = capture_thread_sync.ref();
|
||||||
ref->encode_session_ctx_queue.raise(sync_session_ctx_t {
|
ref->encode_session_ctx_queue.raise(sync_session_ctx_t {
|
||||||
shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data
|
shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data });
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for join signal
|
// Wait for join signal
|
||||||
join_event.view();
|
join_event.view();
|
||||||
@@ -1145,6 +1192,11 @@ bool validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &e
|
|||||||
bool validate_encoder(encoder_t &encoder) {
|
bool validate_encoder(encoder_t &encoder) {
|
||||||
std::shared_ptr<platf::display_t> disp;
|
std::shared_ptr<platf::display_t> disp;
|
||||||
|
|
||||||
|
BOOST_LOG(info) << "Trying encoder ["sv << encoder.name << ']';
|
||||||
|
auto fg = util::fail_guard([&]() {
|
||||||
|
BOOST_LOG(info) << "Encoder ["sv << encoder.name << "] failed"sv;
|
||||||
|
});
|
||||||
|
|
||||||
auto force_hevc = config::video.hevc_mode >= 2;
|
auto force_hevc = config::video.hevc_mode >= 2;
|
||||||
auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode);
|
auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode);
|
||||||
|
|
||||||
@@ -1199,16 +1251,26 @@ bool validate_encoder(encoder_t &encoder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fg.disable();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int init() {
|
int init() {
|
||||||
|
// video depends on input for input::touch_port_event
|
||||||
|
input::init();
|
||||||
|
|
||||||
|
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv;
|
||||||
|
BOOST_LOG(info) << "// //"sv;
|
||||||
|
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. //"sv;
|
||||||
|
BOOST_LOG(info) << "// You can safely ignore those errors. //"sv;
|
||||||
|
BOOST_LOG(info) << "// //"sv;
|
||||||
|
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv;
|
||||||
|
|
||||||
KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
|
KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
|
||||||
if(
|
if(
|
||||||
(!config::video.encoder.empty() && pos->name != config::video.encoder) ||
|
(!config::video.encoder.empty() && pos->name != config::video.encoder) ||
|
||||||
!validate_encoder(*pos) ||
|
!validate_encoder(*pos) ||
|
||||||
(config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE])
|
(config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE])) {
|
||||||
) {
|
|
||||||
pos = encoders.erase(pos);
|
pos = encoders.erase(pos);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -1228,6 +1290,14 @@ int init() {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(info);
|
||||||
|
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv;
|
||||||
|
BOOST_LOG(info) << "// //"sv;
|
||||||
|
BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant //"sv;
|
||||||
|
BOOST_LOG(info) << "// //"sv;
|
||||||
|
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv;
|
||||||
|
BOOST_LOG(info);
|
||||||
|
|
||||||
auto &encoder = encoders.front();
|
auto &encoder = encoders.front();
|
||||||
if(encoder.hevc[encoder_t::PASSED]) {
|
if(encoder.hevc[encoder_t::PASSED]) {
|
||||||
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']';
|
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']';
|
||||||
@@ -1294,7 +1364,7 @@ void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {}
|
|||||||
namespace platf::dxgi {
|
namespace platf::dxgi {
|
||||||
void lock(void *hwdevice);
|
void lock(void *hwdevice);
|
||||||
void unlock(void *hwdevice);
|
void unlock(void *hwdevice);
|
||||||
}
|
} // namespace platf::dxgi
|
||||||
void do_nothing(void *) {}
|
void do_nothing(void *) {}
|
||||||
namespace video {
|
namespace video {
|
||||||
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) {
|
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) {
|
||||||
|
|||||||
@@ -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,16 +15,27 @@ 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() {
|
||||||
@@ -58,6 +70,6 @@ void capture(
|
|||||||
void *channel_data);
|
void *channel_data);
|
||||||
|
|
||||||
int init();
|
int init();
|
||||||
}
|
} // namespace video
|
||||||
|
|
||||||
#endif //SUNSHINE_VIDEO_H
|
#endif //SUNSHINE_VIDEO_H
|
||||||
|
|||||||
184
tools/audio.cpp
184
tools/audio.cpp
@@ -2,9 +2,9 @@
|
|||||||
// Created by loki on 1/24/20.
|
// Created by loki on 1/24/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <roapi.h>
|
|
||||||
#include <mmdeviceapi.h>
|
|
||||||
#include <audioclient.h>
|
#include <audioclient.h>
|
||||||
|
#include <mmdeviceapi.h>
|
||||||
|
#include <roapi.h>
|
||||||
|
|
||||||
#include <synchapi.h>
|
#include <synchapi.h>
|
||||||
|
|
||||||
@@ -26,7 +26,9 @@ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
|||||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||||
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
||||||
|
|
||||||
|
constexpr auto SAMPLE_RATE = 48000;
|
||||||
int device_state_filter = DEVICE_STATE_ACTIVE;
|
int device_state_filter = DEVICE_STATE_ACTIVE;
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
template<class T>
|
template<class T>
|
||||||
void Release(T *p) {
|
void Release(T *p) {
|
||||||
@@ -66,19 +68,118 @@ public:
|
|||||||
const wchar_t *no_null(const wchar_t *str) {
|
const wchar_t *no_null(const wchar_t *str) {
|
||||||
return str ? str : L"Unknown";
|
return str ? str : L"Unknown";
|
||||||
}
|
}
|
||||||
void print_device(device_t &device) {
|
|
||||||
HRESULT status;
|
|
||||||
|
|
||||||
audio::wstring_t::pointer wstring_p {};
|
struct format_t {
|
||||||
|
std::string_view name;
|
||||||
|
int channels;
|
||||||
|
int channel_mask;
|
||||||
|
} formats[] {
|
||||||
|
{ "Mono"sv,
|
||||||
|
1,
|
||||||
|
SPEAKER_FRONT_CENTER },
|
||||||
|
{ "Stereo"sv,
|
||||||
|
2,
|
||||||
|
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT },
|
||||||
|
{ "Surround 5.1"sv,
|
||||||
|
6,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT },
|
||||||
|
{ "Surround 7.1"sv,
|
||||||
|
8,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT |
|
||||||
|
SPEAKER_SIDE_LEFT |
|
||||||
|
SPEAKER_SIDE_RIGHT }
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
||||||
|
wave_format->nChannels = format.channels;
|
||||||
|
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
|
||||||
|
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
|
||||||
|
|
||||||
|
if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||||
|
((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_client_t make_audio_client(device_t &device, const format_t &format) {
|
||||||
|
audio_client_t audio_client;
|
||||||
|
auto status = device->Activate(
|
||||||
|
IID_IAudioClient,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
nullptr,
|
||||||
|
(void **)&audio_client);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_format_t wave_format;
|
||||||
|
status = audio_client->GetMixFormat(&wave_format);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_format->wBitsPerSample = 16;
|
||||||
|
wave_format->nSamplesPerSec = SAMPLE_RATE;
|
||||||
|
switch(wave_format->wFormatTag) {
|
||||||
|
case WAVE_FORMAT_PCM:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_IEEE_FLOAT:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_EXTENSIBLE: {
|
||||||
|
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
|
||||||
|
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
||||||
|
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||||
|
wave_ex->Samples.wValidBitsPerSample = 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
set_wave_format(wave_format, format);
|
||||||
|
|
||||||
|
status = audio_client->Initialize(
|
||||||
|
AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
0, 0,
|
||||||
|
wave_format.get(),
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if(status) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return audio_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_device(device_t &device) {
|
||||||
|
audio::wstring_t wstring;
|
||||||
DWORD device_state;
|
DWORD device_state;
|
||||||
|
|
||||||
device->GetState(&device_state);
|
device->GetState(&device_state);
|
||||||
device->GetId(&wstring_p);
|
device->GetId(&wstring);
|
||||||
audio::wstring_t wstring { wstring_p };
|
|
||||||
|
|
||||||
audio::prop_t::pointer prop_p {};
|
audio::prop_t prop;
|
||||||
device->OpenPropertyStore(STGM_READ, &prop_p);
|
device->OpenPropertyStore(STGM_READ, &prop);
|
||||||
audio::prop_t prop { prop_p };
|
|
||||||
|
|
||||||
prop_var_t adapter_friendly_name;
|
prop_var_t adapter_friendly_name;
|
||||||
prop_var_t device_friendly_name;
|
prop_var_t device_friendly_name;
|
||||||
@@ -114,55 +215,21 @@ void print_device(device_t &device) {
|
|||||||
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
|
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
|
||||||
<< L"Adapter name : "sv << no_null((LPWSTR)adapter_friendly_name.prop.pszVal) << std::endl
|
<< L"Adapter name : "sv << no_null((LPWSTR)adapter_friendly_name.prop.pszVal) << std::endl
|
||||||
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
|
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
|
||||||
<< L"Device state : "sv << device_state_string << std::endl << std::endl;
|
<< L"Device state : "sv << device_state_string << std::endl
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
if(device_state != DEVICE_STATE_ACTIVE) {
|
if(device_state != DEVICE_STATE_ACTIVE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(const auto &format : formats) {
|
||||||
// Ensure WaveFromat is compatible
|
// Ensure WaveFromat is compatible
|
||||||
audio_client_t::pointer audio_client_p{};
|
auto audio_client = make_audio_client(device, format);
|
||||||
status = device->Activate(
|
|
||||||
IID_IAudioClient,
|
|
||||||
CLSCTX_ALL,
|
|
||||||
nullptr,
|
|
||||||
(void **) &audio_client_p);
|
|
||||||
audio_client_t audio_client { audio_client_p };
|
|
||||||
|
|
||||||
if (FAILED(status)) {
|
std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl;
|
||||||
std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wave_format_t::pointer wave_format_p{};
|
|
||||||
status = audio_client->GetMixFormat(&wave_format_p);
|
|
||||||
wave_format_t wave_format { wave_format_p };
|
|
||||||
|
|
||||||
if (FAILED(status)) {
|
|
||||||
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(wave_format->wFormatTag) {
|
|
||||||
case WAVE_FORMAT_PCM:
|
|
||||||
break;
|
|
||||||
case WAVE_FORMAT_IEEE_FLOAT:
|
|
||||||
break;
|
|
||||||
case WAVE_FORMAT_EXTENSIBLE: {
|
|
||||||
auto wave_ex = (PWAVEFORMATEXTENSIBLE) wave_format.get();
|
|
||||||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // namespace audio
|
||||||
|
|
||||||
void print_help() {
|
void print_help() {
|
||||||
std::cout
|
std::cout
|
||||||
@@ -213,14 +280,13 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
|
|
||||||
audio::device_enum_t::pointer device_enum_p{};
|
audio::device_enum_t device_enum;
|
||||||
status = CoCreateInstance(
|
status = CoCreateInstance(
|
||||||
CLSID_MMDeviceEnumerator,
|
CLSID_MMDeviceEnumerator,
|
||||||
nullptr,
|
nullptr,
|
||||||
CLSCTX_ALL,
|
CLSCTX_ALL,
|
||||||
IID_IMMDeviceEnumerator,
|
IID_IMMDeviceEnumerator,
|
||||||
(void **) &device_enum_p);
|
(void **)&device_enum);
|
||||||
audio::device_enum_t device_enum { device_enum_p };
|
|
||||||
|
|
||||||
if(FAILED(status)) {
|
if(FAILED(status)) {
|
||||||
std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||||
@@ -228,9 +294,8 @@ int main(int argc, char *argv[]) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio::collection_t::pointer collection_p {};
|
audio::collection_t collection;
|
||||||
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection_p);
|
status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection);
|
||||||
audio::collection_t collection { collection_p };
|
|
||||||
|
|
||||||
if(FAILED(status)) {
|
if(FAILED(status)) {
|
||||||
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||||
@@ -241,11 +306,10 @@ int main(int argc, char *argv[]) {
|
|||||||
UINT count;
|
UINT count;
|
||||||
collection->GetCount(&count);
|
collection->GetCount(&count);
|
||||||
|
|
||||||
std::cout << "====== Found "sv << count << " potential audio devices ======"sv << std::endl;
|
std::cout << "====== Found "sv << count << " audio devices ======"sv << std::endl;
|
||||||
for(auto x = 0; x < count; ++x) {
|
for(auto x = 0; x < count; ++x) {
|
||||||
audio::device_t::pointer device_p {};
|
audio::device_t device;
|
||||||
collection->Item(x, &device_p);
|
collection->Item(x, &device);
|
||||||
audio::device_t device { device_p };
|
|
||||||
|
|
||||||
audio::print_device(device);
|
audio::print_device(device);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Created by loki on 1/23/20.
|
// Created by loki on 1/23/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <dxgi.h>
|
|
||||||
#include <d3dcommon.h>
|
#include <d3dcommon.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
|
|||||||
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
|
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
|
||||||
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
||||||
|
|
||||||
}
|
} // namespace dxgi
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
@@ -49,7 +49,8 @@ int main(int argc, char *argv[]) {
|
|||||||
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
|
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
|
||||||
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
|
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
|
||||||
<< "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 << std::endl
|
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
|
||||||
|
<< std::endl
|
||||||
<< " ====== OUTPUT ======"sv << std::endl;
|
<< " ====== OUTPUT ======"sv << std::endl;
|
||||||
|
|
||||||
dxgi::output_t::pointer output_p {};
|
dxgi::output_t::pointer output_p {};
|
||||||
@@ -66,7 +67,8 @@ int main(int argc, char *argv[]) {
|
|||||||
<< L" Output Name : "sv << desc.DeviceName << std::endl;
|
<< L" Output Name : "sv << desc.DeviceName << std::endl;
|
||||||
std::cout
|
std::cout
|
||||||
<< " AttachedToDesktop : "sv << (desc.AttachedToDesktop ? "yes"sv : "no"sv) << std::endl
|
<< " AttachedToDesktop : "sv << (desc.AttachedToDesktop ? "yes"sv : "no"sv) << std::endl
|
||||||
<< " Resolution : "sv << width << 'x' << height << std::endl << std::endl;
|
<< " Resolution : "sv << width << 'x' << height << std::endl
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user