From 30496c79ab0ff22fd600824825a3a60244e5c476 Mon Sep 17 00:00:00 2001 From: Jacek Szafarkiewicz Date: Fri, 4 Sep 2020 15:51:20 +0200 Subject: [PATCH 001/158] Make systemd script cleaner --- sunshine.service.in | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sunshine.service.in b/sunshine.service.in index 802442df..be1459ca 100644 --- a/sunshine.service.in +++ b/sunshine.service.in @@ -2,12 +2,8 @@ Description=Sunshine Gamestream Server for Moonlight [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@ +ExecStartPre=/bin/sh -c "test -d %E/sunshine || cp -r '@SUNSHINE_ASSETS_DIR@' '%E/sunshine'" +ExecStart=@SUNSHINE_EXECUTABLE_PATH@ %E/sunshine/sunshine.conf [Install] -WantedBy=default.target +WantedBy=graphical-session.target From 7b39b93bb2cf84f82d37a93e2ac595541c19f759 Mon Sep 17 00:00:00 2001 From: Doomsdayrs <38189170+Doomsdayrs@users.noreply.github.com> Date: Wed, 18 Nov 2020 15:31:13 -0500 Subject: [PATCH 002/158] Update README.md - Cleaned things up a bit more - Simplified instructions - make instruction has `-j ${nproc}` to ease up the word count - Use $USER instead of {username} --- README.md | 55 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fc414370..b71d37ec 100644 --- a/README.md +++ b/README.md @@ -12,39 +12,52 @@ Sunshine is a Gamestream host for Moonlight ### Requirements: Ubuntu 20.04: - - 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 +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 +``` ### Compilation: - `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` - `cd sunshine && mkdir build && cd build` - `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: sunshine needs access to uinput to create mouse and gamepad events: -- Add user to group 'input': "usermod -a -G input username -- Create a file: "/etc/udev/rules.d/85-sunshine-input.rules" -- The contents of the file is as follows: - KERNEL=="uinput", GROUP="input", mode="0660" -- assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf" -- path/to/build/dir/sunshine.service is used to start sunshine in the background: - - `cp sunshine.service $HOME/.config/systemd/user/` - - Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable - - `systemctl --user start sunshine` +- Add user to group 'input': + `usermod -a -G input $USER` +- Create udev rules: + - Run the following command: + `nano /etc/udev/rules.d/85-sunshine-input.rules` + - Input the following contents: + `KERNEL=="uinput", GROUP="input", mode="0660"` + - Save the file and exit + 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 add it, do the following: + 1. Modify `sunshine.conf` to point to the sunshine executable if nessecary + 2. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/` + 3. 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: - * If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": - * groups - * 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 you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": + - `groups $USER` + +- 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 From 0a883ab651ec5e496c3bb39072cd3cb5f13cf3bc Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Mon, 4 Jan 2021 04:45:46 +0000 Subject: [PATCH 003/158] Use realtime GPU priority to avoid stalls during high GPU usage Change derived from OBS project commit: https://github.com/obsproject/obs-studio/commit/ec769ef008b748f7dfba211daec9eb203ea4bea0 See related discussion: https://obsproject.com/forum/threads/obs-studio-24-0-3-gpu-priority-fix-testing.111669/ --- sunshine/platform/windows/display.h | 13 ++++++++++ sunshine/platform/windows/display_base.cpp | 30 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 662115e8..039e6a75 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -84,6 +84,19 @@ public: DXGI_FORMAT format; D3D_FEATURE_LEVEL feature_level; + + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS + { + D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, + D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, + D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME + } + D3DKMT_SCHEDULINGPRIORITYCLASS; + + typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); }; class display_ram_t : public display_base_t { diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 4b42880c..c510b044 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -206,6 +206,36 @@ int display_base_t::init() { // Bump up thread priority { + const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; + TOKEN_PRIVILEGES tp; + HANDLE token; + LUID val; + + if (OpenProcessToken(GetCurrentProcess(), flags, &token) && + !!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = val; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { + BOOST_LOG(error) << "Could not set privilege to increase GPU priority"; + } + } + + CloseHandle(token); + + HMODULE gdi32 = GetModuleHandleA("GDI32"); + if (gdi32) { + PD3DKMTSetProcessSchedulingPriorityClass fn = + (PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass"); + if (fn) { + status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance."; + } + } + } + dxgi::dxgi_t::pointer dxgi_p {}; status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); dxgi::dxgi_t dxgi { dxgi_p }; From 83dd07469e40b38fddc6bf2248eadf630367d38b Mon Sep 17 00:00:00 2001 From: Zlatko Zahariev Date: Fri, 15 Jan 2021 02:07:49 -0500 Subject: [PATCH 004/158] Initial elevated windows work --- sunshine/config.cpp | 3 ++ sunshine/config.h | 3 +- sunshine/nvhttp.cpp | 6 +++- sunshine/platform/windows/desktop.h | 18 ++++++++++++ sunshine/platform/windows/display_base.cpp | 9 ++++-- sunshine/platform/windows/input.cpp | 34 ++++++++++++++++++++-- 6 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 sunshine/platform/windows/desktop.h diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 83c7db9b..39bc9b93 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -332,6 +332,9 @@ int apply_flags(const char *line) { case '1': config::sunshine.flags[config::flag::FRESH_STATE].flip(); break; + case 'p': + config::sunshine.flags[config::flag::CONST_PIN].flip(); + break; default: std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; ret = -1; diff --git a/sunshine/config.h b/sunshine/config.h index 9c14b1fe..a194d956 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -71,7 +71,8 @@ namespace flag { enum flag_e : std::size_t { PIN_STDIN = 0, // Read PIN from stdin instead of http FRESH_STATE, // Do not load or save state - FLAG_SIZE + FLAG_SIZE, + CONST_PIN= 4 // Use "universal" pin }; } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6ce58a7e..da402706 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -360,7 +360,11 @@ void pair(std::shared_ptr> &add_cert, std::shared_ ptr->second.async_insert_pin.salt = std::move(args.at("salt"s)); - if(config::sunshine.flags[config::flag::PIN_STDIN]) { + if(config::sunshine.flags[config::flag::CONST_PIN]) { + std::string pin("6174"); + getservercert(ptr->second, tree, pin); + } + else if(config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; std::cout << "Please insert pin: "sv; diff --git a/sunshine/platform/windows/desktop.h b/sunshine/platform/windows/desktop.h new file mode 100644 index 00000000..b0b2e69b --- /dev/null +++ b/sunshine/platform/windows/desktop.h @@ -0,0 +1,18 @@ +namespace platf { + using namespace std::literals; + inline auto pairInputDesktop(){ + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if (NULL == hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } else { + BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; + if (!SetThreadDesktop(hDesk) ) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + CloseDesktop(hDesk); + } + return hDesk; + }; +}; \ No newline at end of file diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 4b42880c..86b7127b 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -10,6 +10,8 @@ #include "display.h" +#include "desktop.h" + namespace platf { using namespace std::literals; } @@ -90,6 +92,8 @@ int display_base_t::init() { FreeLibrary(user32); }); */ + pairInputDesktop(); + dxgi::factory1_t::pointer factory_p {}; dxgi::adapter_t::pointer adapter_p {}; dxgi::output_t::pointer output_p {}; @@ -150,8 +154,8 @@ int display_base_t::init() { } D3D_FEATURE_LEVEL featureLevels[] { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, + //D3D_FEATURE_LEVEL_12_1, + //D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -164,7 +168,6 @@ int display_base_t::init() { status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; - return -1; } diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 60120738..9b43494f 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -12,11 +12,15 @@ #include "sunshine/main.h" #include "sunshine/platform/common.h" +#include "desktop.h" + namespace platf { using namespace std::literals; using adapteraddrs_t = util::c_ptr; +volatile HDESK _lastKnownInputDesktop = NULL; + class vigem_t { public: using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; @@ -171,9 +175,15 @@ void move_mouse(input_t &input, int deltaX, int deltaY) { mi.dwFlags = MOUSEEVENTF_MOVE; mi.dx = deltaX; mi.dy = deltaY; - + +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 mouse movement input"sv; } } @@ -218,8 +228,14 @@ void button_mouse(input_t &input, int button, bool release) { return; } +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 mouse button input"sv; } } @@ -233,9 +249,15 @@ void scroll(input_t &input, int distance) { mi.dwFlags = MOUSEEVENTF_WHEEL; mi.mouseData = distance; +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send mouse scroll input"sv; } } @@ -282,9 +304,15 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { ki.dwFlags |= KEYEVENTF_KEYUP; } +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send keyboard input"sv; } } From 020e2069cbd5989e6579c6b6cf14d6e83f6e793d Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 28 Jan 2021 18:32:58 -0500 Subject: [PATCH 005/158] Add amd decoder support --- .gitignore | 2 +- sunshine/config.cpp | 67 +++++++++++++++++++++++++++++++- sunshine/config.h | 6 +++ sunshine/video.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c676cc4c..c15ad42a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ cmake-build-* *.swp *.kdev4 -.idea +.idea \ No newline at end of file diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 83c7db9b..8561d298 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -85,6 +85,60 @@ int coder_from_view(const std::string_view &coder) { } } +namespace amd { +enum quality_e : int { + _default = 0, + speed, + balanced, + //quality2, +}; + +enum rc_e : int { + constqp = 0x0, /**< Constant QP mode */ + vbr = 0x1, /**< Variable bitrate mode */ + cbr = 0x2, /**< Constant bitrate mode */ + cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ + cbr_hq = 0x10, /**< CBR, high quality (slower) */ + vbr_hq = 0x20 /**< VBR, high quality (slower) */ +}; + +enum coder_e : int { + _auto = 0, + cabac, + cavlc +}; + +std::optional quality_from_view(const std::string_view &quality) { +#define _CONVERT_(x) if(quality == #x##sv) return x + _CONVERT_(speed); + _CONVERT_(balanced); + //_CONVERT_(quality2); + if(quality == "default"sv) return _default; +#undef _CONVERT_ + return std::nullopt; +} + +std::optional rc_from_view(const std::string_view &rc) { +#define _CONVERT_(x) if(rc == #x##sv) return x + _CONVERT_(constqp); + _CONVERT_(vbr); + _CONVERT_(cbr); + _CONVERT_(cbr_hq); + _CONVERT_(vbr_hq); + _CONVERT_(cbr_ld_hq); +#undef _CONVERT_ + return std::nullopt; +} + +int coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + + return -1; +} +} + video_t video { 0, // crf 28, // qp @@ -103,6 +157,12 @@ video_t video { -1 }, // nv + { + amd::balanced, + std::nullopt, + -1 + }, // amd + {}, // encoder {}, // adapter_name {} // output_name @@ -357,8 +417,13 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "sw_preset", video.sw.preset); string_f(vars, "sw_tune", video.sw.tune); int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); - int_f(vars, "nv_rc", video.nv.preset, nv::rc_from_view); + int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view); int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view); + + int_f(vars, "amd_quality", video.amd.quality, amd::quality_from_view); + int_f(vars, "amd_rc", video.amd.rc, amd::rc_from_view); + int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view); + string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); diff --git a/sunshine/config.h b/sunshine/config.h index 9c14b1fe..f3beb5bd 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -26,6 +26,12 @@ struct video_t { int coder; } nv; + struct { + std::optional quality; + std::optional rc; + int coder; + } amd; + std::string encoder; std::string adapter_name; std::string output_name; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index f32cd60f..1557adbd 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -58,6 +58,20 @@ enum class profile_hevc_e : int { }; } +namespace amd { + +enum class profile_h264_e : int { + main, + high, + constrained_baseline, + constrained_high, +}; + +enum class profile_hevc_e : int { + main, +}; +} + using ctx_t = util::safe_ptr; using frame_t = util::safe_ptr; using buffer_t = util::safe_ptr; @@ -70,6 +84,8 @@ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); void sw_img_to_frame(const platf::img_t &img, frame_t &frame); void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); +void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); +util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); @@ -284,6 +300,38 @@ static encoder_t nvenc { nv_d3d_img_to_frame, nv_d3d_make_hwdevice_ctx }; + +static encoder_t amdvce { + "amdvce"sv, + { (int)amd::profile_h264_e::high, (int)amd::profile_hevc_e::main }, + //AV_HWDEVICE_TYPE_D3D11VA, + //AV_PIX_FMT_NONE, + //AV_PIX_FMT_YUV420P, + AV_HWDEVICE_TYPE_D3D11VA, + AV_PIX_FMT_D3D11, + AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, + { + { + { "quality"s, &config::video.amd.quality }, + { "rc"s, &config::video.amd.rc } + }, + std::nullopt, std::nullopt, + "hevc_amf"s, + }, + { + { + { "quality"s, &config::video.amd.quality }, + { "rc"s, &config::video.amd.rc } + }, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + "h264_amf"s + }, + false, + true, + + amd_d3d_img_to_frame, + amd_d3d_make_hwdevice_ctx +}; #endif static encoder_t software { @@ -323,6 +371,7 @@ static encoder_t software { static std::vector encoders { #ifdef _WIN32 nvenc, + amdvce, #endif software }; @@ -1249,6 +1298,30 @@ void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { frame->width = img.width; } +void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { + if(img.data == frame->data[0]) { + return; + } + + // Need to have something refcounted + if(!frame->buf[0]) { + frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); + } + + auto desc = (AVD3D11FrameDescriptor*)frame->buf[0]->data; + desc->texture = (ID3D11Texture2D*)img.data; + desc->index = 0; + + frame->data[0] = img.data; + frame->data[1] = 0; + + frame->linesize[0] = img.row_pitch; + + frame->height = img.height; + frame->width = img.width; +} + + util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; @@ -1269,6 +1342,27 @@ util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice return ctx_buf; } + +util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { + buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; + auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; + + std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); + + auto device = (ID3D11Device*)hwdevice_ctx->data; + device->AddRef(); + ctx->device = device; + + auto err = av_hwdevice_ctx_init(ctx_buf.get()); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + BOOST_LOG(error) << "Failed to create FFMpeg amddech: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + + return err; + } + + return ctx_buf; +} #endif int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { From 7abcfc0390bb778d7830be3d3ef5585628e0ba66 Mon Sep 17 00:00:00 2001 From: kiralycraft Date: Sun, 28 Feb 2021 15:52:47 +0200 Subject: [PATCH 006/158] Added ability to stream specific monitor on Linux --- CMakeLists.txt | 1 + assets/sunshine.conf | 11 +- sunshine/config.h | 2 + sunshine/platform/linux/display.cpp | 751 +++++++++++++++++----------- 4 files changed, 463 insertions(+), 302 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e933ff..177ce096 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ else() xcb xcb-shm xcb-xfixes + Xrandr ${X11_LIBRARIES} evdev pulse diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 3fabb634..b0216ada 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -93,6 +93,9 @@ # adapter_name = Radeon RX 580 Series # output_name = \\.\DISPLAY1 +# !! Linux only !! +# Set the display number to stream. I have no idea how they are numbered. They start from 0. +linux_monitor_id = 3 ############################################### # FFmpeg software encoding parameters @@ -102,7 +105,7 @@ # Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality # Higher value means more compression, but less quality # If crf == 0, then use QP directly instead -# crf = 0 +crf = 18 # Quantitization Parameter # Higher value means more compression, but less quality @@ -113,7 +116,7 @@ # Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually # worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest # value that can reliably encode at your desired streaming settings on your hardware. -# min_threads = 1 +min_threads = 1 # Allows the client to request HEVC Main or HEVC Main10 video streams. # HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. @@ -121,14 +124,14 @@ # If set to 1, Sunshine will not advertise support for HEVC # If set to 2, Sunshine will advertise support for HEVC Main profile # If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 0 +hevc_mode = 0 # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc # software # -# encoder = nvenc +# encoder = vaapi ##################################### Software ##################################### # See x264 --fullhelp for the different presets # sw_preset = superfast diff --git a/sunshine/config.h b/sunshine/config.h index 9c14b1fe..0102fd84 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -29,6 +29,8 @@ struct video_t { std::string encoder; std::string adapter_name; std::string output_name; + + int linux_monitor_id; //Only used on linux }; struct audio_t { diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 1452920e..b3869d92 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,11 +27,12 @@ #include "sunshine/config.h" #include "sunshine/main.h" -namespace platf { +namespace platf +{ using namespace std::literals; -void freeImage(XImage *); -void freeX(XFixesCursorImage *); +void freeImage(XImage*); +void freeX(XFixesCursorImage*); using ifaddr_t = util::safe_ptr; using xcb_connect_t = util::safe_ptr; @@ -41,407 +43,560 @@ using xdisplay_t = util::safe_ptr_v2; using ximg_t = util::safe_ptr; using xcursor_t = util::safe_ptr; -class shm_id_t { +class shm_id_t +{ public: - shm_id_t() : id { -1 } {} - shm_id_t(int id) : id {id } {} - shm_id_t(shm_id_t &&other) noexcept : id(other.id) { - other.id = -1; - } + shm_id_t() : id { -1 } + { + } + shm_id_t(int id) : id { id } + { + } + shm_id_t(shm_id_t &&other) noexcept : id(other.id) + { + other.id = -1; + } - ~shm_id_t() { - if(id != -1) { - shmctl(id, IPC_RMID, nullptr); - id = -1; - } - } - int id; + ~shm_id_t() + { + if (id != -1) + { + shmctl(id, IPC_RMID, nullptr); + id = -1; + } + } + int id; }; -class shm_data_t { +class shm_data_t +{ public: - shm_data_t() : data {(void*)-1 } {} - shm_data_t(void *data) : data {data } {} + shm_data_t() : data { (void*) -1 } + { + } + shm_data_t(void *data) : data { data } + { + } - shm_data_t(shm_data_t &&other) noexcept : data(other.data) { - other.data = (void*)-1; - } + shm_data_t(shm_data_t &&other) noexcept : data(other.data) + { + other.data = (void*) -1; + } - ~shm_data_t() { - if((std::uintptr_t)data != -1) { - shmdt(data); - data = (void*)-1; - } - } + ~shm_data_t() + { + if ((std::uintptr_t) data != -1) + { + shmdt(data); + data = (void*) -1; + } + } - void *data; + void *data; }; -struct x11_img_t : public img_t { - ximg_t img; +struct x11_img_t: public img_t +{ + ximg_t img; }; -struct shm_img_t : public img_t { - ~shm_img_t() override { - delete[] data; - data = nullptr; - } +struct shm_img_t: public img_t +{ + ~shm_img_t() override + { + delete[] data; + data = nullptr; + } }; -void blend_cursor(Display *display, img_t &img) { - xcursor_t overlay { XFixesGetCursorImage(display) }; +void blend_cursor(Display *display, img_t &img, int offsetX,int offsetY) +{ + xcursor_t overlay { XFixesGetCursorImage(display) }; - if(!overlay) { - BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; - return; - } + if (!overlay) + { + BOOST_LOG(error) + << "Couldn't get cursor from XFixesGetCursorImage"sv; + return; + } - overlay->x -= overlay->xhot; - overlay->y -= overlay->yhot; + overlay->x -= overlay->xhot; + overlay->y -= overlay->yhot; - overlay->x = std::max((short)0, overlay->x); - overlay->y = std::max((short)0, overlay->y); + overlay->x -= offsetX; + overlay->y -= offsetY; - auto pixels = (int*)img.data; + overlay->x = std::max((short) 0, overlay->x); + overlay->y = std::max((short) 0, overlay->y); - auto screen_height = img.height; - auto screen_width = img.width; + auto pixels = (int*) img.data; - auto delta_height = std::min(overlay->height, std::max(0, screen_height - overlay->y)); - auto delta_width = std::min(overlay->width, std::max(0, screen_width - overlay->x)); - for(auto y = 0; y < delta_height; ++y) { + auto screen_height = img.height; + auto screen_width = img.width; - auto overlay_begin = &overlay->pixels[y * overlay->width]; - auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; + auto delta_height = std::min(overlay->height,std::max(0, screen_height - overlay->y)); + auto delta_width = std::min(overlay->width,std::max(0, screen_width - overlay->x)); + for (auto y = 0; y < delta_height; ++y) + { + auto overlay_begin = &overlay->pixels[y * overlay->width]; + auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; - auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x]; - std::for_each(overlay_begin, overlay_end, [&](long pixel) { - int *pixel_p = (int*)&pixel; + auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x]; - auto colors_in = (uint8_t*)pixels_begin; + std::for_each(overlay_begin, overlay_end,[&](long pixel) + { + int *pixel_p = (int*) &pixel; - auto alpha = (*(uint*)pixel_p) >> 24u; - if(alpha == 255) { - *pixels_begin = *pixel_p; - } - else { - auto colors_out = (uint8_t*)pixel_p; - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; - } - ++pixels_begin; - }); - } + auto colors_in = (uint8_t*) pixels_begin; + + auto alpha = (*(uint*) pixel_p) >> 24u; + if (alpha == 255) + { + *pixels_begin = *pixel_p; + } + else + { + auto colors_out = (uint8_t*) pixel_p; + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + } + ++pixels_begin; + }); + } } -struct x11_attr_t : public display_t { - x11_attr_t() : xdisplay {XOpenDisplay(nullptr) }, xwindow { }, xattr {} { - if(!xdisplay) { - BOOST_LOG(fatal) << "Could not open x11 display"sv; - log_flush(); - std::abort(); - } +struct x11_attr_t: public display_t +{ + xdisplay_t xdisplay; + Window xwindow; + XWindowAttributes xattr; - xwindow = DefaultRootWindow(xdisplay.get()); + Display* displayDisplay; - refresh(); + /* + * Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming + */ + int lastWidth,lastHeight; - width = xattr.width; - height = xattr.height; - } + /* + * Offsets for when streaming a specific monitor. By default, they are 0. + */ + int displayOffsetX,displayOffsetY; - void refresh() { - XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); - } + x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { } + { + XInitThreads(); + if (!xdisplay) + { + BOOST_LOG(fatal) + << "Could not open x11 display"sv; + log_flush(); + std::abort(); + } - capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override { - refresh(); + xwindow = DefaultRootWindow(xdisplay.get()); - if(width != xattr.width || height != xattr.height) { - return capture_e::reinit; - } + refresh(); - XImage *img { XGetImage( - xdisplay.get(), - xwindow, - 0, 0, - xattr.width, xattr.height, - AllPlanes, ZPixmap) - }; + int streamedMonitor = config::video.linux_monitor_id; - auto img_out = (x11_img_t*)img_out_base; - img_out->width = img->width; - img_out->height = img->height; - img_out->data = (uint8_t*)img->data; - img_out->row_pitch = img->bytes_per_line; - img_out->pixel_pitch = img->bits_per_pixel / 8; - img_out->img.reset(img); + if (streamedMonitor != -1) //If the value has been set at all + { + BOOST_LOG(warning) << "Configuring selected monitor to stream. If it fails here, you may need Xrandr >= 1.5"sv; + XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); + // This is the key right here. Use XRRScreenResources::noutput + int output = screenr->noutput; - if(cursor) { - blend_cursor(xdisplay.get(), *img_out_base); - } + if (streamedMonitor >= output) + { + BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv; + } + else + { + XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]); + if (NULL == out_info || out_info->connection != RR_Connected) + { + BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv; + } + else + { + XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc); + BOOST_LOG(info)<<"Streaming display: "<name<<" with res "<width<<" x "<height<<+" offset by "<x<<" x "<y<<"."sv; - return capture_e::ok; - } + width = crt_info -> width; + height = crt_info -> height; + displayOffsetX = crt_info -> x; + displayOffsetY = crt_info -> y; - std::shared_ptr alloc_img() override { - return std::make_shared(); - } + XRRFreeCrtcInfo(crt_info); + } + XRRFreeOutputInfo(out_info); + } + XRRFreeScreenResources(screenr); + } + else + { + width = xattr.width; + height = xattr.height; + } - int dummy_img(img_t *img) override { - snapshot(img, 0s, true); - return 0; - } + lastWidth = xattr.width; + lastHeight = xattr.height; + } - xdisplay_t xdisplay; - Window xwindow; - XWindowAttributes xattr; + /** + * Called when the display attributes should change. + */ + void refresh() + { + XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's + } + + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override + { + refresh(); + capture_e toReturn; + if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything + { + BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; + toReturn = capture_e::reinit; + } + else + { + XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) }; + + auto img_out = (x11_img_t*) img_out_base; + img_out->width = img->width; + img_out->height = img->height; + img_out->data = (uint8_t*) img->data; + img_out->row_pitch = img->bytes_per_line; + img_out->pixel_pitch = img->bits_per_pixel / 8; + img_out->img.reset(img); + + if (cursor) + { + blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY); + } + toReturn = capture_e::ok; + } + return toReturn; + } + + std::shared_ptr alloc_img() override + { + return std::make_shared(); + } + + int dummy_img(img_t *img) override + { + snapshot(img, 0s, true); + return 0; + } }; -struct shm_attr_t : public x11_attr_t { - xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay - xcb_connect_t xcb; - xcb_screen_t *display; - std::uint32_t seg; +struct shm_attr_t: public x11_attr_t +{ + xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay + xcb_connect_t xcb; + xcb_screen_t *display; + std::uint32_t seg; - shm_id_t shm_id; + shm_id_t shm_id; - shm_data_t data; + shm_data_t data; - util::TaskPool::task_id_t refresh_task_id; - void delayed_refresh() { - refresh(); + util::TaskPool::task_id_t refresh_task_id; + void delayed_refresh() + { + refresh(); - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; - } + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + } - shm_attr_t() : x11_attr_t(), shm_xdisplay {XOpenDisplay(nullptr) } { - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; - } + shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } + { + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + } - ~shm_attr_t() override { - while(!task_pool.cancel(refresh_task_id)); - } + ~shm_attr_t() override + { + while (!task_pool.cancel(refresh_task_id)); + } - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { - if(width != xattr.width || height != xattr.height) { - return capture_e::reinit; - } + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override + { + capture_e toReturn; + if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything + { + BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv; + toReturn = capture_e::reinit; + } + 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, - 0, 0, - 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) }; + if (!img_reply) + { + BOOST_LOG(error) + << "Could not get image reply"sv; + return capture_e::reinit; + } - xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; - if(!img_reply) { - BOOST_LOG(error) << "Could not get image reply"sv; - 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) + { + blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY); + } - if(cursor) { - blend_cursor(shm_xdisplay.get(), *img); - } + toReturn = capture_e::ok; + } + return toReturn; + } - return capture_e::ok; - } + std::shared_ptr alloc_img() override + { + auto img = std::make_shared(); + img->width = width; + img->height = height; + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->data = new std::uint8_t[height * img->row_pitch]; - std::shared_ptr alloc_img() override { - auto img = std::make_shared(); - img->width = width; - img->height = height; - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->data = new std::uint8_t[height * img->row_pitch]; + return img; + } - return img; - } + int dummy_img(platf::img_t *img) override + { + return 0; + } - int dummy_img(platf::img_t *img) override { - return 0; - } + int init() + { + shm_xdisplay.reset(XOpenDisplay(nullptr)); + xcb.reset(xcb_connect(nullptr, nullptr)); + if (xcb_connection_has_error(xcb.get())) + { + return -1; + } - int init() { - shm_xdisplay.reset(XOpenDisplay(nullptr)); - xcb.reset(xcb_connect(nullptr, nullptr)); - if(xcb_connection_has_error(xcb.get())) { - return -1; - } + if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) + { + BOOST_LOG(error) + << "Missing SHM extension"sv; - if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) { - BOOST_LOG(error) << "Missing SHM extension"sv; + return -1; + } - return -1; - } + auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); + display = iter.data; + seg = xcb_generate_id(xcb.get()); - auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); - display = iter.data; - seg = xcb_generate_id(xcb.get()); + shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); + if (shm_id.id == -1) + { + BOOST_LOG(error) + << "shmget failed"sv; + return -1; + } - shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); - if(shm_id.id == -1) { - BOOST_LOG(error) << "shmget failed"sv; - return -1; - } + xcb_shm_attach(xcb.get(), seg, shm_id.id, false); + data.data = shmat(shm_id.id, nullptr, 0); - xcb_shm_attach(xcb.get(), seg, shm_id.id, false); - data.data = shmat(shm_id.id, nullptr, 0); + if ((uintptr_t) data.data == -1) + { + BOOST_LOG(error) + << "shmat failed"sv; - if ((uintptr_t)data.data == -1) { - 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; - width = display->width_in_pixels; - height = display->height_in_pixels; + return 0; + } - return 0; - } - - std::uint32_t frame_size() { - return width * height * 4; - } + std::uint32_t frame_size() + { + return width * height * 4; + } }; -struct mic_attr_t : public mic_t { - pa_sample_spec ss; - util::safe_ptr mic; +struct mic_attr_t: public mic_t +{ + pa_sample_spec ss; + util::safe_ptr 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 &sample_buf) override { - auto sample_size = sample_buf.size(); + 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 &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); + 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::error; + } - return capture_e::ok; - } + return capture_e::ok; + } }; -std::shared_ptr shm_display() { - auto shm = std::make_shared(); +std::shared_ptr shm_display() +{ + auto shm = std::make_shared(); - if(shm->init()) { - return nullptr; - } + if (shm->init()) + { + return nullptr; + } - return shm; + return shm; } -std::shared_ptr display(platf::dev_type_e hwdevice_type) { - if(hwdevice_type != platf::dev_type_e::none) { - return nullptr; - } +std::shared_ptr display(platf::dev_type_e hwdevice_type) +{ + if (hwdevice_type != platf::dev_type_e::none) + { + BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv; + return nullptr; + } - auto shm_disp = shm_display(); + auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame - if(!shm_disp) { - return std::make_shared(); - } + if (!shm_disp) + { + return std::make_shared(); //Fallback to standard way if else + } - return shm_disp; + return shm_disp; } -std::unique_ptr microphone(std::uint32_t sample_rate) { - auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); +std::unique_ptr microphone(std::uint32_t sample_rate) +{ + auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); - int status; + int status; - const char *audio_sink = "@DEFAULT_MONITOR@"; - if(!config::audio.sink.empty()) { - audio_sink = config::audio.sink.c_str(); - } + 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", &mic->ss, nullptr, nullptr, &status) - ); + mic->mic.reset( + 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; + if (!mic->mic) + { + auto err_str = pa_strerror(status); + BOOST_LOG(error) + << "pa_simple_new() failed: "sv << err_str; - log_flush(); - std::abort(); - } + log_flush(); + std::abort(); + } - return mic; + return mic; } -ifaddr_t get_ifaddrs() { - ifaddrs *p { nullptr }; +ifaddr_t get_ifaddrs() +{ + ifaddrs *p { nullptr }; - getifaddrs(&p); + getifaddrs(&p); - return ifaddr_t { p }; + return ifaddr_t { p }; } -std::string from_sockaddr(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; +std::string from_sockaddr(const sockaddr *const ip_addr) +{ + char data[INET6_ADDRSTRLEN]; - auto family = ip_addr->sa_family; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - } + auto family = ip_addr->sa_family; + if (family == AF_INET6) + { + inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + } - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - } + if (family == AF_INET) + { + inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + } - return std::string { data }; + return std::string { data }; } -std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; +std::pair from_sockaddr_ex( + const sockaddr *const ip_addr) +{ + char data[INET6_ADDRSTRLEN]; - auto family = ip_addr->sa_family; - std::uint16_t port; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6*)ip_addr)->sin6_port; - } + auto family = ip_addr->sa_family; + std::uint16_t port; + if (family == AF_INET6) + { + inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + port = ((sockaddr_in6*) ip_addr)->sin6_port; + } - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in*)ip_addr)->sin_port; - } + if (family == AF_INET) + { + inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + port = ((sockaddr_in*) ip_addr)->sin_port; + } - return { port, std::string { data } }; + return + { port, std::string + { data}}; } -std::string get_mac_address(const std::string_view &address) { - auto ifaddrs = get_ifaddrs(); - for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { - if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { - std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); - if(mac_file.good()) { - std::string mac_address; - std::getline(mac_file, mac_address); - return mac_address; - } - } - } - BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; +std::string get_mac_address(const std::string_view &address) +{ + auto ifaddrs = get_ifaddrs(); + for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) + { + if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) + { + std::ifstream mac_file( + "/sys/class/net/"s + pos->ifa_name + "/address"); + if (mac_file.good()) + { + std::string mac_address; + std::getline(mac_file, mac_address); + return mac_address; + } + } + } + BOOST_LOG(warning) + << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; } -void freeImage(XImage *p) { - XDestroyImage(p); +void freeImage(XImage *p) +{ + XDestroyImage(p); } -void freeX(XFixesCursorImage *p) { - XFree(p); +void freeX(XFixesCursorImage *p) +{ + XFree(p); } } From 87be37293e87cc3c58a3f3652d2579c7326115d8 Mon Sep 17 00:00:00 2001 From: kiralycraft Date: Sun, 28 Feb 2021 15:56:23 +0200 Subject: [PATCH 007/158] Accidentally modified stock config file --- assets/sunshine.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index b0216ada..46e4516b 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -94,8 +94,8 @@ # output_name = \\.\DISPLAY1 # !! Linux only !! -# Set the display number to stream. I have no idea how they are numbered. They start from 0. -linux_monitor_id = 3 +# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually. +# linux_monitor_id = 2 ############################################### # FFmpeg software encoding parameters @@ -105,7 +105,7 @@ linux_monitor_id = 3 # Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality # Higher value means more compression, but less quality # If crf == 0, then use QP directly instead -crf = 18 +# crf = 0 # Quantitization Parameter # Higher value means more compression, but less quality @@ -116,7 +116,7 @@ crf = 18 # Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually # worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest # value that can reliably encode at your desired streaming settings on your hardware. -min_threads = 1 +# min_threads = 1 # Allows the client to request HEVC Main or HEVC Main10 video streams. # HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. @@ -124,14 +124,14 @@ min_threads = 1 # If set to 1, Sunshine will not advertise support for HEVC # If set to 2, Sunshine will advertise support for HEVC Main profile # If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -hevc_mode = 0 +# hevc_mode = 2 # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc # software # -# encoder = vaapi +# encoder = nvenc ##################################### Software ##################################### # See x264 --fullhelp for the different presets # sw_preset = superfast From b336bf2fcbd94d5b490e46ab385882ecdc7af6ab Mon Sep 17 00:00:00 2001 From: kiralycraft Date: Sun, 28 Feb 2021 16:10:29 +0200 Subject: [PATCH 008/158] Attempt to update GCC to version 8 for appveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 2bbc9b2c..1467c82f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,7 @@ environment: matrix: - BUILD_TYPE: Debug - BUILD_TYPE: Release + - GCC_VERSION: 8 install: - sh: sudo apt update From fe3784454ac3808dd7f8e5d04d6fc0259d9b479c Mon Sep 17 00:00:00 2001 From: kiralycraft Date: Sun, 28 Feb 2021 17:56:38 +0200 Subject: [PATCH 009/158] Fixed unspecified monitor streaming the whole X --- sunshine/config.cpp | 5 ++++- sunshine/platform/linux/display.cpp | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 83c7db9b..cccce8be 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -105,7 +105,8 @@ video_t video { {}, // encoder {}, // adapter_name - {} // output_name + {}, // output_name + -1 }; audio_t audio {}; @@ -371,6 +372,8 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "audio_sink", audio.sink); + int_between_f(vars, "linux_monitor_id", video.linux_monitor_id, { 0, 32 }); + string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv }); diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index b3869d92..5729afbf 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -202,7 +202,8 @@ struct x11_attr_t: public display_t if (streamedMonitor != -1) //If the value has been set at all { - BOOST_LOG(warning) << "Configuring selected monitor to stream. If it fails here, you may need Xrandr >= 1.5"sv; + + BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv; XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); // This is the key right here. Use XRRScreenResources::noutput int output = screenr->noutput; From 0828cc3f83118d58dc1f118f0b5cfbfaa06675bd Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Mon, 29 Mar 2021 13:16:56 +0200 Subject: [PATCH 010/158] Started work on AMD Hardware Decoding --- .gitmodules | 2 +- CMakeLists.txt | 1 + assets/sunshine.conf | 1 + pre-compiled | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 5938843f..db296a31 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/ViGEm/ViGEmClient [submodule "pre-compiled"] path = pre-compiled - url = https://bitbucket.org/Loki-47-6F-64/pre-compiled.git + url = https://github.com/TheElixZammuto/sunshine-prebuilt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e933ff..24c316a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ if(WIN32) ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a) endif() else() diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 3fabb634..f887e45a 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -126,6 +126,7 @@ # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc +# amdvce # software # # encoder = nvenc diff --git a/pre-compiled b/pre-compiled index afd9a9bb..d1684dde 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit afd9a9bbfc6ee1a064b0c1f9210bc20b2170c416 +Subproject commit d1684ddea51a9baee94fcef806686de6017e982b From 4daaa1f089a90ae17a7521d51ea126ffde9d38da Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Wed, 31 Mar 2021 14:11:21 +0200 Subject: [PATCH 011/158] Added some keyframes --- pre-compiled | 2 +- sunshine/video.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pre-compiled b/pre-compiled index d1684dde..5c1e48af 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit d1684ddea51a9baee94fcef806686de6017e982b +Subproject commit 5c1e48af98de9ee415e5c102861eb8115a457204 diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 1557adbd..97148360 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -321,9 +321,11 @@ static encoder_t amdvce { { { { "quality"s, &config::video.amd.quality }, - { "rc"s, &config::video.amd.rc } + { "rc"s, "1"s }, + {"log_to_dbg"s,"1"s}, + {"gops_per_idr","1"s} }, - std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + std::nullopt, std::nullopt, "h264_amf"s }, false, @@ -769,7 +771,7 @@ void encode_run( if(idr_events->peek()) { session->frame->pict_type = AV_PICTURE_TYPE_I; - + session->frame->key_frame = 1; auto event = idr_events->pop(); if(!event) { return; @@ -781,6 +783,7 @@ void encode_run( } else if(frame_nr == key_frame_nr) { session->frame->pict_type = AV_PICTURE_TYPE_I; + session->frame->key_frame = 1; } std::this_thread::sleep_until(next_frame); @@ -919,6 +922,7 @@ encode_e encode_run_sync(std::vector> &synce if(ctx->idr_events->peek()) { pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + pos->session.frame->key_frame = 1; auto event = ctx->idr_events->pop(); auto end = event->second; @@ -928,6 +932,7 @@ encode_e encode_run_sync(std::vector> &synce } else if(ctx->frame_nr == ctx->key_frame_nr) { pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + pos->session.frame->key_frame = 1; } if(img_tmp) { @@ -1120,6 +1125,7 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e encoder.img_to_frame(*hwdevice->img, session->frame); session->frame->pict_type = AV_PICTURE_TYPE_I; + session->frame->key_frame = 1; auto packets = std::make_shared(30); if(encode(1, session->ctx, session->frame, packets, nullptr)) { From 99777c8e82702efc6466beddc41c9be4d5833cec Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Thu, 1 Apr 2021 14:25:38 +0200 Subject: [PATCH 012/158] Added more params --- pre-compiled | 2 +- sunshine/video.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pre-compiled b/pre-compiled index 5c1e48af..36f68727 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit 5c1e48af98de9ee415e5c102861eb8115a457204 +Subproject commit 36f687271239e25b2b7085f764194a61e2b9c605 diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 97148360..5fb56a22 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -304,14 +304,14 @@ static encoder_t nvenc { static encoder_t amdvce { "amdvce"sv, { (int)amd::profile_h264_e::high, (int)amd::profile_hevc_e::main }, - //AV_HWDEVICE_TYPE_D3D11VA, - //AV_PIX_FMT_NONE, - //AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_D3D11VA, AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, { { + { "header_insertion_mode"s, "idr"s }, + { "gops_per_idr"s, 30 }, + { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, { "rc"s, &config::video.amd.rc } }, @@ -320,10 +320,10 @@ static encoder_t amdvce { }, { { + { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, - { "rc"s, "1"s }, + { "rc"s, &config::video.amd.rc }, {"log_to_dbg"s,"1"s}, - {"gops_per_idr","1"s} }, std::nullopt, std::nullopt, "h264_amf"s @@ -1125,7 +1125,6 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e encoder.img_to_frame(*hwdevice->img, session->frame); session->frame->pict_type = AV_PICTURE_TYPE_I; - session->frame->key_frame = 1; auto packets = std::make_shared(30); if(encode(1, session->ctx, session->frame, packets, nullptr)) { From bb5b003dd572ae80701892f4e97eda4aada0f90a Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Thu, 1 Apr 2021 20:27:59 +0200 Subject: [PATCH 013/158] Disabled key frames when not needed --- sunshine/video.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 5fb56a22..90e1ded5 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -315,7 +315,7 @@ static encoder_t amdvce { { "quality"s, &config::video.amd.quality }, { "rc"s, &config::video.amd.rc } }, - std::nullopt, std::nullopt, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), "hevc_amf"s, }, { @@ -325,7 +325,7 @@ static encoder_t amdvce { { "rc"s, &config::video.amd.rc }, {"log_to_dbg"s,"1"s}, }, - std::nullopt, std::nullopt, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), "h264_amf"s }, false, @@ -810,6 +810,7 @@ void encode_run( } session->frame->pict_type = AV_PICTURE_TYPE_NONE; + session->frame->key_frame = 0; } } @@ -971,6 +972,7 @@ encode_e encode_run_sync(std::vector> &synce } pos->session.frame->pict_type = AV_PICTURE_TYPE_NONE; + pos->session.frame->key_frame = 0; ++pos; }) From 6112c81db7ba3102aa04fe38bec382ee5c495d93 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 24 Apr 2021 12:07:26 +0200 Subject: [PATCH 014/158] =?UTF-8?q?Fix=20=C2=96undefined=20reference=20to?= =?UTF-8?q?=20=5F=5Fmemcpy=5Fchk=20when=20building=20on=20msys64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e933ff..554344b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ if(WIN32) ViGEmClient/include/ViGEm/Util.h ViGEmClient/include/ViGEm/km/BusShared.h) list(PREPEND PLATFORM_LIBRARIES + ssp winmm ksuser wsock32 From 0cfb440cf683f9479bb279ab1e91dbea5e02dfdc Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 24 Apr 2021 14:23:12 +0200 Subject: [PATCH 015/158] Added config examples to the config file --- assets/sunshine.conf | 27 ++++++++++++++++++++++++++- sunshine/video.cpp | 4 +++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index f887e45a..08b1b209 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -126,7 +126,7 @@ # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc -# amdvce +# amdvce # NOTE: alpha stage. The cursor is not yet displayed # software # # encoder = nvenc @@ -171,6 +171,31 @@ ########################## # nv_coder = auto +##################################### AMD ##################################### +###### presets ########### +# default +# speed +# balanced +########################## +# amd_preset = balanced +# +####### rate control ##### +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr -- variable bitrate +# cbr -- constant bitrate +# cbr_hq -- cbr high quality +# cbr_ld_hq -- cbr low delay high quality +# vbr_hq -- vbr high quality +########################## +# amd_rc = auto + +###### h264 entropy ###### +# auto -- let ffmpeg nvenc decide the entropy encoding +# cabac +# cavlc +########################## +# amd_coder = auto ############################################## # Some configurable parameters, are merely toggles for specific features diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 90e1ded5..d5ba02ed 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -373,9 +373,11 @@ static encoder_t software { static std::vector encoders { #ifdef _WIN32 nvenc, +#endif + software, +#ifdef _WIN32 amdvce, #endif - software }; void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { From 438ae6a7615d1b9731d04dfef80f0b3ff4907fd7 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 24 Apr 2021 15:53:48 +0200 Subject: [PATCH 016/158] Don't stop streaming when UAC is running --- sunshine/platform/windows/display_base.cpp | 2 -- sunshine/video.cpp | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index c510b044..dbddf29a 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -150,8 +150,6 @@ int display_base_t::init() { } D3D_FEATURE_LEVEL featureLevels[] { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, diff --git a/sunshine/video.cpp b/sunshine/video.cpp index d5ba02ed..b2e23855 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -469,7 +469,14 @@ void captureThread( std::this_thread::sleep_for(100ms); } - reset_display(disp, encoder.dev_type); + while(capture_ctx_queue->running()) { + reset_display(disp, encoder.dev_type); + + if(disp) { + break; + } + std::this_thread::sleep_for(200ms); + } if(!disp) { return; } @@ -846,7 +853,16 @@ encode_e encode_run_sync(std::vector> &synce const auto &encoder = encoders.front(); std::shared_ptr disp; - reset_display(disp, encoder.dev_type); + + while(encode_session_ctx_queue.running()) { + reset_display(disp, encoder.dev_type); + if(disp) { + break; + } + + std::this_thread::sleep_for(200ms); + } + if(!disp) { return encode_e::error; } From 0049b36471270bfa2187c081a4602263656c0b71 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 24 Apr 2021 23:41:56 +0200 Subject: [PATCH 017/158] Use existing config option for selecting monitor --- assets/sunshine.conf | 2 +- pre-compiled | 2 +- sunshine/config.cpp | 3 - sunshine/config.h | 2 - sunshine/platform/linux/display.cpp | 813 +++++++++++++--------------- 5 files changed, 375 insertions(+), 447 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 46e4516b..7a318ed4 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -95,7 +95,7 @@ # !! Linux only !! # Set the display number to stream. I have no idea how they are numbered. They start from 1, usually. -# linux_monitor_id = 2 +# output_name = 1 ############################################### # FFmpeg software encoding parameters diff --git a/pre-compiled b/pre-compiled index afd9a9bb..bbf56474 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit afd9a9bbfc6ee1a064b0c1f9210bc20b2170c416 +Subproject commit bbf5647402859945dee541a300b22702a2ee9271 diff --git a/sunshine/config.cpp b/sunshine/config.cpp index cccce8be..26878011 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -106,7 +106,6 @@ video_t video { {}, // encoder {}, // adapter_name {}, // output_name - -1 }; audio_t audio {}; @@ -372,8 +371,6 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "audio_sink", audio.sink); - int_between_f(vars, "linux_monitor_id", video.linux_monitor_id, { 0, 32 }); - string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv }); diff --git a/sunshine/config.h b/sunshine/config.h index 0102fd84..9c14b1fe 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -29,8 +29,6 @@ struct video_t { std::string encoder; std::string adapter_name; std::string output_name; - - int linux_monitor_id; //Only used on linux }; struct audio_t { diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 5729afbf..cde96cf7 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -43,561 +43,494 @@ using xdisplay_t = util::safe_ptr_v2; using ximg_t = util::safe_ptr; using xcursor_t = util::safe_ptr; -class shm_id_t -{ +class shm_id_t { public: - shm_id_t() : id { -1 } - { - } - shm_id_t(int id) : id { id } - { - } - shm_id_t(shm_id_t &&other) noexcept : id(other.id) - { - other.id = -1; - } + shm_id_t() : id { -1 } {} + shm_id_t(int id) : id { id } {} + shm_id_t(shm_id_t &&other) noexcept : id(other.id) { + other.id = -1; + } - ~shm_id_t() - { - if (id != -1) - { - shmctl(id, IPC_RMID, nullptr); - id = -1; - } - } - int id; + ~shm_id_t() { + if (id != -1) { + shmctl(id, IPC_RMID, nullptr); + id = -1; + } + } + int id; }; -class shm_data_t -{ +class shm_data_t { public: - shm_data_t() : data { (void*) -1 } - { - } - shm_data_t(void *data) : data { data } - { - } + shm_data_t() : data { (void*) -1 } + { + } + shm_data_t(void *data) : data { data } + { + } - shm_data_t(shm_data_t &&other) noexcept : data(other.data) - { - other.data = (void*) -1; - } + shm_data_t(shm_data_t &&other) noexcept : data(other.data) + { + other.data = (void*) -1; + } - ~shm_data_t() - { - if ((std::uintptr_t) data != -1) - { - shmdt(data); - data = (void*) -1; - } - } + ~shm_data_t() + { + if ((std::uintptr_t) data != -1) + { + shmdt(data); + data = (void*) -1; + } + } - void *data; + void *data; }; -struct x11_img_t: public img_t -{ - ximg_t img; +struct x11_img_t: public img_t { + ximg_t img; }; -struct shm_img_t: public img_t -{ - ~shm_img_t() override - { - delete[] data; - data = nullptr; - } +struct shm_img_t: public img_t { + ~shm_img_t() override + { + delete[] data; + data = nullptr; + } }; -void blend_cursor(Display *display, img_t &img, int offsetX,int offsetY) -{ - xcursor_t overlay { XFixesGetCursorImage(display) }; +void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { + xcursor_t overlay { XFixesGetCursorImage(display) }; - if (!overlay) - { - BOOST_LOG(error) - << "Couldn't get cursor from XFixesGetCursorImage"sv; - return; - } + if (!overlay) { + BOOST_LOG(error) + << "Couldn't get cursor from XFixesGetCursorImage"sv; + return; + } - overlay->x -= overlay->xhot; - overlay->y -= overlay->yhot; + overlay->x -= overlay->xhot; + overlay->y -= overlay->yhot; - overlay->x -= offsetX; - overlay->y -= offsetY; + overlay->x -= offsetX; + overlay->y -= offsetY; - overlay->x = std::max((short) 0, overlay->x); - overlay->y = std::max((short) 0, overlay->y); + overlay->x = std::max((short) 0, overlay->x); + overlay->y = std::max((short) 0, overlay->y); - auto pixels = (int*) img.data; + auto pixels = (int*) img.data; - auto screen_height = img.height; - auto screen_width = img.width; + auto screen_height = img.height; + auto screen_width = img.width; - auto delta_height = std::min(overlay->height,std::max(0, screen_height - overlay->y)); - auto delta_width = std::min(overlay->width,std::max(0, screen_width - overlay->x)); - for (auto y = 0; y < delta_height; ++y) - { - auto overlay_begin = &overlay->pixels[y * overlay->width]; - auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; + auto delta_height = std::min(overlay->height,std::max(0, screen_height - overlay->y)); + auto delta_width = std::min(overlay->width,std::max(0, screen_width - overlay->x)); + for (auto y = 0; y < delta_height; ++y) { + auto overlay_begin = &overlay->pixels[y * overlay->width]; + auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; - auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x]; + auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x]; - std::for_each(overlay_begin, overlay_end,[&](long pixel) - { - int *pixel_p = (int*) &pixel; + std::for_each(overlay_begin, overlay_end,[&](long pixel) { + int *pixel_p = (int*) &pixel; - auto colors_in = (uint8_t*) pixels_begin; + auto colors_in = (uint8_t*) pixels_begin; - auto alpha = (*(uint*) pixel_p) >> 24u; - if (alpha == 255) - { - *pixels_begin = *pixel_p; - } - else - { - auto colors_out = (uint8_t*) pixel_p; - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; - } - ++pixels_begin; - }); - } + auto alpha = (*(uint*) pixel_p) >> 24u; + if (alpha == 255) { + *pixels_begin = *pixel_p; + } + else { + auto colors_out = (uint8_t*) pixel_p; + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + } + ++pixels_begin; + }); + } } struct x11_attr_t: public display_t { - xdisplay_t xdisplay; - Window xwindow; - XWindowAttributes xattr; + xdisplay_t xdisplay; + Window xwindow; + XWindowAttributes xattr; - Display* displayDisplay; + Display* displayDisplay; - /* - * Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming - */ - int lastWidth,lastHeight; + /* + * Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming + */ + int lastWidth,lastHeight; - /* - * Offsets for when streaming a specific monitor. By default, they are 0. - */ - int displayOffsetX,displayOffsetY; + /* + * 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(); - if (!xdisplay) - { - BOOST_LOG(fatal) - << "Could not open x11 display"sv; - log_flush(); - std::abort(); - } + x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { } + { + XInitThreads(); + if (!xdisplay) { + BOOST_LOG(fatal) << "Could not open x11 display"sv; + log_flush(); + std::abort(); + } - xwindow = DefaultRootWindow(xdisplay.get()); + xwindow = DefaultRootWindow(xdisplay.get()); - refresh(); + refresh(); - int streamedMonitor = config::video.linux_monitor_id; + int streamedMonitor = -1; + if(!config::video.output_name.empty()) { + streamedMonitor = (int)util::from_view(config::video.output_name); + } - if (streamedMonitor != -1) //If the value has been set at all - { + //If the value has been set at all + if (streamedMonitor != -1) { - BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv; - XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); - // This is the key right here. Use XRRScreenResources::noutput - int output = screenr->noutput; + BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv; + XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); + // This is the key right here. Use XRRScreenResources::noutput + int output = screenr->noutput; - if (streamedMonitor >= output) - { - BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv; - } - else - { - XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]); - if (NULL == out_info || out_info->connection != RR_Connected) - { - BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv; - } - else - { - XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc); - BOOST_LOG(info)<<"Streaming display: "<name<<" with res "<width<<" x "<height<<+" offset by "<x<<" x "<y<<"."sv; + if (streamedMonitor >= output) { + BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv; + } + else { + XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]); + if (NULL == out_info || out_info->connection != RR_Connected) + { + BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv; + } + else + { + XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc); + BOOST_LOG(info)<<"Streaming display: "<name<<" with res "<width<<" x "<height<<+" offset by "<x<<" x "<y<<"."sv; - width = crt_info -> width; - height = crt_info -> height; - displayOffsetX = crt_info -> x; - displayOffsetY = crt_info -> y; + width = crt_info -> width; + height = crt_info -> height; + displayOffsetX = crt_info -> x; + displayOffsetY = crt_info -> y; - XRRFreeCrtcInfo(crt_info); - } - XRRFreeOutputInfo(out_info); - } - XRRFreeScreenResources(screenr); - } - else - { - width = xattr.width; - height = xattr.height; - } + XRRFreeCrtcInfo(crt_info); + } + XRRFreeOutputInfo(out_info); + } + XRRFreeScreenResources(screenr); + } + else { + width = xattr.width; + height = xattr.height; + } - lastWidth = xattr.width; - lastHeight = xattr.height; - } + lastWidth = xattr.width; + lastHeight = xattr.height; + } - /** - * Called when the display attributes should change. - */ - void refresh() - { - XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's - } + /** + * Called when the display attributes should change. + */ + void refresh() + { + XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's + } - capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override - { - refresh(); - capture_e toReturn; - if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything - { - BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; - toReturn = capture_e::reinit; - } - else - { - XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) }; + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override { + refresh(); - auto img_out = (x11_img_t*) img_out_base; - img_out->width = img->width; - img_out->height = img->height; - img_out->data = (uint8_t*) img->data; - img_out->row_pitch = img->bytes_per_line; - img_out->pixel_pitch = img->bits_per_pixel / 8; - img_out->img.reset(img); + //The whole X server changed, so we gotta reinit everything + if (xattr.width != lastWidth || xattr.height != lastHeight) { + BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; + return capture_e::reinit; + } + XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) }; - if (cursor) - { - blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY); - } - toReturn = capture_e::ok; - } - return toReturn; - } + auto img_out = (x11_img_t*) img_out_base; + img_out->width = img->width; + img_out->height = img->height; + img_out->data = (uint8_t*) img->data; + img_out->row_pitch = img->bytes_per_line; + img_out->pixel_pitch = img->bits_per_pixel / 8; + img_out->img.reset(img); - std::shared_ptr alloc_img() override - { - return std::make_shared(); - } + if (cursor) { + blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY); + } - int dummy_img(img_t *img) override - { - snapshot(img, 0s, true); - return 0; - } + return capture_e::ok; + } + + std::shared_ptr alloc_img() override { + return std::make_shared(); + } + + int dummy_img(img_t *img) override { + snapshot(img, 0s, true); + return 0; + } }; -struct shm_attr_t: public x11_attr_t -{ - xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay - xcb_connect_t xcb; - xcb_screen_t *display; - std::uint32_t seg; +struct shm_attr_t: public x11_attr_t { + xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay + xcb_connect_t xcb; + xcb_screen_t *display; + std::uint32_t seg; - shm_id_t shm_id; + shm_id_t shm_id; - shm_data_t data; + shm_data_t data; - util::TaskPool::task_id_t refresh_task_id; - void delayed_refresh() - { - refresh(); + util::TaskPool::task_id_t refresh_task_id; - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; - } + void delayed_refresh() { + refresh(); - shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } - { - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; - } + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + } - ~shm_attr_t() override - { - while (!task_pool.cancel(refresh_task_id)); - } + shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } { + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + } - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override - { - capture_e toReturn; - if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything - { - BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv; - toReturn = capture_e::reinit; - } - 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); + ~shm_attr_t() override { + while (!task_pool.cancel(refresh_task_id)); + } - xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie,nullptr) }; - if (!img_reply) - { - BOOST_LOG(error) - << "Could not get image reply"sv; - return capture_e::reinit; - } + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { + //The whole X server changed, so we gotta reinit everything + if (xattr.width != lastWidth || xattr.height != lastHeight) { + BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv; + return capture_e::reinit; + } + 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); - std::copy_n((std::uint8_t*) data.data, frame_size(), img->data); + xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie,nullptr) }; + if (!img_reply) { + BOOST_LOG(error) + << "Could not get image reply"sv; + return capture_e::reinit; + } - if (cursor) - { - blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY); - } + std::copy_n((std::uint8_t*) data.data, frame_size(), img->data); - toReturn = capture_e::ok; - } - return toReturn; - } + if (cursor) { + blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY); + } - std::shared_ptr alloc_img() override - { - auto img = std::make_shared(); - img->width = width; - img->height = height; - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->data = new std::uint8_t[height * img->row_pitch]; + return capture_e::ok; + } + } - return img; - } + std::shared_ptr alloc_img() override { + auto img = std::make_shared(); + img->width = width; + img->height = height; + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->data = new std::uint8_t[height * img->row_pitch]; - int dummy_img(platf::img_t *img) override - { - return 0; - } + return img; + } - int init() - { - shm_xdisplay.reset(XOpenDisplay(nullptr)); - xcb.reset(xcb_connect(nullptr, nullptr)); - if (xcb_connection_has_error(xcb.get())) - { - return -1; - } + int dummy_img(platf::img_t *img) override { + return 0; + } - if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) - { - BOOST_LOG(error) - << "Missing SHM extension"sv; + int init() { + shm_xdisplay.reset(XOpenDisplay(nullptr)); + xcb.reset(xcb_connect(nullptr, nullptr)); + if (xcb_connection_has_error(xcb.get())) + { + return -1; + } - return -1; - } + if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) + { + BOOST_LOG(error) + << "Missing SHM extension"sv; - auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); - display = iter.data; - seg = xcb_generate_id(xcb.get()); + return -1; + } - shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); - if (shm_id.id == -1) - { - BOOST_LOG(error) - << "shmget failed"sv; - return -1; - } + auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); + display = iter.data; + seg = xcb_generate_id(xcb.get()); - xcb_shm_attach(xcb.get(), seg, shm_id.id, false); - data.data = shmat(shm_id.id, nullptr, 0); + shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); + if (shm_id.id == -1) + { + BOOST_LOG(error) + << "shmget failed"sv; + return -1; + } - if ((uintptr_t) data.data == -1) - { - BOOST_LOG(error) - << "shmat failed"sv; + xcb_shm_attach(xcb.get(), seg, shm_id.id, false); + data.data = shmat(shm_id.id, nullptr, 0); - return -1; - } + if ((uintptr_t) data.data == -1) + { + BOOST_LOG(error) + << "shmat failed"sv; - /* - * 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 -1; + } - return 0; - } + /* + * 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; - std::uint32_t frame_size() - { - return width * height * 4; - } + return 0; + } + + std::uint32_t frame_size() { + return width * height * 4; + } }; -struct mic_attr_t: public mic_t -{ - pa_sample_spec ss; - util::safe_ptr mic; +struct mic_attr_t: public mic_t { + pa_sample_spec ss; + util::safe_ptr 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 &sample_buf) override - { - auto sample_size = sample_buf.size(); + explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, + std::uint8_t channels) : ss { format, sample_rate, channels }, mic { } { } - 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); + capture_e sample(std::vector &sample_buf) override { + auto sample_size = sample_buf.size(); - return capture_e::error; - } + 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::ok; - } + return capture_e::error; + } + + return capture_e::ok; + } }; -std::shared_ptr shm_display() -{ - auto shm = std::make_shared(); +std::shared_ptr shm_display() { + auto shm = std::make_shared(); - if (shm->init()) - { - return nullptr; - } + if (shm->init()) { + return nullptr; + } - return shm; + return shm; } -std::shared_ptr display(platf::dev_type_e hwdevice_type) -{ - if (hwdevice_type != platf::dev_type_e::none) - { - BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv; - return nullptr; - } +std::shared_ptr display(platf::dev_type_e hwdevice_type) { + if (hwdevice_type != platf::dev_type_e::none) { + BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv; + return nullptr; + } - auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame + auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame - if (!shm_disp) - { - return std::make_shared(); //Fallback to standard way if else - } + if (!shm_disp) { + return std::make_shared(); //Fallback to standard way if else + } - return shm_disp; + return shm_disp; } -std::unique_ptr microphone(std::uint32_t sample_rate) -{ - auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); +std::unique_ptr microphone(std::uint32_t sample_rate) { + auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); - int status; + int status; - const char *audio_sink = "@DEFAULT_MONITOR@"; - if (!config::audio.sink.empty()) - { - audio_sink = config::audio.sink.c_str(); - } + 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", &mic->ss, nullptr, nullptr, &status)); + mic->mic.reset( + 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; + if (!mic->mic) { + auto err_str = pa_strerror(status); + BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; - log_flush(); - std::abort(); - } + log_flush(); + std::abort(); + } - return mic; + return mic; } -ifaddr_t get_ifaddrs() -{ - ifaddrs *p { nullptr }; +ifaddr_t get_ifaddrs() { + ifaddrs *p { nullptr }; - getifaddrs(&p); + getifaddrs(&p); - return ifaddr_t { p }; + return ifaddr_t { p }; } -std::string from_sockaddr(const sockaddr *const ip_addr) -{ - char data[INET6_ADDRSTRLEN]; +std::string from_sockaddr(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; - auto family = ip_addr->sa_family; - if (family == AF_INET6) - { - inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, - INET6_ADDRSTRLEN); - } + auto family = ip_addr->sa_family; + if (family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + } - if (family == AF_INET) - { - inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, - INET_ADDRSTRLEN); - } + if (family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + } - return std::string { data }; + return std::string { data }; } -std::pair from_sockaddr_ex( - const sockaddr *const ip_addr) -{ - char data[INET6_ADDRSTRLEN]; +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; - auto family = ip_addr->sa_family; - std::uint16_t port; - if (family == AF_INET6) - { - inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, - INET6_ADDRSTRLEN); - port = ((sockaddr_in6*) ip_addr)->sin6_port; - } + auto family = ip_addr->sa_family; + std::uint16_t port; + if (family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + port = ((sockaddr_in6*) ip_addr)->sin6_port; + } - if (family == AF_INET) - { - inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, - INET_ADDRSTRLEN); - port = ((sockaddr_in*) ip_addr)->sin_port; - } + if (family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + port = ((sockaddr_in*) ip_addr)->sin_port; + } - return - { port, std::string - { data}}; + return + { port, std::string {data}}; } -std::string get_mac_address(const std::string_view &address) -{ - auto ifaddrs = get_ifaddrs(); - for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) - { - if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) - { - std::ifstream mac_file( - "/sys/class/net/"s + pos->ifa_name + "/address"); - if (mac_file.good()) - { - std::string mac_address; - std::getline(mac_file, mac_address); - return mac_address; - } - } - } - BOOST_LOG(warning) - << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; +std::string get_mac_address(const std::string_view &address) { + auto ifaddrs = get_ifaddrs(); + for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { + if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { + std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); + if (mac_file.good()) { + std::string mac_address; + std::getline(mac_file, mac_address); + return mac_address; + } + } + } + BOOST_LOG(warning) + << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; } -void freeImage(XImage *p) -{ - XDestroyImage(p); +void freeImage(XImage *p) { + XDestroyImage(p); } -void freeX(XFixesCursorImage *p) -{ - XFree(p); +void freeX(XFixesCursorImage *p) { + XFree(p); } } From e81db118d5a1fa097e1c672ecd7b3fa8b21975a5 Mon Sep 17 00:00:00 2001 From: Loki Date: Mon, 26 Apr 2021 14:46:57 +0200 Subject: [PATCH 018/158] Fix windows build --- .gitmodules | 3 -- CMakeLists.txt | 110 ++++++++++++++++++++++++++++++------------------- pre-compiled | 1 - 3 files changed, 68 insertions(+), 46 deletions(-) delete mode 160000 pre-compiled diff --git a/.gitmodules b/.gitmodules index db296a31..93af2e68 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "ViGEmClient"] path = ViGEmClient url = https://github.com/ViGEm/ViGEmClient -[submodule "pre-compiled"] - path = pre-compiled - url = https://github.com/TheElixZammuto/sunshine-prebuilt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 63f705ec..18cd222f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0) project(Sunshine) @@ -7,56 +7,65 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) # On MSYS2, building a stand-alone binary that links with ffmpeg is not possible, # Therefore, ffmpeg, libx264 and libx265 must be build from source if(WIN32) - option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF) - if(SUNSHINE_STANDALONE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + file( + DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + TIMEOUT 60 + EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6) + + file(ARCHIVE_EXTRACT + INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) - set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows") - endif() - list(PREPEND PLATFORM_LIBRARIES - C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a - C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a - ) - - set(FFMPEG_INCLUDE_DIRS - ${SUNSHINE_PREPARED_BINARIES}/include) - set(FFMPEG_LIBRARIES - ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a - z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a) + if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) + set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") endif() -else() - set(SUNSHINE_STANDALONE OFF) -endif() + set(FFMPEG_INCLUDE_DIRS + ${SUNSHINE_PREPARED_BINARIES}/include) + set(FFMPEG_LIBRARIES + ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a + z lzma bcrypt libiconv.a) +endif() add_subdirectory(Simple-Web-Server) add_subdirectory(moonlight-common-c/enet) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) -if(NOT SUNSHINE_STANDALONE) - find_package(FFmpeg REQUIRED) -endif() - list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare) if(WIN32) + file( + DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + TIMEOUT 60 + EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6) + + file(ARCHIVE_EXTRACT + INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + + if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) + set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") + endif() + add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") - include_directories( - ViGEmClient/include) + + include_directories(ViGEmClient/include) + set(PLATFORM_TARGET_FILES sunshine/platform/windows/input.cpp sunshine/platform/windows/display.h @@ -69,8 +78,27 @@ if(WIN32) ViGEmClient/include/ViGEm/Common.h ViGEmClient/include/ViGEm/Util.h ViGEmClient/include/ViGEm/km/BusShared.h) + + set(FFMPEG_INCLUDE_DIRS + ${SUNSHINE_PREPARED_BINARIES}/include) + set(FFMPEG_LIBRARIES + ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a + z lzma bcrypt libiconv.a) + list(PREPEND PLATFORM_LIBRARIES - ssp + libstdc++.a + libwinpthread.a + libssp.a winmm ksuser wsock32 @@ -181,11 +209,9 @@ if(NOT SUNSHINE_ASSETS_DIR) set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") endif() -if(SUNSHINE_STANDALONE) - set(OPENSSL_LIBRARIES - C:/msys64/mingw64/lib/libssl.a - C:/msys64/mingw64/lib/libcrypto.a) -endif() +set(OPENSSL_LIBRARIES + libssl.a + libcrypto.a) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} diff --git a/pre-compiled b/pre-compiled deleted file mode 160000 index bbf56474..00000000 --- a/pre-compiled +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bbf5647402859945dee541a300b22702a2ee9271 From 208de3dae9ef8ca040d7a8363a92064df9db1592 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 26 Apr 2021 20:36:54 +0200 Subject: [PATCH 019/158] Fix Linux build --- CMakeLists.txt | 9 +++++---- README.md | 15 ++------------- appveyor.yml | 7 +++---- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18cd222f..1347b7d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,10 @@ if(WIN32) ViGEmClient/include/ViGEm/Util.h ViGEmClient/include/ViGEm/km/BusShared.h) + set(OPENSSL_LIBRARIES + libssl.a + libcrypto.a) + set(FFMPEG_INCLUDE_DIRS ${SUNSHINE_PREPARED_BINARIES}/include) set(FFMPEG_LIBRARIES @@ -114,6 +118,7 @@ else() list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11 REQUIRED) + find_package(FFmpeg REQUIRED) set(PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp sunshine/platform/linux/input.cpp) @@ -209,10 +214,6 @@ if(NOT SUNSHINE_ASSETS_DIR) set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") endif() -set(OPENSSL_LIBRARIES - libssl.a - libcrypto.a) - list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} stdc++fs diff --git a/README.md b/README.md index fc414370..3a1bac44 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ sunshine needs access to uinput to create mouse and gamepad events: ### Requirements: - MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost + mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make ### Compilation: -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` +- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive` - `cd sunshine && mkdir build && cd build` - `cmake -G"Unix Makefiles" ..` - `make` @@ -61,17 +61,6 @@ sunshine needs access to uinput to create mouse and gamepad events: ### Setup: - **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases] -### Static build -#### Requirements: - MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost git-lfs - -#### Compilation: -- `git lfs install` -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DSUNSHINE_STANDALONE=ON -G"Unix Makefiles" ..` -- `make` - # Common diff --git a/appveyor.yml b/appveyor.yml index 1467c82f..4a11c494 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,16 +1,15 @@ image: - - Ubuntu + - Ubuntu2004 - Visual Studio 2019 environment: matrix: - BUILD_TYPE: Debug - BUILD_TYPE: Release - - GCC_VERSION: 8 install: - sh: sudo apt update - - sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev + - 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 - 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: @@ -22,7 +21,7 @@ build_script: - cmd: set OLDPATH=%PATH% - cmd: set PATH=C:\msys64\mingw64\bin - sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine .. - - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - sh: make -j$(nproc) - cmd: mingw32-make -j2 - cmd: set PATH=%OLDPATH% From a7030641f3e83c5ffd5bc4338ec16688c97d9ff5 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 26 Apr 2021 20:50:37 +0200 Subject: [PATCH 020/158] Second attempt to fix linux build --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 4a11c494..8c5359c9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ image: - - Ubuntu2004 + - Ubuntu - Visual Studio 2019 environment: From bb88d6f8282d472981c57977ce01164a6fff28a8 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 26 Apr 2021 21:08:20 +0200 Subject: [PATCH 021/158] Third attempt to fix linux build for appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8c5359c9..e0115dab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ image: - - Ubuntu + - Ubuntu2004 - Visual Studio 2019 environment: @@ -8,7 +8,7 @@ environment: - BUILD_TYPE: Release install: - - sh: sudo apt update + - sh: sudo apt update --ignore-missing - sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev 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" From 1be5b4787f3aa22bd32171b8a8cae6e27237a4c8 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 26 Apr 2021 19:27:55 +0200 Subject: [PATCH 022/158] Moved pairInputDesktop() from desktop.h to input.cpp --- sunshine/platform/windows/desktop.h | 18 ------------------ sunshine/platform/windows/display_base.cpp | 3 --- sunshine/platform/windows/input.cpp | 21 +++++++++++++++++++-- 3 files changed, 19 insertions(+), 23 deletions(-) delete mode 100644 sunshine/platform/windows/desktop.h diff --git a/sunshine/platform/windows/desktop.h b/sunshine/platform/windows/desktop.h deleted file mode 100644 index b0b2e69b..00000000 --- a/sunshine/platform/windows/desktop.h +++ /dev/null @@ -1,18 +0,0 @@ -namespace platf { - using namespace std::literals; - inline auto pairInputDesktop(){ - auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if (NULL == hDesk) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; - } else { - BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; - if (!SetThreadDesktop(hDesk) ) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; - } - CloseDesktop(hDesk); - } - return hDesk; - }; -}; \ No newline at end of file diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index cf5c5e4f..29b53523 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -10,8 +10,6 @@ #include "display.h" -#include "desktop.h" - namespace platf { using namespace std::literals; } @@ -92,7 +90,6 @@ int display_base_t::init() { FreeLibrary(user32); }); */ - pairInputDesktop(); dxgi::factory1_t::pointer factory_p {}; dxgi::adapter_t::pointer adapter_p {}; diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 9b43494f..e69fea96 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -12,14 +12,13 @@ #include "sunshine/main.h" #include "sunshine/platform/common.h" -#include "desktop.h" - namespace platf { using namespace std::literals; using adapteraddrs_t = util::c_ptr; volatile HDESK _lastKnownInputDesktop = NULL; +HDESK pairInputDesktop(); class vigem_t { public: @@ -355,6 +354,24 @@ int thread_priority() { return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1; } +HDESK pairInputDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if (NULL == hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + else { + BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; + if (!SetThreadDesktop(hDesk) ) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + CloseDesktop(hDesk); + } + + return hDesk; +}; + void freeInput(void *p) { auto vigem = (vigem_t*)p; From fe8c2ceab9bba05da62202502563293c9e195fab Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 28 Apr 2021 13:44:42 +0200 Subject: [PATCH 023/158] Warn when VideoProcessorSetStreamAlpha isn't supported --- sunshine/platform/windows/display_vram.cpp | 19 +++++++++++++------ sunshine/platform/windows/input.cpp | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 55fce9a4..0d87b40c 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -99,6 +99,7 @@ util::buffer_t make_cursor_image(util::buffer_t &&im class hwdevice_t : public platf::hwdevice_t { public: + hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} hwdevice_t() = delete; @@ -168,8 +169,8 @@ public: auto &processor_in = it->second; D3D11_VIDEO_PROCESSOR_STREAM stream[] { - { TRUE, 0, 0, 0, 0, nullptr, processor_in.get(), nullptr }, - { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get(), nullptr } + { TRUE, 0, 0, 0, 0, nullptr, processor_in.get() }, + { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get() } }; auto status = ctx->VideoProcessorBlt(processor.get(), processor_out.get(), 0, cursor_visible ? 2 : 1, stream); @@ -233,6 +234,12 @@ public: } processor_e.reset(vp_e_p); + D3D11_VIDEO_PROCESSOR_CAPS proc_caps; + processor_e->GetVideoProcessorCaps(&proc_caps); + if(!(proc_caps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_ALPHA_STREAM)) { + BOOST_LOG(warning) << "VideoProcessorSetStreamAlpha() not supported, hardware accelerated mouse cannot be added to the video stream"sv; + } + video::processor_t::pointer processor_p; status = device->CreateVideoProcessor(processor_e.get(), 0, &processor_p); if(FAILED(status)) { @@ -241,6 +248,9 @@ public: } processor.reset(processor_p); + // Tell video processor alpha values need to be enabled + ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); + D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; t.Height = out_height; @@ -275,9 +285,6 @@ public: } processor_out.reset(processor_out_p); - // Tell video processor alpha values need to be enabled - ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); - device_p->AddRef(); data = device_p; return 0; @@ -373,7 +380,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, &data, &tex_p); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } texture2d_t texture { tex_p }; diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index e69fea96..284f3c83 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -188,7 +188,7 @@ retry: } void button_mouse(input_t &input, int button, bool release) { - constexpr SHORT KEY_STATE_DOWN = 0x8000; + constexpr auto KEY_STATE_DOWN = (SHORT)0x8000; INPUT i {}; @@ -370,7 +370,7 @@ HDESK pairInputDesktop() { } return hDesk; -}; +} void freeInput(void *p) { auto vigem = (vigem_t*)p; From 127b5501d9abc50e03fc1a22be5719968cd3c868 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 30 Apr 2021 20:01:15 +0200 Subject: [PATCH 024/158] Render luma onto nv12 surface --- CMakeLists.txt | 35 +- assets/MergeUVPS.hlsl | 27 + assets/MergeUVVS.hlsl | 23 + assets/ScreenPS.hlsl | 20 + assets/ScreenVS.hlsl | 23 + assets/YCbCrPS.hlsl | 40 ++ sunshine/platform/common.h | 15 + sunshine/platform/windows/display.h | 1 + sunshine/platform/windows/display_vram.cpp | 549 +++++++++++++++++---- 9 files changed, 601 insertions(+), 132 deletions(-) create mode 100644 assets/MergeUVPS.hlsl create mode 100644 assets/MergeUVVS.hlsl create mode 100644 assets/ScreenPS.hlsl create mode 100644 assets/ScreenVS.hlsl create mode 100644 assets/YCbCrPS.hlsl diff --git a/CMakeLists.txt b/CMakeLists.txt index 1347b7d7..55d04342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,39 +4,6 @@ project(Sunshine) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) -# On MSYS2, building a stand-alone binary that links with ffmpeg is not possible, -# Therefore, ffmpeg, libx264 and libx265 must be build from source -if(WIN32) - file( - DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" - TIMEOUT 60 - EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6) - - file(ARCHIVE_EXTRACT - INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - - if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) - set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") - endif() - - set(FFMPEG_INCLUDE_DIRS - ${SUNSHINE_PREPARED_BINARIES}/include) - set(FFMPEG_LIBRARIES - ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a - z lzma bcrypt libiconv.a) -endif() add_subdirectory(Simple-Web-Server) add_subdirectory(moonlight-common-c/enet) @@ -108,7 +75,7 @@ if(WIN32) wsock32 ws2_32 iphlpapi - d3d11 dxgi + d3d11 dxgi D3DCompiler setupapi ) diff --git a/assets/MergeUVPS.hlsl b/assets/MergeUVPS.hlsl new file mode 100644 index 00000000..501425ef --- /dev/null +++ b/assets/MergeUVPS.hlsl @@ -0,0 +1,27 @@ +//-------------------------------------------------------------------------------------- +// CombinedUVMipsPS.hlsl +//-------------------------------------------------------------------------------------- +Texture2D txInputU : register(t0); +Texture2D txInputV : register(t1); +Texture1D txInputShift : register(t2); + +SamplerState GenericSampler : register(s0); + +struct PS_INPUT +{ + float4 Pos : SV_POSITION; + float2 Tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float PS(PS_INPUT input) : SV_Target +{ + float fShift = (float)txInputShift.Sample(GenericSampler, input.Tex.x); + + if(fShift == 0.0f) + return (float)txInputU.SampleLevel(GenericSampler, input.Tex, 1.0f); + else + return (float)txInputV.SampleLevel(GenericSampler, input.Tex, 1.0f); +} \ No newline at end of file diff --git a/assets/MergeUVVS.hlsl b/assets/MergeUVVS.hlsl new file mode 100644 index 00000000..618b0f58 --- /dev/null +++ b/assets/MergeUVVS.hlsl @@ -0,0 +1,23 @@ +//-------------------------------------------------------------------------------------- +// CombinedUVVS.hlsl +//-------------------------------------------------------------------------------------- +struct PS_INPUT +{ + float4 Pos : SV_POSITION; + float2 Tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +PS_INPUT VS(uint vI : SV_VERTEXID) +{ + PS_INPUT output = (PS_INPUT)0; + + float2 texcoord = float2(vI & 1, vI >> 1); + + output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y + 0.0f) * 0.5f, 0.0f, 1.0f); + output.Tex = texcoord; + + return output; +} \ No newline at end of file diff --git a/assets/ScreenPS.hlsl b/assets/ScreenPS.hlsl new file mode 100644 index 00000000..2e354877 --- /dev/null +++ b/assets/ScreenPS.hlsl @@ -0,0 +1,20 @@ +//-------------------------------------------------------------------------------------- +// ScreenPS.hlsl +//-------------------------------------------------------------------------------------- +Texture2D txInput : register(t0); + +SamplerState GenericSampler : register(s0); + +struct PS_INPUT +{ + float4 Pos : SV_POSITION; + float2 Tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float4 PS(PS_INPUT input) : SV_Target +{ + return txInput.Sample(GenericSampler, input.Tex); +} \ No newline at end of file diff --git a/assets/ScreenVS.hlsl b/assets/ScreenVS.hlsl new file mode 100644 index 00000000..c89c5cf9 --- /dev/null +++ b/assets/ScreenVS.hlsl @@ -0,0 +1,23 @@ +//-------------------------------------------------------------------------------------- +// ScreenVS.hlsl +//-------------------------------------------------------------------------------------- +struct PS_INPUT +{ + float4 Pos : SV_POSITION; + float2 Tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +PS_INPUT VS(uint vI : SV_VERTEXID) +{ + PS_INPUT output = (PS_INPUT)0; + + float2 texcoord = float2(vI & 1, vI >> 1); + + output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y - 0.5f) * 2.0f, 0.0f, 1.0f); + output.Tex = texcoord; + + return output; +} \ No newline at end of file diff --git a/assets/YCbCrPS.hlsl b/assets/YCbCrPS.hlsl new file mode 100644 index 00000000..88ca0d21 --- /dev/null +++ b/assets/YCbCrPS.hlsl @@ -0,0 +1,40 @@ +//-------------------------------------------------------------------------------------- +// YCbCrPS2.hlsl +//-------------------------------------------------------------------------------------- +Texture2D txInput : register(t0); + +SamplerState GenericSampler : register(s0); + +struct PS_INPUT +{ + float4 Pos : SV_POSITION; + float2 Tex : TEXCOORD; +}; + +struct PS_OUTPUT +{ + float ColorY : SV_Target0; + float2 ColorU: SV_Target1; + float2 ColorV: SV_Target2; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +PS_OUTPUT PS(PS_INPUT input) : SV_Target +{ + PS_OUTPUT output; + + float4 InputColor = txInput.Sample(GenericSampler, input.Tex); + + // Range 0-255 + output.ColorY = (0.257f * InputColor.r + 0.504f * InputColor.g + 0.098f * InputColor.b) + (16 / 256.0f); + output.ColorU = (-0.148f * InputColor.r - 0.291f * InputColor.g + 0.439f * InputColor.b) + (128.0f / 256.0f); + output.ColorV = (0.439f * InputColor.r - 0.368f * InputColor.g - 0.071f * InputColor.b) + (128.0f / 256.0f); + + output.ColorY = clamp(output.ColorY, 0.0f, 255.0f); + output.ColorU = clamp(output.ColorU, 0.0f, 255.0f); + output.ColorV = clamp(output.ColorV, 0.0f, 255.0f); + + return output; +} \ No newline at end of file diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 140b54b1..fa6b3be7 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -43,6 +43,21 @@ enum class pix_fmt_e { unknown }; +inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) { +using namespace std::literals; +#define _CONVERT(x) case pix_fmt_e:: x : return #x ## sv + switch(pix_fmt) { + _CONVERT(yuv420p); + _CONVERT(yuv420p10); + _CONVERT(nv12); + _CONVERT(p010); + _CONVERT(unknown); + } +#undef _CONVERT + + return "unknown"sv; +} + struct gamepad_state_t { std::uint16_t buttonFlags; std::uint8_t lt; diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 039e6a75..8bc7ae9e 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -32,6 +32,7 @@ using output_t = util::safe_ptr>; using output1_t = util::safe_ptr>; using dup_t = util::safe_ptr>; using texture2d_t = util::safe_ptr>; +using texture1d_t = util::safe_ptr>; using resource_t = util::safe_ptr>; using multithread_t = util::safe_ptr>; diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 0d87b40c..af386886 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -1,3 +1,7 @@ +#include + +#include + #include "sunshine/main.h" #include "display.h" @@ -6,9 +10,29 @@ using namespace std::literals; } namespace platf::dxgi { +constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f }; + +using input_layout_t = util::safe_ptr>; +using render_target_t = util::safe_ptr>; +using shader_res_t = util::safe_ptr>; +using raster_state_t = util::safe_ptr>; +using sampler_state_t = util::safe_ptr>; +using vs_t = util::safe_ptr>; +using ps_t = util::safe_ptr>; +using blob_t = util::safe_ptr>; +using depth_stencil_state_t = util::safe_ptr>; +using depth_stencil_view_t = util::safe_ptr>; + +blob_t merge_UV_vs_hlsl; +blob_t merge_UV_ps_hlsl; +blob_t screen_vs_hlsl; +blob_t screen_ps_hlsl; +blob_t YCrCb_ps_hlsl; + struct img_d3d_t : public platf::img_t { - std::shared_ptr display; + shader_res_t input_res; texture2d_t texture; + std::shared_ptr display; ~img_d3d_t() override = default; }; @@ -97,9 +121,43 @@ util::buffer_t make_cursor_image(util::buffer_t &&im return cursor_img; } +blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { + blob_t::pointer msg_p = nullptr; + blob_t::pointer compiled_p; + + DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; + +#ifndef NDEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + std::wstring_convert, wchar_t> converter; + + auto wFile = converter.from_bytes(file); + auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); + + if(msg_p) { + BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; + msg_p->Release(); + } + + if(status) { + BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blob_t { compiled_p }; +} + +blob_t compile_pixel_shader(LPCSTR file) { + return compile_shader(file, "PS", "ps_5_0"); +} + +blob_t compile_vertex_shader(LPCSTR file) { + return compile_shader(file, "VS", "vs_5_0"); +} + class hwdevice_t : public platf::hwdevice_t { public: - hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} hwdevice_t() = delete; @@ -126,23 +184,9 @@ public: RECT rect_in { left_in, top_in, right_in, bottom_in }; RECT rect_out { left_out, top_out, right_out, bottom_out }; - - ctx->VideoProcessorSetStreamSourceRect(processor.get(), 1, TRUE, &rect_in); - ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect_out); } int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { - D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } }; - - video::processor_in_t::pointer processor_in_p; - auto status = device->CreateVideoProcessorInputView(texture, processor_e.get(), &input_desc, &processor_in_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create cursor VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - cursor_in.reset(processor_in_p); - cursor_width = width; cursor_height = height; cursor_scaled_width = ((double)width) / in_width * out_width; @@ -154,38 +198,75 @@ public: int convert(platf::img_t &img_base) override { auto &img = (img_d3d_t&)img_base; - auto it = texture_to_processor_in.find(img.texture.get()); - if(it == std::end(texture_to_processor_in)) { - D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } }; + if(!img.input_res) { + auto device = (device_t::pointer)data; - video::processor_in_t::pointer processor_in_p; - auto status = device->CreateVideoProcessorInputView(img.texture.get(), processor_e.get(), &input_desc, &processor_in_p); + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + shader_res_t::pointer input_rec_p; + auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &input_rec_p); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - it = texture_to_processor_in.emplace(img.texture.get(), processor_in_p).first; + img.input_res.reset(input_rec_p); } - auto &processor_in = it->second; - D3D11_VIDEO_PROCESSOR_STREAM stream[] { - { TRUE, 0, 0, 0, 0, nullptr, processor_in.get() }, - { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get() } + auto nv12_rt_p = nv12_rt.get(); + auto sampler_point_p = sampler_point.get(); + auto input_res_p = img.input_res.get(); + auto luma_sr_p = luma_sr.get(); + + render_target_t::pointer pYCbCrRT[] { + luma_rt.get(), chromaCB_rt.get(), chromaCR_rt.get() }; - auto status = ctx->VideoProcessorBlt(processor.get(), processor_out.get(), 0, cursor_visible ? 2 : 1, stream); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed size and color conversion [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + shader_res_t::pointer merge_ress[] { + chromaCB_sr.get(), chromaCR_sr.get(), shift_sr.get() + }; + + _init_view_port(out_width, out_height); + device_ctx_p->PSSetSamplers(0, 1, &sampler_point_p); + + device_ctx_p->OMSetRenderTargets(3, pYCbCrRT, nullptr); + for(auto rt : pYCbCrRT) { + device_ctx_p->ClearRenderTargetView(rt, aquamarine); } + device_ctx_p->VSSetShader(screen_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(YCrCb_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); + device_ctx_p->Draw(4, 0); + device_ctx_p->Flush(); + + // downsample + device_ctx_p->GenerateMips(chromaCR_sr.get()); + device_ctx_p->GenerateMips(chromaCB_sr.get()); + + device_ctx_p->OMSetRenderTargets(1, &nv12_rt_p, nullptr); + device_ctx_p->ClearRenderTargetView(nv12_rt_p, aquamarine); + device_ctx_p->VSSetShader(screen_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(screen_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &luma_sr_p); + device_ctx_p->Draw(4, 0); + device_ctx_p->Flush(); + + _init_view_port(out_width, out_height *2); + device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(merge_UV_ps.get(), nullptr, 0); + for(int x = 0; x < ARRAYSIZE(merge_ress); ++x) { + device_ctx_p->PSSetShaderResources(x, 1, &merge_ress[x]); + } + device_ctx_p->Draw(4, 0); + device_ctx_p->Flush(); return 0; } - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - colorspace |= (color_range >> 4); - ctx->VideoProcessorSetOutputColorSpace(processor.get(), (D3D11_VIDEO_PROCESSOR_COLOR_SPACE*)&colorspace); - } + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {} int init( std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, @@ -194,6 +275,11 @@ public: ) { HRESULT status; + device_p->AddRef(); + data = device_p; + + this->device_ctx_p = device_ctx_p; + cursor_visible = false; platf::hwdevice_t::img = &img; @@ -203,53 +289,56 @@ public: this->in_width = in_width; this->in_height = in_height; - video::device_t::pointer vdevice_p; - status = device_p->QueryInterface(IID_ID3D11VideoDevice, (void**)&vdevice_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query ID3D11VideoDevice interface [0x"sv << util::hex(status).to_string_view() << ']'; + vs_t::pointer screen_vs_p; + status = device_p->CreateVertexShader(screen_vs_hlsl->GetBufferPointer(), screen_vs_hlsl->GetBufferSize(), nullptr, &screen_vs_p); + if(status) { + BOOST_LOG(error) << "Failed to create screen vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - device.reset(vdevice_p); + screen_vs.reset(screen_vs_p); - video::ctx_t::pointer ctx_p; - status = device_ctx_p->QueryInterface(IID_ID3D11VideoContext, (void**)&ctx_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query ID3D11VideoContext interface [0x"sv << util::hex(status).to_string_view() << ']'; + ps_t::pointer screen_ps_p; + status = device_p->CreatePixelShader(screen_ps_hlsl->GetBufferPointer(), screen_ps_hlsl->GetBufferSize(), nullptr, &screen_ps_p); + if(status) { + BOOST_LOG(error) << "Failed to create screen pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - ctx.reset(ctx_p); + screen_ps.reset(screen_ps_p); - D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc { - D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, - { 1, 1 }, (UINT)in_width, (UINT)in_height, - { 1, 1 }, (UINT)out_width, (UINT)out_height, - D3D11_VIDEO_USAGE_OPTIMAL_QUALITY + ps_t::pointer YCrCb_ps_p; + status = device_p->CreatePixelShader(YCrCb_ps_hlsl->GetBufferPointer(), YCrCb_ps_hlsl->GetBufferSize(), nullptr, &YCrCb_ps_p); + if(status) { + BOOST_LOG(error) << "Failed to create YCrCb pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + YCrCb_ps.reset(YCrCb_ps_p); + + ps_t::pointer merge_UV_ps_p; + status = device_p->CreatePixelShader(merge_UV_ps_hlsl->GetBufferPointer(), merge_UV_ps_hlsl->GetBufferSize(), nullptr, &merge_UV_ps_p); + if(status) { + BOOST_LOG(error) << "Failed to create mergeUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + merge_UV_ps.reset(merge_UV_ps_p); + + vs_t::pointer merge_UV_vs_p; + status = device_p->CreateVertexShader(merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), nullptr, &merge_UV_vs_p); + if(status) { + BOOST_LOG(error) << "Failed to create mergeUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + merge_UV_vs.reset(merge_UV_vs_p); + + D3D11_INPUT_ELEMENT_DESC layout_desc { + "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }; - video::processor_enum_t::pointer vp_e_p; - status = device->CreateVideoProcessorEnumerator(&contentDesc, &vp_e_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create video processor enumerator [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - processor_e.reset(vp_e_p); - - D3D11_VIDEO_PROCESSOR_CAPS proc_caps; - processor_e->GetVideoProcessorCaps(&proc_caps); - if(!(proc_caps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_ALPHA_STREAM)) { - BOOST_LOG(warning) << "VideoProcessorSetStreamAlpha() not supported, hardware accelerated mouse cannot be added to the video stream"sv; - } - - video::processor_t::pointer processor_p; - status = device->CreateVideoProcessor(processor_e.get(), 0, &processor_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create video processor [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - processor.reset(processor_p); - - // Tell video processor alpha values need to be enabled - ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); + input_layout_t::pointer input_layout_p; + status = device_p->CreateInputLayout( + &layout_desc, 1, + merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), + &input_layout_p); + input_layout.reset(input_layout_p); D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; @@ -259,12 +348,12 @@ public: t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_DEFAULT; t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010; - t.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_VIDEO_ENCODER; + t.BindFlags = D3D11_BIND_RENDER_TARGET; dxgi::texture2d_t::pointer tex_p {}; status = device_p->CreateTexture2D(&t, nullptr, &tex_p); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create video output texture [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } @@ -276,17 +365,118 @@ public: img.row_pitch = out_width; img.pixel_pitch = 1; - D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc { D3D11_VPOV_DIMENSION_TEXTURE2D, 0 }; - video::processor_out_t::pointer processor_out_p; - status = device->CreateVideoProcessorOutputView(img.texture.get(), processor_e.get(), &output_desc, &processor_out_p); + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + render_target_t::pointer nv12_rt_p; + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_rt_p); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create VideoProcessorOutputView [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - processor_out.reset(processor_out_p); + nv12_rt.reset(nv12_rt_p); + + if( + _init_rt(&luma_sr, &luma_rt, out_width, out_height, 1, DXGI_FORMAT_R8_UNORM) || + _init_rt(&chromaCB_sr, &chromaCB_rt, out_width, out_height, 2, DXGI_FORMAT_R8_UNORM, D3D11_RESOURCE_MISC_GENERATE_MIPS) || + _init_rt(&chromaCR_sr, &chromaCR_rt, out_width, out_height, 2, DXGI_FORMAT_R8_UNORM, D3D11_RESOURCE_MISC_GENERATE_MIPS) || + _init_shift_sr(out_width)) + { + return -1; + } + + // t.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + // t.BindFlags = D3D11_BIND_DEPTH_STENCIL; + // status = device_p->CreateTexture2D(&t, nullptr, &tex_p); + // if(FAILED(status)) { + // BOOST_LOG(error) << "Failed to create depth stencil texture [0x"sv << util::hex(status).to_string_view() << ']'; + // return -1; + // } + // depth_stencil.reset(tex_p); + + D3D11_SAMPLER_DESC sampler_desc {}; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + sampler_state_t::pointer sampler_state_p; + status = device_p->CreateSamplerState(&sampler_desc, &sampler_state_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + sampler_point.reset(sampler_state_p); + + // D3D11_DEPTH_STENCIL_DESC depth_stencil_desc {}; + // depth_stencil_desc.DepthEnable = FALSE; + // depth_stencil_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + // depth_stencil_desc.StencilEnable = true; + // depth_stencil_desc.StencilReadMask = 0xFF; + // depth_stencil_desc.StencilWriteMask = 0xFF; + + // depth_stencil_desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + // depth_stencil_desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; + // depth_stencil_desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + // depth_stencil_desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // depth_stencil_desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + // depth_stencil_desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; + // depth_stencil_desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + // depth_stencil_desc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // depth_stencil_state_t::pointer depth_state_p; + // status = device_p->CreateDepthStencilState(&depth_stencil_desc, &depth_state_p); + // if(FAILED(status)) { + // BOOST_LOG(error) << "Failed to create depth stencil state [0x"sv << util::hex(status).to_string_view() << ']'; + // return -1; + // } + // depth_state.reset(depth_state_p); + + // D3D11_DEPTH_STENCIL_VIEW_DESC depth_view_desc {}; + // depth_view_desc.Format = t.Format; + // depth_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + + // depth_stencil_view_t::pointer depth_view_p; + // status = device_p->CreateDepthStencilView(depth_stencil.get(), &depth_view_desc, &depth_view_p); + // if(FAILED(status)) { + // BOOST_LOG(error) << "Failed to create depth stencil view [0x"sv << util::hex(status).to_string_view() << ']'; + // return -1; + // } + // depth_view.reset(depth_view_p); + + // // Setup the raster description which will determine how and what polygons will be drawn. + // D3D11_RASTERIZER_DESC raster_desc; + // raster_desc.AntialiasedLineEnable = false; + // raster_desc.CullMode = D3D11_CULL_BACK; + // raster_desc.DepthBias = 0; + // raster_desc.DepthBiasClamp = 0.0f; + // raster_desc.DepthClipEnable = true; + // raster_desc.FillMode = D3D11_FILL_SOLID; + // raster_desc.FrontCounterClockwise = false; + // raster_desc.MultisampleEnable = false; + // raster_desc.ScissorEnable = false; + // raster_desc.SlopeScaledDepthBias = 0.0f; + + // raster_state_t::pointer raster_state_p; + // status = device_p->CreateRasterizerState(&raster_desc, &raster_state_p); + // if(FAILED(status)) { + // BOOST_LOG(error) << "Failed to create rasterizer state [0x"sv << util::hex(status).to_string_view() << ']'; + // return -1; + // } + // raster_state.reset(raster_state_p); + + auto sampler_p = sampler_point.get(); + device_ctx_p->PSSetSamplers(0, 1, &sampler_p); + // device_ctx_p->RSSetState(raster_state.get()); + device_ctx_p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + device_ctx_p->IASetInputLayout(input_layout.get()); - device_p->AddRef(); - data = device_p; return 0; } @@ -300,16 +490,148 @@ public: hwdevices_p->erase(it); } } +private: + void _init_view_port(float width, float height) { + D3D11_VIEWPORT view { + 0.0f, 0.0f, + width, height, + 0.0f, 1.0f + }; + + device_ctx_p->RSSetViewports(1, &view); + } + + int _init_rt(shader_res_t *shader_res, render_target_t *render_target, int width, int height, int mip_levels, DXGI_FORMAT format, int flags = 0) { + D3D11_TEXTURE2D_DESC desc {}; + + desc.Width = width; + desc.Height = height; + desc.Format = format; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.MipLevels = mip_levels; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.MiscFlags = flags; + + auto device = (device_t::pointer)data; + + texture2d_t::pointer tex_p; + auto status = device->CreateTexture2D(&desc, nullptr, &tex_p); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + texture2d_t tex { tex_p }; + + if(shader_res) { + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + shader_resource_desc.Texture2D.MipLevels = mip_levels; + + shader_res_t::pointer shader_res_p; + device->CreateShaderResourceView(tex_p, &shader_resource_desc, &shader_res_p); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + shader_res->reset(shader_res_p); + } + + if(render_target) { + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + render_target_t::pointer render_target_p; + device->CreateRenderTargetView(tex_p, &render_target_desc, &render_target_p); + if(status) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + render_target->reset(render_target_p); + } + + return 0; + } + + int _init_shift_sr(int width) { + auto device = (device_t::pointer)data; + D3D11_TEXTURE1D_DESC desc {}; + desc.Width = width; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_R8_UNORM; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + util::buffer_t data { (std::size_t)width }; + for(int x = 0; x < data.size(); ++x) { + data[x] = x & 1; + } + + D3D11_SUBRESOURCE_DATA data_res { + std::begin(data), + (UINT)data.size() + }; + + texture1d_t::pointer tex_p {}; + auto status = device->CreateTexture1D(&desc, &data_res, &tex_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create shift texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + texture1d_t tex { tex_p }; + + D3D11_SHADER_RESOURCE_VIEW_DESC res_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE1D + }; + res_desc.Texture1D.MipLevels = 1; + + shader_res_t::pointer shader_res_p; + device->CreateShaderResourceView(tex_p, &res_desc, &shader_res_p); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + shift_sr.reset(shader_res_p); + + return 0; + } + +public: + // raster_state_t raster_state; + + sampler_state_t sampler_point; + + // depth_stencil_view_t depth_view; + // depth_stencil_state_t depth_state; + + shader_res_t chromaCB_sr; + shader_res_t chromaCR_sr; + shader_res_t luma_sr; + shader_res_t shift_sr; + + input_layout_t input_layout; + // texture2d_t depth_stencil; + + render_target_t luma_rt; + render_target_t nv12_rt; + render_target_t chromaCB_rt; + render_target_t chromaCR_rt; img_d3d_t img; - video::device_t device; - video::ctx_t ctx; - video::processor_enum_t processor_e; - video::processor_t processor; - video::processor_out_t processor_out; - std::unordered_map texture_to_processor_in; - video::processor_in_t cursor_in; + vs_t merge_UV_vs; + ps_t merge_UV_ps; + vs_t screen_vs; + ps_t screen_ps; + ps_t YCrCb_ps; + ps_t ChromaCbCr_ps; bool cursor_visible; @@ -319,6 +641,8 @@ public: LONG in_width, in_height; double out_width, out_height; + device_ctx_t::pointer device_ctx_p; + std::vector *hwdevices_p; }; @@ -429,7 +753,7 @@ std::shared_ptr display_vram_t::alloc_img() { t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_DEFAULT; t.Format = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, nullptr, &tex_p); @@ -438,12 +762,12 @@ std::shared_ptr display_vram_t::alloc_img() { return nullptr; } + img->texture.reset(tex_p); img->data = (std::uint8_t*)tex_p; img->row_pitch = 0; img->pixel_pitch = 4; img->width = 0; img->height = 0; - img->texture.reset(tex_p); img->display = shared_from_this(); return img; @@ -456,8 +780,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { auto dummy_data = std::make_unique(width * height); D3D11_SUBRESOURCE_DATA data { dummy_data.get(), - (UINT)img->row_pitch, - 0 + (UINT)img->row_pitch }; D3D11_TEXTURE2D_DESC t {}; @@ -468,7 +791,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_DEFAULT; t.Format = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, &data, &tex_p); @@ -477,8 +800,8 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { return -1; } - img->data = (std::uint8_t*)tex_p; img->texture.reset(tex_p); + img->data = (std::uint8_t*)tex_p; img->height = height; img->width = width; img->pixel_pitch = 4; @@ -487,12 +810,42 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { } std::shared_ptr display_vram_t::make_hwdevice(int width, int height, pix_fmt_e pix_fmt) { - if(pix_fmt != platf::pix_fmt_e::nv12 && pix_fmt != platf::pix_fmt_e::p010) { - BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << (int)pix_fmt << ']'; + if(pix_fmt != platf::pix_fmt_e::nv12) { + BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; return nullptr; } + if(!screen_ps_hlsl) { + BOOST_LOG(info) << "Compiling shaders..."sv; + screen_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/ScreenVS.hlsl"); + if(!screen_vs_hlsl) { + return nullptr; + } + + screen_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/ScreenPS.hlsl"); + if(!screen_ps_hlsl) { + return nullptr; + } + + YCrCb_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/YCbCrPS.hlsl"); + if(!YCrCb_ps_hlsl) { + return nullptr; + } + + merge_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeUVPS.hlsl"); + if(!merge_UV_ps_hlsl) { + return nullptr; + } + + merge_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeUVVS.hlsl"); + if(!merge_UV_vs_hlsl) { + return nullptr; + } + + BOOST_LOG(info) << "Compiled shaders"sv; + } + auto hwdevice = std::make_shared(&hwdevices); auto ret = hwdevice->init( From 37a925658724114e8398192364a895dbce969524 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 2 May 2021 22:35:19 +0200 Subject: [PATCH 025/158] Render NV12 color format --- assets/MergeUVPS.hlsl | 39 ++- assets/MergeUVVS.hlsl | 29 +- assets/MergeYPS.hlsl | 26 ++ assets/MergeYVS.hlsl | 22 ++ assets/ScreenPS.hlsl | 20 -- assets/ScreenVS.hlsl | 23 -- assets/YCbCrPS.hlsl | 40 --- sunshine/main.cpp | 4 + sunshine/platform/linux/input.cpp | 2 +- sunshine/platform/windows/audio.cpp | 9 + sunshine/platform/windows/display_vram.cpp | 374 +++++++-------------- sunshine/video.cpp | 3 + 12 files changed, 220 insertions(+), 371 deletions(-) create mode 100644 assets/MergeYPS.hlsl create mode 100644 assets/MergeYVS.hlsl delete mode 100644 assets/ScreenPS.hlsl delete mode 100644 assets/ScreenVS.hlsl delete mode 100644 assets/YCbCrPS.hlsl diff --git a/assets/MergeUVPS.hlsl b/assets/MergeUVPS.hlsl index 501425ef..597c02d8 100644 --- a/assets/MergeUVPS.hlsl +++ b/assets/MergeUVPS.hlsl @@ -1,27 +1,32 @@ -//-------------------------------------------------------------------------------------- -// CombinedUVMipsPS.hlsl -//-------------------------------------------------------------------------------------- -Texture2D txInputU : register(t0); -Texture2D txInputV : register(t1); -Texture1D txInputShift : register(t2); +Texture2D image : register(t0); -SamplerState GenericSampler : register(s0); +SamplerState def_sampler : register(s0); -struct PS_INPUT -{ - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD; +struct FragTexWide { + float3 uuv : TEXCOORD0; }; //-------------------------------------------------------------------------------------- // Pixel Shader //-------------------------------------------------------------------------------------- -float PS(PS_INPUT input) : SV_Target +float2 PS(FragTexWide input) : SV_Target { - float fShift = (float)txInputShift.Sample(GenericSampler, input.Tex.x); + // float4 color_vec_y = { 0.301f, 0.586f, 0.113f, 0.0f }; + // float4 color_vec_u = { -0.168f, -0.328f, 0.496f, 128.0f / 256.0f }; + // float4 color_vec_v = { 0.496f, 0.414f, 0.082f, 128.0f / 256.0f }; + float4 color_vec_y = { 0.299, 0.587, 0.114, 0.0625 }; + float4 color_vec_u = { -0.168736, -0.331264, 0.5, 0.5 }; + float4 color_vec_v = { 0.5, -0.418688, -0.081312, 0.5 }; - if(fShift == 0.0f) - return (float)txInputU.SampleLevel(GenericSampler, input.Tex, 1.0f); - else - return (float)txInputV.SampleLevel(GenericSampler, input.Tex, 1.0f); + // float4 color_vec_y = { 0.2578f, 0.5039f, 0.0977, 0.0625 }; + // float4 color_vec_u = { -0.1484, 0.2891, 0.4375, 128.0f / 256.0f }; + // float4 color_vec_v = { 0.4375, -0.3672, -0.0703, 128.0f / 256.0f }; + + float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb; + float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb; + float3 rgb = (rgb_left + rgb_right) * 0.5; + + float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; + float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + return float2(u, v); } \ No newline at end of file diff --git a/assets/MergeUVVS.hlsl b/assets/MergeUVVS.hlsl index 618b0f58..0e0b1249 100644 --- a/assets/MergeUVVS.hlsl +++ b/assets/MergeUVVS.hlsl @@ -1,23 +1,26 @@ -//-------------------------------------------------------------------------------------- -// CombinedUVVS.hlsl -//-------------------------------------------------------------------------------------- -struct PS_INPUT -{ - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD; +struct VertTexPosWide { + float3 uuv : TEXCOORD; + float4 pos : SV_POSITION; }; //-------------------------------------------------------------------------------------- // Vertex Shader //-------------------------------------------------------------------------------------- -PS_INPUT VS(uint vI : SV_VERTEXID) +VertTexPosWide VS(uint vI : SV_VERTEXID) { - PS_INPUT output = (PS_INPUT)0; + float width_i = 1.0f / 1920.0f; + float idHigh = float(vI >> 1); + float idLow = float(vI & uint(1)); - float2 texcoord = float2(vI & 1, vI >> 1); + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; - output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y + 0.0f) * 0.5f, 0.0f, 1.0f); - output.Tex = texcoord; + float u_right = idHigh * 2.0; + float u_left = u_right - width_i; + float v = 1.0 - idLow * 2.0; - return output; + VertTexPosWide vert_out; + vert_out.uuv = float3(u_left, u_right, v); + vert_out.pos = float4(x, y, 0.0, 1.0); + return vert_out; } \ No newline at end of file diff --git a/assets/MergeYPS.hlsl b/assets/MergeYPS.hlsl new file mode 100644 index 00000000..146d517c --- /dev/null +++ b/assets/MergeYPS.hlsl @@ -0,0 +1,26 @@ +//-------------------------------------------------------------------------------------- +// YCbCrPS2.hlsl +//-------------------------------------------------------------------------------------- +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float PS(PS_INPUT frag_in) : SV_Target +{ + float4 color_vec_y = { 0.299, 0.587, 0.114, 0.0625 }; + float4 color_vec_u = { -0.168736, -0.331264, 0.5, 0.5 }; + float4 color_vec_v = { 0.5, -0.418688, -0.081312, 0.5 }; + + float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb; + float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; + return y; +} \ No newline at end of file diff --git a/assets/MergeYVS.hlsl b/assets/MergeYVS.hlsl new file mode 100644 index 00000000..d95ed7b4 --- /dev/null +++ b/assets/MergeYVS.hlsl @@ -0,0 +1,22 @@ +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +PS_INPUT VS(uint vI : SV_VERTEXID) +{ + float idHigh = float(vI >> 1); + float idLow = float(vI & uint(1)); + + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; + + PS_INPUT vert_out; + vert_out.pos = float4(x, y, 0.0, 1.0); + vert_out.tex = float2(idHigh, idLow); + return vert_out; +} \ No newline at end of file diff --git a/assets/ScreenPS.hlsl b/assets/ScreenPS.hlsl deleted file mode 100644 index 2e354877..00000000 --- a/assets/ScreenPS.hlsl +++ /dev/null @@ -1,20 +0,0 @@ -//-------------------------------------------------------------------------------------- -// ScreenPS.hlsl -//-------------------------------------------------------------------------------------- -Texture2D txInput : register(t0); - -SamplerState GenericSampler : register(s0); - -struct PS_INPUT -{ - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD; -}; - -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- -float4 PS(PS_INPUT input) : SV_Target -{ - return txInput.Sample(GenericSampler, input.Tex); -} \ No newline at end of file diff --git a/assets/ScreenVS.hlsl b/assets/ScreenVS.hlsl deleted file mode 100644 index c89c5cf9..00000000 --- a/assets/ScreenVS.hlsl +++ /dev/null @@ -1,23 +0,0 @@ -//-------------------------------------------------------------------------------------- -// ScreenVS.hlsl -//-------------------------------------------------------------------------------------- -struct PS_INPUT -{ - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD; -}; - -//-------------------------------------------------------------------------------------- -// Vertex Shader -//-------------------------------------------------------------------------------------- -PS_INPUT VS(uint vI : SV_VERTEXID) -{ - PS_INPUT output = (PS_INPUT)0; - - float2 texcoord = float2(vI & 1, vI >> 1); - - output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y - 0.5f) * 2.0f, 0.0f, 1.0f); - output.Tex = texcoord; - - return output; -} \ No newline at end of file diff --git a/assets/YCbCrPS.hlsl b/assets/YCbCrPS.hlsl deleted file mode 100644 index 88ca0d21..00000000 --- a/assets/YCbCrPS.hlsl +++ /dev/null @@ -1,40 +0,0 @@ -//-------------------------------------------------------------------------------------- -// YCbCrPS2.hlsl -//-------------------------------------------------------------------------------------- -Texture2D txInput : register(t0); - -SamplerState GenericSampler : register(s0); - -struct PS_INPUT -{ - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD; -}; - -struct PS_OUTPUT -{ - float ColorY : SV_Target0; - float2 ColorU: SV_Target1; - float2 ColorV: SV_Target2; -}; - -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- -PS_OUTPUT PS(PS_INPUT input) : SV_Target -{ - PS_OUTPUT output; - - float4 InputColor = txInput.Sample(GenericSampler, input.Tex); - - // Range 0-255 - output.ColorY = (0.257f * InputColor.r + 0.504f * InputColor.g + 0.098f * InputColor.b) + (16 / 256.0f); - output.ColorU = (-0.148f * InputColor.r - 0.291f * InputColor.g + 0.439f * InputColor.b) + (128.0f / 256.0f); - output.ColorV = (0.439f * InputColor.r - 0.368f * InputColor.g - 0.071f * InputColor.b) + (128.0f / 256.0f); - - output.ColorY = clamp(output.ColorY, 0.0f, 255.0f); - output.ColorU = clamp(output.ColorU, 0.0f, 255.0f); - output.ColorV = clamp(output.ColorV, 0.0f, 255.0f); - - return output; -} \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c21f81fc..a24b562e 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -138,6 +138,10 @@ int main(int argc, char *argv[]) { proc::proc = std::move(*proc_opt); auto deinit_guard = platf::init(); + if(!deinit_guard) { + return 4; + } + input::init(); reed_solomon_init(); if(video::init()) { diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index f5696522..6bb5d495 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -502,5 +502,5 @@ void freeInput(void *p) { delete input; } -std::unique_ptr init() { return nullptr; } +std::unique_ptr init() { return std::make_unique(); } } diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index a71e4a77..0ba9a168 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -319,6 +319,12 @@ public: } namespace platf { + +// It's not big enough to justify it's own source file :/ +namespace dxgi { +int init(); +} + std::unique_ptr microphone(std::uint32_t sample_rate) { auto mic = std::make_unique(); @@ -330,6 +336,9 @@ std::unique_ptr microphone(std::uint32_t sample_rate) { } std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } return std::make_unique(); } } diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index af386886..29b938e4 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -15,6 +15,7 @@ constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.00000 using input_layout_t = util::safe_ptr>; using render_target_t = util::safe_ptr>; using shader_res_t = util::safe_ptr>; +using blend_t = util::safe_ptr>; using raster_state_t = util::safe_ptr>; using sampler_state_t = util::safe_ptr>; using vs_t = util::safe_ptr>; @@ -25,9 +26,8 @@ using depth_stencil_view_t = util::safe_ptrOMSetBlendState(blend.get(), nullptr, 0xffffffff); _init_view_port(out_width, out_height); - device_ctx_p->PSSetSamplers(0, 1, &sampler_point_p); + device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); - device_ctx_p->OMSetRenderTargets(3, pYCbCrRT, nullptr); - for(auto rt : pYCbCrRT) { - device_ctx_p->ClearRenderTargetView(rt, aquamarine); - } - device_ctx_p->VSSetShader(screen_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(YCrCb_ps.get(), nullptr, 0); + device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); + device_ctx_p->ClearRenderTargetView(Y_rt_p, aquamarine); + device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(merge_Y_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(4, 0); device_ctx_p->Flush(); - // downsample - device_ctx_p->GenerateMips(chromaCR_sr.get()); - device_ctx_p->GenerateMips(chromaCB_sr.get()); - - device_ctx_p->OMSetRenderTargets(1, &nv12_rt_p, nullptr); - device_ctx_p->ClearRenderTargetView(nv12_rt_p, aquamarine); - device_ctx_p->VSSetShader(screen_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(screen_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &luma_sr_p); - device_ctx_p->Draw(4, 0); - device_ctx_p->Flush(); - - _init_view_port(out_width, out_height *2); + _init_view_port(out_width / 2, out_height / 2); + device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); + device_ctx_p->ClearRenderTargetView(UV_rt_p, aquamarine); device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(merge_UV_ps.get(), nullptr, 0); - for(int x = 0; x < ARRAYSIZE(merge_ress); ++x) { - device_ctx_p->PSSetShaderResources(x, 1, &merge_ress[x]); - } + device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(4, 0); device_ctx_p->Flush(); @@ -289,45 +269,35 @@ public: this->in_width = in_width; this->in_height = in_height; - vs_t::pointer screen_vs_p; - status = device_p->CreateVertexShader(screen_vs_hlsl->GetBufferPointer(), screen_vs_hlsl->GetBufferSize(), nullptr, &screen_vs_p); + vs_t::pointer vs_p; + status = device_p->CreateVertexShader(merge_Y_vs_hlsl->GetBufferPointer(), merge_Y_vs_hlsl->GetBufferSize(), nullptr, &vs_p); if(status) { BOOST_LOG(error) << "Failed to create screen vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - screen_vs.reset(screen_vs_p); + merge_Y_vs.reset(vs_p); - ps_t::pointer screen_ps_p; - status = device_p->CreatePixelShader(screen_ps_hlsl->GetBufferPointer(), screen_ps_hlsl->GetBufferSize(), nullptr, &screen_ps_p); - if(status) { - BOOST_LOG(error) << "Failed to create screen pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - screen_ps.reset(screen_ps_p); - - ps_t::pointer YCrCb_ps_p; - status = device_p->CreatePixelShader(YCrCb_ps_hlsl->GetBufferPointer(), YCrCb_ps_hlsl->GetBufferSize(), nullptr, &YCrCb_ps_p); + ps_t::pointer ps_p; + status = device_p->CreatePixelShader(merge_Y_ps_hlsl->GetBufferPointer(), merge_Y_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { BOOST_LOG(error) << "Failed to create YCrCb pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - YCrCb_ps.reset(YCrCb_ps_p); + merge_Y_ps.reset(ps_p); - ps_t::pointer merge_UV_ps_p; - status = device_p->CreatePixelShader(merge_UV_ps_hlsl->GetBufferPointer(), merge_UV_ps_hlsl->GetBufferSize(), nullptr, &merge_UV_ps_p); + status = device_p->CreatePixelShader(merge_UV_ps_hlsl->GetBufferPointer(), merge_UV_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { BOOST_LOG(error) << "Failed to create mergeUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_UV_ps.reset(merge_UV_ps_p); + merge_UV_ps.reset(ps_p); - vs_t::pointer merge_UV_vs_p; - status = device_p->CreateVertexShader(merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), nullptr, &merge_UV_vs_p); + status = device_p->CreateVertexShader(merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), nullptr, &vs_p); if(status) { BOOST_LOG(error) << "Failed to create mergeUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_UV_vs.reset(merge_UV_vs_p); + merge_UV_vs.reset(vs_p); D3D11_INPUT_ELEMENT_DESC layout_desc { "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 @@ -340,6 +310,20 @@ public: &input_layout_p); input_layout.reset(input_layout_p); + D3D11_BLEND_DESC blend_desc {}; + blend_desc.RenderTarget[0].BlendEnable = FALSE; + blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + blend_t::pointer blend_p; + status = device_p->CreateBlendState(&blend_desc, &blend_p); + if(status) { + BOOST_LOG(error) << "Failed to create blend state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + blend.reset(blend_p); + D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; t.Height = out_height; @@ -376,30 +360,20 @@ public: BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - nv12_rt.reset(nv12_rt_p); + nv12_Y_rt.reset(nv12_rt_p); - if( - _init_rt(&luma_sr, &luma_rt, out_width, out_height, 1, DXGI_FORMAT_R8_UNORM) || - _init_rt(&chromaCB_sr, &chromaCB_rt, out_width, out_height, 2, DXGI_FORMAT_R8_UNORM, D3D11_RESOURCE_MISC_GENERATE_MIPS) || - _init_rt(&chromaCR_sr, &chromaCR_rt, out_width, out_height, 2, DXGI_FORMAT_R8_UNORM, D3D11_RESOURCE_MISC_GENERATE_MIPS) || - _init_shift_sr(out_width)) - { + nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_rt_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - - // t.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - // t.BindFlags = D3D11_BIND_DEPTH_STENCIL; - // status = device_p->CreateTexture2D(&t, nullptr, &tex_p); - // if(FAILED(status)) { - // BOOST_LOG(error) << "Failed to create depth stencil texture [0x"sv << util::hex(status).to_string_view() << ']'; - // return -1; - // } - // depth_stencil.reset(tex_p); + nv12_UV_rt.reset(nv12_rt_p); D3D11_SAMPLER_DESC sampler_desc {}; - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; - sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; sampler_desc.MinLOD = 0; @@ -411,69 +385,16 @@ public: BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } + sampler_linear.reset(sampler_state_p); + + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + status = device_p->CreateSamplerState(&sampler_desc, &sampler_state_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } sampler_point.reset(sampler_state_p); - // D3D11_DEPTH_STENCIL_DESC depth_stencil_desc {}; - // depth_stencil_desc.DepthEnable = FALSE; - // depth_stencil_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; - // depth_stencil_desc.StencilEnable = true; - // depth_stencil_desc.StencilReadMask = 0xFF; - // depth_stencil_desc.StencilWriteMask = 0xFF; - - // depth_stencil_desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; - // depth_stencil_desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; - // depth_stencil_desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; - // depth_stencil_desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; - - // depth_stencil_desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; - // depth_stencil_desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; - // depth_stencil_desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; - // depth_stencil_desc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; - - // depth_stencil_state_t::pointer depth_state_p; - // status = device_p->CreateDepthStencilState(&depth_stencil_desc, &depth_state_p); - // if(FAILED(status)) { - // BOOST_LOG(error) << "Failed to create depth stencil state [0x"sv << util::hex(status).to_string_view() << ']'; - // return -1; - // } - // depth_state.reset(depth_state_p); - - // D3D11_DEPTH_STENCIL_VIEW_DESC depth_view_desc {}; - // depth_view_desc.Format = t.Format; - // depth_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; - - // depth_stencil_view_t::pointer depth_view_p; - // status = device_p->CreateDepthStencilView(depth_stencil.get(), &depth_view_desc, &depth_view_p); - // if(FAILED(status)) { - // BOOST_LOG(error) << "Failed to create depth stencil view [0x"sv << util::hex(status).to_string_view() << ']'; - // return -1; - // } - // depth_view.reset(depth_view_p); - - // // Setup the raster description which will determine how and what polygons will be drawn. - // D3D11_RASTERIZER_DESC raster_desc; - // raster_desc.AntialiasedLineEnable = false; - // raster_desc.CullMode = D3D11_CULL_BACK; - // raster_desc.DepthBias = 0; - // raster_desc.DepthBiasClamp = 0.0f; - // raster_desc.DepthClipEnable = true; - // raster_desc.FillMode = D3D11_FILL_SOLID; - // raster_desc.FrontCounterClockwise = false; - // raster_desc.MultisampleEnable = false; - // raster_desc.ScissorEnable = false; - // raster_desc.SlopeScaledDepthBias = 0.0f; - - // raster_state_t::pointer raster_state_p; - // status = device_p->CreateRasterizerState(&raster_desc, &raster_state_p); - // if(FAILED(status)) { - // BOOST_LOG(error) << "Failed to create rasterizer state [0x"sv << util::hex(status).to_string_view() << ']'; - // return -1; - // } - // raster_state.reset(raster_state_p); - - auto sampler_p = sampler_point.get(); - device_ctx_p->PSSetSamplers(0, 1, &sampler_p); - // device_ctx_p->RSSetState(raster_state.get()); device_ctx_p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); device_ctx_p->IASetInputLayout(input_layout.get()); @@ -491,9 +412,9 @@ public: } } private: - void _init_view_port(float width, float height) { + void _init_view_port(float x, float y, float width, float height) { D3D11_VIEWPORT view { - 0.0f, 0.0f, + x, y, width, height, 0.0f, 1.0f }; @@ -501,7 +422,11 @@ private: device_ctx_p->RSSetViewports(1, &view); } - int _init_rt(shader_res_t *shader_res, render_target_t *render_target, int width, int height, int mip_levels, DXGI_FORMAT format, int flags = 0) { + void _init_view_port(float width, float height) { + _init_view_port(0.0f, 0.0f, width, height); + } + + int _init_rt(shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { D3D11_TEXTURE2D_DESC desc {}; desc.Width = width; @@ -509,10 +434,9 @@ private: desc.Format = format; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - desc.MipLevels = mip_levels; + desc.MipLevels = 1; desc.ArraySize = 1; desc.SampleDesc.Count = 1; - desc.MiscFlags = flags; auto device = (device_t::pointer)data; @@ -524,114 +448,54 @@ private: } texture2d_t tex { tex_p }; - if(shader_res) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = mip_levels; - shader_res_t::pointer shader_res_p; - device->CreateShaderResourceView(tex_p, &shader_resource_desc, &shader_res_p); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - shader_res->reset(shader_res_p); - } - - if(render_target) { - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - render_target_t::pointer render_target_p; - device->CreateRenderTargetView(tex_p, &render_target_desc, &render_target_p); - if(status) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - render_target->reset(render_target_p); - } - - return 0; - } - - int _init_shift_sr(int width) { - auto device = (device_t::pointer)data; - D3D11_TEXTURE1D_DESC desc {}; - desc.Width = width; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_R8_UNORM; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - util::buffer_t data { (std::size_t)width }; - for(int x = 0; x < data.size(); ++x) { - data[x] = x & 1; - } - - D3D11_SUBRESOURCE_DATA data_res { - std::begin(data), - (UINT)data.size() + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D }; - - texture1d_t::pointer tex_p {}; - auto status = device->CreateTexture1D(&desc, &data_res, &tex_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create shift texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - texture1d_t tex { tex_p }; - - D3D11_SHADER_RESOURCE_VIEW_DESC res_desc { - DXGI_FORMAT_R8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE1D - }; - res_desc.Texture1D.MipLevels = 1; + shader_resource_desc.Texture2D.MipLevels = 1; shader_res_t::pointer shader_res_p; - device->CreateShaderResourceView(tex_p, &res_desc, &shader_res_p); + device->CreateShaderResourceView(tex_p, &shader_resource_desc, &shader_res_p); if(status) { BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - shift_sr.reset(shader_res_p); + shader_res.reset(shader_res_p); + + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + render_target_t::pointer render_target_p; + device->CreateRenderTargetView(tex_p, &render_target_desc, &render_target_p); + if(status) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + render_target.reset(render_target_p); return 0; } public: - // raster_state_t raster_state; - + sampler_state_t sampler_linear; sampler_state_t sampler_point; - // depth_stencil_view_t depth_view; - // depth_stencil_state_t depth_state; - - shader_res_t chromaCB_sr; - shader_res_t chromaCR_sr; - shader_res_t luma_sr; - shader_res_t shift_sr; - input_layout_t input_layout; - // texture2d_t depth_stencil; - render_target_t luma_rt; - render_target_t nv12_rt; - render_target_t chromaCB_rt; - render_target_t chromaCR_rt; + render_target_t nv12_Y_rt; + render_target_t nv12_UV_rt; + + blend_t blend; img_d3d_t img; vs_t merge_UV_vs; ps_t merge_UV_ps; - vs_t screen_vs; - ps_t screen_ps; - ps_t YCrCb_ps; - ps_t ChromaCbCr_ps; + vs_t merge_Y_vs; + ps_t merge_Y_ps; bool cursor_visible; @@ -816,36 +680,6 @@ std::shared_ptr display_vram_t::make_hwdevice(int width, int return nullptr; } - if(!screen_ps_hlsl) { - BOOST_LOG(info) << "Compiling shaders..."sv; - screen_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/ScreenVS.hlsl"); - if(!screen_vs_hlsl) { - return nullptr; - } - - screen_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/ScreenPS.hlsl"); - if(!screen_ps_hlsl) { - return nullptr; - } - - YCrCb_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/YCbCrPS.hlsl"); - if(!YCrCb_ps_hlsl) { - return nullptr; - } - - merge_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeUVPS.hlsl"); - if(!merge_UV_ps_hlsl) { - return nullptr; - } - - merge_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeUVVS.hlsl"); - if(!merge_UV_vs_hlsl) { - return nullptr; - } - - BOOST_LOG(info) << "Compiled shaders"sv; - } - auto hwdevice = std::make_shared(&hwdevices); auto ret = hwdevice->init( @@ -868,4 +702,30 @@ std::shared_ptr display_vram_t::make_hwdevice(int width, int return hwdevice; } + +int init() { + BOOST_LOG(info) << "Compiling shaders..."sv; + merge_Y_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeYVS.hlsl"); + if(!merge_Y_vs_hlsl) { + return -1; + } + + merge_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeYPS.hlsl"); + if(!merge_Y_ps_hlsl) { + return -1; + } + + merge_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeUVPS.hlsl"); + if(!merge_UV_ps_hlsl) { + return -1; + } + + merge_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeUVVS.hlsl"); + if(!merge_UV_vs_hlsl) { + return -1; + } + BOOST_LOG(info) << "Compiled shaders"sv; + + return 0; +} } \ No newline at end of file diff --git a/sunshine/video.cpp b/sunshine/video.cpp index b2e23855..53b7b760 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -622,6 +622,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & case 0: default: // Rec. 601 + BOOST_LOG(info) << "Color coding [Rec. 601]"sv; ctx->color_primaries = AVCOL_PRI_SMPTE170M; ctx->color_trc = AVCOL_TRC_SMPTE170M; ctx->colorspace = AVCOL_SPC_SMPTE170M; @@ -630,6 +631,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & case 1: // Rec. 709 + BOOST_LOG(info) << "Color coding [Rec. 709]"sv; ctx->color_primaries = AVCOL_PRI_BT709; ctx->color_trc = AVCOL_TRC_BT709; ctx->colorspace = AVCOL_SPC_BT709; @@ -638,6 +640,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & case 2: // Rec. 2020 + BOOST_LOG(info) << "Color coding [Rec. 2020]"sv; ctx->color_primaries = AVCOL_PRI_BT2020; ctx->color_trc = AVCOL_TRC_BT2020_10; ctx->colorspace = AVCOL_SPC_BT2020_NCL; From 900d59b3ac075c529b6db9e2e831596cfed0560a Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 3 May 2021 22:06:55 +0200 Subject: [PATCH 026/158] Dynamically set colors during runtime --- assets/MergeUVPS.hlsl | 18 +-- assets/MergeUVVS.hlsl | 5 +- assets/MergeYPS.hlsl | 11 +- sunshine/platform/windows/display_vram.cpp | 148 +++++++++++++++------ sunshine/video.cpp | 1 + 5 files changed, 130 insertions(+), 53 deletions(-) diff --git a/assets/MergeUVPS.hlsl b/assets/MergeUVPS.hlsl index 597c02d8..7754f046 100644 --- a/assets/MergeUVPS.hlsl +++ b/assets/MergeUVPS.hlsl @@ -6,27 +6,23 @@ struct FragTexWide { float3 uuv : TEXCOORD0; }; +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; +}; + //-------------------------------------------------------------------------------------- // Pixel Shader //-------------------------------------------------------------------------------------- float2 PS(FragTexWide input) : SV_Target { - // float4 color_vec_y = { 0.301f, 0.586f, 0.113f, 0.0f }; - // float4 color_vec_u = { -0.168f, -0.328f, 0.496f, 128.0f / 256.0f }; - // float4 color_vec_v = { 0.496f, 0.414f, 0.082f, 128.0f / 256.0f }; - float4 color_vec_y = { 0.299, 0.587, 0.114, 0.0625 }; - float4 color_vec_u = { -0.168736, -0.331264, 0.5, 0.5 }; - float4 color_vec_v = { 0.5, -0.418688, -0.081312, 0.5 }; - - // float4 color_vec_y = { 0.2578f, 0.5039f, 0.0977, 0.0625 }; - // float4 color_vec_u = { -0.1484, 0.2891, 0.4375, 128.0f / 256.0f }; - // float4 color_vec_v = { 0.4375, -0.3672, -0.0703, 128.0f / 256.0f }; - float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb; float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb; float3 rgb = (rgb_left + rgb_right) * 0.5; float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + return float2(u, v); } \ No newline at end of file diff --git a/assets/MergeUVVS.hlsl b/assets/MergeUVVS.hlsl index 0e0b1249..7aa0e171 100644 --- a/assets/MergeUVVS.hlsl +++ b/assets/MergeUVVS.hlsl @@ -3,12 +3,15 @@ struct VertTexPosWide { float4 pos : SV_POSITION; }; +cbuffer info : register(b0) { + float width_i; +}; + //-------------------------------------------------------------------------------------- // Vertex Shader //-------------------------------------------------------------------------------------- VertTexPosWide VS(uint vI : SV_VERTEXID) { - float width_i = 1.0f / 1920.0f; float idHigh = float(vI >> 1); float idLow = float(vI & uint(1)); diff --git a/assets/MergeYPS.hlsl b/assets/MergeYPS.hlsl index 146d517c..4a015f04 100644 --- a/assets/MergeYPS.hlsl +++ b/assets/MergeYPS.hlsl @@ -5,6 +5,12 @@ Texture2D image : register(t0); SamplerState def_sampler : register(s0); +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; +}; + struct PS_INPUT { float4 pos : SV_POSITION; @@ -16,11 +22,8 @@ struct PS_INPUT //-------------------------------------------------------------------------------------- float PS(PS_INPUT frag_in) : SV_Target { - float4 color_vec_y = { 0.299, 0.587, 0.114, 0.0625 }; - float4 color_vec_u = { -0.168736, -0.331264, 0.5, 0.5 }; - float4 color_vec_v = { 0.5, -0.418688, -0.081312, 0.5 }; - float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb; float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; + return y; } \ No newline at end of file diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 29b938e4..5f5bef76 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "sunshine/main.h" #include "display.h" @@ -15,6 +16,7 @@ constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.00000 using input_layout_t = util::safe_ptr>; using render_target_t = util::safe_ptr>; using shader_res_t = util::safe_ptr>; +using buf_t = util::safe_ptr>; using blend_t = util::safe_ptr>; using raster_state_t = util::safe_ptr>; using sampler_state_t = util::safe_ptr>; @@ -24,6 +26,49 @@ using blob_t = util::safe_ptr>; using depth_stencil_state_t = util::safe_ptr>; using depth_stencil_view_t = util::safe_ptr>; +struct __attribute__ ((__aligned__ (16))) color_t { + DirectX::XMFLOAT4 color_vec_y; + DirectX::XMFLOAT4 color_vec_u; + DirectX::XMFLOAT4 color_vec_v; +}; + +color_t colors[] { + { + { 0.299f, 0.587f, 0.114f, 0.0625f }, // Color Luma (Y) + { -0.14713f, -0.28886f, 0.436f, 0.5f }, // Color Cb (U) + { 0.615f, -0.51499f, -0.10001f, 0.5f }, // Color Cr (V) + }, // BT602 -- MPEG + { + { 0.299f, 0.587f, 0.114f, 0.0f }, // Color Luma (Y) + { -0.168736f, -0.331264f, 0.5f, 0.5f }, // Color Cb (U) + { 0.5f, -0.418688f, -0.081312f, 0.5f }, // Color Cr (V) + } // BT601 -- JPEG +}; + +template +buf_t make_buffer(device_t::pointer device, const T& t) { + static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); + + D3D11_BUFFER_DESC buffer_desc { + sizeof(T), + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_CONSTANT_BUFFER, + }; + + D3D11_SUBRESOURCE_DATA init_data { + &t + }; + + buf_t::pointer buf_p; + auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); + if(status) { + BOOST_LOG(error) << "Failed to create shader resource view"sv; + return nullptr; + } + + return buf_t { buf_p }; +} + blob_t merge_UV_vs_hlsl; blob_t merge_UV_ps_hlsl; blob_t merge_Y_vs_hlsl; @@ -168,22 +213,22 @@ public: return; } - LONG x = ((double)rel_x) * out_width / (double)in_width; - LONG y = ((double)rel_y) * out_height / (double)in_height; + // LONG x = ((double)rel_x) * out_width / (double)in_width; + // LONG y = ((double)rel_y) * out_height / (double)in_height; - // Ensure it's within bounds - auto left_out = std::min(out_width, std::max(0, x)); - auto top_out = std::min(out_height, std::max(0, y)); - auto right_out = std::max(0, std::min(out_width, x + cursor_scaled_width)); - auto bottom_out = std::max(0, std::min(out_height, y + cursor_scaled_height)); + // // Ensure it's within bounds + // auto left_out = std::min(out_width, std::max(0, x)); + // auto top_out = std::min(out_height, std::max(0, y)); + // auto right_out = std::max(0, std::min(out_width, x + cursor_scaled_width)); + // auto bottom_out = std::max(0, std::min(out_height, y + cursor_scaled_height)); - auto left_in = std::max(0, -rel_x); - auto top_in = std::max(0, -rel_y); - auto right_in = std::min(in_width - rel_x, cursor_width); - auto bottom_in = std::min(in_height - rel_y, cursor_height); + // auto left_in = std::max(0, -rel_x); + // auto top_in = std::max(0, -rel_y); + // auto right_in = std::min(in_width - rel_x, cursor_width); + // auto bottom_in = std::min(in_height - rel_y, cursor_height); - RECT rect_in { left_in, top_in, right_in, bottom_in }; - RECT rect_out { left_out, top_out, right_out, bottom_out }; + // RECT rect_in { left_in, top_in, right_in, bottom_in }; + // RECT rect_out { left_out, top_out, right_out, bottom_out }; } int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { @@ -216,23 +261,19 @@ public: img.input_res.reset(input_rec_p); } - auto sampler_linear_p = sampler_linear.get(); auto input_res_p = img.input_res.get(); auto Y_rt_p = nv12_Y_rt.get(); auto UV_rt_p = nv12_UV_rt.get(); - // device_ctx_p->OMSetBlendState(blend.get(), nullptr, 0xffffffff); _init_view_port(out_width, out_height); - device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); device_ctx_p->ClearRenderTargetView(Y_rt_p, aquamarine); device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(merge_Y_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); - device_ctx_p->Draw(4, 0); - device_ctx_p->Flush(); + device_ctx_p->Draw(3, 0); _init_view_port(out_width / 2, out_height / 2); device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); @@ -240,13 +281,38 @@ public: device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(merge_UV_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); - device_ctx_p->Draw(4, 0); - device_ctx_p->Flush(); + device_ctx_p->Draw(3, 0); return 0; } - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {} + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + switch (colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &colors[0]; + break; + case 1: // SWS_CS_ITU709 + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + auto color_matrix = make_buffer((device_t::pointer)data, *color_p); + if(!color_matrix) { + BOOST_LOG(warning) << "Failed to create color matrix"sv; + return; + } + + auto buf_p = color_matrix.get(); + device_ctx_p->PSSetConstantBuffers(0, 1, &buf_p); + this->color_matrix = std::move(color_matrix); + } int init( std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, @@ -299,6 +365,19 @@ public: } merge_UV_vs.reset(vs_p); + color_matrix = make_buffer(device_p, colors[0]); + if(!color_matrix) { + BOOST_LOG(error) << "Failed to create color matrix buffer"sv; + return -1; + } + + float info_in[16] { 1.0f / (float)out_width }; //aligned to 16-byte + info = make_buffer(device_p, info_in); + if(!info_in) { + BOOST_LOG(error) << "Failed to create info buffer"sv; + return -1; + } + D3D11_INPUT_ELEMENT_DESC layout_desc { "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }; @@ -310,20 +389,6 @@ public: &input_layout_p); input_layout.reset(input_layout_p); - D3D11_BLEND_DESC blend_desc {}; - blend_desc.RenderTarget[0].BlendEnable = FALSE; - blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; - blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; - blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - blend_t::pointer blend_p; - status = device_p->CreateBlendState(&blend_desc, &blend_p); - if(status) { - BOOST_LOG(error) << "Failed to create blend state [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - blend.reset(blend_p); - D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; t.Height = out_height; @@ -395,6 +460,12 @@ public: } sampler_point.reset(sampler_state_p); + auto sampler_linear_p = sampler_linear.get(); + auto color_matrix_buf_p = color_matrix.get(); + auto info_buf_p = info.get(); + device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix_buf_p); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_buf_p); device_ctx_p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); device_ctx_p->IASetInputLayout(input_layout.get()); @@ -480,6 +551,11 @@ private: } public: + color_t *color_p; + + buf_t info; + buf_t color_matrix; + sampler_state_t sampler_linear; sampler_state_t sampler_point; @@ -488,8 +564,6 @@ public: render_target_t nv12_Y_rt; render_target_t nv12_UV_rt; - blend_t blend; - img_d3d_t img; vs_t merge_UV_vs; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 53b7b760..b41ffb17 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -647,6 +647,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & sws_color_space = SWS_CS_BT2020; break; } + BOOST_LOG(info) << "Color range: ["sv << ((config.encoderCscMode & 0x1) ? "JPEG"sv : "MPEG"sv) << ']'; AVPixelFormat sw_fmt; if(config.dynamicRange == 0) { From 1b7e103ef62d80465982bfee593e65f8b68135c4 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 4 May 2021 10:21:56 +0200 Subject: [PATCH 027/158] Allow resizing the image during conversion --- assets/MergeYPS.hlsl | 2 +- assets/MergeYVS.hlsl | 5 +++- sunshine/video.cpp | 65 +++++++------------------------------------- 3 files changed, 15 insertions(+), 57 deletions(-) diff --git a/assets/MergeYPS.hlsl b/assets/MergeYPS.hlsl index 4a015f04..66c53afd 100644 --- a/assets/MergeYPS.hlsl +++ b/assets/MergeYPS.hlsl @@ -22,7 +22,7 @@ struct PS_INPUT //-------------------------------------------------------------------------------------- float PS(PS_INPUT frag_in) : SV_Target { - float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb; + float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb; float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; return y; diff --git a/assets/MergeYVS.hlsl b/assets/MergeYVS.hlsl index d95ed7b4..f38bb3bf 100644 --- a/assets/MergeYVS.hlsl +++ b/assets/MergeYVS.hlsl @@ -15,8 +15,11 @@ PS_INPUT VS(uint vI : SV_VERTEXID) float x = idHigh * 4.0 - 1.0; float y = idLow * 4.0 - 1.0; + float u = idHigh * 2.0; + float v = 1.0 - idLow * 2.0; + PS_INPUT vert_out; vert_out.pos = float4(x, y, 0.0, 1.0); - vert_out.tex = float2(idHigh, idLow); + vert_out.tex = float2(u, v); return vert_out; } \ No newline at end of file diff --git a/sunshine/video.cpp b/sunshine/video.cpp index b41ffb17..986ef05f 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -82,10 +82,10 @@ platf::dev_type_e map_dev_type(AVHWDeviceType type); platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); void sw_img_to_frame(const platf::img_t &img, frame_t &frame); -void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); -util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); -void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); -util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); +void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame); +util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); +void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame); +util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); @@ -297,8 +297,8 @@ static encoder_t nvenc { false, true, - nv_d3d_img_to_frame, - nv_d3d_make_hwdevice_ctx + dxgi_img_to_frame, + dxgi_make_hwdevice_ctx }; static encoder_t amdvce { @@ -331,8 +331,8 @@ static encoder_t amdvce { false, true, - amd_d3d_img_to_frame, - amd_d3d_make_hwdevice_ctx + dxgi_img_to_frame, + dxgi_make_hwdevice_ctx }; #endif @@ -1304,7 +1304,7 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {} #ifdef _WIN32 -void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { +void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) { if(img.data == frame->data[0]) { return; } @@ -1327,31 +1327,7 @@ void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { frame->width = img.width; } -void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { - if(img.data == frame->data[0]) { - return; - } - - // Need to have something refcounted - if(!frame->buf[0]) { - frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); - } - - auto desc = (AVD3D11FrameDescriptor*)frame->buf[0]->data; - desc->texture = (ID3D11Texture2D*)img.data; - desc->index = 0; - - frame->data[0] = img.data; - frame->data[1] = 0; - - frame->linesize[0] = img.row_pitch; - - frame->height = img.height; - frame->width = img.width; -} - - -util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { +util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; @@ -1371,27 +1347,6 @@ util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice return ctx_buf; } - -util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { - buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; - auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; - - std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); - - auto device = (ID3D11Device*)hwdevice_ctx->data; - device->AddRef(); - ctx->device = device; - - auto err = av_hwdevice_ctx_init(ctx_buf.get()); - if(err) { - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; - BOOST_LOG(error) << "Failed to create FFMpeg amddech: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); - - return err; - } - - return ctx_buf; -} #endif int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { From c19853f03fbffc3f52533f2ad7960cbd7c785d96 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 5 May 2021 11:28:57 +0200 Subject: [PATCH 028/158] Add color for BT701 colorspace --- sunshine/platform/windows/display_vram.cpp | 33 ++++++++++++++-------- sunshine/video.cpp | 16 ++++++++++- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 5f5bef76..aa96d85b 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -32,17 +32,24 @@ struct __attribute__ ((__aligned__ (16))) color_t { DirectX::XMFLOAT4 color_vec_v; }; +color_t make_color_matrix(float Cr, float Cb, float U_max, float V_max, float add_Y, float add_UV) { + float Cg = 1.0f - Cr - Cb; + + float Cr_i = 1.0f - Cr; + float Cb_i = 1.0f - Cb; + + return { + { Cr, Cg, Cb, add_Y }, + { -(Cr * U_max / Cb_i), -(Cg * U_max / Cb_i), U_max, add_UV }, + { V_max, -(Cg * V_max / Cr_i), -(Cb * V_max / Cr_i), add_UV } + }; +} + color_t colors[] { - { - { 0.299f, 0.587f, 0.114f, 0.0625f }, // Color Luma (Y) - { -0.14713f, -0.28886f, 0.436f, 0.5f }, // Color Cb (U) - { 0.615f, -0.51499f, -0.10001f, 0.5f }, // Color Cr (V) - }, // BT602 -- MPEG - { - { 0.299f, 0.587f, 0.114f, 0.0f }, // Color Luma (Y) - { -0.168736f, -0.331264f, 0.5f, 0.5f }, // Color Cb (U) - { 0.5f, -0.418688f, -0.081312f, 0.5f }, // Color Cr (V) - } // BT601 -- JPEG + make_color_matrix(0.299f, 0.114f, 0.436f, 0.615f, 0.0625, 0.5f), // BT601 MPEG + make_color_matrix(0.299f, 0.114f, 0.5f, 0.5f, 0.0f, 0.5f), // BT601 JPEG + make_color_matrix(0.2126f, 0.0722f, 0.436f, 0.615f, 0.0625, 0.5f), //BT701 MPEG + make_color_matrix(0.2126f, 0.0722f, 0.5f, 0.5f, 0.0f, 0.5f), //BT701 JPEG }; template @@ -267,7 +274,6 @@ public: auto UV_rt_p = nv12_UV_rt.get(); _init_view_port(out_width, out_height); - device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); device_ctx_p->ClearRenderTargetView(Y_rt_p, aquamarine); device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); @@ -275,6 +281,8 @@ public: device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + _init_view_port(out_width / 2, out_height / 2); device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); device_ctx_p->ClearRenderTargetView(UV_rt_p, aquamarine); @@ -292,6 +300,8 @@ public: color_p = &colors[0]; break; case 1: // SWS_CS_ITU709 + color_p = &colors[2]; + break; case 9: // SWS_CS_BT2020 default: BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; @@ -581,6 +591,7 @@ public: device_ctx_t::pointer device_ctx_p; + // The destructor will remove itself from the list of hardware devices, this is done synchronously std::vector *hwdevices_p; }; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 986ef05f..e8e9b4b5 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -1304,6 +1304,15 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {} #ifdef _WIN32 +} + +// Ugly, but need to declare for wio +namespace platf::dxgi { +void lock(void *hwdevice); +void unlock(void *hwdevice); +} +void do_nothing(void*) {} +namespace video { void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) { if(img.data == frame->data[0]) { return; @@ -1334,13 +1343,18 @@ util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_c std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); auto device = (ID3D11Device*)hwdevice_ctx->data; + device->AddRef(); ctx->device = device; + ctx->lock_ctx = (void*)1; + ctx->lock = do_nothing; + ctx->unlock = do_nothing; + auto err = av_hwdevice_ctx_init(ctx_buf.get()); if(err) { char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; - BOOST_LOG(error) << "Failed to create FFMpeg nvenc: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); return err; } From 88c3828ad3515b002aa6842885544460d02b61c5 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 5 May 2021 12:17:25 +0200 Subject: [PATCH 029/158] Fixed not testing for 10bit pixels support --- sunshine/video.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index e8e9b4b5..e0a11dc6 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -306,7 +306,7 @@ static encoder_t amdvce { { (int)amd::profile_h264_e::high, (int)amd::profile_hevc_e::main }, AV_HWDEVICE_TYPE_D3D11VA, AV_PIX_FMT_D3D11, - AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_NV12, AV_PIX_FMT_P010, { { { "header_insertion_mode"s, "idr"s }, @@ -1200,7 +1200,7 @@ bool validate_encoder(encoder_t &encoder) { encoder.hevc[encoder_t::PASSED] = test_hevc; std::vector> configs { - { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 1, 1, 1 } } + { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } } }; for(auto &[flag, config] : configs) { auto h264 = config; From a93bad4cf3832238ab52dc477a26ec1634e26241 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 5 May 2021 15:53:22 +0200 Subject: [PATCH 030/158] Fix crash when sending SIGINT before starting the http server --- sunshine/main.cpp | 2 ++ sunshine/nvhttp.cpp | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/sunshine/main.cpp b/sunshine/main.cpp index a24b562e..801493aa 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -154,6 +154,8 @@ int main(int argc, char *argv[]) { stream::rtpThread(shutdown_event); httpThread.join(); + task_pool.stop(); + task_pool.join(); return 0; } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index da402706..98f5a79d 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -846,8 +846,22 @@ void start(std::shared_ptr shutdown_event) { return; } - std::thread ssl { &https_server_t::accept_and_run, &https_server }; - std::thread tcp { &http_server_t::accept_and_run, &http_server }; + auto accept_and_run = [&](auto *http_server) { + try { + http_server->accept_and_run(); + } catch(boost::system::system_error &err) { + // It's possible the exception gets thrown after calling http_server->stop() from a different thread + if(shutdown_event->peek()) { + return; + } + + BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + shutdown_event->raise(true); + return; + } + }; + std::thread ssl { accept_and_run, &https_server }; + std::thread tcp { accept_and_run, &http_server }; // Wait for any event shutdown_event->view(); From 3a0377851dd8fd973bd743e163a366d3fe0a72d7 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 5 May 2021 16:00:27 +0200 Subject: [PATCH 031/158] Update gitignore --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c15ad42a..2584b70d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ build -cmake-build-* +cmake-build* .DS_Store - +.vscode +.vs *.swp *.kdev4 From 0232d8027ce939dc51342b2fb5b90521959b4e9a Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 6 May 2021 12:00:39 +0200 Subject: [PATCH 032/158] Render cursor on duplicated image --- assets/MergeUVPS.hlsl | 2 +- assets/MergeUVVS.hlsl | 2 +- assets/MergeYPS.hlsl | 8 +- assets/MergeYVS.hlsl | 5 +- assets/ScenePS.hlsl | 26 ++++ sunshine/platform/windows/display_vram.cpp | 134 +++++++++++++-------- 6 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 assets/ScenePS.hlsl diff --git a/assets/MergeUVPS.hlsl b/assets/MergeUVPS.hlsl index 7754f046..05c9f0fa 100644 --- a/assets/MergeUVPS.hlsl +++ b/assets/MergeUVPS.hlsl @@ -15,7 +15,7 @@ cbuffer ColorMatrix : register(b0) { //-------------------------------------------------------------------------------------- // Pixel Shader //-------------------------------------------------------------------------------------- -float2 PS(FragTexWide input) : SV_Target +float2 main_ps(FragTexWide input) : SV_Target { float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb; float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb; diff --git a/assets/MergeUVVS.hlsl b/assets/MergeUVVS.hlsl index 7aa0e171..66d97d43 100644 --- a/assets/MergeUVVS.hlsl +++ b/assets/MergeUVVS.hlsl @@ -10,7 +10,7 @@ cbuffer info : register(b0) { //-------------------------------------------------------------------------------------- // Vertex Shader //-------------------------------------------------------------------------------------- -VertTexPosWide VS(uint vI : SV_VERTEXID) +VertTexPosWide main_vs(uint vI : SV_VERTEXID) { float idHigh = float(vI >> 1); float idLow = float(vI & uint(1)); diff --git a/assets/MergeYPS.hlsl b/assets/MergeYPS.hlsl index 66c53afd..f46a7232 100644 --- a/assets/MergeYPS.hlsl +++ b/assets/MergeYPS.hlsl @@ -1,6 +1,3 @@ -//-------------------------------------------------------------------------------------- -// YCbCrPS2.hlsl -//-------------------------------------------------------------------------------------- Texture2D image : register(t0); SamplerState def_sampler : register(s0); @@ -17,10 +14,7 @@ struct PS_INPUT float2 tex : TEXCOORD; }; -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- -float PS(PS_INPUT frag_in) : SV_Target +float main_ps(PS_INPUT frag_in) : SV_Target { float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb; float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; diff --git a/assets/MergeYVS.hlsl b/assets/MergeYVS.hlsl index f38bb3bf..51319ddb 100644 --- a/assets/MergeYVS.hlsl +++ b/assets/MergeYVS.hlsl @@ -4,10 +4,7 @@ struct PS_INPUT float2 tex : TEXCOORD; }; -//-------------------------------------------------------------------------------------- -// Vertex Shader -//-------------------------------------------------------------------------------------- -PS_INPUT VS(uint vI : SV_VERTEXID) +PS_INPUT main_vs(uint vI : SV_VERTEXID) { float idHigh = float(vI >> 1); float idLow = float(vI & uint(1)); diff --git a/assets/ScenePS.hlsl b/assets/ScenePS.hlsl new file mode 100644 index 00000000..8b4368bd --- /dev/null +++ b/assets/ScenePS.hlsl @@ -0,0 +1,26 @@ +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; +}; + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float4 main_ps(PS_INPUT frag_in) : SV_Target +{ + float4 color = image.Sample(def_sampler, frag_in.tex, 0); + + clip(color.a < 0.1f ? -1 : 1); + return color; +} \ No newline at end of file diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index aa96d85b..889ce030 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -59,7 +59,7 @@ buf_t make_buffer(device_t::pointer device, const T& t) { D3D11_BUFFER_DESC buffer_desc { sizeof(T), D3D11_USAGE_IMMUTABLE, - D3D11_BIND_CONSTANT_BUFFER, + D3D11_BIND_CONSTANT_BUFFER }; D3D11_SUBRESOURCE_DATA init_data { @@ -69,7 +69,7 @@ buf_t make_buffer(device_t::pointer device, const T& t) { buf_t::pointer buf_p; auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); if(status) { - BOOST_LOG(error) << "Failed to create shader resource view"sv; + BOOST_LOG(error) << "Failed to create buffer"sv; return nullptr; } @@ -80,6 +80,7 @@ blob_t merge_UV_vs_hlsl; blob_t merge_UV_ps_hlsl; blob_t merge_Y_vs_hlsl; blob_t merge_Y_ps_hlsl; +blob_t scene_ps_hlsl; struct img_d3d_t : public platf::img_t { shader_res_t input_res; @@ -201,11 +202,11 @@ blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { } blob_t compile_pixel_shader(LPCSTR file) { - return compile_shader(file, "PS", "ps_5_0"); + return compile_shader(file, "main_ps", "ps_5_0"); } blob_t compile_vertex_shader(LPCSTR file) { - return compile_shader(file, "VS", "vs_5_0"); + return compile_shader(file, "main_vs", "vs_5_0"); } class hwdevice_t : public platf::hwdevice_t { @@ -220,29 +221,34 @@ public: return; } - // LONG x = ((double)rel_x) * out_width / (double)in_width; - // LONG y = ((double)rel_y) * out_height / (double)in_height; + auto x = ((float)rel_x) * cursor_scale; + auto y = ((float)rel_y) * cursor_scale; - // // Ensure it's within bounds - // auto left_out = std::min(out_width, std::max(0, x)); - // auto top_out = std::min(out_height, std::max(0, y)); - // auto right_out = std::max(0, std::min(out_width, x + cursor_scaled_width)); - // auto bottom_out = std::max(0, std::min(out_height, y + cursor_scaled_height)); - - // auto left_in = std::max(0, -rel_x); - // auto top_in = std::max(0, -rel_y); - // auto right_in = std::min(in_width - rel_x, cursor_width); - // auto bottom_in = std::min(in_height - rel_y, cursor_height); - - // RECT rect_in { left_in, top_in, right_in, bottom_in }; - // RECT rect_out { left_out, top_out, right_out, bottom_out }; + cursor_view.TopLeftX = x; + cursor_view.TopLeftY = y; + cursor_view.Width = cursor_scaled_width; + cursor_view.Height = cursor_scaled_height; } int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { - cursor_width = width; - cursor_height = height; - cursor_scaled_width = ((double)width) / in_width * out_width; - cursor_scaled_height = ((double)height) / in_height * out_height; + auto device = (device_t::pointer)data; + + cursor_scaled_width = ((float)width) * cursor_scale; + cursor_scaled_height = ((float)height) * cursor_scale; + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + shader_res_t::pointer cursor_res_p; + auto status = device->CreateShaderResourceView(texture, &desc, &cursor_res_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + img.input_res.reset(cursor_res_p); return 0; } @@ -259,23 +265,43 @@ public: }; desc.Texture2D.MipLevels = 1; - shader_res_t::pointer input_rec_p; - auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &input_rec_p); + shader_res_t::pointer input_res_p; + auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &input_res_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - img.input_res.reset(input_rec_p); + img.input_res.reset(input_res_p); } auto input_res_p = img.input_res.get(); + auto cursor_res_p = this->img.input_res.get(); + auto scene_rt_p = scene_rt.get(); auto Y_rt_p = nv12_Y_rt.get(); auto UV_rt_p = nv12_UV_rt.get(); + if(cursor_visible) { + _init_view_port(img.width, img.height); + + device_ctx_p->OMSetRenderTargets(1, &scene_rt_p, nullptr); + device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(scene_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); + + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + device_ctx_p->RSSetViewports(1, &cursor_view); + device_ctx_p->PSSetShaderResources(0, 1, &cursor_res_p); + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + input_res_p = scene_sr.get(); + } + _init_view_port(out_width, out_height); device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); - device_ctx_p->ClearRenderTargetView(Y_rt_p, aquamarine); device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(merge_Y_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); @@ -285,7 +311,6 @@ public: _init_view_port(out_width / 2, out_height / 2); device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); - device_ctx_p->ClearRenderTargetView(UV_rt_p, aquamarine); device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(merge_UV_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); @@ -336,14 +361,15 @@ public: this->device_ctx_p = device_ctx_p; + cursor_scale = (float)out_width / (float)in_width; cursor_visible = false; + cursor_view.MinDepth = 0.0f; + cursor_view.MaxDepth = 1.0f; platf::hwdevice_t::img = &img; this->out_width = out_width; this->out_height = out_height; - this->in_width = in_width; - this->in_height = in_height; vs_t::pointer vs_p; status = device_p->CreateVertexShader(merge_Y_vs_hlsl->GetBufferPointer(), merge_Y_vs_hlsl->GetBufferSize(), nullptr, &vs_p); @@ -375,6 +401,17 @@ public: } merge_UV_vs.reset(vs_p); + status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + scene_ps.reset(ps_p); + + if(_init_rt(scene_sr, scene_rt, in_width, in_height, DXGI_FORMAT_B8G8R8A8_UNORM)) { + return -1; + } + color_matrix = make_buffer(device_p, colors[0]); if(!color_matrix) { BOOST_LOG(error) << "Failed to create color matrix buffer"sv; @@ -382,9 +419,9 @@ public: } float info_in[16] { 1.0f / (float)out_width }; //aligned to 16-byte - info = make_buffer(device_p, info_in); + info_scene = make_buffer(device_p, info_in); if(!info_in) { - BOOST_LOG(error) << "Failed to create info buffer"sv; + BOOST_LOG(error) << "Failed to create info scene buffer"sv; return -1; } @@ -462,17 +499,9 @@ public: } sampler_linear.reset(sampler_state_p); - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - status = device_p->CreateSamplerState(&sampler_desc, &sampler_state_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - sampler_point.reset(sampler_state_p); - auto sampler_linear_p = sampler_linear.get(); auto color_matrix_buf_p = color_matrix.get(); - auto info_buf_p = info.get(); + auto info_buf_p = info_scene.get(); device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix_buf_p); device_ctx_p->VSSetConstantBuffers(0, 1, &info_buf_p); @@ -563,31 +592,33 @@ private: public: color_t *color_p; - buf_t info; + buf_t info_scene; buf_t color_matrix; sampler_state_t sampler_linear; - sampler_state_t sampler_point; input_layout_t input_layout; render_target_t nv12_Y_rt; render_target_t nv12_UV_rt; + render_target_t scene_rt; + shader_res_t scene_sr; + img_d3d_t img; vs_t merge_UV_vs; ps_t merge_UV_ps; vs_t merge_Y_vs; ps_t merge_Y_ps; + ps_t scene_ps; + D3D11_VIEWPORT cursor_view; + float cursor_scaled_width, cursor_scaled_height; + float cursor_scale; bool cursor_visible; - LONG cursor_width, cursor_height; - LONG cursor_scaled_width, cursor_scaled_height; - - LONG in_width, in_height; - double out_width, out_height; + float out_width, out_height; device_ctx_t::pointer device_ctx_p; @@ -648,7 +679,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_DEFAULT; t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - t.BindFlags = D3D11_BIND_RENDER_TARGET; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, &data, &tex_p); @@ -809,6 +840,11 @@ int init() { if(!merge_UV_vs_hlsl) { return -1; } + + scene_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/scenePS.hlsl"); + if(!scene_ps_hlsl) { + return -1; + } BOOST_LOG(info) << "Compiled shaders"sv; return 0; From 0f661e467e95c50f56439dd422ce16bfb5211428 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 6 May 2021 12:36:26 +0200 Subject: [PATCH 033/158] Blend cursor onto the image --- assets/ScenePS.hlsl | 14 +------ sunshine/platform/windows/display_vram.cpp | 48 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/assets/ScenePS.hlsl b/assets/ScenePS.hlsl index 8b4368bd..aa601231 100644 --- a/assets/ScenePS.hlsl +++ b/assets/ScenePS.hlsl @@ -2,25 +2,13 @@ Texture2D image : register(t0); SamplerState def_sampler : register(s0); -cbuffer ColorMatrix : register(b0) { - float4 color_vec_y; - float4 color_vec_u; - float4 color_vec_v; -}; - struct PS_INPUT { float4 pos : SV_POSITION; float2 tex : TEXCOORD; }; -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- float4 main_ps(PS_INPUT frag_in) : SV_Target { - float4 color = image.Sample(def_sampler, frag_in.tex, 0); - - clip(color.a < 0.1f ? -1 : 1); - return color; + return image.Sample(def_sampler, frag_in.tex, 0); } \ No newline at end of file diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 889ce030..30fd2eba 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -69,13 +69,40 @@ buf_t make_buffer(device_t::pointer device, const T& t) { buf_t::pointer buf_p; auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); if(status) { - BOOST_LOG(error) << "Failed to create buffer"sv; + BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; return nullptr; } return buf_t { buf_p }; } +blend_t make_blend(device_t::pointer device, bool enable) { + D3D11_BLEND_DESC bdesc {}; + auto &rt = bdesc.RenderTarget[0]; + rt.BlendEnable = enable; + rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + if(enable) { + rt.BlendOp = D3D11_BLEND_OP_ADD; + rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; + + rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + + rt.SrcBlendAlpha = D3D11_BLEND_ZERO; + rt.DestBlendAlpha = D3D11_BLEND_ZERO; + } + + blend_t::pointer blend_p; + auto status = device->CreateBlendState(&bdesc, &blend_p); + if(status) { + BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blend_t { blend_p }; +} + blob_t merge_UV_vs_hlsl; blob_t merge_UV_ps_hlsl; blob_t merge_Y_vs_hlsl; @@ -290,12 +317,12 @@ public: device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); + device_ctx_p->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); device_ctx_p->RSSetViewports(1, &cursor_view); device_ctx_p->PSSetShaderResources(0, 1, &cursor_res_p); device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); + device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); input_res_p = scene_sr.get(); } @@ -307,8 +334,6 @@ public: device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); - _init_view_port(out_width / 2, out_height / 2); device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); @@ -374,7 +399,7 @@ public: vs_t::pointer vs_p; status = device_p->CreateVertexShader(merge_Y_vs_hlsl->GetBufferPointer(), merge_Y_vs_hlsl->GetBufferSize(), nullptr, &vs_p); if(status) { - BOOST_LOG(error) << "Failed to create screen vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create mergeY vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } merge_Y_vs.reset(vs_p); @@ -382,7 +407,7 @@ public: ps_t::pointer ps_p; status = device_p->CreatePixelShader(merge_Y_ps_hlsl->GetBufferPointer(), merge_Y_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { - BOOST_LOG(error) << "Failed to create YCrCb pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create mergeY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } merge_Y_ps.reset(ps_p); @@ -408,6 +433,9 @@ public: } scene_ps.reset(ps_p); + blend_disable = make_blend(device_p, false); + blend_enable = make_blend(device_p, true); + if(_init_rt(scene_sr, scene_rt, in_width, in_height, DXGI_FORMAT_B8G8R8A8_UNORM)) { return -1; } @@ -418,7 +446,7 @@ public: return -1; } - float info_in[16] { 1.0f / (float)out_width }; //aligned to 16-byte + float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte info_scene = make_buffer(device_p, info_in); if(!info_in) { BOOST_LOG(error) << "Failed to create info scene buffer"sv; @@ -502,6 +530,7 @@ public: auto sampler_linear_p = sampler_linear.get(); auto color_matrix_buf_p = color_matrix.get(); auto info_buf_p = info_scene.get(); + device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix_buf_p); device_ctx_p->VSSetConstantBuffers(0, 1, &info_buf_p); @@ -592,6 +621,9 @@ private: public: color_t *color_p; + blend_t blend_enable; + blend_t blend_disable; + buf_t info_scene; buf_t color_matrix; From 7b45f0d89909685c03b5bab3221e08977579144e Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 6 May 2021 13:51:29 +0200 Subject: [PATCH 034/158] Fix cursor height and width --- sunshine/platform/windows/display_vram.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 30fd2eba..8dd5c7f7 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -253,21 +253,19 @@ public: cursor_view.TopLeftX = x; cursor_view.TopLeftY = y; - cursor_view.Width = cursor_scaled_width; - cursor_view.Height = cursor_scaled_height; } int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { auto device = (device_t::pointer)data; - cursor_scaled_width = ((float)width) * cursor_scale; - cursor_scaled_height = ((float)height) * cursor_scale; + cursor_view.Width = width; + cursor_view.Height = height; D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; shader_res_t::pointer cursor_res_p; auto status = device->CreateShaderResourceView(texture, &desc, &cursor_res_p); @@ -646,7 +644,6 @@ public: ps_t scene_ps; D3D11_VIEWPORT cursor_view; - float cursor_scaled_width, cursor_scaled_height; float cursor_scale; bool cursor_visible; From 67df04e0a2baab57c5fdc1ce2296b54635bbbced Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 6 May 2021 16:51:59 +0200 Subject: [PATCH 035/158] Fix cursor position on lower resolution screens --- sunshine/platform/windows/display_vram.cpp | 6 ++---- sunshine/video.cpp | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 8dd5c7f7..6dd7852f 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -248,8 +248,8 @@ public: return; } - auto x = ((float)rel_x) * cursor_scale; - auto y = ((float)rel_y) * cursor_scale; + auto x = ((float)rel_x); + auto y = ((float)rel_y); cursor_view.TopLeftX = x; cursor_view.TopLeftY = y; @@ -384,7 +384,6 @@ public: this->device_ctx_p = device_ctx_p; - cursor_scale = (float)out_width / (float)in_width; cursor_visible = false; cursor_view.MinDepth = 0.0f; cursor_view.MaxDepth = 1.0f; @@ -644,7 +643,6 @@ public: ps_t scene_ps; D3D11_VIEWPORT cursor_view; - float cursor_scale; bool cursor_visible; float out_width, out_height; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index e0a11dc6..ad298922 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -373,11 +373,9 @@ static encoder_t software { static std::vector encoders { #ifdef _WIN32 nvenc, -#endif - software, -#ifdef _WIN32 amdvce, #endif + software }; void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { From 513942c888b3f7386d89989eb2400c91433d5383 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 8 May 2021 12:03:58 +0200 Subject: [PATCH 036/158] Improve colors for nv12 --- assets/MergeUVPS.hlsl | 7 +++- assets/MergeYPS.hlsl | 6 ++-- sunshine/platform/windows/display_vram.cpp | 39 +++++++++++++++++----- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/assets/MergeUVPS.hlsl b/assets/MergeUVPS.hlsl index 05c9f0fa..2b72cddf 100644 --- a/assets/MergeUVPS.hlsl +++ b/assets/MergeUVPS.hlsl @@ -10,6 +10,8 @@ cbuffer ColorMatrix : register(b0) { float4 color_vec_y; float4 color_vec_u; float4 color_vec_v; + float2 range_y; + float2 range_uv; }; //-------------------------------------------------------------------------------------- @@ -24,5 +26,8 @@ float2 main_ps(FragTexWide input) : SV_Target float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; - return float2(u, v); + u = u * range_uv.x + range_uv.y; + v = v * range_uv.x + range_uv.y; + + return float2(u, v * 224.0f/256.0f + 0.0625); } \ No newline at end of file diff --git a/assets/MergeYPS.hlsl b/assets/MergeYPS.hlsl index f46a7232..386133c8 100644 --- a/assets/MergeYPS.hlsl +++ b/assets/MergeYPS.hlsl @@ -6,6 +6,8 @@ cbuffer ColorMatrix : register(b0) { float4 color_vec_y; float4 color_vec_u; float4 color_vec_v; + float2 range_y; + float2 range_uv; }; struct PS_INPUT @@ -17,7 +19,7 @@ struct PS_INPUT float main_ps(PS_INPUT frag_in) : SV_Target { float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb; - float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; + float y = dot(color_vec_y.xyz, rgb); - return y; + return y * range_y.x + range_y.y; } \ No newline at end of file diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 6dd7852f..81172036 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -26,30 +26,42 @@ using blob_t = util::safe_ptr>; using depth_stencil_state_t = util::safe_ptr>; using depth_stencil_view_t = util::safe_ptr>; +using float4 = DirectX::XMFLOAT4; +using float3 = DirectX::XMFLOAT3; +using float2 = DirectX::XMFLOAT2; struct __attribute__ ((__aligned__ (16))) color_t { - DirectX::XMFLOAT4 color_vec_y; - DirectX::XMFLOAT4 color_vec_u; - DirectX::XMFLOAT4 color_vec_v; + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; }; -color_t make_color_matrix(float Cr, float Cb, float U_max, float V_max, float add_Y, float add_UV) { +color_t make_color_matrix(float Cr, float Cb, float U_max, float V_max, float add_Y, float add_UV, float2 range_Y, float2 range_UV) { float Cg = 1.0f - Cr - Cb; float Cr_i = 1.0f - Cr; float Cb_i = 1.0f - Cb; + float shift_y = range_Y.x / 256.0f; + float shift_uv = range_UV.x / 256.0f; + + float scale_y = (range_Y.y - range_Y.x) / 256.0f; + float scale_uv = (range_UV.y - range_UV.x) / 256.0f; return { { Cr, Cg, Cb, add_Y }, { -(Cr * U_max / Cb_i), -(Cg * U_max / Cb_i), U_max, add_UV }, - { V_max, -(Cg * V_max / Cr_i), -(Cb * V_max / Cr_i), add_UV } + { V_max, -(Cg * V_max / Cr_i), -(Cb * V_max / Cr_i), add_UV }, + { scale_y, shift_y }, + { scale_uv, shift_uv }, }; } color_t colors[] { - make_color_matrix(0.299f, 0.114f, 0.436f, 0.615f, 0.0625, 0.5f), // BT601 MPEG - make_color_matrix(0.299f, 0.114f, 0.5f, 0.5f, 0.0f, 0.5f), // BT601 JPEG - make_color_matrix(0.2126f, 0.0722f, 0.436f, 0.615f, 0.0625, 0.5f), //BT701 MPEG - make_color_matrix(0.2126f, 0.0722f, 0.5f, 0.5f, 0.0f, 0.5f), //BT701 JPEG + make_color_matrix(0.299f, 0.114f, 0.436f, 0.615f, 0.0625, 0.5f, { 16.0f, 235.0f }, { 16.0f, 240.0f }), // BT601 MPEG + make_color_matrix(0.299f, 0.114f, 0.5f, 0.5f, 0.0f, 0.5f, { 0.0f, 255.0f }, { 0.0f, 255.0f }), // BT601 JPEG + make_color_matrix(0.2126f, 0.0722f, 0.436f, 0.615f, 0.0625, 0.5f, { 16.0f, 235.0f }, { 16.0f, 240.0f }), //BT701 MPEG + make_color_matrix(0.2126f, 0.0722f, 0.5f, 0.5f, 0.0f, 0.5f, { 0.0f, 255.0f }, { 0.0f, 255.0f }), //BT701 JPEG }; template @@ -847,6 +859,15 @@ std::shared_ptr display_vram_t::make_hwdevice(int width, int } int init() { + for(auto &color : colors) { + BOOST_LOG(debug) << "Color Matrix"sv; + BOOST_LOG(debug) << "Y ["sv << color.color_vec_y.x << ", "sv << color.color_vec_y.y << ", "sv << color.color_vec_y.z << ", "sv << color.color_vec_y.w << ']'; + BOOST_LOG(debug) << "U ["sv << color.color_vec_u.x << ", "sv << color.color_vec_u.y << ", "sv << color.color_vec_u.z << ", "sv << color.color_vec_u.w << ']'; + BOOST_LOG(debug) << "V ["sv << color.color_vec_v.x << ", "sv << color.color_vec_v.y << ", "sv << color.color_vec_v.z << ", "sv << color.color_vec_v.w << ']'; + BOOST_LOG(debug) << "range Y ["sv << color.range_y.x << ", "sv << color.range_y.y << ']'; + BOOST_LOG(debug) << "range UV ["sv << color.range_uv.x << ", "sv << color.range_uv.y << ']'; + } + BOOST_LOG(info) << "Compiling shaders..."sv; merge_Y_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeYVS.hlsl"); if(!merge_Y_vs_hlsl) { From 71b214ca43088ceade1ad30f816e349fa557dc92 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 8 May 2021 13:08:07 +0200 Subject: [PATCH 037/158] Fix Stuck Modifiers on Disconnection --- sunshine/input.cpp | 7 +++++++ sunshine/input.h | 1 + sunshine/stream.cpp | 2 ++ 3 files changed, 10 insertions(+) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 7d9b7733..402d4ce3 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -402,6 +402,13 @@ void passthrough(std::shared_ptr &input, std::vector &&in task_pool.push(passthrough_helper, input, util::cmove(input_data)); } +void reset(){ + for(auto& kp : key_press){ + platf::keyboard(platf_input, kp.first & 0x00FF, true); + key_press[kp.first] = false; + } +} + void init() { platf_input = platf::input(); } diff --git a/sunshine/input.h b/sunshine/input.h index ce5b9161..e1d4c82c 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -11,6 +11,7 @@ namespace input { struct input_t; void print(void *input); +void reset(); void passthrough(std::shared_ptr &input, std::vector &&input_data); void init(); diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index f6d4ef21..ccc6c697 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -885,6 +885,8 @@ state_e state(session_t &session) { void stop(session_t &session) { while_starting_do_nothing(session.state); + //Reset input on session stop to avoid stuck repeated keys + input::reset(); auto expected = state_e::RUNNING; auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING); if(already_stopping) { From 13c2da07e8118dc6e42ffd6142b08e977a63d682 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 8 May 2021 13:22:04 +0200 Subject: [PATCH 038/158] Move shaders to seperate folder in assets --- .../ConvertUVPS.hlsl} | 0 .../ConvertUVVS.hlsl} | 0 .../ConvertYPS.hlsl} | 0 assets/{ => shaders}/ScenePS.hlsl | 0 .../{MergeYVS.hlsl => shaders/SceneVS.hlsl} | 0 sunshine/platform/windows/display_vram.cpp | 63 ++++++++++--------- 6 files changed, 32 insertions(+), 31 deletions(-) rename assets/{MergeUVPS.hlsl => shaders/ConvertUVPS.hlsl} (100%) rename assets/{MergeUVVS.hlsl => shaders/ConvertUVVS.hlsl} (100%) rename assets/{MergeYPS.hlsl => shaders/ConvertYPS.hlsl} (100%) rename assets/{ => shaders}/ScenePS.hlsl (100%) rename assets/{MergeYVS.hlsl => shaders/SceneVS.hlsl} (100%) diff --git a/assets/MergeUVPS.hlsl b/assets/shaders/ConvertUVPS.hlsl similarity index 100% rename from assets/MergeUVPS.hlsl rename to assets/shaders/ConvertUVPS.hlsl diff --git a/assets/MergeUVVS.hlsl b/assets/shaders/ConvertUVVS.hlsl similarity index 100% rename from assets/MergeUVVS.hlsl rename to assets/shaders/ConvertUVVS.hlsl diff --git a/assets/MergeYPS.hlsl b/assets/shaders/ConvertYPS.hlsl similarity index 100% rename from assets/MergeYPS.hlsl rename to assets/shaders/ConvertYPS.hlsl diff --git a/assets/ScenePS.hlsl b/assets/shaders/ScenePS.hlsl similarity index 100% rename from assets/ScenePS.hlsl rename to assets/shaders/ScenePS.hlsl diff --git a/assets/MergeYVS.hlsl b/assets/shaders/SceneVS.hlsl similarity index 100% rename from assets/MergeYVS.hlsl rename to assets/shaders/SceneVS.hlsl diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 81172036..c8744b18 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -6,6 +6,7 @@ #include "sunshine/main.h" #include "display.h" +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders" namespace platf { using namespace std::literals; } @@ -115,10 +116,10 @@ blend_t make_blend(device_t::pointer device, bool enable) { return blend_t { blend_p }; } -blob_t merge_UV_vs_hlsl; -blob_t merge_UV_ps_hlsl; -blob_t merge_Y_vs_hlsl; -blob_t merge_Y_ps_hlsl; +blob_t convert_UV_vs_hlsl; +blob_t convert_UV_ps_hlsl; +blob_t scene_vs_hlsl; +blob_t convert_Y_ps_hlsl; blob_t scene_ps_hlsl; struct img_d3d_t : public platf::img_t { @@ -322,7 +323,7 @@ public: _init_view_port(img.width, img.height); device_ctx_p->OMSetRenderTargets(1, &scene_rt_p, nullptr); - device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); + device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(scene_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); @@ -339,15 +340,15 @@ public: _init_view_port(out_width, out_height); device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); - device_ctx_p->VSSetShader(merge_Y_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(merge_Y_ps.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->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); _init_view_port(out_width / 2, out_height / 2); device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); - device_ctx_p->VSSetShader(merge_UV_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(merge_UV_ps.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->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); @@ -406,34 +407,34 @@ public: this->out_height = out_height; vs_t::pointer vs_p; - status = device_p->CreateVertexShader(merge_Y_vs_hlsl->GetBufferPointer(), merge_Y_vs_hlsl->GetBufferSize(), nullptr, &vs_p); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &vs_p); if(status) { BOOST_LOG(error) << "Failed to create mergeY vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_Y_vs.reset(vs_p); + scene_vs.reset(vs_p); ps_t::pointer ps_p; - status = device_p->CreatePixelShader(merge_Y_ps_hlsl->GetBufferPointer(), merge_Y_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { BOOST_LOG(error) << "Failed to create mergeY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_Y_ps.reset(ps_p); + convert_Y_ps.reset(ps_p); - status = device_p->CreatePixelShader(merge_UV_ps_hlsl->GetBufferPointer(), merge_UV_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { BOOST_LOG(error) << "Failed to create mergeUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_UV_ps.reset(ps_p); + convert_UV_ps.reset(ps_p); - status = device_p->CreateVertexShader(merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), nullptr, &vs_p); + status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &vs_p); if(status) { BOOST_LOG(error) << "Failed to create mergeUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - merge_UV_vs.reset(vs_p); + convert_UV_vs.reset(vs_p); status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &ps_p); if(status) { @@ -469,7 +470,7 @@ public: input_layout_t::pointer input_layout_p; status = device_p->CreateInputLayout( &layout_desc, 1, - merge_UV_vs_hlsl->GetBufferPointer(), merge_UV_vs_hlsl->GetBufferSize(), + convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), &input_layout_p); input_layout.reset(input_layout_p); @@ -648,11 +649,11 @@ public: img_d3d_t img; - vs_t merge_UV_vs; - ps_t merge_UV_ps; - vs_t merge_Y_vs; - ps_t merge_Y_ps; + vs_t convert_UV_vs; + ps_t convert_UV_ps; + ps_t convert_Y_ps; ps_t scene_ps; + vs_t scene_vs; D3D11_VIEWPORT cursor_view; bool cursor_visible; @@ -869,27 +870,27 @@ int init() { } BOOST_LOG(info) << "Compiling shaders..."sv; - merge_Y_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeYVS.hlsl"); - if(!merge_Y_vs_hlsl) { + scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); + if(!scene_vs_hlsl) { return -1; } - merge_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeYPS.hlsl"); - if(!merge_Y_ps_hlsl) { + convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); + if(!convert_Y_ps_hlsl) { return -1; } - merge_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/MergeUVPS.hlsl"); - if(!merge_UV_ps_hlsl) { + convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); + if(!convert_UV_ps_hlsl) { return -1; } - merge_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_ASSETS_DIR "/MergeUVVS.hlsl"); - if(!merge_UV_vs_hlsl) { + convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); + if(!convert_UV_vs_hlsl) { return -1; } - scene_ps_hlsl = compile_pixel_shader(SUNSHINE_ASSETS_DIR "/scenePS.hlsl"); + scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); if(!scene_ps_hlsl) { return -1; } From 661c8260e525161d139d812a702d126f0b952def Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 8 May 2021 13:46:20 +0200 Subject: [PATCH 039/158] Fix Position for input::reset --- sunshine/stream.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index ccc6c697..a86f4cca 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -884,9 +884,6 @@ state_e state(session_t &session) { void stop(session_t &session) { while_starting_do_nothing(session.state); - - //Reset input on session stop to avoid stuck repeated keys - input::reset(); auto expected = state_e::RUNNING; auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING); if(already_stopping) { @@ -903,6 +900,9 @@ void join(session_t &session) { session.audioThread.join(); BOOST_LOG(debug) << "Waiting for control to end..."sv; session.controlEnd.view(); + //Reset input on session stop to avoid stuck repeated keys + BOOST_LOG(debug) << "Resetting Input..."sv; + input::reset(); BOOST_LOG(debug) << "Session ended"sv; } From 2970ad662c79ca4996bf0a4133eb23444f2d7685 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 9 May 2021 11:40:12 +0200 Subject: [PATCH 040/158] No more dumb pointers for initialization --- sunshine/crypto.cpp | 8 +- sunshine/platform/windows/audio.cpp | 30 +-- sunshine/platform/windows/display_base.cpp | 54 ++-- sunshine/platform/windows/display_ram.cpp | 10 +- sunshine/platform/windows/display_vram.cpp | 128 ++++------ sunshine/utility.h | 283 ++++++++++++++++----- 6 files changed, 307 insertions(+), 206 deletions(-) diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index 70a98289..398c8e09 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -187,10 +187,10 @@ x509_t x509(const std::string_view &x) { BIO_write(io.get(), x.data(), x.size()); - X509 *p = nullptr; + x509_t p; PEM_read_bio_X509(io.get(), &p, nullptr, nullptr); - return x509_t { p }; + return p; } pkey_t pkey(const std::string_view &k) { @@ -198,10 +198,10 @@ pkey_t pkey(const std::string_view &k) { BIO_write(io.get(), k.data(), k.size()); - EVP_PKEY *p = nullptr; + pkey_t p = nullptr; PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr); - return pkey_t { p }; + return p; } std::string pem(x509_t &x509) { diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index 0ba9a168..63369ccb 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -81,50 +81,43 @@ public: HRESULT status; - device_enum_t::pointer device_enum_p{}; status = CoCreateInstance( CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void **) &device_enum_p); - device_enum.reset(device_enum_p); + (void **) &device_enum); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - device_t::pointer device_p{}; - if(config::audio.sink.empty()) { status = device_enum->GetDefaultAudioEndpoint( eRender, eConsole, - &device_p); + &device); } else { std::wstring_convert, wchar_t> converter; auto wstring_device_id = converter.from_bytes(config::audio.sink); - status = device_enum->GetDevice(wstring_device_id.c_str(), &device_p); + status = device_enum->GetDevice(wstring_device_id.c_str(), &device); } - device.reset(device_p); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - audio_client_t::pointer audio_client_p{}; status = device->Activate( IID_IAudioClient, CLSCTX_ALL, nullptr, - (void **) &audio_client_p); - audio_client.reset(audio_client_p); + (void **) &audio_client); if (FAILED(status)) { BOOST_LOG(error) << "Couldn't activate audio Device [0x"sv << util::hex(status).to_string_view() << ']'; @@ -132,11 +125,8 @@ public: return -1; } - wave_format_t::pointer wave_format_p{}; - status = audio_client->GetMixFormat(&wave_format_p); - wave_format.reset(wave_format_p); - - if (FAILED(status)) { + 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; @@ -198,9 +188,7 @@ public: sample_buf = util::buffer_t { frames }; sample_buf_pos = std::begin(sample_buf); - audio_capture_t::pointer audio_capture_p {}; - status = audio_client->GetService(IID_IAudioCaptureClient, (void**)&audio_capture_p); - audio_capture.reset(audio_capture_p); + status = audio_client->GetService(IID_IAudioCaptureClient, (void**)&audio_capture); if (FAILED(status)) { BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 29b53523..8644ed88 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -90,17 +90,10 @@ int display_base_t::init() { FreeLibrary(user32); }); */ - - dxgi::factory1_t::pointer factory_p {}; - dxgi::adapter_t::pointer adapter_p {}; - dxgi::output_t::pointer output_p {}; - dxgi::device_t::pointer device_p {}; - dxgi::device_ctx_t::pointer device_ctx_p {}; HRESULT status; - status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); - factory.reset(factory_p); + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -111,7 +104,8 @@ int display_base_t::init() { auto adapter_name = converter.from_bytes(config::video.adapter_name); auto output_name = converter.from_bytes(config::video.output_name); - for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { + adapter_t::pointer adapter_p; + for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { dxgi::adapter_t adapter_tmp { adapter_p }; DXGI_ADAPTER_DESC1 adapter_desc; @@ -121,8 +115,9 @@ int display_base_t::init() { continue; } + dxgi::output_t::pointer output_p; for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { - dxgi::output_t output_tmp {output_p }; + dxgi::output_t output_tmp { output_p }; DXGI_OUTPUT_DESC desc; output_tmp->GetDesc(&desc); @@ -173,14 +168,12 @@ int display_base_t::init() { D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, - &device_p, + &device, &feature_level, - &device_ctx_p); + &device_ctx); adapter_p->Release(); - device.reset(device_p); - device_ctx.reset(device_ctx_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; @@ -216,7 +209,7 @@ int display_base_t::init() { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { - BOOST_LOG(error) << "Could not set privilege to increase GPU priority"; + BOOST_LOG(warning) << "Could not set privilege to increase GPU priority"; } } @@ -229,17 +222,15 @@ int display_base_t::init() { if (fn) { status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); if (FAILED(status)) { - BOOST_LOG(error) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance."; + BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance."; } } } - dxgi::dxgi_t::pointer dxgi_p {}; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); - dxgi::dxgi_t dxgi { dxgi_p }; - + dxgi::dxgi_t dxgi; + status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } @@ -248,25 +239,24 @@ int display_base_t::init() { // Try to reduce latency { - dxgi::dxgi1_t::pointer dxgi_p {}; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); - dxgi::dxgi1_t dxgi { dxgi_p }; - + dxgi::dxgi1_t dxgi {}; + status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - dxgi->SetMaximumFrameLatency(1); + status = dxgi->SetMaximumFrameLatency(1); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to set maximum frame latency [0x"sv << util::hex(status).to_string_view() << ']'; + } } //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD //TODO: Use IDXGIOutput5 for improved performance { - dxgi::output1_t::pointer output1_p {}; - status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p); - dxgi::output1_t output1 {output1_p }; - + dxgi::output1_t output1 {}; + status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; return -1; @@ -274,10 +264,8 @@ int display_base_t::init() { // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { - dxgi::dup_t::pointer dup_p {}; - status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p); + status = output1->DuplicateOutput((IUnknown*)device.get(), &dup.dup); if(SUCCEEDED(status)) { - dup.reset(dup_p); break; } std::this_thread::sleep_for(200ms); diff --git a/sunshine/platform/windows/display_ram.cpp b/sunshine/platform/windows/display_ram.cpp index 15d608fe..ffcbbd25 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/sunshine/platform/windows/display_ram.cpp @@ -203,9 +203,8 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise // If frame has been updated if (frame_info.LastPresentTime.QuadPart != 0) { { - texture2d_t::pointer src_p {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); - texture2d_t src{src_p}; + texture2d_t src {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); if (FAILED(status)) { BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; @@ -279,10 +278,7 @@ int display_ram_t::init() { t.Format = format; t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - dxgi::texture2d_t::pointer tex_p {}; - auto status = device->CreateTexture2D(&t, nullptr, &tex_p); - - texture.reset(tex_p); + auto status = device->CreateTexture2D(&t, nullptr, &texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index c8744b18..00e0c69d 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -106,14 +106,14 @@ blend_t make_blend(device_t::pointer device, bool enable) { rt.DestBlendAlpha = D3D11_BLEND_ZERO; } - blend_t::pointer blend_p; - auto status = device->CreateBlendState(&bdesc, &blend_p); + blend_t blend; + auto status = device->CreateBlendState(&bdesc, &blend); if(status) { BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; return nullptr; } - return blend_t { blend_p }; + return blend; } blob_t convert_UV_vs_hlsl; @@ -280,13 +280,11 @@ public: }; desc.Texture2D.MipLevels = 1; - shader_res_t::pointer cursor_res_p; - auto status = device->CreateShaderResourceView(texture, &desc, &cursor_res_p); + auto status = device->CreateShaderResourceView(texture, &desc, &img.input_res); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - img.input_res.reset(cursor_res_p); return 0; } @@ -303,26 +301,19 @@ public: }; desc.Texture2D.MipLevels = 1; - shader_res_t::pointer input_res_p; - auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &input_res_p); + auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &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; } - img.input_res.reset(input_res_p); } auto input_res_p = img.input_res.get(); - auto cursor_res_p = this->img.input_res.get(); - - auto scene_rt_p = scene_rt.get(); - auto Y_rt_p = nv12_Y_rt.get(); - auto UV_rt_p = nv12_UV_rt.get(); if(cursor_visible) { _init_view_port(img.width, img.height); - device_ctx_p->OMSetRenderTargets(1, &scene_rt_p, nullptr); + device_ctx_p->OMSetRenderTargets(1, &scene_rt, nullptr); device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(scene_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); @@ -331,7 +322,7 @@ public: device_ctx_p->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); device_ctx_p->RSSetViewports(1, &cursor_view); - device_ctx_p->PSSetShaderResources(0, 1, &cursor_res_p); + device_ctx_p->PSSetShaderResources(0, 1, &this->img.input_res); device_ctx_p->Draw(3, 0); device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); @@ -339,14 +330,14 @@ public: } _init_view_port(out_width, out_height); - device_ctx_p->OMSetRenderTargets(1, &Y_rt_p, nullptr); + device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); device_ctx_p->Draw(3, 0); _init_view_port(out_width / 2, out_height / 2); - device_ctx_p->OMSetRenderTargets(1, &UV_rt_p, nullptr); + device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); device_ctx_p->PSSetShaderResources(0, 1, &input_res_p); @@ -380,8 +371,7 @@ public: return; } - auto buf_p = color_matrix.get(); - device_ctx_p->PSSetConstantBuffers(0, 1, &buf_p); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); this->color_matrix = std::move(color_matrix); } @@ -406,46 +396,43 @@ public: this->out_width = out_width; this->out_height = out_height; - vs_t::pointer vs_p; - status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &vs_p); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); if(status) { - BOOST_LOG(error) << "Failed to create mergeY vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - scene_vs.reset(vs_p); - ps_t::pointer ps_p; - status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); if(status) { - BOOST_LOG(error) << "Failed to create mergeY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - convert_Y_ps.reset(ps_p); - status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); if(status) { - BOOST_LOG(error) << "Failed to create mergeUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - convert_UV_ps.reset(ps_p); - status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &vs_p); + status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); if(status) { - BOOST_LOG(error) << "Failed to create mergeUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - convert_UV_vs.reset(vs_p); - status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &ps_p); + status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); if(status) { BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - scene_ps.reset(ps_p); blend_disable = make_blend(device_p, false); blend_enable = make_blend(device_p, true); + if(!blend_disable || !blend_enable) { + return -1; + } + if(_init_rt(scene_sr, scene_rt, in_width, in_height, DXGI_FORMAT_B8G8R8A8_UNORM)) { return -1; } @@ -467,12 +454,10 @@ public: "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }; - input_layout_t::pointer input_layout_p; status = device_p->CreateInputLayout( &layout_desc, 1, convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), - &input_layout_p); - input_layout.reset(input_layout_p); + &input_layout); D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; @@ -484,18 +469,16 @@ public: t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010; t.BindFlags = D3D11_BIND_RENDER_TARGET; - dxgi::texture2d_t::pointer tex_p {}; - status = device_p->CreateTexture2D(&t, nullptr, &tex_p); + status = device_p->CreateTexture2D(&t, nullptr, &img.texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - img.texture.reset(tex_p); img.display = std::move(display); img.width = out_width; img.height = out_height; - img.data = (std::uint8_t*)tex_p; + img.data = (std::uint8_t*)img.texture.get(); img.row_pitch = out_width; img.pixel_pitch = 1; @@ -504,21 +487,18 @@ public: D3D11_RTV_DIMENSION_TEXTURE2D }; - render_target_t::pointer nv12_rt_p; - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_rt_p); + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - nv12_Y_rt.reset(nv12_rt_p); nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_rt_p); + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - nv12_UV_rt.reset(nv12_rt_p); D3D11_SAMPLER_DESC sampler_desc {}; sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; @@ -529,21 +509,16 @@ public: sampler_desc.MinLOD = 0; sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; - sampler_state_t::pointer sampler_state_p; - status = device_p->CreateSamplerState(&sampler_desc, &sampler_state_p); + status = device_p->CreateSamplerState(&sampler_desc, &sampler_linear); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - sampler_linear.reset(sampler_state_p); - auto sampler_linear_p = sampler_linear.get(); - auto color_matrix_buf_p = color_matrix.get(); - auto info_buf_p = info_scene.get(); device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - device_ctx_p->PSSetSamplers(0, 1, &sampler_linear_p); - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix_buf_p); - device_ctx_p->VSSetConstantBuffers(0, 1, &info_buf_p); + device_ctx_p->PSSetSamplers(0, 1, &sampler_linear); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); device_ctx_p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); device_ctx_p->IASetInputLayout(input_layout.get()); @@ -589,13 +564,12 @@ private: auto device = (device_t::pointer)data; - texture2d_t::pointer tex_p; - auto status = device->CreateTexture2D(&desc, nullptr, &tex_p); + texture2d_t tex; + auto status = device->CreateTexture2D(&desc, nullptr, &tex); if(status) { BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - texture2d_t tex { tex_p }; D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { @@ -604,26 +578,22 @@ private: }; shader_resource_desc.Texture2D.MipLevels = 1; - shader_res_t::pointer shader_res_p; - device->CreateShaderResourceView(tex_p, &shader_resource_desc, &shader_res_p); + device->CreateShaderResourceView(tex.get(), &shader_resource_desc, &shader_res); if(status) { BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - shader_res.reset(shader_res_p); D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { format, D3D11_RTV_DIMENSION_TEXTURE2D }; - render_target_t::pointer render_target_p; - device->CreateRenderTargetView(tex_p, &render_target_desc, &render_target_p); + device->CreateRenderTargetView(tex.get(), &render_target_desc, &render_target); if(status) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - render_target.reset(render_target_p); return 0; } @@ -721,16 +691,15 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - dxgi::texture2d_t::pointer tex_p {}; - auto status = device->CreateTexture2D(&t, &data, &tex_p); + texture2d_t texture; + auto status = device->CreateTexture2D(&t, &data, &texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } - texture2d_t texture { tex_p }; for(auto *hwdevice : hwdevices) { - if(hwdevice->set_cursor_texture(tex_p, t.Width, t.Height)) { + if(hwdevice->set_cursor_texture(texture.get(), t.Width, t.Height)) { return capture_e::error; } } @@ -747,15 +716,14 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec } if(frame_update_flag) { - texture2d_t::pointer src_p {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); + texture2d_t src; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); if(FAILED(status)) { BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } - texture2d_t src { src_p }; device_ctx->CopyResource(img->texture.get(), src.get()); } @@ -775,15 +743,13 @@ std::shared_ptr display_vram_t::alloc_img() { t.Format = format; t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - dxgi::texture2d_t::pointer tex_p {}; - auto status = device->CreateTexture2D(&t, nullptr, &tex_p); + auto status = device->CreateTexture2D(&t, nullptr, &img->texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; return nullptr; } - img->texture.reset(tex_p); - img->data = (std::uint8_t*)tex_p; + img->data = (std::uint8_t*)img->texture.get(); img->row_pitch = 0; img->pixel_pitch = 4; img->width = 0; @@ -813,15 +779,15 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { t.Format = format; t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - dxgi::texture2d_t::pointer tex_p {}; - auto status = device->CreateTexture2D(&t, &data, &tex_p); + dxgi::texture2d_t tex; + auto status = device->CreateTexture2D(&t, &data, &tex); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - img->texture.reset(tex_p); - img->data = (std::uint8_t*)tex_p; + img->texture = std::move(tex); + img->data = (std::uint8_t*)img->texture.get(); img->height = height; img->width = width; img->pixel_pitch = 4; diff --git a/sunshine/utility.h b/sunshine/utility.h index 0c03c51d..63be108c 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -76,37 +76,6 @@ struct __either { template using either_t = typename __either::type; -template -struct __false_v; - -template -struct __false_v>> { - static constexpr std::nullopt_t value = std::nullopt; -}; - -template -struct __false_v || instantiation_of_v || instantiation_of_v) - >> { - static constexpr std::nullptr_t value = nullptr; -}; - -template -struct __false_v>> { - static constexpr bool value = false; -}; - -template -static constexpr auto false_v = __false_v::value; - -template -using optional_t = either_t< - (std::is_same_v || - instantiation_of_v || - instantiation_of_v || - std::is_pointer_v), - T, std::optional>; - template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; @@ -362,35 +331,6 @@ auto enm(T& val) -> std::underlying_type_t& { return *reinterpret_cast*>(&val); } -template -struct Function { - typedef ReturnType (*type)(Args...); -}; - -template::type function> -struct Destroy { - typedef T pointer; - - void operator()(pointer p) { - function(p); - } -}; - -template::type function> -using safe_ptr = std::unique_ptr>; - -// You cannot specialize an alias -template::type function> -using safe_ptr_v2 = std::unique_ptr>; - -template -void c_free(T *p) { - free(p); -} - -template -using c_ptr = safe_ptr>; - inline std::int64_t from_chars(const char *begin, const char *end) { std::int64_t res {}; std::int64_t mul = 1; @@ -436,6 +376,163 @@ public: } }; +// Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself +template > +class uniq_ptr { +public: + using element_type = T; + using pointer = element_type*; + using deleter_type = D; + + constexpr uniq_ptr() noexcept : _p { nullptr } {} + constexpr uniq_ptr(std::nullptr_t) noexcept : _p { nullptr } {} + + uniq_ptr(const uniq_ptr &other) noexcept = delete; + uniq_ptr &operator=(const uniq_ptr &other) noexcept = delete; + + template + uniq_ptr(V *p) noexcept : _p { p } { + static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); + } + + template + uniq_ptr(std::unique_ptr &&uniq) noexcept : _p { uniq.release() } { + static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); + } + + template + uniq_ptr(uniq_ptr &&other) noexcept : _p { other.release() } { + static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); + } + + template + uniq_ptr &operator=(uniq_ptr &&other) noexcept { + static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); + reset(other.release()); + + return *this; + } + + template + uniq_ptr &operator=(std::unique_ptr &&uniq) noexcept { + static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); + + reset(uniq.release()); + + return *this; + } + + ~uniq_ptr() { + reset(); + } + + void reset(pointer p = pointer()) { + if(_p) { + _deleter(_p); + } + + _p = p; + } + + pointer release() { + auto tmp = _p; + _p = nullptr; + return tmp; + } + + pointer get() { + return _p; + } + + const pointer get() const { + return _p; + } + + const std::add_lvalue_reference_t operator*() const { + return *_p; + } + std::add_lvalue_reference_t operator*() { + return *_p; + } + const pointer operator->() const { + return _p; + } + pointer operator->() { + return _p; + } + pointer *operator&() const { + return &_p; + } + + pointer *operator&() { + return &_p; + } + + deleter_type& get_deleter() { + return _deleter; + } + + const deleter_type& get_deleter() const { + return _deleter; + } + + explicit operator bool() const { + return _p != nullptr; + } +protected: + pointer _p; + deleter_type _deleter; +}; + +template +bool operator==(const uniq_ptr& x, const uniq_ptr& y) { + return x.get() == y.get(); +} + +template +bool operator!=(const uniq_ptr& x, const uniq_ptr& y) { + return x.get() != y.get(); +} + +template +bool operator==(const std::unique_ptr& x, const uniq_ptr& y) { + return x.get() == y.get(); +} + +template +bool operator!=(const std::unique_ptr& x, const uniq_ptr& y) { + return x.get() != y.get(); +} + +template +bool operator==(const uniq_ptr& x, const std::unique_ptr& y) { + return x.get() == y.get(); +} + +template +bool operator!=(const uniq_ptr& x, const std::unique_ptr& y) { + return x.get() != y.get(); +} + +template +bool operator==(const uniq_ptr& x, std::nullptr_t) { + return !(bool)x; +} + +template +bool operator!=(const uniq_ptr& x, std::nullptr_t) { + return (bool)x; +} + +template +bool operator==(std::nullptr_t, const uniq_ptr& y) { + return !(bool)y; +} + +template +bool operator!=(std::nullptr_t, const uniq_ptr& y) { + return (bool)y; +} template class wrap_ptr { @@ -510,6 +607,43 @@ private: pointer _p; }; +template +struct __false_v; + +template +struct __false_v>> { + static constexpr std::nullopt_t value = std::nullopt; +}; + +template +struct __false_v || + instantiation_of_v || + instantiation_of_v || + instantiation_of_v + ) + >> { + static constexpr std::nullptr_t value = nullptr; +}; + +template +struct __false_v>> { + static constexpr bool value = false; +}; + +template +static constexpr auto false_v = __false_v::value; + +template +using optional_t = either_t< + (std::is_same_v || + instantiation_of_v || + instantiation_of_v || + instantiation_of_v || + std::is_pointer_v), + T, std::optional>; + template class buffer_t { public: @@ -569,6 +703,35 @@ T either(std::optional &&l, T &&r) { return std::forward(r); } +template +struct Function { + typedef ReturnType (*type)(Args...); +}; + +template::type function> +struct Destroy { + typedef T pointer; + + void operator()(pointer p) { + function(p); + } +}; + +template::type function> +using safe_ptr = uniq_ptr>; + +// You cannot specialize an alias +template::type function> +using safe_ptr_v2 = uniq_ptr>; + +template +void c_free(T *p) { + free(p); +} + +template +using c_ptr = safe_ptr>; + namespace endian { template struct endianness { From 6f428eb316310501dcd5f5e08500a2b10b968eef Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 9 May 2021 11:56:53 +0200 Subject: [PATCH 041/158] Ensure no input remains in the task_pool before resetting --- sunshine/input.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 402d4ce3..9a41dd03 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -402,11 +402,18 @@ void passthrough(std::shared_ptr &input, std::vector &&in task_pool.push(passthrough_helper, input, util::cmove(input_data)); } -void reset(){ - for(auto& kp : key_press){ +void reset() { + if(task_id) { + task_pool.cancel(task_id); + } + + // Ensure input is synchronous + task_pool.push([]() { + for(auto& kp : key_press) { platf::keyboard(platf_input, kp.first & 0x00FF, true); key_press[kp.first] = false; } + }); } void init() { From ade2ef3a15f4b2a3ad9e898203cfb8e238845840 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 9 May 2021 16:19:05 +0200 Subject: [PATCH 042/158] Fix profile values for amfvce --- sunshine/video.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index ad298922..c6befe8d 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -58,20 +58,6 @@ enum class profile_hevc_e : int { }; } -namespace amd { - -enum class profile_h264_e : int { - main, - high, - constrained_baseline, - constrained_high, -}; - -enum class profile_hevc_e : int { - main, -}; -} - using ctx_t = util::safe_ptr; using frame_t = util::safe_ptr; using buffer_t = util::safe_ptr; @@ -303,7 +289,7 @@ static encoder_t nvenc { static encoder_t amdvce { "amdvce"sv, - { (int)amd::profile_h264_e::high, (int)amd::profile_hevc_e::main }, + { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN }, AV_HWDEVICE_TYPE_D3D11VA, AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, From 377b0868827e1a4120704bc1ce82a96efe8fdf71 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 9 May 2021 16:37:40 +0200 Subject: [PATCH 043/158] Fix amd_rc config options --- assets/sunshine.conf | 12 +++++------- sunshine/config.cpp | 16 ++++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 356acd79..12377eda 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -183,13 +183,11 @@ # amd_preset = balanced # ####### rate control ##### -# auto -- let ffmpeg decide rate control -# constqp -- constant QP mode -# vbr -- variable bitrate -# cbr -- constant bitrate -# cbr_hq -- cbr high quality -# cbr_ld_hq -- cbr low delay high quality -# vbr_hq -- vbr high quality +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr_latency -- Latency Constrained Variable Bitrate +# vbr_peak -- Peak Contrained Variable Bitrate +# cbr -- constant bitrate ########################## # amd_rc = auto diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 88cc4502..9408578c 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -94,12 +94,10 @@ enum quality_e : int { }; enum rc_e : int { - constqp = 0x0, /**< Constant QP mode */ - vbr = 0x1, /**< Variable bitrate mode */ - cbr = 0x2, /**< Constant bitrate mode */ - cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ - cbr_hq = 0x10, /**< CBR, high quality (slower) */ - vbr_hq = 0x20 /**< VBR, high quality (slower) */ + constqp, /**< Constant QP mode */ + vbr_latency, /**< Latency Constrained Variable Bitrate */ + vbr_peak, /**< Peak Contrained Variable Bitrate */ + cbr, /**< Constant bitrate mode */ }; enum coder_e : int { @@ -121,11 +119,9 @@ std::optional quality_from_view(const std::string_view &quality) { std::optional rc_from_view(const std::string_view &rc) { #define _CONVERT_(x) if(rc == #x##sv) return x _CONVERT_(constqp); - _CONVERT_(vbr); + _CONVERT_(vbr_latency); + _CONVERT_(vbr_peak); _CONVERT_(cbr); - _CONVERT_(cbr_hq); - _CONVERT_(vbr_hq); - _CONVERT_(cbr_ld_hq); #undef _CONVERT_ return std::nullopt; } From 4fe90dcbd66a61157f75e1d6f6847a03039658bf Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 9 May 2021 18:55:34 +0200 Subject: [PATCH 044/158] Started Work on Web UI --- CMakeLists.txt | 2 + assets/web/apps.html | 124 +++++++++++++++++++ assets/web/clients.html | 3 + assets/web/config.html | 4 + assets/web/header.html | 45 +++++++ assets/web/index.html | 4 + assets/web/pin.html | 22 ++++ sunshine/confighttp.cpp | 262 ++++++++++++++++++++++++++++++++++++++++ sunshine/confighttp.h | 20 +++ sunshine/main.cpp | 17 +-- sunshine/nvhttp.h | 4 +- sunshine/process.cpp | 5 + 12 files changed, 498 insertions(+), 14 deletions(-) create mode 100644 assets/web/apps.html create mode 100644 assets/web/clients.html create mode 100644 assets/web/config.html create mode 100644 assets/web/header.html create mode 100644 assets/web/index.html create mode 100644 assets/web/pin.html create mode 100644 sunshine/confighttp.cpp create mode 100644 sunshine/confighttp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1347b7d7..33e09423 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ set(SUNSHINE_TARGET_FILES sunshine/crypto.h sunshine/nvhttp.cpp sunshine/nvhttp.h + sunshine/confighttp.cpp + sunshine/confighttp.h sunshine/rtsp.cpp sunshine/rtsp.h sunshine/stream.cpp diff --git a/assets/web/apps.html b/assets/web/apps.html new file mode 100644 index 00000000..d63f8260 --- /dev/null +++ b/assets/web/apps.html @@ -0,0 +1,124 @@ +
+

Applications

+
Applications are refreshed only when Client is restarted
+ + + + + + + + + + + + + +
NameActions
{{app.name}} +
+
+
+ + +
Application Name, as shown on Moonlight
+
+
+ + +
The file where the output of the command is stored, if it is not specified, the output is ignored
+
+
+ +
A list of commands to be run before/after the application.
If any of the prep-commands fail, starting the application is aborted
+ + + + + + + + + + + + + +
DoUndo
+ +
+
+ + +
The main application, if it is not specified, a processs is started that sleeps indefinitely
+
+
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/assets/web/clients.html b/assets/web/clients.html new file mode 100644 index 00000000..c2d03025 --- /dev/null +++ b/assets/web/clients.html @@ -0,0 +1,3 @@ +
+

Clients

+
\ No newline at end of file diff --git a/assets/web/config.html b/assets/web/config.html new file mode 100644 index 00000000..c63ec39d --- /dev/null +++ b/assets/web/config.html @@ -0,0 +1,4 @@ +
+

Hello, Sunshine!

+

Placeholer for config page

+
\ No newline at end of file diff --git a/assets/web/header.html b/assets/web/header.html new file mode 100644 index 00000000..59c11c2f --- /dev/null +++ b/assets/web/header.html @@ -0,0 +1,45 @@ + + + + + + + + Sunshine + + + + + + + \ No newline at end of file diff --git a/assets/web/index.html b/assets/web/index.html new file mode 100644 index 00000000..376f8613 --- /dev/null +++ b/assets/web/index.html @@ -0,0 +1,4 @@ +
+

Hello, Sunshine!

+

Sunshine is a Gamestream host for Moonlight

+
\ No newline at end of file diff --git a/assets/web/pin.html b/assets/web/pin.html new file mode 100644 index 00000000..811a1796 --- /dev/null +++ b/assets/web/pin.html @@ -0,0 +1,22 @@ +
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp new file mode 100644 index 00000000..aa923f11 --- /dev/null +++ b/sunshine/confighttp.cpp @@ -0,0 +1,262 @@ +// +// Created by TheElixZammuto on 2021-05-09. +// TODO: Authentication, better handling of routes common to nvhttp, cleanup + +#include "process.h" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include "config.h" +#include "utility.h" +#include "rtsp.h" +#include "crypto.h" +#include "confighttp.h" +#include "platform/common.h" +#include "network.h" +#include "nvhttp.h" +#include "uuid.h" +#include "main.h" + +std::string read_file(std::string path); + +namespace confighttp +{ + using namespace std::literals; + constexpr auto PORT_HTTP = 47990; + + namespace fs = std::filesystem; + namespace pt = boost::property_tree; + + using https_server_t = SimpleWeb::Server; + + using args_t = SimpleWeb::CaseInsensitiveMultimap; + using resp_https_t = std::shared_ptr::Response>; + using req_https_t = std::shared_ptr::Request>; + + enum class op_e + { + ADD, + REMOVE + }; + + template + void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + pt::ptree tree; + tree.put("root..status_code", 404); + + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); + } + + void getIndexPage(resp_https_t response, req_https_t request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "index.html"); + response->write(header + content); + } + + template + void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "pin.html"); + response->write(header + content); + } + + template + void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "apps.html"); + response->write(header + content); + } + + template + void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "clients.html"); + response->write(header + content); + } + + template + void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "config.html"); + response->write(header + content); + } + + void getApps(resp_https_t response, req_https_t request) + { + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + response->write(content); + } + + void saveApp(resp_https_t response, req_https_t request) + { + std::stringstream ss; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree,fileTree; + try { + //TODO: Input Validation + pt::read_json(ss, inputTree); + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = inputTree.get("index"); + BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); + if(inputTree.get_child("prep-cmd").empty())inputTree.erase("prep-cmd"); + inputTree.erase("index"); + if(index == -1){ + apps_node.push_back(std::make_pair("",inputTree)); + } else { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto& kv : apps_node) { + if(i == index){ + newApps.push_back(std::make_pair("",inputTree)); + } else { + newApps.push_back(std::make_pair("",kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps",newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status","true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } catch (std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status","false"); + outputTree.put("error","Invalid Input JSON"); + return; + } + } + + void deleteApp(resp_https_t response, req_https_t request) + { + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree fileTree; + try { + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = stoi(request->path_match[1]); + BOOST_LOG(info) << index; + if(index <= 0){ + outputTree.put("status","false"); + outputTree.put("error","Invalid Index"); + return; + } else { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto& kv : apps_node) { + if(i != index){ + newApps.push_back(std::make_pair("",kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps",newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status","true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } catch (std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status","false"); + outputTree.put("error","Invalid File JSON"); + return; + } + } + + void start(std::shared_ptr shutdown_event) + { + auto ctx = std::make_shared(boost::asio::ssl::context::tls); + ctx->use_certificate_chain_file(config::nvhttp.cert); + ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); + https_server_t http_server { ctx, 0 }; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; + + try + { + http_server.bind(); + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + } + catch (boost::system::system_error &err) + { + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); + + shutdown_event->raise(true); + return; + } + + std::thread tcp{&https_server_t::accept_and_run, &http_server}; + + // Wait for any event + shutdown_event->view(); + + http_server.stop(); + + tcp.join(); + } +} + +std::string read_file(std::string path) +{ + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; +} \ No newline at end of file diff --git a/sunshine/confighttp.h b/sunshine/confighttp.h new file mode 100644 index 00000000..d3b9e85e --- /dev/null +++ b/sunshine/confighttp.h @@ -0,0 +1,20 @@ +// +// Created by loki on 6/3/19. +// + +#ifndef SUNSHINE_CONFIGHTTP_H +#define SUNSHINE_CONFIGHTTP_H + +#include +#include + +#include "thread_safe.h" + +#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/" + + +namespace confighttp { + void start(std::shared_ptr shutdown_event); +} + +#endif //SUNSHINE_CONFIGHTTP_H diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c21f81fc..2938e69f 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -17,6 +17,7 @@ #include "video.h" #include "input.h" #include "nvhttp.h" +#include "confighttp.h" #include "rtsp.h" #include "config.h" #include "thread_pool.h" @@ -124,19 +125,8 @@ int main(int argc, char *argv[]) { shutdown_event->raise(true); }); - auto proc_opt = proc::parse(config::stream.file_apps); - if(!proc_opt) { - return 7; - } - - { - proc::ctx_t ctx; - ctx.name = "Desktop"s; - proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx)); - } - - proc::proc = std::move(*proc_opt); - + proc::refresh(config::stream.file_apps); + auto deinit_guard = platf::init(); input::init(); reed_solomon_init(); @@ -147,6 +137,7 @@ int main(int argc, char *argv[]) { task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; + std::thread configThread { confighttp::start, shutdown_event }; stream::rtpThread(shutdown_event); httpThread.join(); diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index eb90540a..9958f193 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -7,7 +7,8 @@ #include #include - +#include +#include #include "thread_safe.h" #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" @@ -16,6 +17,7 @@ namespace nvhttp { void start(std::shared_ptr shutdown_event); +template void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); } #endif //SUNSHINE_NVHTTP_H diff --git a/sunshine/process.cpp b/sunshine/process.cpp index ec2aa2fe..c61b2481 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -302,6 +302,11 @@ void refresh(const std::string &file_name) { auto proc_opt = proc::parse(file_name); if(proc_opt) { + { + proc::ctx_t ctx; + ctx.name = "Desktop"s; + proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx)); + } proc = std::move(*proc_opt); } } From 9c41972b65c4d36b10091b325227182118500f3c Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 10 May 2021 01:02:24 +0200 Subject: [PATCH 045/158] minor modification for sunshine.service --- pre-compiled | 2 +- sunshine.service.in | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pre-compiled b/pre-compiled index afd9a9bb..bbf56474 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit afd9a9bbfc6ee1a064b0c1f9210bc20b2170c416 +Subproject commit bbf5647402859945dee541a300b22702a2ee9271 diff --git a/sunshine.service.in b/sunshine.service.in index be1459ca..c0c38289 100644 --- a/sunshine.service.in +++ b/sunshine.service.in @@ -2,8 +2,7 @@ Description=Sunshine Gamestream Server for Moonlight [Service] -ExecStartPre=/bin/sh -c "test -d %E/sunshine || cp -r '@SUNSHINE_ASSETS_DIR@' '%E/sunshine'" -ExecStart=@SUNSHINE_EXECUTABLE_PATH@ %E/sunshine/sunshine.conf +ExecStart=@SUNSHINE_EXECUTABLE_PATH@ [Install] WantedBy=graphical-session.target From 1ba8da978012f67bdad5468a9cfdbf47562f5969 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 10 May 2021 01:07:24 +0200 Subject: [PATCH 046/158] Fix error in README file --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fb3c15ce..102a1207 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,12 @@ sunshine needs access to uinput to create mouse and gamepad events: - Save the file and exit 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: +- `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 add it, do the following: - 1. Modify `sunshine.conf` to point to the sunshine executable if nessecary - 2. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/` - 3. Starting + `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: @@ -118,8 +117,8 @@ sunshine needs access to uinput to create mouse and gamepad events: "cmd":"command to open app", "prep-cmd":[ { - "do":"somecommand", - "undo":"undothatcommand" + "do":"somecommand", + "undo":"undothatcommand" } ] } From 25309f21ee48824139fd8679a739bf5fb2d34904 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 10 May 2021 01:31:08 +0200 Subject: [PATCH 047/158] Fix appveyor build --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e0115dab..d737446a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ environment: install: - 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" before_build: @@ -20,7 +20,7 @@ before_build: build_script: - cmd: set OLDPATH=%PATH% - 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" .. - sh: make -j$(nproc) - cmd: mingw32-make -j2 From b97c902d104479c11d1a92b5a70be54b5d657090 Mon Sep 17 00:00:00 2001 From: Loki Date: Mon, 10 May 2021 15:04:41 +0200 Subject: [PATCH 048/158] print absolute mouse movement packets --- CMakeLists.txt | 9 +++++++++ moonlight-common-c | 2 +- sunshine/input.cpp | 31 +++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55d04342..0dcbe718 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,14 @@ project(Sunshine) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(Simple-Web-Server) + +if(WIN32) + # Ugly hack to compile with #include + add_compile_definitions( + QOS_FLOWID=UINT32 + PQOS_FLOWID=UINT32* + QOS_NON_ADAPTIVE_FLOW=2) +endif() add_subdirectory(moonlight-common-c/enet) find_package(Threads REQUIRED) @@ -70,6 +78,7 @@ if(WIN32) libstdc++.a libwinpthread.a libssp.a + Qwave winmm ksuser wsock32 diff --git a/moonlight-common-c b/moonlight-common-c index cfeb0ffd..5d09d43b 160000 --- a/moonlight-common-c +++ b/moonlight-common-c @@ -1 +1 @@ -Subproject commit cfeb0ffd90992ee0fa4b6b4a179652e3e2786873 +Subproject commit 5d09d43b0866d9fa30cc1741f9e10de5420deaad diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 9a41dd03..a9aee2c4 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -2,6 +2,8 @@ // Created by loki on 6/20/19. // +// define uint32_t for +#include extern "C" { #include } @@ -86,12 +88,22 @@ struct input_t { using namespace std::literals; -void print(PNV_MOUSE_MOVE_PACKET packet) { +void print(PNV_REL_MOUSE_MOVE_PACKET packet) { 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 << "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) { @@ -138,8 +150,11 @@ void print(void *input) { int input_type = util::endian::big(*(int*)input); switch(input_type) { - case PACKET_TYPE_MOUSE_MOVE: - print((PNV_MOUSE_MOVE_PACKET)input); + case PACKET_TYPE_REL_MOUSE_MOVE: + print((PNV_REL_MOUSE_MOVE_PACKET)input); + break; + case PACKET_TYPE_ABS_MOUSE_MOVE: + print((PNV_ABS_MOUSE_MOVE_PACKET)input); break; case PACKET_TYPE_MOUSE_BUTTON: print((PNV_MOUSE_BUTTON_PACKET)input); @@ -162,7 +177,7 @@ void print(void *input) { } } -void passthrough(platf::input_t &input, PNV_MOUSE_MOVE_PACKET packet) { +void passthrough(platf::input_t &input, PNV_REL_MOUSE_MOVE_PACKET packet) { display_cursor = true; platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY)); @@ -374,8 +389,8 @@ void passthrough_helper(std::shared_ptr input, std::vector Date: Tue, 11 May 2021 18:01:56 +0200 Subject: [PATCH 049/158] absolute mouse coordinate support for single monitor on Linux --- sunshine/input.cpp | 77 ++++++++++++++++++----- sunshine/input.h | 2 +- sunshine/platform/common.h | 1 + sunshine/platform/linux/input.cpp | 96 +++++++++++++++++++++++++---- sunshine/platform/windows/input.cpp | 1 + sunshine/stream.cpp | 2 +- 6 files changed, 152 insertions(+), 27 deletions(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index a9aee2c4..81a4920a 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -19,6 +19,9 @@ extern "C" { namespace input { 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 { NONE, DOWN, @@ -80,10 +83,12 @@ struct gamepad_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::vector gamepads; + + util::ThreadPool::task_id_t mouse_left_button_timeout; }; using namespace std::literals; @@ -177,23 +182,61 @@ void print(void *input) { } } -void passthrough(platf::input_t &input, PNV_REL_MOUSE_MOVE_PACKET packet) { +void passthrough(std::shared_ptr &input, PNV_REL_MOUSE_MOVE_PACKET packet) { 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, 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; + } + + platf::abs_mouse(platf_input, util::endian::big(packet->x), util::endian::big(packet->y)); } void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet) { auto constexpr BUTTON_RELEASED = 0x09; + auto constexpr BUTTON_LEFT = 0x01; display_cursor = true; + auto release = packet->action == BUTTON_RELEASED; + auto button = util::endian::big(packet->button); if(button > 0 && button < mouse_press.size()) { - mouse_press[button] = packet->action != BUTTON_RELEASED; + mouse_press[button] = !release; } - platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED); + /*/ + * 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) { + input->mouse_left_button_timeout = task_pool.pushDelayed([=]() { + platf::button_mouse(platf_input, button, release); + + input->mouse_left_button_timeout = nullptr; + }, 10ms).task_id; + + return; + } + + platf::button_mouse(platf_input, button, release); } void repeat_key(short key_code) { @@ -238,10 +281,10 @@ void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { 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; - platf::scroll(input, util::endian::big(packet->scrollAmt1)); + platf::scroll(platf_input, util::endian::big(packet->scrollAmt1)); } int updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state) { @@ -390,7 +433,10 @@ void passthrough_helper(std::shared_ptr input, std::vector input, std::vector &input, std::vector &&in task_pool.push(passthrough_helper, input, util::cmove(input_data)); } -void reset() { - if(task_id) { - task_pool.cancel(task_id); - } +void reset(std::shared_ptr &input) { + task_pool.cancel(task_id); + task_pool.cancel(input->mouse_left_button_timeout); - // Ensure input is synchronous + // Ensure input is synchronous, by using the task_pool task_pool.push([]() { + for(int x = 0; x < mouse_press.size(); ++x) { + platf::button_mouse(platf_input, x, true); + } + for(auto& kp : key_press) { platf::keyboard(platf_input, kp.first & 0x00FF, true); key_press[kp.first] = false; diff --git a/sunshine/input.h b/sunshine/input.h index e1d4c82c..71e1805c 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -11,7 +11,7 @@ namespace input { struct input_t; void print(void *input); -void reset(); +void reset(std::shared_ptr &input); void passthrough(std::shared_ptr &input, std::vector &&input_data); void init(); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index fa6b3be7..c8675bfc 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -146,6 +146,7 @@ std::shared_ptr display(dev_type_e hwdevice_type); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); +void abs_mouse(input_t &input, int x, int y); void button_mouse(input_t &input, int button, bool release); void scroll(input_t &input, int distance); void keyboard(input_t &input, uint16_t modcode, bool release); diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 6bb5d495..df34e6e8 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -31,6 +31,15 @@ using keyboard_t = util::safe_ptr_v2; struct input_raw_t { 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() { std::filesystem::path mouse_path { "sunshine_mouse"sv }; @@ -55,9 +64,7 @@ public: } int create_mouse() { - libevdev_uinput *buf {}; - int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf); - mouse_input.reset(buf); + int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input); if(err) { BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err); @@ -69,13 +76,24 @@ public: 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) { 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, &buf); + int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input); - input.reset(buf); gamepad_state = gamepad_state_t {}; if(err) { @@ -96,6 +114,7 @@ public: } void clear() { + clear_touchscreen(); clear_mouse(); for(int x = 0; x < gamepads.size(); ++x) { clear_gamepad(x); @@ -106,16 +125,28 @@ public: clear(); } - evdev_t gamepad_dev; - std::vector> gamepads; - - evdev_t mouse_dev; uinput_t mouse_input; + uinput_t touch_input; + + evdev_t gamepad_dev; + evdev_t touch_dev; + evdev_t mouse_dev; keyboard_t keyboard; }; +void abs_mouse(input_t &input, int x, int y) { + auto touchscreen = ((input_raw_t*)input.get())->touch_input.get(); + + libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, x); + libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, 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) { auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); @@ -409,6 +440,48 @@ evdev_t mouse() { 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, + 1919, + 1, + 0, + 28 + }; + + input_absinfo absy { + 0, + 0, + 1199, + 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 dev { libevdev_new() }; @@ -486,10 +559,11 @@ input_t input() { // Ensure starting from clean slate gp.clear(); + gp.touch_dev = touchscreen(); gp.mouse_dev = mouse(); gp.gamepad_dev = x360(); - if(gp.create_mouse()) { + if(gp.create_mouse() || gp.create_touchscreen()) { log_flush(); std::abort(); } diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 284f3c83..d0af736e 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -165,6 +165,7 @@ input_t input() { return result; } +void abs_mouse(input_t &input, int x, int y) {} void move_mouse(input_t &input, int deltaX, int deltaY) { INPUT i {}; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index a86f4cca..c505fc0a 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -902,7 +902,7 @@ void join(session_t &session) { session.controlEnd.view(); //Reset input on session stop to avoid stuck repeated keys BOOST_LOG(debug) << "Resetting Input..."sv; - input::reset(); + input::reset(session.input); BOOST_LOG(debug) << "Session ended"sv; } From 92cd8648fae1e1f1dacaf4dfe8e7330b339ac4fe Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 11 May 2021 20:10:33 +0200 Subject: [PATCH 050/158] Some refactoring for linux/display.cpp --- sunshine/platform/linux/display.cpp | 211 +++++++++++++--------------- 1 file changed, 94 insertions(+), 117 deletions(-) diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index cde96cf7..39fbd616 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -37,12 +37,15 @@ void freeX(XFixesCursorImage*); using ifaddr_t = util::safe_ptr; using xcb_connect_t = util::safe_ptr; using xcb_img_t = util::c_ptr; -using xcb_cursor_img = util::c_ptr; using xdisplay_t = util::safe_ptr_v2; using ximg_t = util::safe_ptr; using xcursor_t = util::safe_ptr; +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 { public: shm_id_t() : id { -1 } {} @@ -62,24 +65,16 @@ public: class shm_data_t { public: - shm_data_t() : data { (void*) -1 } - { - } - shm_data_t(void *data) : data { data } - { + shm_data_t() : data { (void*) -1 } {} + shm_data_t(void *data) : data { data } {} + + shm_data_t(shm_data_t &&other) noexcept : data(other.data) { + other.data = (void*)-1; } - shm_data_t(shm_data_t &&other) noexcept : data(other.data) - { - other.data = (void*) -1; - } - - ~shm_data_t() - { - if ((std::uintptr_t) data != -1) - { + ~shm_data_t() { + if((std::uintptr_t) data != -1) { shmdt(data); - data = (void*) -1; } } @@ -91,8 +86,7 @@ struct x11_img_t: public img_t { }; struct shm_img_t: public img_t { - ~shm_img_t() override - { + ~shm_img_t() override { delete[] data; data = nullptr; } @@ -102,8 +96,7 @@ void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { xcursor_t overlay { XFixesGetCursorImage(display) }; if (!overlay) { - BOOST_LOG(error) - << "Couldn't get cursor from XFixesGetCursorImage"sv; + BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; return; } @@ -113,10 +106,10 @@ void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { overlay->x -= offsetX; overlay->y -= offsetY; - overlay->x = std::max((short) 0, overlay->x); - overlay->y = std::max((short) 0, overlay->y); + overlay->x = std::max((short)0, overlay->x); + overlay->y = std::max((short)0, overlay->y); - auto pixels = (int*) img.data; + auto pixels = (int*)img.data; auto screen_height = img.height; auto screen_width = img.width; @@ -154,25 +147,25 @@ struct x11_attr_t: public display_t Window xwindow; 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; /* * Offsets for when streaming a specific monitor. By default, they are 0. */ - int displayOffsetX,displayOffsetY; + int displayOffsetX, displayOffsetY; - x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { } - { + x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow { }, xattr { }, displayOffsetX { 0 }, displayOffsetY { 0 } { XInitThreads(); - if (!xdisplay) { - BOOST_LOG(fatal) << "Could not open x11 display"sv; - log_flush(); - std::abort(); + } + + int init() { + if(!xdisplay) { + BOOST_LOG(error) << "Could not open X11 display"sv; + return -1; } xwindow = DefaultRootWindow(xdisplay.get()); @@ -184,38 +177,30 @@ struct x11_attr_t: public display_t streamedMonitor = (int)util::from_view(config::video.output_name); } - //If the value has been set at all - if (streamedMonitor != -1) { - - BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv; - XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); - // This is the key right here. Use XRRScreenResources::noutput + if(streamedMonitor != -1) { + BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv; + screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) }; int output = screenr->noutput; - if (streamedMonitor >= output) { - BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv; + if(streamedMonitor >= output) { + 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]); - if (NULL == out_info || out_info->connection != RR_Connected) - { - BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv; - } - else - { - XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc); - BOOST_LOG(info)<<"Streaming display: "<name<<" with res "<width<<" x "<height<<+" offset by "<x<<" x "<y<<"."sv; - width = crt_info -> width; - height = crt_info -> height; - displayOffsetX = crt_info -> x; - displayOffsetY = crt_info -> y; - - XRRFreeCrtcInfo(crt_info); - } - XRRFreeOutputInfo(out_info); + output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[streamedMonitor]) }; + 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; + return -1; } - XRRFreeScreenResources(screenr); + + crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), out_info->crtc) }; + BOOST_LOG(info) + << "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; + height = crt_info->height; + displayOffsetX = crt_info->x; + displayOffsetY = crt_info->y; } else { width = xattr.width; @@ -224,13 +209,14 @@ struct x11_attr_t: public display_t lastWidth = xattr.width; lastHeight = xattr.height; + + return 0; } /** * Called when the display attributes should change. */ - void refresh() - { + void refresh() { XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's } @@ -242,7 +228,7 @@ struct x11_attr_t: public display_t BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; return capture_e::reinit; } - XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) }; + XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width, height, AllPlanes, ZPixmap) }; auto img_out = (x11_img_t*) img_out_base; img_out->width = img->width; @@ -253,7 +239,7 @@ struct x11_attr_t: public display_t img_out->img.reset(img); if (cursor) { - blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY); + blend_cursor(xdisplay.get(), *img_out_base, displayOffsetX, displayOffsetY); } return capture_e::ok; @@ -288,21 +274,21 @@ struct shm_attr_t: public x11_attr_t { } shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } { - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; } ~shm_attr_t() override { - 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 { //The whole X server changed, so we gotta reinit everything - if (xattr.width != lastWidth || xattr.height != lastHeight) { + if(xattr.width != lastWidth || xattr.height != lastHeight) { BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv; return capture_e::reinit; } 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, displayOffsetX, displayOffsetY, 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) }; if (!img_reply) { @@ -314,7 +300,7 @@ struct shm_attr_t: public x11_attr_t { std::copy_n((std::uint8_t*) data.data, frame_size(), img->data); if (cursor) { - blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY); + blend_cursor(shm_xdisplay.get(), *img, displayOffsetX, displayOffsetY); } return capture_e::ok; @@ -337,17 +323,18 @@ struct shm_attr_t: public x11_attr_t { } int init() { + if(x11_attr_t::init()) { + return 1; + } + shm_xdisplay.reset(XOpenDisplay(nullptr)); xcb.reset(xcb_connect(nullptr, nullptr)); - if (xcb_connection_has_error(xcb.get())) - { + if(xcb_connection_has_error(xcb.get())) { return -1; } - if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) - { - BOOST_LOG(error) - << "Missing SHM extension"sv; + if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) { + BOOST_LOG(error) << "Missing SHM extension"sv; return -1; } @@ -357,30 +344,20 @@ struct shm_attr_t: public x11_attr_t { seg = xcb_generate_id(xcb.get()); shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); - if (shm_id.id == -1) - { - BOOST_LOG(error) - << "shmget failed"sv; + if(shm_id.id == -1) { + BOOST_LOG(error) << "shmget failed"sv; return -1; } xcb_shm_attach(xcb.get(), seg, shm_id.id, false); data.data = shmat(shm_id.id, nullptr, 0); - if ((uintptr_t) data.data == -1) - { - BOOST_LOG(error) - << "shmat failed"sv; + if((uintptr_t)data.data == -1) { + BOOST_LOG(error) << "shmat failed"sv; 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; } @@ -401,10 +378,8 @@ struct mic_attr_t: public mic_t { 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); + 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; } @@ -413,29 +388,32 @@ struct mic_attr_t: public mic_t { } }; -std::shared_ptr shm_display() { - auto shm = std::make_shared(); - - if (shm->init()) { - return nullptr; - } - - return shm; -} - std::shared_ptr 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; 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(); - if (!shm_disp) { - return std::make_shared(); //Fallback to standard way if else + auto status = shm_disp->init(); + if(status > 0) { + // x11_attr_t::init() failed, don't bother trying again. + return nullptr; } - return shm_disp; + if(status == 0) { + return shm_disp; + } + + // Fallback + auto x11_disp = std::make_shared(); + if(x11_disp->init()) { + return nullptr; + } + + return x11_disp; } std::unique_ptr microphone(std::uint32_t sample_rate) { @@ -494,20 +472,19 @@ std::pair from_sockaddr_ex(const sockaddr *const ip_ auto family = ip_addr->sa_family; std::uint16_t port; - if (family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); port = ((sockaddr_in6*) ip_addr)->sin6_port; } - if (family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); port = ((sockaddr_in*) ip_addr)->sin_port; } - return - { port, std::string {data}}; + return { port, std::string {data} }; } std::string get_mac_address(const std::string_view &address) { @@ -522,8 +499,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; } From 04421d84a34c99598c0c10747103cd17807fbe9b Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Tue, 11 May 2021 22:19:29 +0200 Subject: [PATCH 051/158] Fix Indentations and Shutdown Handling --- sunshine/confighttp.cpp | 456 ++++++++++++++++++++++------------------ 1 file changed, 246 insertions(+), 210 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index aa923f11..8241c9fe 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -30,233 +30,269 @@ std::string read_file(std::string path); namespace confighttp { - using namespace std::literals; - constexpr auto PORT_HTTP = 47990; +using namespace std::literals; +constexpr auto PORT_HTTP = 47990; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; - using https_server_t = SimpleWeb::Server; +using https_server_t = SimpleWeb::Server; - using args_t = SimpleWeb::CaseInsensitiveMultimap; - using resp_https_t = std::shared_ptr::Response>; - using req_https_t = std::shared_ptr::Request>; +using args_t = SimpleWeb::CaseInsensitiveMultimap; +using resp_https_t = std::shared_ptr::Response>; +using req_https_t = std::shared_ptr::Request>; - enum class op_e +enum class op_e +{ + ADD, + REMOVE +}; + +template +void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + pt::ptree tree; + tree.put("root..status_code", 404); + + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); +} + +void getIndexPage(resp_https_t response, req_https_t request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "index.html"); + response->write(header + content); +} + +template +void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "pin.html"); + response->write(header + content); +} + +template +void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "apps.html"); + response->write(header + content); +} + +template +void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "clients.html"); + response->write(header + content); +} + +template +void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "config.html"); + response->write(header + content); +} + +void getApps(resp_https_t response, req_https_t request) +{ + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + response->write(content); +} + +void saveApp(resp_https_t response, req_https_t request) +{ + std::stringstream ss; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree, fileTree; + try + { + //TODO: Input Validation + pt::read_json(ss, inputTree); + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = inputTree.get("index"); + BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); + if (inputTree.get_child("prep-cmd").empty()) + inputTree.erase("prep-cmd"); + inputTree.erase("index"); + if (index == -1) { - ADD, - REMOVE - }; - - template - void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - pt::ptree tree; - tree.put("root..status_code", 404); - - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - - *response << "HTTP/1.1 404 NOT FOUND\r\n" - << data.str(); + apps_node.push_back(std::make_pair("", inputTree)); } - - void getIndexPage(resp_https_t response, req_https_t request) + else { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "index.html"); - response->write(header + content); - } - - template - void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "pin.html"); - response->write(header + content); - } - - template - void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "apps.html"); - response->write(header + content); - } - - template - void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "clients.html"); - response->write(header + content); - } - - template - void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "config.html"); - response->write(header + content); - } - - void getApps(resp_https_t response, req_https_t request) - { - std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - response->write(content); - } - - void saveApp(resp_https_t response, req_https_t request) - { - std::stringstream ss; - ss << request->content.rdbuf(); - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree inputTree,fileTree; - try { - //TODO: Input Validation - pt::read_json(ss, inputTree); - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); - BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); - if(inputTree.get_child("prep-cmd").empty())inputTree.erase("prep-cmd"); - inputTree.erase("index"); - if(index == -1){ - apps_node.push_back(std::make_pair("",inputTree)); - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i == index){ - newApps.push_back(std::make_pair("",inputTree)); - } else { - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid Input JSON"); - return; - } - } - - void deleteApp(resp_https_t response, req_https_t request) - { - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree fileTree; - try { - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); - BOOST_LOG(info) << index; - if(index <= 0){ - outputTree.put("status","false"); - outputTree.put("error","Invalid Index"); - return; - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i != index){ - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid File JSON"); - return; - } - } - - void start(std::shared_ptr shutdown_event) - { - auto ctx = std::make_shared(boost::asio::ssl::context::tls); - ctx->use_certificate_chain_file(config::nvhttp.cert); - ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - https_server_t http_server { ctx, 0 }; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; - - try + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i == index) { - http_server.bind(); - BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + newApps.push_back(std::make_pair("", inputTree)); } - catch (boost::system::system_error &err) + else { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); - - shutdown_event->raise(true); - return; + newApps.push_back(std::make_pair("", kv.second)); } - - std::thread tcp{&https_server_t::accept_and_run, &http_server}; - - // Wait for any event - shutdown_event->view(); - - http_server.stop(); - - tcp.join(); + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Input JSON"); + return; + } +} + +void deleteApp(resp_https_t response, req_https_t request) +{ + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree fileTree; + try + { + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = stoi(request->path_match[1]); + BOOST_LOG(info) << index; + if (index <= 0) + { + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Index"); + return; + } + else + { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i != index) + { + newApps.push_back(std::make_pair("", kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON"); + return; + } +} + +void start(std::shared_ptr shutdown_event) +{ + auto ctx = std::make_shared(boost::asio::ssl::context::tls); + ctx->use_certificate_chain_file(config::nvhttp.cert); + ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); + https_server_t http_server{ctx, 0}; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; + + try + { + http_server.bind(); + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + } + catch (boost::system::system_error &err) + { + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); + + shutdown_event->raise(true); + return; + } + auto accept_and_run = [&](auto *http_server) { + try + { + http_server->accept_and_run(); + } + catch (boost::system::system_error &err) + { + // It's possible the exception gets thrown after calling http_server->stop() from a different thread + if (shutdown_event->peek()) + { + return; + } + + BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + shutdown_event->raise(true); + return; + } + }; + std::thread tcp{accept_and_run, &http_server}; + + // Wait for any event + shutdown_event->view(); + + http_server.stop(); + + tcp.join(); +} } std::string read_file(std::string path) { - std::ifstream in(path); + std::ifstream in(path); - std::string input; - std::string base64_cert; + std::string input; + std::string base64_cert; - //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { - std::getline(in, input); - base64_cert += input + '\n'; - } + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } - return base64_cert; + return base64_cert; } \ No newline at end of file From 1d84c8f9ce57ab6ce6be52d0449c4814d68733dd Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 11 May 2021 23:30:56 +0200 Subject: [PATCH 052/158] Correct dimensions for touchscreen when single monitor attached --- sunshine/input.cpp | 24 ++++++++++++++++++++++-- sunshine/input.h | 7 ++++++- sunshine/platform/common.h | 14 +++++++++++++- sunshine/platform/linux/input.cpp | 19 ++++++++++++++----- sunshine/video.cpp | 10 +++------- sunshine/video.h | 24 ++++++++++++++++++------ 6 files changed, 76 insertions(+), 22 deletions(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 81a4920a..2ad4b7cf 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -13,8 +13,9 @@ extern "C" { #include "main.h" #include "config.h" #include "utility.h" -#include "platform/common.h" #include "thread_pool.h" +#include "input.h" +#include "platform/common.h" namespace input { @@ -45,6 +46,11 @@ void free_id(std::bitset &gamepad_mask, int id) { gamepad_mask[id] = false; } +touch_port_event_t touch_port_event; +platf::touch_port_t touch_port { + 0, 0, 0, 0 +}; + static util::TaskPool::task_id_t task_id {}; static std::unordered_map key_press {}; static std::array mouse_press {}; @@ -196,7 +202,20 @@ void passthrough(std::shared_ptr &input, PNV_ABS_MOUSE_MOVE_PACKET pack input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY; } - platf::abs_mouse(platf_input, util::endian::big(packet->x), util::endian::big(packet->y)); + 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); + + 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, PNV_MOUSE_BUTTON_PACKET packet) { @@ -481,6 +500,7 @@ void reset(std::shared_ptr &input) { } void init() { + touch_port_event = std::make_unique(); platf_input = platf::input(); } diff --git a/sunshine/input.h b/sunshine/input.h index 71e1805c..16267619 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -5,7 +5,8 @@ #ifndef SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H - +#include "thread_safe.h" +#include "platform/common.h" namespace input { struct input_t; @@ -14,9 +15,13 @@ void print(void *input); void reset(std::shared_ptr &input); void passthrough(std::shared_ptr &input, std::vector &&input_data); + void init(); std::shared_ptr alloc(); + +using touch_port_event_t = std::unique_ptr>; +extern touch_port_event_t touch_port_event; } #endif //SUNSHINE_INPUT_H diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index c8675bfc..69e41922 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -58,6 +58,18 @@ using namespace std::literals; 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 { std::uint16_t buttonFlags; std::uint8_t lt; @@ -146,7 +158,7 @@ std::shared_ptr display(dev_type_e hwdevice_type); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); -void abs_mouse(input_t &input, int x, int y); +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 scroll(input_t &input, int distance); void keyboard(input_t &input, uint16_t modcode, bool release); diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index df34e6e8..8b4578bc 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -29,6 +30,11 @@ using uinput_t = util::safe_ptr; using keyboard_t = util::safe_ptr_v2; +constexpr touch_port_t target_touch_port { + 0, 0, + 19200, 12000 +}; + struct input_raw_t { public: void clear_touchscreen() { @@ -136,11 +142,14 @@ public: keyboard_t keyboard; }; -void abs_mouse(input_t &input, int x, int y) { +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(); - libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, x); - libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, y); + auto scaled_x = (int)std::lround(x * ((float)target_touch_port.width / (float)touch_port.width)); + auto scaled_y = (int)std::lround(y * ((float)target_touch_port.height / (float)touch_port.height)); + + libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x + touch_port.offset_x); + libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y + touch_port.offset_y); libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1); libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0); @@ -461,7 +470,7 @@ evdev_t touchscreen() { input_absinfo absx { 0, 0, - 1919, + target_touch_port.width, 1, 0, 28 @@ -470,7 +479,7 @@ evdev_t touchscreen() { input_absinfo absy { 0, 0, - 1199, + target_touch_port.height, 1, 0, 28 diff --git a/sunshine/video.cpp b/sunshine/video.cpp index c6befe8d..88dfd01b 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -38,10 +38,6 @@ void free_buffer(AVBufferRef *ref) { av_buffer_unref(&ref); } -void free_packet(AVPacket *packet) { - av_packet_free(&packet); -} - namespace nv { enum class profile_h264_e : int { @@ -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 dxgi_img_to_frame(const platf::img_t &img, frame_t &frame); util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); -void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame); -util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); @@ -183,7 +177,7 @@ public: session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr &&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) } {} // Ensure objects are destroyed in the correct order @@ -862,6 +856,7 @@ encode_e encode_run_sync(std::vector> &synce return encode_e::error; } + input::touch_port_event->raise(0, 0, img->width, img->height); std::vector synced_sessions; for(auto &ctx : synced_session_ctxs) { auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx); @@ -1069,6 +1064,7 @@ void capture_async( if(display->dummy_img(dummy_img.get())) { return; } + input::touch_port_event->raise(0, 0, dummy_img->width, dummy_img->height); images->raise(std::move(dummy_img)); encode_run( diff --git a/sunshine/video.h b/sunshine/video.h index e4560e05..b628f303 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -6,6 +6,7 @@ #define SUNSHINE_VIDEO_H #include "thread_safe.h" +#include "input.h" #include "platform/common.h" extern "C" { @@ -14,16 +15,27 @@ extern "C" { struct AVPacket; namespace video { -void free_packet(AVPacket *packet); struct packet_raw_t : public AVPacket { - template - explicit packet_raw_t(P *user_data) : channel_data { user_data } { - av_init_packet(this); + void init_packet() { + pts = AV_NOPTS_VALUE; + 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 } { - av_init_packet(this); + template + 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() { From 27a1144217f16a4821fb698a880b6d2c4c40b4b2 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Tue, 11 May 2021 23:38:45 +0200 Subject: [PATCH 053/158] Moved Common HTTPS Initialization Logic in a common file --- CMakeLists.txt | 2 + sunshine/confighttp.cpp | 2 +- sunshine/httpcommon.cpp | 148 ++++++++++++++++++++++++++++++++++++++++ sunshine/httpcommon.h | 7 ++ sunshine/main.cpp | 4 +- sunshine/nvhttp.cpp | 117 +++---------------------------- 6 files changed, 168 insertions(+), 112 deletions(-) create mode 100644 sunshine/httpcommon.cpp create mode 100644 sunshine/httpcommon.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a85d880d..1dc30920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,8 @@ set(SUNSHINE_TARGET_FILES sunshine/crypto.h sunshine/nvhttp.cpp sunshine/nvhttp.h + sunshine/httpcommon.cpp + sunshine/httpcommon.h sunshine/confighttp.cpp sunshine/confighttp.h sunshine/rtsp.cpp diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 8241c9fe..c71fc0f7 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -264,7 +264,7 @@ void start(std::shared_ptr shutdown_event) return; } - BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTP << ", "sv << PORT_HTTP << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp new file mode 100644 index 00000000..6c05dba9 --- /dev/null +++ b/sunshine/httpcommon.cpp @@ -0,0 +1,148 @@ +#include "process.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include "config.h" +#include "utility.h" +#include "rtsp.h" +#include "crypto.h" +#include "nvhttp.h" +#include "platform/common.h" +#include "network.h" +#include "uuid.h" +#include "main.h" +#include "httpcommon.h" + +namespace http +{ + using namespace std::literals; + namespace fs = std::filesystem; + + int create_creds(const std::string &pkey, const std::string &cert); + std::string read_file(const char *path); + int write_file(const char *path, const std::string_view &contents); + std::string unique_id; + + void init(std::shared_ptr shutdown_event) + { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + if (clean_slate) + { + unique_id = util::uuid_t::generate().string(); + auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; + config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); + config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); + } + + if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) + { + if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) + { + shutdown_event->raise(true); + return; + } + } + } + + int create_creds(const std::string &pkey, const std::string &cert) + { + fs::path pkey_path = pkey; + fs::path cert_path = cert; + + auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); + + auto pkey_dir = pkey_path; + auto cert_dir = cert_path; + pkey_dir.remove_filename(); + cert_dir.remove_filename(); + + std::error_code err_code{}; + fs::create_directories(pkey_dir, err_code); + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); + return -1; + } + + fs::create_directories(cert_dir, err_code); + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); + return -1; + } + + if (write_file(pkey.c_str(), creds.pkey)) + { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; + return -1; + } + + if (write_file(cert.c_str(), creds.x509)) + { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; + return -1; + } + + fs::permissions(pkey_path, + fs::perms::owner_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); + return -1; + } + + fs::permissions(cert_path, + fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); + return -1; + } + + return 0; + } + int write_file(const char *path, const std::string_view &contents) + { + std::ofstream out(path); + + if (!out.is_open()) + { + return -1; + } + + out << contents; + + return 0; + } + + std::string read_file(const char *path) + { + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; + } +} \ No newline at end of file diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h new file mode 100644 index 00000000..5320025a --- /dev/null +++ b/sunshine/httpcommon.h @@ -0,0 +1,7 @@ +namespace http{ + void init(std::shared_ptr shutdown_event); + int create_creds(const std::string &pkey, const std::string &cert); + std::string read_file(const char *path); + int write_file(const char *path, const std::string_view &contents); + extern std::string unique_id; +} \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 15248deb..defaa348 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -17,6 +17,7 @@ #include "video.h" #include "input.h" #include "nvhttp.h" +#include "httpcommon.h" #include "confighttp.h" #include "rtsp.h" #include "config.h" @@ -137,13 +138,12 @@ int main(int argc, char *argv[]) { if(video::init()) { return 2; } - + http::init(shutdown_event); task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; std::thread configThread { confighttp::start, shutdown_event }; stream::rtpThread(shutdown_event); - httpThread.join(); task_pool.stop(); task_pool.join(); diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 98f5a79d..8799dc1f 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -25,6 +25,7 @@ #include "network.h" #include "uuid.h" #include "main.h" +#include "httpcommon.h" namespace nvhttp { @@ -38,9 +39,6 @@ constexpr auto GFE_VERSION = "3.12.0.1"; namespace fs = std::filesystem; namespace pt = boost::property_tree; -std::string read_file(const char *path); -int write_file(const char *path, const std::string_view &contents); - using https_server_t = SimpleWeb::Server; using http_server_t = SimpleWeb::Server; @@ -78,7 +76,6 @@ struct pair_session_t { // uniqueID, session std::unordered_map map_id_sess; std::unordered_map map_id_client; -std::string unique_id; net::net_e origin_pin_allowed; using args_t = SimpleWeb::CaseInsensitiveMultimap; @@ -95,7 +92,7 @@ enum class op_e { void save_state() { pt::ptree root; - root.put("root.uniqueid", unique_id); + root.put("root.uniqueid", http::unique_id); auto &nodes = root.add_child("root.devices", pt::ptree {}); for(auto &[_,client] : map_id_client) { pt::ptree node; @@ -120,7 +117,7 @@ void load_state() { auto file_state = fs::current_path() / config::nvhttp.file_state; if(!fs::exists(file_state)) { - unique_id = util::uuid_t::generate().string(); + http::unique_id = util::uuid_t::generate().string(); return; } @@ -133,7 +130,7 @@ void load_state() { return; } - unique_id = root.get("root.uniqueid"); + http::unique_id = root.get("root.uniqueid"); auto device_nodes = root.get_child("root.devices"); for(auto &[_,device_node] : device_nodes) { @@ -474,7 +471,7 @@ void serverinfo(std::shared_ptr::Response> res tree.put("root.appversion", VERSION); tree.put("root.GfeVersion", GFE_VERSION); - tree.put("root.uniqueid", unique_id); + tree.put("root.uniqueid", http::unique_id); tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0"); tree.put("root.LocalIP", request->local_endpoint_address()); @@ -677,88 +674,17 @@ void appasset(resp_https_t response, req_https_t request) { response->write(SimpleWeb::StatusCode::success_ok, in); } -int create_creds(const std::string &pkey, const std::string &cert) { - fs::path pkey_path = pkey; - fs::path cert_path = cert; - - auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); - - auto pkey_dir = pkey_path; - auto cert_dir = cert_path; - pkey_dir.remove_filename(); - cert_dir.remove_filename(); - - std::error_code err_code{}; - fs::create_directories(pkey_dir, err_code); - if (err_code) { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); - return -1; - } - - fs::create_directories(cert_dir, err_code); - if (err_code) { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); - return -1; - } - - if (write_file(pkey.c_str(), creds.pkey)) { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; - return -1; - } - - if (write_file(cert.c_str(), creds.x509)) { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; - return -1; - } - - fs::permissions(pkey_path, - fs::perms::owner_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); - return -1; - } - - fs::permissions(cert_path, - fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); - return -1; - } - - return 0; -} - void start(std::shared_ptr shutdown_event) { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - if(clean_slate) { - unique_id = util::uuid_t::generate().string(); - - auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; - - config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); - config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); - } - - - if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { - if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { - shutdown_event->raise(true); - return; - } - } - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if(!clean_slate) { load_state(); } - conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); - conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); + conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); + conf_intern.servercert = http::read_file(config::nvhttp.cert.c_str()); auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); @@ -872,31 +798,4 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); } - -int write_file(const char *path, const std::string_view &contents) { - std::ofstream out(path); - - if(!out.is_open()) { - return -1; - } - - out << contents; - - return 0; -} - -std::string read_file(const char *path) { - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - //FIXME: Being unable to read file could result in infinite loop - while(!in.eof()) { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; -} } From 2e9a1cfbbaacbe1ea63808214801aeb306fdfb11 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 11 May 2021 23:51:45 +0200 Subject: [PATCH 054/158] absolute mouse coordinates regardless of the number of monitors attached on Linux --- sunshine/platform/common.h | 4 ++++ sunshine/platform/linux/display.cpp | 28 +++++++++++----------------- sunshine/platform/linux/input.cpp | 8 ++++---- sunshine/platform/windows/input.cpp | 2 +- sunshine/video.cpp | 9 +++++++-- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 69e41922..96c3d84e 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -122,6 +122,7 @@ enum class capture_e : int { class display_t { 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 std::shared_ptr alloc_img() = 0; @@ -133,6 +134,9 @@ public: virtual ~display_t() = default; + // Offsets for when streaming a specific monitor. By default, they are 0. + int offset_x, offset_y; + int width, height; }; diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 39fbd616..4ca09f59 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -153,12 +153,7 @@ struct x11_attr_t: public display_t */ int lastWidth, lastHeight; - /* - * Offsets for when streaming a specific monitor. By default, they are 0. - */ - int displayOffsetX, displayOffsetY; - - x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow { }, xattr { }, displayOffsetX { 0 }, displayOffsetY { 0 } { + x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow { }, xattr { } { XInitThreads(); } @@ -199,8 +194,8 @@ struct x11_attr_t: public display_t width = crt_info->width; height = crt_info->height; - displayOffsetX = crt_info->x; - displayOffsetY = crt_info->y; + offset_x = crt_info->x; + offset_y = crt_info->y; } else { width = xattr.width; @@ -228,7 +223,7 @@ struct x11_attr_t: public display_t BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; 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; img_out->width = img->width; @@ -239,7 +234,7 @@ struct x11_attr_t: public display_t img_out->img.reset(img); 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; @@ -288,19 +283,18 @@ struct shm_attr_t: public x11_attr_t { return capture_e::reinit; } 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) }; - if (!img_reply) { - BOOST_LOG(error) - << "Could not get image reply"sv; + if(!img_reply) { + BOOST_LOG(error) << "Could not get image reply"sv; 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) { - blend_cursor(shm_xdisplay.get(), *img, displayOffsetX, displayOffsetY); + if(cursor) { + blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y); } return capture_e::ok; diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 8b4578bc..b0a5cec1 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -145,11 +145,11 @@ public: 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 * ((float)target_touch_port.width / (float)touch_port.width)); - auto scaled_y = (int)std::lround(y * ((float)target_touch_port.height / (float)touch_port.height)); + 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 + touch_port.offset_x); - libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y + touch_port.offset_y); + 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); diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index d0af736e..4f40576c 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -165,7 +165,7 @@ input_t input() { return result; } -void abs_mouse(input_t &input, int x, int y) {} +void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {} void move_mouse(input_t &input, int deltaX, int deltaY) { INPUT i {}; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 88dfd01b..54252375 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -856,7 +856,9 @@ encode_e encode_run_sync(std::vector> &synce return encode_e::error; } - input::touch_port_event->raise(0, 0, img->width, img->height); + // 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 synced_sessions; for(auto &ctx : synced_session_ctxs) { auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx); @@ -1064,9 +1066,12 @@ void capture_async( if(display->dummy_img(dummy_img.get())) { return; } - input::touch_port_event->raise(0, 0, dummy_img->width, dummy_img->height); + 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( frame_nr, key_frame_nr, shutdown_event, From 022b2202f67ef42fc9ba3d120fae3f0a0940e15b Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 12 May 2021 14:42:10 +0200 Subject: [PATCH 055/158] Absolute mouse coordinates on Windows --- sunshine/input.cpp | 38 +++++++++- sunshine/platform/windows/display_base.cpp | 6 +- sunshine/platform/windows/input.cpp | 87 +++++++++++----------- 3 files changed, 84 insertions(+), 47 deletions(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 2ad4b7cf..ce2a81e3 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -209,6 +209,14 @@ void passthrough(std::shared_ptr &input, PNV_ABS_MOUSE_MOVE_PACKET pack 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); @@ -220,7 +228,9 @@ void passthrough(std::shared_ptr &input, PNV_ABS_MOUSE_MOVE_PACKET pack void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet) { auto constexpr BUTTON_RELEASED = 0x09; - auto constexpr BUTTON_LEFT = 0x01; + + auto constexpr BUTTON_LEFT = 0x01; + auto constexpr BUTTON_RIGHT = 0x03; display_cursor = true; @@ -228,9 +238,14 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet auto button = util::endian::big(packet->button); if(button > 0 && button < mouse_press.size()) { + if(mouse_press[button] != release) { + // button state is already what we want + return; + } + 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. @@ -247,13 +262,30 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet /*/ if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) { input->mouse_left_button_timeout = task_pool.pushDelayed([=]() { - platf::button_mouse(platf_input, button, release); + auto left_released = mouse_press[BUTTON_LEFT]; + if(left_released) { + // Already released left button + return; + } + platf::button_mouse(platf_input, BUTTON_LEFT, release); input->mouse_left_button_timeout = nullptr; }, 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); } diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 8644ed88..46405397 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -129,8 +129,10 @@ int display_base_t::init() { if(desc.AttachedToDesktop) { output = std::move(output_tmp); - width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; - height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; + offset_x = desc.DesktopCoordinates.left; + offset_y = desc.DesktopCoordinates.top; + width = desc.DesktopCoordinates.right - offset_x; + height = desc.DesktopCoordinates.bottom - offset_y; } } diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 4f40576c..b372afd2 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -17,7 +18,12 @@ using namespace std::literals; using adapteraddrs_t = util::c_ptr; -volatile HDESK _lastKnownInputDesktop = NULL; +volatile HDESK _lastKnownInputDesktop = NULL; +constexpr touch_port_t target_touch_port { + 0, 0, + 65535, 65535 +}; + HDESK pairInputDesktop(); class vigem_t { @@ -165,7 +171,40 @@ input_t input() { return result; } -void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {} +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) { INPUT i {}; @@ -176,16 +215,7 @@ void move_mouse(input_t &input, int deltaX, int deltaY) { mi.dx = deltaX; mi.dy = deltaY; -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 mouse movement input"sv; - } + send_input(i); } void button_mouse(input_t &input, int button, bool release) { @@ -228,16 +258,7 @@ void button_mouse(input_t &input, int button, bool release) { return; } -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 mouse button input"sv; - } + send_input(i); } void scroll(input_t &input, int distance) { @@ -249,16 +270,7 @@ void scroll(input_t &input, int distance) { mi.dwFlags = MOUSEEVENTF_WHEEL; mi.mouseData = distance; -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 mouse scroll input"sv; - } + send_input(i); } void keyboard(input_t &input, uint16_t modcode, bool release) { @@ -304,16 +316,7 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { ki.dwFlags |= KEYEVENTF_KEYUP; } -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 keyboard input"sv; - } + send_input(i); } int alloc_gamepad(input_t &input, int nr) { From 5d313b509ed6a4fa8e09a34b42a7dac66600f94d Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 12 May 2021 15:48:39 +0200 Subject: [PATCH 056/158] fix resetting mouse buttons on end stream --- sunshine/input.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index ce2a81e3..6a585106 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -269,6 +269,7 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet } platf::button_mouse(platf_input, BUTTON_LEFT, release); + mouse_press[BUTTON_LEFT] = false; input->mouse_left_button_timeout = nullptr; }, 10ms).task_id; @@ -521,7 +522,10 @@ void reset(std::shared_ptr &input) { // Ensure input is synchronous, by using the task_pool task_pool.push([]() { for(int x = 0; x < mouse_press.size(); ++x) { - platf::button_mouse(platf_input, x, true); + if(mouse_press[x]) { + platf::button_mouse(platf_input, x, true); + mouse_press[x] = false; + } } for(auto& kp : key_press) { From 9e48e582210496491ee3d5d6786dc0e03b235ac2 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 12 May 2021 23:01:30 +0200 Subject: [PATCH 057/158] Allow applications started by sunshine to be detached --- README.md | 9 +++++++-- assets/apps_linux.json | 2 +- assets/apps_windows.json | 4 +--- sunshine/process.cpp | 35 ++++++++++++++++++++++++++++++----- sunshine/process.h | 9 +++++++++ 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 102a1207..12490bde 100644 --- a/README.md +++ b/README.md @@ -117,15 +117,20 @@ sunshine needs access to uinput to create mouse and gamepad events: "cmd":"command to open app", "prep-cmd":[ { - "do":"somecommand", - "undo":"undothatcommand" + "do":"some-command", + "undo":"undo-that-command" } + ], + "detached":[ + "some-command", + "another-command" ] } ``` - name: Self explanatory - output : The file where the output of the command is stored - 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 - If any of the prep-commands fail, starting the application is aborted - do: Run before the application diff --git a/assets/apps_linux.json b/assets/apps_linux.json index 13950778..1cfeb3d4 100644 --- a/assets/apps_linux.json +++ b/assets/apps_linux.json @@ -13,7 +13,7 @@ "name":"Steam BigPicture", "output":"steam.txt", - "cmd":"steam -bigpicture" + "detached":["setsid steam steam://open/bigpicture"] } ] } diff --git a/assets/apps_windows.json b/assets/apps_windows.json index 512fd497..f2a2a7ec 100644 --- a/assets/apps_windows.json +++ b/assets/apps_windows.json @@ -7,9 +7,7 @@ "name":"Steam BigPicture", "output":"steam.txt", - "prep-cmd":[ - {"do":"steam \"steam://open/bigpicture\""} - ] + "detached":["steam steam://open/bigpicture"] } ] } diff --git a/sunshine/process.cpp b/sunshine/process.cpp index ec2aa2fe..85767eac 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -80,17 +80,31 @@ int proc_t::execute(int app_id) { auto ret = exe(cmd, _env, _pipe, ec); if(ec) { - BOOST_LOG(error) << "System: "sv << ec.message(); + BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message(); return -1; } if(ret != 0) { - BOOST_LOG(error) << "Return code ["sv << ret << ']'; + BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']'; 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()) { placebo = true; } @@ -102,7 +116,7 @@ int proc_t::execute(int app_id) { } if(ec) { - BOOST_LOG(info) << "System: "sv << ec.message(); + BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message(); return -1; } @@ -251,12 +265,12 @@ std::optional parse(const std::string& file_name) { proc::ctx_t ctx; 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("output"s); auto name = parse_env_val(this_env, app_node.get("name"s)); auto cmd = app_node.get_optional("cmd"s); std::vector prep_cmds; - if(prep_nodes_opt) { auto &prep_nodes = *prep_nodes_opt; @@ -274,6 +288,16 @@ std::optional parse(const std::string& file_name) { } } + std::vector 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())); + } + } + if(output) { ctx.output = parse_env_val(this_env, *output); } @@ -284,6 +308,7 @@ std::optional parse(const std::string& file_name) { ctx.name = std::move(name); ctx.prep_cmds = std::move(prep_cmds); + ctx.detached = std::move(detached); apps.emplace_back(std::move(ctx)); } diff --git a/sunshine/process.h b/sunshine/process.h index 37c17acd..e267fa44 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -30,6 +30,7 @@ struct cmd_t { }; /* * pre_cmds -- guaranteed to be executed unless any of the commands fail. + * detached -- commands detached from Sunshine * cmd -- Runs indefinitely until: * No session is running and a different set of commands it to be executed * Command exits @@ -41,6 +42,14 @@ struct cmd_t { struct ctx_t { std::vector 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 detached; + std::string name; std::string cmd; std::string output; From 1050978246ce203b6f352b75499454e6a2773638 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 12 May 2021 23:22:13 +0200 Subject: [PATCH 058/158] Add warning to ignore errors during encoder validation --- sunshine/main.cpp | 2 -- sunshine/video.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 801493aa..9dc686a9 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -15,7 +15,6 @@ #include #include "video.h" -#include "input.h" #include "nvhttp.h" #include "rtsp.h" #include "config.h" @@ -142,7 +141,6 @@ int main(int argc, char *argv[]) { return 4; } - input::init(); reed_solomon_init(); if(video::init()) { return 2; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 54252375..3127ccbc 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -1146,6 +1146,11 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e bool validate_encoder(encoder_t &encoder) { std::shared_ptr 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 test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode); @@ -1200,10 +1205,21 @@ bool validate_encoder(encoder_t &encoder) { } } + fg.disable(); return true; } int init() { + // video depends on input for input::touch_port_event + input::init(); + + BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv; + BOOST_LOG(info) << "// //"sv; + BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. //"sv; + 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), { if( (!config::video.encoder.empty() && pos->name != config::video.encoder) || @@ -1229,6 +1245,14 @@ int init() { 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(); if(encoder.hevc[encoder_t::PASSED]) { BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; From 381e8bcfaafe550070b09cc8361f831ffe4c98fd Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 13 May 2021 15:26:08 +0200 Subject: [PATCH 059/158] Prepare for release --- gen-deb.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen-deb.in b/gen-deb.in index cc7c6a0a..ea4e671e 100755 --- a/gen-deb.in +++ b/gen-deb.in @@ -35,7 +35,7 @@ Package: sunshine Architecture: amd64 Maintainer: @loki Priority: optional -Version: 0.2.1 +Version: 0.3.1 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 EOF From 3901e404a97e2502397be27d90e7f2a0a9a65982 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 14 May 2021 21:32:46 +0200 Subject: [PATCH 060/158] Add forgotten requirement for compilation on ubuntu --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12490bde..627d51c6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Sunshine is a Gamestream host for Moonlight 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: From 33a330fd6c198fdb14f3da77233c69f5d21c4c92 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 14 May 2021 21:44:20 +0200 Subject: [PATCH 061/158] Downmix surround 5.1 to stereo --- sunshine/audio.cpp | 10 +- sunshine/platform/common.h | 2 +- sunshine/platform/linux/display.cpp | 2 +- sunshine/platform/windows/audio.cpp | 249 ++++++++++++++++++++-------- tools/audio.cpp | 171 ++++++++++++------- 5 files changed, 305 insertions(+), 129 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 4e13cc8f..7ab716e4 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -94,16 +94,16 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co //FIXME: Pick correct opus_stream_config_t based on config.channels auto stream = &stereo; - auto mic = platf::microphone(stream->sampleRate); + auto frame_size = config.packetDuration * stream->sampleRate / 1000; + int samples_per_frame = frame_size * stream->channelCount; + + auto mic = platf::microphone(stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't create audio input"sv ; return; } - auto frame_size = config.packetDuration * stream->sampleRate / 1000; - int samples_per_frame = frame_size * stream->channelCount; - while(!shutdown_event->peek()) { std::vector sample_buffer; sample_buffer.resize(samples_per_frame); @@ -116,7 +116,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co continue; case platf::capture_e::reinit: mic.reset(); - mic = platf::microphone(stream->sampleRate); + mic = platf::microphone(stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't re-initialize audio input"sv ; diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 96c3d84e..2f414509 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -157,7 +157,7 @@ std::string get_mac_address(const std::string_view &address); std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); -std::unique_ptr microphone(std::uint32_t sample_rate); +std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t frame_size); std::shared_ptr display(dev_type_e hwdevice_type); input_t input(); diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 4ca09f59..79bd437b 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -410,7 +410,7 @@ std::shared_ptr display(platf::dev_type_e hwdevice_type) { return x11_disp; } -std::unique_ptr microphone(std::uint32_t sample_rate) { +std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); int status; diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index 63369ccb..dea262f5 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -49,10 +49,154 @@ public: } }; +struct format_t { + std::string_view name; + int channels; + int channel_mask; +} formats [] { + { + "Stereo"sv, + 2, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT + }, + { + "Mono"sv, + 1, + SPEAKER_FRONT_CENTER + }, + { + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_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; + } +} + +void surround51_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { + enum surround51_e : int { + front_left, + front_right, + front_center, + low_frequency, // subwoofer + back_left, + back_right, + channels51 // number of channels in surround sound + }; + + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; + + for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; sample_out_p += channels51) { + std::uint32_t left {}, right {}; + + left += sample_out_p[front_left]; + left += sample_out_p[front_center] * 90 / 100; + left += sample_out_p[low_frequency] * 30 / 100; + left += sample_out_p[back_left] * 70 / 100; + left += sample_out_p[back_right] * 30 / 100; + + right += sample_out_p[front_right]; + right += sample_out_p[front_center] * 90 / 100; + right += sample_out_p[low_frequency] * 30 / 100; + right += sample_out_p[back_left] * 30 / 100; + right += sample_out_p[back_right] * 70 / 100;; + + *sample_in_pos++ = (std::uint16_t)left; + *sample_in_pos++ = (std::uint16_t)right; + } +} + +void mono_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2; + + for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; ++sample_out_p) { + *sample_in_pos++ = *sample_out_p; + *sample_in_pos++ = *sample_out_p; + } +} + +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; + } + + 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; + } + + 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 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; +} + class mic_wasapi_t : public mic_t { public: capture_e sample(std::vector &sample_in) override { - while(sample_buf_pos - std::begin(sample_buf) < sample_in.size()) { + auto sample_size = sample_in.size() /2 * format->channels; + 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 auto capture_result = _fill_buffer(); @@ -61,17 +205,32 @@ public: } } - std::copy_n(std::begin(sample_buf), sample_in.size(), std::begin(sample_in)); + switch(format->channels) { + case 1: + mono_to_stereo(sample_in, sample_buf); + break; + case 2: + std::copy_n(std::begin(sample_buf), sample_size, std::begin(sample_in)); + break; + case 6: + if(format->name == "Surround 5.1"sv) { + surround51_to_stereo(sample_in, sample_buf); + break; + } + + BOOST_LOG(error) << '[' << format->name << "] not yet supported"sv; + return capture_e::error; + } // The excess samples should be in front of the queue - std::move(&sample_buf[sample_in.size()], sample_buf_pos, std::begin(sample_buf)); - sample_buf_pos -= sample_in.size(); + std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); + sample_buf_pos -= sample_size; return capture_e::ok; } - int init(std::uint32_t sample_rate) { + int init(std::uint32_t sample_rate, std::uint32_t frame_size) { audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); if(!audio_event) { BOOST_LOG(error) << "Couldn't create Event handle"sv; @@ -113,70 +272,26 @@ public: return -1; } - status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **) &audio_client); + for(auto &format : formats) { + BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; + audio_client = make_audio_client(device, format, sample_rate); - if (FAILED(status)) { - BOOST_LOG(error) << "Couldn't activate audio Device [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - 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: + if(audio_client) { + BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; + this->format = &format; break; - case WAVE_FORMAT_IEEE_FLOAT: - wave_format->wFormatTag = WAVE_FORMAT_PCM; - 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; - } - - BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; - return -1; } - default: - BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; - return -1; - }; + } + + if(!audio_client) { + BOOST_LOG(error) << "Couldn't find supported format for audio"sv; + return -1; + } REFERENCE_TIME default_latency; audio_client->GetDevicePeriod(&default_latency, nullptr); 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; status = audio_client->GetBufferSize(&frames); if (FAILED(status)) { @@ -185,7 +300,8 @@ public: return -1; } - sample_buf = util::buffer_t { frames }; + // *2 --> needs to fit double + sample_buf = util::buffer_t { std::max(frames *2, frame_size * format->channels *2) }; sample_buf_pos = std::begin(sample_buf); status = audio_client->GetService(IID_IAudioCaptureClient, (void**)&audio_capture); @@ -267,7 +383,7 @@ private: } 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 * format->channels); if (buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { std::fill_n(sample_buf_pos, n, 0); @@ -296,13 +412,14 @@ public: device_enum_t device_enum; device_t device; audio_client_t audio_client; - wave_format_t wave_format; audio_capture_t audio_capture; REFERENCE_TIME default_latency_ms; util::buffer_t sample_buf; std::int16_t *sample_buf_pos; + + format_t *format; }; } @@ -313,10 +430,10 @@ namespace dxgi { int init(); } -std::unique_ptr microphone(std::uint32_t sample_rate) { +std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t frame_size) { auto mic = std::make_unique(); - if(mic->init(sample_rate)) { + if(mic->init(sample_rate, frame_size)) { return nullptr; } diff --git a/tools/audio.cpp b/tools/audio.cpp index 05717b23..18556c50 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -26,7 +26,9 @@ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioClient = __uuidof(IAudioClient); const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); +constexpr auto SAMPLE_RATE = 48000; int device_state_filter = DEVICE_STATE_ACTIVE; + namespace audio { template void Release(T *p) { @@ -66,19 +68,114 @@ public: const wchar_t *no_null(const wchar_t *str) { 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 + } +}; + +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; device->GetState(&device_state); - device->GetId(&wstring_p); - audio::wstring_t wstring { wstring_p }; + device->GetId(&wstring); - audio::prop_t::pointer prop_p {}; - device->OpenPropertyStore(STGM_READ, &prop_p); - audio::prop_t prop { prop_p }; + audio::prop_t prop; + device->OpenPropertyStore(STGM_READ, &prop); prop_var_t adapter_friendly_name; prop_var_t device_friendly_name; @@ -120,47 +217,12 @@ void print_device(device_t &device) { return; } - // Ensure WaveFromat is compatible - audio_client_t::pointer audio_client_p{}; - status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **) &audio_client_p); - audio_client_t audio_client { audio_client_p }; + for(const auto &format : formats) { + // Ensure WaveFromat is compatible + auto audio_client = make_audio_client(device, format); - if (FAILED(status)) { - std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; - - return; + std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl; } - - 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; - }; } } @@ -213,14 +275,13 @@ int main(int argc, char *argv[]) { HRESULT status; - audio::device_enum_t::pointer device_enum_p{}; + audio::device_enum_t device_enum; status = CoCreateInstance( CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void **) &device_enum_p); - audio::device_enum_t device_enum { device_enum_p }; + (void **) &device_enum); if (FAILED(status)) { std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; @@ -228,9 +289,8 @@ int main(int argc, char *argv[]) { return -1; } - audio::collection_t::pointer collection_p {}; - status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection_p); - audio::collection_t collection { collection_p }; + audio::collection_t collection; + status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection); if (FAILED(status)) { std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; @@ -243,9 +303,8 @@ int main(int argc, char *argv[]) { std::cout << "====== Found "sv << count << " potential audio devices ======"sv << std::endl; for(auto x = 0; x < count; ++x) { - audio::device_t::pointer device_p {}; - collection->Item(x, &device_p); - audio::device_t device { device_p }; + audio::device_t device; + collection->Item(x, &device); audio::print_device(device); } From a6c164949369b6528ac466a78e26f759e2737435 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 15 May 2021 17:01:03 +0200 Subject: [PATCH 062/158] Remove redundent code --- sunshine/platform/windows/audio.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index dea262f5..4f453ac0 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -213,11 +213,9 @@ public: std::copy_n(std::begin(sample_buf), sample_size, std::begin(sample_in)); break; case 6: - if(format->name == "Surround 5.1"sv) { - surround51_to_stereo(sample_in, sample_buf); - break; - } - + surround51_to_stereo(sample_in, sample_buf); + break; + default: BOOST_LOG(error) << '[' << format->name << "] not yet supported"sv; return capture_e::error; } From 03236a50e555fefd0417a35ff6f13159871b79ea Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 16 May 2021 20:06:06 +0200 Subject: [PATCH 063/158] UI for Application Config --- assets/web/config.html | 414 +++++++++++++++++++++++++++++++++++++++- assets/web/header.html | 5 +- sunshine/config.cpp | 1 - sunshine/config.h | 2 + sunshine/confighttp.cpp | 79 ++++++++ 5 files changed, 492 insertions(+), 9 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index c63ec39d..eb15bfe2 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -1,4 +1,410 @@ -
-

Hello, Sunshine!

-

Placeholer for config page

-
\ No newline at end of file +
+

Configuration

+
+ + + +
+ +
+ + +
The name displayed by Moonlight. If not specified, the PC's hostname is used +
+
+ +
+ + +
The minimum log level printed to standard out
+
+ +
+ + +
The origin of the remote endpoint address that is not denied for HTTP method /pin +
+
+ +
+ + +
If no external IP address is given, the local IP address is used
+
+ +
+ + +
How long to wait in milliseconds for data from moonlight before shutting down the + stream
+
+
+ +
+ +
+ + +
The private key must be 2048 bits
+
+ +
+ + +
The certificate must be signed with a 2048 bit key
+
+ + +
+ + +
The file where current state of Sunshine is stored
+
+ +
+ + +
The file where current apps of Sunshine are stored
+
+
+
+ +
+ + +
+ The back/select button on the controller.
+ On the Shield, the home and powerbutton are not passed to Moonlight.
+ If, after the timeout, the back button is still pressed down, Home/Guide button press is + emulated.
+ If back_button_timeout < 0, then the Home/Guide button will not be emulated
+
+
+ +
+ + +
+ Control how fast keys will repeat themselves
+ The initial delay in milliseconds before repeating keys +
+
+ +
+ + +
+ How often keys repeat every second
+ This configurable option supports decimals +
+
+
+ +
+ +
+ + +
+ The name of the audio sink used for Audio Loopback
+ You can find the name of the audio sink using the following command:
+ tools\audio-info.exe +
+
+ The name of the audio sink used for Audio Loopback
+ If you do not specify this variable, pulseaudio will select the default monitor device.
+
+ You can find the name of the audio sink using the following command:
+ pacmd list-sources | grep "name:"
+
+
+ +
+ + +
+ You can select the video card you want to stream:
+ The appropriate values can be found using the following command:
+ tools\dxgi-info.exe +
+
+ +
+ + +
+ You can select the video card you want to stream:
+ The appropriate values can be found using the following command:
+ tools\dxgi-info.exe
+ !! Linux only !!
+ Set the display number to stream. I have no idea how they are numbered. They start from 1, + usually.
+ output_name = 1
+
+
+
+
+ +
+ + +
+ Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still + image, + resulting in constant perceived quality
+ Higher value means more compression, but less quality
+ If crf == 0, then use QP directly instead +
+
+ +
+ + +
+ Quantitization Parameter
+ Higher value means more compression, but less quality
+ If crf != 0, then this parameter is ignored +
+
+ +
+ + +
+ Minimum number of threads used by ffmpeg to encode the video.
+ Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
+ worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
+ value that can reliably encode at your desired streaming settings on your hardware. +
+
+ +
+ + +
+ Allows the client to request HEVC Main or HEVC Main10 video streams.
+ HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software + encoding. +
+
+ +
+ + +
+ Force a specific encoder, otherwise Sunshine will use the first encoder that is available +
+
+ +
+ + +
+ How much error correcting packets must be send for every video.
+ This is just some random number, don't know the optimal value.
+ The higher fec_percentage, the lower space for the actual data to send per frame there is +
+
+ +
+ + +
+ When multicasting, it could be useful to have different configurations for each connected Client. + For example: +
    +
  • Clients connected through WAN and LAN have different bitrate contstraints.
  • +
  • Decoders may require different settings for color
  • +
+ Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
+ Note, CPU usage increases for each distinct video stream generated +
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/assets/web/header.html b/assets/web/header.html index 59c11c2f..9c621101 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -11,7 +11,7 @@ - + @@ -34,9 +34,6 @@ - diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 9408578c..d2a97d2e 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include diff --git a/sunshine/config.h b/sunshine/config.h index f67ac12b..1dbcabbc 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace config { struct video_t { @@ -96,6 +97,7 @@ extern input_t input; extern sunshine_t sunshine; int parse(int argc, char *argv[]); +std::unordered_map parse_config(std::string_view file_content); } #endif diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index c71fc0f7..670b9204 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -21,6 +21,7 @@ #include "crypto.h" #include "confighttp.h" #include "platform/common.h" +#include "httpcommon.h" #include "network.h" #include "nvhttp.h" #include "uuid.h" @@ -219,6 +220,82 @@ void deleteApp(resp_https_t response, req_https_t request) } } +void getConfig(resp_https_t response, req_https_t request) +{ + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + try + { + outputTree.put("status","true"); + #ifdef _WIN32 + outputTree.put("platform","windows"); + #elif + outputTree.put("platform","linux"); + #endif + const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; + std::ifstream in { config_file }; + + if(!in.is_open()) { + std::cout << "Error: Couldn't open "sv << config_file << std::endl; + } + + auto vars = config::parse_config(std::string { + // Quick and dirty + std::istreambuf_iterator(in), + std::istreambuf_iterator() + }); + + for(auto &[name,value] : vars) { + outputTree.put(std::move(name), std::move(value)); + } + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON"); + return; + } +} + +void saveConfig(resp_https_t response, req_https_t request){ + std::stringstream ss; + std::stringstream configStream; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree; + try + { + //TODO: Input Validation + pt::read_json(ss, inputTree); + for (const auto &kv : inputTree) + { + std::string value = inputTree.get(kv.first); + if(value.length() == 0 || value.compare("null") == 0)continue; + configStream << kv.first << " = " << value << std::endl; + } + http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf",configStream.str()); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", e.what()); + return; + } +} + void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); @@ -231,6 +308,8 @@ void start(std::shared_ptr shutdown_event) http_server.resource["^/apps$"]["GET"] = getAppsPage; http_server.resource["^/api/apps$"]["GET"] = getApps; http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/config$"]["GET"] = getConfig; + http_server.resource["^/api/config$"]["POST"] = saveConfig; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; http_server.resource["^/clients$"]["GET"] = getClientsPage; http_server.resource["^/config$"]["GET"] = getConfigPage; From 3d8a99f541ea4c8dbf45404508caf6a5d03b50c9 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 17 May 2021 21:21:57 +0200 Subject: [PATCH 064/158] clang-format --- .clang-format | 67 +++ sunshine/audio.cpp | 79 ++-- sunshine/audio.h | 8 +- sunshine/config.cpp | 171 ++++---- sunshine/config.h | 12 +- sunshine/crypto.cpp | 50 +-- sunshine/crypto.h | 26 +- sunshine/input.cpp | 182 ++++---- sunshine/input.h | 4 +- sunshine/main.cpp | 58 +-- sunshine/main.h | 2 +- sunshine/move_by_copy.h | 21 +- sunshine/network.cpp | 26 +- sunshine/network.h | 6 +- sunshine/nvhttp.cpp | 160 +++---- sunshine/nvhttp.h | 4 +- sunshine/platform/common.h | 31 +- sunshine/platform/linux/display.cpp | 181 ++++---- sunshine/platform/linux/input.cpp | 262 +++++------ sunshine/platform/windows/audio.cpp | 155 ++++--- sunshine/platform/windows/display.h | 16 +- sunshine/platform/windows/display_base.cpp | 92 ++-- sunshine/platform/windows/display_ram.cpp | 116 ++--- sunshine/platform/windows/display_vram.cpp | 270 ++++++------ sunshine/platform/windows/input.cpp | 113 ++--- sunshine/process.cpp | 73 ++-- sunshine/process.h | 15 +- sunshine/round_robin.h | 51 ++- sunshine/rtsp.cpp | 183 ++++---- sunshine/rtsp.h | 2 +- sunshine/stream.cpp | 246 ++++++----- sunshine/stream.h | 6 +- sunshine/sync.h | 11 +- sunshine/task_pool.h | 57 +-- sunshine/thread_pool.h | 22 +- sunshine/thread_safe.h | 76 ++-- sunshine/utility.h | 284 ++++++------ sunshine/uuid.h | 12 +- sunshine/video.cpp | 480 ++++++++++----------- sunshine/video.h | 22 +- tools/CMakeLists.txt | 4 +- tools/audio.cpp | 113 +++-- tools/dxgi.cpp | 20 +- 43 files changed, 1917 insertions(+), 1872 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..a069b562 --- /dev/null +++ b/.clang-format @@ -0,0 +1,67 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: AcrossComments +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 diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 7ab716e4..c9ddab76 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -4,14 +4,14 @@ #include "platform/common.h" -#include "utility.h" -#include "thread_safe.h" #include "audio.h" #include "main.h" +#include "thread_safe.h" +#include "utility.h" namespace audio { using namespace std::literals; -using opus_t = util::safe_ptr; +using opus_t = util::safe_ptr; using sample_queue_t = std::shared_ptr>>; struct opus_stream_config_t { @@ -23,31 +23,31 @@ struct opus_stream_config_t { }; constexpr std::uint8_t map_stereo[] { 0, 1 }; -constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3}; -constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5}; -constexpr auto SAMPLE_RATE = 48000; +constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 }; +constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 }; +constexpr auto SAMPLE_RATE = 48000; static opus_stream_config_t stereo = { - SAMPLE_RATE, - 2, - 1, - 1, - map_stereo + SAMPLE_RATE, + 2, + 1, + 1, + map_stereo }; static opus_stream_config_t Surround51 = { - SAMPLE_RATE, - 6, - 4, - 2, - map_surround51 + SAMPLE_RATE, + 6, + 4, + 2, + map_surround51 }; static opus_stream_config_t HighSurround51 = { - SAMPLE_RATE, - 6, - 6, - 0, - map_high_surround51 + SAMPLE_RATE, + 6, + 6, + 0, + map_high_surround51 }; void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) { @@ -60,12 +60,11 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi stream->coupledStreams, stream->mapping, OPUS_APPLICATION_AUDIO, - nullptr) - }; + nullptr) }; auto frame_size = config.packetDuration * stream->sampleRate / 1000; while(auto sample = samples->pop()) { - packet_t packet { 16*1024 }; // 16KB + packet_t packet { 16 * 1024 }; // 16KB int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size()); if(bytes < 0) { @@ -94,12 +93,12 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co //FIXME: Pick correct opus_stream_config_t based on config.channels auto stream = &stereo; - auto frame_size = config.packetDuration * stream->sampleRate / 1000; + auto frame_size = config.packetDuration * stream->sampleRate / 1000; int samples_per_frame = frame_size * stream->channelCount; auto mic = platf::microphone(stream->sampleRate, frame_size); if(!mic) { - BOOST_LOG(error) << "Couldn't create audio input"sv ; + BOOST_LOG(error) << "Couldn't create audio input"sv; return; } @@ -110,24 +109,24 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co auto status = mic->sample(sample_buffer); switch(status) { - case platf::capture_e::ok: - break; - case platf::capture_e::timeout: - continue; - case platf::capture_e::reinit: - mic.reset(); - mic = platf::microphone(stream->sampleRate, frame_size); - if(!mic) { - BOOST_LOG(error) << "Couldn't re-initialize audio input"sv ; + case platf::capture_e::ok: + break; + case platf::capture_e::timeout: + continue; + case platf::capture_e::reinit: + mic.reset(); + mic = platf::microphone(stream->sampleRate, frame_size); + if(!mic) { + BOOST_LOG(error) << "Couldn't re-initialize audio input"sv; - return; - } - return; - default: return; + } + return; + default: + return; } samples->raise(std::move(sample_buffer)); } } -} +} // namespace audio diff --git a/sunshine/audio.h b/sunshine/audio.h index 4e27c8c0..f170fc36 100644 --- a/sunshine/audio.h +++ b/sunshine/audio.h @@ -1,8 +1,8 @@ #ifndef SUNSHINE_AUDIO_H #define SUNSHINE_AUDIO_H -#include "utility.h" #include "thread_safe.h" +#include "utility.h" namespace audio { struct config_t { int packetDuration; @@ -10,9 +10,9 @@ struct config_t { int mask; }; -using packet_t = util::buffer_t; -using packet_queue_t = std::shared_ptr>>; +using packet_t = util::buffer_t; +using packet_queue_t = std::shared_ptr>>; void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data); -} +} // namespace audio #endif diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 9408578c..6cc158bd 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -1,12 +1,12 @@ #include -#include #include +#include #include #include -#include "utility.h" #include "config.h" +#include "utility.h" #define CA_DIR "credentials" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" @@ -33,12 +33,12 @@ enum preset_e : int { }; enum rc_e : int { - constqp = 0x0, /**< Constant QP mode */ - vbr = 0x1, /**< Variable bitrate mode */ - cbr = 0x2, /**< Constant bitrate mode */ - cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ - cbr_hq = 0x10, /**< CBR, high quality (slower) */ - vbr_hq = 0x20 /**< VBR, high quality (slower) */ + constqp = 0x0, /**< Constant QP mode */ + vbr = 0x1, /**< Variable bitrate mode */ + cbr = 0x2, /**< Constant bitrate mode */ + cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ + cbr_hq = 0x10, /**< CBR, high quality (slower) */ + vbr_hq = 0x20 /**< VBR, high quality (slower) */ }; enum coder_e : int { @@ -48,7 +48,8 @@ enum coder_e : int { }; std::optional 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_(medium); _CONVERT_(fast); @@ -65,7 +66,8 @@ std::optional preset_from_view(const std::string_view &preset) { } std::optional 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_(vbr); _CONVERT_(cbr); @@ -78,12 +80,12 @@ std::optional rc_from_view(const std::string_view &rc) { int coder_from_view(const std::string_view &coder) { if(coder == "auto"sv) return _auto; - if(coder == "cabac"sv || coder == "ac"sv) return cabac; - if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; return -1; } -} +} // namespace nv namespace amd { enum quality_e : int { @@ -107,7 +109,8 @@ enum coder_e : int { }; std::optional 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_(balanced); //_CONVERT_(quality2); @@ -117,7 +120,8 @@ std::optional quality_from_view(const std::string_view &quality) { } std::optional 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_(vbr_latency); _CONVERT_(vbr_peak); @@ -128,40 +132,38 @@ std::optional rc_from_view(const std::string_view &rc) { int coder_from_view(const std::string_view &coder) { if(coder == "auto"sv) return _auto; - if(coder == "cabac"sv || coder == "ac"sv) return cabac; - if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; return -1; } -} +} // namespace amd video_t video { - 0, // crf + 0, // crf 28, // qp 0, // hevc_mode 1, // min_threads { - "superfast"s, // preset + "superfast"s, // preset "zerolatency"s, // tune - }, // software + }, // software { nv::llhq, std::nullopt, - -1 - }, // nv + -1 }, // nv { amd::balanced, std::nullopt, - -1 - }, // amd + -1 }, // amd {}, // encoder {}, // adapter_name - {}, // output_name + {}, // output_name }; audio_t audio {}; @@ -172,7 +174,7 @@ stream_t stream { APPS_JSON_PATH, 10, // fecPercentage - 1 // channels + 1 // channels }; nvhttp_t nvhttp { @@ -181,18 +183,18 @@ nvhttp_t nvhttp { CERTIFICATE_FILE, boost::asio::ip::host_name(), // sunshine_name, - "sunshine_state.json"s // file_state + "sunshine_state.json"s // file_state }; input_t input { - 2s, // back_button_timeout - 500ms, // key_repeat_delay - std::chrono::duration { 1 / 24.9 } // key_repeat_period + 2s, // back_button_timeout + 500ms, // key_repeat_delay + std::chrono::duration { 1 / 24.9 } // key_repeat_period }; sunshine_t sunshine { 2, // min_log_level - 0 // flags + 0 // flags }; bool whitespace(char ch) { @@ -213,7 +215,7 @@ std::optional> parse_line(std::string_view:: return std::nullopt; } - auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base(); + auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base(); 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) }; @@ -227,7 +229,7 @@ std::unordered_map parse_config(std::string_view file_ while(pos < end) { auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; }); - auto var = parse_line(pos, newline); + auto var = parse_line(pos, newline); pos = (*newline == '\r') ? newline + 2 : newline + 1; if(!var) { @@ -271,7 +273,7 @@ void int_f(std::unordered_map &vars, const std::string } auto &val = it->second; - input = util::from_chars(&val[0], &val[0] + val.size()); + input = util::from_chars(&val[0], &val[0] + val.size()); vars.erase(it); } @@ -284,7 +286,7 @@ void int_f(std::unordered_map &vars, const std::string } auto &val = it->second; - input = util::from_chars(&val[0], &val[0] + val.size()); + input = util::from_chars(&val[0], &val[0] + val.size()); vars.erase(it); } @@ -319,15 +321,14 @@ void int_between_f(std::unordered_map &vars, const std } 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 - boolean == "true"sv || - boolean == "yes"sv || - boolean == "enable"sv || - (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); + return boolean == "true"sv || + boolean == "yes"sv || + boolean == "enable"sv || + (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); } -void bool_f(std::unordered_map &vars, const std::string &name, int &input) { +void bool_f(std::unordered_map &vars, const std::string &name, int &input) { std::string tmp; string_f(vars, name, tmp); @@ -338,7 +339,7 @@ void bool_f(std::unordered_map &vars, const std::strin input = to_bool(tmp) ? 1 : 0; } -void double_f(std::unordered_map &vars, const std::string &name, double &input) { +void double_f(std::unordered_map &vars, const std::string &name, double &input) { std::string tmp; string_f(vars, name, tmp); @@ -368,32 +369,33 @@ void double_between_f(std::unordered_map &vars, const } void print_help(const char *name) { - std::cout << - "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 << - " --help | print help"sv << std::endl << std::endl << - " flags"sv << std::endl << - " -0 | Read PIN from stdin"sv << std::endl << - " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl << - " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv; + std::cout << "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 + << " --help | print help"sv << std::endl + << std::endl + << " flags"sv << std::endl + << " -0 | Read PIN from stdin"sv << std::endl + << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl + << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv; } int apply_flags(const char *line) { int ret = 0; while(*line != '\0') { switch(*line) { - case '0': - config::sunshine.flags[config::flag::PIN_STDIN].flip(); - break; - case '1': - config::sunshine.flags[config::flag::FRESH_STATE].flip(); - break; - case 'p': - config::sunshine.flags[config::flag::CONST_PIN].flip(); - break; - default: - std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; - ret = -1; + case '0': + config::sunshine.flags[config::flag::PIN_STDIN].flip(); + break; + case '1': + config::sunshine.flags[config::flag::FRESH_STATE].flip(); + break; + case 'p': + config::sunshine.flags[config::flag::CONST_PIN].flip(); + break; + default: + std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; + ret = -1; } ++line; @@ -410,9 +412,7 @@ void apply_config(std::unordered_map &&vars) { int_f(vars, "crf", video.crf); int_f(vars, "qp", video.qp); int_f(vars, "min_threads", video.min_threads); - int_between_f(vars, "hevc_mode", video.hevc_mode, { - 0, 3 - }); + int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 }); string_f(vars, "sw_preset", video.sw.preset); string_f(vars, "sw_tune", video.sw.tune); int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); @@ -435,26 +435,18 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "audio_sink", audio.sink); - string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { - "pc"sv, "lan"sv, "wan"sv - }); + string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv }); int to = -1; - int_between_f(vars, "ping_timeout", to, { - -1, std::numeric_limits::max() - }); + int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits::max() }); if(to != -1) { stream.ping_timeout = std::chrono::milliseconds(to); } - int_between_f(vars, "channels", stream.channels, { - 1, std::numeric_limits::max() - }); + int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits::max() }); string_f(vars, "file_apps", stream.file_apps); - int_between_f(vars, "fec_percentage", stream.fec_percentage, { - 1, 100 - }); + int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 100 }); to = std::numeric_limits::min(); int_f(vars, "back_button_timeout", to); @@ -464,12 +456,10 @@ void apply_config(std::unordered_map &&vars) { } double repeat_frequency { 0 }; - double_between_f(vars, "key_repeat_frequency", repeat_frequency, { - 0, std::numeric_limits::max() - }); + double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits::max() }); if(repeat_frequency > 0) { - config::input.key_repeat_period = std::chrono::duration {1 / repeat_frequency }; + config::input.key_repeat_period = std::chrono::duration { 1 / repeat_frequency }; } to = -1; @@ -479,9 +469,7 @@ void apply_config(std::unordered_map &&vars) { } std::string log_level_string; - string_restricted_f(vars, "min_log_level", log_level_string, { - "verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv - }); + string_restricted_f(vars, "min_log_level", log_level_string, { "verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv }); if(!log_level_string.empty()) { if(log_level_string == "verbose"sv) { @@ -515,7 +503,7 @@ void apply_config(std::unordered_map &&vars) { } if(sunshine.min_log_level <= 3) { - for(auto &[var,_] : vars) { + for(auto &[var, _] : vars) { std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl; } } @@ -526,7 +514,7 @@ int parse(int argc, char *argv[]) { std::unordered_map cmd_vars; - for(auto x = argc -1; x > 0; --x) { + for(auto x = argc - 1; x > 0; --x) { auto line = argv[x]; if(line == "--help"sv) { @@ -569,10 +557,9 @@ int parse(int argc, char *argv[]) { auto vars = parse_config(std::string { // Quick and dirty std::istreambuf_iterator(in), - std::istreambuf_iterator() - }); + std::istreambuf_iterator() }); - for(auto &[name,value] : cmd_vars) { + for(auto &[name, value] : cmd_vars) { vars.insert_or_assign(std::move(name), std::move(value)); } @@ -580,4 +567,4 @@ int parse(int argc, char *argv[]) { return 0; } -} +} // namespace config diff --git a/sunshine/config.h b/sunshine/config.h index f67ac12b..be75f3d2 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -1,16 +1,16 @@ #ifndef SUNSHINE_CONFIG_H #define SUNSHINE_CONFIG_H -#include -#include #include +#include #include +#include namespace config { struct video_t { // ffmpeg params int crf; // higher == more compression and less quality - int qp; // higher == more compression and less quality, ignored if crf != 0 + int qp; // higher == more compression and less quality, ignored if crf != 0 int hevc_mode; @@ -76,9 +76,9 @@ struct input_t { namespace flag { enum flag_e : std::size_t { PIN_STDIN = 0, // Read PIN from stdin instead of http - FRESH_STATE, // Do not load or save state + FRESH_STATE, // Do not load or save state FLAG_SIZE, - CONST_PIN= 4 // Use "universal" pin + CONST_PIN = 4 // Use "universal" pin }; } @@ -96,6 +96,6 @@ extern input_t input; extern sunshine_t sunshine; int parse(int argc, char *argv[]); -} +} // namespace config #endif diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index 398c8e09..c5069a71 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -2,14 +2,14 @@ // Created by loki on 5/31/19. // -#include #include "crypto.h" +#include namespace crypto { -using big_num_t = util::safe_ptr; +using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; using asn1_string_t = util::safe_ptr; -cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx {X509_STORE_CTX_new() } {} +cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {} void cert_chain_t::add(x509_t &&cert) { x509_store_t x509_store { X509_STORE_new() }; @@ -26,7 +26,7 @@ void cert_chain_t::add(x509_t &&cert) { */ const char *cert_chain_t::verify(x509_t::element_type *cert) { int err_code = 0; - for(auto &[_,x509_store] : _certs) { + for(auto &[_, x509_store] : _certs) { auto fg = util::fail_guard([this]() { X509_STORE_CTX_cleanup(_cert_ctx.get()); }); @@ -36,7 +36,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) { auto err = X509_verify_cert(_cert_ctx.get()); - if (err == 1) { + if(err == 1) { return nullptr; } @@ -46,7 +46,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) { if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) { return nullptr; } - if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) { + if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) { return X509_verify_cert_error_string(err_code); } } @@ -63,7 +63,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector }); // Gen 7 servers use 128-bit AES ECB - if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { + if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { return -1; } @@ -72,11 +72,11 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector plaintext.resize((cipher.size() + 15) / 16 * 16); auto size = (int)plaintext.size(); // Encrypt into the caller's buffer, leaving room for the auth tag to be prepended - if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) { + if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { return -1; } - if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) { + if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) { return -1; } @@ -85,7 +85,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector } int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, - std::vector &plaintext) { + std::vector &plaintext) { auto cipher = tagged_cipher.substr(16); auto tag = tagged_cipher.substr(0, 16); @@ -93,15 +93,15 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, EVP_CIPHER_CTX_reset(ctx.get()); }); - if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) { + if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) { return -1; } - if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) { + if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) { return -1; } - if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) { + if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) { return -1; } @@ -109,16 +109,16 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, plaintext.resize((cipher.size() + 15) / 16 * 16); int size; - if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) { + if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { return -1; } - if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())) != 1) { + if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())) != 1) { return -1; } int len = size; - if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) { + if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) { return -1; } @@ -134,7 +134,7 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vectordata, (std::size_t)asn1->length }; + return { (const char *)asn1->data, (std::size_t)asn1->length }; } std::string rand(std::size_t bytes) { std::string r; r.resize(bytes); - RAND_bytes((uint8_t*)r.data(), r.size()); + RAND_bytes((uint8_t *)r.data(), r.size()); return r; } @@ -297,8 +297,8 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) { X509_set_pubkey(x509.get(), pkey.get()); auto name = X509_get_subject_name(x509.get()); - X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, - (const std::uint8_t*)cn.data(), cn.size(), + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const std::uint8_t *)cn.data(), cn.size(), -1, 0); X509_set_issuer_name(x509.get(), name); @@ -324,7 +324,7 @@ bool verify(const x509_t &x509, const std::string_view &data, const std::string_ return false; } - if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) { + if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) { return false; } @@ -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) { EVP_MD_CTX_destroy(ctx); } -} +} // namespace crypto diff --git a/sunshine/crypto.h b/sunshine/crypto.h index faf1fc1f..8a1b70c8 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -5,12 +5,12 @@ #ifndef SUNSHINE_CRYPTO_H #define SUNSHINE_CRYPTO_H -#include #include +#include #include +#include #include #include -#include #include "utility.h" @@ -25,14 +25,14 @@ void md_ctx_destroy(EVP_MD_CTX *); using sha256_t = std::array; -using aes_t = std::array; -using x509_t = util::safe_ptr; -using x509_store_t = util::safe_ptr; +using aes_t = std::array; +using x509_t = util::safe_ptr; +using x509_store_t = util::safe_ptr; using x509_store_ctx_t = util::safe_ptr; -using cipher_ctx_t = util::safe_ptr; -using md_ctx_t = util::safe_ptr; -using bio_t = util::safe_ptr; -using pkey_t = util::safe_ptr; +using cipher_ctx_t = util::safe_ptr; +using md_ctx_t = util::safe_ptr; +using bio_t = util::safe_ptr; +using pkey_t = util::safe_ptr; sha256_t hash(const std::string_view &plaintext); aes_t gen_aes_key(const std::array &salt, const std::string_view &pin); @@ -58,6 +58,7 @@ public: void add(x509_t &&cert); const char *verify(x509_t::element_type *cert); + private: std::vector> _certs; x509_store_ctx_t _cert_ctx; @@ -66,13 +67,14 @@ private: class cipher_t { public: cipher_t(const aes_t &key); - cipher_t(cipher_t&&) noexcept = default; - cipher_t &operator=(cipher_t&&) noexcept = default; + cipher_t(cipher_t &&) noexcept = default; + cipher_t &operator=(cipher_t &&) noexcept = default; int encrypt(const std::string_view &plaintext, std::vector &cipher); int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector &plaintext); int decrypt(const std::string_view &cipher, std::vector &plaintext); + private: cipher_ctx_t ctx; aes_t key; @@ -80,6 +82,6 @@ private: public: bool padding; }; -} +} // namespace crypto #endif //SUNSHINE_CRYPTO_H diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 6a585106..6be53163 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -10,16 +10,16 @@ extern "C" { #include -#include "main.h" #include "config.h" -#include "utility.h" -#include "thread_pool.h" #include "input.h" +#include "main.h" #include "platform/common.h" +#include "thread_pool.h" +#include "utility.h" 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 @@ -59,7 +59,7 @@ static platf::input_t platf_input; static std::bitset gamepadMask {}; void free_gamepad(platf::input_t &platf_input, int id) { - platf::gamepad(platf_input, id, platf::gamepad_state_t{}); + platf::gamepad(platf_input, id, platf::gamepad_state_t {}); platf::free_gamepad(platf_input, id); free_id(gamepadMask, id); @@ -68,7 +68,7 @@ struct gamepad_t { gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {} ~gamepad_t() { if(id >= 0) { - task_pool.push([id=this->id]() { + task_pool.push([id = this->id]() { free_gamepad(platf_input, id); }); } @@ -89,7 +89,7 @@ struct gamepad_t { }; struct input_t { - input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS), mouse_left_button_timeout {} { } + input_t() : active_gamepad_state {}, gamepads(MAX_GAMEPADS), mouse_left_button_timeout {} {} std::uint16_t active_gamepad_state; std::vector gamepads; @@ -158,33 +158,32 @@ void print(PNV_MULTI_CONTROLLER_PACKET packet) { constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL; void print(void *input) { - int input_type = util::endian::big(*(int*)input); + int input_type = util::endian::big(*(int *)input); switch(input_type) { - case PACKET_TYPE_REL_MOUSE_MOVE: - print((PNV_REL_MOUSE_MOVE_PACKET)input); - break; - case PACKET_TYPE_ABS_MOUSE_MOVE: - print((PNV_ABS_MOUSE_MOVE_PACKET)input); - break; - case PACKET_TYPE_MOUSE_BUTTON: - print((PNV_MOUSE_BUTTON_PACKET)input); - break; - case PACKET_TYPE_SCROLL_OR_KEYBOARD: - { - char *tmp_input = (char*)input + 4; - if(tmp_input[0] == 0x0A) { - print((PNV_SCROLL_PACKET)input); - } - else { - print((PNV_KEYBOARD_PACKET)input); - } - - break; + case PACKET_TYPE_REL_MOUSE_MOVE: + print((PNV_REL_MOUSE_MOVE_PACKET)input); + break; + case PACKET_TYPE_ABS_MOUSE_MOVE: + print((PNV_ABS_MOUSE_MOVE_PACKET)input); + break; + case PACKET_TYPE_MOUSE_BUTTON: + print((PNV_MOUSE_BUTTON_PACKET)input); + break; + case PACKET_TYPE_SCROLL_OR_KEYBOARD: { + char *tmp_input = (char *)input + 4; + if(tmp_input[0] == 0x0A) { + print((PNV_SCROLL_PACKET)input); } - case PACKET_TYPE_MULTI_CONTROLLER: - print((PNV_MULTI_CONTROLLER_PACKET)input); - break; + else { + print((PNV_KEYBOARD_PACKET)input); + } + + break; + } + case PACKET_TYPE_MULTI_CONTROLLER: + print((PNV_MULTI_CONTROLLER_PACKET)input); + break; } } @@ -245,8 +244,8 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet 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 @@ -261,7 +260,7 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet * when the last mouse coordinates were absolute /*/ if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) { - input->mouse_left_button_timeout = task_pool.pushDelayed([=]() { + auto f = [=]() { auto left_released = mouse_press[BUTTON_LEFT]; if(left_released) { // Already released left button @@ -269,16 +268,17 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet } platf::button_mouse(platf_input, BUTTON_LEFT, release); - mouse_press[BUTTON_LEFT] = false; + mouse_press[BUTTON_LEFT] = false; input->mouse_left_button_timeout = nullptr; - }, 10ms).task_id; + }; + + 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 - ) { + 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); @@ -286,7 +286,7 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet return; } -/////////////////////////////////// + /////////////////////////////////// platf::button_mouse(platf_input, button, release); } @@ -341,7 +341,7 @@ void passthrough(PNV_SCROLL_PACKET packet) { int updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state) { auto xorGamepadMask = old_state ^ new_state; - if (!xorGamepadMask) { + if(!xorGamepadMask) { return 0; } @@ -350,7 +350,7 @@ int updateGamepads(std::vector &gamepads, std::int16_t old_state, std auto &gamepad = gamepads[x]; if((old_state >> x) & 1) { - if (gamepad.id < 0) { + if(gamepad.id < 0) { return -1; } @@ -410,7 +410,7 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa display_cursor = false; std::uint16_t bf = packet->buttonFlags; - platf::gamepad_state_t gamepad_state{ + platf::gamepad_state_t gamepad_state { bf, packet->leftTrigger, packet->rightTrigger, @@ -422,30 +422,30 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa auto bf_new = gamepad_state.buttonFlags; switch(gamepad.back_button_state) { - case button_state_e::UP: - if(!(platf::BACK & bf_new)) { - gamepad.back_button_state = button_state_e::NONE; - } - gamepad_state.buttonFlags &= ~platf::BACK; - break; - case button_state_e::DOWN: - if(platf::BACK & bf_new) { - gamepad.back_button_state = button_state_e::NONE; - } - gamepad_state.buttonFlags |= platf::BACK; - break; - case button_state_e::NONE: - break; + case button_state_e::UP: + if(!(platf::BACK & bf_new)) { + gamepad.back_button_state = button_state_e::NONE; + } + gamepad_state.buttonFlags &= ~platf::BACK; + break; + case button_state_e::DOWN: + if(platf::BACK & bf_new) { + gamepad.back_button_state = button_state_e::NONE; + } + gamepad_state.buttonFlags |= platf::BACK; + break; + case button_state_e::NONE: + break; } - bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags; + bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags; bf_new = gamepad_state.buttonFlags; - if (platf::BACK & bf) { - if (platf::BACK & bf_new) { + if(platf::BACK & bf) { + if(platf::BACK & bf_new) { // Don't emulate home button if timeout < 0 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 &state = gamepad.gamepad_state; @@ -464,10 +464,12 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa platf::gamepad(platf_input, gamepad.id, state); gamepad.back_timeout_id = nullptr; - }, config::input.back_button_timeout).task_id; + }; + + gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id; } } - else if (gamepad.back_timeout_id) { + else if(gamepad.back_timeout_id) { task_pool.cancel(gamepad.back_timeout_id); gamepad.back_timeout_id = nullptr; } @@ -481,33 +483,32 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa void passthrough_helper(std::shared_ptr input, std::vector &&input_data) { void *payload = input_data.data(); - int input_type = util::endian::big(*(int*)payload); + int input_type = util::endian::big(*(int *)payload); switch(input_type) { - case PACKET_TYPE_REL_MOUSE_MOVE: - passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload); - break; - case PACKET_TYPE_ABS_MOUSE_MOVE: - passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload); - break; - case PACKET_TYPE_MOUSE_BUTTON: - passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload); - break; - case PACKET_TYPE_SCROLL_OR_KEYBOARD: - { - char *tmp_input = (char*)payload + 4; - if(tmp_input[0] == 0x0A) { - passthrough((PNV_SCROLL_PACKET)payload); - } - else { - passthrough(input, (PNV_KEYBOARD_PACKET)payload); - } - - break; + case PACKET_TYPE_REL_MOUSE_MOVE: + passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload); + break; + case PACKET_TYPE_ABS_MOUSE_MOVE: + passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload); + break; + case PACKET_TYPE_MOUSE_BUTTON: + passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload); + break; + case PACKET_TYPE_SCROLL_OR_KEYBOARD: { + char *tmp_input = (char *)payload + 4; + if(tmp_input[0] == 0x0A) { + passthrough((PNV_SCROLL_PACKET)payload); } - case PACKET_TYPE_MULTI_CONTROLLER: - passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload); - break; + else { + passthrough(input, (PNV_KEYBOARD_PACKET)payload); + } + + break; + } + case PACKET_TYPE_MULTI_CONTROLLER: + passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload); + break; } } @@ -528,7 +529,7 @@ void reset(std::shared_ptr &input) { } } - for(auto& kp : key_press) { + for(auto &kp : key_press) { platf::keyboard(platf_input, kp.first & 0x00FF, true); key_press[kp.first] = false; } @@ -537,7 +538,7 @@ void reset(std::shared_ptr &input) { void init() { touch_port_event = std::make_unique(); - platf_input = platf::input(); + platf_input = platf::input(); } std::shared_ptr alloc() { @@ -547,8 +548,9 @@ std::shared_ptr alloc() { task_pool.pushDelayed([]() { platf::move_mouse(platf_input, 1, 1); platf::move_mouse(platf_input, -1, -1); - }, 100ms); + }, + 100ms); return input; } -} +} // namespace input diff --git a/sunshine/input.h b/sunshine/input.h index 16267619..4c94bb4a 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -5,8 +5,8 @@ #ifndef SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H -#include "thread_safe.h" #include "platform/common.h" +#include "thread_safe.h" namespace input { struct input_t; @@ -22,6 +22,6 @@ std::shared_ptr alloc(); using touch_port_event_t = std::unique_ptr>; extern touch_port_event_t touch_port_event; -} +} // namespace input #endif //SUNSHINE_INPUT_H diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 9dc686a9..0da0a6f6 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -4,26 +4,26 @@ #include "process.h" -#include -#include #include +#include +#include -#include -#include -#include -#include #include +#include +#include +#include +#include -#include "video.h" +#include "config.h" #include "nvhttp.h" #include "rtsp.h" -#include "config.h" #include "thread_pool.h" +#include "video.h" #include "platform/common.h" extern "C" { -#include #include +#include } using namespace std::literals; @@ -79,31 +79,31 @@ int main(int argc, char *argv[]) { sink->locked_backend()->add_stream(stream); sink->set_filter(severity >= config::sunshine.min_log_level); - sink->set_formatter([message="Message"s, severity="Severity"s](const bl::record_view &view, bl::formatting_ostream &os) { - constexpr int DATE_BUFFER_SIZE = 21 +2 +1; // Full string plus ": \0" + sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) { + constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0" auto log_level = view.attribute_values()[severity].extract().get(); std::string_view log_type; switch(log_level) { - case 0: - log_type = "Verbose: "sv; - break; - case 1: - log_type = "Debug: "sv; - break; - case 2: - log_type = "Info: "sv; - break; - case 3: - log_type = "Warning: "sv; - break; - case 4: - log_type = "Error: "sv; - break; - case 5: - log_type = "Fatal: "sv; - break; + case 0: + log_type = "Verbose: "sv; + break; + case 1: + log_type = "Debug: "sv; + break; + case 2: + log_type = "Info: "sv; + break; + case 3: + log_type = "Warning: "sv; + break; + case 4: + log_type = "Error: "sv; + break; + case 5: + log_type = "Fatal: "sv; + break; }; char _date[DATE_BUFFER_SIZE]; diff --git a/sunshine/main.h b/sunshine/main.h index b8a85e5b..1cb6f1da 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -5,8 +5,8 @@ #ifndef SUNSHINE_MAIN_H #define SUNSHINE_MAIN_H -#include #include "thread_pool.h" +#include extern util::ThreadPool task_pool; extern bool display_cursor; diff --git a/sunshine/move_by_copy.h b/sunshine/move_by_copy.h index 61e205f5..804a72ba 100644 --- a/sunshine/move_by_copy.h +++ b/sunshine/move_by_copy.h @@ -11,23 +11,24 @@ template class MoveByCopy { public: typedef T move_type; + private: move_type _to_move; -public: - explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { } +public: + explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {} MoveByCopy(MoveByCopy &&other) = default; - + MoveByCopy(const MoveByCopy &other) { *this = other; } - MoveByCopy& operator=(MoveByCopy &&other) = default; - - MoveByCopy& operator=(const MoveByCopy &other) { - this->_to_move = std::move(const_cast(other)._to_move); - + MoveByCopy &operator=(MoveByCopy &&other) = default; + + MoveByCopy &operator=(const MoveByCopy &other) { + this->_to_move = std::move(const_cast(other)._to_move); + return *this; } @@ -44,7 +45,7 @@ MoveByCopy cmove(T &movable) { // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller template MoveByCopy const_cmove(const T &movable) { - return MoveByCopy(std::move(const_cast(movable))); -} + return MoveByCopy(std::move(const_cast(movable))); } +} // namespace util #endif diff --git a/sunshine/network.cpp b/sunshine/network.cpp index 1efb5364..28e031ef 100644 --- a/sunshine/network.cpp +++ b/sunshine/network.cpp @@ -2,9 +2,9 @@ // Created by loki on 12/27/19. // -#include #include "network.h" #include "utility.h" +#include namespace net { using namespace std::literals; @@ -21,17 +21,17 @@ std::vector> lan_ips { }; std::uint32_t ip(const std::string_view &ip_str) { - auto begin = std::begin(ip_str); - auto end = std::end(ip_str); + auto begin = std::begin(ip_str); + auto end = std::end(ip_str); auto temp_end = std::find(begin, end, '.'); std::uint32_t ip = 0; - auto shift = 24; + auto shift = 24; while(temp_end != end) { ip += (util::from_chars(begin, temp_end) << shift); shift -= 8; - begin = temp_end + 1; + begin = temp_end + 1; temp_end = std::find(begin, end, '.'); } @@ -43,7 +43,7 @@ std::uint32_t ip(const std::string_view &ip_str) { // In the format "xxx.xxx.xxx.xxx/x" std::pair ip_block(const std::string_view &ip_str) { auto begin = std::begin(ip_str); - auto end = std::find(begin, std::end(ip_str), '/'); + auto end = std::find(begin, std::end(ip_str), '/'); auto addr = ip({ begin, (std::size_t)(end - begin) }); @@ -82,12 +82,12 @@ net_e from_address(const std::string_view &view) { std::string_view to_enum_string(net_e net) { switch(net) { - case PC: - return "pc"sv; - case LAN: - return "lan"sv; - case WAN: - return "wan"sv; + case PC: + return "pc"sv; + case LAN: + return "lan"sv; + case WAN: + return "wan"sv; } // avoid warning @@ -112,4 +112,4 @@ void free_host(ENetHost *host) { enet_host_destroy(host); } -} \ No newline at end of file +} // namespace net \ No newline at end of file diff --git a/sunshine/network.h b/sunshine/network.h index b3e65a97..88f18a40 100644 --- a/sunshine/network.h +++ b/sunshine/network.h @@ -14,8 +14,8 @@ namespace net { void free_host(ENetHost *host); -using host_t = util::safe_ptr; -using peer_t = ENetPeer*; +using host_t = util::safe_ptr; +using peer_t = ENetPeer *; using packet_t = util::safe_ptr; enum net_e : int { @@ -30,6 +30,6 @@ std::string_view to_enum_string(net_e net); net_e from_address(const std::string_view &view); host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port); -} +} // namespace net #endif //SUNSHINE_NETWORK_H diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 98f5a79d..ae413c39 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -6,9 +6,9 @@ #include +#include #include #include -#include #include @@ -17,14 +17,14 @@ #include #include "config.h" -#include "utility.h" -#include "rtsp.h" #include "crypto.h" +#include "main.h" +#include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "network.h" +#include "rtsp.h" +#include "utility.h" #include "uuid.h" -#include "main.h" namespace nvhttp { @@ -69,8 +69,8 @@ struct pair_session_t { struct { util::Either< std::shared_ptr::Response>, - std::shared_ptr::Response> - > response; + std::shared_ptr::Response>> + response; std::string salt; } async_insert_pin; }; @@ -81,11 +81,11 @@ std::unordered_map map_id_client; std::string unique_id; net::net_e origin_pin_allowed; -using args_t = SimpleWeb::CaseInsensitiveMultimap; +using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; -using req_https_t = std::shared_ptr::Request>; -using resp_http_t = std::shared_ptr::Response>; -using req_http_t = std::shared_ptr::Request>; +using req_https_t = std::shared_ptr::Request>; +using resp_http_t = std::shared_ptr::Response>; +using req_http_t = std::shared_ptr::Request>; enum class op_e { ADD, @@ -97,7 +97,7 @@ void save_state() { root.put("root.uniqueid", unique_id); auto &nodes = root.add_child("root.devices", pt::ptree {}); - for(auto &[_,client] : map_id_client) { + for(auto &[_, client] : map_id_client) { pt::ptree node; node.put("uniqueid"s, client.uniqueID); @@ -127,17 +127,18 @@ void load_state() { pt::ptree root; try { pt::read_json(config::nvhttp.file_state, root); - } catch (std::exception &e) { + } + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); return; } - unique_id = root.get("root.uniqueid"); + unique_id = root.get("root.uniqueid"); auto device_nodes = root.get_child("root.devices"); - for(auto &[_,device_node] : device_nodes) { - auto uniqID = device_node.get("uniqueid"); + for(auto &[_, device_node] : device_nodes) { + auto uniqID = device_node.get("uniqueid"); auto &client = map_id_client.emplace(uniqID, client_t {}).first->second; client.uniqueID = uniqID; @@ -150,16 +151,14 @@ void load_state() { void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) { switch(op) { - case op_e::ADD: - { - auto &client = map_id_client[uniqueID]; - client.certs.emplace_back(std::move(cert)); - client.uniqueID = uniqueID; - } - break; - case op_e::REMOVE: - map_id_client.erase(uniqueID); - break; + case op_e::ADD: { + auto &client = map_id_client[uniqueID]; + client.certs.emplace_back(std::move(cert)); + client.uniqueID = uniqueID; + } break; + case op_e::REMOVE: + map_id_client.erase(uniqueID); + break; } if(!config::sunshine.flags[config::flag::FRESH_STATE]) { @@ -175,10 +174,10 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin } std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 }; - + auto salt = util::from_hex>(salt_view, true); - auto key = crypto::gen_aes_key(*salt, pin); + auto key = crypto::gen_aes_key(*salt, pin); sess.cipher_key = std::make_unique(key); tree.put("root.paired", 1); @@ -197,7 +196,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar sess.clienthash = std::move(decrypted); auto serversecret = sess.serversecret; - auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret); + auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret); serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign)); @@ -215,14 +214,14 @@ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) std::vector decrypted; cipher.decrypt(challenge, decrypted); - auto x509 = crypto::x509(conf_intern.servercert); - auto sign = crypto::signature(x509); + auto x509 = crypto::x509(conf_intern.servercert); + auto sign = crypto::signature(x509); auto serversecret = crypto::rand(16); decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign)); decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret)); - auto hash = crypto::hash({ (char*)decrypted.data(), decrypted.size() }); + auto hash = crypto::hash({ (char *)decrypted.data(), decrypted.size() }); auto serverchallenge = crypto::rand(16); std::string plaintext; @@ -252,7 +251,7 @@ void clientpairingsecret(std::shared_ptr> &add_cer assert((secret.size() + sign.size()) == pairingsecret.size()); - auto x509 = crypto::x509(client.cert); + auto x509 = crypto::x509(client.cert); auto x509_sign = crypto::signature(x509); std::string data; @@ -306,8 +305,8 @@ template void print_req(std::shared_ptr::Request> request) { BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel::to_string; - BOOST_LOG(debug) << "METHOD :: "sv << request->method; - BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; + BOOST_LOG(debug) << "METHOD :: "sv << request->method; + BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; for(auto &[name, val] : request->header) { BOOST_LOG(debug) << name << " -- " << val; @@ -334,7 +333,8 @@ void not_found(std::shared_ptr::Response> resp pt::write_xml(data, tree); response->write(data.str()); - *response << "HTTP/1.1 404 NOT FOUND\r\n" << data.str(); + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); } template @@ -351,9 +351,9 @@ void pair(std::shared_ptr> &add_cert, std::shared_ if(it = args.find("phrase"); it != std::end(args)) { if(it->second == "getservercert"sv) { pair_session_t sess; - + sess.client.uniqueID = std::move(uniqID); - sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true); + sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true); BOOST_LOG(debug) << sess.client.cert; auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; @@ -435,7 +435,7 @@ void pin(std::shared_ptr::Response> response, if(async_response.has_left() && async_response.left()) { async_response.left()->write(data.str()); } - else if(async_response.has_right() && async_response.right()){ + else if(async_response.has_right() && async_response.right()) { async_response.right()->write(data.str()); } else { @@ -455,13 +455,13 @@ void serverinfo(std::shared_ptr::Response> res print_req(request); int pair_status = 0; - if constexpr (std::is_same_v) { - auto args = request->parse_query_string(); + if constexpr(std::is_same_v) { + auto args = request->parse_query_string(); auto clientID = args.find("uniqueid"s); if(clientID != std::end(args)) { - if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) { + if(auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) { pair_status = 1; } } @@ -507,7 +507,7 @@ void serverinfo(std::shared_ptr::Response> res void applist(resp_https_t response, req_https_t request) { print_req(request); - auto args = request->parse_query_string(); + auto args = request->parse_query_string(); auto clientID = args.at("uniqueid"s); pt::ptree tree; @@ -560,8 +560,8 @@ void launch(resp_https_t response, req_https_t request) { return; } - auto args = request->parse_query_string(); - auto appid = util::from_view(args.at("appid")) -1; + auto args = request->parse_query_string(); + auto appid = util::from_view(args.at("appid")) - 1; auto current_appid = proc::proc.running(); if(current_appid != -1) { @@ -583,10 +583,10 @@ void launch(resp_https_t response, req_https_t request) { stream::launch_session_t launch_session; - auto clientID = args.at("uniqueid"s); + auto clientID = args.at("uniqueid"s); launch_session.gcm_key = *util::from_hex(args.at("rikey"s), true); - uint32_t prepend_iv = util::endian::big(util::from_view(args.at("rikeyid"s))); - auto prepend_iv_p = (uint8_t*)&prepend_iv; + uint32_t prepend_iv = util::endian::big(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); @@ -627,11 +627,11 @@ void resume(resp_https_t response, req_https_t request) { stream::launch_session_t launch_session; - auto args = request->parse_query_string(); - auto clientID = args.at("uniqueid"s); + auto args = request->parse_query_string(); + auto clientID = args.at("uniqueid"s); launch_session.gcm_key = *util::from_hex(args.at("rikey"s), true); - uint32_t prepend_iv = util::endian::big(util::from_view(args.at("rikeyid"s))); - auto prepend_iv_p = (uint8_t*)&prepend_iv; + uint32_t prepend_iv = util::endian::big(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); @@ -688,25 +688,25 @@ int create_creds(const std::string &pkey, const std::string &cert) { pkey_dir.remove_filename(); cert_dir.remove_filename(); - std::error_code err_code{}; + std::error_code err_code {}; fs::create_directories(pkey_dir, err_code); - if (err_code) { + if(err_code) { BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); return -1; } fs::create_directories(cert_dir, err_code); - if (err_code) { + if(err_code) { BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); return -1; } - if (write_file(pkey.c_str(), creds.pkey)) { + if(write_file(pkey.c_str(), creds.pkey)) { BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; return -1; } - if (write_file(cert.c_str(), creds.x509)) { + if(write_file(cert.c_str(), creds.x509)) { BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; return -1; } @@ -715,7 +715,7 @@ int create_creds(const std::string &pkey, const std::string &cert) { fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code); - if (err_code) { + if(err_code) { BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); return -1; } @@ -724,7 +724,7 @@ int create_creds(const std::string &pkey, const std::string &cert) { fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, fs::perm_options::replace, err_code); - if (err_code) { + if(err_code) { BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); return -1; } @@ -757,7 +757,7 @@ void start(std::shared_ptr shutdown_event) { load_state(); } - conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); + conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); auto ctx = std::make_shared(boost::asio::ssl::context::tls); @@ -765,7 +765,7 @@ void start(std::shared_ptr shutdown_event) { ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); crypto::cert_chain_t cert_chain; - for(auto &[_,client] : map_id_client) { + for(auto &[_, client] : map_id_client) { for(auto &cert : client.certs) { cert_chain.add(crypto::x509(cert)); } @@ -813,33 +813,34 @@ void start(std::shared_ptr shutdown_event) { https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once }; http_server_t http_server; - https_server.default_resource = not_found; - https_server.resource["^/serverinfo$"]["GET"] = serverinfo; - https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; - https_server.resource["^/applist$"]["GET"] = applist; - https_server.resource["^/appasset$"]["GET"] = appasset; - https_server.resource["^/launch$"]["GET"] = launch; + https_server.default_resource = not_found; + https_server.resource["^/serverinfo$"]["GET"] = serverinfo; + https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; + https_server.resource["^/applist$"]["GET"] = applist; + https_server.resource["^/appasset$"]["GET"] = appasset; + https_server.resource["^/launch$"]["GET"] = launch; https_server.resource["^/pin/([0-9]+)$"]["GET"] = pin; - https_server.resource["^/resume$"]["GET"] = resume; - https_server.resource["^/cancel$"]["GET"] = cancel; + https_server.resource["^/resume$"]["GET"] = resume; + https_server.resource["^/cancel$"]["GET"] = cancel; https_server.config.reuse_address = true; - https_server.config.address = "0.0.0.0"s; - https_server.config.port = PORT_HTTPS; + https_server.config.address = "0.0.0.0"s; + https_server.config.port = PORT_HTTPS; - http_server.default_resource = not_found; - http_server.resource["^/serverinfo$"]["GET"] = serverinfo; - http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; + http_server.default_resource = not_found; + http_server.resource["^/serverinfo$"]["GET"] = serverinfo; + http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin; http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; try { https_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(); shutdown_event->raise(true); @@ -849,7 +850,8 @@ void start(std::shared_ptr shutdown_event) { auto accept_and_run = [&](auto *http_server) { try { 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 if(shutdown_event->peek()) { return; @@ -899,4 +901,4 @@ std::string read_file(const char *path) { return base64_cert; } -} +} // namespace nvhttp diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index eb90540a..8b3d50a4 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -11,8 +11,8 @@ #include "thread_safe.h" #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" -#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" -#define CERTIFICATE_FILE CA_DIR "/cacert.pem" +#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" +#define CERTIFICATE_FILE CA_DIR "/cacert.pem" namespace nvhttp { void start(std::shared_ptr shutdown_event); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 2f414509..c80fc750 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -5,9 +5,9 @@ #ifndef SUNSHINE_COMMON_H #define SUNSHINE_COMMON_H -#include -#include #include "sunshine/utility.h" +#include +#include struct sockaddr; namespace platf { @@ -44,8 +44,10 @@ enum class pix_fmt_e { }; inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) { -using namespace std::literals; -#define _CONVERT(x) case pix_fmt_e:: x : return #x ## sv + using namespace std::literals; +#define _CONVERT(x) \ + case pix_fmt_e::x: \ + return #x##sv switch(pix_fmt) { _CONVERT(yuv420p); _CONVERT(yuv420p10); @@ -65,9 +67,8 @@ struct touch_port_t { 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 } {}; + 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 { @@ -87,15 +88,15 @@ public: struct img_t { public: - std::uint8_t *data {}; - std::int32_t width {}; + std::uint8_t *data {}; + std::int32_t width {}; std::int32_t height {}; std::int32_t pixel_pitch {}; std::int32_t row_pitch {}; - img_t() = default; - img_t(const img_t&) = delete; - img_t(img_t&&) = delete; + img_t() = default; + img_t(const img_t &) = delete; + img_t(img_t &&) = delete; virtual ~img_t() = default; }; @@ -124,7 +125,7 @@ class display_t { 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 std::shared_ptr alloc_img() = 0; + virtual std::shared_ptr alloc_img() = 0; virtual int dummy_img(img_t *img) = 0; @@ -148,7 +149,7 @@ public: }; -void freeInput(void*); +void freeInput(void *); using input_t = util::safe_ptr; @@ -172,6 +173,6 @@ int alloc_gamepad(input_t &input, int nr); void free_gamepad(input_t &input, int nr); [[nodiscard]] std::unique_ptr init(); -} +} // namespace platf #endif //SUNSHINE_COMMON_H diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 79bd437b..0c45c749 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -4,8 +4,8 @@ #include "sunshine/platform/common.h" -#include #include +#include #include #include @@ -15,36 +15,35 @@ #include #include #include -#include -#include #include #include +#include +#include -#include #include +#include -#include "sunshine/task_pool.h" #include "sunshine/config.h" #include "sunshine/main.h" +#include "sunshine/task_pool.h" -namespace platf -{ +namespace platf { using namespace std::literals; -void freeImage(XImage*); -void freeX(XFixesCursorImage*); +void freeImage(XImage *); +void freeX(XFixesCursorImage *); -using ifaddr_t = util::safe_ptr; +using ifaddr_t = util::safe_ptr; using xcb_connect_t = util::safe_ptr; -using xcb_img_t = util::c_ptr; +using xcb_img_t = util::c_ptr; using xdisplay_t = util::safe_ptr_v2; -using ximg_t = util::safe_ptr; -using xcursor_t = util::safe_ptr; +using ximg_t = util::safe_ptr; +using xcursor_t = util::safe_ptr; -using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>; +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>; +using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>; class shm_id_t { public: @@ -55,7 +54,7 @@ public: } ~shm_id_t() { - if (id != -1) { + if(id != -1) { shmctl(id, IPC_RMID, nullptr); id = -1; } @@ -65,15 +64,15 @@ public: class shm_data_t { public: - shm_data_t() : data { (void*) -1 } {} + shm_data_t() : data { (void *)-1 } {} shm_data_t(void *data) : data { data } {} shm_data_t(shm_data_t &&other) noexcept : data(other.data) { - other.data = (void*)-1; + other.data = (void *)-1; } ~shm_data_t() { - if((std::uintptr_t) data != -1) { + if((std::uintptr_t)data != -1) { shmdt(data); } } @@ -81,11 +80,11 @@ public: void *data; }; -struct x11_img_t: public img_t { +struct x11_img_t : public img_t { ximg_t img; }; -struct shm_img_t: public img_t { +struct shm_img_t : public img_t { ~shm_img_t() override { delete[] data; data = nullptr; @@ -95,7 +94,7 @@ struct shm_img_t: public img_t { void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { xcursor_t overlay { XFixesGetCursorImage(display) }; - if (!overlay) { + if(!overlay) { BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; return; } @@ -109,40 +108,39 @@ void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { overlay->x = std::max((short)0, overlay->x); overlay->y = std::max((short)0, overlay->y); - auto pixels = (int*)img.data; + auto pixels = (int *)img.data; auto screen_height = img.height; - auto screen_width = img.width; + auto screen_width = img.width; - auto delta_height = std::min(overlay->height,std::max(0, screen_height - overlay->y)); - auto delta_width = std::min(overlay->width,std::max(0, screen_width - overlay->x)); - for (auto y = 0; y < delta_height; ++y) { + auto delta_height = std::min(overlay->height, std::max(0, screen_height - overlay->y)); + auto delta_width = std::min(overlay->width, std::max(0, screen_width - overlay->x)); + for(auto y = 0; y < delta_height; ++y) { auto overlay_begin = &overlay->pixels[y * overlay->width]; - auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; + auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; - auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x]; + auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x]; - std::for_each(overlay_begin, overlay_end,[&](long pixel) { - int *pixel_p = (int*) &pixel; + std::for_each(overlay_begin, overlay_end, [&](long pixel) { + int *pixel_p = (int *)&pixel; - auto colors_in = (uint8_t*) pixels_begin; + auto colors_in = (uint8_t *)pixels_begin; - auto alpha = (*(uint*) pixel_p) >> 24u; - if (alpha == 255) { + auto alpha = (*(uint *)pixel_p) >> 24u; + if(alpha == 255) { *pixels_begin = *pixel_p; } else { - auto colors_out = (uint8_t*) pixel_p; - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + auto colors_out = (uint8_t *)pixel_p; + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; } ++pixels_begin; }); } } -struct x11_attr_t: public display_t -{ +struct x11_attr_t : public display_t { xdisplay_t xdisplay; Window xwindow; XWindowAttributes xattr; @@ -153,7 +151,7 @@ struct x11_attr_t: public display_t */ int lastWidth, lastHeight; - x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow { }, xattr { } { + x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {} { XInitThreads(); } @@ -192,17 +190,17 @@ struct x11_attr_t: public display_t BOOST_LOG(info) << "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; - height = crt_info->height; + width = crt_info->width; + height = crt_info->height; offset_x = crt_info->x; offset_y = crt_info->y; } else { - width = xattr.width; + width = xattr.width; height = xattr.height; } - lastWidth = xattr.width; + lastWidth = xattr.width; lastHeight = xattr.height; return 0; @@ -219,21 +217,21 @@ struct x11_attr_t: public display_t refresh(); //The whole X server changed, so we gotta reinit everything - if (xattr.width != lastWidth || xattr.height != lastHeight) { - BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv; + if(xattr.width != lastWidth || xattr.height != lastHeight) { + BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv; return capture_e::reinit; } XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) }; - auto img_out = (x11_img_t*) img_out_base; - img_out->width = img->width; - img_out->height = img->height; - img_out->data = (uint8_t*) img->data; - img_out->row_pitch = img->bytes_per_line; + auto img_out = (x11_img_t *)img_out_base; + img_out->width = img->width; + img_out->height = img->height; + img_out->data = (uint8_t *)img->data; + img_out->row_pitch = img->bytes_per_line; img_out->pixel_pitch = img->bits_per_pixel / 8; img_out->img.reset(img); - if (cursor) { + if(cursor) { blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y); } @@ -250,7 +248,7 @@ struct x11_attr_t: public display_t } }; -struct shm_attr_t: public x11_attr_t { +struct shm_attr_t : public x11_attr_t { xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay xcb_connect_t xcb; xcb_screen_t *display; @@ -265,7 +263,7 @@ struct shm_attr_t: public x11_attr_t { void delayed_refresh() { refresh(); - refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id; + refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; } shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } { @@ -273,25 +271,26 @@ struct shm_attr_t: public x11_attr_t { } ~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 { //The whole X server changed, so we gotta reinit everything - if(xattr.width != lastWidth || xattr.height != lastHeight) { - BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv; + if(xattr.width != lastWidth || xattr.height != lastHeight) { + BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv; return capture_e::reinit; } else { auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0); - 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) { BOOST_LOG(error) << "Could not get image reply"sv; 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) { blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y); @@ -302,12 +301,12 @@ struct shm_attr_t: public x11_attr_t { } std::shared_ptr alloc_img() override { - auto img = std::make_shared(); - img->width = width; - img->height = height; + auto img = std::make_shared(); + img->width = width; + img->height = height; img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->data = new std::uint8_t[height * img->row_pitch]; + img->row_pitch = img->pixel_pitch * width; + img->data = new std::uint8_t[height * img->row_pitch]; return img; } @@ -334,8 +333,8 @@ struct shm_attr_t: public x11_attr_t { } auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); - display = iter.data; - seg = xcb_generate_id(xcb.get()); + display = iter.data; + seg = xcb_generate_id(xcb.get()); shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); if(shm_id.id == -1) { @@ -360,19 +359,19 @@ struct shm_attr_t: public x11_attr_t { } }; -struct mic_attr_t: public mic_t { +struct mic_attr_t : public mic_t { pa_sample_spec ss; util::safe_ptr mic; explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, - std::uint8_t channels) : ss { format, sample_rate, channels }, mic { } { } + std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {} capture_e sample(std::vector &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)) { + 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; @@ -384,7 +383,7 @@ struct mic_attr_t: public mic_t { std::shared_ptr display(platf::dev_type_e hwdevice_type) { 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; } @@ -416,16 +415,16 @@ std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { int status; const char *audio_sink = "@DEFAULT_MONITOR@"; - if (!config::audio.sink.empty()) { + 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, + pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, "sunshine-record", &mic->ss, nullptr, nullptr, &status)); - if (!mic->mic) { + if(!mic->mic) { auto err_str = pa_strerror(status); BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; @@ -448,14 +447,14 @@ std::string from_sockaddr(const sockaddr *const ip_addr) { char data[INET6_ADDRSTRLEN]; auto family = ip_addr->sa_family; - if (family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, - INET6_ADDRSTRLEN); + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); } - if (family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, - INET_ADDRSTRLEN); + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); } return std::string { data }; @@ -467,26 +466,26 @@ std::pair from_sockaddr_ex(const sockaddr *const ip_ auto family = ip_addr->sa_family; std::uint16_t port; if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, - INET6_ADDRSTRLEN); - port = ((sockaddr_in6*) ip_addr)->sin6_port; + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; } if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, - INET_ADDRSTRLEN); - port = ((sockaddr_in*) ip_addr)->sin_port; + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; } - return { port, std::string {data} }; + return { port, std::string { data } }; } std::string get_mac_address(const std::string_view &address) { auto ifaddrs = get_ifaddrs(); - for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { - if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { + for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { + if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); - if (mac_file.good()) { + if(mac_file.good()) { std::string mac_address; std::getline(mac_file, mac_address); return mac_address; @@ -504,4 +503,4 @@ void freeImage(XImage *p) { void freeX(XFixesCursorImage *p) { XFree(p); } -} +} // namespace platf diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index b0a5cec1..ed3b5985 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include #include @@ -10,8 +10,8 @@ #include #include -#include "sunshine/platform/common.h" #include "sunshine/main.h" +#include "sunshine/platform/common.h" #include "sunshine/utility.h" // Support older versions @@ -25,7 +25,7 @@ namespace platf { using namespace std::literals; -using evdev_t = util::safe_ptr; +using evdev_t = util::safe_ptr; using uinput_t = util::safe_ptr; using keyboard_t = util::safe_ptr_v2; @@ -66,7 +66,7 @@ public: std::filesystem::remove(gamepad_path); } - gamepads[nr] = std::make_pair(uinput_t{}, gamepad_state_t {}); + gamepads[nr] = std::make_pair(uinput_t {}, gamepad_state_t {}); } int create_mouse() { @@ -143,7 +143,7 @@ public: }; 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 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)); @@ -157,7 +157,7 @@ void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) } void move_mouse(input_t &input, int deltaX, int deltaY) { - auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); + auto mouse = ((input_raw_t *)input.get())->mouse_input.get(); if(deltaX) { libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX); @@ -176,26 +176,26 @@ void button_mouse(input_t &input, int button, bool release) { if(button == 1) { btn_type = BTN_LEFT; - scan = 90001; + scan = 90001; } else if(button == 2) { btn_type = BTN_MIDDLE; - scan = 90003; + scan = 90003; } else if(button == 3) { btn_type = BTN_RIGHT; - scan = 90002; + scan = 90002; } else if(button == 4) { btn_type = BTN_SIDE; - scan = 90004; + scan = 90004; } else { btn_type = BTN_EXTRA; - scan = 90005; + scan = 90005; } - auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); + auto mouse = ((input_raw_t *)input.get())->mouse_input.get(); libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan); libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); @@ -204,7 +204,7 @@ void button_mouse(input_t &input, int button, bool release) { void scroll(input_t &input, int high_res_distance) { int distance = high_res_distance / 120; - auto mouse = ((input_raw_t*)input.get())->mouse_input.get(); + auto mouse = ((input_raw_t *)input.get())->mouse_input.get(); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); @@ -212,7 +212,7 @@ void scroll(input_t &input, int high_res_distance) { uint16_t keysym(uint16_t modcode) { constexpr auto VK_NUMPAD = 0x60; - constexpr auto VK_F1 = 0x70; + constexpr auto VK_F1 = 0x70; if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) { return XK_KP_0 + (modcode - VK_NUMPAD); @@ -224,108 +224,108 @@ uint16_t keysym(uint16_t modcode) { switch(modcode) { - case 0x08: - return XK_BackSpace; - case 0x09: - return XK_Tab; - case 0x0D: - return XK_Return; - case 0x13: - return XK_Pause; - case 0x14: - return XK_Caps_Lock; - case 0x1B: - return XK_Escape; - case 0x21: - return XK_Page_Up; - case 0x22: - return XK_Page_Down; - case 0x23: - return XK_End; - case 0x24: - return XK_Home; - case 0x25: - return XK_Left; - case 0x26: - return XK_Up; - case 0x27: - return XK_Right; - case 0x28: - return XK_Down; - case 0x29: - return XK_Select; - case 0x2B: - return XK_Execute; - case 0x2C: - return XK_Print; - case 0x2D: - return XK_Insert; - case 0x2E: - return XK_Delete; - case 0x2F: - return XK_Help; - case 0x6A: - return XK_KP_Multiply; - case 0x6B: - return XK_KP_Add; - case 0x6C: - return XK_KP_Separator; - case 0x6D: - return XK_KP_Subtract; - case 0x6E: - return XK_KP_Decimal; - case 0x6F: - return XK_KP_Divide; - case 0x90: - return XK_Num_Lock; - case 0x91: - return XK_Scroll_Lock; - case 0xA0: - return XK_Shift_L; - case 0xA1: - return XK_Shift_R; - case 0xA2: - return XK_Control_L; - case 0xA3: - return XK_Control_R; - case 0xA4: - return XK_Alt_L; - case 0xA5: /* return XK_Alt_R; */ - return XK_Super_L; - case 0x5B: - return XK_Super_L; - case 0x5C: - return XK_Super_R; - case 0xBA: - return XK_semicolon; - case 0xBB: - return XK_equal; - case 0xBC: - return XK_comma; - case 0xBD: - return XK_minus; - case 0xBE: - return XK_period; - case 0xBF: - return XK_slash; - case 0xC0: - return XK_grave; - case 0xDB: - return XK_bracketleft; - case 0xDC: - return XK_backslash; - case 0xDD: - return XK_bracketright; - case 0xDE: - return XK_apostrophe; + case 0x08: + return XK_BackSpace; + case 0x09: + return XK_Tab; + case 0x0D: + return XK_Return; + case 0x13: + return XK_Pause; + case 0x14: + return XK_Caps_Lock; + case 0x1B: + return XK_Escape; + case 0x21: + return XK_Page_Up; + case 0x22: + return XK_Page_Down; + case 0x23: + return XK_End; + case 0x24: + return XK_Home; + case 0x25: + return XK_Left; + case 0x26: + return XK_Up; + case 0x27: + return XK_Right; + case 0x28: + return XK_Down; + case 0x29: + return XK_Select; + case 0x2B: + return XK_Execute; + case 0x2C: + return XK_Print; + case 0x2D: + return XK_Insert; + case 0x2E: + return XK_Delete; + case 0x2F: + return XK_Help; + case 0x6A: + return XK_KP_Multiply; + case 0x6B: + return XK_KP_Add; + case 0x6C: + return XK_KP_Separator; + case 0x6D: + return XK_KP_Subtract; + case 0x6E: + return XK_KP_Decimal; + case 0x6F: + return XK_KP_Divide; + case 0x90: + return XK_Num_Lock; + case 0x91: + return XK_Scroll_Lock; + case 0xA0: + return XK_Shift_L; + case 0xA1: + return XK_Shift_R; + case 0xA2: + return XK_Control_L; + case 0xA3: + return XK_Control_R; + case 0xA4: + return XK_Alt_L; + case 0xA5: /* return XK_Alt_R; */ + return XK_Super_L; + case 0x5B: + return XK_Super_L; + case 0x5C: + return XK_Super_R; + case 0xBA: + return XK_semicolon; + case 0xBB: + return XK_equal; + case 0xBC: + return XK_comma; + case 0xBD: + return XK_minus; + case 0xBE: + return XK_period; + case 0xBF: + return XK_slash; + case 0xC0: + return XK_grave; + case 0xDB: + return XK_bracketleft; + case 0xDC: + return XK_backslash; + case 0xDD: + return XK_bracketright; + case 0xDE: + return XK_apostrophe; } return modcode; } void keyboard(input_t &input, uint16_t modcode, bool release) { - auto &keyboard = ((input_raw_t*)input.get())->keyboard; - KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode)); + auto &keyboard = ((input_raw_t *)input.get())->keyboard; + KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode)); if(!kc) { return; @@ -338,18 +338,18 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { } int alloc_gamepad(input_t &input, int nr) { - return ((input_raw_t*)input.get())->alloc_gamepad(nr); + return ((input_raw_t *)input.get())->alloc_gamepad(nr); } void free_gamepad(input_t &input, int nr) { - ((input_raw_t*)input.get())->clear_gamepad(nr); + ((input_raw_t *)input.get())->clear_gamepad(nr); } void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { - TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t*)input.get())->gamepads[nr]); + TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t *)input.get())->gamepads[nr]); - auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags; + auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags; auto bf_new = gamepad_state.buttonFlags; if(bf) { @@ -366,17 +366,17 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state); } - if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0); - if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0); - if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0); - if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0); - if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0); - if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0); - if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0); - if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0); - if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0); - if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0); - if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0); + if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0); + if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0); + if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0); + if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0); + if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0); + if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0); + if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0); + if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0); + if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0); + if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0); + if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0); } if(gamepad_state_old.lt != gamepad_state.lt) { @@ -408,7 +408,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { } evdev_t mouse() { - evdev_t dev { libevdev_new() }; + evdev_t dev { libevdev_new() }; libevdev_set_uniq(dev.get(), "Sunshine Mouse"); libevdev_set_id_product(dev.get(), 0x4038); @@ -553,7 +553,7 @@ evdev_t x360() { input_t input() { input_t result { new input_raw_t() }; - auto &gp = *(input_raw_t*)result.get(); + auto &gp = *(input_raw_t *)result.get(); gp.keyboard.reset(XOpenDisplay(nullptr)); @@ -568,8 +568,8 @@ input_t input() { // Ensure starting from clean slate gp.clear(); - gp.touch_dev = touchscreen(); - gp.mouse_dev = mouse(); + gp.touch_dev = touchscreen(); + gp.mouse_dev = mouse(); gp.gamepad_dev = x360(); if(gp.create_mouse() || gp.create_touchscreen()) { @@ -581,9 +581,9 @@ input_t input() { } void freeInput(void *p) { - auto *input = (input_raw_t*)p; + auto *input = (input_raw_t *)p; delete input; } std::unique_ptr init() { return std::make_unique(); } -} +} // namespace platf diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index 4f453ac0..d59efb50 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -2,9 +2,9 @@ // Created by loki on 1/12/20. // -#include -#include #include +#include +#include #include @@ -36,7 +36,7 @@ using device_t = util::safe_ptr>; using audio_client_t = util::safe_ptr>; using audio_capture_t = util::safe_ptr>; using wave_format_t = util::safe_ptr>; -using handle_t = util::safe_ptr_v2; +using handle_t = util::safe_ptr_v2; class co_init_t : public deinit_t { public: @@ -53,32 +53,26 @@ struct format_t { std::string_view name; int channels; int channel_mask; -} formats [] { - { - "Stereo"sv, +} formats[] { + { "Stereo"sv, 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT - }, - { - "Mono"sv, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT }, + { "Mono"sv, 1, - SPEAKER_FRONT_CENTER - }, - { - "Surround 5.1"sv, + SPEAKER_FRONT_CENTER }, + { "Surround 5.1"sv, 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT - } + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_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->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) { @@ -97,8 +91,8 @@ void surround51_to_stereo(std::vector &sample_in, const util::buff channels51 // number of channels in surround sound }; - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; sample_out_p += channels51) { std::uint32_t left {}, right {}; @@ -113,7 +107,8 @@ void surround51_to_stereo(std::vector &sample_in, const util::buff right += sample_out_p[front_center] * 90 / 100; right += sample_out_p[low_frequency] * 30 / 100; right += sample_out_p[back_left] * 30 / 100; - right += sample_out_p[back_right] * 70 / 100;; + right += sample_out_p[back_right] * 70 / 100; + ; *sample_in_pos++ = (std::uint16_t)left; *sample_in_pos++ = (std::uint16_t)right; @@ -121,8 +116,8 @@ void surround51_to_stereo(std::vector &sample_in, const util::buff } void mono_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2; + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2; for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; ++sample_out_p) { *sample_in_pos++ = *sample_out_p; @@ -156,23 +151,23 @@ audio_client_t make_audio_client(device_t &device, const format_t &format, int s wave_format->wBitsPerSample = 16; wave_format->nSamplesPerSec = sample_rate; switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: + 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; - 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; - } - - 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 nullptr; + + 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 nullptr; }; set_wave_format(wave_format, format); @@ -195,7 +190,7 @@ audio_client_t make_audio_client(device_t &device, const format_t &format, int s class mic_wasapi_t : public mic_t { public: capture_e sample(std::vector &sample_in) override { - auto sample_size = sample_in.size() /2 * format->channels; + auto sample_size = sample_in.size() / 2 * format->channels; 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 auto capture_result = _fill_buffer(); @@ -243,7 +238,7 @@ public: nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void **) &device_enum); + (void **)&device_enum); if(FAILED(status)) { BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; @@ -292,32 +287,32 @@ public: std::uint32_t frames; status = audio_client->GetBufferSize(&frames); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames *2, frame_size * format->channels *2) }; + sample_buf = util::buffer_t { std::max(frames * 2, frame_size * format->channels * 2) }; sample_buf_pos = std::begin(sample_buf); - status = audio_client->GetService(IID_IAudioCaptureClient, (void**)&audio_capture); - if (FAILED(status)) { + status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } status = audio_client->SetEventHandle(audio_event.get()); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } status = audio_client->Start(); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -331,6 +326,7 @@ public: audio_client->Stop(); } } + private: capture_e _fill_buffer() { HRESULT status; @@ -347,45 +343,45 @@ private: } block_aligned; status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); - switch (status) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - return capture_e::timeout; - default: - BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; + switch(status) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return capture_e::timeout; + default: + BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; } - std::uint32_t packet_size{}; - for ( + std::uint32_t packet_size {}; + for( status = audio_capture->GetNextPacketSize(&packet_size); SUCCEEDED(status) && packet_size > 0; - status = audio_capture->GetNextPacketSize(&packet_size) - ) { + status = audio_capture->GetNextPacketSize(&packet_size)) { DWORD buffer_flags; status = audio_capture->GetBuffer( - (BYTE **) &sample_aligned.samples, + (BYTE **)&sample_aligned.samples, &block_aligned.audio_sample_size, &buffer_flags, nullptr, nullptr); - switch (status) { - case S_OK: - break; - case AUDCLNT_E_DEVICE_INVALIDATED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; + switch(status) { + case S_OK: + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; } sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * format->channels); + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * format->channels); - if (buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { + if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { std::fill_n(sample_buf_pos, n, 0); - } else { + } + else { std::copy_n(sample_aligned.samples, n, sample_buf_pos); } @@ -394,16 +390,17 @@ private: audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); } - if (status == AUDCLNT_E_DEVICE_INVALIDATED) { + if(status == AUDCLNT_E_DEVICE_INVALIDATED) { return capture_e::reinit; } - if (FAILED(status)) { + if(FAILED(status)) { return capture_e::error; } return capture_e::ok; } + public: handle_t audio_event; @@ -419,7 +416,7 @@ public: format_t *format; }; -} +} // namespace platf::audio namespace platf { @@ -444,4 +441,4 @@ std::unique_ptr init() { } return std::make_unique(); } -} +} // namespace platf diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 8bc7ae9e..0c8f5b3d 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -5,14 +5,14 @@ #ifndef SUNSHINE_DISPLAY_H #define SUNSHINE_DISPLAY_H -#include #include #include #include +#include #include -#include "sunshine/utility.h" #include "sunshine/platform/common.h" +#include "sunshine/utility.h" namespace platf::dxgi { extern const char *format_str[]; @@ -43,7 +43,7 @@ using processor_t = util::safe_ptr>; using processor_in_t = util::safe_ptr>; using processor_enum_t = util::safe_ptr>; -} +} // namespace video class hwdevice_t; struct cursor_t { @@ -86,16 +86,14 @@ public: DXGI_FORMAT format; D3D_FEATURE_LEVEL feature_level; - typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS - { + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME - } - D3DKMT_SCHEDULINGPRIORITYCLASS; + } D3DKMT_SCHEDULINGPRIORITYCLASS; typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); }; @@ -123,8 +121,8 @@ public: std::shared_ptr make_hwdevice(int width, int height, pix_fmt_e pix_fmt) override; gpu_cursor_t cursor; - std::vector hwdevices; + std::vector hwdevices; }; -} +} // namespace platf::dxgi #endif \ No newline at end of file diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 46405397..58b93e62 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -23,18 +23,18 @@ capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::ch auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p); switch(status) { - case S_OK: - has_frame = true; - return capture_e::ok; - case DXGI_ERROR_WAIT_TIMEOUT: - return capture_e::timeout; - case WAIT_ABANDONED: - case DXGI_ERROR_ACCESS_LOST: - case DXGI_ERROR_ACCESS_DENIED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); - return capture_e::error; + case S_OK: + has_frame = true; + return capture_e::ok; + case DXGI_ERROR_WAIT_TIMEOUT: + return capture_e::timeout; + case WAIT_ABANDONED: + case DXGI_ERROR_ACCESS_LOST: + case DXGI_ERROR_ACCESS_DENIED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); + return capture_e::error; } } @@ -52,20 +52,20 @@ capture_e duplication_t::release_frame() { } auto status = dup->ReleaseFrame(); - switch (status) { - case S_OK: - has_frame = false; - return capture_e::ok; - case DXGI_ERROR_WAIT_TIMEOUT: - return capture_e::timeout; - case WAIT_ABANDONED: - case DXGI_ERROR_ACCESS_LOST: - case DXGI_ERROR_ACCESS_DENIED: - has_frame = false; - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); - return capture_e::error; + switch(status) { + case S_OK: + has_frame = false; + return capture_e::ok; + case DXGI_ERROR_WAIT_TIMEOUT: + return capture_e::timeout; + case WAIT_ABANDONED: + case DXGI_ERROR_ACCESS_LOST: + case DXGI_ERROR_ACCESS_DENIED: + has_frame = false; + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); + return capture_e::error; } } @@ -74,7 +74,7 @@ duplication_t::~duplication_t() { } int display_base_t::init() { -/* Uncomment when use of IDXGIOutput5 is implemented + /* Uncomment when use of IDXGIOutput5 is implemented std::call_once(windows_cpp_once_flag, []() { DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); @@ -93,7 +93,7 @@ int display_base_t::init() { HRESULT status; - status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory); + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -102,7 +102,7 @@ int display_base_t::init() { std::wstring_convert, wchar_t> converter; auto adapter_name = converter.from_bytes(config::video.adapter_name); - auto output_name = converter.from_bytes(config::video.output_name); + auto output_name = converter.from_bytes(config::video.output_name); adapter_t::pointer adapter_p; for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { @@ -131,8 +131,8 @@ int display_base_t::init() { offset_x = desc.DesktopCoordinates.left; offset_y = desc.DesktopCoordinates.top; - width = desc.DesktopCoordinates.right - offset_x; - height = desc.DesktopCoordinates.bottom - offset_y; + width = desc.DesktopCoordinates.right - offset_x; + height = desc.DesktopCoordinates.bottom - offset_y; } } @@ -157,7 +157,7 @@ int display_base_t::init() { D3D_FEATURE_LEVEL_9_1 }; - status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); + status = adapter->QueryInterface(IID_IDXGIAdapter, (void **)&adapter_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; return -1; @@ -195,7 +195,7 @@ int display_base_t::init() { << "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl << "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl << "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl - << "Capture size : "sv << width << 'x' << height; + << "Capture size : "sv << width << 'x' << height; // Bump up thread priority { @@ -204,13 +204,13 @@ int display_base_t::init() { HANDLE token; LUID val; - if (OpenProcessToken(GetCurrentProcess(), flags, &token) && + if(OpenProcessToken(GetCurrentProcess(), flags, &token) && !!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = val; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = val; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { + if(!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { BOOST_LOG(warning) << "Could not set privilege to increase GPU priority"; } } @@ -218,19 +218,19 @@ int display_base_t::init() { CloseHandle(token); HMODULE gdi32 = GetModuleHandleA("GDI32"); - if (gdi32) { + if(gdi32) { PD3DKMTSetProcessSchedulingPriorityClass fn = (PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass"); - if (fn) { + if(fn) { status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance."; } } } dxgi::dxgi_t dxgi; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi); + status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi); if(FAILED(status)) { BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -242,7 +242,7 @@ int display_base_t::init() { // Try to reduce latency { dxgi::dxgi1_t dxgi {}; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi); + status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -258,7 +258,7 @@ int display_base_t::init() { //TODO: Use IDXGIOutput5 for improved performance { dxgi::output1_t output1 {}; - status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1); + status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; return -1; @@ -266,7 +266,7 @@ int display_base_t::init() { // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { - status = output1->DuplicateOutput((IUnknown*)device.get(), &dup.dup); + status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup); if(SUCCEEDED(status)) { break; } @@ -414,7 +414,7 @@ const char *format_str[] = { "DXGI_FORMAT_V408" }; -} +} // namespace platf::dxgi namespace platf { std::shared_ptr display(dev_type_e hwdevice_type) { @@ -435,4 +435,4 @@ std::shared_ptr display(dev_type_e hwdevice_type) { return nullptr; } -} +} // namespace platf diff --git a/sunshine/platform/windows/display_ram.cpp b/sunshine/platform/windows/display_ram.cpp index ffcbbd25..cb3930d4 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/sunshine/platform/windows/display_ram.cpp @@ -1,12 +1,12 @@ -#include "sunshine/main.h" #include "display.h" +#include "sunshine/main.h" namespace platf { using namespace std::literals; } namespace platf::dxgi { -struct img_t : public ::platf::img_t { +struct img_t : public ::platf::img_t { ~img_t() override { delete[] data; data = nullptr; @@ -22,29 +22,29 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { auto cursor_skip_y = -std::min(0, cursor.y); auto cursor_skip_x = -std::min(0, cursor.x); - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data auto cursor_truncate_y = std::max(0, cursor.y - img.height); auto cursor_truncate_x = std::max(0, cursor.x - img.width); - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; auto cursor_height = height - cursor_skip_y - cursor_truncate_y; if(cursor_height > height || cursor_width > width) { return; } - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; + auto bytes_per_row = delta_width / pixels_per_byte; - auto img_data = (int*)img.data; + auto img_data = (int *)img.data; for(int i = 0; i < delta_height; ++i) { auto and_mask = &cursor_img_data[i * pitch]; auto xor_mask = &cursor_img_data[(i + height) * pitch]; @@ -76,8 +76,8 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { } void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { - auto colors_out = (std::uint8_t*)&cursor_pixel; - auto colors_in = (std::uint8_t*)img_pixel_p; + auto colors_out = (std::uint8_t *)&cursor_pixel; + auto colors_in = (std::uint8_t *)img_pixel_p; //TODO: When use of IDXGIOutput5 is implemented, support different color formats auto alpha = colors_out[3]; @@ -85,15 +85,15 @@ void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { *img_pixel_p = cursor_pixel; } else { - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; } } void apply_color_masked(int *img_pixel_p, int cursor_pixel) { //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = ((std::uint8_t*)&cursor_pixel)[3]; + auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; if(alpha == 0xFF) { *img_pixel_p ^= cursor_pixel; } @@ -111,30 +111,30 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { auto cursor_skip_y = -std::min(0, cursor.y); auto cursor_skip_x = -std::min(0, cursor.x); - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data auto cursor_truncate_y = std::max(0, cursor.y - img.height); auto cursor_truncate_x = std::max(0, cursor.x - img.width); - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; auto cursor_height = height - cursor_skip_y - cursor_truncate_y; if(cursor_height > height || cursor_width > width) { return; } - auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch]; + auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - auto img_data = (int*)img.data; + auto img_data = (int *)img.data; for(int i = 0; i < delta_height; ++i) { auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; - auto cursor_end = &cursor_begin[delta_width]; + auto cursor_end = &cursor_begin[delta_width]; auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { @@ -151,22 +151,22 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { void blend_cursor(const cursor_t &cursor, img_t &img) { switch(cursor.shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - blend_cursor_color(cursor, img, false); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: - blend_cursor_monochrome(cursor, img); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - blend_cursor_color(cursor, img, true); - break; - default: - BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + blend_cursor_color(cursor, img, false); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + blend_cursor_monochrome(cursor, img); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + blend_cursor_color(cursor, img, true); + break; + default: + BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; } } capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_t*)img_base; + auto img = (img_t *)img_base; HRESULT status; @@ -174,9 +174,9 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise resource_t::pointer res_p {}; auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res{res_p}; + resource_t res { res_p }; - if (capture_status != capture_e::ok) { + if(capture_status != capture_e::ok) { return capture_status; } @@ -187,7 +187,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise UINT dummy; status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; @@ -195,18 +195,18 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise } if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.x = frame_info.PointerPosition.Position.x; - cursor.y = frame_info.PointerPosition.Position.y; + cursor.x = frame_info.PointerPosition.Position.x; + cursor.y = frame_info.PointerPosition.Position.y; cursor.visible = frame_info.PointerPosition.Visible; } // If frame has been updated - if (frame_info.LastPresentTime.QuadPart != 0) { + if(frame_info.LastPresentTime.QuadPart != 0) { { texture2d_t src {}; status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } @@ -221,14 +221,14 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise } status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } } - const bool mouse_update = + const bool mouse_update = (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && (cursor_visible && cursor.visible); @@ -238,7 +238,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise return capture_e::timeout; } - std::copy_n((std::uint8_t*)img_info.pData, height * img_info.RowPitch, (std::uint8_t*)img->data); + std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); if(cursor_visible && cursor.visible) { blend_cursor(cursor, *img); @@ -250,11 +250,11 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise std::shared_ptr display_ram_t::alloc_img() { auto img = std::make_shared(); - img->pixel_pitch = 4; - img->row_pitch = img_info.RowPitch; - img->width = width; - img->height = height; - img->data = new std::uint8_t[img->row_pitch * height]; + img->pixel_pitch = 4; + img->row_pitch = img_info.RowPitch; + img->width = width; + img->height = height; + img->data = new std::uint8_t[img->row_pitch * height]; return img; } @@ -269,14 +269,14 @@ int display_ram_t::init() { } D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + t.Usage = D3D11_USAGE_STAGING; + t.Format = format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; auto status = device->CreateTexture2D(&t, nullptr, &texture); @@ -294,4 +294,4 @@ int display_ram_t::init() { return 0; } -} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 00e0c69d..9c44b6fb 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -3,8 +3,8 @@ #include #include -#include "sunshine/main.h" #include "display.h" +#include "sunshine/main.h" #define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders" namespace platf { @@ -30,7 +30,7 @@ using depth_stencil_view_t = util::safe_ptr -buf_t make_buffer(device_t::pointer device, const T& t) { +buf_t make_buffer(device_t::pointer device, const T &t) { static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); D3D11_BUFFER_DESC buffer_desc { @@ -91,18 +91,18 @@ buf_t make_buffer(device_t::pointer device, const T& t) { blend_t make_blend(device_t::pointer device, bool enable) { D3D11_BLEND_DESC bdesc {}; - auto &rt = bdesc.RenderTarget[0]; - rt.BlendEnable = enable; + auto &rt = bdesc.RenderTarget[0]; + rt.BlendEnable = enable; rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; if(enable) { - rt.BlendOp = D3D11_BLEND_OP_ADD; + rt.BlendOp = D3D11_BLEND_OP_ADD; rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; - rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - rt.SrcBlendAlpha = D3D11_BLEND_ZERO; + rt.SrcBlendAlpha = D3D11_BLEND_ZERO; rt.DestBlendAlpha = D3D11_BLEND_ZERO; } @@ -130,79 +130,79 @@ struct img_d3d_t : public platf::img_t { ~img_d3d_t() override = default; }; -util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { - constexpr std::uint32_t black = 0xFF000000; - constexpr std::uint32_t white = 0xFFFFFFFF; +util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { + constexpr std::uint32_t black = 0xFF000000; + constexpr std::uint32_t white = 0xFFFFFFFF; constexpr std::uint32_t transparent = 0; switch(shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - std::for_each((std::uint32_t*)std::begin(img_data), (std::uint32_t*)std::end(img_data), [](auto &pixel) { - if(pixel & 0xFF000000) { - pixel = transparent; - } - }); - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - return std::move(img_data); - default: - break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { + if(pixel & 0xFF000000) { + pixel = transparent; + } + }); + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + return std::move(img_data); + default: + break; } shape_info.Height /= 2; util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; - auto bytes = shape_info.Pitch * shape_info.Height; - auto pixel_begin = (std::uint32_t*)std::begin(cursor_img); - auto pixel_data = pixel_begin; - auto and_mask = std::begin(img_data); - auto xor_mask = std::begin(img_data) + bytes; + auto bytes = shape_info.Pitch * shape_info.Height; + auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); + auto pixel_data = pixel_begin; + auto and_mask = std::begin(img_data); + auto xor_mask = std::begin(img_data) + bytes; - for(auto x = 0; x < bytes; ++x) { + for(auto x = 0; x < bytes; ++x) { for(auto c = 7; c >= 0; --c) { - auto bit = 1 << c; + auto bit = 1 << c; auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); switch(color_type) { - case 0: //black - *pixel_data = black; - break; - case 2: //white - *pixel_data = white; - break; - case 1: //transparent - { - *pixel_data = transparent; + case 0: //black + *pixel_data = black; + break; + case 2: //white + *pixel_data = white; + break; + case 1: //transparent + { + *pixel_data = transparent; - break; + break; + } + case 3: //inverse + { + auto top_p = pixel_data - shape_info.Width; + auto left_p = pixel_data - 1; + auto right_p = pixel_data + 1; + auto bottom_p = pixel_data + shape_info.Width; + + // Get the x coordinate of the pixel + auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; + + if(top_p >= pixel_begin && *top_p == transparent) { + *top_p = black; } - case 3: //inverse - { - auto top_p = pixel_data - shape_info.Width; - auto left_p = pixel_data - 1; - auto right_p = pixel_data + 1; - auto bottom_p = pixel_data + shape_info.Width; - // Get the x coordinate of the pixel - auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; - - if(top_p >= pixel_begin && *top_p == transparent) { - *top_p = black; - } - - if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { - *left_p = black; - } - - if(bottom_p < (std::uint32_t*)std::end(cursor_img)) { - *bottom_p = black; - } - - if(column != shape_info.Width -1) { - *right_p = black; - } - *pixel_data = white; + if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { + *left_p = black; } + + if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { + *bottom_p = black; + } + + if(column != shape_info.Width - 1) { + *right_p = black; + } + *pixel_data = white; + } } ++pixel_data; @@ -225,7 +225,7 @@ blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { #endif std::wstring_convert, wchar_t> converter; - auto wFile = converter.from_bytes(file); + auto wFile = converter.from_bytes(file); auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); if(msg_p) { @@ -251,7 +251,7 @@ blob_t compile_vertex_shader(LPCSTR file) { class hwdevice_t : public platf::hwdevice_t { public: - hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} + hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} hwdevice_t() = delete; void set_cursor_pos(LONG rel_x, LONG rel_y, bool visible) { @@ -271,7 +271,7 @@ public: int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { auto device = (device_t::pointer)data; - cursor_view.Width = width; + cursor_view.Width = width; cursor_view.Height = height; D3D11_SHADER_RESOURCE_VIEW_DESC desc { @@ -290,7 +290,7 @@ public: } int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t&)img_base; + auto &img = (img_d3d_t &)img_base; if(!img.input_res) { auto device = (device_t::pointer)data; @@ -347,17 +347,17 @@ public: } void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - switch (colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &colors[0]; + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &colors[0]; }; if(color_range > 1) { @@ -378,8 +378,7 @@ public: int init( std::shared_ptr 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, - pix_fmt_e pix_fmt - ) { + pix_fmt_e pix_fmt) { HRESULT status; device_p->AddRef(); @@ -387,7 +386,7 @@ public: this->device_ctx_p = device_ctx_p; - cursor_visible = false; + cursor_visible = false; cursor_view.MinDepth = 0.0f; cursor_view.MaxDepth = 1.0f; @@ -427,7 +426,7 @@ public: } blend_disable = make_blend(device_p, false); - blend_enable = make_blend(device_p, true); + blend_enable = make_blend(device_p, true); if(!blend_disable || !blend_enable) { return -1; @@ -460,14 +459,14 @@ public: &input_layout); D3D11_TEXTURE2D_DESC t {}; - t.Width = out_width; - t.Height = out_height; - t.MipLevels = 1; - t.ArraySize = 1; + t.Width = out_width; + t.Height = out_height; + t.MipLevels = 1; + t.ArraySize = 1; t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010; - t.BindFlags = D3D11_BIND_RENDER_TARGET; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010; + t.BindFlags = D3D11_BIND_RENDER_TARGET; status = device_p->CreateTexture2D(&t, nullptr, &img.texture); if(FAILED(status)) { @@ -475,11 +474,11 @@ public: return -1; } - img.display = std::move(display); - img.width = out_width; - img.height = out_height; - img.data = (std::uint8_t*)img.texture.get(); - img.row_pitch = out_width; + img.display = std::move(display); + img.width = out_width; + img.height = out_height; + img.data = (std::uint8_t *)img.texture.get(); + img.row_pitch = out_width; img.pixel_pitch = 1; D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { @@ -494,20 +493,20 @@ public: } nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } D3D11_SAMPLER_DESC sampler_desc {}; - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - sampler_desc.MinLOD = 0; - sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; status = device_p->CreateSamplerState(&sampler_desc, &sampler_linear); if(FAILED(status)) { @@ -527,7 +526,7 @@ public: ~hwdevice_t() override { if(data) { - ((ID3D11Device*)data)->Release(); + ((ID3D11Device *)data)->Release(); } auto it = std::find(std::begin(*hwdevices_p), std::end(*hwdevices_p), this); @@ -535,6 +534,7 @@ public: hwdevices_p->erase(it); } } + private: void _init_view_port(float x, float y, float width, float height) { D3D11_VIEWPORT view { @@ -633,11 +633,11 @@ public: device_ctx_t::pointer device_ctx_p; // The destructor will remove itself from the list of hardware devices, this is done synchronously - std::vector *hwdevices_p; + std::vector *hwdevices_p; }; capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_d3d_t*)img_base; + auto img = (img_d3d_t *)img_base; HRESULT status; @@ -645,7 +645,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec resource_t::pointer res_p {}; auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res{ res_p }; + resource_t res { res_p }; if(capture_status != capture_e::ok) { return capture_status; @@ -653,7 +653,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; - const bool update_flag = mouse_update_flag || frame_update_flag; + const bool update_flag = mouse_update_flag || frame_update_flag; if(!update_flag) { return capture_e::timeout; @@ -666,7 +666,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec UINT dummy; status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); - if (FAILED(status)) { + if(FAILED(status)) { BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; @@ -682,14 +682,14 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec // Create texture for cursor D3D11_TEXTURE2D_DESC t {}; - t.Width = shape_info.Width; - t.Height = cursor_img.size() / data.SysMemPitch; - t.MipLevels = 1; - t.ArraySize = 1; + t.Width = shape_info.Width; + t.Height = cursor_img.size() / data.SysMemPitch; + t.MipLevels = 1; + t.ArraySize = 1; t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; texture2d_t texture; auto status = device->CreateTexture2D(&t, &data, &texture); @@ -734,14 +734,14 @@ std::shared_ptr display_vram_t::alloc_img() { auto img = std::make_shared(); D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; auto status = device->CreateTexture2D(&t, nullptr, &img->texture); if(FAILED(status)) { @@ -749,7 +749,7 @@ std::shared_ptr display_vram_t::alloc_img() { return nullptr; } - img->data = (std::uint8_t*)img->texture.get(); + img->data = (std::uint8_t *)img->texture.get(); img->row_pitch = 0; img->pixel_pitch = 4; img->width = 0; @@ -760,9 +760,9 @@ std::shared_ptr display_vram_t::alloc_img() { } int display_vram_t::dummy_img(platf::img_t *img_base) { - auto img = (img_d3d_t*)img_base; + auto img = (img_d3d_t *)img_base; - img->row_pitch = width * 4; + img->row_pitch = width * 4; auto dummy_data = std::make_unique(width * height); D3D11_SUBRESOURCE_DATA data { dummy_data.get(), @@ -770,14 +770,14 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { }; D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; dxgi::texture2d_t tex; auto status = device->CreateTexture2D(&t, &data, &tex); @@ -787,7 +787,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { } img->texture = std::move(tex); - img->data = (std::uint8_t*)img->texture.get(); + img->data = (std::uint8_t *)img->texture.get(); img->height = height; img->width = width; img->pixel_pitch = 4; @@ -864,4 +864,4 @@ int init() { return 0; } -} \ No newline at end of file +} // namespace platf::dxgi \ No newline at end of file diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index b372afd2..d7667694 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -1,12 +1,12 @@ -#include -#include #include +#include +#include -#include -#include -#include -#include #include +#include +#include +#include +#include #include @@ -100,11 +100,11 @@ std::string from_sockaddr(const sockaddr *const socket_address) { auto family = socket_address->sa_family; if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); } if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*)socket_address)->sin_addr, data, INET_ADDRSTRLEN); + inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); } return std::string { data }; @@ -116,13 +116,13 @@ std::pair from_sockaddr_ex(const sockaddr *const ip_ auto family = ip_addr->sa_family; std::uint16_t port; if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6*)ip_addr)->sin6_port; + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; } if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in*)ip_addr)->sin_port; + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; } return { port, std::string { data } }; @@ -163,7 +163,7 @@ std::string get_mac_address(const std::string_view &address) { input_t input() { input_t result { new vigem_t {} }; - auto vigem = (vigem_t*)result.get(); + auto vigem = (vigem_t *)result.get(); if(vigem->init()) { return nullptr; } @@ -176,7 +176,7 @@ retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { auto hDesk = pairInputDesktop(); - if (_lastKnownInputDesktop != hDesk) { + if(_lastKnownInputDesktop != hDesk) { _lastKnownInputDesktop = hDesk; goto retry; } @@ -186,7 +186,7 @@ retry: void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { INPUT i {}; - i.type = INPUT_MOUSE; + i.type = INPUT_MOUSE; auto &mi = i.mi; mi.dwFlags = @@ -208,13 +208,13 @@ void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) void move_mouse(input_t &input, int deltaX, int deltaY) { INPUT i {}; - i.type = INPUT_MOUSE; + i.type = INPUT_MOUSE; auto &mi = i.mi; mi.dwFlags = MOUSEEVENTF_MOVE; - mi.dx = deltaX; - mi.dy = deltaY; - + mi.dx = deltaX; + mi.dy = deltaY; + send_input(i); } @@ -223,34 +223,34 @@ void button_mouse(input_t &input, int button, bool release) { INPUT i {}; - i.type = INPUT_MOUSE; + i.type = INPUT_MOUSE; auto &mi = i.mi; int mouse_button; if(button == 1) { - mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN; + mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN; mouse_button = VK_LBUTTON; } else if(button == 2) { - mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN; + mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN; mouse_button = VK_MBUTTON; } else if(button == 3) { - mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN; + mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN; mouse_button = VK_RBUTTON; } else if(button == 4) { - mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; + mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.mouseData = XBUTTON1; mouse_button = VK_XBUTTON1; } else { - mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; + mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.mouseData = XBUTTON2; mouse_button = VK_XBUTTON2; } - auto key_state = GetAsyncKeyState(mouse_button); + auto key_state = GetAsyncKeyState(mouse_button); bool key_state_down = (key_state & KEY_STATE_DOWN) != 0; if(key_state_down != release) { BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv; @@ -264,10 +264,10 @@ void button_mouse(input_t &input, int button, bool release) { void scroll(input_t &input, int distance) { INPUT i {}; - i.type = INPUT_MOUSE; + i.type = INPUT_MOUSE; auto &mi = i.mi; - mi.dwFlags = MOUSEEVENTF_WHEEL; + mi.dwFlags = MOUSEEVENTF_WHEEL; mi.mouseData = distance; send_input(i); @@ -279,12 +279,12 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { } INPUT i {}; - i.type = INPUT_KEYBOARD; + i.type = INPUT_KEYBOARD; auto &ki = i.ki; // For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/ if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) { - ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC); + ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC); ki.dwFlags = KEYEVENTF_SCANCODE; } else { @@ -293,23 +293,23 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { // https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags switch(modcode) { - case VK_RMENU: - case VK_RCONTROL: - case VK_INSERT: - case VK_DELETE: - case VK_HOME: - case VK_END: - case VK_PRIOR: - case VK_NEXT: - case VK_UP: - case VK_DOWN: - case VK_LEFT: - case VK_RIGHT: - case VK_DIVIDE: - ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; - break; - default: - break; + case VK_RMENU: + case VK_RCONTROL: + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + case VK_UP: + case VK_DOWN: + case VK_LEFT: + case VK_RIGHT: + case VK_DIVIDE: + ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + break; + default: + break; } if(release) { @@ -324,7 +324,7 @@ int alloc_gamepad(input_t &input, int nr) { return 0; } - return ((vigem_t*)input.get())->alloc_x360(nr); + return ((vigem_t *)input.get())->alloc_x360(nr); } void free_gamepad(input_t &input, int nr) { @@ -332,7 +332,7 @@ void free_gamepad(input_t &input, int nr) { return; } - ((vigem_t*)input.get())->free_target(nr); + ((vigem_t *)input.get())->free_target(nr); } void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { // If there is no gamepad support @@ -340,7 +340,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { return; } - auto vigem = (vigem_t*)input.get(); + auto vigem = (vigem_t *)input.get(); auto &xusb = *(PXUSB_REPORT)&gamepad_state; auto &x360 = vigem->x360s[nr]; @@ -354,19 +354,20 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { } } -int thread_priority() { +int thread_priority() { return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1; } HDESK pairInputDesktop() { auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if (NULL == hDesk) { + if(NULL == hDesk) { auto err = GetLastError(); BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; } else { - BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; - if (!SetThreadDesktop(hDesk) ) { + BOOST_LOG(info) << std::endl + << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; + if(!SetThreadDesktop(hDesk)) { auto err = GetLastError(); BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; } @@ -377,8 +378,8 @@ HDESK pairInputDesktop() { } void freeInput(void *p) { - auto vigem = (vigem_t*)p; + auto vigem = (vigem_t *)p; delete vigem; } -} +} // namespace platf diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 85767eac..d9c6f71e 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -4,14 +4,14 @@ #include "process.h" -#include #include +#include -#include #include +#include -#include "utility.h" #include "main.h" +#include "utility.h" namespace proc { using namespace std::literals; @@ -57,11 +57,11 @@ int proc_t::execute(int app_id) { // Ensure starting from a clean slate terminate(); - _app_id = app_id; + _app_id = app_id; auto &proc = _apps[app_id]; _undo_begin = std::begin(proc.prep_cmds); - _undo_it = _undo_begin; + _undo_it = _undo_begin; if(!proc.output.empty() && proc.output != "null"sv) { _pipe.reset(fopen(proc.output.c_str(), "a")); @@ -147,7 +147,7 @@ void proc_t::terminate() { std::abort(); } - for(;_undo_it != _undo_begin; --_undo_it) { + for(; _undo_it != _undo_begin; --_undo_it) { auto &cmd = (_undo_it - 1)->undo_cmd; if(cmd.empty()) { @@ -192,9 +192,11 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str do { ++begin; switch(*begin) { - case '(': ++stack; + case '(': + ++stack; break; - case ')': --stack; + case ')': + --stack; } } while(begin != end && stack != 0); @@ -205,7 +207,7 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str } std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) { - auto pos = std::begin(val_raw); + auto pos = std::begin(val_raw); auto dollar = std::find(pos, std::end(val_raw), '$'); std::stringstream ss; @@ -214,23 +216,23 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v auto next = dollar + 1; if(next != std::end(val_raw)) { switch(*next) { - case '(': { - ss.write(pos, (dollar - pos)); - auto var_begin = next + 1; - auto var_end = find_match(next, std::end(val_raw)); + case '(': { + ss.write(pos, (dollar - pos)); + auto var_begin = next + 1; + auto var_end = find_match(next, std::end(val_raw)); - ss << env[std::string { var_begin, var_end }].to_string(); + ss << env[std::string { var_begin, var_end }].to_string(); - pos = var_end + 1; - next = var_end; + pos = var_end + 1; + next = var_end; - break; - } - case '$': - ss.write(pos, (next - pos)); - pos = next + 1; - ++next; - break; + break; + } + case '$': + ss.write(pos, (next - pos)); + pos = next + 1; + ++next; + break; } dollar = std::find(next, std::end(val_raw), '$'); @@ -245,7 +247,7 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v return ss.str(); } -std::optional parse(const std::string& file_name) { +std::optional parse(const std::string &file_name) { pt::ptree tree; try { @@ -256,19 +258,19 @@ std::optional parse(const std::string& file_name) { auto this_env = boost::this_process::environment(); - for(auto &[name,val] : env_vars) { + for(auto &[name, val] : env_vars) { this_env[name] = parse_env_val(this_env, val.get_value()); } std::vector apps; - for(auto &[_,app_node] : apps_node) { + for(auto &[_, app_node] : apps_node) { 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("output"s); - auto name = parse_env_val(this_env, app_node.get("name"s)); - auto cmd = app_node.get_optional("cmd"s); + auto output = app_node.get_optional("output"s); + auto name = parse_env_val(this_env, app_node.get("name"s)); + auto cmd = app_node.get_optional("cmd"s); std::vector prep_cmds; if(prep_nodes_opt) { @@ -276,7 +278,7 @@ std::optional parse(const std::string& file_name) { prep_cmds.reserve(prep_nodes.size()); for(auto &[_, prep_node] : prep_nodes) { - auto do_cmd = parse_env_val(this_env, prep_node.get("do"s)); + auto do_cmd = parse_env_val(this_env, prep_node.get("do"s)); auto undo_cmd = prep_node.get_optional("undo"s); if(undo_cmd) { @@ -306,9 +308,9 @@ std::optional parse(const std::string& file_name) { ctx.cmd = parse_env_val(this_env, *cmd); } - ctx.name = std::move(name); + ctx.name = std::move(name); ctx.prep_cmds = std::move(prep_cmds); - ctx.detached = std::move(detached); + ctx.detached = std::move(detached); apps.emplace_back(std::move(ctx)); } @@ -316,7 +318,8 @@ std::optional parse(const std::string& file_name) { return proc::proc_t { std::move(this_env), std::move(apps) }; - } catch (std::exception &e) { + } + catch(std::exception &e) { BOOST_LOG(error) << e.what(); } @@ -330,4 +333,4 @@ void refresh(const std::string &file_name) { proc = std::move(*proc_opt); } } -} +} // namespace proc diff --git a/sunshine/process.h b/sunshine/process.h index e267fa44..39d7cff6 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -9,8 +9,8 @@ #define __kernel_entry #endif -#include #include +#include #include @@ -61,10 +61,9 @@ public: proc_t( boost::process::environment &&env, - std::vector &&apps) : - _app_id(-1), - _env(std::move(env)), - _apps(std::move(apps)) {} + std::vector &&apps) : _app_id(-1), + _env(std::move(env)), + _apps(std::move(apps)) {} int execute(int app_id); @@ -77,7 +76,7 @@ public: const std::vector &get_apps() const; std::vector &get_apps(); - + void terminate(); private: @@ -98,8 +97,8 @@ private: }; void refresh(const std::string &file_name); -std::optional parse(const std::string& file_name); +std::optional parse(const std::string &file_name); extern proc_t proc; -} +} // namespace proc #endif //SUNSHINE_PROCESS_H diff --git a/sunshine/round_robin.h b/sunshine/round_robin.h index 47e125db..e522d16c 100644 --- a/sunshine/round_robin.h +++ b/sunshine/round_robin.h @@ -10,12 +10,12 @@ public: typedef T iterator; typedef typename std::iterator::value_type class_t; - typedef class_t& reference; - typedef class_t* pointer; + typedef class_t &reference; + typedef class_t *pointer; typedef std::ptrdiff_t diff_t; - iterator operator += (diff_t step) { + iterator operator+=(diff_t step) { while(step-- > 0) { ++_this(); } @@ -23,7 +23,7 @@ public: return _this(); } - iterator operator -= (diff_t step) { + iterator operator-=(diff_t step) { while(step-- > 0) { --_this(); } @@ -31,19 +31,19 @@ public: return _this(); } - iterator operator +(diff_t step) { + iterator operator+(diff_t step) { iterator new_ = _this(); return new_ += step; } - iterator operator -(diff_t step) { + iterator operator-(diff_t step) { iterator new_ = _this(); return new_ -= step; } - diff_t operator -(iterator first) { + diff_t operator-(iterator first) { diff_t step = 0; while(first != _this()) { ++step; @@ -53,8 +53,14 @@ public: return step; } - iterator operator++() { _this().inc(); return _this(); } - iterator operator--() { _this().dec(); return _this(); } + iterator operator++() { + _this().inc(); + return _this(); + } + iterator operator--() { + _this().dec(); + return _this(); + } iterator operator++(int) { iterator new_ = _this(); @@ -78,35 +84,35 @@ public: pointer operator->() { return &*_this(); } const pointer operator->() const { return &*_this(); } - bool operator != (const iterator &other) const { + bool operator!=(const iterator &other) const { return !(_this() == other); } - bool operator < (const iterator &other) const { + bool operator<(const iterator &other) const { return !(_this() >= other); } - bool operator >= (const iterator &other) const { + bool operator>=(const iterator &other) const { return _this() == other || _this() > other; } - bool operator <= (const iterator &other) const { + bool operator<=(const iterator &other) const { return _this() == other || _this() < other; } - bool operator == (const iterator &other) const { return _this().eq(other); }; - bool operator > (const iterator &other) const { return _this().gt(other); } -private: + bool operator==(const iterator &other) const { return _this().eq(other); }; + bool operator>(const iterator &other) const { return _this().gt(other); } - iterator &_this() { return *static_cast(this); } - const iterator &_this() const { return *static_cast(this); } +private: + iterator &_this() { return *static_cast(this); } + const iterator &_this() const { return *static_cast(this); } }; template class round_robin_t : public it_wrap_t> { public: using iterator = It; - using pointer = V*; + using pointer = V *; round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {} @@ -119,10 +125,10 @@ public: } void dec() { - if(_pos == _begin) { + if(_pos == _begin) { _pos = _end; } - + --_pos; } @@ -133,6 +139,7 @@ public: pointer get() const { return &*_pos; } + private: It _begin; It _end; @@ -144,6 +151,6 @@ template round_robin_t make_round_robin(It begin, It end) { return round_robin_t(begin, end); } -} +} // namespace util #endif diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 77d1179e..fc6b77ef 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -7,10 +7,10 @@ extern "C" { } #include "config.h" +#include "input.h" #include "main.h" #include "network.h" #include "rtsp.h" -#include "input.h" #include "stream.h" #include "sync.h" @@ -25,7 +25,7 @@ namespace stream { //FIXME: Quick and dirty workaround for bug in MinGW 9.3 causing a linker error when using std::to_string template -std::string to_string(T && t) { +std::string to_string(T &&t) { std::stringstream ss; ss << std::forward(t); return ss.str(); @@ -41,11 +41,11 @@ void free_msg(PRTSP_MESSAGE msg) { class rtsp_server_t; -using msg_t = util::safe_ptr; -using cmd_func_t = std::function; +using msg_t = util::safe_ptr; +using cmd_func_t = std::function; void print_msg(PRTSP_MESSAGE msg); -void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t&& req); +void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req); class rtsp_server_t { public: @@ -82,61 +82,61 @@ public: ENetEvent event; auto res = enet_host_service(_host.get(), &event, std::chrono::floor(timeout).count()); - if (res > 0) { - switch (event.type) { - case ENET_EVENT_TYPE_RECEIVE: { - net::packet_t packet{event.packet}; - net::peer_t peer{event.peer}; + if(res > 0) { + switch(event.type) { + case ENET_EVENT_TYPE_RECEIVE: { + net::packet_t packet { event.packet }; + net::peer_t peer { event.peer }; - msg_t req { new msg_t::element_type }; + msg_t req { new msg_t::element_type }; - //TODO: compare addresses of the peers - if (_queue_packet.second == nullptr) { - parseRtspMessage(req.get(), (char *) packet->data, packet->dataLength); - for (auto option = req->options; option != nullptr; option = option->next) { - if ("Content-length"sv == option->option) { - _queue_packet = std::make_pair(peer, std::move(packet)); - return; - } + //TODO: compare addresses of the peers + if(_queue_packet.second == nullptr) { + parseRtspMessage(req.get(), (char *)packet->data, packet->dataLength); + for(auto option = req->options; option != nullptr; option = option->next) { + if("Content-length"sv == option->option) { + _queue_packet = std::make_pair(peer, std::move(packet)); + return; } } - else { - std::vector full_payload; - - auto old_msg = std::move(_queue_packet); - auto &old_packet = old_msg.second; - - std::string_view new_payload{(char *) packet->data, packet->dataLength}; - std::string_view old_payload{(char *) old_packet->data, old_packet->dataLength}; - full_payload.resize(new_payload.size() + old_payload.size()); - - std::copy(std::begin(old_payload), std::end(old_payload), std::begin(full_payload)); - std::copy(std::begin(new_payload), std::end(new_payload), std::begin(full_payload) + old_payload.size()); - - parseRtspMessage(req.get(), full_payload.data(), full_payload.size()); - } - - print_msg(req.get()); - - msg_t resp; - auto func = _map_cmd_cb.find(req->message.request.command); - if (func != std::end(_map_cmd_cb)) { - func->second(this, peer, std::move(req)); - } - else { - cmd_not_found(host(), peer, std::move(req)); - } - - return; } - case ENET_EVENT_TYPE_CONNECT: - BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv; - break; - case ENET_EVENT_TYPE_DISCONNECT: - BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv; - break; - case ENET_EVENT_TYPE_NONE: - break; + else { + std::vector full_payload; + + auto old_msg = std::move(_queue_packet); + auto &old_packet = old_msg.second; + + std::string_view new_payload { (char *)packet->data, packet->dataLength }; + std::string_view old_payload { (char *)old_packet->data, old_packet->dataLength }; + full_payload.resize(new_payload.size() + old_payload.size()); + + std::copy(std::begin(old_payload), std::end(old_payload), std::begin(full_payload)); + std::copy(std::begin(new_payload), std::end(new_payload), std::begin(full_payload) + old_payload.size()); + + parseRtspMessage(req.get(), full_payload.data(), full_payload.size()); + } + + print_msg(req.get()); + + msg_t resp; + auto func = _map_cmd_cb.find(req->message.request.command); + if(func != std::end(_map_cmd_cb)) { + func->second(this, peer, std::move(req)); + } + else { + cmd_not_found(host(), peer, std::move(req)); + } + + return; + } + case ENET_EVENT_TYPE_CONNECT: + BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv; + break; + case ENET_EVENT_TYPE_DISCONNECT: + BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv; + break; + case ENET_EVENT_TYPE_NONE: + break; } } } @@ -149,7 +149,7 @@ public: auto lg = _session_slots.lock(); for(auto &slot : *_session_slots) { - if (slot && (all || session::state(*slot) == session::state_e::STOPPING)) { + if(slot && (all || session::state(*slot) == session::state_e::STOPPING)) { session::stop(*slot); session::join(*slot); @@ -194,7 +194,6 @@ public: safe::event_t launch_event; private: - // named _queue_packet because I want to make it an actual queue // It's like this for convenience sake std::pair _queue_packet; @@ -225,11 +224,11 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) { auto payload = std::make_pair(resp->payload, resp->payloadLength); auto lg = util::fail_guard([&]() { - resp->payload = payload.first; + resp->payload = payload.first; resp->payloadLength = payload.second; }); - resp->payload = nullptr; + resp->payload = nullptr; resp->payloadLength = 0; int serialized_len; @@ -264,35 +263,35 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) { void respond(net::host_t::pointer host, net::peer_t peer, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) { msg_t resp { new msg_t::element_type }; - createRtspResponse(resp.get(), nullptr, 0, const_cast("RTSP/1.0"), statuscode, const_cast(status_msg), seqn, options, const_cast(payload.data()), (int)payload.size()); + createRtspResponse(resp.get(), nullptr, 0, const_cast("RTSP/1.0"), statuscode, const_cast(status_msg), seqn, options, const_cast(payload.data()), (int)payload.size()); respond(host, peer, resp); } -void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t&& req) { +void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req) { respond(host, peer, nullptr, 404, "NOT FOUND", req->sequenceNumber, {}); } -void cmd_option(rtsp_server_t *server, net::peer_t peer, msg_t&& req) { +void cmd_option(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified - option.option = const_cast("CSeq"); + option.option = const_cast("CSeq"); - auto seqn_str = to_string(req->sequenceNumber); - option.content = const_cast(seqn_str.c_str()); + auto seqn_str = to_string(req->sequenceNumber); + option.content = const_cast(seqn_str.c_str()); respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {}); } -void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) { +void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified - option.option = const_cast("CSeq"); + option.option = const_cast("CSeq"); - auto seqn_str = to_string(req->sequenceNumber); - option.content = const_cast(seqn_str.c_str()); + auto seqn_str = to_string(req->sequenceNumber); + option.content = const_cast(seqn_str.c_str()); std::string_view payload; if(config::video.hevc_mode == 1) { @@ -311,10 +310,10 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { auto &seqn = options[0]; auto &session_option = options[1]; - seqn.option = const_cast("CSeq"); + seqn.option = const_cast("CSeq"); auto seqn_str = to_string(req->sequenceNumber); - seqn.content = const_cast(seqn_str.c_str()); + seqn.content = const_cast(seqn_str.c_str()); std::string_view target { req->message.request.target }; auto begin = std::find(std::begin(target), std::end(target), '=') + 1; @@ -324,8 +323,8 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { if(type == "audio"sv) { seqn.next = &session_option; - session_option.option = const_cast("Session"); - session_option.content = const_cast("DEADBEEFCAFE;timeout = 90"); + session_option.option = const_cast("Session"); + session_option.content = const_cast("DEADBEEFCAFE;timeout = 90"); } else if(type != "video"sv && type != "control"sv) { cmd_not_found(server->host(), peer, std::move(req)); @@ -340,10 +339,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified - option.option = const_cast("CSeq"); + option.option = const_cast("CSeq"); - auto seqn_str = to_string(req->sequenceNumber); - option.content = const_cast(seqn_str.c_str()); + auto seqn_str = to_string(req->sequenceNumber); + option.content = const_cast(seqn_str.c_str()); if(!server->launch_event.peek()) { // /launch has not been used @@ -362,10 +361,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { }; { - auto pos = std::begin(payload); + auto pos = std::begin(payload); auto begin = pos; - while (pos != std::end(payload)) { - if (whitespace(*pos++)) { + while(pos != std::end(payload)) { + if(whitespace(*pos++)) { lines.emplace_back(begin, pos - begin - 1); while(pos != std::end(payload) && whitespace(*pos)) { ++pos; } @@ -386,10 +385,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { auto pos = line.find(':'); auto name = line.substr(2, pos - 2); - auto val = line.substr(pos + 1); + auto val = line.substr(pos + 1); - if(val[val.size() -1] == ' ') { - val = val.substr(0, val.size() -1); + if(val[val.size() - 1] == ' ') { + val = val.substr(0, val.size() - 1); } args.emplace(name, val); } @@ -418,8 +417,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.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)); - - } catch(std::out_of_range &) { + } + catch(std::out_of_range &) { respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); return; @@ -442,7 +441,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { return; } - if(session::start(*session, platf::from_sockaddr((sockaddr*)&peer->address.address))) { + if(session::start(*session, platf::from_sockaddr((sockaddr *)&peer->address.address))) { BOOST_LOG(error) << "Failed to start a streaming session"sv; server->clear(slot); @@ -457,10 +456,10 @@ void cmd_play(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified - option.option = const_cast("CSeq"); + option.option = const_cast("CSeq"); - auto seqn_str = to_string(req->sequenceNumber); - option.content = const_cast(seqn_str.c_str()); + auto seqn_str = to_string(req->sequenceNumber); + option.content = const_cast(seqn_str.c_str()); respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {}); } @@ -518,7 +517,7 @@ void print_msg(PRTSP_MESSAGE msg) { BOOST_LOG(debug) << "status :: "sv << status; } else { - auto& req = msg->message.request; + auto &req = msg->message.request; std::string_view command { req.command }; std::string_view target { req.target }; @@ -534,6 +533,8 @@ void print_msg(PRTSP_MESSAGE msg) { 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 diff --git a/sunshine/rtsp.h b/sunshine/rtsp.h index ac60e8d6..cab06fc4 100644 --- a/sunshine/rtsp.h +++ b/sunshine/rtsp.h @@ -21,6 +21,6 @@ int session_count(); void rtpThread(std::shared_ptr shutdown_event); -} +} // namespace stream #endif //SUNSHINE_RTSP_H diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index c505fc0a..8971c266 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -4,8 +4,8 @@ #include "process.h" -#include #include +#include #include #include @@ -15,14 +15,14 @@ extern "C" { #include } -#include "network.h" #include "config.h" -#include "utility.h" -#include "stream.h" -#include "thread_safe.h" -#include "sync.h" #include "input.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_REQUEST_IDR_FRAME 0 @@ -45,7 +45,7 @@ static const short packetTypes[] = { }; constexpr auto VIDEO_STREAM_PORT = 47998; -constexpr auto CONTROL_PORT = 47999; +constexpr auto CONTROL_PORT = 47999; constexpr auto AUDIO_STREAM_PORT = 48000; namespace asio = boost::asio; @@ -89,7 +89,7 @@ using rh_t = util::safe_ptr; using video_packet_t = util::c_ptr; using audio_packet_t = util::c_ptr; -using message_queue_t = std::shared_ptr>>; +using message_queue_t = std::shared_ptr>>; using message_queue_queue_t = std::shared_ptr>>; static inline void while_starting_do_nothing(std::atomic &state) { @@ -124,7 +124,7 @@ public: // Therefore, iterate is implemented further down the source file void iterate(std::chrono::milliseconds timeout); - void map(uint16_t type, std::function cb) { + void map(uint16_t type, std::function cb) { _map_type_cb.emplace(type, std::move(cb)); } @@ -140,10 +140,10 @@ public: } // Callbacks - std::unordered_map> _map_type_cb; + std::unordered_map> _map_type_cb; // Mapping ip:port to session - util::sync_t>> _map_addr_session; + util::sync_t>> _map_addr_session; ENetAddress _addr; net::host_t _host; @@ -210,7 +210,7 @@ static auto broadcast = safe::make_shared(start_broadcast, end_ safe::signal_t broadcast_shutdown_event; session_t *control_server_t::get_session(const net::peer_t peer) { - TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr*)&peer->address.address)); + TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr *)&peer->address.address)); auto lg = _map_addr_session.lock(); TUPLE_2D(begin, end, _map_addr_session->equal_range(addr_string)); @@ -231,7 +231,7 @@ session_t *control_server_t::get_session(const net::peer_t peer) { TUPLE_2D_REF(session_port, session_p, it->second); session_p->control.peer = peer; - session_port = port; + session_port = port; return session_p; } @@ -246,7 +246,7 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) { if(res > 0) { auto session = get_session(event.peer); if(!session) { - BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr*)&event.peer->address.address) << "]: it's not properly set up"sv; + BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr *)&event.peer->address.address) << "]: it's not properly set up"sv; enet_peer_disconnect_now(event.peer, 0); return; @@ -255,37 +255,37 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) { session->pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout; switch(event.type) { - case ENET_EVENT_TYPE_RECEIVE: - { - net::packet_t packet { event.packet }; + case ENET_EVENT_TYPE_RECEIVE: { + net::packet_t packet { event.packet }; - auto type = (std::uint16_t *)packet->data; - std::string_view payload { (char*)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) }; + auto type = (std::uint16_t *)packet->data; + std::string_view payload { (char *)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) }; - auto cb = _map_type_cb.find(*type); - if(cb == std::end(_map_type_cb)) { - BOOST_LOG(warning) - << "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; - } - - else { - cb->second(session, payload); - } + auto cb = _map_type_cb.find(*type); + if(cb == std::end(_map_type_cb)) { + BOOST_LOG(warning) + << "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; } - break; - case ENET_EVENT_TYPE_CONNECT: - BOOST_LOG(info) << "CLIENT CONNECTED"sv; - break; - case ENET_EVENT_TYPE_DISCONNECT: - BOOST_LOG(info) << "CLIENT DISCONNECTED"sv; - // No more clients to send video data to ^_^ - if(session->state == session::state_e::RUNNING) { - session::stop(*session); - } - break; - case ENET_EVENT_TYPE_NONE: - break; + + else { + cb->second(session, payload); + } + } break; + case ENET_EVENT_TYPE_CONNECT: + BOOST_LOG(info) << "CLIENT CONNECTED"sv; + break; + case ENET_EVENT_TYPE_DISCONNECT: + BOOST_LOG(info) << "CLIENT DISCONNECTED"sv; + // No more clients to send video data to ^_^ + if(session->state == session::state_e::RUNNING) { + session::stop(*session); + } + break; + case ENET_EVENT_TYPE_NONE: + break; } } } @@ -302,11 +302,11 @@ struct fec_t { util::buffer_t shards; char *data(size_t el) { - return &shards[el*blocksize]; + return &shards[el * blocksize]; } std::string_view operator[](size_t el) const { - return { &shards[el*blocksize], blocksize }; + return { &shards[el * blocksize], blocksize }; } size_t size() const { @@ -321,7 +321,7 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen auto data_shards = payload_size / blocksize + (pad ? 1 : 0); auto parity_shards = (data_shards * fecpercentage + 99) / 100; - auto nr_shards = data_shards + parity_shards; + auto nr_shards = data_shards + parity_shards; if(nr_shards > DATA_SHARDS_MAX) { BOOST_LOG(error) @@ -332,14 +332,14 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen } util::buffer_t shards { nr_shards * blocksize }; - util::buffer_t shards_p { nr_shards }; + util::buffer_t shards_p { nr_shards }; // copy payload + padding auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards)); std::fill(next, std::end(shards), 0); // padding with zero for(auto x = 0; x < nr_shards; ++x) { - shards_p[x] = (uint8_t*)&shards[x * blocksize]; + shards_p[x] = (uint8_t *)&shards[x * blocksize]; } // packets = parity_shards + data_shards @@ -355,11 +355,11 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen std::move(shards) }; } -} +} // namespace fec template std::vector insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data, F &&f) { - auto pad = data.size() % slice_size != 0; + auto pad = data.size() % slice_size != 0; auto elements = data.size() / slice_size + (pad ? 1 : 0); std::vector result; @@ -367,20 +367,20 @@ std::vector insert(uint64_t insert_size, uint64_t slice_size, const std auto next = std::begin(data); for(auto x = 0; x < elements - 1; ++x) { - void *p = &result[x*(insert_size + slice_size)]; + void *p = &result[x * (insert_size + slice_size)]; f(p, x, elements); - std::copy(next, next + slice_size, (char*)p + insert_size); + std::copy(next, next + slice_size, (char *)p + insert_size); next += slice_size; } - auto x = elements - 1; - void *p = &result[x*(insert_size + slice_size)]; + auto x = elements - 1; + void *p = &result[x * (insert_size + slice_size)]; f(p, x, elements); - std::copy(next, std::end(data), (char*)p + insert_size); + std::copy(next, std::end(data), (char *)p + insert_size); return result; } @@ -389,7 +389,7 @@ std::vector replace(const std::string_view &original, const std::string std::vector replaced; auto begin = std::begin(original); - auto next = std::search(begin, std::end(original), std::begin(old), std::end(old)); + auto next = std::search(begin, std::end(original), std::begin(old), std::end(old)); std::copy(begin, next, std::back_inserter(replaced)); std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced)); @@ -408,8 +408,8 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se }); server->map(packetTypes[IDX_LOSS_STATS], [&](session_t *session, const std::string_view &payload) { - int32_t *stats = (int32_t*)payload.data(); - auto count = stats[0]; + int32_t *stats = (int32_t *)payload.data(); + auto count = stats[0]; std::chrono::milliseconds t { stats[1] }; auto lastGoodFrame = stats[3]; @@ -424,9 +424,9 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se }); server->map(packetTypes[IDX_INVALIDATE_REF_FRAMES], [&](session_t *session, const std::string_view &payload) { - auto frames = (std::int64_t *)payload.data(); + auto frames = (std::int64_t *)payload.data(); auto firstFrame = frames[0]; - auto lastFrame = frames[1]; + auto lastFrame = frames[1]; BOOST_LOG(debug) << "type [IDX_INVALIDATE_REF_FRAMES]"sv << std::endl @@ -439,7 +439,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) { BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv; - int32_t tagged_cipher_length = util::endian::big(*(int32_t*)payload.data()); + int32_t tagged_cipher_length = util::endian::big(*(int32_t *)payload.data()); std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length }; crypto::cipher_t cipher { session->gcm_key }; @@ -498,7 +498,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se payload[0] = packetTypes[IDX_TERMINATION]; payload[1] = reason; - server->send(std::string_view {(char*)payload.data(), payload.size()}); + server->send(std::string_view { (char *)payload.data(), payload.size() }); auto lg = server->_map_addr_session.lock(); for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) { @@ -519,7 +519,7 @@ void recvThread(broadcast_ctx_t &ctx) { auto &audio_sock = ctx.audio_sock; auto &message_queue_queue = ctx.message_queue_queue; - auto &io = ctx.io; + auto &io = ctx.io; udp::endpoint peer; @@ -532,28 +532,28 @@ void recvThread(broadcast_ctx_t &ctx) { TUPLE_3D_REF(socket_type, addr, message_queue, *message_queue_opt); switch(socket_type) { - case socket_e::video: - if(message_queue) { - peer_to_video_session.emplace(addr, message_queue); - } - else { - peer_to_video_session.erase(addr); - } - break; - case socket_e::audio: - if(message_queue) { - peer_to_audio_session.emplace(addr, message_queue); - } - else { - peer_to_audio_session.erase(addr); - } - break; + case socket_e::video: + if(message_queue) { + peer_to_video_session.emplace(addr, message_queue); + } + else { + peer_to_video_session.erase(addr); + } + break; + case socket_e::audio: + if(message_queue) { + peer_to_audio_session.emplace(addr, message_queue); + } + else { + peer_to_audio_session.erase(addr); + } + break; } } }; auto recv_func_init = [&](udp::socket &sock, int buf_elem, std::map &peer_to_session) { - recv_func[buf_elem] = [&,buf_elem](const boost::system::error_code &ec, size_t bytes) { + recv_func[buf_elem] = [&, buf_elem](const boost::system::error_code &ec, size_t bytes) { auto fg = util::fail_guard([&]() { sock.async_receive_from(asio::buffer(buf[buf_elem]), peer, 0, recv_func[buf_elem]); }); @@ -601,20 +601,20 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid break; } - auto session = (session_t*)packet->channel_data; - auto lowseq = session->video.lowseq; + auto session = (session_t *)packet->channel_data; + auto lowseq = session->video.lowseq; - std::string_view payload{(char *) packet->data, (size_t) packet->size}; + std::string_view payload { (char *)packet->data, (size_t)packet->size }; std::vector payload_new; auto nv_packet_header = "\0017charss"sv; std::copy(std::begin(nv_packet_header), std::end(nv_packet_header), std::back_inserter(payload_new)); std::copy(std::begin(payload), std::end(payload), std::back_inserter(payload_new)); - payload = {(char *) payload_new.data(), payload_new.size()}; + payload = { (char *)payload_new.data(), payload_new.size() }; // make sure moonlight recognizes the nalu code for IDR frames - if (packet->flags & AV_PKT_FLAG_KEY) { + if(packet->flags & AV_PKT_FLAG_KEY) { // TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix std::string_view frame_old = "\000\000\001e"sv; std::string_view frame_new = "\000\000\000\001e"sv; @@ -624,27 +624,25 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid } payload_new = replace(payload, frame_old, frame_new); - payload = {(char *) payload_new.data(), payload_new.size()}; + payload = { (char *)payload_new.data(), payload_new.size() }; } // insert packet headers - auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE; + auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE; auto payload_blocksize = blocksize - sizeof(video_packet_raw_t); auto fecPercentage = config::stream.fec_percentage; payload_new = insert(sizeof(video_packet_raw_t), payload_blocksize, - payload, [&](void *p, int fecIndex, int end) { + payload, [&](void *p, int fecIndex, int end) { video_packet_raw_t *video_packet = (video_packet_raw_t *)p; - video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA; - video_packet->packet.frameIndex = packet->pts; + video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA; + video_packet->packet.frameIndex = packet->pts; video_packet->packet.streamPacketIndex = ((uint32_t)lowseq + fecIndex) << 8; - video_packet->packet.fecInfo = ( - fecIndex << 12 | - end << 22 | - fecPercentage << 4 - ); + video_packet->packet.fecInfo = (fecIndex << 12 | + end << 22 | + fecPercentage << 4); if(fecIndex == 0) { video_packet->packet.flags |= FLAG_SOF; @@ -654,11 +652,11 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid video_packet->packet.flags |= FLAG_EOF; } - video_packet->rtp.header = FLAG_EXTENSION; + video_packet->rtp.header = FLAG_EXTENSION; video_packet->rtp.sequenceNumber = util::endian::big(lowseq + fecIndex); }); - payload = {(char *) payload_new.data(), payload_new.size()}; + payload = { (char *)payload_new.data(), payload_new.size() }; auto shards = fec::encode(payload, blocksize, fecPercentage); if(shards.data_shards == 0) { @@ -666,17 +664,15 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid continue; } - for (auto x = shards.data_shards; x < shards.size(); ++x) { + for(auto x = shards.data_shards; x < shards.size(); ++x) { auto *inspect = (video_packet_raw_t *)shards.data(x); inspect->packet.frameIndex = packet->pts; - inspect->packet.fecInfo = ( - x << 12 | - shards.data_shards << 22 | - fecPercentage << 4 - ); + inspect->packet.fecInfo = (x << 12 | + shards.data_shards << 22 | + fecPercentage << 4); - inspect->rtp.header = FLAG_EXTENSION; + inspect->rtp.header = FLAG_EXTENSION; inspect->rtp.sequenceNumber = util::endian::big(lowseq + x); } @@ -698,27 +694,27 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid } void audioBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, audio::packet_queue_t packets) { - while (auto packet = packets->pop()) { + while(auto packet = packets->pop()) { if(shutdown_event->peek()) { break; } TUPLE_2D_REF(channel_data, packet_data, *packet); - auto session = (session_t*)channel_data; + auto session = (session_t *)channel_data; auto frame = session->audio.frame++; - audio_packet_t audio_packet { (audio_packet_raw_t*)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) }; + audio_packet_t audio_packet { (audio_packet_raw_t *)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) }; - audio_packet->rtp.header = 0; - audio_packet->rtp.packetType = 97; + audio_packet->rtp.header = 0; + audio_packet->rtp.packetType = 97; audio_packet->rtp.sequenceNumber = util::endian::big(frame); - audio_packet->rtp.timestamp = 0; - audio_packet->rtp.ssrc = 0; + audio_packet->rtp.timestamp = 0; + audio_packet->rtp.ssrc = 0; std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload()); - sock.send_to(asio::buffer((char*)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer); + sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer); BOOST_LOG(verbose) << "Audio ["sv << frame << "] :: send..."sv; } @@ -761,12 +757,12 @@ int start_broadcast(broadcast_ctx_t &ctx) { return -1; } - ctx.video_packets = std::make_shared(30); - ctx.audio_packets = std::make_shared(30); + ctx.video_packets = std::make_shared(30); + ctx.audio_packets = std::make_shared(30); ctx.message_queue_queue = std::make_shared(30); - ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets }; - ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets }; + ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets }; + ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets }; ctx.control_thread = std::thread { controlBroadcastThread, &broadcast_shutdown_event, &ctx.control_server }; ctx.recv_thread = std::thread { recvThread, std::ref(ctx) }; @@ -842,7 +838,7 @@ void videoThread(session_t *session, std::string addr_str) { while_starting_do_nothing(session->state); auto addr = asio::ip::make_address(addr_str); - auto ref = broadcast.ref(); + auto ref = broadcast.ref(); auto port = recv_ping(ref, socket_e::video, addr, config::stream.ping_timeout); if(port < 0) { return; @@ -864,7 +860,7 @@ void audioThread(session_t *session, std::string addr_str) { auto addr = asio::ip::make_address(addr_str); - auto ref = broadcast.ref(); + auto ref = broadcast.ref(); auto port = recv_ping(ref, socket_e::audio, addr, config::stream.ping_timeout); if(port < 0) { return; @@ -884,7 +880,7 @@ state_e state(session_t &session) { void stop(session_t &session) { while_starting_do_nothing(session.state); - auto expected = state_e::RUNNING; + auto expected = state_e::RUNNING; auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING); if(already_stopping) { return; @@ -918,8 +914,8 @@ int start(session_t &session, const std::string &addr_string) { session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout; - session.audioThread = std::thread {audioThread, &session, addr_string}; - session.videoThread = std::thread {videoThread, &session, addr_string}; + session.audioThread = std::thread { audioThread, &session, addr_string }; + session.videoThread = std::thread { videoThread, &session, addr_string }; session.state.store(state_e::RUNNING, std::memory_order_relaxed); @@ -929,12 +925,12 @@ int start(session_t &session, const std::string &addr_string) { std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv) { auto session = std::make_shared(); - session->config = config; + session->config = config; session->gcm_key = gcm_key; - session->iv = iv; + session->iv = iv; session->video.idr_events = std::make_shared(); - session->video.lowseq = 0; + session->video.lowseq = 0; session->audio.frame = 1; @@ -943,5 +939,5 @@ std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypt return session; } -} -} +} // namespace session +} // namespace stream diff --git a/sunshine/stream.h b/sunshine/stream.h index cd63fcb2..57f69403 100644 --- a/sunshine/stream.h +++ b/sunshine/stream.h @@ -7,9 +7,9 @@ #include -#include "video.h" #include "audio.h" #include "crypto.h" +#include "video.h" namespace stream { struct session_t; @@ -35,9 +35,9 @@ int start(session_t &session, const std::string &addr_string); void stop(session_t &session); void join(session_t &session); state_e state(session_t &session); -} +} // namespace session extern safe::signal_t broadcast_shutdown_event; -} +} // namespace stream #endif //SUNSHINE_STREAM_H diff --git a/sunshine/sync.h b/sunshine/sync.h index 4689e595..8d67de2a 100644 --- a/sunshine/sync.h +++ b/sunshine/sync.h @@ -5,9 +5,9 @@ #ifndef SUNSHINE_SYNC_H #define SUNSHINE_SYNC_H -#include -#include #include +#include +#include namespace util { @@ -21,8 +21,8 @@ public: return std::lock_guard { _lock }; } - template - sync_t(Args&&... args) : raw {std::forward(args)... } {} + template + sync_t(Args &&...args) : raw { std::forward(args)... } {} sync_t &operator=(sync_t &&other) noexcept { std::lock(_lock, other._lock); @@ -84,11 +84,12 @@ public: } value_t raw; + private: mutex_t _lock; }; -} +} // namespace util #endif //T_MAN_SYNC_H diff --git a/sunshine/task_pool.h b/sunshine/task_pool.h index 83fe8596..fc07e8ab 100644 --- a/sunshine/task_pool.h +++ b/sunshine/task_pool.h @@ -1,18 +1,18 @@ #ifndef KITTY_TASK_POOL_H #define KITTY_TASK_POOL_H -#include -#include -#include #include -#include +#include #include +#include #include -#include #include +#include +#include +#include -#include "utility.h" #include "move_by_copy.h" +#include "utility.h" namespace util { class _ImplBase { @@ -29,8 +29,7 @@ class _Impl : public _ImplBase { Function _func; public: - - _Impl(Function&& f) : _func(std::forward(f)) { } + _Impl(Function &&f) : _func(std::forward(f)) {} void run() override { _func(); @@ -40,7 +39,7 @@ public: class TaskPool { public: typedef std::unique_ptr<_ImplBase> __task; - typedef _ImplBase* task_id_t; + typedef _ImplBase *task_id_t; typedef std::chrono::steady_clock::time_point __time_point; @@ -53,9 +52,10 @@ public: timer_task_t(task_id_t task_id, std::future &future) : task_id { task_id }, future { std::move(future) } {} }; + protected: std::deque<__task> _tasks; - std::vector> _timer_tasks; + std::vector> _timer_tasks; std::mutex _task_mutex; public: @@ -70,8 +70,8 @@ public: } template - auto push(Function && newTask, Args &&... args) { - static_assert(std::is_invocable_v, "arguments don't match the function"); + auto push(Function &&newTask, Args &&...args) { + static_assert(std::is_invocable_v, "arguments don't match the function"); using __return = std::invoke_result_t; using task_t = std::packaged_task<__return()>; @@ -81,12 +81,12 @@ public: }; task_t task(std::move(bind)); - + auto future = task.get_future(); - + std::lock_guard lg(_task_mutex); _tasks.emplace_back(toRunnable(std::move(task))); - + return future; } @@ -107,14 +107,14 @@ public: * @return an id to potentially delay the task */ template - auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&... args) { - static_assert(std::is_invocable_v, "arguments don't match the function"); + auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&...args) { + static_assert(std::is_invocable_v, "arguments don't match the function"); using __return = std::invoke_result_t; using task_t = std::packaged_task<__return()>; __time_point time_point; - if constexpr (std::is_floating_point_v) { + if constexpr(std::is_floating_point_v) { time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration); } else { @@ -127,7 +127,7 @@ public: task_t task(std::move(bind)); - auto future = task.get_future(); + auto future = task.get_future(); auto runnable = toRunnable(std::move(task)); task_id_t task_id = &*runnable; @@ -160,13 +160,14 @@ public: } // smaller time goes to the back - auto prev = it -1; + auto prev = it - 1; while(it > _timer_tasks.cbegin()) { if(std::get<0>(*it) > std::get<0>(*prev)) { std::swap(*it, *prev); } - --prev; --it; + --prev; + --it; } } @@ -201,20 +202,20 @@ public: std::optional<__task> pop() { std::lock_guard lg(_task_mutex); - + if(!_tasks.empty()) { __task task = std::move(_tasks.front()); _tasks.pop_front(); return std::move(task); } - + if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) { __task task = std::move(std::get<1>(_timer_tasks.back())); _timer_tasks.pop_back(); - + return std::move(task); } - + return std::nullopt; } @@ -233,12 +234,12 @@ public: return std::get<0>(_timer_tasks.back()); } -private: +private: template std::unique_ptr<_ImplBase> toRunnable(Function &&f) { - return std::make_unique<_Impl>(std::forward(f)); + return std::make_unique<_Impl>(std::forward(f)); } }; -} +} // namespace util #endif diff --git a/sunshine/thread_pool.h b/sunshine/thread_pool.h index be3f4eb9..8048e6d0 100644 --- a/sunshine/thread_pool.h +++ b/sunshine/thread_pool.h @@ -1,8 +1,8 @@ #ifndef KITTY_THREAD_POOL_H #define KITTY_THREAD_POOL_H -#include #include "task_pool.h" +#include namespace util { /* @@ -12,32 +12,33 @@ namespace util { class ThreadPool : public TaskPool { public: typedef TaskPool::__task __task; - + private: std::vector _thread; std::condition_variable _cv; std::mutex _lock; - + bool _continue; + public: ThreadPool() : _continue { false } {} explicit ThreadPool(int threads) : _thread(threads), _continue { true } { - for (auto &t : _thread) { + for(auto &t : _thread) { t = std::thread(&ThreadPool::_main, this); } } ~ThreadPool() noexcept { - if (!_continue) return; + if(!_continue) return; stop(); join(); } template - auto push(Function && newTask, Args &&... args) { + auto push(Function &&newTask, Args &&...args) { std::lock_guard lg(_lock); auto future = TaskPool::push(std::forward(newTask), std::forward(args)...); @@ -52,7 +53,7 @@ public: } template - auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&... args) { + auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&...args) { std::lock_guard lg(_lock); auto future = TaskPool::pushDelayed(std::forward(newTask), duration, std::forward(args)...); @@ -79,15 +80,14 @@ public: } void join() { - for (auto & t : _thread) { + for(auto &t : _thread) { t.join(); } } public: - void _main() { - while (_continue) { + while(_continue) { if(auto task = this->pop()) { (*task)->run(); } @@ -117,5 +117,5 @@ public: } } }; -} +} // namespace util #endif diff --git a/sunshine/thread_safe.h b/sunshine/thread_safe.h index d166cc0f..21db9974 100644 --- a/sunshine/thread_safe.h +++ b/sunshine/thread_safe.h @@ -5,11 +5,11 @@ #ifndef SUNSHINE_THREAD_SAFE_H #define SUNSHINE_THREAD_SAFE_H -#include -#include -#include #include +#include #include +#include +#include #include "utility.h" @@ -19,14 +19,14 @@ class event_t { using status_t = util::optional_t; public: - template + template void raise(Args &&...args) { std::lock_guard lg { _lock }; if(!_continue) { return; } - if constexpr (std::is_same_v, status_t>) { + if constexpr(std::is_same_v, status_t>) { _status = std::make_optional(std::forward(args)...); } else { @@ -38,42 +38,42 @@ public: // pop and view shoud not be used interchangebly status_t pop() { - std::unique_lock ul{ _lock }; + std::unique_lock ul { _lock }; - if (!_continue) { + if(!_continue) { return util::false_v; } - while (!_status) { + while(!_status) { _cv.wait(ul); - if (!_continue) { + if(!_continue) { return util::false_v; } } auto val = std::move(_status); - _status = util::false_v; + _status = util::false_v; return val; } // pop and view shoud not be used interchangebly template status_t pop(std::chrono::duration delay) { - std::unique_lock ul{ _lock }; + std::unique_lock ul { _lock }; - if (!_continue) { + if(!_continue) { return util::false_v; } - while (!_status) { - if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { + while(!_status) { + if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { return util::false_v; } } auto val = std::move(_status); - _status = util::false_v; + _status = util::false_v; return val; } @@ -81,14 +81,14 @@ public: const status_t &view() { std::unique_lock ul { _lock }; - if (!_continue) { + if(!_continue) { return util::false_v; } - while (!_status) { + while(!_status) { _cv.wait(ul); - if (!_continue) { + if(!_continue) { return util::false_v; } } @@ -103,7 +103,7 @@ public: } void stop() { - std::lock_guard lg{ _lock }; + std::lock_guard lg { _lock }; _continue = false; @@ -111,7 +111,7 @@ public: } void reset() { - std::lock_guard lg{ _lock }; + std::lock_guard lg { _lock }; _continue = true; @@ -121,8 +121,8 @@ public: [[nodiscard]] bool running() const { return _continue; } -private: +private: bool _continue { true }; status_t _status { util::false_v }; @@ -137,8 +137,8 @@ class queue_t { public: queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {} - template - void raise(Args &&... args) { + template + void raise(Args &&...args) { std::lock_guard ul { _lock }; if(!_continue) { @@ -164,12 +164,12 @@ public: status_t pop(std::chrono::duration delay) { std::unique_lock ul { _lock }; - if (!_continue) { + if(!_continue) { return util::false_v; } - while (_queue.empty()) { - if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { + while(_queue.empty()) { + if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { return util::false_v; } } @@ -183,14 +183,14 @@ public: status_t pop() { std::unique_lock ul { _lock }; - if (!_continue) { + if(!_continue) { return util::false_v; } - while (_queue.empty()) { + while(_queue.empty()) { _cv.wait(ul); - if (!_continue) { + if(!_continue) { return util::false_v; } } @@ -219,7 +219,6 @@ public: } private: - bool _continue { true }; std::uint32_t _max_elements; @@ -235,7 +234,7 @@ public: using element_type = T; using construct_f = std::function; - using destruct_f = std::function; + using destruct_f = std::function; struct ptr_t { shared_t *owner; @@ -252,7 +251,7 @@ public: return; } - auto tmp = ptr.owner->ref(); + auto tmp = ptr.owner->ref(); tmp.owner = nullptr; } @@ -282,7 +281,7 @@ public: } } - operator bool () const { + operator bool() const { return owner != nullptr; } @@ -298,22 +297,22 @@ public: } element_type *get() const { - return reinterpret_cast(owner->_object_buf.data()); + return reinterpret_cast(owner->_object_buf.data()); } element_type *operator->() { - return reinterpret_cast(owner->_object_buf.data()); + return reinterpret_cast(owner->_object_buf.data()); } }; template - shared_t(FC && fc, FD &&fd) : _construct { std::forward(fc) }, _destruct { std::forward(fd) } {} + shared_t(FC &&fc, FD &&fd) : _construct { std::forward(fc) }, _destruct { std::forward(fd) } {} [[nodiscard]] ptr_t ref() { std::lock_guard lg { _lock }; if(!_count) { new(_object_buf.data()) element_type; - if(_construct(*reinterpret_cast(_object_buf.data()))) { + if(_construct(*reinterpret_cast(_object_buf.data()))) { return ptr_t { nullptr }; } } @@ -322,6 +321,7 @@ public: return ptr_t { this }; } + private: construct_f _construct; destruct_f _destruct; @@ -340,6 +340,6 @@ auto make_shared(F_Construct &&fc, F_Destruct &&fd) { } using signal_t = event_t; -} +} // namespace safe #endif //SUNSHINE_THREAD_SAFE_H diff --git a/sunshine/utility.h b/sunshine/utility.h index 63be108c..66606a38 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -1,63 +1,67 @@ #ifndef UTILITY_H #define UTILITY_H +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#define KITTY_WHILE_LOOP(x, y, z) { x;while(y) z } -#define KITTY_DECL_CONSTR(x)\ - x(x&&) noexcept = default;\ - x&operator=(x&&) noexcept = default;\ +#define KITTY_WHILE_LOOP(x, y, z) \ + { \ + x; \ + while(y) z \ + } +#define KITTY_DECL_CONSTR(x) \ + x(x &&) noexcept = default; \ + x &operator=(x &&) noexcept = default; \ x(); -#define KITTY_DEFAULT_CONSTR(x)\ - x(x&&) noexcept = default;\ - x&operator=(x&&) noexcept = default;\ - x() = default; +#define KITTY_DEFAULT_CONSTR(x) \ + x(x &&) noexcept = default; \ + x &operator=(x &&) noexcept = default; \ + x() = default; -#define KITTY_DEFAULT_CONSTR_THROW(x)\ - x(x&&) = default;\ - x&operator=(x&&) = default;\ - x() = default; +#define KITTY_DEFAULT_CONSTR_THROW(x) \ + x(x &&) = default; \ + x &operator=(x &&) = default; \ + x() = default; -#define TUPLE_2D(a,b, expr)\ - decltype(expr) a##_##b = expr;\ - auto &a = std::get<0>(a##_##b);\ - auto &b = std::get<1>(a##_##b) +#define TUPLE_2D(a, b, expr) \ + decltype(expr) a##_##b = expr; \ + auto &a = std::get<0>(a##_##b); \ + auto &b = std::get<1>(a##_##b) -#define TUPLE_2D_REF(a,b, expr)\ - auto &a##_##b = expr;\ - auto &a = std::get<0>(a##_##b);\ - auto &b = std::get<1>(a##_##b) +#define TUPLE_2D_REF(a, b, expr) \ + auto &a##_##b = expr; \ + auto &a = std::get<0>(a##_##b); \ + auto &b = std::get<1>(a##_##b) -#define TUPLE_3D(a,b,c, expr)\ - decltype(expr) a##_##b##_##c = expr;\ - auto &a = std::get<0>(a##_##b##_##c);\ - auto &b = std::get<1>(a##_##b##_##c);\ - auto &c = std::get<2>(a##_##b##_##c) +#define TUPLE_3D(a, b, c, expr) \ + decltype(expr) a##_##b##_##c = expr; \ + auto &a = std::get<0>(a##_##b##_##c); \ + auto &b = std::get<1>(a##_##b##_##c); \ + auto &c = std::get<2>(a##_##b##_##c) -#define TUPLE_3D_REF(a,b,c, expr)\ - auto &a##_##b##_##c = expr;\ - auto &a = std::get<0>(a##_##b##_##c);\ - auto &b = std::get<1>(a##_##b##_##c);\ - auto &c = std::get<2>(a##_##b##_##c) +#define TUPLE_3D_REF(a, b, c, expr) \ + auto &a##_##b##_##c = expr; \ + auto &a = std::get<0>(a##_##b##_##c); \ + auto &b = std::get<1>(a##_##b##_##c); \ + auto &c = std::get<2>(a##_##b##_##c) namespace util { -template class X, class...Y> +template class X, class... Y> struct __instantiation_of : public std::false_type {}; template class X, class... Y> struct __instantiation_of> : public std::true_type {}; -template class X, class T, class...Y> +template class X, class T, class... Y> static constexpr auto instantiation_of_v = __instantiation_of::value; template @@ -76,14 +80,16 @@ struct __either { template using either_t = typename __either::type; -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; template class FailGuard { public: FailGuard() = delete; - FailGuard(T && f) noexcept : _func { std::forward(f) } {} + FailGuard(T &&f) noexcept : _func { std::forward(f) } {} FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } { this->failure = other.failure; @@ -103,12 +109,13 @@ public: void disable() { failure = false; } bool failure { true }; + private: T _func; }; template -[[nodiscard]] auto fail_guard(T && f) { +[[nodiscard]] auto fail_guard(T &&f) { return FailGuard { std::forward(f) }; } @@ -118,9 +125,9 @@ void append_struct(std::vector &buf, const T &_struct) { buf.reserve(data_len); - auto *data = (uint8_t *) & _struct; + auto *data = (uint8_t *)&_struct; - for (size_t x = 0; x < data_len; ++x) { + for(size_t x = 0; x < data_len; ++x) { buf.push_back(data[x]); } } @@ -129,24 +136,26 @@ template class Hex { public: typedef T elem_type; + private: const char _bits[16] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char _hex[sizeof(elem_type) * 2]; + public: Hex(const elem_type &elem, bool rev) { if(!rev) { const uint8_t *data = reinterpret_cast(&elem) + sizeof(elem_type) - 1; - for (auto it = begin(); it < cend();) { + for(auto it = begin(); it < cend();) { *it++ = _bits[*data / 16]; *it++ = _bits[*data-- % 16]; } } else { const uint8_t *data = reinterpret_cast(&elem); - for (auto it = begin(); it < cend();) { + for(auto it = begin(); it < cend();) { *it++ = _bits[*data / 16]; *it++ = _bits[*data++ % 16]; } @@ -178,7 +187,7 @@ Hex hex(const T &elem, bool rev = false) { template std::string hex_vec(It begin, It end, bool rev = false) { - auto str_size = 2*std::distance(begin, end); + auto str_size = 2 * std::distance(begin, end); std::string hex; @@ -189,14 +198,14 @@ std::string hex_vec(It begin, It end, bool rev = false) { }; if(rev) { - for (auto it = std::begin(hex); it < std::end(hex);) { + for(auto it = std::begin(hex); it < std::end(hex);) { *it++ = _bits[((uint8_t)*begin) / 16]; *it++ = _bits[((uint8_t)*begin++) % 16]; } } else { --end; - for (auto it = std::begin(hex); it < std::end(hex);) { + for(auto it = std::begin(hex); it < std::end(hex);) { *it++ = _bits[((uint8_t)*end) / 16]; *it++ = _bits[((uint8_t)*end--) % 16]; } @@ -207,7 +216,7 @@ std::string hex_vec(It begin, It end, bool rev = false) { } template -std::string hex_vec(C&& c, bool rev = false) { +std::string hex_vec(C &&c, bool rev = false) { return hex_vec(std::begin(c), std::end(c), rev); } @@ -216,7 +225,7 @@ std::optional from_hex(const std::string_view &hex, bool rev = false) { std::uint8_t buf[sizeof(T)]; static char constexpr shift_bit = 'a' - 'A'; - auto is_convertable = [] (char ch) -> bool { + auto is_convertable = [](char ch) -> bool { if(isdigit(ch)) { return true; } @@ -235,9 +244,9 @@ std::optional from_hex(const std::string_view &hex, bool rev = false) { return std::nullopt; } - const char *data = hex.data() + hex.size() -1; + const char *data = hex.data() + hex.size() - 1; - auto convert = [] (char ch) -> std::uint8_t { + auto convert = [](char ch) -> std::uint8_t { if(ch >= '0' && ch <= '9') { return (std::uint8_t)ch - '0'; } @@ -266,7 +275,7 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) { std::string buf; static char constexpr shift_bit = 'a' - 'A'; - auto is_convertable = [] (char ch) -> bool { + auto is_convertable = [](char ch) -> bool { if(isdigit(ch)) { return true; } @@ -283,9 +292,9 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) { auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2; buf.resize(buf_size); - const char *data = hex.data() + hex.size() -1; + const char *data = hex.data() + hex.size() - 1; - auto convert = [] (char ch) -> std::uint8_t { + auto convert = [](char ch) -> std::uint8_t { if(ch >= '0' && ch <= '9') { return (std::uint8_t)ch - '0'; } @@ -317,18 +326,18 @@ public: std::size_t operator()(const value_type &value) const { const auto *p = reinterpret_cast(&value); - return std::hash{}(std::string_view { p, sizeof(value_type) }); + return std::hash {}(std::string_view { p, sizeof(value_type) }); } }; template -auto enm(const T& val) -> const std::underlying_type_t& { - return *reinterpret_cast*>(&val); +auto enm(const T &val) -> const std::underlying_type_t & { + return *reinterpret_cast *>(&val); } template -auto enm(T& val) -> std::underlying_type_t& { - return *reinterpret_cast*>(&val); +auto enm(T &val) -> std::underlying_type_t & { + return *reinterpret_cast *>(&val); } inline std::int64_t from_chars(const char *begin, const char *end) { @@ -377,11 +386,11 @@ public: }; // Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself -template > +template> class uniq_ptr { public: using element_type = T; - using pointer = element_type*; + using pointer = element_type *; using deleter_type = D; constexpr uniq_ptr() noexcept : _p { nullptr } {} @@ -436,7 +445,7 @@ public: pointer release() { auto tmp = _p; - _p = nullptr; + _p = nullptr; return tmp; } @@ -468,69 +477,70 @@ public: return &_p; } - deleter_type& get_deleter() { + deleter_type &get_deleter() { return _deleter; } - const deleter_type& get_deleter() const { + const deleter_type &get_deleter() const { return _deleter; } explicit operator bool() const { return _p != nullptr; } + protected: pointer _p; deleter_type _deleter; }; template -bool operator==(const uniq_ptr& x, const uniq_ptr& y) { +bool operator==(const uniq_ptr &x, const uniq_ptr &y) { return x.get() == y.get(); } template -bool operator!=(const uniq_ptr& x, const uniq_ptr& y) { +bool operator!=(const uniq_ptr &x, const uniq_ptr &y) { return x.get() != y.get(); } template -bool operator==(const std::unique_ptr& x, const uniq_ptr& y) { +bool operator==(const std::unique_ptr &x, const uniq_ptr &y) { return x.get() == y.get(); } template -bool operator!=(const std::unique_ptr& x, const uniq_ptr& y) { +bool operator!=(const std::unique_ptr &x, const uniq_ptr &y) { return x.get() != y.get(); } template -bool operator==(const uniq_ptr& x, const std::unique_ptr& y) { +bool operator==(const uniq_ptr &x, const std::unique_ptr &y) { return x.get() == y.get(); } template -bool operator!=(const uniq_ptr& x, const std::unique_ptr& y) { +bool operator!=(const uniq_ptr &x, const std::unique_ptr &y) { return x.get() != y.get(); } template -bool operator==(const uniq_ptr& x, std::nullptr_t) { +bool operator==(const uniq_ptr &x, std::nullptr_t) { return !(bool)x; } template -bool operator!=(const uniq_ptr& x, std::nullptr_t) { +bool operator!=(const uniq_ptr &x, std::nullptr_t) { return (bool)x; } template -bool operator==(std::nullptr_t, const uniq_ptr& y) { +bool operator==(std::nullptr_t, const uniq_ptr &y) { return !(bool)y; } template -bool operator!=(std::nullptr_t, const uniq_ptr& y) { +bool operator!=(std::nullptr_t, const uniq_ptr &y) { return (bool)y; } @@ -538,8 +548,8 @@ template class wrap_ptr { public: using element_type = T; - using pointer = element_type*; - using reference = element_type&; + using pointer = element_type *; + using reference = element_type &; wrap_ptr() : _own_ptr { false }, _p { nullptr } {} wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {} @@ -555,7 +565,7 @@ public: _p = other._p; - _own_ptr = other._own_ptr; + _own_ptr = other._own_ptr; other._own_ptr = false; return *this; @@ -565,7 +575,7 @@ public: wrap_ptr &operator=(std::unique_ptr &&uniq_ptr) { static_assert(std::is_base_of_v, "element_type must be base class of V"); _own_ptr = true; - _p = uniq_ptr.release(); + _p = uniq_ptr.release(); return *this; } @@ -575,7 +585,7 @@ public: delete _p; } - _p = p; + _p = p; _own_ptr = false; return *this; @@ -617,13 +627,11 @@ struct __false_v>> { template struct __false_v || - instantiation_of_v || - instantiation_of_v || - instantiation_of_v - ) - >> { + ( + std::is_pointer_v || + instantiation_of_v || + instantiation_of_v || + instantiation_of_v)>> { static constexpr std::nullptr_t value = nullptr; }; @@ -638,18 +646,18 @@ static constexpr auto false_v = __false_v::value; template using optional_t = either_t< (std::is_same_v || - instantiation_of_v || - instantiation_of_v || - instantiation_of_v || - std::is_pointer_v), + instantiation_of_v || + instantiation_of_v || + instantiation_of_v || + std::is_pointer_v), T, std::optional>; template class buffer_t { public: buffer_t() : _els { 0 } {}; - buffer_t(buffer_t&&) noexcept = default; - buffer_t &operator=(buffer_t&& other) noexcept = default; + buffer_t(buffer_t &&) noexcept = default; + buffer_t &operator=(buffer_t &&other) noexcept = default; explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique(elements) } {} explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique(elements) } { @@ -703,7 +711,7 @@ T either(std::optional &&l, T &&r) { return std::forward(r); } -template +template struct Function { typedef ReturnType (*type)(Args...); }; @@ -711,18 +719,18 @@ struct Function { template::type function> struct Destroy { typedef T pointer; - + void operator()(pointer p) { function(p); } }; -template::type function> -using safe_ptr = uniq_ptr>; +template::type function> +using safe_ptr = uniq_ptr>; // You cannot specialize an alias -template::type function> -using safe_ptr_v2 = uniq_ptr>; +template::type function> +using safe_ptr_v2 = uniq_ptr>; template void c_free(T *p) { @@ -737,22 +745,22 @@ template struct endianness { enum : bool { #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ - defined(__BIG_ENDIAN__) || \ - defined(__ARMEB__) || \ - defined(__THUMBEB__) || \ - defined(__AARCH64EB__) || \ - defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) + defined(__BIG_ENDIAN__) || \ + defined(__ARMEB__) || \ + defined(__THUMBEB__) || \ + defined(__AARCH64EB__) || \ + defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) // It's a big-endian target architecture little = false, #elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ - defined(__LITTLE_ENDIAN__) || \ - defined(__ARMEL__) || \ - defined(__THUMBEL__) || \ - defined(__AARCH64EL__) || \ - defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ - defined(_WIN32) + defined(__LITTLE_ENDIAN__) || \ + defined(__ARMEL__) || \ + defined(__THUMBEL__) || \ + defined(__AARCH64EL__) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ + defined(_WIN32) // It's a little-endian target architecture - little = true, + little = true, #else #error "Unknown Endianness" #endif @@ -761,15 +769,14 @@ struct endianness { }; template -struct endian_helper { }; +struct endian_helper {}; template struct endian_helper) ->> { + !(instantiation_of_v)>> { static inline T big(T x) { - if constexpr (endianness::little) { - uint8_t *data = reinterpret_cast(&x); + if constexpr(endianness::little) { + uint8_t *data = reinterpret_cast(&x); std::reverse(data, data + sizeof(x)); } @@ -778,8 +785,8 @@ struct endian_helper::big) { - uint8_t *data = reinterpret_cast(&x); + if constexpr(endianness::big) { + uint8_t *data = reinterpret_cast(&x); std::reverse(data, data + sizeof(x)); } @@ -790,32 +797,31 @@ struct endian_helper struct endian_helper ->> { -static inline T little(T x) { - if(!x) return x; + instantiation_of_v>> { + static inline T little(T x) { + if(!x) return x; - if constexpr (endianness::big) { - auto *data = reinterpret_cast(&*x); + if constexpr(endianness::big) { + auto *data = reinterpret_cast(&*x); - std::reverse(data, data + sizeof(*x)); + std::reverse(data, data + sizeof(*x)); + } + + return x; } - return x; -} + static inline T big(T x) { + if(!x) return x; -static inline T big(T x) { - if(!x) return x; + if constexpr(endianness::big) { + auto *data = reinterpret_cast(&*x); - if constexpr (endianness::big) { - auto *data = reinterpret_cast(&*x); + std::reverse(data, data + sizeof(*x)); + } - std::reverse(data, data + sizeof(*x)); + return x; } - - return x; -} }; template @@ -823,7 +829,7 @@ inline auto little(T x) { return endian_helper::little(x); } template inline auto big(T x) { return endian_helper::big(x); } -} /* endian */ +} // namespace endian -} /* util */ +} // namespace util #endif diff --git a/sunshine/uuid.h b/sunshine/uuid.h index 01fad1d6..6d8abe80 100644 --- a/sunshine/uuid.h +++ b/sunshine/uuid.h @@ -18,12 +18,12 @@ union uuid_t { std::uniform_int_distribution dist(0, std::numeric_limits::max()); uuid_t buf; - for (auto &el : buf.b8) { + for(auto &el : buf.b8) { el = dist(engine); } - buf.b8[7] &= (std::uint8_t) 0b00101111; - buf.b8[9] &= (std::uint8_t) 0b10011111; + buf.b8[7] &= (std::uint8_t)0b00101111; + buf.b8[9] &= (std::uint8_t)0b10011111; return buf; } @@ -31,7 +31,7 @@ union uuid_t { static uuid_t generate() { std::random_device r; - std::default_random_engine engine{r()}; + std::default_random_engine engine { r() }; return generate(engine); } @@ -41,7 +41,7 @@ union uuid_t { result.reserve(sizeof(uuid_t) * 2 + 4); - auto hex = util::hex(*this, true); + auto hex = util::hex(*this, true); auto hex_view = hex.to_string_view(); std::string_view slices[] = { @@ -75,5 +75,5 @@ union uuid_t { return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1])); } }; -} +} // namespace util #endif //T_MAN_UUID_H diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 3127ccbc..1069d0e6 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -3,19 +3,19 @@ // #include -#include #include +#include extern "C" { #include } +#include "config.h" +#include "main.h" #include "platform/common.h" #include "round_robin.h" #include "sync.h" -#include "config.h" #include "video.h" -#include "main.h" #ifdef _WIN32 extern "C" { @@ -52,7 +52,7 @@ enum class profile_hevc_e : int { main_10, rext, }; -} +} // namespace nv using ctx_t = util::safe_ptr; using frame_t = util::safe_ptr; @@ -81,7 +81,7 @@ public: img.row_pitch, 0 }; - int ret = sws_scale(sws.get(), (std::uint8_t*const*)&img.data, linesizes, 0, img.height, frame->data, frame->linesize); + int ret = sws_scale(sws.get(), (std::uint8_t *const *)&img.data, linesizes, 0, img.height, frame->data, frame->linesize); if(ret <= 0) { BOOST_LOG(fatal) << "Couldn't convert image to required format and/or size"sv; @@ -94,9 +94,8 @@ public: virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { sws_setColorspaceDetails(sws.get(), sws_getCoefficients(SWS_CS_DEFAULT), 0, - sws_getCoefficients(colorspace), color_range -1, - 0, 1 << 16, 1 << 16 - ); + sws_getCoefficients(colorspace), color_range - 1, + 0, 1 << 16, 1 << 16); } int init(int in_width, int in_height, int out_width, int out_height, AVFrame *frame, AVPixelFormat format) { @@ -104,8 +103,7 @@ public: in_width, in_height, AV_PIX_FMT_BGR0, out_width, out_height, format, SWS_LANCZOS | SWS_ACCURATE_RND, - nullptr, nullptr, nullptr - )); + nullptr, nullptr, nullptr)); data = frame; return sws ? 0 : -1; @@ -119,8 +117,8 @@ public: struct encoder_t { std::string_view name; enum flag_e { - PASSED, // Is supported - REF_FRAMES_RESTRICT, // Set maximum reference frames + PASSED, // Is supported + REF_FRAMES_RESTRICT, // Set maximum reference frames REF_FRAMES_AUTOSELECT, // Allow encoder to select maximum reference frames (If !REF_FRAMES_RESTRICT --> REF_FRAMES_AUTOSELECT) DYNAMIC_RANGE, MAX_FLAGS @@ -131,9 +129,9 @@ struct encoder_t { option_t(const option_t &) = default; std::string name; - std::variant*, std::string, std::string*> value; + std::variant *, std::string, std::string *> value; - option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {} + option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {} }; struct { @@ -167,24 +165,22 @@ struct encoder_t { bool system_memory; bool hevc_mode; - std::function img_to_frame; + std::function img_to_frame; std::function(platf::hwdevice_t *hwdevice)> make_hwdevice_ctx; }; class session_t { public: session_t() = default; - session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr &&device) : - ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {} + session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr &&device) : ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {} - session_t(session_t &&other) noexcept : - ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {} + session_t(session_t &&other) noexcept : ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {} // Ensure objects are destroyed in the correct order session_t &operator=(session_t &&other) { device = std::move(other.device); - frame = std::move(other.frame); - ctx = std::move(other.ctx); + frame = std::move(other.frame); + ctx = std::move(other.ctx); return *this; } @@ -207,7 +203,7 @@ struct sync_session_ctx_t { struct sync_session_t { sync_session_ctx_t *ctx; - + std::chrono::steady_clock::time_point next_frame; std::chrono::nanoseconds delay; @@ -217,7 +213,7 @@ struct sync_session_t { }; using encode_session_ctx_queue_t = safe::queue_t; -using encode_e = platf::capture_e; +using encode_e = platf::capture_e; struct capture_ctx_t { img_event_t images; @@ -244,7 +240,7 @@ void end_capture_async(capture_thread_async_ctx_t &ctx); // Keep a reference counter to ensure the capture thread only runs when other threads have a reference to the capture thread auto capture_thread_async = safe::make_shared(start_capture_async, end_capture_async); -auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); +auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); #ifdef _WIN32 static encoder_t nvenc { @@ -254,26 +250,21 @@ static encoder_t nvenc { AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, { - { - { "forced-idr"s, 1 }, + { { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "preset"s, &config::video.nv.preset }, - { "rc"s, &config::video.nv.rc } - }, - std::nullopt, std::nullopt, + { "rc"s, &config::video.nv.rc } }, + std::nullopt, + std::nullopt, "hevc_nvenc"s, }, - { - { - { "forced-idr"s, 1 }, + { { { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "preset"s, &config::video.nv.preset }, { "rc"s, &config::video.nv.rc }, - { "coder"s, &config::video.nv.coder } - }, - std::nullopt, std::make_optional({"qp"s, &config::video.qp}), - "h264_nvenc"s - }, + { "coder"s, &config::video.nv.coder } }, + std::nullopt, std::make_optional({ "qp"s, &config::video.qp }), + "h264_nvenc"s }, false, true, @@ -288,26 +279,23 @@ static encoder_t amdvce { AV_PIX_FMT_D3D11, 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 }, { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, - { "rc"s, &config::video.amd.rc } - }, - std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + { "rc"s, &config::video.amd.rc } }, + std::nullopt, + std::make_optional({ "qp"s, &config::video.qp }), "hevc_amf"s, }, - { - { + { { { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, { "rc"s, &config::video.amd.rc }, - {"log_to_dbg"s,"1"s}, + { "log_to_dbg"s, "1"s }, }, - std::nullopt, std::make_optional({"qp"s, &config::video.qp}), - "h264_amf"s - }, + std::nullopt, std::make_optional({ "qp"s, &config::video.qp }), + "h264_amf"s }, false, true, @@ -322,27 +310,20 @@ static encoder_t software { AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_NONE, 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. // It also looks like gop_size isn't passed on to x265, so we have to set // 'keyint=-1' in the parameters ourselves. { { "x265-params"s, "info=0:keyint=-1"s }, { "preset"s, &config::video.sw.preset }, - { "tune"s, &config::video.sw.tune } - }, + { "tune"s, &config::video.sw.tune } }, std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), - "libx265"s - }, - { - { - { "preset"s, &config::video.sw.preset }, - { "tune"s, &config::video.sw.tune } - }, + "libx265"s }, + { { { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune } }, std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), - "libx264"s - }, + "libx264"s }, true, false, @@ -366,7 +347,7 @@ void reset_display(std::shared_ptr &disp, AVHWDeviceType type) if(disp) { break; } - + std::this_thread::sleep_for(200ms); } } @@ -375,8 +356,7 @@ void captureThread( std::shared_ptr> capture_ctx_queue, util::sync_t> &display_wp, safe::signal_t &reinit_event, - const encoder_t &encoder - ) { + const encoder_t &encoder) { std::vector capture_ctxs; auto fg = util::fail_guard([&]() { @@ -410,7 +390,7 @@ void captureThread( } } - if(auto capture_ctx = capture_ctx_queue->pop()) { + if(auto capture_ctx = capture_ctx_queue->pop()) { capture_ctxs.emplace_back(std::move(*capture_ctx)); delay = capture_ctxs.back().delay; @@ -430,64 +410,64 @@ void captureThread( while(img.use_count() > 1) {} auto status = disp->snapshot(img.get(), 1000ms, display_cursor); - switch (status) { - case platf::capture_e::reinit: { - reinit_event.raise(true); + switch(status) { + case platf::capture_e::reinit: { + reinit_event.raise(true); - // Some classes of images contain references to the display --> display won't delete unless img is deleted - for(auto &img : imgs) { - img.reset(); + // Some classes of images contain references to the display --> display won't delete unless img is deleted + for(auto &img : imgs) { + img.reset(); + } + + // Some classes of display cannot have multiple instances at once + disp.reset(); + + // display_wp is modified in this thread only + while(!display_wp->expired()) { + std::this_thread::sleep_for(100ms); + } + + while(capture_ctx_queue->running()) { + reset_display(disp, encoder.dev_type); + + if(disp) { + break; } + std::this_thread::sleep_for(200ms); + } + if(!disp) { + return; + } - // Some classes of display cannot have multiple instances at once - disp.reset(); - - // display_wp is modified in this thread only - while(!display_wp->expired()) { - std::this_thread::sleep_for(100ms); - } - - while(capture_ctx_queue->running()) { - reset_display(disp, encoder.dev_type); - - if(disp) { - break; - } - std::this_thread::sleep_for(200ms); - } - if(!disp) { + display_wp = disp; + // Re-allocate images + for(auto &img : imgs) { + img = disp->alloc_img(); + if(!img) { + BOOST_LOG(error) << "Couldn't initialize an image"sv; return; } - - display_wp = disp; - // Re-allocate images - for(auto &img : imgs) { - img = disp->alloc_img(); - if(!img) { - BOOST_LOG(error) << "Couldn't initialize an image"sv; - return; - } - } - - reinit_event.reset(); - continue; } - case platf::capture_e::error: - return; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return; + + reinit_event.reset(); + continue; + } + case platf::capture_e::error: + return; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return; } KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), { if(!capture_ctx->images->running()) { auto tmp_delay = capture_ctx->delay; - capture_ctx = capture_ctxs.erase(capture_ctx); + capture_ctx = capture_ctxs.erase(capture_ctx); if(tmp_delay == delay) { delay = std::min_element(std::begin(capture_ctxs), std::end(capture_ctxs), [](const auto &l, const auto &r) { @@ -513,21 +493,21 @@ int encode(int64_t frame_nr, ctx_t &ctx, frame_t &frame, packet_queue_t &packets /* send the frame to the encoder */ auto ret = avcodec_send_frame(ctx.get(), frame.get()); - if (ret < 0) { - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + if(ret < 0) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; BOOST_LOG(error) << "Could not send a frame for encoding: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, ret); return -1; } - while (ret >= 0) { + while(ret >= 0) { auto packet = std::make_unique(nullptr); ret = avcodec_receive_packet(ctx.get(), packet.get()); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return 0; } - else if (ret < 0) { + else if(ret < 0) { return ret; } @@ -543,7 +523,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc; if(!video_format[encoder_t::PASSED]) { - BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv; + BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv; return std::nullopt; } @@ -560,10 +540,10 @@ std::optional make_session(const encoder_t &encoder, const config_t & } ctx_t ctx { avcodec_alloc_context3(codec) }; - ctx->width = config.width; - ctx->height = config.height; - ctx->time_base = AVRational{1, config.framerate}; - ctx->framerate = AVRational{config.framerate, 1}; + ctx->width = config.width; + ctx->height = config.height; + ctx->time_base = AVRational { 1, config.framerate }; + ctx->framerate = AVRational { config.framerate, 1 }; if(config.videoFormat == 0) { ctx->profile = encoder.profile.h264_high; @@ -579,7 +559,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->max_b_frames = 0; // Use an infinite GOP length since I-frames are generated on demand - ctx->gop_size = std::numeric_limits::max(); + ctx->gop_size = std::numeric_limits::max(); ctx->keyint_min = ctx->gop_size; if(config.numRefFrames == 0) { @@ -596,34 +576,34 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->color_range = (config.encoderCscMode & 0x1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; int sws_color_space; - switch (config.encoderCscMode >> 1) { - case 0: - default: - // Rec. 601 - BOOST_LOG(info) << "Color coding [Rec. 601]"sv; - ctx->color_primaries = AVCOL_PRI_SMPTE170M; - ctx->color_trc = AVCOL_TRC_SMPTE170M; - ctx->colorspace = AVCOL_SPC_SMPTE170M; - sws_color_space = SWS_CS_SMPTE170M; - break; + switch(config.encoderCscMode >> 1) { + case 0: + default: + // Rec. 601 + BOOST_LOG(info) << "Color coding [Rec. 601]"sv; + ctx->color_primaries = AVCOL_PRI_SMPTE170M; + ctx->color_trc = AVCOL_TRC_SMPTE170M; + ctx->colorspace = AVCOL_SPC_SMPTE170M; + sws_color_space = SWS_CS_SMPTE170M; + break; - case 1: - // Rec. 709 - BOOST_LOG(info) << "Color coding [Rec. 709]"sv; - ctx->color_primaries = AVCOL_PRI_BT709; - ctx->color_trc = AVCOL_TRC_BT709; - ctx->colorspace = AVCOL_SPC_BT709; - sws_color_space = SWS_CS_ITU709; - break; + case 1: + // Rec. 709 + BOOST_LOG(info) << "Color coding [Rec. 709]"sv; + ctx->color_primaries = AVCOL_PRI_BT709; + ctx->color_trc = AVCOL_TRC_BT709; + ctx->colorspace = AVCOL_SPC_BT709; + sws_color_space = SWS_CS_ITU709; + break; - case 2: - // Rec. 2020 - BOOST_LOG(info) << "Color coding [Rec. 2020]"sv; - ctx->color_primaries = AVCOL_PRI_BT2020; - ctx->color_trc = AVCOL_TRC_BT2020_10; - ctx->colorspace = AVCOL_SPC_BT2020_NCL; - sws_color_space = SWS_CS_BT2020; - break; + case 2: + // Rec. 2020 + BOOST_LOG(info) << "Color coding [Rec. 2020]"sv; + ctx->color_primaries = AVCOL_PRI_BT2020; + ctx->color_trc = AVCOL_TRC_BT2020_10; + ctx->colorspace = AVCOL_SPC_BT2020_NCL; + sws_color_space = SWS_CS_BT2020; + break; } BOOST_LOG(info) << "Color range: ["sv << ((config.encoderCscMode & 0x1) ? "JPEG"sv : "MPEG"sv) << ']'; @@ -660,18 +640,19 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->slices = std::max(config.slicesPerFrame, config::video.min_threads); } - ctx->thread_type = FF_THREAD_SLICE; + ctx->thread_type = FF_THREAD_SLICE; ctx->thread_count = ctx->slices; - AVDictionary *options {nullptr}; + AVDictionary *options { nullptr }; auto handle_option = [&options](const encoder_t::option_t &option) { - 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); }, - [&](std::optional *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); }, - [&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } - }, option.value); + 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); }, + [&](std::optional *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); }, + [&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } }, + option.value); }; for(auto &option : video_format.options) { @@ -679,11 +660,11 @@ std::optional make_session(const encoder_t &encoder, const config_t & } if(config.bitrate > 500) { - auto bitrate = config.bitrate * 1000; - ctx->rc_max_rate = bitrate; + auto bitrate = config.bitrate * 1000; + ctx->rc_max_rate = bitrate; ctx->rc_buffer_size = bitrate / config.framerate; - ctx->bit_rate = bitrate; - ctx->rc_min_rate = bitrate; + ctx->bit_rate = bitrate; + ctx->rc_min_rate = bitrate; } else if(video_format.crf && config::video.crf != 0) { handle_option(*video_format.crf); @@ -698,7 +679,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & avcodec_open2(ctx.get(), codec, &options); - frame_t frame {av_frame_alloc() }; + frame_t frame { av_frame_alloc() }; frame->format = ctx->pix_fmt; frame->width = ctx->width; frame->height = ctx->height; @@ -730,13 +711,12 @@ std::optional make_session(const encoder_t &encoder, const config_t & return std::make_optional(session_t { std::move(ctx), std::move(frame), - std::move(device) - }); + std::move(device) }); } void encode_run( int &frame_nr, int &key_frame_nr, // Store progress of the frame number - safe::signal_t* shutdown_event, // Signal for shutdown event of the session + safe::signal_t *shutdown_event, // Signal for shutdown event of the session packet_queue_t packets, idr_event_t idr_events, img_event_t images, @@ -763,13 +743,13 @@ void encode_run( if(idr_events->peek()) { session->frame->pict_type = AV_PICTURE_TYPE_I; session->frame->key_frame = 1; - auto event = idr_events->pop(); + auto event = idr_events->pop(); if(!event) { return; } - auto end = event->second; - frame_nr = end; + auto end = event->second; + frame_nr = end; key_frame_nr = end + config.framerate; } else if(frame_nr == key_frame_nr) { @@ -794,7 +774,7 @@ void encode_run( break; } } - + if(encode(frame_nr++, session->ctx, session->frame, packets, channel_data)) { BOOST_LOG(error) << "Could not encode video packet"sv; return; @@ -808,12 +788,12 @@ void encode_run( std::optional make_synced_session(platf::display_t *disp, const encoder_t &encoder, platf::img_t &img, sync_session_ctx_t &ctx) { sync_session_t encode_session; - encode_session.ctx = &ctx; + encode_session.ctx = &ctx; encode_session.next_frame = std::chrono::steady_clock::now(); encode_session.delay = std::chrono::nanoseconds { 1s } / ctx.config.framerate; - auto pix_fmt = ctx.config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); + auto pix_fmt = ctx.config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); auto hwdevice = disp->make_hwdevice(ctx.config.width, ctx.config.height, pix_fmt); if(!hwdevice) { return std::nullopt; @@ -824,9 +804,9 @@ std::optional make_synced_session(platf::display_t *disp, const return std::nullopt; } - encode_session.img_tmp = &img; + encode_session.img_tmp = &img; encode_session.hwdevice = std::move(hwdevice); - encode_session.session = std::move(*session); + encode_session.session = std::move(*session); return std::move(encode_session); } @@ -873,7 +853,7 @@ encode_e encode_run_sync(std::vector> &synce while(encode_session_ctx_queue.running()) { while(encode_session_ctx_queue.peek()) { auto encode_session_ctx = encode_session_ctx_queue.pop(); - if(!encode_session_ctx) { + if(!encode_session_ctx) { return encode_e::ok; } @@ -892,28 +872,28 @@ encode_e encode_run_sync(std::vector> &synce auto delay = std::max(0ms, std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now())); auto status = disp->snapshot(img.get(), delay, display_cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - break; - case platf::capture_e::ok: - img_tmp = img.get(); - break; + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + break; + case platf::capture_e::ok: + img_tmp = img.get(); + break; } - + auto now = std::chrono::steady_clock::now(); - + next_frame = now + 1s; KITTY_WHILE_LOOP(auto pos = std::begin(synced_sessions), pos != std::end(synced_sessions), { auto ctx = pos->ctx; if(ctx->shutdown_event->peek()) { // Let waiting thread know it can delete shutdown_event ctx->join_event->raise(true); - + pos = synced_sessions.erase(pos); - synced_session_ctxs.erase(std::find_if(std::begin(synced_session_ctxs), std::end(synced_session_ctxs), [&ctx_p=ctx](auto &ctx) { + synced_session_ctxs.erase(std::find_if(std::begin(synced_session_ctxs), std::end(synced_session_ctxs), [&ctx_p = ctx](auto &ctx) { return ctx.get() == ctx_p; })); @@ -929,9 +909,9 @@ encode_e encode_run_sync(std::vector> &synce pos->session.frame->key_frame = 1; auto event = ctx->idr_events->pop(); - auto end = event->second; + auto end = event->second; - ctx->frame_nr = end; + ctx->frame_nr = end; ctx->key_frame_nr = end + ctx->config.framerate; } else if(ctx->frame_nr == ctx->key_frame_nr) { @@ -992,7 +972,7 @@ void captureThreadSync() { std::vector> synced_session_ctxs; auto &ctx = ref->encode_session_ctx_queue; - auto lg = util::fail_guard([&]() { + auto lg = util::fail_guard([&]() { ctx.stop(); for(auto &ctx : synced_session_ctxs) { @@ -1006,7 +986,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( @@ -1017,7 +998,7 @@ void capture_async( void *channel_data) { auto images = std::make_shared(); - auto lg = util::fail_guard([&]() { + auto lg = util::fail_guard([&]() { images->stop(); shutdown_event->raise(true); }); @@ -1029,14 +1010,13 @@ void capture_async( auto delay = std::chrono::floor(1s) / config.framerate; ref->capture_ctx_queue->raise(capture_ctx_t { - images, delay - }); + images, delay }); if(!ref->capture_ctx_queue->running()) { return; } - int frame_nr = 1; + int frame_nr = 1; int key_frame_nr = 1; while(!shutdown_event->peek() && images->running()) { @@ -1056,7 +1036,7 @@ void capture_async( display = ref->display_wp->lock(); } - auto pix_fmt = config.dynamicRange == 0 ? platf::pix_fmt_e::yuv420p : platf::pix_fmt_e::yuv420p10; + auto pix_fmt = config.dynamicRange == 0 ? platf::pix_fmt_e::yuv420p : platf::pix_fmt_e::yuv420p10; auto hwdevice = display->make_hwdevice(config.width, config.height, pix_fmt); if(!hwdevice) { return; @@ -1098,8 +1078,7 @@ void capture( safe::signal_t join_event; auto ref = capture_thread_sync.ref(); ref->encode_session_ctx_queue.raise(sync_session_ctx_t { - shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data - }); + shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data }); // Wait for join signal join_event.view(); @@ -1112,7 +1091,7 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e return false; } - auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); + auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); auto hwdevice = disp->make_hwdevice(config.width, config.height, pix_fmt); if(!hwdevice) { return false; @@ -1152,14 +1131,14 @@ bool validate_encoder(encoder_t &encoder) { }); 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); encoder.h264.capabilities.set(); encoder.hevc.capabilities.set(); // First, test encoder viability config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 }; - config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 }; + config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 }; auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames); auto autoselect_h264 = validate_config(disp, encoder, config_autoselect); @@ -1168,13 +1147,13 @@ bool validate_encoder(encoder_t &encoder) { return false; } - encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264; + encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264; encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264; - encoder.h264[encoder_t::PASSED] = true; + encoder.h264[encoder_t::PASSED] = true; if(test_hevc) { config_max_ref_frames.videoFormat = 1; - config_autoselect.videoFormat = 1; + config_autoselect.videoFormat = 1; auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); auto autoselect_hevc = validate_config(disp, encoder, config_autoselect); @@ -1184,7 +1163,7 @@ bool validate_encoder(encoder_t &encoder) { return false; } - encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; + encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; } encoder.hevc[encoder_t::PASSED] = test_hevc; @@ -1222,10 +1201,9 @@ int init() { KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { if( - (!config::video.encoder.empty() && pos->name != config::video.encoder) || - !validate_encoder(*pos) || - (config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE]) - ) { + (!config::video.encoder.empty() && pos->name != config::video.encoder) || + !validate_encoder(*pos) || + (config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE])) { pos = encoders.erase(pos); continue; @@ -1235,7 +1213,7 @@ int init() { }) if(encoders.empty()) { - if(config::video.encoder.empty()) { + if(config::video.encoder.empty()) { BOOST_LOG(fatal) << "Couldn't find any encoder"sv; } else { @@ -1258,7 +1236,7 @@ int init() { BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; } else { - BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']'; + BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']'; } if(config::video.hevc_mode == 0) { @@ -1274,12 +1252,12 @@ util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevic int err; if(hwdevice) { ctx.reset(av_hwdevice_ctx_alloc(type)); - ((AVHWDeviceContext*)ctx.get())->hwctx = hwdevice; + ((AVHWDeviceContext *)ctx.get())->hwctx = hwdevice; err = av_hwdevice_ctx_init(ctx.get()); } else { - AVBufferRef *ref {}; + AVBufferRef *ref {}; err = av_hwdevice_ctx_create(&ref, type, nullptr, nullptr, 0); ctx.reset(ref); } @@ -1292,13 +1270,13 @@ util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevic } int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { - buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get())}; + buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get()) }; - auto frame_ctx = (AVHWFramesContext*)frame_ref->data; - frame_ctx->format = ctx->pix_fmt; - frame_ctx->sw_format = format; - frame_ctx->height = ctx->height; - frame_ctx->width = ctx->width; + auto frame_ctx = (AVHWFramesContext *)frame_ref->data; + frame_ctx->format = ctx->pix_fmt; + frame_ctx->sw_format = format; + frame_ctx->height = ctx->height; + frame_ctx->width = ctx->width; frame_ctx->initial_pool_size = 0; if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) { @@ -1319,22 +1297,22 @@ void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {} namespace platf::dxgi { void lock(void *hwdevice); void unlock(void *hwdevice); -} -void do_nothing(void*) {} +} // namespace platf::dxgi +void do_nothing(void *) {} namespace video { void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) { if(img.data == frame->data[0]) { return; } - + // Need to have something refcounted if(!frame->buf[0]) { frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); } - auto desc = (AVD3D11FrameDescriptor*)frame->buf[0]->data; - desc->texture = (ID3D11Texture2D*)img.data; - desc->index = 0; + auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; + desc->texture = (ID3D11Texture2D *)img.data; + desc->index = 0; frame->data[0] = img.data; frame->data[1] = 0; @@ -1342,27 +1320,27 @@ void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) { frame->linesize[0] = img.row_pitch; frame->height = img.height; - frame->width = img.width; + frame->width = img.width; } util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; - auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; - - std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); + auto ctx = (AVD3D11VADeviceContext *)((AVHWDeviceContext *)ctx_buf->data)->hwctx; - auto device = (ID3D11Device*)hwdevice_ctx->data; + std::fill_n((std::uint8_t *)ctx, sizeof(AVD3D11VADeviceContext), 0); + + auto device = (ID3D11Device *)hwdevice_ctx->data; device->AddRef(); ctx->device = device; - ctx->lock_ctx = (void*)1; - ctx->lock = do_nothing; - ctx->unlock = do_nothing; + ctx->lock_ctx = (void *)1; + ctx->lock = do_nothing; + ctx->unlock = do_nothing; auto err = av_hwdevice_ctx_init(ctx_buf.get()); if(err) { - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); return err; @@ -1402,12 +1380,12 @@ void end_capture_sync(capture_thread_sync_ctx_t &ctx) {} platf::dev_type_e map_dev_type(AVHWDeviceType type) { switch(type) { - case AV_HWDEVICE_TYPE_D3D11VA: - return platf::dev_type_e::dxgi; - case AV_PICTURE_TYPE_NONE: - return platf::dev_type_e::none; - default: - return platf::dev_type_e::unknown; + case AV_HWDEVICE_TYPE_D3D11VA: + return platf::dev_type_e::dxgi; + case AV_PICTURE_TYPE_NONE: + return platf::dev_type_e::none; + default: + return platf::dev_type_e::unknown; } return platf::dev_type_e::unknown; @@ -1415,16 +1393,16 @@ platf::dev_type_e map_dev_type(AVHWDeviceType type) { platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt) { switch(fmt) { - case AV_PIX_FMT_YUV420P10: - return platf::pix_fmt_e::yuv420p10; - case AV_PIX_FMT_YUV420P: - return platf::pix_fmt_e::yuv420p; - case AV_PIX_FMT_NV12: - return platf::pix_fmt_e::nv12; - case AV_PIX_FMT_P010: - return platf::pix_fmt_e::p010; - default: - return platf::pix_fmt_e::unknown; + case AV_PIX_FMT_YUV420P10: + return platf::pix_fmt_e::yuv420p10; + case AV_PIX_FMT_YUV420P: + return platf::pix_fmt_e::yuv420p; + case AV_PIX_FMT_NV12: + return platf::pix_fmt_e::nv12; + case AV_PIX_FMT_P010: + return platf::pix_fmt_e::p010; + default: + return platf::pix_fmt_e::unknown; } return platf::pix_fmt_e::unknown; diff --git a/sunshine/video.h b/sunshine/video.h index b628f303..6fff9875 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -5,9 +5,9 @@ #ifndef SUNSHINE_VIDEO_H #define SUNSHINE_VIDEO_H -#include "thread_safe.h" #include "input.h" #include "platform/common.h" +#include "thread_safe.h" extern "C" { #include @@ -18,15 +18,15 @@ namespace video { struct packet_raw_t : public AVPacket { void init_packet() { - pts = AV_NOPTS_VALUE; - dts = AV_NOPTS_VALUE; - pos = -1; - duration = 0; - flags = 0; - stream_index = 0; - buf = nullptr; - side_data = nullptr; - side_data_elems = 0; + pts = AV_NOPTS_VALUE; + dts = AV_NOPTS_VALUE; + pos = -1; + duration = 0; + flags = 0; + stream_index = 0; + buf = nullptr; + side_data = nullptr; + side_data_elems = 0; } template @@ -70,6 +70,6 @@ void capture( void *channel_data); int init(); -} +} // namespace video #endif //SUNSHINE_VIDEO_H diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8d59af33..e3e79969 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -9,7 +9,7 @@ set_target_properties(dxgi-info PROPERTIES CXX_STANDARD 17) target_link_libraries(dxgi-info ${CMAKE_THREAD_LIBS_INIT} dxgi - ${PLATFORM_LIBRARIES}) + ${PLATFORM_LIBRARIES}) target_compile_options(dxgi-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) add_executable(audio-info audio.cpp) @@ -17,5 +17,5 @@ set_target_properties(audio-info PROPERTIES CXX_STANDARD 17) target_link_libraries(audio-info ${CMAKE_THREAD_LIBS_INIT} ksuser - ${PLATFORM_LIBRARIES}) + ${PLATFORM_LIBRARIES}) target_compile_options(audio-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) diff --git a/tools/audio.cpp b/tools/audio.cpp index 18556c50..b99edf5f 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -2,9 +2,9 @@ // Created by loki on 1/24/20. // -#include -#include #include +#include +#include #include @@ -16,8 +16,8 @@ #include "sunshine/utility.h" -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_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); using namespace std::literals; @@ -27,7 +27,7 @@ const IID IID_IAudioClient = __uuidof(IAudioClient); 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 { template @@ -73,32 +73,26 @@ struct format_t { std::string_view name; int channels; int channel_mask; -} formats [] { - { - "Mono"sv, +} formats[] { + { "Mono"sv, 1, - SPEAKER_FRONT_CENTER - }, - { - "Stereo"sv, + SPEAKER_FRONT_CENTER }, + { "Stereo"sv, 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT - }, - { - "Surround 5.1"sv, + 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 - } + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_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->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) { @@ -112,7 +106,7 @@ audio_client_t make_audio_client(device_t &device, const format_t &format) { IID_IAudioClient, CLSCTX_ALL, nullptr, - (void **) &audio_client); + (void **)&audio_client); if(FAILED(status)) { std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; @@ -123,7 +117,7 @@ audio_client_t make_audio_client(device_t &device, const format_t &format) { wave_format_t wave_format; status = audio_client->GetMixFormat(&wave_format); - if (FAILED(status)) { + if(FAILED(status)) { std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return nullptr; @@ -132,23 +126,23 @@ audio_client_t make_audio_client(device_t &device, const format_t &format) { wave_format->wBitsPerSample = 16; wave_format->nSamplesPerSec = SAMPLE_RATE; switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: + 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; - 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; + + 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); @@ -191,18 +185,18 @@ void print_device(device_t &device) { std::wstring device_state_string = L"Unknown"s; switch(device_state) { - case DEVICE_STATE_ACTIVE: - device_state_string = L"Active"s; - break; - case DEVICE_STATE_DISABLED: - device_state_string = L"Disabled"s; - break; - case DEVICE_STATE_UNPLUGGED: - device_state_string = L"Unplugged"s; - break; - case DEVICE_STATE_NOTPRESENT: - device_state_string = L"Not present"s; - break; + case DEVICE_STATE_ACTIVE: + device_state_string = L"Active"s; + break; + case DEVICE_STATE_DISABLED: + device_state_string = L"Disabled"s; + break; + case DEVICE_STATE_UNPLUGGED: + device_state_string = L"Unplugged"s; + break; + case DEVICE_STATE_NOTPRESENT: + device_state_string = L"Not present"s; + break; } std::wcout @@ -211,7 +205,8 @@ void print_device(device_t &device) { << 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"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) { return; @@ -224,7 +219,7 @@ void print_device(device_t &device) { std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl; } } -} +} // namespace audio void print_help() { std::cout @@ -281,9 +276,9 @@ int main(int argc, char *argv[]) { nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void **) &device_enum); + (void **)&device_enum); - if (FAILED(status)) { + if(FAILED(status)) { std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return -1; @@ -292,7 +287,7 @@ int main(int argc, char *argv[]) { audio::collection_t collection; status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection); - if (FAILED(status)) { + if(FAILED(status)) { std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return -1; diff --git a/tools/dxgi.cpp b/tools/dxgi.cpp index 3b3f9c88..9c5f7307 100644 --- a/tools/dxgi.cpp +++ b/tools/dxgi.cpp @@ -2,8 +2,8 @@ // Created by loki on 1/23/20. // -#include #include +#include #include @@ -16,17 +16,17 @@ void Release(T *dxgi) { dxgi->Release(); } -using factory1_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -using output_t = util::safe_ptr>; +using factory1_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; -} +} // namespace dxgi int main(int argc, char *argv[]) { HRESULT status; dxgi::factory1_t::pointer factory_p {}; - status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory_p); dxgi::factory1_t factory { factory_p }; if(FAILED(status)) { std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; @@ -49,12 +49,13 @@ int main(int argc, char *argv[]) { << "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 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; dxgi::output_t::pointer output_p {}; for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { - dxgi::output_t output {output_p }; + dxgi::output_t output { output_p }; DXGI_OUTPUT_DESC desc; output->GetDesc(&desc); @@ -66,7 +67,8 @@ int main(int argc, char *argv[]) { << L" Output Name : "sv << desc.DeviceName << std::endl; std::cout << " 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; } } From fe08c241ece3855bd22f7a0d11bc827ace7d7e4c Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Mon, 17 May 2021 21:30:03 +0200 Subject: [PATCH 065/158] Fixes --- CMakeLists.txt | 2 ++ sunshine/confighttp.cpp | 13 +++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dc30920..61426cb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ if(WIN32) set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") endif() + add_compile_definitions(SUNSHINE_PLATFORM="windows") add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") @@ -82,6 +83,7 @@ if(WIN32) set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") else() + add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11 REQUIRED) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 670b9204..2b859c29 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -181,7 +181,7 @@ void deleteApp(resp_https_t response, req_https_t request) pt::ptree fileTree; try { - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + pt::read_json(config::stream.file_apps, fileTree); auto &apps_node = fileTree.get_child("apps"s); int index = stoi(request->path_match[1]); BOOST_LOG(info) << index; @@ -193,7 +193,7 @@ void deleteApp(resp_https_t response, req_https_t request) } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for (const auto &kv : apps_node) @@ -232,11 +232,7 @@ void getConfig(resp_https_t response, req_https_t request) try { outputTree.put("status","true"); - #ifdef _WIN32 - outputTree.put("platform","windows"); - #elif - outputTree.put("platform","linux"); - #endif + outputTree.put("platform",SUNSHINE_PLATFORM); const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; std::ifstream in { config_file }; @@ -263,7 +259,8 @@ void getConfig(resp_https_t response, req_https_t request) } } -void saveConfig(resp_https_t response, req_https_t request){ +void saveConfig(resp_https_t response, req_https_t request) +{ std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); From 57f444357d64e4536a89c0a33a5951aa02d5965a Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Mon, 17 May 2021 21:56:55 +0200 Subject: [PATCH 066/158] Web UI IP Based Authentication --- assets/web/config.html | 6 +++--- sunshine/confighttp.cpp | 22 ++++++++++++++++++++++ sunshine/httpcommon.cpp | 2 ++ sunshine/httpcommon.h | 2 ++ sunshine/nvhttp.cpp | 4 +--- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index eb15bfe2..fa9612d9 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -35,9 +35,9 @@
The origin of the remote endpoint address that is not denied for HTTP method /pin
diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 2b859c29..e8fe4184 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -49,6 +49,18 @@ enum class op_e REMOVE }; +bool authenticate(resp_https_t response, req_https_t request) +{ +auto address = request->remote_endpoint_address(); + auto ip_type = net::from_address(address); + if(ip_type > http::origin_pin_allowed) { + BOOST_LOG(info) << '[' << address << "] -- denied"sv; + response->write(SimpleWeb::StatusCode::client_error_forbidden); + return false; + } + return true; +} + template void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { @@ -66,6 +78,7 @@ void not_found(std::shared_ptr::Response> resp void getIndexPage(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); @@ -74,6 +87,7 @@ void getIndexPage(resp_https_t response, req_https_t request) template void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); @@ -82,6 +96,7 @@ void getPinPage(std::shared_ptr::Response> res template void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); @@ -90,6 +105,7 @@ void getAppsPage(std::shared_ptr::Response> re template void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); @@ -98,6 +114,7 @@ void getClientsPage(std::shared_ptr::Response> template void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); @@ -105,12 +122,14 @@ void getConfigPage(std::shared_ptr::Response> void getApps(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } void saveApp(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -171,6 +190,7 @@ void saveApp(resp_https_t response, req_https_t request) void deleteApp(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -222,6 +242,7 @@ void deleteApp(resp_https_t response, req_https_t request) void getConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -261,6 +282,7 @@ void getConfig(resp_https_t response, req_https_t request) void saveConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 6c05dba9..53da503a 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -32,10 +32,12 @@ namespace http std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); std::string unique_id; + net::net_e origin_pin_allowed; void init(std::shared_ptr shutdown_event) { bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if (clean_slate) { unique_id = util::uuid_t::generate().string(); diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 5320025a..40cc0a66 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -1,7 +1,9 @@ +#include "network.h" namespace http{ void init(std::shared_ptr shutdown_event); int create_creds(const std::string &pkey, const std::string &cert); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); extern std::string unique_id; + extern net::net_e origin_pin_allowed; } \ No newline at end of file diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 8799dc1f..9261ff17 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -76,7 +76,6 @@ struct pair_session_t { // uniqueID, session std::unordered_map map_id_sess; std::unordered_map map_id_client; -net::net_e origin_pin_allowed; using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; @@ -405,7 +404,7 @@ void pin(std::shared_ptr::Response> response, auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); - if(ip_type > origin_pin_allowed) { + if(ip_type > http::origin_pin_allowed) { BOOST_LOG(info) << '[' << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); @@ -677,7 +676,6 @@ void appasset(resp_https_t response, req_https_t request) { void start(std::shared_ptr shutdown_event) { bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if(!clean_slate) { load_state(); From 2b04e1428cd1e6ea9913ca576f18cb313ae23450 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 18 May 2021 13:36:12 +0200 Subject: [PATCH 067/158] Select audio output on Linux --- CMakeLists.txt | 1 + assets/sunshine.conf | 4 +- sunshine/audio.cpp | 82 ++++++- sunshine/config.h | 4 +- sunshine/platform/common.h | 29 +++ sunshine/platform/linux/audio.cpp | 361 ++++++++++++++++++++++++++++ sunshine/platform/linux/display.cpp | 52 ---- sunshine/platform/linux/input.cpp | 2 - sunshine/thread_safe.h | 92 ++++++- sunshine/utility.h | 5 +- 10 files changed, 564 insertions(+), 68 deletions(-) create mode 100644 sunshine/platform/linux/audio.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dcbe718..ffa12301 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ else() find_package(FFmpeg REQUIRED) set(PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp + sunshine/platform/linux/audio.cpp sunshine/platform/linux/input.cpp) set(PLATFORM_LIBRARIES diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 12377eda..c4ea57a4 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -94,8 +94,8 @@ # output_name = \\.\DISPLAY1 # !! Linux only !! -# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually. -# output_name = 1 +# Set the display number to stream. I have no idea how they are numbered. They start from 0, usually. +# output_name = 0 ############################################### # FFmpeg software encoding parameters diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index c9ddab76..5960562d 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -25,7 +25,9 @@ struct opus_stream_config_t { constexpr std::uint8_t map_stereo[] { 0, 1 }; constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 }; constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 }; -constexpr auto SAMPLE_RATE = 48000; + +constexpr auto SAMPLE_RATE = 48000; + static opus_stream_config_t stereo = { SAMPLE_RATE, 2, @@ -79,7 +81,78 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi } } +const platf::card_t *active_card(const std::vector &cards) { + for(auto &card : cards) { + if(card.active_profile) { + return &card; + } + } + + return nullptr; +} + 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 = &stereo; + + auto control = platf::audio_control(); + if(!control) { + BOOST_LOG(error) << "Couldn't create audio control"sv; + + return; + } + + auto cards = control->card_info(); + if(cards.empty()) { + return; + } + + auto card = active_card(cards); + if(!card || (card->stereo.empty() && card->surround51.empty() && card->surround71.empty())) { + return; + } + + const platf::profile_t *profile; + switch(config.channels) { + case 2: + if(!card->stereo.empty()) { + profile = &card->stereo[0]; + } + else if(!card->surround51.empty()) { + profile = &card->surround51[0]; + } + else if(!card->surround71.empty()) { + profile = &card->surround71[0]; + } + break; + case 6: + if(!card->surround51.empty()) { + profile = &card->surround51[0]; + } + else if(!card->surround71.empty()) { + profile = &card->surround71[0]; + } + else { + profile = &card->stereo[0]; + } + break; + case 8: + if(!card->surround71.empty()) { + profile = &card->surround71[0]; + } + else if(!card->surround51.empty()) { + profile = &card->surround51[0]; + } + else { + profile = &card->stereo[0]; + } + break; + } + + if(control->set_output(*card, *profile)) { + return; + } + auto samples = std::make_shared(30); std::thread thread { encodeThread, packets, samples, config, channel_data }; @@ -90,13 +163,10 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co shutdown_event->view(); }); - //FIXME: Pick correct opus_stream_config_t based on config.channels - auto stream = &stereo; - auto frame_size = config.packetDuration * stream->sampleRate / 1000; int samples_per_frame = frame_size * stream->channelCount; - auto mic = platf::microphone(stream->sampleRate, frame_size); + auto mic = control->create_mic(stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't create audio input"sv; @@ -115,7 +185,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co continue; case platf::capture_e::reinit: mic.reset(); - mic = platf::microphone(stream->sampleRate, frame_size); + mic = control->create_mic(stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't re-initialize audio input"sv; diff --git a/sunshine/config.h b/sunshine/config.h index be75f3d2..3bced756 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -77,8 +77,8 @@ namespace flag { enum flag_e : std::size_t { PIN_STDIN = 0, // Read PIN from stdin instead of http FRESH_STATE, // Do not load or save state - FLAG_SIZE, - CONST_PIN = 4 // Use "universal" pin + CONST_PIN = 4, // Use "universal" pin + FLAG_SIZE }; } diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index c80fc750..c044aa53 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -6,6 +6,7 @@ #define SUNSHINE_COMMON_H #include "sunshine/utility.h" +#include #include #include @@ -101,6 +102,23 @@ public: virtual ~img_t() = default; }; +struct profile_t { + std::string name; + std::string description; + + bool available; +}; + +struct card_t { + std::string name; + std::string description; + + std::optional active_profile; + std::vector stereo; + std::vector surround51; + std::vector surround71; +}; + struct hwdevice_t { void *data {}; platf::img_t *img {}; @@ -148,6 +166,16 @@ public: virtual ~mic_t() = default; }; +class audio_control_t { +public: + virtual int set_output(const card_t &card, const profile_t &) = 0; + + virtual std::unique_ptr create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) = 0; + + virtual std::vector card_info() = 0; + + virtual ~audio_control_t() = default; +}; void freeInput(void *); @@ -158,6 +186,7 @@ std::string get_mac_address(const std::string_view &address); std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); +std::unique_ptr audio_control(); std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t frame_size); std::shared_ptr display(dev_type_e hwdevice_type); diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp new file mode 100644 index 00000000..86cac399 --- /dev/null +++ b/sunshine/platform/linux/audio.cpp @@ -0,0 +1,361 @@ +// +// Created by loki on 5/16/21. +// +#include +#include + +#include +#include +#include + +#include "sunshine/platform/common.h" + +#include "sunshine/config.h" +#include "sunshine/main.h" +#include "sunshine/thread_safe.h" + +namespace platf { +using namespace std::literals; + +struct mic_attr_t : public mic_t { + pa_sample_spec ss; + util::safe_ptr 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 &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 microphone(std::uint32_t sample_rate, std::uint32_t) { + auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); + + int status; + + const char *audio_sink = "@DEFAULT_MONITOR@"; + if(!config::audio.sink.empty()) { + audio_sink = config::audio.sink.c_str(); + } + + mic->mic.reset( + 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; +} + +namespace pa { +template +void pa_free(T *p) { + pa_xfree(p); +} +using ctx_t = util::safe_ptr; +using loop_t = util::safe_ptr; +using op_t = util::safe_ptr; +using string_t = util::safe_ptr>; + +template +using cb_t = std::function; + +template +void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) { + auto &f = *(cb_t *)userdata; + + // For some reason, pulseaudio calls this callback after disconnecting + if(i && eol) { + return; + } + + f(ctx, i, eol); +} + +void ctx_state_cb(ctx_t::pointer ctx, void *userdata) { + auto &f = *(std::function *)userdata; + + f(ctx); +} + +void success_cb(ctx_t::pointer ctx, int status, void *userdata) { + assert(userdata != nullptr); + + auto alarm = (safe::alarm_raw_t *)userdata; + alarm->ring(status ? 0 : 1); +} + +profile_t make(pa_card_profile_info2 *profile) { + return profile_t { + profile->name, + profile->description, + profile->available == 1 + }; +} + +card_t make(const pa_card_info *card) { + boost::regex stereo_expr { + ".*output(?!.*surround).*stereo.*" + }; + + boost::regex surround51_expr { + ".*output.*surround(.*(^\\d|51).*|$)" + }; + + boost::regex surround71_expr { + ".*output.*surround.*7.?1.*" + }; + + std::vector stereo; + std::vector surround51; + std::vector surround71; + + std::for_each_n(card->profiles2, card->n_profiles, [&](pa_card_profile_info2 *profile) { + if(boost::regex_match(profile->name, stereo_expr)) { + stereo.emplace_back(make(profile)); + } + if(boost::regex_match(profile->name, surround51_expr)) { + surround51.emplace_back(make(profile)); + } + if(boost::regex_match(profile->name, surround71_expr)) { + surround71.emplace_back(make(profile)); + } + }); + + std::optional active_profile; + if(card->active_profile2->name != "off"sv) { + active_profile = make(card->active_profile2); + } + + return card_t { + card->name, + card->driver, + std::move(active_profile), + std::move(stereo), + std::move(surround51), + std::move(surround71), + }; +} + +const card_t *active_card(const std::vector &cards) { + for(auto &card : cards) { + if(card.active_profile) { + return &card; + } + } + + return nullptr; +} +class server_t : public audio_control_t { + enum ctx_event_e : int { + ready, + terminated, + failed + }; + +public: + loop_t loop; + ctx_t ctx; + + std::unique_ptr> events; + std::unique_ptr> events_cb; + + std::thread worker; + int init() { + events = std::make_unique>(); + loop.reset(pa_mainloop_new()); + ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine")); + + events_cb = std::make_unique>([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; + } + + std::vector card_info() override { + auto alarm = safe::make_alarm(); + + std::vector cards; + cb_t f = [&cards, alarm](ctx_t::pointer ctx, const pa_card_info *card_info, int eol) { + if(!card_info) { + if(!eol) { + BOOST_LOG(error) << "Couldn't get pulseaudio card info: "sv << pa_strerror(pa_context_errno(ctx)); + } + + alarm->ring(true); + return; + } + + cards.emplace_back(make(card_info)); + }; + + op_t op { pa_context_get_card_info_list(ctx.get(), cb, &f) }; + + if(!op) { + BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get())); + + return {}; + } + + alarm->wait(); + + return cards; + } + + std::unique_ptr create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) override { + return microphone(sample_rate, frame_size); + } + + int set_output(const card_t &card, const profile_t &profile) override { + auto alarm = safe::make_alarm(); + + op_t op { pa_context_set_card_profile_by_name( + ctx.get(), card.name.c_str(), profile.name.c_str(), success_cb, alarm.get()) + }; + + if(!op) { + BOOST_LOG(error) << "Couldn't create set profile operation: "sv << pa_strerror(pa_context_errno(ctx.get())); + return -1; + } + + alarm->wait(); + if(*alarm->status()) { + BOOST_LOG(error) << "Couldn't set profile ["sv << profile.name << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); + + return -1; + } + + return 0; + } + + ~server_t() override { + 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() { + auto audio = std::make_unique(); + + if(audio->init()) { + return nullptr; + } + + return audio; +} + +std::unique_ptr init() { + pa::server_t server; + if(server.init()) { + return std::make_unique(); + } + + auto cards = server.card_info(); + + for(auto &card : cards) { + BOOST_LOG(info) << "---- CARD ----"sv; + BOOST_LOG(info) << "Name: ["sv << card.name << ']'; + BOOST_LOG(info) << "Description: ["sv << card.description << ']'; + + if(card.active_profile) { + BOOST_LOG(info) << "Active profile:"sv; + BOOST_LOG(info) << " Name: [" << card.active_profile->name << ']'; + BOOST_LOG(info) << " Description: ["sv << card.active_profile->description << ']'; + BOOST_LOG(info) << " Available: ["sv << card.active_profile->available << ']'; + BOOST_LOG(info); + } + + + BOOST_LOG(info) << " -- stereo --"sv; + for(auto &profile : card.stereo) { + BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; + } + + BOOST_LOG(info) << " -- surround 5.1 --"sv; + for(auto &profile : card.surround51) { + BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; + } + + BOOST_LOG(info) << " -- surround 7.1 --"sv; + for(auto &profile : card.surround71) { + BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; + } + } + + return std::make_unique(); +} +} // namespace platf \ No newline at end of file diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 0c45c749..b405ba12 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -4,7 +4,6 @@ #include "sunshine/platform/common.h" -#include #include #include @@ -20,9 +19,6 @@ #include #include -#include -#include - #include "sunshine/config.h" #include "sunshine/main.h" #include "sunshine/task_pool.h" @@ -359,28 +355,6 @@ struct shm_attr_t : public x11_attr_t { } }; -struct mic_attr_t : public mic_t { - pa_sample_spec ss; - util::safe_ptr 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 &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(platf::dev_type_e hwdevice_type) { if(hwdevice_type != platf::dev_type_e::none) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; @@ -409,32 +383,6 @@ std::shared_ptr display(platf::dev_type_e hwdevice_type) { return x11_disp; } -std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { - auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); - - int status; - - const char *audio_sink = "@DEFAULT_MONITOR@"; - if(!config::audio.sink.empty()) { - audio_sink = config::audio.sink.c_str(); - } - - mic->mic.reset( - pa_simple_new(nullptr, "sunshine", - pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, - "sunshine-record", &mic->ss, nullptr, nullptr, &status)); - - if(!mic->mic) { - auto err_str = pa_strerror(status); - BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; - - log_flush(); - std::abort(); - } - - return mic; -} - ifaddr_t get_ifaddrs() { ifaddrs *p { nullptr }; diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index ed3b5985..ad3397d5 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -584,6 +584,4 @@ void freeInput(void *p) { auto *input = (input_raw_t *)p; delete input; } - -std::unique_ptr init() { return std::make_unique(); } } // namespace platf diff --git a/sunshine/thread_safe.h b/sunshine/thread_safe.h index 21db9974..8918a676 100644 --- a/sunshine/thread_safe.h +++ b/sunshine/thread_safe.h @@ -16,9 +16,9 @@ namespace safe { template class event_t { +public: using status_t = util::optional_t; -public: template void raise(Args &&...args) { std::lock_guard lg { _lock }; @@ -131,10 +131,97 @@ private: }; template -class queue_t { +class alarm_raw_t { +public: using status_t = util::optional_t; + alarm_raw_t() : _status { util::false_v } {} + + 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 + auto wait_for(const std::chrono::duration &rel_time) { + std::unique_lock ul(_lock); + + return _cv.wait_for(ul, rel_time, [this]() { return (bool)status(); }); + } + + template + auto wait_for(const std::chrono::duration &rel_time, Pred &&pred) { + std::unique_lock ul(_lock); + + return _cv.wait_for(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); }); + } + + template + auto wait_until(const std::chrono::duration &rel_time) { + std::unique_lock ul(_lock); + + return _cv.wait_until(ul, rel_time, [this]() { return (bool)status(); }); + } + + template + auto wait_until(const std::chrono::duration &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 + 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 +using alarm_t = std::shared_ptr>; + +template +alarm_t make_alarm() { + return std::make_shared>(); +} + +template +class queue_t { public: + using status_t = util::optional_t; + queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {} template @@ -202,7 +289,6 @@ public: } std::vector &unsafe() { - std::lock_guard { _lock }; return _queue; } diff --git a/sunshine/utility.h b/sunshine/utility.h index 66606a38..200f7c0f 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -53,6 +53,10 @@ auto &b = std::get<1>(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(a##_) + namespace util { template class X, class... Y> @@ -830,6 +834,5 @@ inline auto little(T x) { return endian_helper::little(x); } template inline auto big(T x) { return endian_helper::big(x); } } // namespace endian - } // namespace util #endif From 66a78f0d406fe992684585b6649986bac9160594 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 19 May 2021 09:42:38 +0200 Subject: [PATCH 068/158] Fix windows build --- sunshine/platform/windows/input.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index d7667694..0d34ad1e 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -2,11 +2,14 @@ #include #include +// prevent clang format from "optimizing" the header include order +// clang-format off +#include #include #include -#include #include #include +// clang-format on #include From 0868d898f6331d14ab8baa53a99f3c7816bbcfc9 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 19 May 2021 18:11:06 +0200 Subject: [PATCH 069/158] Create virtual audio sinks on Linux --- assets/sunshine.conf | 4 +- sunshine/audio.cpp | 204 ++++++++++-------- sunshine/audio.h | 21 ++ sunshine/nvhttp.cpp | 4 +- sunshine/platform/common.h | 61 ++++-- sunshine/platform/linux/audio.cpp | 332 ++++++++++++++++++------------ sunshine/rtsp.cpp | 22 +- 7 files changed, 403 insertions(+), 245 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index c4ea57a4..6dd5cecf 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -79,8 +79,8 @@ # # You can find the name of the audio sink using the following command: # !! Linux only !! -# pacmd list-sources | grep "name:" -# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor +# pacmd list-sinks | grep "name:" +# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo # # !! Windows only !! # tools\audio-info.exe diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 5960562d..39158f7b 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -5,6 +5,7 @@ #include "platform/common.h" #include "audio.h" +#include "config.h" #include "main.h" #include "thread_safe.h" #include "utility.h" @@ -14,47 +15,65 @@ using namespace std::literals; using opus_t = util::safe_ptr; using sample_queue_t = std::shared_ptr>>; -struct opus_stream_config_t { - std::int32_t sampleRate; - int channelCount; - int streams; - int coupledStreams; - const std::uint8_t *mapping; +struct audio_ctx_t { + // We want to change the sink for the first stream only + std::unique_ptr sink_flag; + std::unique_ptr control; + + platf::sink_t sink; }; -constexpr std::uint8_t map_stereo[] { 0, 1 }; -constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 }; -constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 }; +static int start_audio_control(audio_ctx_t &ctx); +static void stop_audio_control(audio_ctx_t &); + +int map_stream(int channels, bool quality); constexpr auto SAMPLE_RATE = 48000; -static opus_stream_config_t stereo = { - SAMPLE_RATE, - 2, - 1, - 1, - map_stereo +opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] { + { + SAMPLE_RATE, + 2, + 1, + 1, + platf::speaker::map_stereo, + }, + { + SAMPLE_RATE, + 6, + 4, + 2, + platf::speaker::map_surround51, + }, + { + SAMPLE_RATE, + 6, + 6, + 0, + platf::speaker::map_surround51, + }, + { + SAMPLE_RATE, + 8, + 5, + 3, + platf::speaker::map_surround71, + }, + { + SAMPLE_RATE, + 8, + 8, + 0, + platf::speaker::map_surround71, + }, }; -static opus_stream_config_t Surround51 = { - SAMPLE_RATE, - 6, - 4, - 2, - map_surround51 -}; - -static opus_stream_config_t HighSurround51 = { - SAMPLE_RATE, - 6, - 6, - 0, - map_high_surround51 -}; +auto control_shared = safe::make_shared(start_audio_control, stop_audio_control); 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 - auto stream = &stereo; + auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; + opus_t opus { opus_multistream_encoder_create( stream->sampleRate, stream->channelCount, @@ -81,75 +100,43 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi } } -const platf::card_t *active_card(const std::vector &cards) { - for(auto &card : cards) { - if(card.active_profile) { - return &card; - } - } - - return nullptr; -} - 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 = &stereo; + auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; - auto control = platf::audio_control(); + auto ref = control_shared.ref(); + if(!ref) { + return; + } + + auto &control = ref->control; if(!control) { BOOST_LOG(error) << "Couldn't create audio control"sv; return; } - auto cards = control->card_info(); - if(cards.empty()) { - return; + std::string *sink = &ref->sink.host; + 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; + } } - auto card = active_card(cards); - if(!card || (card->stereo.empty() && card->surround51.empty() && card->surround71.empty())) { - return; - } + // Only the first may change the default sink + if( + !ref->sink_flag->exchange(true, std::memory_order_acquire) && + control->set_sink(*sink)) { - const platf::profile_t *profile; - switch(config.channels) { - case 2: - if(!card->stereo.empty()) { - profile = &card->stereo[0]; - } - else if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - break; - case 6: - if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - else { - profile = &card->stereo[0]; - } - break; - case 8: - if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - else if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else { - profile = &card->stereo[0]; - } - break; - } - - if(control->set_output(*card, *profile)) { return; } @@ -166,7 +153,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co auto frame_size = config.packetDuration * stream->sampleRate / 1000; int samples_per_frame = frame_size * stream->channelCount; - auto mic = control->create_mic(stream->sampleRate, frame_size); + auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't create audio input"sv; @@ -185,7 +172,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co continue; case platf::capture_e::reinit: mic.reset(); - mic = control->create_mic(stream->sampleRate, frame_size); + mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't re-initialize audio input"sv; @@ -199,4 +186,41 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co 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(false); + + if(!(ctx.control = platf::audio_control())) { + return -1; + } + + auto sink = ctx.control->sink_info(); + if(!sink) { + return -1; + } + + ctx.sink = std::move(*sink); + return 0; +} +void stop_audio_control(audio_ctx_t &ctx) { + // restore audio-sink if applicable + 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 diff --git a/sunshine/audio.h b/sunshine/audio.h index f170fc36..01f145af 100644 --- a/sunshine/audio.h +++ b/sunshine/audio.h @@ -4,10 +4,31 @@ #include "thread_safe.h" #include "utility.h" 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 { int packetDuration; int channels; int mask; + + bool high_quality; }; using packet_t = util::buffer_t; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index ae413c39..6ac68d6e 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -117,9 +117,7 @@ void save_state() { } void load_state() { - auto file_state = fs::current_path() / config::nvhttp.file_state; - - if(!fs::exists(file_state)) { + if(!fs::exists(config::nvhttp.file_state)) { unique_id = util::uuid_t::generate().string(); return; } diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index c044aa53..978182f9 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -30,6 +30,36 @@ constexpr std::uint16_t B = 0x2000; constexpr std::uint16_t X = 0x4000; 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, +}; + +constexpr std::uint8_t map_stereo[] { + FRONT_LEFT, FRONT_RIGHT +}; +constexpr std::uint8_t map_surround51[] { + FRONT_LEFT, BACK_LEFT, FRONT_RIGHT, BACK_RIGHT, FRONT_CENTER, LOW_FREQUENCY +}; +constexpr std::uint8_t map_surround71[] { + FRONT_LEFT, + BACK_LEFT, + FRONT_RIGHT, + BACK_RIGHT, + SIDE_LEFT, + SIDE_RIGHT, + FRONT_CENTER, + LOW_FREQUENCY, +}; +} // namespace speaker + enum class dev_type_e { none, dxgi, @@ -102,21 +132,18 @@ public: virtual ~img_t() = default; }; -struct profile_t { - std::string name; - std::string description; +struct sink_t { + // Play on host PC + std::string host; - bool available; -}; - -struct card_t { - std::string name; - std::string description; - - std::optional active_profile; - std::vector stereo; - std::vector surround51; - std::vector surround71; + // 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; }; struct hwdevice_t { @@ -168,11 +195,11 @@ public: class audio_control_t { public: - virtual int set_output(const card_t &card, const profile_t &) = 0; + virtual int set_sink(const std::string &sink) = 0; - virtual std::unique_ptr create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) = 0; + virtual std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0; - virtual std::vector card_info() = 0; + virtual std::optional sink_info() = 0; virtual ~audio_control_t() = default; }; diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index 86cac399..f0044ee8 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -17,12 +17,36 @@ namespace platf { using namespace std::literals; -struct mic_attr_t : public mic_t { - pa_sample_spec ss; - util::safe_ptr mic; +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, +}; - explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, - std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {} +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 mic; capture_e sample(std::vector &sample_buf) override { auto sample_size = sample_buf.size(); @@ -39,8 +63,16 @@ struct mic_attr_t : public mic_t { } }; -std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { - auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); +std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate) { + auto mic = std::make_unique(); + + 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; @@ -52,7 +84,7 @@ std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { mic->mic.reset( pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, - "sunshine-record", &mic->ss, nullptr, nullptr, &status)); + "sunshine-record", &ss, &pa_map, nullptr, &status)); if(!mic->mic) { auto err_str = pa_strerror(status); @@ -66,6 +98,22 @@ std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { } namespace pa { +template +struct add_const_helper; + +template +struct add_const_helper { + using type = const std::remove_pointer_t *; +}; + +template +struct add_const_helper { + using type = const T *; +}; + +template +using add_const_t = typename add_const_helper, T>::type; + template void pa_free(T *p) { pa_xfree(p); @@ -76,10 +124,10 @@ using op_t = util::safe_ptr; using string_t = util::safe_ptr>; template -using cb_t = std::function; +using cb_t = std::function i, int eol)>; template -void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) { +void cb(ctx_t::pointer ctx, add_const_t i, int eol, void *userdata) { auto &f = *(cb_t *)userdata; // For some reason, pulseaudio calls this callback after disconnecting @@ -90,6 +138,12 @@ void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) { f(ctx, i, eol); } +void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) { + auto alarm = (safe::alarm_raw_t *)userdata; + + alarm->ring(i); +} + void ctx_state_cb(ctx_t::pointer ctx, void *userdata) { auto &f = *(std::function *)userdata; @@ -103,67 +157,6 @@ void success_cb(ctx_t::pointer ctx, int status, void *userdata) { alarm->ring(status ? 0 : 1); } -profile_t make(pa_card_profile_info2 *profile) { - return profile_t { - profile->name, - profile->description, - profile->available == 1 - }; -} - -card_t make(const pa_card_info *card) { - boost::regex stereo_expr { - ".*output(?!.*surround).*stereo.*" - }; - - boost::regex surround51_expr { - ".*output.*surround(.*(^\\d|51).*|$)" - }; - - boost::regex surround71_expr { - ".*output.*surround.*7.?1.*" - }; - - std::vector stereo; - std::vector surround51; - std::vector surround71; - - std::for_each_n(card->profiles2, card->n_profiles, [&](pa_card_profile_info2 *profile) { - if(boost::regex_match(profile->name, stereo_expr)) { - stereo.emplace_back(make(profile)); - } - if(boost::regex_match(profile->name, surround51_expr)) { - surround51.emplace_back(make(profile)); - } - if(boost::regex_match(profile->name, surround71_expr)) { - surround71.emplace_back(make(profile)); - } - }); - - std::optional active_profile; - if(card->active_profile2->name != "off"sv) { - active_profile = make(card->active_profile2); - } - - return card_t { - card->name, - card->driver, - std::move(active_profile), - std::move(stereo), - std::move(surround51), - std::move(surround71), - }; -} - -const card_t *active_card(const std::vector &cards) { - for(auto &card : cards) { - if(card.active_profile) { - return &card; - } - } - - return nullptr; -} class server_t : public audio_control_t { enum ctx_event_e : int { ready, @@ -175,6 +168,12 @@ 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> events; std::unique_ptr> events_cb; @@ -237,55 +236,169 @@ public: return 0; } - std::vector card_info() override { - auto alarm = safe::make_alarm(); + int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) { + auto alarm = safe::make_alarm(); - std::vector cards; - cb_t f = [&cards, alarm](ctx_t::pointer ctx, const pa_card_info *card_info, int eol) { - if(!card_info) { + 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(); + + 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_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(); + + 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 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 card info: "sv << pa_strerror(pa_context_errno(ctx)); + BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx)); + + alarm->ring(-1); } - alarm->ring(true); + alarm->ring(0); return; } - cards.emplace_back(make(card_info)); + 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_card_info_list(ctx.get(), cb, &f) }; + op_t op { pa_context_get_sink_info_list(ctx.get(), cb, &f) }; if(!op) { BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get())); - return {}; + return std::nullopt; } alarm->wait(); - return cards; + 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 create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) override { - return microphone(sample_rate, frame_size); + std::unique_ptr 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_output(const card_t &card, const profile_t &profile) override { + int set_sink(const std::string &sink) override { auto alarm = safe::make_alarm(); - op_t op { pa_context_set_card_profile_by_name( - ctx.get(), card.name.c_str(), profile.name.c_str(), success_cb, alarm.get()) + 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 profile operation: "sv << pa_strerror(pa_context_errno(ctx.get())); + 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 profile ["sv << profile.name << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); + BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); return -1; } @@ -294,6 +407,10 @@ public: } ~server_t() override { + unload_null(index.stereo); + unload_null(index.surround51); + unload_null(index.surround71); + if(worker.joinable()) { pa_context_disconnect(ctx.get()); @@ -319,43 +436,6 @@ std::unique_ptr audio_control() { } std::unique_ptr init() { - pa::server_t server; - if(server.init()) { - return std::make_unique(); - } - - auto cards = server.card_info(); - - for(auto &card : cards) { - BOOST_LOG(info) << "---- CARD ----"sv; - BOOST_LOG(info) << "Name: ["sv << card.name << ']'; - BOOST_LOG(info) << "Description: ["sv << card.description << ']'; - - if(card.active_profile) { - BOOST_LOG(info) << "Active profile:"sv; - BOOST_LOG(info) << " Name: [" << card.active_profile->name << ']'; - BOOST_LOG(info) << " Description: ["sv << card.active_profile->description << ']'; - BOOST_LOG(info) << " Available: ["sv << card.active_profile->available << ']'; - BOOST_LOG(info); - } - - - BOOST_LOG(info) << " -- stereo --"sv; - for(auto &profile : card.stereo) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - - BOOST_LOG(info) << " -- surround 5.1 --"sv; - for(auto &profile : card.surround51) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - - BOOST_LOG(info) << " -- surround 7.1 --"sv; - for(auto &profile : card.surround71) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - } - return std::make_unique(); } } // namespace platf \ No newline at end of file diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index fc6b77ef..1dae7c94 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -293,15 +293,22 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { auto seqn_str = to_string(req->sequenceNumber); option.content = const_cast(seqn_str.c_str()); - std::string_view payload; - if(config::video.hevc_mode == 1) { - payload = "surround-params=NONE"sv; - } - else { - payload = "sprop-parameter-sets=AAAAAU;surround-params=NONE"sv; + std::stringstream ss; + if(config::video.hevc_mode != 1) { + ss << "sprop-parameter-sets=AAAAAU"sv << std::endl; } - respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, payload); + for(auto &stream_config : audio::stream_configs) { + ss << "a=fmtp:97 surround-params="sv << stream_config.channelCount << stream_config.streams << stream_config.coupledStreams; + + std::for_each_n(stream_config.mapping, 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) { @@ -404,6 +411,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { try { 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.high_quality = util::from_view(args.at("x-nv-audio.surround.AudioQuality"sv)); config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv)); config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv)); From cd870bdcddf28ae5d64e22c2ac4ff76ff2951a57 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 19 May 2021 20:02:03 +0200 Subject: [PATCH 070/158] Guard against missing parameters --- sunshine/nvhttp.cpp | 51 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6ac68d6e..8f9068d7 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -339,12 +339,19 @@ template void pair(std::shared_ptr> &add_cert, std::shared_ptr::Response> response, std::shared_ptr::Request> request) { print_req(request); + pt::ptree tree; + auto args = request->parse_query_string(); + if(args.find("uniqueid"s) == std::end(args)) { + tree.put("root.resume", 0); + tree.put("root..status_code", 400); + + return; + } + auto uniqID { std::move(args.at("uniqueid"s)) }; auto sess_it = map_id_sess.find(uniqID); - pt::ptree tree; - args_t::const_iterator it; if(it = args.find("phrase"); it != std::end(args)) { if(it->second == "getservercert"sv) { @@ -505,9 +512,6 @@ void serverinfo(std::shared_ptr::Response> res void applist(resp_https_t response, req_https_t request) { print_req(request); - auto args = request->parse_query_string(); - auto clientID = args.at("uniqueid"s); - pt::ptree tree; auto g = util::fail_guard([&]() { @@ -517,6 +521,16 @@ void applist(resp_https_t response, req_https_t request) { response->write(data.str()); }); + auto args = request->parse_query_string(); + if(args.find("uniqueid"s) == std::end(args)) { + tree.put("root.resume", 0); + tree.put("root..status_code", 400); + + return; + } + + auto clientID = args.at("uniqueid"s); + auto client = map_id_client.find(clientID); if(client == std::end(map_id_client)) { tree.put("root..status_code", 501); @@ -558,7 +572,19 @@ void launch(resp_https_t response, req_https_t request) { return; } - auto args = request->parse_query_string(); + auto args = request->parse_query_string(); + if( + args.find("uniqueid"s) == std::end(args) || + args.find("rikey"s) == std::end(args) || + args.find("rikey"s) == std::end(args) || + args.find("appid"s) == std::end(args)) { + + tree.put("root.resume", 0); + tree.put("root..status_code", 400); + + return; + } + auto appid = util::from_view(args.at("appid")) - 1; auto current_appid = proc::proc.running(); @@ -625,7 +651,18 @@ void resume(resp_https_t response, req_https_t request) { stream::launch_session_t launch_session; - auto args = request->parse_query_string(); + auto args = request->parse_query_string(); + if( + args.find("uniqueid"s) == std::end(args) || + args.find("rikey"s) == std::end(args) || + args.find("rikeyid"s) == std::end(args)) { + + tree.put("root.resume", 0); + tree.put("root..status_code", 400); + + return; + } + auto clientID = args.at("uniqueid"s); launch_session.gcm_key = *util::from_hex(args.at("rikey"s), true); uint32_t prepend_iv = util::endian::big(util::from_view(args.at("rikeyid"s))); From 825efb512ab8d3d5a804eb913d5835445a48fe3f Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 19 May 2021 21:44:42 +0200 Subject: [PATCH 071/158] Based on client request, mute the host on Linux --- sunshine/audio.cpp | 28 +++++++++++++++------- sunshine/audio.h | 8 ++++++- sunshine/nvhttp.cpp | 57 +++++++++++++++++++++------------------------ sunshine/rtsp.cpp | 7 +++++- sunshine/rtsp.h | 2 ++ sunshine/stream.h | 1 - 6 files changed, 61 insertions(+), 42 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 39158f7b..6c34a0f9 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -18,8 +18,10 @@ using sample_queue_t = std::shared_ptr>> struct audio_ctx_t { // We want to change the sink for the first stream only std::unique_ptr sink_flag; + std::unique_ptr control; + bool restore_sink; platf::sink_t sink; }; @@ -72,7 +74,7 @@ auto control_shared = safe::make_shared(start_audio_control, stop_a 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 - auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; + auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; opus_t opus { opus_multistream_encoder_create( stream->sampleRate, @@ -102,7 +104,7 @@ 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) { //FIXME: Pick correct opus_stream_config_t based on config.channels - auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; + auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto ref = control_shared.ref(); if(!ref) { @@ -116,7 +118,8 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co return; } - std::string *sink = &ref->sink.host; + 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) { @@ -132,12 +135,14 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co } } - // Only the first may change the default sink - if( - !ref->sink_flag->exchange(true, std::memory_order_acquire) && - control->set_sink(*sink)) { + // 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]; - return; + // 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(30); @@ -212,11 +217,18 @@ int start_audio_control(audio_ctx_t &ctx) { 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 diff --git a/sunshine/audio.h b/sunshine/audio.h index 01f145af..27018423 100644 --- a/sunshine/audio.h +++ b/sunshine/audio.h @@ -24,11 +24,17 @@ struct opus_stream_config_t { extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG]; struct config_t { + enum flags_e : int { + HIGH_QUALITY, + HOST_AUDIO, + MAX_FLAGS + }; + int packetDuration; int channels; int mask; - bool high_quality; + std::bitset flags; }; using packet_t = util::buffer_t; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 8f9068d7..ebf6468a 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -164,6 +164,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(args.at("rikey"s), true); + uint32_t prepend_iv = util::endian::big(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) { if(sess.async_insert_pin.salt.size() < 32) { tree.put("root.paired", 0); @@ -343,7 +357,6 @@ void pair(std::shared_ptr> &add_cert, std::shared_ auto args = request->parse_query_string(); if(args.find("uniqueid"s) == std::end(args)) { - tree.put("root.resume", 0); tree.put("root..status_code", 400); return; @@ -523,7 +536,6 @@ void applist(resp_https_t response, req_https_t request) { auto args = request->parse_query_string(); if(args.find("uniqueid"s) == std::end(args)) { - tree.put("root.resume", 0); tree.put("root..status_code", 400); return; @@ -554,7 +566,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(request); pt::ptree tree; @@ -574,9 +586,9 @@ void launch(resp_https_t response, req_https_t request) { auto args = request->parse_query_string(); if( - args.find("uniqueid"s) == std::end(args) || - args.find("rikey"s) == std::end(args) || 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); @@ -605,23 +617,14 @@ void launch(resp_https_t response, req_https_t request) { } } - stream::launch_session_t launch_session; - - auto clientID = args.at("uniqueid"s); - launch_session.gcm_key = *util::from_hex(args.at("rikey"s), true); - uint32_t prepend_iv = util::endian::big(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); + host_audio = util::from_view(args.at("localAudioPlayMode")); + stream::launch_session_raise(make_launch_session(host_audio, args)); tree.put("root..status_code", 200); 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(request); pt::ptree tree; @@ -649,11 +652,8 @@ void resume(resp_https_t response, req_https_t request) { return; } - stream::launch_session_t launch_session; - auto args = request->parse_query_string(); if( - args.find("uniqueid"s) == std::end(args) || args.find("rikey"s) == std::end(args) || args.find("rikeyid"s) == std::end(args)) { @@ -663,15 +663,7 @@ void resume(resp_https_t response, req_https_t request) { return; } - auto clientID = args.at("uniqueid"s); - launch_session.gcm_key = *util::from_hex(args.at("rikey"s), true); - uint32_t prepend_iv = util::endian::big(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); + stream::launch_session_raise(make_launch_session(host_audio, args)); tree.put("root..status_code", 200); tree.put("root.resume", 1); @@ -844,6 +836,9 @@ void start(std::shared_ptr shutdown_event) { return 1; }); + // /resume doesn't get the parameter "localAudioPlayMode" + // /launch will store it in host_audio + bool host_audio {}; https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once }; http_server_t http_server; @@ -853,9 +848,9 @@ void start(std::shared_ptr shutdown_event) { https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; https_server.resource["^/applist$"]["GET"] = applist; 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; - 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.config.reuse_address = true; diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 1dae7c94..5b46fa76 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -69,6 +69,7 @@ public: } 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; launch_event.raise(launch_session); } @@ -408,12 +409,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); config_t config; + + config.audio.flags[audio::config_t::HOST_AUDIO] = launch_session->host_audio; try { 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.high_quality = util::from_view(args.at("x-nv-audio.surround.AudioQuality"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.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv)); diff --git a/sunshine/rtsp.h b/sunshine/rtsp.h index cab06fc4..f5e085b3 100644 --- a/sunshine/rtsp.h +++ b/sunshine/rtsp.h @@ -14,6 +14,8 @@ namespace stream { struct launch_session_t { crypto::aes_t gcm_key; crypto::aes_t iv; + + bool host_audio; }; void launch_session_raise(launch_session_t launch_session); diff --git a/sunshine/stream.h b/sunshine/stream.h index 57f69403..9c8dc5ae 100644 --- a/sunshine/stream.h +++ b/sunshine/stream.h @@ -18,7 +18,6 @@ struct config_t { video::config_t monitor; int packetsize; - bool sops; std::optional gcmap; }; From c6a0eeef3ff48e7c143524d9e90e4ea09ff719e5 Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Thu, 20 May 2021 00:21:38 +0100 Subject: [PATCH 072/158] Force IDR frames for libx265 encoder Parameter is needed to avoid infinite black screen with "Waiting for IDR frame" in moonlight-qt (3.1.3) on Windows. --- sunshine/video.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 1069d0e6..fd077eea 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -315,6 +315,7 @@ static encoder_t software { // It also looks like gop_size isn't passed on to x265, so we have to set // 'keyint=-1' in the parameters ourselves. { + { "forced-idr"s, 1 }, { "x265-params"s, "info=0:keyint=-1"s }, { "preset"s, &config::video.sw.preset }, { "tune"s, &config::video.sw.tune } }, From b119121e108e9b3fa002f12445124fc28d816792 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 21 May 2021 13:53:12 +0200 Subject: [PATCH 073/158] Change default audio device on Windows --- .clang-format | 2 +- README.md | 1 + sunshine/platform/windows/PolicyConfig.h | 164 +++ sunshine/platform/windows/audio.cpp | 1183 ++++++++++++++-------- tools/audio.cpp | 16 +- 5 files changed, 918 insertions(+), 448 deletions(-) create mode 100644 sunshine/platform/windows/PolicyConfig.h diff --git a/.clang-format b/.clang-format index a069b562..1c31d7ba 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign -AlignConsecutiveAssignments: AcrossComments +AlignConsecutiveAssignments: true AlignOperands: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false diff --git a/README.md b/README.md index 627d51c6..2d7d4d9d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ sunshine needs access to uinput to create mouse and gamepad events: - [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server) - [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 :) +- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically) ## Application List: - You can use Environment variables in place of values diff --git a/sunshine/platform/windows/PolicyConfig.h b/sunshine/platform/windows/PolicyConfig.h new file mode 100644 index 00000000..60f36a68 --- /dev/null +++ b/sunshine/platform/windows/PolicyConfig.h @@ -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 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 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 +}; + +// ---------------------------------------------------------------------------- diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index d59efb50..b054aa27 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -1,444 +1,739 @@ -// -// Created by loki on 1/12/20. -// - -#include -#include -#include - -#include - -#include - -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - -using namespace std::literals; -namespace platf::audio { -template -void Release(T *p) { - p->Release(); -} - -template -void co_task_free(T *p) { - CoTaskMemFree((LPVOID)p); -} - -using device_enum_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using audio_client_t = util::safe_ptr>; -using audio_capture_t = util::safe_ptr>; -using wave_format_t = util::safe_ptr>; -using handle_t = util::safe_ptr_v2; - -class co_init_t : public deinit_t { -public: - co_init_t() { - CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); - } - - ~co_init_t() override { - CoUninitialize(); - } -}; - -struct format_t { - std::string_view name; - int channels; - int channel_mask; -} formats[] { - { "Stereo"sv, - 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT }, - { "Mono"sv, - 1, - SPEAKER_FRONT_CENTER }, - { "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_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; - } -} - -void surround51_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { - enum surround51_e : int { - front_left, - front_right, - front_center, - low_frequency, // subwoofer - back_left, - back_right, - channels51 // number of channels in surround sound - }; - - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; - - for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; sample_out_p += channels51) { - std::uint32_t left {}, right {}; - - left += sample_out_p[front_left]; - left += sample_out_p[front_center] * 90 / 100; - left += sample_out_p[low_frequency] * 30 / 100; - left += sample_out_p[back_left] * 70 / 100; - left += sample_out_p[back_right] * 30 / 100; - - right += sample_out_p[front_right]; - right += sample_out_p[front_center] * 90 / 100; - right += sample_out_p[low_frequency] * 30 / 100; - right += sample_out_p[back_left] * 30 / 100; - right += sample_out_p[back_right] * 70 / 100; - ; - - *sample_in_pos++ = (std::uint16_t)left; - *sample_in_pos++ = (std::uint16_t)right; - } -} - -void mono_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2; - - for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; ++sample_out_p) { - *sample_in_pos++ = *sample_out_p; - *sample_in_pos++ = *sample_out_p; - } -} - -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; - } - - 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; - } - - 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 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; -} - -class mic_wasapi_t : public mic_t { -public: - capture_e sample(std::vector &sample_in) override { - auto sample_size = sample_in.size() / 2 * format->channels; - 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 - auto capture_result = _fill_buffer(); - - if(capture_result != capture_e::ok) { - return capture_result; - } - } - - switch(format->channels) { - case 1: - mono_to_stereo(sample_in, sample_buf); - break; - case 2: - std::copy_n(std::begin(sample_buf), sample_size, std::begin(sample_in)); - break; - case 6: - surround51_to_stereo(sample_in, sample_buf); - break; - default: - BOOST_LOG(error) << '[' << format->name << "] not yet supported"sv; - return capture_e::error; - } - - // The excess samples should be in front of the queue - std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); - sample_buf_pos -= sample_size; - - return capture_e::ok; - } - - - int init(std::uint32_t sample_rate, std::uint32_t frame_size) { - audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); - if(!audio_event) { - BOOST_LOG(error) << "Couldn't create Event handle"sv; - - return -1; - } - - HRESULT status; - - 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 -1; - } - - if(config::audio.sink.empty()) { - status = device_enum->GetDefaultAudioEndpoint( - eRender, - eConsole, - &device); - } - else { - std::wstring_convert, 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; - } - - for(auto &format : formats) { - BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; - audio_client = make_audio_client(device, format, sample_rate); - - if(audio_client) { - BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; - this->format = &format; - break; - } - } - - if(!audio_client) { - BOOST_LOG(error) << "Couldn't find supported format for audio"sv; - return -1; - } - - REFERENCE_TIME default_latency; - audio_client->GetDevicePeriod(&default_latency, nullptr); - default_latency_ms = default_latency / 1000; - - std::uint32_t frames; - status = audio_client->GetBufferSize(&frames); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames * 2, frame_size * format->channels * 2) }; - sample_buf_pos = std::begin(sample_buf); - - status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->SetEventHandle(audio_event.get()); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->Start(); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~mic_wasapi_t() override { - if(audio_client) { - audio_client->Stop(); - } - } - -private: - capture_e _fill_buffer() { - HRESULT status; - - // Total number of samples - struct sample_aligned_t { - std::uint32_t uninitialized; - std::int16_t *samples; - } sample_aligned; - - // number of samples / number of channels - struct block_aligned_t { - std::uint32_t audio_sample_size; - } block_aligned; - - status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); - switch(status) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - return capture_e::timeout; - default: - BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - std::uint32_t packet_size {}; - for( - status = audio_capture->GetNextPacketSize(&packet_size); - SUCCEEDED(status) && packet_size > 0; - status = audio_capture->GetNextPacketSize(&packet_size)) { - DWORD buffer_flags; - status = audio_capture->GetBuffer( - (BYTE **)&sample_aligned.samples, - &block_aligned.audio_sample_size, - &buffer_flags, - nullptr, nullptr); - - switch(status) { - case S_OK: - break; - case AUDCLNT_E_DEVICE_INVALIDATED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * format->channels); - - if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { - std::fill_n(sample_buf_pos, n, 0); - } - else { - std::copy_n(sample_aligned.samples, n, sample_buf_pos); - } - - sample_buf_pos += n; - - audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); - } - - if(status == AUDCLNT_E_DEVICE_INVALIDATED) { - return capture_e::reinit; - } - - if(FAILED(status)) { - return capture_e::error; - } - - return capture_e::ok; - } - -public: - handle_t audio_event; - - device_enum_t device_enum; - device_t device; - audio_client_t audio_client; - audio_capture_t audio_capture; - - REFERENCE_TIME default_latency_ms; - - util::buffer_t sample_buf; - std::int16_t *sample_buf_pos; - - format_t *format; -}; -} // namespace platf::audio - -namespace platf { - -// It's not big enough to justify it's own source file :/ -namespace dxgi { -int init(); -} - -std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t frame_size) { - auto mic = std::make_unique(); - - if(mic->init(sample_rate, frame_size)) { - return nullptr; - } - - return mic; -} - -std::unique_ptr init() { - if(dxgi::init()) { - return nullptr; - } - return std::make_unique(); -} -} // namespace platf +// +// Created by loki on 1/12/20. +// + +#include +#include +#include + +#include + +#include + +#define INITGUID +#include +#undef INITGUID + +#include "sunshine/config.h" +#include "sunshine/main.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 IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); + +using namespace std::literals; +namespace platf::audio { +constexpr auto SAMPLE_RATE = 48000; + +template +void Release(T *p) { + p->Release(); +} + +template +void co_task_free(T *p) { + CoTaskMemFree((LPVOID)p); +} + +using device_enum_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using collection_t = util::safe_ptr>; +using audio_client_t = util::safe_ptr>; +using audio_capture_t = util::safe_ptr>; +using wave_format_t = util::safe_ptr>; +using wstring_t = util::safe_ptr>; +using handle_t = util::safe_ptr_v2; +using policy_t = util::safe_ptr>; +using prop_t = util::safe_ptr>; + +class co_init_t : public deinit_t { +public: + co_init_t() { + CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); + } + + ~co_init_t() override { + CoUninitialize(); + } +}; + +class prop_var_t { +public: + prop_var_t() { + PropVariantInit(&prop); + } + + ~prop_var_t() { + PropVariantClear(&prop); + } + + PROPVARIANT prop; +}; + +static std::wstring_convert, 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, + }, +}; + +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; +} + +void surround51_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { + enum surround51_e : int { + front_left, + front_right, + front_center, + low_frequency, // subwoofer + back_left, + back_right, + channels51 // number of channels in surround sound + }; + + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; + + for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; sample_out_p += channels51) { + std::uint32_t left {}, right {}; + + left += sample_out_p[front_left]; + left += sample_out_p[front_center] * 90 / 100; + left += sample_out_p[low_frequency] * 30 / 100; + left += sample_out_p[back_left] * 70 / 100; + left += sample_out_p[back_right] * 30 / 100; + + right += sample_out_p[front_right]; + right += sample_out_p[front_center] * 90 / 100; + right += sample_out_p[low_frequency] * 30 / 100; + right += sample_out_p[back_left] * 30 / 100; + right += sample_out_p[back_right] * 70 / 100; + + *sample_in_pos++ = (std::uint16_t)left; + *sample_in_pos++ = (std::uint16_t)right; + } +} + +void mono_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { + auto sample_in_pos = std::begin(sample_in); + auto sample_end = std::begin(sample_out) + sample_in.size() / 2; + + for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; ++sample_out_p) { + *sample_in_pos++ = *sample_out_p; + *sample_in_pos++ = *sample_out_p; + } +} + +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) { + 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 { +public: + capture_e sample(std::vector &sample_in) override { + auto sample_size = sample_in.size() / 2 * format->channels; + 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 + auto capture_result = _fill_buffer(); + + if(capture_result != capture_e::ok) { + return capture_result; + } + } + + switch(format->channels) { + case 1: + mono_to_stereo(sample_in, sample_buf); + break; + case 2: + std::copy_n(std::begin(sample_buf), sample_size, std::begin(sample_in)); + break; + case 6: + surround51_to_stereo(sample_in, sample_buf); + break; + default: + BOOST_LOG(error) << '[' << format->name << "] not yet supported"sv; + return capture_e::error; + } + + // The excess samples should be in front of the queue + std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); + sample_buf_pos -= sample_size; + + return capture_e::ok; + } + + + int init(std::uint32_t sample_rate, std::uint32_t frame_size) { + audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); + if(!audio_event) { + BOOST_LOG(error) << "Couldn't create Event handle"sv; + + return -1; + } + + HRESULT status; + + 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 -1; + } + + auto device = default_device(device_enum); + if(!device) { + return -1; + } + + for(auto &format : formats) { + BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; + audio_client = make_audio_client(device, format, sample_rate); + + if(audio_client) { + BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; + this->format = &format; + break; + } + } + + if(!audio_client) { + BOOST_LOG(error) << "Couldn't find supported format for audio"sv; + return -1; + } + + REFERENCE_TIME default_latency; + audio_client->GetDevicePeriod(&default_latency, nullptr); + default_latency_ms = default_latency / 1000; + + std::uint32_t frames; + status = audio_client->GetBufferSize(&frames); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + // *2 --> needs to fit double + sample_buf = util::buffer_t { std::max(frames * 2, frame_size * format->channels * 2) }; + sample_buf_pos = std::begin(sample_buf); + + status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->SetEventHandle(audio_event.get()); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->Start(); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~mic_wasapi_t() override { + if(audio_client) { + audio_client->Stop(); + } + } + +private: + capture_e _fill_buffer() { + HRESULT status; + + // Total number of samples + struct sample_aligned_t { + std::uint32_t uninitialized; + std::int16_t *samples; + } sample_aligned; + + // number of samples / number of channels + struct block_aligned_t { + std::uint32_t audio_sample_size; + } block_aligned; + + status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); + switch(status) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return capture_e::timeout; + default: + BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + std::uint32_t packet_size {}; + for( + status = audio_capture->GetNextPacketSize(&packet_size); + SUCCEEDED(status) && packet_size > 0; + status = audio_capture->GetNextPacketSize(&packet_size)) { + DWORD buffer_flags; + status = audio_capture->GetBuffer( + (BYTE **)&sample_aligned.samples, + &block_aligned.audio_sample_size, + &buffer_flags, + nullptr, nullptr); + + switch(status) { + case S_OK: + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * format->channels); + + if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { + std::fill_n(sample_buf_pos, n, 0); + } + else { + std::copy_n(sample_aligned.samples, n, sample_buf_pos); + } + + sample_buf_pos += n; + + audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); + } + + if(status == AUDCLNT_E_DEVICE_INVALIDATED) { + return capture_e::reinit; + } + + if(FAILED(status)) { + return capture_e::error; + } + + return capture_e::ok; + } + +public: + handle_t audio_event; + + device_enum_t device_enum; + device_t device; + audio_client_t audio_client; + audio_capture_t audio_capture; + + REFERENCE_TIME default_latency_ms; + + util::buffer_t sample_buf; + std::int16_t *sample_buf_pos; + + format_t *format; +}; + +class audio_control_t : public ::platf::audio_control_t { +public: + std::optional 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; + BOOST_LOG(debug) << "====== Found "sv << count << " potential audio devices ======"sv; + for(auto x = 0; x < count; ++x) { + audio::device_t device; + collection->Item(x, &device); + + auto type = validate_device(device); + 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(debug) + << 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 microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { + auto mic = std::make_unique(); + + if(mic->init(sample_rate, frame_size)) { + 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 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]); + + WAVEFORMATEX p { *wave_format.get() }; + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), &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 { + +// It's not big enough to justify it's own source file :/ +namespace dxgi { +int init(); +} + +std::unique_ptr audio_control() { + auto control = std::make_unique(); + + if(control->init() || control->set_sink("virtual-Stereo{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"s)) { + return nullptr; + } + + return control; +} + +std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } + return std::make_unique(); +} +} // namespace platf diff --git a/tools/audio.cpp b/tools/audio.cpp index b99edf5f..a130d384 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -87,7 +87,17 @@ struct format_t { SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT } + 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) { @@ -285,7 +295,7 @@ int main(int argc, char *argv[]) { } audio::collection_t collection; - status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection); + status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection); if(FAILED(status)) { std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; @@ -296,7 +306,7 @@ int main(int argc, char *argv[]) { UINT 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) { audio::device_t device; collection->Item(x, &device); From 2fb5f8a7d01db652a78ce8a662dee8cff0700e02 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 21 May 2021 14:28:24 +0200 Subject: [PATCH 074/158] Add config option for virtual_sink on windows --- assets/sunshine.conf | 6 +++++- sunshine/config.h | 1 + sunshine/platform/common.h | 1 - sunshine/platform/windows/audio.cpp | 8 ++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 6dd5cecf..78f31b2d 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -84,7 +84,11 @@ # # !! Windows only !! # 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 !! # You can select the video card you want to stream: diff --git a/sunshine/config.h b/sunshine/config.h index 3bced756..1b62818e 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -39,6 +39,7 @@ struct video_t { struct audio_t { std::string sink; + std::string virtual_sink; }; struct stream_t { diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 978182f9..91fe185b 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -214,7 +214,6 @@ std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); std::unique_ptr audio_control(); -std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t frame_size); std::shared_ptr display(dev_type_e hwdevice_type); input_t input(); diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index b054aa27..1dc82016 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -259,10 +259,10 @@ const wchar_t *no_null(const wchar_t *str) { return str ? str : L"Unknown"; } -format_t::type_e validate_device(device_t &device) { +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); + auto audio_client = make_audio_client(device, format, sample_rate); BOOST_LOG(debug) << format.name << ": "sv << !audio_client ? "unsupported"sv : "supported"sv; @@ -550,13 +550,13 @@ public: UINT count; collection->GetCount(&count); - std::string virtual_device_id; + std::string virtual_device_id = config::audio.virtual_sink; BOOST_LOG(debug) << "====== Found "sv << count << " potential audio devices ======"sv; for(auto x = 0; x < count; ++x) { audio::device_t device; collection->Item(x, &device); - auto type = validate_device(device); + auto type = validate_device(device, SAMPLE_RATE); if(type == format_t::none) { continue; } From ba07fd510ed2466d317e42498dc603ac8b5153f5 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 22 May 2021 19:51:01 +0200 Subject: [PATCH 075/158] Upmix/Downmix audio --- sunshine/config.cpp | 1 + sunshine/platform/common.h | 14 +- sunshine/platform/windows/audio.cpp | 379 +++++++++++++++++++++++----- 3 files changed, 325 insertions(+), 69 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 6cc158bd..6d3b58bf 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -434,6 +434,7 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "external_ip", nvhttp.external_ip); 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, { "pc"sv, "lan"sv, "wan"sv }); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 91fe185b..672e8f5c 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -46,17 +46,23 @@ constexpr std::uint8_t map_stereo[] { FRONT_LEFT, FRONT_RIGHT }; constexpr std::uint8_t map_surround51[] { - FRONT_LEFT, BACK_LEFT, FRONT_RIGHT, BACK_RIGHT, FRONT_CENTER, LOW_FREQUENCY + FRONT_LEFT, + FRONT_RIGHT, + FRONT_CENTER, + LOW_FREQUENCY, + BACK_LEFT, + BACK_RIGHT, }; constexpr std::uint8_t map_surround71[] { FRONT_LEFT, - BACK_LEFT, FRONT_RIGHT, + FRONT_CENTER, + LOW_FREQUENCY, + LOW_FREQUENCY, + BACK_LEFT, BACK_RIGHT, SIDE_LEFT, SIDE_RIGHT, - FRONT_CENTER, - LOW_FREQUENCY, }; } // namespace speaker diff --git a/sunshine/platform/windows/audio.cpp b/sunshine/platform/windows/audio.cpp index 1dc82016..61df413a 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -81,6 +81,257 @@ public: 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; + using buf_t = util::buffer_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, wchar_t> converter; struct format_t { enum type_e : int { @@ -133,6 +384,18 @@ struct format_t { }, }; +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; @@ -169,50 +432,6 @@ int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { return 0; } -void surround51_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { - enum surround51_e : int { - front_left, - front_right, - front_center, - low_frequency, // subwoofer - back_left, - back_right, - channels51 // number of channels in surround sound - }; - - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2 * channels51; - - for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; sample_out_p += channels51) { - std::uint32_t left {}, right {}; - - left += sample_out_p[front_left]; - left += sample_out_p[front_center] * 90 / 100; - left += sample_out_p[low_frequency] * 30 / 100; - left += sample_out_p[back_left] * 70 / 100; - left += sample_out_p[back_right] * 30 / 100; - - right += sample_out_p[front_right]; - right += sample_out_p[front_center] * 90 / 100; - right += sample_out_p[low_frequency] * 30 / 100; - right += sample_out_p[back_left] * 30 / 100; - right += sample_out_p[back_right] * 70 / 100; - - *sample_in_pos++ = (std::uint16_t)left; - *sample_in_pos++ = (std::uint16_t)right; - } -} - -void mono_to_stereo(std::vector &sample_in, const util::buffer_t &sample_out) { - auto sample_in_pos = std::begin(sample_in); - auto sample_end = std::begin(sample_out) + sample_in.size() / 2; - - for(auto sample_out_p = std::begin(sample_out); sample_out_p != sample_end; ++sample_out_p) { - *sample_in_pos++ = *sample_out_p; - *sample_in_pos++ = *sample_out_p; - } -} - 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( @@ -264,7 +483,7 @@ format_t::type_e validate_device(device_t &device, int sample_rate) { // 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; + BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); if(audio_client) { return format.type; @@ -294,8 +513,8 @@ device_t default_device(device_enum_t &device_enum) { class mic_wasapi_t : public mic_t { public: - capture_e sample(std::vector &sample_in) override { - auto sample_size = sample_in.size() / 2 * format->channels; + capture_e sample(std::vector &sample_out) override { + 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 auto capture_result = _fill_buffer(); @@ -305,18 +524,18 @@ public: } } - switch(format->channels) { - case 1: - mono_to_stereo(sample_in, sample_buf); - break; + switch(channels_out) { case 2: - std::copy_n(std::begin(sample_buf), sample_size, std::begin(sample_in)); + pipe->to_stereo(sample_out, sample_buf); break; case 6: - surround51_to_stereo(sample_in, sample_buf); + pipe->to_51(sample_out, sample_buf); + break; + case 8: + pipe->to_71(sample_out, sample_buf); break; default: - BOOST_LOG(error) << '[' << format->name << "] not yet supported"sv; + BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; return capture_e::error; } @@ -328,7 +547,7 @@ public: } - int init(std::uint32_t sample_rate, std::uint32_t frame_size) { + 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)); if(!audio_event) { BOOST_LOG(error) << "Couldn't create Event handle"sv; @@ -362,7 +581,26 @@ public: if(audio_client) { BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; - this->format = &format; + channels_in = format.channels; + this->channels_out = channels_out; + + switch(channels_in) { + case 1: + pipe = std::make_unique(); + break; + case 2: + pipe = std::make_unique(); + break; + case 6: + pipe = std::make_unique(); + break; + case 8: + pipe = std::make_unique(); + break; + default: + BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; + return -1; + } break; } } @@ -385,7 +623,7 @@ public: } // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames * 2, frame_size * format->channels * 2) }; + sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; sample_buf_pos = std::begin(sample_buf); status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); @@ -467,7 +705,7 @@ private: } sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * format->channels); + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { std::fill_n(sample_buf_pos, n, 0); @@ -505,7 +743,12 @@ public: util::buffer_t sample_buf; std::int16_t *sample_buf_pos; - format_t *format; + // out --> our audio output + int channels_out; + // in --> our wasapi input + int channels_in; + + std::unique_ptr pipe; }; class audio_control_t : public ::platf::audio_control_t { @@ -551,7 +794,6 @@ public: collection->GetCount(&count); std::string virtual_device_id = config::audio.virtual_sink; - BOOST_LOG(debug) << "====== Found "sv << count << " potential audio devices ======"sv; for(auto x = 0; x < count; ++x) { audio::device_t device; collection->Item(x, &device); @@ -576,7 +818,7 @@ public: prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); - BOOST_LOG(debug) + 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 @@ -603,7 +845,7 @@ public: std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { auto mic = std::make_unique(); - if(mic->init(sample_rate, frame_size)) { + if(mic->init(sample_rate, frame_size, channels)) { return nullptr; } @@ -660,8 +902,15 @@ public: } set_wave_format(wave_format, formats[(int)type - 1]); - WAVEFORMATEX p { *wave_format.get() }; - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), &p); + 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() << ']'; @@ -723,7 +972,7 @@ int init(); std::unique_ptr audio_control() { auto control = std::make_unique(); - if(control->init() || control->set_sink("virtual-Stereo{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"s)) { + if(control->init()) { return nullptr; } From dcb32eaaf7ddf1d800b50935393d046cd2f8f621 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 22 May 2021 23:09:53 +0200 Subject: [PATCH 076/158] Skip error correction when a video frame is to large, the alternative is skipping the frame itself --- sunshine/stream.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 8971c266..9500a041 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -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 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; 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 - << 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 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]; } - // packets = parity_shards + data_shards - rs_t rs { reed_solomon_new(data_shards, parity_shards) }; + if(data_shards + parity_shards <= DATA_SHARDS_MAX) { + // packets = parity_shards + data_shards + rs_t rs { reed_solomon_new(data_shards, parity_shards) }; - reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize); + reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize); + } return { data_shards, @@ -670,7 +674,7 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid inspect->packet.frameIndex = packet->pts; inspect->packet.fecInfo = (x << 12 | shards.data_shards << 22 | - fecPercentage << 4); + shards.percentage << 4); inspect->rtp.header = FLAG_EXTENSION; inspect->rtp.sequenceNumber = util::endian::big(lowseq + x); From 1c4435312e58fa29219fa25ae69f995ed1b7c424 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 24 May 2021 18:08:32 +0200 Subject: [PATCH 077/158] maintain aspect ratio when using software encoder --- sunshine/video.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index fd077eea..7d41dbb5 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -81,9 +81,16 @@ public: 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) { - 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; } @@ -98,7 +105,63 @@ public: 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 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( in_width, in_height, AV_PIX_FMT_BGR0, out_width, out_height, format, @@ -112,6 +175,9 @@ public: ~swdevice_t() override {} sws_t sws; + + // offset of input image to output frame in pixels + int offset; }; struct encoder_t { @@ -698,7 +764,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & if(!hwdevice->data) { auto device_tmp = std::make_unique(); - 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; } From 7aff15f7430310a58a9272c03f5e2fd28ec7281a Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 24 May 2021 20:58:05 +0200 Subject: [PATCH 078/158] Mainain aspect ratio when using nvenc and amdvce --- sunshine/platform/windows/display_vram.cpp | 68 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 9c44b6fb..b3fbde3f 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -1,3 +1,5 @@ +#include + #include #include @@ -12,8 +14,6 @@ using namespace std::literals; } namespace platf::dxgi { -constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f }; - using input_layout_t = util::safe_ptr>; using render_target_t = util::safe_ptr>; using shader_res_t = util::safe_ptr>; @@ -329,19 +329,32 @@ public: 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->VSSetShader(scene_vs.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->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->VSSetShader(convert_UV_vs.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->Draw(3, 0); + device_ctx_p->Flush(); return 0; } @@ -377,7 +390,7 @@ public: int init( std::shared_ptr 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) { HRESULT status; @@ -392,8 +405,20 @@ public: platf::hwdevice_t::img = &img; - this->out_width = out_width; - this->out_height = out_height; + float in_width = display->width; + 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); if(status) { @@ -514,6 +539,25 @@ public: 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->PSSetSamplers(0, 1, &sampler_linear); device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); @@ -619,17 +663,21 @@ public: img_d3d_t img; + // Clear nv12 render target to black + img_d3d_t back_img; + vs_t convert_UV_vs; ps_t convert_UV_ps; ps_t convert_Y_ps; ps_t scene_ps; vs_t scene_vs; + D3D11_VIEWPORT outY_view; + D3D11_VIEWPORT outUV_view; + D3D11_VIEWPORT cursor_view; bool cursor_visible; - float out_width, out_height; - device_ctx_t::pointer device_ctx_p; // 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(), (UINT)img->row_pitch }; + std::fill_n(dummy_data.get(), width * height, 0); D3D11_TEXTURE2D_DESC t {}; t.Width = width; @@ -808,7 +857,6 @@ std::shared_ptr display_vram_t::make_hwdevice(int width, int shared_from_this(), device.get(), device_ctx.get(), - this->width, this->height, width, height, pix_fmt); From 9f6b4ed93bec45282f3784cb90349626fb8da98a Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 25 May 2021 18:55:29 +0200 Subject: [PATCH 079/158] Work around weird mapping for audio channels --- sunshine/platform/common.h | 1 + sunshine/rtsp.cpp | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 672e8f5c..cac7db34 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -40,6 +40,7 @@ enum speaker_e { BACK_RIGHT, SIDE_LEFT, SIDE_RIGHT, + MAX_SPEAKERS, }; constexpr std::uint8_t map_stereo[] { diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 5b46fa76..2ef9e171 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -299,10 +299,27 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { ss << "sprop-parameter-sets=AAAAAU"sv << std::endl; } - for(auto &stream_config : audio::stream_configs) { + 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(stream_config.mapping, stream_config.channelCount, [&ss](std::uint8_t digit) { + std::for_each_n(mapping_p, stream_config.channelCount, [&ss](std::uint8_t digit) { ss << (char)(digit + '0'); }); From 113e7a52d4b78d697b9836bcdcda43a7380110ab Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 26 May 2021 17:34:25 +0200 Subject: [PATCH 080/158] advertise display modes --- .gitignore | 1 + assets/sunshine.conf | 22 +++++- sunshine/config.cpp | 184 ++++++++++++++++++++++++++++++++++++------- sunshine/config.h | 3 + sunshine/nvhttp.cpp | 29 ++++++- sunshine/process.cpp | 2 + 6 files changed, 212 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 2584b70d..ad68b535 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ cmake-build* *.swp *.kdev4 +.cache .idea \ No newline at end of file diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 78f31b2d..a26de0c5 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -37,6 +37,26 @@ # The file where current state of Sunshine is stored # 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 # ping_timeout = 2000 @@ -210,4 +230,4 @@ # To set the initial state of flags -0 and -1 to on, set the following flags: # flags = 01 # -# See: sunshine --help for all options under the header: flags +# See: sunshine --help for all options under the header: flags \ No newline at end of file diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 6d3b58bf..e24514b2 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -183,7 +184,22 @@ nvhttp_t nvhttp { CERTIFICATE_FILE, 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 { @@ -197,28 +213,84 @@ sunshine_t sunshine { 0 // flags }; -bool whitespace(char ch) { +bool endline(char ch) { + return ch == '\r' || ch == '\n'; +} + +bool space_tab(char ch) { return ch == ' ' || ch == '\t'; } -std::string to_string(const char *begin, const char *end) { - return { begin, (std::size_t)(end - begin) }; +bool whitespace(char ch) { + return space_tab(ch) || endline(ch); } -std::optional> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) { - begin = std::find_if(begin, end, std::not_fn(whitespace)); - end = std::find(begin, end, '#'); - end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base(); +std::string to_string(const char *begin, const char *end) { + std::string result; - auto eq = std::find(begin, end, '='); - if(eq == end || eq == begin) { - return std::nullopt; + 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; +} + +template +It skip_list(It skipper, It end) { + int stack = 1; + while(skipper != end && stack) { + if(*skipper == '[') { + ++stack; + } + if(*skipper == ']') { + --stack; + } + + ++skipper; } - auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base(); - auto begin_val = std::find_if(eq + 1, end, std::not_fn(whitespace)); + return skipper; +} - return std::pair { to_string(begin, end_name), to_string(begin_val, end) }; +std::pair< + std::string_view::const_iterator, + std::optional>> +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 parse_config(std::string_view file_content) { @@ -228,10 +300,14 @@ std::unordered_map parse_config(std::string_view file_ auto end = std::end(file_content); while(pos < end) { - auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; }); - auto var = parse_line(pos, newline); + // auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; }); + 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) { continue; } @@ -368,16 +444,68 @@ void double_between_f(std::unordered_map &vars, const } } +void list_string_f(std::unordered_map &vars, const std::string &name, std::vector &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 &vars, const std::string &name, std::vector &input) { + std::vector list; + list_string_f(vars, name, list); + + for(auto &el : list) { + input.emplace_back(util::from_view(el)); + } +} + void print_help(const char *name) { - std::cout << "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 - << " --help | print help"sv << std::endl - << std::endl - << " flags"sv << std::endl - << " -0 | Read PIN from stdin"sv << std::endl - << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl - << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv; + std::cout + << "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 + << " --help | print help"sv << std::endl + << std::endl + << " flags"sv << std::endl + << " -0 | Read PIN from stdin"sv << std::endl + << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl + << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv; } int apply_flags(const char *line) { @@ -432,6 +560,8 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "sunshine_name", nvhttp.sunshine_name); string_f(vars, "file_state", nvhttp.file_state); 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, "virtual_sink", audio.virtual_sink); @@ -536,7 +666,7 @@ int parse(int argc, char *argv[]) { config_file = line; } else { - auto var = parse_line(line, line_end); + TUPLE_EL(var, 1, parse_option(line, line_end)); if(!var) { print_help(*argv); return -1; diff --git a/sunshine/config.h b/sunshine/config.h index 1b62818e..37f59066 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace config { struct video_t { @@ -66,6 +67,8 @@ struct nvhttp_t { std::string file_state; std::string external_ip; + std::vector resolutions; + std::vector fps; }; struct input_t { diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index ebf6468a..255407cc 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -2,6 +2,8 @@ // Created by loki on 6/3/19. // +#define BOOST_BIND_GLOBAL_PLACEHOLDERS + #include "process.h" #include @@ -511,10 +513,35 @@ void serverinfo(std::shared_ptr::Response> res 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(); tree.put("root.PairStatus", pair_status); 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; diff --git a/sunshine/process.cpp b/sunshine/process.cpp index d9c6f71e..88532aca 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -2,6 +2,8 @@ // Created by loki on 12/14/19. // +#define BOOST_BIND_GLOBAL_PLACEHOLDERS + #include "process.h" #include From fd43ebc2bf52282e7bfb213d74181d6e4877c570 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 26 May 2021 21:33:28 +0200 Subject: [PATCH 081/158] add build status --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2d7d4d9d..9185eaee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Introduction Sunshine is a Gamestream host for Moonlight +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master) +[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases) + - [Building](README.md#building) - [Credits](README.md#credits) From fe5375f17b2ae435250b52301059908453ec726d Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 26 May 2021 21:40:23 +0200 Subject: [PATCH 082/158] update debian package version --- gen-deb.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen-deb.in b/gen-deb.in index ea4e671e..22112418 100755 --- a/gen-deb.in +++ b/gen-deb.in @@ -35,7 +35,7 @@ Package: sunshine Architecture: amd64 Maintainer: @loki Priority: optional -Version: 0.3.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 Description: Gamestream host for Moonlight EOF From 0ea6363172dce933f43b5f3058875f6e6d7fe4d9 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Fri, 28 May 2021 22:49:27 +0200 Subject: [PATCH 083/158] Username/Password Authentication for UI --- sunshine/config.cpp | 6 ++++- sunshine/config.h | 5 ++++- sunshine/confighttp.cpp | 29 ++++++++++++++++++++++-- sunshine/crypto.cpp | 21 ++++++++++++++++++ sunshine/crypto.h | 3 +++ sunshine/httpcommon.cpp | 49 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 4 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index d2a97d2e..4e08cf1d 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -191,7 +191,11 @@ input_t input { sunshine_t sunshine { 2, // min_log_level - 0 // flags + 0, // flags + "user_credentials.json"s,//User file + ""s,//Username + ""s,//Password + ""s//Password Salt }; bool whitespace(char ch) { diff --git a/sunshine/config.h b/sunshine/config.h index 1dbcabbc..0daf0146 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -85,8 +85,11 @@ enum flag_e : std::size_t { struct sunshine_t { int min_log_level; - std::bitset flags; + std::string credentials_file; + std::string username; + std::string password; + std::string salt; }; extern video_t video; diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index e8fe4184..a707d8de 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "config.h" @@ -49,16 +50,40 @@ enum class op_e REMOVE }; +void send_unauthorized(resp_https_t response, req_https_t request) +{ + auto address = request->remote_endpoint_address(); + BOOST_LOG(info) << '[' << address << "] -- denied"sv; + const SimpleWeb::CaseInsensitiveMultimap headers { + {"WWW-Authenticate",R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"} + }; + response->write(SimpleWeb::StatusCode::client_error_unauthorized,headers); +} + bool authenticate(resp_https_t response, req_https_t request) { -auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { BOOST_LOG(info) << '[' << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); return false; } - return true; + auto auth = request->header.find("authorization"); + if(auth == request->header.end() ){ + send_unauthorized(response,request); + return false; + } + std::string rawAuth = auth->second; + std::string authData = rawAuth.substr("Basic "sv.length()); + authData = SimpleWeb::Crypto::Base64::decode(authData); + int index = authData.find(':'); + std::string username = authData.substr(0,index); + std::string password = authData.substr(index + 1); + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + if(username == config::sunshine.username && hash == config::sunshine.password) return true; + send_unauthorized(response,request); + return false; } template diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index 398c8e09..b4a5ce80 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -4,6 +4,7 @@ #include #include "crypto.h" +#include namespace crypto { using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; @@ -338,4 +339,24 @@ bool verify256(const x509_t &x509, const std::string_view &data, const std::stri void md_ctx_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } + +std::string rand_string(std::size_t bytes) +{ + std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"; + std::string value = rand(bytes); + for (std::size_t i = 0; i != value.size(); ++i) { + value[i] = alphabet[value[i] % alphabet.length()]; + } + return value; +} + +std::string hash_hexstr(const std::string_view &plaintext) +{ + sha256_t hashBytes = crypto::hash(plaintext); + std::ostringstream hashStream; + hashStream << std::hex << std::setfill( '0' ); + std::for_each( hashBytes.cbegin(), hashBytes.cend(), [&]( int c ) { hashStream << std::setw( 2 ) << c; } ); + std::string hashString = hashStream.str(); + return hashString; +} } diff --git a/sunshine/crypto.h b/sunshine/crypto.h index faf1fc1f..f0781597 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "utility.h" @@ -35,6 +36,7 @@ using bio_t = util::safe_ptr; using pkey_t = util::safe_ptr; sha256_t hash(const std::string_view &plaintext); +std::string hash_hexstr(const std::string_view &plaintext); aes_t gen_aes_key(const std::array &salt, const std::string_view &pin); x509_t x509(const std::string_view &x); @@ -50,6 +52,7 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits); std::string_view signature(const x509_t &x); std::string rand(std::size_t bytes); +std::string rand_string(std::size_t bytes); class cert_chain_t { public: diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 53da503a..b8763ffe 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -27,8 +27,11 @@ namespace http { using namespace std::literals; namespace fs = std::filesystem; + namespace pt = boost::property_tree; int create_creds(const std::string &pkey, const std::string &cert); + int generate_user_creds(const std::string &file); + int reload_user_creds(const std::string &file); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); std::string unique_id; @@ -54,8 +57,54 @@ namespace http return; } } + if(!fs::exists(config::sunshine.credentials_file)){ + if(generate_user_creds(config::sunshine.credentials_file)){ + shutdown_event->raise(true); + return; + } + } + if(reload_user_creds(config::sunshine.credentials_file)){ + shutdown_event->raise(true); + return; + } } + int generate_user_creds(const std::string &file) + { + pt::ptree outputTree; + try { + std::string username = "sunshine"; + std::string plainPassword = crypto::rand_string(16); + std::string salt = crypto::rand_string(16); + outputTree.put("username","sunshine"); + outputTree.put("salt",salt); + outputTree.put("password",crypto::hash_hexstr(plainPassword + salt)); + BOOST_LOG(info) << "New credentials has been created"; + BOOST_LOG(info) << "Username: " << username; + BOOST_LOG(info) << "Password: " << plainPassword; + pt::write_json(file,outputTree); + } catch (std::exception &e){ + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; + } + + int reload_user_creds(const std::string &file) + { + pt::ptree inputTree; + try { + pt::read_json(file, inputTree); + config::sunshine.username = inputTree.get("username"); + config::sunshine.password = inputTree.get("password"); + config::sunshine.salt = inputTree.get("salt"); + } catch(std::exception &e){ + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; + } + int create_creds(const std::string &pkey, const std::string &cert) { fs::path pkey_path = pkey; From ff1ea1a63ec1dac081d75779279ad8b98e8a2f65 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 29 May 2021 16:25:37 +0200 Subject: [PATCH 084/158] Use VAAPI for hardware encoding on Linux --- sunshine/platform/common.h | 8 +- sunshine/platform/linux/display.cpp | 4 +- sunshine/platform/windows/display_base.cpp | 6 +- sunshine/stream.cpp | 1 + sunshine/video.cpp | 305 ++++++++++++++------- 5 files changed, 223 insertions(+), 101 deletions(-) diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index cac7db34..1be71e48 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -67,8 +67,8 @@ constexpr std::uint8_t map_surround71[] { }; } // namespace speaker -enum class dev_type_e { - none, +enum class mem_type_e { + system, dxgi, unknown }; @@ -155,7 +155,7 @@ struct sink_t { struct hwdevice_t { void *data {}; - platf::img_t *img {}; + void *img {}; virtual int convert(platf::img_t &img) { return -1; @@ -221,7 +221,7 @@ std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); std::unique_ptr audio_control(); -std::shared_ptr display(dev_type_e hwdevice_type); +std::shared_ptr display(mem_type_e hwdevice_type); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index b405ba12..438d7b9e 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -355,8 +355,8 @@ struct shm_attr_t : public x11_attr_t { } }; -std::shared_ptr display(platf::dev_type_e hwdevice_type) { - if(hwdevice_type != platf::dev_type_e::none) { +std::shared_ptr display(platf::mem_type_e hwdevice_type) { + if(hwdevice_type != platf::mem_type_e::system) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; return nullptr; } diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 58b93e62..0034be64 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -417,15 +417,15 @@ const char *format_str[] = { } // namespace platf::dxgi namespace platf { -std::shared_ptr display(dev_type_e hwdevice_type) { - if(hwdevice_type == dev_type_e::dxgi) { +std::shared_ptr display(mem_type_e hwdevice_type) { + if(hwdevice_type == mem_type_e::dxgi) { auto disp = std::make_shared(); if(!disp->init()) { return disp; } } - else if(hwdevice_type == dev_type_e::none) { + else if(hwdevice_type == mem_type_e::system) { auto disp = std::make_shared(); if(!disp->init()) { diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 9500a041..8a070be6 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -619,6 +619,7 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid // make sure moonlight recognizes the nalu code for IDR frames if(packet->flags & AV_PKT_FLAG_KEY) { + BOOST_LOG(debug) << "Sending IDR frame"sv; // TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix std::string_view frame_old = "\000\000\001e"sv; std::string_view frame_new = "\000\000\000\001e"sv; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 7d41dbb5..a27f7fa9 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -60,20 +60,21 @@ using buffer_t = util::safe_ptr; using sws_t = util::safe_ptr; using img_event_t = std::shared_ptr>>; -platf::dev_type_e map_dev_type(AVHWDeviceType type); +platf::mem_type_e map_dev_type(AVHWDeviceType type); platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); -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); +int sw_img_to_frame(const void *img, frame_t &frame); +int vaapi_img_to_frame(const void *img, frame_t &frame); +int dxgi_img_to_frame(const void *img, frame_t &frame); util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); +util::Either vaapi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); -util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); class swdevice_t : public platf::hwdevice_t { public: int convert(platf::img_t &img) override { - auto frame = (AVFrame *)data; + auto frame = (AVFrame *)this->img; av_frame_make_writable(frame); @@ -81,12 +82,19 @@ public: img.row_pitch, 0 }; - std::uint8_t *const data[] { - frame->data[0] + offset, - frame->data[1] + offset / 2, - frame->data[2] + offset / 2, - 0 - }; + + std::uint8_t *data[4]; + + data[0] = frame->data[0] + offset; + if(frame->format == AV_PIX_FMT_NV12) { + data[1] = frame->data[1] + offset; + data[2] = nullptr; + } + else { + data[1] = frame->data[1] + offset / 2; + data[2] = frame->data[2] + offset / 2; + data[3] = nullptr; + } int ret = sws_scale(sws.get(), (std::uint8_t *const *)&img.data, linesizes, 0, img.height, data, frame->linesize); if(ret <= 0) { @@ -108,14 +116,16 @@ public: /** * When preserving aspect ratio, ensure that padding is black */ - int prefill(AVFrame *frame, AVPixelFormat format) { + int prefill() { + auto frame = (frame_t::pointer)img; + auto width = frame->width; auto height = frame->height; sws_t sws { sws_getContext( width, height, AV_PIX_FMT_BGR0, - width, height, format, + width, height, (AVPixelFormat)frame->format, SWS_LANCZOS | SWS_ACCURATE_RND, nullptr, nullptr, nullptr) }; @@ -145,7 +155,27 @@ public: } int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format) { - if(prefill(frame, format)) { + // If the device used is hardware, yet the image resides on main memory + if(frame->hw_frames_ctx) { + if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { + return -1; + } + + img = av_frame_alloc(); + + auto sw_frame = (frame_t::pointer)img; + + sw_frame->width = frame->width; + sw_frame->height = frame->height; + sw_frame->format = format; + } + else { + img = frame; + } + + av_frame_get_buffer((frame_t::pointer)img, 0); + + if(prefill()) { return -1; } @@ -167,12 +197,15 @@ public: out_width, out_height, format, SWS_LANCZOS | SWS_ACCURATE_RND, nullptr, nullptr, nullptr)); - data = frame; return sws ? 0 : -1; } - ~swdevice_t() override {} + ~swdevice_t() override { + if(img) { + av_frame_unref((frame_t::pointer)img); + } + } sws_t sws; @@ -180,12 +213,20 @@ public: int offset; }; +enum flag_e { + DEFAULT = 0x00, + SYSTEM_MEMORY = 0x01, + H264_ONLY = 0x02, + LIMITED_GOP_SIZE = 0x04, +}; + struct encoder_t { std::string_view name; enum flag_e { PASSED, // Is supported REF_FRAMES_RESTRICT, // Set maximum reference frames REF_FRAMES_AUTOSELECT, // Allow encoder to select maximum reference frames (If !REF_FRAMES_RESTRICT --> REF_FRAMES_AUTOSELECT) + SLICE, // Allow frame to be partitioned into multiple slices DYNAMIC_RANGE, MAX_FLAGS }; @@ -228,10 +269,9 @@ struct encoder_t { } } hevc, h264; - bool system_memory; - bool hevc_mode; + int flags; - std::function img_to_frame; + std::function img_to_frame; std::function(platf::hwdevice_t *hwdevice)> make_hwdevice_ctx; }; @@ -316,23 +356,29 @@ static encoder_t nvenc { AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, { - { { "forced-idr"s, 1 }, + { + { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "preset"s, &config::video.nv.preset }, - { "rc"s, &config::video.nv.rc } }, + { "rc"s, &config::video.nv.rc }, + }, std::nullopt, std::nullopt, "hevc_nvenc"s, }, - { { { "forced-idr"s, 1 }, + { + { + { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "preset"s, &config::video.nv.preset }, { "rc"s, &config::video.nv.rc }, - { "coder"s, &config::video.nv.coder } }, - std::nullopt, std::make_optional({ "qp"s, &config::video.qp }), - "h264_nvenc"s }, - false, - true, + { "coder"s, &config::video.nv.coder }, + }, + std::nullopt, + std::make_optional({ "qp"s, &config::video.qp }), + "h264_nvenc"s, + }, + DEFAULT, dxgi_img_to_frame, dxgi_make_hwdevice_ctx @@ -345,27 +391,31 @@ static encoder_t amdvce { AV_PIX_FMT_D3D11, 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 }, { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, - { "rc"s, &config::video.amd.rc } }, + { "rc"s, &config::video.amd.rc }, + }, std::nullopt, std::make_optional({ "qp"s, &config::video.qp }), "hevc_amf"s, }, - { { + { + { { "usage"s, "ultralowlatency"s }, { "quality"s, &config::video.amd.quality }, { "rc"s, &config::video.amd.rc }, { "log_to_dbg"s, "1"s }, }, - std::nullopt, std::make_optional({ "qp"s, &config::video.qp }), - "h264_amf"s }, - false, - true, + std::nullopt, + std::make_optional({ "qp"s, &config::video.qp }), + "h264_amf"s, + }, + DEFAULT - dxgi_img_to_frame, + dxgi_img_to_frame, dxgi_make_hwdevice_ctx }; #endif @@ -376,7 +426,8 @@ static encoder_t software { AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_NONE, 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. // It also looks like gop_size isn't passed on to x265, so we have to set // 'keyint=-1' in the parameters ourselves. @@ -384,24 +435,68 @@ static encoder_t software { { "forced-idr"s, 1 }, { "x265-params"s, "info=0:keyint=-1"s }, { "preset"s, &config::video.sw.preset }, - { "tune"s, &config::video.sw.tune } }, - std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), - "libx265"s }, - { { { "preset"s, &config::video.sw.preset }, - { "tune"s, &config::video.sw.tune } }, - std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), - "libx264"s }, - true, - false, + { "tune"s, &config::video.sw.tune }, + }, + std::make_optional("crf"s, &config::video.crf), + std::make_optional("qp"s, &config::video.qp), + "libx265"s, + }, + { + { + { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune }, + }, + std::make_optional("crf"s, &config::video.crf), + std::make_optional("qp"s, &config::video.qp), + "libx264"s, + }, + H264_ONLY | SYSTEM_MEMORY, sw_img_to_frame, nullptr }; +#ifdef __linux__ +static encoder_t vaapi { + "vaapi"sv, + { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, + AV_HWDEVICE_TYPE_VAAPI, + AV_PIX_FMT_VAAPI, + AV_PIX_FMT_NV12, + AV_PIX_FMT_YUV420P10, + { + { + { "sei"s, 0 }, + { "idr_interval"s, std::numeric_limits::max() }, + }, + std::nullopt, + std::nullopt, + "hevc_vaapi"s, + }, + { + { + { "sei"s, 0 }, + { "idr_interval"s, std::numeric_limits::max() }, + // { "quality"s, 10 }, + }, + std::nullopt, + std::nullopt, + "h264_vaapi"s, + }, + LIMITED_GOP_SIZE | SYSTEM_MEMORY, + + vaapi_img_to_frame, + vaapi_make_hwdevice_ctx +}; +#endif + static std::vector encoders { #ifdef _WIN32 nvenc, amdvce, +#endif +#ifdef __linux__ + vaapi, #endif software }; @@ -626,8 +721,11 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->max_b_frames = 0; // Use an infinite GOP length since I-frames are generated on demand - ctx->gop_size = std::numeric_limits::max(); - ctx->keyint_min = ctx->gop_size; + ctx->gop_size = encoder.flags & LIMITED_GOP_SIZE ? + std::numeric_limits::max() : + std::numeric_limits::max(); + + ctx->keyint_min = std::numeric_limits::max(); if(config.numRefFrames == 0) { ctx->refs = video_format[encoder_t::REF_FRAMES_AUTOSELECT] ? 0 : 16; @@ -707,6 +805,10 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->slices = std::max(config.slicesPerFrame, config::video.min_threads); } + if(!video_format[encoder_t::SLICE]) { + ctx->slices = 1; + } + ctx->thread_type = FF_THREAD_SLICE; ctx->thread_count = ctx->slices; @@ -744,7 +846,15 @@ std::optional make_session(const encoder_t &encoder, const config_t & return std::nullopt; } - avcodec_open2(ctx.get(), codec, &options); + if(auto status = avcodec_open2(ctx.get(), codec, &options)) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; + BOOST_LOG(error) + << "Could not open codec ["sv + << video_format.name << "]: "sv + << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, status); + + return std::nullopt; + } frame_t frame { av_frame_alloc() }; frame->format = ctx->pix_fmt; @@ -755,9 +865,6 @@ std::optional make_session(const encoder_t &encoder, const config_t & if(hardware) { frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx); } - else /* software */ { - av_frame_get_buffer(frame.get(), 0); - } util::wrap_ptr device; @@ -778,7 +885,8 @@ std::optional make_session(const encoder_t &encoder, const config_t & return std::make_optional(session_t { std::move(ctx), std::move(frame), - std::move(device) }); + std::move(device), + }); } void encode_run( @@ -832,7 +940,9 @@ void encode_run( if(auto img = images->pop(delay)) { session->device->convert(*img); - encoder.img_to_frame(*session->device->img, session->frame); + if(encoder.img_to_frame(session->device->img, session->frame)) { + return; + } } else if(images->running()) { continue; @@ -1011,7 +1121,9 @@ encode_e encode_run_sync(std::vector> &synce } pos->img_tmp = nullptr; - encoder.img_to_frame(*pos->hwdevice->img, pos->session.frame); + if(encoder.img_to_frame(pos->hwdevice->img, pos->session.frame)) { + return encode_e::error; + } } if(encode(ctx->frame_nr++, pos->session.ctx, pos->session.frame, ctx->packets, ctx->channel_data)) { @@ -1053,8 +1165,7 @@ 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( @@ -1138,7 +1249,7 @@ void capture( void *channel_data) { idr_events->raise(std::make_pair(0, 1)); - if(encoders.front().system_memory) { + if(encoders.front().flags & SYSTEM_MEMORY) { capture_async(shutdown_event, packets, idr_events, config, channel_data); } else { @@ -1177,7 +1288,9 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e return false; } - encoder.img_to_frame(*hwdevice->img, session->frame); + if(encoder.img_to_frame(session->device->img, session->frame)) { + return false; + } session->frame->pict_type = AV_PICTURE_TYPE_I; @@ -1198,11 +1311,13 @@ bool validate_encoder(encoder_t &encoder) { }); 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.flags & H264_ONLY)); encoder.h264.capabilities.set(); encoder.hevc.capabilities.set(); + encoder.hevc[encoder_t::PASSED] = test_hevc; + // First, test encoder viability config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 }; config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 }; @@ -1218,6 +1333,7 @@ bool validate_encoder(encoder_t &encoder) { encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264; encoder.h264[encoder_t::PASSED] = true; + encoder.h264[encoder_t::SLICE] = validate_config(disp, encoder, config_max_ref_frames); if(test_hevc) { config_max_ref_frames.videoFormat = 1; config_autoselect.videoFormat = 1; @@ -1232,11 +1348,13 @@ bool validate_encoder(encoder_t &encoder) { encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; + + encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc || autoselect_hevc; } - encoder.hevc[encoder_t::PASSED] = test_hevc; std::vector> configs { - { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } } + { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } }, + { encoder_t::SLICE, { 1920, 1080, 60, 1000, 2, 1, 1, 0, 0 } }, }; for(auto &[flag, config] : configs) { auto h264 = config; @@ -1246,7 +1364,7 @@ bool validate_encoder(encoder_t &encoder) { hevc.videoFormat = 1; encoder.h264[flag] = validate_config(disp, encoder, h264); - if(test_hevc && encoder.hevc[encoder_t::PASSED]) { + if(encoder.hevc[encoder_t::PASSED]) { encoder.hevc[flag] = validate_config(disp, encoder, hevc); } } @@ -1313,29 +1431,6 @@ int init() { return 0; } -util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice) { - buffer_t ctx; - - int err; - if(hwdevice) { - ctx.reset(av_hwdevice_ctx_alloc(type)); - ((AVHWDeviceContext *)ctx.get())->hwctx = hwdevice; - - err = av_hwdevice_ctx_init(ctx.get()); - } - else { - AVBufferRef *ref {}; - err = av_hwdevice_ctx_create(&ref, type, nullptr, nullptr, 0); - ctx.reset(ref); - } - - if(err < 0) { - return err; - } - - return ctx; -} - int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get()) }; @@ -1344,7 +1439,7 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { frame_ctx->sw_format = format; frame_ctx->height = ctx->height; frame_ctx->width = ctx->width; - frame_ctx->initial_pool_size = 0; + frame_ctx->initial_pool_size = 20; if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) { return err; @@ -1355,7 +1450,32 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { return 0; } -void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {} +int sw_img_to_frame(const void *img, frame_t &frame) { + return 0; +} + +int vaapi_img_to_frame(const void *img, frame_t &frame) { + auto status = av_hwframe_transfer_data(frame.get(), (frame_t::pointer)img, 0); + if(status < 0) { + char string[AV_ERROR_MAX_STRING_SIZE]; + BOOST_LOG(error) << "Failed to transfer image data to hardware frame: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); + return -1; + } + + return 0; +} + +util::Either vaapi_make_hwdevice_ctx(platf::hwdevice_t *) { + buffer_t hw_device_buf; + auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD129", nullptr, 0); + if(status < 0) { + char string[AV_ERROR_MAX_STRING_SIZE]; + BOOST_LOG(error) << "Failed to create a VAAPI device: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); + return -1; + } + + return hw_device_buf; +} #ifdef _WIN32 } @@ -1445,17 +1565,18 @@ int start_capture_sync(capture_thread_sync_ctx_t &ctx) { } void end_capture_sync(capture_thread_sync_ctx_t &ctx) {} -platf::dev_type_e map_dev_type(AVHWDeviceType type) { +platf::mem_type_e map_dev_type(AVHWDeviceType type) { switch(type) { case AV_HWDEVICE_TYPE_D3D11VA: - return platf::dev_type_e::dxgi; + return platf::mem_type_e::dxgi; case AV_PICTURE_TYPE_NONE: - return platf::dev_type_e::none; + case AV_HWDEVICE_TYPE_VAAPI: + return platf::mem_type_e::system; default: - return platf::dev_type_e::unknown; + return platf::mem_type_e::unknown; } - return platf::dev_type_e::unknown; + return platf::mem_type_e::unknown; } platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt) { From 4835366a0ca3e6376131e10819e4cf90b26c471c Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 22:26:17 +0200 Subject: [PATCH 085/158] Fixed Build --- sunshine/config.cpp | 1 + sunshine/config.h | 1 + sunshine/nvhttp.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index f28e5f76..a990ccf6 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include diff --git a/sunshine/config.h b/sunshine/config.h index 957544c9..55b9296d 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -105,4 +105,5 @@ extern sunshine_t sunshine; int parse(int argc, char *argv[]); std::unordered_map parse_config(std::string_view file_content); +} #endif diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 4601a93a..b7d90132 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -857,4 +857,5 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); +} } \ No newline at end of file From ffb80c5fc3b1cc52c41c24ae38f4f16dc992ba35 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 22:29:10 +0200 Subject: [PATCH 086/158] Fix Format --- sunshine/config.cpp | 12 +- sunshine/config.h | 4 +- sunshine/confighttp.cpp | 254 +++++++++++++------------------ sunshine/confighttp.h | 2 +- sunshine/crypto.cpp | 18 +-- sunshine/crypto.h | 3 +- sunshine/httpcommon.cpp | 320 +++++++++++++++++++--------------------- sunshine/httpcommon.h | 16 +- sunshine/main.cpp | 6 +- sunshine/nvhttp.cpp | 12 +- sunshine/nvhttp.h | 11 +- 11 files changed, 298 insertions(+), 360 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index a990ccf6..f2cf391e 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -209,12 +209,12 @@ input_t input { }; sunshine_t sunshine { - 2, // min_log_level - 0, // flags - "user_credentials.json"s,//User file - ""s,//Username - ""s,//Password - ""s//Password Salt + 2, // min_log_level + 0, // flags + "user_credentials.json"s, //User file + ""s, //Username + ""s, //Password + ""s //Password Salt }; bool endline(char ch) { diff --git a/sunshine/config.h b/sunshine/config.h index 55b9296d..99f50408 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include #include namespace config { @@ -105,5 +105,5 @@ extern sunshine_t sunshine; int parse(int argc, char *argv[]); std::unordered_map parse_config(std::string_view file_content); -} +} // namespace config #endif diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index a707d8de..fb20a8dd 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -6,32 +6,31 @@ #include +#include #include #include -#include #include -#include #include +#include #include #include "config.h" -#include "utility.h" -#include "rtsp.h" -#include "crypto.h" #include "confighttp.h" -#include "platform/common.h" +#include "crypto.h" #include "httpcommon.h" +#include "main.h" #include "network.h" #include "nvhttp.h" +#include "platform/common.h" +#include "rtsp.h" +#include "utility.h" #include "uuid.h" -#include "main.h" std::string read_file(std::string path); -namespace confighttp -{ +namespace confighttp { using namespace std::literals; constexpr auto PORT_HTTP = 47990; @@ -40,28 +39,25 @@ namespace pt = boost::property_tree; using https_server_t = SimpleWeb::Server; -using args_t = SimpleWeb::CaseInsensitiveMultimap; +using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; -using req_https_t = std::shared_ptr::Request>; +using req_https_t = std::shared_ptr::Request>; -enum class op_e -{ +enum class op_e { ADD, REMOVE }; -void send_unauthorized(resp_https_t response, req_https_t request) -{ +void send_unauthorized(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); BOOST_LOG(info) << '[' << address << "] -- denied"sv; const SimpleWeb::CaseInsensitiveMultimap headers { - {"WWW-Authenticate",R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"} + { "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" } }; - response->write(SimpleWeb::StatusCode::client_error_unauthorized,headers); + response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers); } -bool authenticate(resp_https_t response, req_https_t request) -{ +bool authenticate(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { @@ -70,25 +66,24 @@ bool authenticate(resp_https_t response, req_https_t request) return false; } auto auth = request->header.find("authorization"); - if(auth == request->header.end() ){ - send_unauthorized(response,request); + if(auth == request->header.end()) { + send_unauthorized(response, request); return false; } - std::string rawAuth = auth->second; + std::string rawAuth = auth->second; std::string authData = rawAuth.substr("Basic "sv.length()); - authData = SimpleWeb::Crypto::Base64::decode(authData); - int index = authData.find(':'); - std::string username = authData.substr(0,index); + authData = SimpleWeb::Crypto::Base64::decode(authData); + int index = authData.find(':'); + std::string username = authData.substr(0, index); std::string password = authData.substr(index + 1); - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); if(username == config::sunshine.username && hash == config::sunshine.password) return true; - send_unauthorized(response,request); + send_unauthorized(response, request); return false; } -template -void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ +template +void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { pt::ptree tree; tree.put("root..status_code", 404); @@ -101,60 +96,53 @@ void not_found(std::shared_ptr::Response> resp << data.str(); } -void getIndexPage(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +void getIndexPage(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); } -template -void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); } -template -void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); } -template -void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); } -template -void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); } -void getApps(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void getApps(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } -void saveApp(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void saveApp(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -165,34 +153,28 @@ void saveApp(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree inputTree, fileTree; - try - { + try { //TODO: Input Validation pt::read_json(ss, inputTree); pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); + int index = inputTree.get("index"); BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); - if (inputTree.get_child("prep-cmd").empty()) + if(inputTree.get_child("prep-cmd").empty()) inputTree.erase("prep-cmd"); inputTree.erase("index"); - if (index == -1) - { + if(index == -1) { apps_node.push_back(std::make_pair("", inputTree)); } - else - { + else { //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick pt::ptree newApps; int i = 0; - for (const auto &kv : apps_node) - { - if (i == index) - { + for(const auto &kv : apps_node) { + if(i == index) { newApps.push_back(std::make_pair("", inputTree)); } - else - { + else { newApps.push_back(std::make_pair("", kv.second)); } i++; @@ -204,8 +186,7 @@ void saveApp(resp_https_t response, req_https_t request) outputTree.put("status", "true"); proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid Input JSON"); @@ -213,9 +194,8 @@ void saveApp(resp_https_t response, req_https_t request) } } -void deleteApp(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void deleteApp(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -224,27 +204,22 @@ void deleteApp(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree fileTree; - try - { + try { pt::read_json(config::stream.file_apps, fileTree); auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); + int index = stoi(request->path_match[1]); BOOST_LOG(info) << index; - if (index <= 0) - { + if(index <= 0) { outputTree.put("status", "false"); outputTree.put("error", "Invalid Index"); return; } - else - { + else { //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; - for (const auto &kv : apps_node) - { - if (i != index) - { + for(const auto &kv : apps_node) { + if(i != index) { newApps.push_back(std::make_pair("", kv.second)); } i++; @@ -256,8 +231,7 @@ void deleteApp(resp_https_t response, req_https_t request) outputTree.put("status", "true"); proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid File JSON"); @@ -265,9 +239,8 @@ void deleteApp(resp_https_t response, req_https_t request) } } -void getConfig(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void getConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -275,10 +248,9 @@ void getConfig(resp_https_t response, req_https_t request) pt::write_json(data, outputTree); response->write(data.str()); }); - try - { - outputTree.put("status","true"); - outputTree.put("platform",SUNSHINE_PLATFORM); + try { + outputTree.put("status", "true"); + outputTree.put("platform", SUNSHINE_PLATFORM); const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; std::ifstream in { config_file }; @@ -289,15 +261,13 @@ void getConfig(resp_https_t response, req_https_t request) auto vars = config::parse_config(std::string { // Quick and dirty std::istreambuf_iterator(in), - std::istreambuf_iterator() - }); + std::istreambuf_iterator() }); - for(auto &[name,value] : vars) { + for(auto &[name, value] : vars) { outputTree.put(std::move(name), std::move(value)); } } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid File JSON"); @@ -305,9 +275,8 @@ void getConfig(resp_https_t response, req_https_t request) } } -void saveConfig(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void saveConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -319,20 +288,17 @@ void saveConfig(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree inputTree; - try - { + try { //TODO: Input Validation pt::read_json(ss, inputTree); - for (const auto &kv : inputTree) - { + for(const auto &kv : inputTree) { std::string value = inputTree.get(kv.first); - if(value.length() == 0 || value.compare("null") == 0)continue; + if(value.length() == 0 || value.compare("null") == 0) continue; configStream << kv.first << " = " << value << std::endl; } - http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf",configStream.str()); + http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", configStream.str()); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", e.what()); @@ -340,50 +306,44 @@ void saveConfig(resp_https_t response, req_https_t request) } } -void start(std::shared_ptr shutdown_event) -{ +void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - https_server_t http_server{ctx, 0}; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/config$"]["GET"] = getConfig; - http_server.resource["^/api/config$"]["POST"] = saveConfig; + https_server_t http_server { ctx, 0 }; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/config$"]["GET"] = getConfig; + http_server.resource["^/api/config$"]["POST"] = saveConfig; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; - try - { + try { http_server.bind(); BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; } - catch (boost::system::system_error &err) - { + catch(boost::system::system_error &err) { BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); shutdown_event->raise(true); return; } auto accept_and_run = [&](auto *http_server) { - try - { + try { 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 - if (shutdown_event->peek()) - { + if(shutdown_event->peek()) { return; } @@ -392,7 +352,7 @@ void start(std::shared_ptr shutdown_event) return; } }; - std::thread tcp{accept_and_run, &http_server}; + std::thread tcp { accept_and_run, &http_server }; // Wait for any event shutdown_event->view(); @@ -401,18 +361,16 @@ void start(std::shared_ptr shutdown_event) tcp.join(); } -} +} // namespace confighttp -std::string read_file(std::string path) -{ +std::string read_file(std::string path) { std::ifstream in(path); std::string input; std::string base64_cert; //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { + while(!in.eof()) { std::getline(in, input); base64_cert += input + '\n'; } diff --git a/sunshine/confighttp.h b/sunshine/confighttp.h index d3b9e85e..edd7181e 100644 --- a/sunshine/confighttp.h +++ b/sunshine/confighttp.h @@ -14,7 +14,7 @@ namespace confighttp { - void start(std::shared_ptr shutdown_event); +void start(std::shared_ptr shutdown_event); } #endif //SUNSHINE_CONFIGHTTP_H diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index a49af9da..58053a03 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -6,7 +6,7 @@ #include #include namespace crypto { -using big_num_t = util::safe_ptr; +using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; using asn1_string_t = util::safe_ptr; @@ -340,23 +340,21 @@ void md_ctx_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } -std::string rand_string(std::size_t bytes) -{ +std::string rand_string(std::size_t bytes) { std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"; - std::string value = rand(bytes); - for (std::size_t i = 0; i != value.size(); ++i) { + std::string value = rand(bytes); + for(std::size_t i = 0; i != value.size(); ++i) { value[i] = alphabet[value[i] % alphabet.length()]; } return value; } -std::string hash_hexstr(const std::string_view &plaintext) -{ +std::string hash_hexstr(const std::string_view &plaintext) { sha256_t hashBytes = crypto::hash(plaintext); std::ostringstream hashStream; - hashStream << std::hex << std::setfill( '0' ); - std::for_each( hashBytes.cbegin(), hashBytes.cend(), [&]( int c ) { hashStream << std::setw( 2 ) << c; } ); + hashStream << std::hex << std::setfill('0'); + std::for_each(hashBytes.cbegin(), hashBytes.cend(), [&](int c) { hashStream << std::setw(2) << c; }); std::string hashString = hashStream.str(); return hashString; } -} \ No newline at end of file +} // namespace crypto \ No newline at end of file diff --git a/sunshine/crypto.h b/sunshine/crypto.h index ed6373a9..369a417c 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -7,12 +7,11 @@ #include #include +#include #include #include #include #include -#include -#include #include "utility.h" diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index b8763ffe..9dda7a00 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -2,9 +2,9 @@ #include +#include #include #include -#include #include @@ -13,187 +13,171 @@ #include #include "config.h" -#include "utility.h" -#include "rtsp.h" #include "crypto.h" +#include "httpcommon.h" +#include "main.h" +#include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "network.h" +#include "rtsp.h" +#include "utility.h" #include "uuid.h" -#include "main.h" -#include "httpcommon.h" -namespace http -{ - using namespace std::literals; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; +namespace http { +using namespace std::literals; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; - int create_creds(const std::string &pkey, const std::string &cert); - int generate_user_creds(const std::string &file); - int reload_user_creds(const std::string &file); - std::string read_file(const char *path); - int write_file(const char *path, const std::string_view &contents); - std::string unique_id; - net::net_e origin_pin_allowed; - - void init(std::shared_ptr shutdown_event) - { - bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); - if (clean_slate) - { - unique_id = util::uuid_t::generate().string(); - auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; - config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); - config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); - } +int create_creds(const std::string &pkey, const std::string &cert); +int generate_user_creds(const std::string &file); +int reload_user_creds(const std::string &file); +std::string read_file(const char *path); +int write_file(const char *path, const std::string_view &contents); +std::string unique_id; +net::net_e origin_pin_allowed; - if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) - { - if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) - { - shutdown_event->raise(true); - return; - } - } - if(!fs::exists(config::sunshine.credentials_file)){ - if(generate_user_creds(config::sunshine.credentials_file)){ - shutdown_event->raise(true); - return; - } - } - if(reload_user_creds(config::sunshine.credentials_file)){ +void init(std::shared_ptr shutdown_event) { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); + if(clean_slate) { + unique_id = util::uuid_t::generate().string(); + auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; + config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); + config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); + } + + if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { + if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { shutdown_event->raise(true); return; } } - - int generate_user_creds(const std::string &file) - { - pt::ptree outputTree; - try { - std::string username = "sunshine"; - std::string plainPassword = crypto::rand_string(16); - std::string salt = crypto::rand_string(16); - outputTree.put("username","sunshine"); - outputTree.put("salt",salt); - outputTree.put("password",crypto::hash_hexstr(plainPassword + salt)); - BOOST_LOG(info) << "New credentials has been created"; - BOOST_LOG(info) << "Username: " << username; - BOOST_LOG(info) << "Password: " << plainPassword; - pt::write_json(file,outputTree); - } catch (std::exception &e){ - BOOST_LOG(fatal) << e.what(); - return 1; + if(!fs::exists(config::sunshine.credentials_file)) { + if(generate_user_creds(config::sunshine.credentials_file)) { + shutdown_event->raise(true); + return; } - return 0; + } + if(reload_user_creds(config::sunshine.credentials_file)) { + shutdown_event->raise(true); + return; + } +} + +int generate_user_creds(const std::string &file) { + pt::ptree outputTree; + try { + std::string username = "sunshine"; + std::string plainPassword = crypto::rand_string(16); + std::string salt = crypto::rand_string(16); + outputTree.put("username", "sunshine"); + outputTree.put("salt", salt); + outputTree.put("password", crypto::hash_hexstr(plainPassword + salt)); + BOOST_LOG(info) << "New credentials has been created"; + BOOST_LOG(info) << "Username: " << username; + BOOST_LOG(info) << "Password: " << plainPassword; + pt::write_json(file, outputTree); + } + catch(std::exception &e) { + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; +} + +int reload_user_creds(const std::string &file) { + pt::ptree inputTree; + try { + pt::read_json(file, inputTree); + config::sunshine.username = inputTree.get("username"); + config::sunshine.password = inputTree.get("password"); + config::sunshine.salt = inputTree.get("salt"); + } + catch(std::exception &e) { + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; +} + +int create_creds(const std::string &pkey, const std::string &cert) { + fs::path pkey_path = pkey; + fs::path cert_path = cert; + + auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); + + auto pkey_dir = pkey_path; + auto cert_dir = cert_path; + pkey_dir.remove_filename(); + cert_dir.remove_filename(); + + std::error_code err_code {}; + fs::create_directories(pkey_dir, err_code); + if(err_code) { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); + return -1; } - int reload_user_creds(const std::string &file) - { - pt::ptree inputTree; - try { - pt::read_json(file, inputTree); - config::sunshine.username = inputTree.get("username"); - config::sunshine.password = inputTree.get("password"); - config::sunshine.salt = inputTree.get("salt"); - } catch(std::exception &e){ - BOOST_LOG(fatal) << e.what(); - return 1; - } - return 0; - } - - int create_creds(const std::string &pkey, const std::string &cert) - { - fs::path pkey_path = pkey; - fs::path cert_path = cert; - - auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); - - auto pkey_dir = pkey_path; - auto cert_dir = cert_path; - pkey_dir.remove_filename(); - cert_dir.remove_filename(); - - std::error_code err_code{}; - fs::create_directories(pkey_dir, err_code); - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); - return -1; - } - - fs::create_directories(cert_dir, err_code); - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); - return -1; - } - - if (write_file(pkey.c_str(), creds.pkey)) - { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; - return -1; - } - - if (write_file(cert.c_str(), creds.x509)) - { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; - return -1; - } - - fs::permissions(pkey_path, - fs::perms::owner_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); - return -1; - } - - fs::permissions(cert_path, - fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); - return -1; - } - - return 0; - } - int write_file(const char *path, const std::string_view &contents) - { - std::ofstream out(path); - - if (!out.is_open()) - { - return -1; - } - - out << contents; - - return 0; + fs::create_directories(cert_dir, err_code); + if(err_code) { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); + return -1; } - std::string read_file(const char *path) - { - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; + if(write_file(pkey.c_str(), creds.pkey)) { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; + return -1; } -} \ No newline at end of file + + if(write_file(cert.c_str(), creds.x509)) { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; + return -1; + } + + fs::permissions(pkey_path, + fs::perms::owner_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if(err_code) { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); + return -1; + } + + fs::permissions(cert_path, + fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if(err_code) { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); + return -1; + } + + return 0; +} +int write_file(const char *path, const std::string_view &contents) { + std::ofstream out(path); + + if(!out.is_open()) { + return -1; + } + + out << contents; + + return 0; +} + +std::string read_file(const char *path) { + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while(!in.eof()) { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; +} +} // namespace http \ No newline at end of file diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 40cc0a66..4997b2b1 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -1,9 +1,9 @@ #include "network.h" -namespace http{ - void init(std::shared_ptr shutdown_event); - int create_creds(const std::string &pkey, const std::string &cert); - std::string read_file(const char *path); - int write_file(const char *path, const std::string_view &contents); - extern std::string unique_id; - extern net::net_e origin_pin_allowed; -} \ No newline at end of file +namespace http { +void init(std::shared_ptr shutdown_event); +int create_creds(const std::string &pkey, const std::string &cert); +std::string read_file(const char *path); +int write_file(const char *path, const std::string_view &contents); +extern std::string unique_id; +extern net::net_e origin_pin_allowed; +} // namespace http \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index fd029a72..1d1693ea 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -15,9 +15,9 @@ #include #include "config.h" -#include "nvhttp.h" -#include "httpcommon.h" #include "confighttp.h" +#include "httpcommon.h" +#include "nvhttp.h" #include "rtsp.h" #include "thread_pool.h" #include "video.h" @@ -126,7 +126,7 @@ int main(int argc, char *argv[]) { }); proc::refresh(config::stream.file_apps); - + auto deinit_guard = platf::init(); if(!deinit_guard) { return 4; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index b7d90132..e1dc5c0c 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -20,6 +20,7 @@ #include "config.h" #include "crypto.h" +#include "httpcommon.h" #include "main.h" #include "network.h" #include "nvhttp.h" @@ -27,9 +28,6 @@ #include "rtsp.h" #include "utility.h" #include "uuid.h" -#include "main.h" -#include "httpcommon.h" - namespace nvhttp { @@ -133,7 +131,7 @@ void load_state() { return; } - http::unique_id = root.get("root.uniqueid"); + http::unique_id = root.get("root.uniqueid"); auto device_nodes = root.get_child("root.devices"); for(auto &[_, device_node] : device_nodes) { @@ -731,14 +729,14 @@ void appasset(resp_https_t response, req_https_t request) { } void start(std::shared_ptr shutdown_event) { - + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; if(!clean_slate) { load_state(); } - conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); + conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); conf_intern.servercert = http::read_file(config::nvhttp.cert.c_str()); auto ctx = std::make_shared(boost::asio::ssl::context::tls); @@ -858,4 +856,4 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); } -} \ No newline at end of file +} // namespace nvhttp \ No newline at end of file diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index bfa3e119..18aaa969 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -5,11 +5,11 @@ #ifndef SUNSHINE_NVHTTP_H #define SUNSHINE_NVHTTP_H -#include -#include +#include "thread_safe.h" #include #include -#include "thread_safe.h" +#include +#include #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" @@ -17,7 +17,8 @@ namespace nvhttp { void start(std::shared_ptr shutdown_event); -template void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); -} +template +void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); +} // namespace nvhttp #endif //SUNSHINE_NVHTTP_H From 8961b0462ed7cd6e97d4dca120e7f6782e890ef0 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 23:05:37 +0200 Subject: [PATCH 087/158] Fix Incldue Order --- sunshine/httpcommon.cpp | 12 ++++++------ sunshine/main.cpp | 4 ++-- sunshine/nvhttp.cpp | 5 +++-- sunshine/nvhttp.h | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 9dda7a00..b6f7ac0a 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -2,9 +2,9 @@ #include -#include #include #include +#include #include @@ -13,15 +13,15 @@ #include #include "config.h" +#include "utility.h" +#include "rtsp.h" #include "crypto.h" -#include "httpcommon.h" -#include "main.h" -#include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "rtsp.h" -#include "utility.h" +#include "network.h" #include "uuid.h" +#include "main.h" +#include "httpcommon.h" namespace http { using namespace std::literals; diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 1d1693ea..70301297 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -15,9 +15,9 @@ #include #include "config.h" -#include "confighttp.h" -#include "httpcommon.h" #include "nvhttp.h" +#include "httpcommon.h" +#include "confighttp.h" #include "rtsp.h" #include "thread_pool.h" #include "video.h" diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index e1dc5c0c..1793b060 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -18,9 +18,9 @@ #include #include + #include "config.h" #include "crypto.h" -#include "httpcommon.h" #include "main.h" #include "network.h" #include "nvhttp.h" @@ -28,7 +28,8 @@ #include "rtsp.h" #include "utility.h" #include "uuid.h" - +#include "main.h" +#include "httpcommon.h" namespace nvhttp { using namespace std::literals; diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index 18aaa969..db995fc8 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -5,11 +5,11 @@ #ifndef SUNSHINE_NVHTTP_H #define SUNSHINE_NVHTTP_H -#include "thread_safe.h" -#include -#include #include #include +#include +#include +#include "thread_safe.h" #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" From ec9481392a7614a322f8f5e3b751e709c3728b0e Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 23:35:44 +0200 Subject: [PATCH 088/158] Password Change UI --- assets/web/config.html | 22 ++++----- assets/web/header.html | 3 ++ assets/web/password.html | 97 ++++++++++++++++++++++++++++++++++++++++ sunshine/confighttp.cpp | 64 ++++++++++++++++++++++++-- sunshine/httpcommon.h | 1 + 5 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 assets/web/password.html diff --git a/assets/web/config.html b/assets/web/config.html index fa9612d9..b2b642cb 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -1,5 +1,5 @@
-

Configuration

+

Configuration

\ No newline at end of file diff --git a/assets/web/password.html b/assets/web/password.html new file mode 100644 index 00000000..3e3f2140 --- /dev/null +++ b/assets/web/password.html @@ -0,0 +1,97 @@ +
+

Password Change

+
+
+
+

Current Credentials

+
+ + +
 
+
+
+ + +
+
+
+

New Credentials

+
+ + +
If not specified, the username will not change +
+
+
+ + +
+
+ + +
+
+
+
Error: {{error}}
+
Success! This page will reload soon, your browser will ask you for the new credentials
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index fb20a8dd..e76b8ca4 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -135,6 +135,14 @@ void getConfigPage(std::shared_ptr::Response> response->write(header + content); } +template +void getPasswordPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "password.html"); + response->write(header + content); +} + void getApps(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); @@ -306,6 +314,54 @@ void saveConfig(resp_https_t response, req_https_t request) { } } +void savePassword(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; + std::stringstream ss; + std::stringstream configStream; + ss << request->content.rdbuf(); + + pt::ptree inputTree,outputTree,fileTree; + + auto g = util::fail_guard([&]() { + std::ostringstream data; + pt::write_json(data, outputTree); + response->write(data.str()); + }); + + try { + //TODO: Input Validation + pt::read_json(ss, inputTree); + std::string username = inputTree.get("currentUsername"); + std::string newUsername = inputTree.get("newUsername"); + std::string password = inputTree.get("currentPassword"); + std::string newPassword = inputTree.get("newPassword"); + std::string confirmPassword = inputTree.get("confirmNewPassword"); + if(newUsername.length() == 0) newUsername = username; + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + if(username == config::sunshine.username && hash == config::sunshine.password){ + if(newPassword != confirmPassword){ + outputTree.put("status",false); + outputTree.put("error","Password Mismatch"); + } + fileTree.put("username",newUsername); + fileTree.put("password",crypto::hash_hexstr(newPassword + config::sunshine.salt)); + fileTree.put("salt",config::sunshine.salt); + pt::write_json(config::sunshine.credentials_file,fileTree); + http::reload_user_creds(config::sunshine.credentials_file); + outputTree.put("status",true); + } else { + outputTree.put("status",false); + outputTree.put("error","Invalid Current Credentials"); + } + } + catch(std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", false); + outputTree.put("error", e.what()); + return; + } +} + void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); @@ -315,14 +371,16 @@ void start(std::shared_ptr shutdown_event) { http_server.resource["^/$"]["GET"] = getIndexPage; http_server.resource["^/pin$"]["GET"] = getPinPage; http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/password$"]["GET"] = getPasswordPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; http_server.resource["^/api/apps$"]["GET"] = getApps; http_server.resource["^/api/apps$"]["POST"] = saveApp; http_server.resource["^/api/config$"]["GET"] = getConfig; http_server.resource["^/api/config$"]["POST"] = saveConfig; + http_server.resource["^/api/password$"]["POST"] = savePassword; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; http_server.config.reuse_address = true; http_server.config.address = "0.0.0.0"s; http_server.config.port = PORT_HTTP; diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 4997b2b1..7c441a7c 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -4,6 +4,7 @@ void init(std::shared_ptr shutdown_event); int create_creds(const std::string &pkey, const std::string &cert); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); +int reload_user_creds(const std::string &file); extern std::string unique_id; extern net::net_e origin_pin_allowed; } // namespace http \ No newline at end of file From bb251d5046ac47764b06366324102dcd0f8fa2c7 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 14:58:38 +0200 Subject: [PATCH 089/158] Code Cleanup --- sunshine/confighttp.cpp | 90 +++++++++++++++++++++++------------------ sunshine/httpcommon.cpp | 2 +- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index e76b8ca4..6546b31d 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -76,14 +76,14 @@ bool authenticate(resp_https_t response, req_https_t request) { int index = authData.find(':'); std::string username = authData.substr(0, index); std::string password = authData.substr(index + 1); - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + std::string hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(username == config::sunshine.username && hash == config::sunshine.password) return true; + send_unauthorized(response, request); return false; } -template -void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void not_found(resp_https_t response, req_https_t request) { pt::ptree tree; tree.put("root..status_code", 404); @@ -98,46 +98,47 @@ void not_found(std::shared_ptr::Response> resp void getIndexPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); } -template -void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getPinPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); } -template -void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getAppsPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); } -template -void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getClientsPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); } -template -void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getConfigPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); } -template -void getPasswordPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getPasswordPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "password.html"); response->write(header + content); @@ -145,12 +146,14 @@ void getPasswordPage(std::shared_ptr::Response void getApps(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } void saveApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -170,12 +173,13 @@ void saveApp(resp_https_t response, req_https_t request) { BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); if(inputTree.get_child("prep-cmd").empty()) inputTree.erase("prep-cmd"); + inputTree.erase("index"); if(index == -1) { apps_node.push_back(std::make_pair("", inputTree)); } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for(const auto &kv : apps_node) { @@ -204,6 +208,7 @@ void saveApp(resp_https_t response, req_https_t request) { void deleteApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -249,6 +254,7 @@ void deleteApp(resp_https_t response, req_https_t request) { void getConfig(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -285,6 +291,7 @@ void getConfig(resp_https_t response, req_https_t request) { void saveConfig(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -302,6 +309,7 @@ void saveConfig(resp_https_t response, req_https_t request) { for(const auto &kv : inputTree) { std::string value = inputTree.get(kv.first); if(value.length() == 0 || value.compare("null") == 0) continue; + configStream << kv.first << " = " << value << std::endl; } http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", configStream.str()); @@ -316,6 +324,7 @@ void saveConfig(resp_https_t response, req_https_t request) { void savePassword(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -337,14 +346,15 @@ void savePassword(resp_https_t response, req_https_t request) { std::string newPassword = inputTree.get("newPassword"); std::string confirmPassword = inputTree.get("confirmNewPassword"); if(newUsername.length() == 0) newUsername = username; - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + + std::string hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(username == config::sunshine.username && hash == config::sunshine.password){ if(newPassword != confirmPassword){ outputTree.put("status",false); outputTree.put("error","Password Mismatch"); } fileTree.put("username",newUsername); - fileTree.put("password",crypto::hash_hexstr(newPassword + config::sunshine.salt)); + fileTree.put("password",util::hex(crypto::hash(newPassword + config::sunshine.salt)).to_string()); fileTree.put("salt",config::sunshine.salt); pt::write_json(config::sunshine.credentials_file,fileTree); http::reload_user_creds(config::sunshine.credentials_file); @@ -366,27 +376,27 @@ void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - https_server_t http_server { ctx, 0 }; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/password$"]["GET"] = getPasswordPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/config$"]["GET"] = getConfig; - http_server.resource["^/api/config$"]["POST"] = saveConfig; - http_server.resource["^/api/password$"]["POST"] = savePassword; - http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + https_server_t server { ctx, 0 }; + server.default_resource = not_found; + server.resource["^/$"]["GET"] = getIndexPage; + server.resource["^/pin$"]["GET"] = getPinPage; + server.resource["^/apps$"]["GET"] = getAppsPage; + server.resource["^/clients$"]["GET"] = getClientsPage; + server.resource["^/config$"]["GET"] = getConfigPage; + server.resource["^/password$"]["GET"] = getPasswordPage; + server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + server.resource["^/api/apps$"]["GET"] = getApps; + server.resource["^/api/apps$"]["POST"] = saveApp; + server.resource["^/api/config$"]["GET"] = getConfig; + server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/password$"]["POST"] = savePassword; + server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + server.config.reuse_address = true; + server.config.address = "0.0.0.0"s; + server.config.port = PORT_HTTP; try { - http_server.bind(); + server.bind(); BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; } catch(boost::system::system_error &err) { @@ -395,12 +405,12 @@ void start(std::shared_ptr shutdown_event) { shutdown_event->raise(true); return; } - auto accept_and_run = [&](auto *http_server) { + auto accept_and_run = [&](auto *server) { try { - http_server->accept_and_run(); + server->accept_and_run(); } catch(boost::system::system_error &err) { - // It's possible the exception gets thrown after calling http_server->stop() from a different thread + // It's possible the exception gets thrown after calling server->stop() from a different thread if(shutdown_event->peek()) { return; } @@ -410,12 +420,12 @@ void start(std::shared_ptr shutdown_event) { return; } }; - std::thread tcp { accept_and_run, &http_server }; + std::thread tcp { accept_and_run, &server }; // Wait for any event shutdown_event->view(); - http_server.stop(); + server.stop(); tcp.join(); } diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index b6f7ac0a..825ae3d6 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -72,7 +72,7 @@ int generate_user_creds(const std::string &file) { std::string salt = crypto::rand_string(16); outputTree.put("username", "sunshine"); outputTree.put("salt", salt); - outputTree.put("password", crypto::hash_hexstr(plainPassword + salt)); + outputTree.put("password", util::hex(crypto::hash(plainPassword + salt)).to_string()); BOOST_LOG(info) << "New credentials has been created"; BOOST_LOG(info) << "Username: " << username; BOOST_LOG(info) << "Password: " << plainPassword; From bc261fddf23705c00a17194475565b03df89b8fd Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 15:56:13 +0200 Subject: [PATCH 090/158] Updated Config Page --- assets/web/apps.html | 6 ++- assets/web/config.html | 120 ++++++++++++++++++++++++++++++++++------- assets/web/index.html | 9 +++- assets/web/pin.html | 14 +++-- 4 files changed, 123 insertions(+), 26 deletions(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index d63f8260..4f49f7da 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -1,6 +1,8 @@
-

Applications

-
Applications are refreshed only when Client is restarted
+
+

Applications

+
Applications are refreshed only when Client is restarted
+
diff --git a/assets/web/config.html b/assets/web/config.html index b2b642cb..356e4d45 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -55,6 +55,43 @@
How long to wait in milliseconds for data from moonlight before shutting down the stream
+ +
+ +
+ +
+
+ {{r}} + × +
+
+ + + +
+
+
+ +
+
+ {{f}} + × +
+
+ + + +
+
+
+ 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. +
+
@@ -98,7 +135,7 @@
-
+
@@ -107,7 +144,7 @@
-
+
@@ -122,27 +159,36 @@
-
+
The name of the audio sink used for Audio Loopback
You can find the name of the audio sink using the following command:
- tools\audio-info.exe +
tools\audio-info.exe
-
+
The name of the audio sink used for Audio Loopback
If you do not specify this variable, pulseaudio will select the default monitor device.

You can find the name of the audio sink using the following command:
- pacmd list-sources | grep "name:"
+
pacmd list-sinks | grep "name:"

+
+
+ +
+ + +
+ The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine + to stream audio, while muting the speakers.
-
+
You can select the video card you want to stream:
The appropriate values can be found using the following command:
- tools\dxgi-info.exe +
tools\dxgi-info.exe
@@ -154,9 +200,7 @@ The appropriate values can be found using the following command:
tools\dxgi-info.exe
!! Linux only !!
- Set the display number to stream. I have no idea how they are numbered. They start from 1, - usually.
- output_name = 1
+ Set the display number to stream. I have no idea how they are numbered. They start from 0, usually.
@@ -214,7 +258,7 @@
+ +
- - + +
@@ -293,7 +337,7 @@
- + - +

PIN Pairing

+
+
+ + +
+
+ Warning! Make sure you have access to the client you are pairing with.
+ This software can give total control to your computer, so be careful! +
From b848db8f2be3d44d7298cb6db5fda39cb1160735 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 16:42:40 +0200 Subject: [PATCH 091/158] Config Update and PIN POST Method --- assets/web/apps.html | 170 ++++++++++++++++++++++++---------------- assets/web/pin.html | 7 +- sunshine/confighttp.cpp | 34 +++++++- sunshine/nvhttp.cpp | 64 ++++++++------- sunshine/nvhttp.h | 3 +- 5 files changed, 173 insertions(+), 105 deletions(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index 4f49f7da..d1784608 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -3,62 +3,90 @@

Applications

Applications are refreshed only when Client is restarted
-
- - - - - - - - - - - - -
NameActions
{{app.name}} -
-
-
- - -
Application Name, as shown on Moonlight
-
-
- - -
The file where the output of the command is stored, if it is not specified, the output is ignored
-
-
- -
A list of commands to be run before/after the application.
If any of the prep-commands fail, starting the application is aborted
- - - - - - - - - - - - - -
DoUndo
- -
-
- - -
The main application, if it is not specified, a processs is started that sleeps indefinitely
-
-
- - +
+ + + + + + + + + + + + + +
NameActions
{{app.name}} +
+
+
+
+ +
+ + +
Application Name, as shown on Moonlight
+
+ +
+ + +
The file where the output of the command is stored, if it is not + specified, the output is ignored
+
+ +
+ +
A list of commands to be run before/after the application.
If any of the + prep-commands fail, starting the application is aborted
+ + + + + + + + + + + + + +
DoUndo
+ +
+ +
+ +
+
{{c}}
+ +
+
+ + +
+
A list of commands to be run and forgotten about
+
+ +
+ + +
The main application, if it is not specified, a processs is started that + sleeps indefinitely
+
+ +
+ + +
- +
+ +