diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index df3e69caa6f..79674aa427a 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -6,7 +6,7 @@ on: branches: [nightly] paths: # prevents workflow from running unless these files change - '.github/workflows/localize.yml' - - 'sunshine/**' + - 'src/**' - 'locale/sunshine.po' workflow_dispatch: diff --git a/.gitmodules b/.gitmodules index 814f340942e..98de04d063b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,6 @@ [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers -[submodule "sunshine/platform/macos/TPCircularBuffer"] - path = sunshine/platform/macos/TPCircularBuffer +[submodule "src/platform/macos/TPCircularBuffer"] + path = src/platform/macos/TPCircularBuffer url = https://github.com/michaeltyson/TPCircularBuffer diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e94d53a3fe..9cc18d54ffd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,18 +100,18 @@ if(WIN32) if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico") endif() - configure_file(sunshine/platform/windows/windows.rs.in windows.rc @ONLY) + configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - sunshine/platform/windows/publish.cpp - sunshine/platform/windows/misc.h - sunshine/platform/windows/misc.cpp - sunshine/platform/windows/input.cpp - sunshine/platform/windows/display.h - sunshine/platform/windows/display_base.cpp - sunshine/platform/windows/display_vram.cpp - sunshine/platform/windows/display_ram.cpp - sunshine/platform/windows/audio.cpp + src/platform/windows/publish.cpp + src/platform/windows/misc.h + src/platform/windows/misc.cpp + src/platform/windows/input.cpp + src/platform/windows/display.h + src/platform/windows/display_base.cpp + src/platform/windows/display_vram.cpp + src/platform/windows/display_ram.cpp + src/platform/windows/audio.cpp third-party/ViGEmClient/src/ViGEmClient.cpp third-party/ViGEmClient/include/ViGEm/Client.h third-party/ViGEmClient/include/ViGEm/Common.h @@ -180,21 +180,21 @@ elseif(APPLE) set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) set(PLATFORM_TARGET_FILES - sunshine/platform/macos/av_audio.h - sunshine/platform/macos/av_audio.m - sunshine/platform/macos/av_img_t.h - sunshine/platform/macos/av_video.h - sunshine/platform/macos/av_video.m - sunshine/platform/macos/display.mm - sunshine/platform/macos/input.cpp - sunshine/platform/macos/microphone.mm - sunshine/platform/macos/misc.cpp - sunshine/platform/macos/misc.h - sunshine/platform/macos/nv12_zero_device.cpp - sunshine/platform/macos/nv12_zero_device.h - sunshine/platform/macos/publish.cpp - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h + src/platform/macos/av_audio.h + src/platform/macos/av_audio.m + src/platform/macos/av_img_t.h + src/platform/macos/av_video.h + src/platform/macos/av_video.m + src/platform/macos/display.mm + src/platform/macos/input.cpp + src/platform/macos/microphone.mm + src/platform/macos/misc.cpp + src/platform/macos/misc.h + src/platform/macos/nv12_zero_device.cpp + src/platform/macos/nv12_zero_device.h + src/platform/macos/publish.cpp + src/platform/macos/TPCircularBuffer/TPCircularBuffer.c + src/platform/macos/TPCircularBuffer/TPCircularBuffer.h ${APPLE_PLIST_FILE}) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") @@ -242,14 +242,14 @@ else() if(X11_FOUND) add_compile_definitions(SUNSHINE_BUILD_X11) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp) endif() if(CUDA_FOUND) include_directories(third-party/nvfbc) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/cuda.cu - sunshine/platform/linux/cuda.cpp + src/platform/linux/cuda.cu + src/platform/linux/cuda.cpp third-party/nvfbc/NvFBC.h) add_compile_definitions(SUNSHINE_BUILD_CUDA) @@ -259,7 +259,7 @@ else() add_compile_definitions(SUNSHINE_BUILD_DRM) include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) elseif(LIBDRM_FOUND) message(WARNING "Found libdrm, yet there is no libcap") @@ -301,26 +301,26 @@ else() list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/wlgrab.cpp - sunshine/platform/linux/wayland.cpp) + src/platform/linux/wlgrab.cpp + src/platform/linux/wayland.cpp) endif() if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${}) message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)") endif() list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/publish.cpp - sunshine/platform/linux/vaapi.h - sunshine/platform/linux/vaapi.cpp - sunshine/platform/linux/cuda.h - sunshine/platform/linux/graphics.h - sunshine/platform/linux/graphics.cpp - sunshine/platform/linux/misc.h - sunshine/platform/linux/misc.cpp - sunshine/platform/linux/audio.cpp - sunshine/platform/linux/input.cpp - sunshine/platform/linux/x11grab.h - sunshine/platform/linux/wayland.h + src/platform/linux/publish.cpp + src/platform/linux/vaapi.h + src/platform/linux/vaapi.cpp + src/platform/linux/cuda.h + src/platform/linux/graphics.h + src/platform/linux/graphics.cpp + src/platform/linux/misc.h + src/platform/linux/misc.cpp + src/platform/linux/audio.cpp + src/platform/linux/input.cpp + src/platform/linux/x11grab.h + src/platform/linux/wayland.h third-party/glad/src/egl.c third-party/glad/src/gl.c third-party/glad/include/EGL/eglplatform.h @@ -356,47 +356,47 @@ set(SUNSHINE_TARGET_FILES third-party/moonlight-common-c/src/Rtsp.h third-party/moonlight-common-c/src/RtspParser.c third-party/moonlight-common-c/src/Video.h - sunshine/upnp.cpp - sunshine/upnp.h - sunshine/cbs.cpp - sunshine/utility.h - sunshine/uuid.h - sunshine/config.h - sunshine/config.cpp - sunshine/main.cpp - sunshine/main.h - sunshine/crypto.cpp - sunshine/crypto.h - sunshine/nvhttp.cpp - sunshine/nvhttp.h - sunshine/httpcommon.cpp - sunshine/httpcommon.h - sunshine/confighttp.cpp - sunshine/confighttp.h - sunshine/rtsp.cpp - sunshine/rtsp.h - sunshine/stream.cpp - sunshine/stream.h - sunshine/video.cpp - sunshine/video.h - sunshine/input.cpp - sunshine/input.h - sunshine/audio.cpp - sunshine/audio.h - sunshine/platform/common.h - sunshine/process.cpp - sunshine/process.h - sunshine/network.cpp - sunshine/network.h - sunshine/move_by_copy.h - sunshine/task_pool.h - sunshine/thread_pool.h - sunshine/thread_safe.h - sunshine/sync.h - sunshine/round_robin.h + src/upnp.cpp + src/upnp.h + src/cbs.cpp + src/utility.h + src/uuid.h + src/config.h + src/config.cpp + src/main.cpp + src/main.h + src/crypto.cpp + src/crypto.h + src/nvhttp.cpp + src/nvhttp.h + src/httpcommon.cpp + src/httpcommon.h + src/confighttp.cpp + src/confighttp.h + src/rtsp.cpp + src/rtsp.h + src/stream.cpp + src/stream.h + src/video.cpp + src/video.h + src/input.cpp + src/input.h + src/audio.cpp + src/audio.h + src/platform/common.h + src/process.cpp + src/process.h + src/network.cpp + src/network.h + src/move_by_copy.h + src/task_pool.h + src/thread_pool.h + src/thread_safe.h + src/sync.h + src/round_robin.h ${PLATFORM_TARGET_FILES}) -set_source_files_properties(sunshine/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} @@ -414,7 +414,7 @@ string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE) if("${BUILD_TYPE}" STREQUAL "XDEBUG") list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3) if(WIN32) - set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) + set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 32ec4a5eca6..8b04867f16d 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -67,7 +67,7 @@ any of the following paths are modified. .. code-block:: yaml - - 'sunshine/**' + - 'src/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, diff --git a/sunshine/audio.cpp b/src/audio.cpp similarity index 100% rename from sunshine/audio.cpp rename to src/audio.cpp diff --git a/sunshine/audio.h b/src/audio.h similarity index 100% rename from sunshine/audio.h rename to src/audio.h diff --git a/sunshine/cbs.cpp b/src/cbs.cpp similarity index 99% rename from sunshine/cbs.cpp rename to src/cbs.cpp index e4e24571363..6a4bd184e8d 100644 --- a/sunshine/cbs.cpp +++ b/src/cbs.cpp @@ -297,4 +297,4 @@ bool validate_sps(const AVPacket *packet, int codec_id) { return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag; } -} // namespace cbs \ No newline at end of file +} // namespace cbs diff --git a/sunshine/cbs.h b/src/cbs.h similarity index 98% rename from sunshine/cbs.h rename to src/cbs.h index dc772dd94b4..cd989b4a172 100644 --- a/sunshine/cbs.h +++ b/src/cbs.h @@ -31,4 +31,4 @@ h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet); bool validate_sps(const AVPacket *packet, int codec_id); } // namespace cbs -#endif \ No newline at end of file +#endif diff --git a/sunshine/config.cpp b/src/config.cpp similarity index 100% rename from sunshine/config.cpp rename to src/config.cpp diff --git a/sunshine/config.h b/src/config.h similarity index 100% rename from sunshine/config.h rename to src/config.h diff --git a/sunshine/confighttp.cpp b/src/confighttp.cpp similarity index 100% rename from sunshine/confighttp.cpp rename to src/confighttp.cpp diff --git a/sunshine/confighttp.h b/src/confighttp.h similarity index 100% rename from sunshine/confighttp.h rename to src/confighttp.h diff --git a/sunshine/crypto.cpp b/src/crypto.cpp similarity index 99% rename from sunshine/crypto.cpp rename to src/crypto.cpp index 006cf7fb69e..072be91c9b6 100644 --- a/sunshine/crypto.cpp +++ b/src/crypto.cpp @@ -492,4 +492,4 @@ std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) { return value; } -} // namespace crypto \ No newline at end of file +} // namespace crypto diff --git a/sunshine/crypto.h b/src/crypto.h similarity index 100% rename from sunshine/crypto.h rename to src/crypto.h diff --git a/sunshine/httpcommon.cpp b/src/httpcommon.cpp similarity index 99% rename from sunshine/httpcommon.cpp rename to src/httpcommon.cpp index 9107414495d..86429eaba72 100644 --- a/sunshine/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -180,4 +180,4 @@ int create_creds(const std::string &pkey, const std::string &cert) { return 0; } -} // namespace http \ No newline at end of file +} // namespace http diff --git a/sunshine/httpcommon.h b/src/httpcommon.h similarity index 95% rename from sunshine/httpcommon.h rename to src/httpcommon.h index 37d8451fab5..e1a1509ac67 100644 --- a/sunshine/httpcommon.h +++ b/src/httpcommon.h @@ -16,4 +16,4 @@ extern std::string unique_id; extern net::net_e origin_pin_allowed; extern net::net_e origin_web_ui_allowed; -} // namespace http \ No newline at end of file +} // namespace http diff --git a/sunshine/input.cpp b/src/input.cpp similarity index 100% rename from sunshine/input.cpp rename to src/input.cpp diff --git a/sunshine/input.h b/src/input.h similarity index 100% rename from sunshine/input.h rename to src/input.h diff --git a/sunshine/main.cpp b/src/main.cpp similarity index 100% rename from sunshine/main.cpp rename to src/main.cpp diff --git a/sunshine/main.h b/src/main.h similarity index 100% rename from sunshine/main.h rename to src/main.h diff --git a/sunshine/move_by_copy.h b/src/move_by_copy.h similarity index 100% rename from sunshine/move_by_copy.h rename to src/move_by_copy.h diff --git a/sunshine/network.cpp b/src/network.cpp similarity index 99% rename from sunshine/network.cpp rename to src/network.cpp index ccf9e32f959..b5002fd6bb0 100644 --- a/sunshine/network.cpp +++ b/src/network.cpp @@ -112,4 +112,4 @@ void free_host(ENetHost *host) { enet_host_destroy(host); } -} // namespace net \ No newline at end of file +} // namespace net diff --git a/sunshine/network.h b/src/network.h similarity index 100% rename from sunshine/network.h rename to src/network.h diff --git a/sunshine/nvhttp.cpp b/src/nvhttp.cpp similarity index 100% rename from sunshine/nvhttp.cpp rename to src/nvhttp.cpp diff --git a/sunshine/nvhttp.h b/src/nvhttp.h similarity index 100% rename from sunshine/nvhttp.h rename to src/nvhttp.h diff --git a/sunshine/platform/common.h b/src/platform/common.h similarity index 99% rename from sunshine/platform/common.h rename to src/platform/common.h index ef9a2cf612b..60ae9d141e1 100644 --- a/sunshine/platform/common.h +++ b/src/platform/common.h @@ -11,8 +11,8 @@ #include #include -#include "sunshine/thread_safe.h" -#include "sunshine/utility.h" +#include "src/thread_safe.h" +#include "src/utility.h" struct sockaddr; struct AVFrame; diff --git a/sunshine/platform/linux/audio.cpp b/src/platform/linux/audio.cpp similarity index 98% rename from sunshine/platform/linux/audio.cpp rename to src/platform/linux/audio.cpp index 2770e515732..ea68809a7aa 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -10,11 +10,11 @@ #include #include -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/thread_safe.h" +#include "src/config.h" +#include "src/main.h" +#include "src/thread_safe.h" namespace platf { using namespace std::literals; @@ -503,4 +503,4 @@ std::unique_ptr audio_control() { return audio; } -} // namespace platf \ No newline at end of file +} // namespace platf diff --git a/sunshine/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp similarity index 99% rename from sunshine/platform/linux/cuda.cpp rename to src/platform/linux/cuda.cpp index c907ae6c236..0292a789c7a 100644 --- a/sunshine/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -11,8 +11,8 @@ extern "C" { #include "cuda.h" #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/utility.h" #include "wayland.h" #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv @@ -714,4 +714,4 @@ std::vector nvfbc_display_names() { return display_names; } -} // namespace platf \ No newline at end of file +} // namespace platf diff --git a/sunshine/platform/linux/cuda.cu b/src/platform/linux/cuda.cu similarity index 99% rename from sunshine/platform/linux/cuda.cu rename to src/platform/linux/cuda.cu index f69be7308a8..6552149e6d2 100644 --- a/sunshine/platform/linux/cuda.cu +++ b/src/platform/linux/cuda.cu @@ -328,4 +328,4 @@ int sws_t::load_ram(platf::img_t &img, cudaArray_t array) { return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array"); } -} // namespace cuda \ No newline at end of file +} // namespace cuda diff --git a/sunshine/platform/linux/cuda.h b/src/platform/linux/cuda.h similarity index 99% rename from sunshine/platform/linux/cuda.h rename to src/platform/linux/cuda.h index b175140d3de..04dec693e37 100644 --- a/sunshine/platform/linux/cuda.h +++ b/src/platform/linux/cuda.h @@ -104,4 +104,4 @@ class sws_t { }; } // namespace cuda -#endif \ No newline at end of file +#endif diff --git a/sunshine/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp similarity index 100% rename from sunshine/platform/linux/graphics.cpp rename to src/platform/linux/graphics.cpp diff --git a/sunshine/platform/linux/graphics.h b/src/platform/linux/graphics.h similarity index 98% rename from sunshine/platform/linux/graphics.h rename to src/platform/linux/graphics.h index 2cc24b01210..c566b48fc4f 100644 --- a/sunshine/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -8,9 +8,9 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" #define SUNSHINE_STRINGIFY_HELPER(x) #x #define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x) diff --git a/sunshine/platform/linux/input.cpp b/src/platform/linux/input.cpp similarity index 99% rename from sunshine/platform/linux/input.cpp rename to src/platform/linux/input.cpp index be923936135..c8e5887e95e 100644 --- a/sunshine/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -9,11 +9,11 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" // Support older versions #ifndef REL_HWHEEL_HI_RES diff --git a/sunshine/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp similarity index 99% rename from sunshine/platform/linux/kmsgrab.cpp rename to src/platform/linux/kmsgrab.cpp index 28ef656e413..3b9257be37f 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -8,10 +8,10 @@ #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" // Cursor rendering support through x11 #include "graphics.h" diff --git a/sunshine/platform/linux/misc.cpp b/src/platform/linux/misc.cpp similarity index 98% rename from sunshine/platform/linux/misc.cpp rename to src/platform/linux/misc.cpp index 6d7643c0121..f3f4f4048b6 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -11,8 +11,8 @@ #include "misc.h" #include "vaapi.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" #ifdef __GNUC__ #define SUNSHINE_GNUC_EXTENSION __extension__ @@ -299,4 +299,4 @@ std::unique_ptr init() { return std::make_unique(); } -} // namespace platf \ No newline at end of file +} // namespace platf diff --git a/sunshine/platform/linux/misc.h b/src/platform/linux/misc.h similarity index 93% rename from sunshine/platform/linux/misc.h rename to src/platform/linux/misc.h index 432aa9ed42e..26305ec8164 100644 --- a/sunshine/platform/linux/misc.h +++ b/src/platform/linux/misc.h @@ -4,7 +4,7 @@ #include #include -#include "sunshine/utility.h" +#include "src/utility.h" KITTY_USING_MOVE_T(file_t, int, -1, { if(el >= 0) { @@ -28,4 +28,4 @@ void *handle(const std::vector &libs); } // namespace dyn -#endif \ No newline at end of file +#endif diff --git a/sunshine/platform/linux/publish.cpp b/src/platform/linux/publish.cpp similarity index 98% rename from sunshine/platform/linux/publish.cpp rename to src/platform/linux/publish.cpp index 825f37407e2..88f660fea9f 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; @@ -426,4 +426,4 @@ class deinit_t : public ::platf::deinit_t { return std::make_unique(std::thread { avahi::simple_poll_loop, poll.get() }); } -} // namespace platf::publish \ No newline at end of file +} // namespace platf::publish diff --git a/sunshine/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp similarity index 99% rename from sunshine/platform/linux/vaapi.cpp rename to src/platform/linux/vaapi.cpp index 37a64cb850a..9dcb2dfead8 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -9,10 +9,10 @@ extern "C" { #include "graphics.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.h b/src/platform/linux/vaapi.h similarity index 95% rename from sunshine/platform/linux/vaapi.h rename to src/platform/linux/vaapi.h index ddb5c61ee17..eb55ec76891 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/src/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_VAAPI_H #include "misc.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace egl { struct surface_descriptor_t; @@ -24,4 +24,4 @@ bool validate(int fd); int init(); } // namespace va -#endif \ No newline at end of file +#endif diff --git a/sunshine/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp similarity index 97% rename from sunshine/platform/linux/wayland.cpp rename to src/platform/linux/wayland.cpp index 9b65ef8d307..8cb35fee6d9 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -4,10 +4,10 @@ #include #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" #include "wayland.h" extern const wl_interface wl_output_interface; @@ -265,4 +265,4 @@ int init() { } // namespace wl -#pragma GCC diagnostic pop \ No newline at end of file +#pragma GCC diagnostic pop diff --git a/sunshine/platform/linux/wayland.h b/src/platform/linux/wayland.h similarity index 99% rename from sunshine/platform/linux/wayland.h rename to src/platform/linux/wayland.h index d6e63843bcb..3e950da55c3 100644 --- a/sunshine/platform/linux/wayland.h +++ b/src/platform/linux/wayland.h @@ -213,4 +213,4 @@ inline int init() { return -1; } } // namespace wl #endif -#endif \ No newline at end of file +#endif diff --git a/sunshine/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp similarity index 99% rename from sunshine/platform/linux/wlgrab.cpp rename to src/platform/linux/wlgrab.cpp index c4ab3dbfe9e..8bffb415ae5 100644 --- a/sunshine/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -1,6 +1,6 @@ -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/main.h" +#include "src/main.h" #include "vaapi.h" #include "wayland.h" @@ -366,4 +366,4 @@ std::vector wl_display_names() { return display_names; } -} // namespace platf \ No newline at end of file +} // namespace platf diff --git a/sunshine/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp similarity index 99% rename from sunshine/platform/linux/x11grab.cpp rename to src/platform/linux/x11grab.cpp index c9b4beaad57..c024f4d8f40 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -2,7 +2,7 @@ // Created by loki on 6/21/19. // -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include @@ -16,9 +16,9 @@ #include #include -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/task_pool.h" +#include "src/config.h" +#include "src/main.h" +#include "src/task_pool.h" #include "cuda.h" #include "graphics.h" diff --git a/sunshine/platform/linux/x11grab.h b/src/platform/linux/x11grab.h similarity index 94% rename from sunshine/platform/linux/x11grab.h rename to src/platform/linux/x11grab.h index 9fde2664740..9484b27846f 100644 --- a/sunshine/platform/linux/x11grab.h +++ b/src/platform/linux/x11grab.h @@ -3,8 +3,8 @@ #include -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/platform/common.h" +#include "src/utility.h" // X11 Display extern "C" struct _XDisplay; @@ -57,4 +57,4 @@ xdisplay_t make_display() { return nullptr; } #endif } // namespace platf::x11 -#endif \ No newline at end of file +#endif diff --git a/src/platform/macos/TPCircularBuffer/README.markdown b/src/platform/macos/TPCircularBuffer/README.markdown new file mode 100644 index 00000000000..04056d049fb --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/README.markdown @@ -0,0 +1,55 @@ +A simple, fast circular buffer implementation for audio processing +================================================================== + +A simple C implementation for a circular (ring) buffer. Thread-safe with a single producer and a single consumer, using OSAtomic.h primitives, and avoids any need for buffer wrapping logic by using a virtual memory map technique to place a virtual copy of the buffer straight after the end of the real buffer. + +Usage +----- + +Initialisation and cleanup: `TPCircularBufferInit` and `TPCircularBufferCleanup` to allocate and free resources. + +Producing: Use `TPCircularBufferHead` to get a pointer to write to the buffer, followed by `TPCircularBufferProduce` to submit the written data. `TPCircularBufferProduceBytes` is a convenience routine for writing data straight to the buffer. + +Consuming: Use `TPCircularBufferTail` to get a pointer to the next data to read, followed by `TPCircularBufferConsume` to free up the space once processed. + +TPCircularBuffer+AudioBufferList.(c,h) contain helper functions to queue and dequeue AudioBufferList +structures. These will automatically adjust the mData fields of each buffer to point to 16-byte aligned +regions within the circular buffer. + +Thread safety +------------- + +As long as you restrict multithreaded access to just one producer, and just one consumer, this utility should be thread safe. + +Only one shared variable is used (the buffer fill count), and OSAtomic primitives are used to write to this value to ensure atomicity. + +License +------- + +Copyright (C) 2012-2013 A Tasty Pixel + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +----------------------------------------------------- + +Virtual memory technique originally proposed by [Philip Howard](http://vrb.slashusr.org/), and [adapted to Darwin](http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) by [Kurt Revis](http://www.snoize.com) + +See more info at [atastypixel.com](http://atastypixel.com/blog/a-simple-fast-circular-buffer-implementation-for-audio-processing/) + diff --git a/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.c b/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.c new file mode 100644 index 00000000000..fa894ca27bf --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.c @@ -0,0 +1,318 @@ +// +// TPCircularBuffer+AudioBufferList.c +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 20/03/2012. +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "TPCircularBuffer+AudioBufferList.h" +#import + +static double __secondsToHostTicks = 0.0; + +static inline unsigned long align16byte(unsigned long val) { + if ( val & (16-1) ) { + return val + (16 - (val & (16-1))); + } + return val; +} + +static inline unsigned long min(unsigned long a, unsigned long b) { + return a > b ? b : a; +} + +AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferList(TPCircularBuffer *buffer, UInt32 numberOfBuffers, UInt32 bytesPerBuffer, const AudioTimeStamp *inTimestamp) { + uint32_t availableBytes; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); + if ( !block || availableBytes < sizeof(TPCircularBufferABLBlockHeader)+((numberOfBuffers-1)*sizeof(AudioBuffer))+(numberOfBuffers*bytesPerBuffer) ) return NULL; + + #ifdef DEBUG + assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); + #endif + + if ( inTimestamp ) { + memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); + } else { + memset(&block->timestamp, 0, sizeof(AudioTimeStamp)); + } + + memset(&block->bufferList, 0, sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer))); + block->bufferList.mNumberBuffers = numberOfBuffers; + + char *dataPtr = (char*)&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)); + for ( UInt32 i=0; i availableBytes ) { + return NULL; + } + + block->bufferList.mBuffers[i].mData = dataPtr; + block->bufferList.mBuffers[i].mDataByteSize = bytesPerBuffer; + block->bufferList.mBuffers[i].mNumberChannels = 1; + + dataPtr += bytesPerBuffer; + } + + // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length + block->totalLength = (UInt32)align16byte((unsigned long)(dataPtr - (char*)block)); + if ( block->totalLength > availableBytes ) { + return NULL; + } + + return &block->bufferList; +} + +AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat, UInt32 frameCount, const AudioTimeStamp *timestamp) { + return TPCircularBufferPrepareEmptyAudioBufferList(buffer, + (audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? audioFormat->mChannelsPerFrame : 1, + audioFormat->mBytesPerFrame * frameCount, + timestamp); +} + +void TPCircularBufferProduceAudioBufferList(TPCircularBuffer *buffer, const AudioTimeStamp *inTimestamp) { + uint32_t availableBytes; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); + + assert(block); + + #ifdef DEBUG + assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); + #endif + + assert(block->bufferList.mBuffers[0].mDataByteSize > 0); + + if ( inTimestamp ) { + memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); + } + + UInt32 calculatedLength = (UInt32)(((char*)block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mData + block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mDataByteSize) - (char*)block); + + // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length + calculatedLength = (UInt32)align16byte(calculatedLength); + + assert(calculatedLength <= block->totalLength && calculatedLength <= availableBytes); + + block->totalLength = calculatedLength; + + TPCircularBufferProduce(buffer, block->totalLength); +} + +bool TPCircularBufferCopyAudioBufferList(TPCircularBuffer *buffer, const AudioBufferList *inBufferList, const AudioTimeStamp *inTimestamp, UInt32 frames, const AudioStreamBasicDescription *audioDescription) { + if ( frames == 0 ) return true; + + UInt32 byteCount = inBufferList->mBuffers[0].mDataByteSize; + if ( frames != kTPCircularBufferCopyAll ) { + byteCount = frames * audioDescription->mBytesPerFrame; + assert(byteCount <= inBufferList->mBuffers[0].mDataByteSize); + } + + if ( byteCount == 0 ) return true; + + AudioBufferList *bufferList = TPCircularBufferPrepareEmptyAudioBufferList(buffer, inBufferList->mNumberBuffers, byteCount, inTimestamp); + if ( !bufferList ) return false; + + for ( UInt32 i=0; imNumberBuffers; i++ ) { + memcpy(bufferList->mBuffers[i].mData, inBufferList->mBuffers[i].mData, byteCount); + } + + TPCircularBufferProduceAudioBufferList(buffer, NULL); + + return true; +} + +AudioBufferList *TPCircularBufferNextBufferListAfter(TPCircularBuffer *buffer, const AudioBufferList *bufferList, AudioTimeStamp *outTimestamp) { + uint32_t availableBytes; + void *tail = TPCircularBufferTail(buffer, &availableBytes); + void *end = (char*)tail + availableBytes; + assert((void*)bufferList > (void*)tail && (void*)bufferList < end); + + TPCircularBufferABLBlockHeader *originalBlock = (TPCircularBufferABLBlockHeader*)((char*)bufferList - offsetof(TPCircularBufferABLBlockHeader, bufferList)); + + #ifdef DEBUG + assert(!((unsigned long)originalBlock & 0xF) /* Beware unaligned accesses */); + #endif + + TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)originalBlock + originalBlock->totalLength); + if ( (void*)nextBlock >= end ) return NULL; + + #ifdef DEBUG + assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); + #endif + + if ( outTimestamp ) { + memcpy(outTimestamp, &nextBlock->timestamp, sizeof(AudioTimeStamp)); + } + + return &nextBlock->bufferList; +} + +void TPCircularBufferConsumeNextBufferListPartial(TPCircularBuffer *buffer, UInt32 framesToConsume, const AudioStreamBasicDescription *audioFormat) { + + uint32_t dontcare; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); + if ( !block ) return; + + #ifdef DEBUG + assert(!((unsigned long)block & 0xF)); // Beware unaligned accesses + #endif + + UInt32 bytesToConsume = (UInt32)min(framesToConsume * audioFormat->mBytesPerFrame, block->bufferList.mBuffers[0].mDataByteSize); + + if ( bytesToConsume == block->bufferList.mBuffers[0].mDataByteSize ) { + TPCircularBufferConsumeNextBufferList(buffer); + return; + } + + for ( UInt32 i=0; ibufferList.mNumberBuffers; i++ ) { + assert(bytesToConsume <= block->bufferList.mBuffers[i].mDataByteSize); + + block->bufferList.mBuffers[i].mData = (char*)block->bufferList.mBuffers[i].mData + bytesToConsume; + block->bufferList.mBuffers[i].mDataByteSize -= bytesToConsume; + } + + if ( block->timestamp.mFlags & kAudioTimeStampSampleTimeValid ) { + block->timestamp.mSampleTime += framesToConsume; + } + if ( block->timestamp.mFlags & kAudioTimeStampHostTimeValid ) { + if ( __secondsToHostTicks == 0.0 ) { + mach_timebase_info_data_t tinfo; + mach_timebase_info(&tinfo); + __secondsToHostTicks = 1.0 / (((double)tinfo.numer / tinfo.denom) * 1.0e-9); + } + + block->timestamp.mHostTime += (UInt64)(((double)framesToConsume / audioFormat->mSampleRate) * __secondsToHostTicks); + } + + // Reposition block forward, just before the audio data, ensuring 16-byte alignment + TPCircularBufferABLBlockHeader *newBlock = (TPCircularBufferABLBlockHeader*)(((unsigned long)block + bytesToConsume) & ~0xFul); + memmove(newBlock, block, sizeof(TPCircularBufferABLBlockHeader) + (block->bufferList.mNumberBuffers-1)*sizeof(AudioBuffer)); + UInt32 bytesFreed = (UInt32)((intptr_t)newBlock - (intptr_t)block); + newBlock->totalLength -= bytesFreed; + TPCircularBufferConsume(buffer, bytesFreed); +} + +void TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, const AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { + bool hasTimestamp = false; + UInt32 bytesToGo = *ioLengthInFrames * audioFormat->mBytesPerFrame; + UInt32 bytesCopied = 0; + while ( bytesToGo > 0 ) { + AudioBufferList *bufferList = TPCircularBufferNextBufferList(buffer, !hasTimestamp ? outTimestamp : NULL); + if ( !bufferList ) break; + + hasTimestamp = true; + UInt32 bytesToCopy = (UInt32)min(bytesToGo, bufferList->mBuffers[0].mDataByteSize); + + if ( outputBufferList ) { + for ( UInt32 i=0; imNumberBuffers; i++ ) { + assert(bytesCopied + bytesToCopy <= outputBufferList->mBuffers[i].mDataByteSize); + memcpy((char*)outputBufferList->mBuffers[i].mData + bytesCopied, bufferList->mBuffers[i].mData, bytesToCopy); + } + } + + TPCircularBufferConsumeNextBufferListPartial(buffer, bytesToCopy/audioFormat->mBytesPerFrame, audioFormat); + + bytesToGo -= bytesToCopy; + bytesCopied += bytesToCopy; + } + + *ioLengthInFrames -= bytesToGo / audioFormat->mBytesPerFrame; +} + +UInt32 TPCircularBufferPeekContiguousWrapped(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime, UInt32 wrapPoint) { + uint32_t availableBytes; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &availableBytes); + if ( !block ) return 0; + + #ifdef DEBUG + assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); + #endif + + if ( outTimestamp ) { + memcpy(outTimestamp, &block->timestamp, sizeof(AudioTimeStamp)); + } + + void *end = (char*)block + availableBytes; + + UInt32 byteCount = 0; + + while ( 1 ) { + byteCount += block->bufferList.mBuffers[0].mDataByteSize; + TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)block + block->totalLength); + if ( (void*)nextBlock >= end ) { + break; + } + + if ( contiguousToleranceSampleTime != UINT32_MAX ) { + UInt32 frames = block->bufferList.mBuffers[0].mDataByteSize / audioFormat->mBytesPerFrame; + Float64 nextTime = block->timestamp.mSampleTime + frames; + if ( wrapPoint && nextTime > wrapPoint ) nextTime = fmod(nextTime, wrapPoint); + Float64 diff = fabs(nextBlock->timestamp.mSampleTime - nextTime); + if ( diff > contiguousToleranceSampleTime && (!wrapPoint || fabs(diff-wrapPoint) > contiguousToleranceSampleTime) ) { + break; + } + } + + #ifdef DEBUG + assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); + #endif + + block = nextBlock; + } + + return byteCount / audioFormat->mBytesPerFrame; +} + +UInt32 TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { + return TPCircularBufferPeekContiguousWrapped(buffer, outTimestamp, audioFormat, UINT32_MAX, 0); +} + +UInt32 TPCircularBufferPeekContiguous(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime) { + return TPCircularBufferPeekContiguousWrapped(buffer, outTimestamp, audioFormat, contiguousToleranceSampleTime, 0); +} + +UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat) { + // Look at buffer head; make sure there's space for the block metadata + uint32_t availableBytes; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); + if ( !block ) return 0; + + #ifdef DEBUG + assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); + #endif + + // Now find out how much 16-byte aligned audio we can store in the space available + UInt32 numberOfBuffers = audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved ? audioFormat->mChannelsPerFrame : 1; + char * endOfBuffer = (char*)block + availableBytes; + char * dataPtr = (char*)align16byte((unsigned long)(&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)))); + if ( dataPtr >= endOfBuffer ) return 0; + UInt32 availableAudioBytes = (UInt32)(endOfBuffer - dataPtr); + + UInt32 availableAudioBytesPerBuffer = availableAudioBytes / numberOfBuffers; + availableAudioBytesPerBuffer -= (availableAudioBytesPerBuffer % (16-1)); + + return availableAudioBytesPerBuffer > 0 ? availableAudioBytesPerBuffer / audioFormat->mBytesPerFrame : 0; +} diff --git a/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.h b/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.h new file mode 100644 index 00000000000..8da01df36cc --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/TPCircularBuffer+AudioBufferList.h @@ -0,0 +1,232 @@ +// +// TPCircularBuffer+AudioBufferList.h +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 20/03/2012. +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef TPCircularBuffer_AudioBufferList_h +#define TPCircularBuffer_AudioBufferList_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include "TPCircularBuffer.h" +#include + +#define kTPCircularBufferCopyAll UINT32_MAX + +typedef struct { + AudioTimeStamp timestamp; + UInt32 totalLength; + AudioBufferList bufferList; +} TPCircularBufferABLBlockHeader; + + +/*! + * Prepare an empty buffer list, stored on the circular buffer + * + * @param buffer Circular buffer + * @param numberOfBuffers The number of buffers to be contained within the buffer list + * @param bytesPerBuffer The number of bytes to store for each buffer + * @param timestamp The timestamp associated with the buffer, or NULL. Note that you can also pass a timestamp into TPCircularBufferProduceAudioBufferList, to set it there instead. + * @return The empty buffer list, or NULL if circular buffer has insufficient space + */ +AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferList(TPCircularBuffer *buffer, UInt32 numberOfBuffers, UInt32 bytesPerBuffer, const AudioTimeStamp *timestamp); + +/*! + * Prepare an empty buffer list, stored on the circular buffer, using an audio description to automatically configure buffer + * + * @param buffer Circular buffer + * @param audioFormat The kind of audio that will be stored + * @param frameCount The number of frames that will be stored + * @param timestamp The timestamp associated with the buffer, or NULL. Note that you can also pass a timestamp into TPCircularBufferProduceAudioBufferList, to set it there instead. + * @return The empty buffer list, or NULL if circular buffer has insufficient space + */ +AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat, UInt32 frameCount, const AudioTimeStamp *timestamp); + +/*! + * Mark next audio buffer list as ready for reading + * + * This marks the audio buffer list prepared using TPCircularBufferPrepareEmptyAudioBufferList + * as ready for reading. You must not call this function without first calling + * TPCircularBufferPrepareEmptyAudioBufferList. + * + * @param buffer Circular buffer + * @param inTimestamp The timestamp associated with the buffer, or NULL to leave as-is. Note that you can also pass a timestamp into TPCircularBufferPrepareEmptyAudioBufferList, to set it there instead. + */ +void TPCircularBufferProduceAudioBufferList(TPCircularBuffer *buffer, const AudioTimeStamp *inTimestamp); + +/*! + * Copy the audio buffer list onto the buffer + * + * @param buffer Circular buffer + * @param bufferList Buffer list containing audio to copy to buffer + * @param timestamp The timestamp associated with the buffer, or NULL + * @param frames Length of audio in frames. Specify kTPCircularBufferCopyAll to copy the whole buffer (audioFormat can be NULL, in this case) + * @param audioFormat The AudioStreamBasicDescription describing the audio, or NULL if you specify kTPCircularBufferCopyAll to the `frames` argument + * @return YES if buffer list was successfully copied; NO if there was insufficient space + */ +bool TPCircularBufferCopyAudioBufferList(TPCircularBuffer *buffer, const AudioBufferList *bufferList, const AudioTimeStamp *timestamp, UInt32 frames, const AudioStreamBasicDescription *audioFormat); + +/*! + * Get a pointer to the next stored buffer list + * + * @param buffer Circular buffer + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the buffer + * @return Pointer to the next buffer list in the buffer + */ +static __inline__ __attribute__((always_inline)) AudioBufferList *TPCircularBufferNextBufferList(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp) { + uint32_t dontcare; // Length of segment is contained within buffer list, so we can ignore this + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); + if ( !block ) { + if ( outTimestamp ) { + memset(outTimestamp, 0, sizeof(AudioTimeStamp)); + } + return NULL; + } + if ( outTimestamp ) { + memcpy(outTimestamp, &block->timestamp, sizeof(AudioTimeStamp)); + } + return &block->bufferList; +} + +/*! + * Get a pointer to the next stored buffer list after the given one + * + * @param buffer Circular buffer + * @param bufferList Preceding buffer list + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the buffer + * @return Pointer to the next buffer list in the buffer, or NULL + */ +AudioBufferList *TPCircularBufferNextBufferListAfter(TPCircularBuffer *buffer, const AudioBufferList *bufferList, AudioTimeStamp *outTimestamp); + +/*! + * Consume the next buffer list + * + * @param buffer Circular buffer + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferConsumeNextBufferList(TPCircularBuffer *buffer) { + uint32_t dontcare; + TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); + if ( !block ) return; + TPCircularBufferConsume(buffer, block->totalLength); +} + +/*! + * Consume a portion of the next buffer list + * + * This will also increment the sample time and host time portions of the timestamp of + * the buffer list, if present. + * + * @param buffer Circular buffer + * @param framesToConsume The number of frames to consume from the buffer list + * @param audioFormat The AudioStreamBasicDescription describing the audio + */ +void TPCircularBufferConsumeNextBufferListPartial(TPCircularBuffer *buffer, UInt32 framesToConsume, const AudioStreamBasicDescription *audioFormat); + +/*! + * Consume a certain number of frames from the buffer, possibly from multiple queued buffer lists + * + * Copies the given number of frames from the buffer into outputBufferList, of the + * given audio description, then consumes the audio buffers. If an audio buffer has + * not been entirely consumed, then updates the queued buffer list structure to point + * to the unconsumed data only. + * + * @param buffer Circular buffer + * @param ioLengthInFrames On input, the number of frames in the given audio format to consume; on output, the number of frames provided + * @param outputBufferList The buffer list to copy audio to, or NULL to discard audio. If not NULL, the structure must be initialised properly, and the mData pointers must not be NULL. + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame returned + * @param audioFormat The format of the audio stored in the buffer + */ +void TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, const AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat); + +/*! + * Determine how many frames of audio are buffered + * + * Given the provided audio format, determines the frame count of all queued buffers + * + * Note: This function should only be used on the consumer thread, not the producer thread. + * + * @param buffer Circular buffer + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame + * @param audioFormat The format of the audio stored in the buffer + * @return The number of frames in the given audio format that are in the buffer + */ +UInt32 TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat); + +/*! + * Determine how many contiguous frames of audio are buffered + * + * Given the provided audio format, determines the frame count of all queued buffers that are contiguous, + * given their corresponding timestamps (sample time). + * + * Note: This function should only be used on the consumer thread, not the producer thread. + * + * @param buffer Circular buffer + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame + * @param audioFormat The format of the audio stored in the buffer + * @param contiguousToleranceSampleTime The number of samples of discrepancy to tolerate + * @return The number of frames in the given audio format that are in the buffer + */ +UInt32 TPCircularBufferPeekContiguous(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime); + +/*! + * Determine how many contiguous frames of audio are buffered, with wrap around + * + * Like TPCircularBufferPeekContiguous, determines how many contiguous frames are buffered, + * but considers audio that wraps around a region of a given length as also contiguous. This + * is good for audio that loops. + * + * Note: This function should only be used on the consumer thread, not the producer thread. + * + * @param buffer Circular buffer + * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame + * @param audioFormat The format of the audio stored in the buffer + * @param contiguousToleranceSampleTime The number of samples of discrepancy to tolerate + * @param wrapPoint The point around which the audio may wrap and still be considered contiguous, or 0 to disable + * @return The number of frames in the given audio format that are in the buffer + */ +UInt32 TPCircularBufferPeekContiguousWrapped(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime, UInt32 wrapPoint); + +/*! + * Determine how many much space there is in the buffer + * + * Given the provided audio format, determines the number of frames of audio that can be buffered. + * + * Note: This function should only be used on the producer thread, not the consumer thread. + * + * @param buffer Circular buffer + * @param audioFormat The format of the audio stored in the buffer + * @return The number of frames in the given audio format that can be stored in the buffer + */ +UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/platform/macos/TPCircularBuffer/TPCircularBuffer.c b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.c new file mode 100644 index 00000000000..a3e6b3c56fd --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.c @@ -0,0 +1,149 @@ +// +// TPCircularBuffer.c +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "TPCircularBuffer.h" +#include +#include +#include + +#define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) +static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { + if ( result != ERR_SUCCESS ) { + printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); + return false; + } + return true; +} + +bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize) { + + assert(length > 0); + + if ( structSize != sizeof(TPCircularBuffer) ) { + fprintf(stderr, "TPCircularBuffer: Header version mismatch. Check for old versions of TPCircularBuffer in your project\n"); + abort(); + } + + // Keep trying until we get our buffer, needed to handle race conditions + int retries = 3; + while ( true ) { + + buffer->length = (uint32_t)round_page(length); // We need whole page sizes + + // Temporarily allocate twice the length, so we have the contiguous address space to + // support a second instance of the buffer directly after + vm_address_t bufferAddress; + kern_return_t result = vm_allocate(mach_task_self(), + &bufferAddress, + buffer->length * 2, + VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer allocation"); + return false; + } + // Try again if we fail + continue; + } + + // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... + result = vm_deallocate(mach_task_self(), + bufferAddress + buffer->length, + buffer->length); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer deallocation"); + return false; + } + // If this fails somehow, deallocate the whole region and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + // Re-map the buffer to the address space immediately after the buffer + vm_address_t virtualAddress = bufferAddress + buffer->length; + vm_prot_t cur_prot, max_prot; + result = vm_remap(mach_task_self(), + &virtualAddress, // mirror target + buffer->length, // size of mirror + 0, // auto alignment + 0, // force remapping to virtualAddress + mach_task_self(), // same task + bufferAddress, // mirror source + 0, // MAP READ-WRITE, NOT COPY + &cur_prot, // unused protection struct + &max_prot, // unused protection struct + VM_INHERIT_DEFAULT); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Remap buffer memory"); + return false; + } + // If this remap failed, we hit a race condition, so deallocate and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + if ( virtualAddress != bufferAddress+buffer->length ) { + // If the memory is not contiguous, clean up both allocated buffers and try again + if ( retries-- == 0 ) { + printf("Couldn't map buffer memory to end of buffer\n"); + return false; + } + + vm_deallocate(mach_task_self(), virtualAddress, buffer->length); + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + buffer->buffer = (void*)bufferAddress; + buffer->fillCount = 0; + buffer->head = buffer->tail = 0; + buffer->atomic = true; + + return true; + } + return false; +} + +void TPCircularBufferCleanup(TPCircularBuffer *buffer) { + vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); + memset(buffer, 0, sizeof(TPCircularBuffer)); +} + +void TPCircularBufferClear(TPCircularBuffer *buffer) { + uint32_t fillCount; + if ( TPCircularBufferTail(buffer, &fillCount) ) { + TPCircularBufferConsume(buffer, fillCount); + } +} + +void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic) { + buffer->atomic = atomic; +} diff --git a/src/platform/macos/TPCircularBuffer/TPCircularBuffer.h b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.h new file mode 100644 index 00000000000..02c18c220e9 --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.h @@ -0,0 +1,243 @@ +// +// TPCircularBuffer.h +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// +// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy +// of the buffer memory directly after the buffer's end, negating the need for any buffer wrap-around +// logic. Clients can simply use the returned memory address as if it were contiguous space. +// +// The implementation is thread-safe in the case of a single producer and single consumer. +// +// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and +// adapted to Darwin by Kurt Revis (http://www.snoize.com, +// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) +// +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef TPCircularBuffer_h +#define TPCircularBuffer_h + +#include +#include +#include + +#ifdef __cplusplus + extern "C++" { + #include + typedef std::atomic_int atomicInt; + #define atomicFetchAdd(a,b) std::atomic_fetch_add(a,b) + } +#else + #include + typedef atomic_int atomicInt; + #define atomicFetchAdd(a,b) atomic_fetch_add(a,b) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void *buffer; + uint32_t length; + uint32_t tail; + uint32_t head; + volatile atomicInt fillCount; + bool atomic; +} TPCircularBuffer; + +/*! + * Initialise buffer + * + * Note that the length is advisory only: Because of the way the + * memory mirroring technique works, the true buffer length will + * be multiples of the device page size (e.g. 4096 bytes) + * + * If you intend to use the AudioBufferList utilities, you should + * always allocate a bit more space than you need for pure audio + * data, so there's room for the metadata. How much extra is required + * depends on how many AudioBufferList structures are used, which is + * a function of how many audio frames each buffer holds. A good rule + * of thumb is to add 15%, or at least another 2048 bytes or so. + * + * @param buffer Circular buffer + * @param length Length of buffer + */ +#define TPCircularBufferInit(buffer, length) \ + _TPCircularBufferInit(buffer, length, sizeof(*buffer)) +bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize); + +/*! + * Cleanup buffer + * + * Releases buffer resources. + */ +void TPCircularBufferCleanup(TPCircularBuffer *buffer); + +/*! + * Clear buffer + * + * Resets buffer to original, empty state. + * + * This is safe for use by consumer while producer is accessing + * buffer. + */ +void TPCircularBufferClear(TPCircularBuffer *buffer); + +/*! + * Set the atomicity + * + * If you set the atomiticy to false using this method, the buffer will + * not use atomic operations. This can be used to give the compiler a little + * more optimisation opportunities when the buffer is only used on one thread. + * + * Important note: Only set this to false if you know what you're doing! + * + * The default value is true (the buffer will use atomic operations) + * + * @param buffer Circular buffer + * @param atomic Whether the buffer is atomic (default true) + */ +void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic); + +// Reading (consuming) + +/*! + * Access end of buffer + * + * This gives you a pointer to the end of the buffer, ready + * for reading, and the number of available bytes to read. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for reading + * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty + */ +static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, uint32_t* availableBytes) { + *availableBytes = buffer->fillCount; + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->tail); +} + +/*! + * Consume bytes in buffer + * + * This frees up the just-read bytes, ready for writing again. + * + * @param buffer Circular buffer + * @param amount Number of bytes to consume + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, uint32_t amount) { + buffer->tail = (buffer->tail + amount) % buffer->length; + if ( buffer->atomic ) { + atomicFetchAdd(&buffer->fillCount, -(int)amount); + } else { + buffer->fillCount -= amount; + } + assert(buffer->fillCount >= 0); +} + +/*! + * Access front of buffer + * + * This gives you a pointer to the front of the buffer, ready + * for writing, and the number of available bytes to write. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for writing + * @return Pointer to the first bytes ready for writing, or NULL if buffer is full + */ +static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, uint32_t* availableBytes) { + *availableBytes = (buffer->length - buffer->fillCount); + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->head); +} + +// Writing (producing) + +/*! + * Produce bytes in buffer + * + * This marks the given section of the buffer ready for reading. + * + * @param buffer Circular buffer + * @param amount Number of bytes to produce + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, uint32_t amount) { + buffer->head = (buffer->head + amount) % buffer->length; + if ( buffer->atomic ) { + atomicFetchAdd(&buffer->fillCount, (int)amount); + } else { + buffer->fillCount += amount; + } + assert(buffer->fillCount <= buffer->length); +} + +/*! + * Helper routine to copy bytes to buffer + * + * This copies the given bytes to the buffer, and marks them ready for reading. + * + * @param buffer Circular buffer + * @param src Source buffer + * @param len Number of bytes in source buffer + * @return true if bytes copied, false if there was insufficient space + */ +static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, uint32_t len) { + uint32_t space; + void *ptr = TPCircularBufferHead(buffer, &space); + if ( space < len ) return false; + memcpy(ptr, src, len); + TPCircularBufferProduce(buffer, len); + return true; +} + +/*! + * Deprecated method + */ +static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferConsume instead") +void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, uint32_t amount) { + buffer->tail = (buffer->tail + amount) % buffer->length; + buffer->fillCount -= amount; + assert(buffer->fillCount >= 0); +} + +/*! + * Deprecated method + */ +static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferProduce instead") +void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, uint32_t amount) { + buffer->head = (buffer->head + amount) % buffer->length; + buffer->fillCount += amount; + assert(buffer->fillCount <= buffer->length); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/platform/macos/TPCircularBuffer/TPCircularBuffer.podspec b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.podspec new file mode 100644 index 00000000000..b4326ce4fea --- /dev/null +++ b/src/platform/macos/TPCircularBuffer/TPCircularBuffer.podspec @@ -0,0 +1,21 @@ +license = <<-END +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." +END + +Pod::Spec.new do |s| + s.name = "TPCircularBuffer" + s.version = '1.6.1' + s.summary = 'A simple, fast circular buffer implementation.' + s.homepage = 'https://github.com/michaeltyson/TPCircularBuffer' + s.authors = { 'Michael Tyson' => 'michael@atastypixel.com' } + s.license = { :type => 'MIT', :text => license } + s.source = { :git => 'https://github.com/michaeltyson/TPCircularBuffer.git', :tag => '1.6.1' } + s.source_files = '*.{c,h}' + s.requires_arc = false + s.frameworks = 'AudioToolbox' + s.ios.deployment_target = '4.3' + s.osx.deployment_target = '10.8' + s.tvos.deployment_target = '9.0' +end diff --git a/sunshine/platform/macos/av_audio.h b/src/platform/macos/av_audio.h similarity index 91% rename from sunshine/platform/macos/av_audio.h rename to src/platform/macos/av_audio.h index 8c04e5bb7b3..130fb5d110c 100644 --- a/sunshine/platform/macos/av_audio.h +++ b/src/platform/macos/av_audio.h @@ -3,7 +3,7 @@ #import -#include "sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h" +#include "src/platform/macos/TPCircularBuffer/TPCircularBuffer.h" #define kBufferLength 2048 diff --git a/sunshine/platform/macos/av_audio.m b/src/platform/macos/av_audio.m similarity index 100% rename from sunshine/platform/macos/av_audio.m rename to src/platform/macos/av_audio.m diff --git a/sunshine/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h similarity index 89% rename from sunshine/platform/macos/av_img_t.h rename to src/platform/macos/av_img_t.h index 7af3cbcc839..a7a24935ed1 100644 --- a/sunshine/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -1,7 +1,7 @@ #ifndef av_img_t_h #define av_img_t_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include #include diff --git a/sunshine/platform/macos/av_video.h b/src/platform/macos/av_video.h similarity index 100% rename from sunshine/platform/macos/av_video.h rename to src/platform/macos/av_video.h diff --git a/sunshine/platform/macos/av_video.m b/src/platform/macos/av_video.m similarity index 100% rename from sunshine/platform/macos/av_video.m rename to src/platform/macos/av_video.m diff --git a/sunshine/platform/macos/display.mm b/src/platform/macos/display.mm similarity index 96% rename from sunshine/platform/macos/display.mm rename to src/platform/macos/display.mm index f00297c2979..1601e3ecd83 100644 --- a/sunshine/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -1,10 +1,10 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_img_t.h" -#include "sunshine/platform/macos/av_video.h" -#include "sunshine/platform/macos/nv12_zero_device.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_img_t.h" +#include "src/platform/macos/av_video.h" +#include "src/platform/macos/nv12_zero_device.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/input.cpp b/src/platform/macos/input.cpp similarity index 99% rename from sunshine/platform/macos/input.cpp rename to src/platform/macos/input.cpp index fbea6619730..3d9c21a35ec 100644 --- a/sunshine/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -2,9 +2,9 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" // Delay for a double click // FIXME: we probably want to make this configurable diff --git a/sunshine/platform/macos/microphone.mm b/src/platform/macos/microphone.mm similarity index 94% rename from sunshine/platform/macos/microphone.mm rename to src/platform/macos/microphone.mm index 4b6516909c9..cfe9562da17 100644 --- a/sunshine/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -1,8 +1,8 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_audio.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_audio.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/macos/misc.cpp b/src/platform/macos/misc.cpp similarity index 98% rename from sunshine/platform/macos/misc.cpp rename to src/platform/macos/misc.cpp index fdc4668817e..a7b0a89f53f 100644 --- a/sunshine/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -6,8 +6,8 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" using namespace std::literals; namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/misc.h b/src/platform/macos/misc.h similarity index 100% rename from sunshine/platform/macos/misc.h rename to src/platform/macos/misc.h diff --git a/sunshine/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp similarity index 95% rename from sunshine/platform/macos/nv12_zero_device.cpp rename to src/platform/macos/nv12_zero_device.cpp index 7e0a4a77606..1af0e058058 100644 --- a/sunshine/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -1,7 +1,7 @@ -#include "sunshine/platform/macos/nv12_zero_device.h" -#include "sunshine/platform/macos/av_img_t.h" +#include "src/platform/macos/nv12_zero_device.h" +#include "src/platform/macos/av_img_t.h" -#include "sunshine/video.h" +#include "src/video.h" extern "C" { #include "libavutil/imgutils.h" diff --git a/sunshine/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h similarity index 96% rename from sunshine/platform/macos/nv12_zero_device.h rename to src/platform/macos/nv12_zero_device.h index 847a8f0ab19..3b74ebcc6ba 100644 --- a/sunshine/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -1,7 +1,7 @@ #ifndef vtdevice_h #define vtdevice_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace platf { diff --git a/sunshine/platform/macos/publish.cpp b/src/platform/macos/publish.cpp similarity index 99% rename from sunshine/platform/macos/publish.cpp rename to src/platform/macos/publish.cpp index cc10cd826ea..bd943ec9966 100644 --- a/sunshine/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/windows/PolicyConfig.h b/src/platform/windows/PolicyConfig.h similarity index 96% rename from sunshine/platform/windows/PolicyConfig.h rename to src/platform/windows/PolicyConfig.h index 60f36a68b2e..be18ec6f8a3 100644 --- a/sunshine/platform/windows/PolicyConfig.h +++ b/src/platform/windows/PolicyConfig.h @@ -1,164 +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 -}; - -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// 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/src/platform/windows/audio.cpp similarity index 96% rename from sunshine/platform/windows/audio.cpp rename to src/platform/windows/audio.cpp index 61df413afa0..0a041a40489 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -1,988 +1,988 @@ -// -// 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; -}; - -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 { - none, - mono, - stereo, - surr51, - surr71, - } type; - - std::string_view name; - int channels; - int channel_mask; -} formats[] { - { - format_t::mono, - "Mono"sv, - 1, - SPEAKER_FRONT_CENTER, - }, - { - format_t::stereo, - "Stereo"sv, - 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, - }, - { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT, - }, - { - format_t::surr71, - "Surround 7.1"sv, - 8, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, - }, -}; - -static format_t surround_51_side_speakers { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, -}; - -void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { - wave_format->nChannels = format.channels; - wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; - wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; - - if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; - } -} - -int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { - wave_format->wBitsPerSample = 16; - wave_format->nSamplesPerSec = sample_rate; - switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: - break; - case WAVE_FORMAT_IEEE_FLOAT: - break; - case WAVE_FORMAT_EXTENSIBLE: { - auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); - if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { - wave_ex->Samples.wValidBitsPerSample = 16; - wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - } - - BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; - } - default: - BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; - return -1; - }; - - return 0; -} - -audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { - audio_client_t audio_client; - auto status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **)&audio_client); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - wave_format_t wave_format; - status = audio_client->GetMixFormat(&wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - if(init_wave_format(wave_format, sample_rate)) { - return nullptr; - } - set_wave_format(wave_format, format); - - status = audio_client->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, - wave_format.get(), - nullptr); - - if(status) { - BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return audio_client; -} - -const wchar_t *no_null(const wchar_t *str) { - return str ? str : L"Unknown"; -} - -format_t::type_e validate_device(device_t &device, int sample_rate) { - for(const auto &format : formats) { - // Ensure WaveFromat is compatible - auto audio_client = make_audio_client(device, format, sample_rate); - - BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); - - if(audio_client) { - return format.type; - } - } - - return format_t::none; -} - -device_t default_device(device_enum_t &device_enum) { - device_t device; - HRESULT status; - status = device_enum->GetDefaultAudioEndpoint( - eRender, - eConsole, - &device); - - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - return device; -} - -class mic_wasapi_t : public mic_t { -public: - 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(); - - if(capture_result != capture_e::ok) { - return capture_result; - } - } - - switch(channels_out) { - case 2: - pipe->to_stereo(sample_out, sample_buf); - break; - case 6: - pipe->to_51(sample_out, sample_buf); - break; - case 8: - pipe->to_71(sample_out, sample_buf); - break; - default: - BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; - return capture_e::error; - } - - // The excess samples should be in front of the queue - 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, 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; - - 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 << ']'; - 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; - } - } - - 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, frame_size) * 2 * channels_in }; - 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 * channels_in); - - 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; - - // 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 { -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 = config::audio.virtual_sink; - for(auto x = 0; x < count; ++x) { - audio::device_t device; - collection->Item(x, &device); - - auto type = validate_device(device, SAMPLE_RATE); - if(type == format_t::none) { - continue; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - audio::prop_t prop; - device->OpenPropertyStore(STGM_READ, &prop); - - prop_var_t adapter_friendly_name; - prop_var_t device_friendly_name; - prop_var_t device_desc; - - prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); - prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); - prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); - - auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); - BOOST_LOG(verbose) - << L"===== Device ====="sv << std::endl - << L"Device ID : "sv << wstring.get() << std::endl - << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl - << L"Adapter name : "sv << adapter_name << std::endl - << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl - << std::endl; - - if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { - virtual_device_id = converter.to_bytes(wstring.get()); - } - } - - if(!virtual_device_id.empty()) { - sink.null = std::make_optional(sink_t::null_t { - "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, - }); - } - - return sink; - } - - std::unique_ptr 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, channels)) { - return nullptr; - } - - return mic; - } - - /** - * If the requested sink is a virtual sink, meaning no speakers attached to - * the host, then we can seamlessly set the format to stereo and surround sound. - * - * Any virtual sink detected will be prefixed by: - * virtual-(format name) - * If it doesn't contain that prefix, then the format will not be changed - */ - std::optional set_format(const std::string &sink) { - std::string_view sv { sink.c_str(), sink.size() }; - - format_t::type_e type = format_t::none; - // sink format: - // [virtual-(format name)]device_id - auto prefix = "virtual-"sv; - if(sv.find(prefix) == 0) { - sv = sv.substr(prefix.size(), sv.size() - prefix.size()); - - for(auto &format : formats) { - auto &name = format.name; - if(sv.find(name) == 0) { - type = format.type; - sv = sv.substr(name.size(), sv.size() - name.size()); - - break; - } - } - } - - auto wstring_device_id = converter.from_bytes(sv.data()); - - if(type == format_t::none) { - // wstring_device_id does not contain virtual-(format name) - // It's a simple deviceId, just pass it back - return std::make_optional(std::move(wstring_device_id)); - } - - wave_format_t wave_format; - auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - if(init_wave_format(wave_format, SAMPLE_RATE)) { - return std::nullopt; - } - set_wave_format(wave_format, formats[(int)type - 1]); - - WAVEFORMATEXTENSIBLE p {}; - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - - // Surround 5.1 might contain side-{left, right} instead of speaker in the back - // Try again with different speaker mask. - if(status == 0x88890008 && type == format_t::surr51) { - set_wave_format(wave_format, surround_51_side_speakers); - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - } - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - return std::make_optional(std::move(wstring_device_id)); - } - - int set_sink(const std::string &sink) override { - auto wstring_device_id = set_format(sink); - if(!wstring_device_id) { - return -1; - } - - int failure {}; - for(int x = 0; x < (int)ERole_enum_count; ++x) { - auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); - if(status) { - BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; - - ++failure; - } - } - - return failure; - } - - int init() { - auto status = CoCreateInstance( - CLSID_CPolicyConfigClient, - nullptr, - CLSCTX_ALL, - IID_IPolicyConfig, - (void **)&policy); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~audio_control_t() override {} - - policy_t policy; -}; -} // namespace platf::audio - -namespace platf { - -// 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()) { - return nullptr; - } - - return control; -} - -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; +}; + +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 { + none, + mono, + stereo, + surr51, + surr71, + } type; + + std::string_view name; + int channels; + int channel_mask; +} formats[] { + { + format_t::mono, + "Mono"sv, + 1, + SPEAKER_FRONT_CENTER, + }, + { + format_t::stereo, + "Stereo"sv, + 2, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + }, + { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT, + }, + { + format_t::surr71, + "Surround 7.1"sv, + 8, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, + }, +}; + +static format_t surround_51_side_speakers { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, +}; + +void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { + wave_format->nChannels = format.channels; + wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; + wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; + + if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; + } +} + +int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { + wave_format->wBitsPerSample = 16; + wave_format->nSamplesPerSec = sample_rate; + switch(wave_format->wFormatTag) { + case WAVE_FORMAT_PCM: + break; + case WAVE_FORMAT_IEEE_FLOAT: + break; + case WAVE_FORMAT_EXTENSIBLE: { + auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); + if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { + wave_ex->Samples.wValidBitsPerSample = 16; + wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + } + + BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; + } + default: + BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; + return -1; + }; + + return 0; +} + +audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { + audio_client_t audio_client; + auto status = device->Activate( + IID_IAudioClient, + CLSCTX_ALL, + nullptr, + (void **)&audio_client); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + wave_format_t wave_format; + status = audio_client->GetMixFormat(&wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + if(init_wave_format(wave_format, sample_rate)) { + return nullptr; + } + set_wave_format(wave_format, format); + + status = audio_client->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, 0, + wave_format.get(), + nullptr); + + if(status) { + BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return audio_client; +} + +const wchar_t *no_null(const wchar_t *str) { + return str ? str : L"Unknown"; +} + +format_t::type_e validate_device(device_t &device, int sample_rate) { + for(const auto &format : formats) { + // Ensure WaveFromat is compatible + auto audio_client = make_audio_client(device, format, sample_rate); + + BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); + + if(audio_client) { + return format.type; + } + } + + return format_t::none; +} + +device_t default_device(device_enum_t &device_enum) { + device_t device; + HRESULT status; + status = device_enum->GetDefaultAudioEndpoint( + eRender, + eConsole, + &device); + + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + return device; +} + +class mic_wasapi_t : public mic_t { +public: + 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(); + + if(capture_result != capture_e::ok) { + return capture_result; + } + } + + switch(channels_out) { + case 2: + pipe->to_stereo(sample_out, sample_buf); + break; + case 6: + pipe->to_51(sample_out, sample_buf); + break; + case 8: + pipe->to_71(sample_out, sample_buf); + break; + default: + BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; + return capture_e::error; + } + + // The excess samples should be in front of the queue + 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, 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; + + 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 << ']'; + 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; + } + } + + 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, frame_size) * 2 * channels_in }; + 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 * channels_in); + + 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; + + // 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 { +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 = config::audio.virtual_sink; + for(auto x = 0; x < count; ++x) { + audio::device_t device; + collection->Item(x, &device); + + auto type = validate_device(device, SAMPLE_RATE); + if(type == format_t::none) { + continue; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + audio::prop_t prop; + device->OpenPropertyStore(STGM_READ, &prop); + + prop_var_t adapter_friendly_name; + prop_var_t device_friendly_name; + prop_var_t device_desc; + + prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); + prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); + prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); + + auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); + BOOST_LOG(verbose) + << L"===== Device ====="sv << std::endl + << L"Device ID : "sv << wstring.get() << std::endl + << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl + << L"Adapter name : "sv << adapter_name << std::endl + << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl + << std::endl; + + if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { + virtual_device_id = converter.to_bytes(wstring.get()); + } + } + + if(!virtual_device_id.empty()) { + sink.null = std::make_optional(sink_t::null_t { + "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, + }); + } + + return sink; + } + + std::unique_ptr 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, channels)) { + return nullptr; + } + + return mic; + } + + /** + * If the requested sink is a virtual sink, meaning no speakers attached to + * the host, then we can seamlessly set the format to stereo and surround sound. + * + * Any virtual sink detected will be prefixed by: + * virtual-(format name) + * If it doesn't contain that prefix, then the format will not be changed + */ + std::optional set_format(const std::string &sink) { + std::string_view sv { sink.c_str(), sink.size() }; + + format_t::type_e type = format_t::none; + // sink format: + // [virtual-(format name)]device_id + auto prefix = "virtual-"sv; + if(sv.find(prefix) == 0) { + sv = sv.substr(prefix.size(), sv.size() - prefix.size()); + + for(auto &format : formats) { + auto &name = format.name; + if(sv.find(name) == 0) { + type = format.type; + sv = sv.substr(name.size(), sv.size() - name.size()); + + break; + } + } + } + + auto wstring_device_id = converter.from_bytes(sv.data()); + + if(type == format_t::none) { + // wstring_device_id does not contain virtual-(format name) + // It's a simple deviceId, just pass it back + return std::make_optional(std::move(wstring_device_id)); + } + + wave_format_t wave_format; + auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + if(init_wave_format(wave_format, SAMPLE_RATE)) { + return std::nullopt; + } + set_wave_format(wave_format, formats[(int)type - 1]); + + WAVEFORMATEXTENSIBLE p {}; + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + + // Surround 5.1 might contain side-{left, right} instead of speaker in the back + // Try again with different speaker mask. + if(status == 0x88890008 && type == format_t::surr51) { + set_wave_format(wave_format, surround_51_side_speakers); + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + } + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + return std::make_optional(std::move(wstring_device_id)); + } + + int set_sink(const std::string &sink) override { + auto wstring_device_id = set_format(sink); + if(!wstring_device_id) { + return -1; + } + + int failure {}; + for(int x = 0; x < (int)ERole_enum_count; ++x) { + auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); + if(status) { + BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; + + ++failure; + } + } + + return failure; + } + + int init() { + auto status = CoCreateInstance( + CLSID_CPolicyConfigClient, + nullptr, + CLSCTX_ALL, + IID_IPolicyConfig, + (void **)&policy); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~audio_control_t() override {} + + policy_t policy; +}; +} // namespace platf::audio + +namespace platf { + +// 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()) { + return nullptr; + } + + return control; +} + +std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } + return std::make_unique(); +} +} // namespace platf diff --git a/sunshine/platform/windows/display.h b/src/platform/windows/display.h similarity index 97% rename from sunshine/platform/windows/display.h rename to src/platform/windows/display.h index 6a1501354e4..ece700aa5ea 100644 --- a/sunshine/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -1,177 +1,177 @@ -// -// Created by loki on 4/23/20. -// - -#ifndef SUNSHINE_DISPLAY_H -#define SUNSHINE_DISPLAY_H - -#include -#include -#include -#include -#include -#include - -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" - -namespace platf::dxgi { -extern const char *format_str[]; - -template -void Release(T *dxgi) { - dxgi->Release(); -} - -using factory1_t = util::safe_ptr>; -using dxgi_t = util::safe_ptr>; -using dxgi1_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using device_ctx_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -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>; -using vs_t = util::safe_ptr>; -using ps_t = util::safe_ptr>; -using blend_t = util::safe_ptr>; -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 raster_state_t = util::safe_ptr>; -using sampler_state_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>; - -namespace video { -using device_t = util::safe_ptr>; -using ctx_t = util::safe_ptr>; -using processor_t = util::safe_ptr>; -using processor_out_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 { - std::vector img_data; - - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x, y; - bool visible; -}; - -class gpu_cursor_t { -public: - gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; - void set_pos(LONG rel_x, LONG rel_y, bool visible) { - cursor_view.TopLeftX = rel_x; - cursor_view.TopLeftY = rel_y; - - this->visible = visible; - } - - void set_texture(LONG width, LONG height, texture2d_t &&texture) { - cursor_view.Width = width; - cursor_view.Height = height; - - this->texture = std::move(texture); - } - - texture2d_t texture; - shader_res_t input_res; - - D3D11_VIEWPORT cursor_view; - - bool visible; -}; - -class duplication_t { -public: - dup_t dup; - bool has_frame {}; - bool use_dwmflush {}; - - capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); - capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); - capture_e release_frame(); - - ~duplication_t(); -}; - -class display_base_t : public display_t { -public: - int init(int framerate, const std::string &display_name); - - std::chrono::nanoseconds delay; - - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - duplication_t dup; - - 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 { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img) override; - - int init(int framerate, const std::string &display_name); - - cursor_t cursor; - D3D11_MAPPED_SUBRESOURCE img_info; - texture2d_t texture; -}; - -class display_vram_t : public display_base_t, public std::enable_shared_from_this { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img_base) override; - - int init(int framerate, const std::string &display_name); - - std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; - - sampler_state_t sampler_linear; - - blend_t blend_enable; - blend_t blend_disable; - - ps_t scene_ps; - vs_t scene_vs; - - texture2d_t src; - gpu_cursor_t cursor; -}; -} // namespace platf::dxgi - -#endif +// +// Created by loki on 4/23/20. +// + +#ifndef SUNSHINE_DISPLAY_H +#define SUNSHINE_DISPLAY_H + +#include +#include +#include +#include +#include +#include + +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +namespace platf::dxgi { +extern const char *format_str[]; + +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using dxgi_t = util::safe_ptr>; +using dxgi1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using device_ctx_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +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>; +using vs_t = util::safe_ptr>; +using ps_t = util::safe_ptr>; +using blend_t = util::safe_ptr>; +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 raster_state_t = util::safe_ptr>; +using sampler_state_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>; + +namespace video { +using device_t = util::safe_ptr>; +using ctx_t = util::safe_ptr>; +using processor_t = util::safe_ptr>; +using processor_out_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 { + std::vector img_data; + + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + int x, y; + bool visible; +}; + +class gpu_cursor_t { +public: + gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; + void set_pos(LONG rel_x, LONG rel_y, bool visible) { + cursor_view.TopLeftX = rel_x; + cursor_view.TopLeftY = rel_y; + + this->visible = visible; + } + + void set_texture(LONG width, LONG height, texture2d_t &&texture) { + cursor_view.Width = width; + cursor_view.Height = height; + + this->texture = std::move(texture); + } + + texture2d_t texture; + shader_res_t input_res; + + D3D11_VIEWPORT cursor_view; + + bool visible; +}; + +class duplication_t { +public: + dup_t dup; + bool has_frame {}; + bool use_dwmflush {}; + + capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e release_frame(); + + ~duplication_t(); +}; + +class display_base_t : public display_t { +public: + int init(int framerate, const std::string &display_name); + + std::chrono::nanoseconds delay; + + factory1_t factory; + adapter_t adapter; + output_t output; + device_t device; + device_ctx_t device_ctx; + duplication_t dup; + + 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 { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img) override; + + int init(int framerate, const std::string &display_name); + + cursor_t cursor; + D3D11_MAPPED_SUBRESOURCE img_info; + texture2d_t texture; +}; + +class display_vram_t : public display_base_t, public std::enable_shared_from_this { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img_base) override; + + int init(int framerate, const std::string &display_name); + + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; + + sampler_state_t sampler_linear; + + blend_t blend_enable; + blend_t blend_disable; + + ps_t scene_ps; + vs_t scene_vs; + + texture2d_t src; + gpu_cursor_t cursor; +}; +} // namespace platf::dxgi + +#endif diff --git a/sunshine/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp similarity index 100% rename from sunshine/platform/windows/display_base.cpp rename to src/platform/windows/display_base.cpp diff --git a/sunshine/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp similarity index 96% rename from sunshine/platform/windows/display_ram.cpp rename to src/platform/windows/display_ram.cpp index 285f0bcb6d6..868ee8c251f 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -1,327 +1,327 @@ -#include "display.h" -#include "sunshine/main.h" - -namespace platf { -using namespace std::literals; -} - -namespace platf::dxgi { -struct img_t : public ::platf::img_t { - ~img_t() override { - delete[] data; - data = nullptr; - } -}; - -void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { - int height = cursor.shape_info.Height / 2; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.{x,y} < 0, skip parts of the cursor.img_data - 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 - 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_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 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)); - - auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; - - 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]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - - auto skip_x = cursor_skip_x; - for(int x = 0; x < bytes_per_row; ++x) { - for(auto bit = 0u; bit < 8; ++bit) { - if(skip_x > 0) { - --skip_x; - - continue; - } - - int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; - int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; - - *img_pixel_p &= and_; - *img_pixel_p ^= xor_; - - ++img_pixel_p; - } - - ++and_mask; - ++xor_mask; - } - } -} - -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; - - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = colors_out[3]; - if(alpha == 255) { - *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; - } -} - -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]; - if(alpha == 0xFF) { - *img_pixel_p ^= cursor_pixel; - } - else { - *img_pixel_p = cursor_pixel; - } -} - -void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { - int height = cursor.shape_info.Height; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.y < 0, skip parts of the cursor.img_data - 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 - 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 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]; - - 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)); - - 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 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) { - if(masked) { - apply_color_masked(img_pixel_p, cursor_pixel); - } - else { - apply_color_alpha(img_pixel_p, cursor_pixel); - } - ++img_pixel_p; - }); - } -} - -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 << ']'; - } -} - -capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - if(frame_info.PointerShapeBufferSize > 0) { - auto &img_data = cursor.img_data; - - img_data.resize(frame_info.PointerShapeBufferSize); - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - 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) { - { - 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; - } - - //Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - } - - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } - - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - 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 = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); - - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; - - if(!update_flag) { - return capture_e::timeout; - } - - 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); - } - - return capture_e::ok; -} - -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]; - - return img; -} - -int display_ram_t::dummy_img(platf::img_t *img) { - return 0; -} - -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_TEXTURE2D_DESC t {}; - 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; - - 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() << ']'; - return -1; - } - - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} -} // namespace platf::dxgi +#include "display.h" +#include "sunshine/main.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_t : public ::platf::img_t { + ~img_t() override { + delete[] data; + data = nullptr; + } +}; + +void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { + int height = cursor.shape_info.Height / 2; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.{x,y} < 0, skip parts of the cursor.img_data + 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 + 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_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 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)); + + auto pixels_per_byte = width / pitch; + auto bytes_per_row = delta_width / pixels_per_byte; + + 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]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + + auto skip_x = cursor_skip_x; + for(int x = 0; x < bytes_per_row; ++x) { + for(auto bit = 0u; bit < 8; ++bit) { + if(skip_x > 0) { + --skip_x; + + continue; + } + + int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; + int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; + + *img_pixel_p &= and_; + *img_pixel_p ^= xor_; + + ++img_pixel_p; + } + + ++and_mask; + ++xor_mask; + } + } +} + +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; + + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = colors_out[3]; + if(alpha == 255) { + *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; + } +} + +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]; + if(alpha == 0xFF) { + *img_pixel_p ^= cursor_pixel; + } + else { + *img_pixel_p = cursor_pixel; + } +} + +void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { + int height = cursor.shape_info.Height; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.y < 0, skip parts of the cursor.img_data + 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 + 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 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]; + + 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)); + + 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 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) { + if(masked) { + apply_color_masked(img_pixel_p, cursor_pixel); + } + else { + apply_color_alpha(img_pixel_p, cursor_pixel); + } + ++img_pixel_p; + }); + } +} + +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 << ']'; + } +} + +capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + if(frame_info.PointerShapeBufferSize > 0) { + auto &img_data = cursor.img_data; + + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + 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) { + { + 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; + } + + //Copy from GPU to CPU + device_ctx->CopyResource(texture.get(), src.get()); + } + + if(img_info.pData) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + } + + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + 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 = + (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && + (cursor_visible && cursor.visible); + + const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + + if(!update_flag) { + return capture_e::timeout; + } + + 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); + } + + return capture_e::ok; +} + +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]; + + return img; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + return 0; +} + +int display_ram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_TEXTURE2D_DESC t {}; + 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; + + 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() << ']'; + return -1; + } + + // map the texture simply to get the pitch and stride + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp similarity index 96% rename from sunshine/platform/windows/display_vram.cpp rename to src/platform/windows/display_vram.cpp index 9a932665b53..76ce57a3c93 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -1,888 +1,888 @@ -#include - -#include - -#include -#include - -extern "C" { -#include -#include -} - -#include "display.h" -#include "sunshine/main.h" -#include "sunshine/video.h" - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" -namespace platf { -using namespace std::literals; -} - -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; - -namespace platf::dxgi { - -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 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 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; -} - -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 { - std::shared_ptr display; - - shader_res_t input_res; - render_target_t scene_rt; - - texture2d_t texture; - - ~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; - 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; - } - - 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; - - for(auto x = 0; x < bytes; ++x) { - for(auto c = 7; c >= 0; --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; - - 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; - } - - 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; - } - ++and_mask; - ++xor_mask; - } - - 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, "main_ps", "ps_5_0"); -} - -blob_t compile_vertex_shader(LPCSTR file) { - return compile_shader(file, "main_vs", "vs_5_0"); -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = 1; - - auto status = device->CreateShaderResourceView(tex, &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; - } - - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device->CreateRenderTargetView(tex, &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; - } - - return 0; -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { - 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 = 1; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - - 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; - } - - return init_rt(device, shader_res, render_target, width, height, format, tex.get()); -} - -class hwdevice_t : public platf::hwdevice_t { -public: - int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; - - device_ctx_p->IASetInputLayout(input_layout.get()); - - _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, &img.input_res); - device_ctx_p->Draw(3, 0); - - // 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, &img.input_res); - device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); - - return 0; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &::video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &::video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &::video::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; - } - - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - this->color_matrix = std::move(color_matrix); - } - - int set_frame(AVFrame *frame) { - this->hwframe.reset(frame); - this->frame = frame; - - auto device_p = (device_t::pointer)data; - - auto out_width = frame->width; - auto out_height = frame->height; - - float in_width = img.display->width; - float in_height = img.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 }; - - D3D11_TEXTURE2D_DESC t {}; - 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 = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; - - auto 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.width = out_width; - img.height = out_height; - img.data = (std::uint8_t *)img.texture.get(); - img.row_pitch = out_width * 4; - img.pixel_pitch = 4; - - 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; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - DXGI_FORMAT_R8_UNORM, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - 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_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - - 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; - } - - // 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; - - return 0; - } - - int init( - std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, - pix_fmt_e pix_fmt) { - - HRESULT status; - - device_p->AddRef(); - data = device_p; - - this->device_ctx_p = device_ctx_p; - - format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); - status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - 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 convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - 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 convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - 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 convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - 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; - } - - color_matrix = make_buffer(device_p, ::video::colors[0]); - if(!color_matrix) { - BOOST_LOG(error) << "Failed to create color matrix 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 - }; - - status = device_p->CreateInputLayout( - &layout_desc, 1, - convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), - &input_layout); - - img.display = std::move(display); - - // 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->IASetInputLayout(input_layout.get()); - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); - - return 0; - } - - ~hwdevice_t() override { - if(data) { - ((ID3D11Device *)data)->Release(); - } - } - -private: - void _init_view_port(float x, float y, float width, float height) { - D3D11_VIEWPORT view { - x, y, - width, height, - 0.0f, 1.0f - }; - - device_ctx_p->RSSetViewports(1, &view); - } - - void _init_view_port(float width, float height) { - _init_view_port(0.0f, 0.0f, width, height); - } - -public: - frame_t hwframe; - - ::video::color_t *color_p; - - buf_t info_scene; - buf_t color_matrix; - - input_layout_t input_layout; - - render_target_t nv12_Y_rt; - render_target_t nv12_UV_rt; - - // The image referenced by hwframe - // The resulting image is stored here. - 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; - - DXGI_FORMAT format; - - device_ctx_t::pointer device_ctx_p; -}; - -capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -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; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - 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; - - if(!update_flag) { - return capture_e::timeout; - } - - if(frame_info.PointerShapeBufferSize > 0) { - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; - - util::buffer_t img_data { frame_info.PointerShapeBufferSize }; - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - - auto cursor_img = make_cursor_image(std::move(img_data), shape_info); - - D3D11_SUBRESOURCE_DATA data { - std::begin(cursor_img), - 4 * shape_info.Width, - 0 - }; - - // 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.SampleDesc.Count = 1; - 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); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - // Free resources before allocating on the next line. - cursor.input_res.reset(); - status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - cursor.set_texture(t.Width, t.Height, std::move(texture)); - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); - } - - if(frame_update_flag) { - src.reset(); - 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; - } - } - - device_ctx->CopyResource(img->texture.get(), src.get()); - if(cursor.visible) { - D3D11_VIEWPORT view { - 0.0f, 0.0f, - (float)width, (float)height, - 0.0f, 1.0f - }; - - device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &view); - device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); - device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); - device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->RSSetViewports(1, &cursor.cursor_view); - device_ctx->Draw(3, 0); - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - } - - return capture_e::ok; -} - -int display_vram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - 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.ComparisonFunc = D3D11_COMPARISON_NEVER; - sampler_desc.MinLOD = 0; - sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; - - auto status = device->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; - } - - status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->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; - } - - blend_enable = make_blend(device.get(), true); - blend_disable = make_blend(device.get(), false); - - if(!blend_disable || !blend_enable) { - return -1; - } - - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - - return 0; -} - -std::shared_ptr display_vram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->width = width; - img->height = height; - img->display = shared_from_this(); - - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - 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 | D3D11_BIND_RENDER_TARGET; - - auto status = device->CreateTexture2D(&t, &data, &img->texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { - return nullptr; - } - - img->data = (std::uint8_t *)img->texture.get(); - - return img; -} - -int display_vram_t::dummy_img(platf::img_t *img_base) { - auto img = (img_d3d_t *)img_base; - - if(img->texture) { - return 0; - } - - img->row_pitch = width * 4; - auto dummy_data = std::make_unique(width * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), width * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - 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; - - 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 = std::move(tex); - img->data = (std::uint8_t *)img->texture.get(); - - return 0; -} - -std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e 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; - } - - auto hwdevice = std::make_shared(); - - auto ret = hwdevice->init( - shared_from_this(), - device.get(), - device_ctx.get(), - pix_fmt); - - if(ret) { - return nullptr; - } - - return hwdevice; -} - -int init() { - BOOST_LOG(info) << "Compiling shaders..."sv; - scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); - if(!scene_vs_hlsl) { - return -1; - } - - convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); - if(!convert_Y_ps_hlsl) { - return -1; - } - - convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); - if(!convert_UV_ps_hlsl) { - return -1; - } - - 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_SHADERS_DIR "/ScenePS.hlsl"); - if(!scene_ps_hlsl) { - return -1; - } - BOOST_LOG(info) << "Compiled shaders"sv; - - return 0; -} -} // namespace platf::dxgi \ No newline at end of file +#include + +#include + +#include +#include + +extern "C" { +#include +#include +} + +#include "display.h" +#include "sunshine/main.h" +#include "sunshine/video.h" + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" +namespace platf { +using namespace std::literals; +} + +static void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} + +using frame_t = util::safe_ptr; + +namespace platf::dxgi { + +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 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 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; +} + +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 { + std::shared_ptr display; + + shader_res_t input_res; + render_target_t scene_rt; + + texture2d_t texture; + + ~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; + 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; + } + + 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; + + for(auto x = 0; x < bytes; ++x) { + for(auto c = 7; c >= 0; --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; + + 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; + } + + 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; + } + ++and_mask; + ++xor_mask; + } + + 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, "main_ps", "ps_5_0"); +} + +blob_t compile_vertex_shader(LPCSTR file) { + return compile_shader(file, "main_vs", "vs_5_0"); +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + shader_resource_desc.Texture2D.MipLevels = 1; + + auto status = device->CreateShaderResourceView(tex, &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; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device->CreateRenderTargetView(tex, &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; + } + + return 0; +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { + 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 = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + + 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; + } + + return init_rt(device, shader_res, render_target, width, height, format, tex.get()); +} + +class hwdevice_t : public platf::hwdevice_t { +public: + int convert(platf::img_t &img_base) override { + auto &img = (img_d3d_t &)img_base; + + device_ctx_p->IASetInputLayout(input_layout.get()); + + _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, &img.input_res); + device_ctx_p->Draw(3, 0); + + // 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, &img.input_res); + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &::video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &::video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &::video::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; + } + + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + this->color_matrix = std::move(color_matrix); + } + + int set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; + + auto device_p = (device_t::pointer)data; + + auto out_width = frame->width; + auto out_height = frame->height; + + float in_width = img.display->width; + float in_height = img.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 }; + + D3D11_TEXTURE2D_DESC t {}; + 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 = format; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + auto 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.width = out_width; + img.height = out_height; + img.data = (std::uint8_t *)img.texture.get(); + img.row_pitch = out_width * 4; + img.pixel_pitch = 4; + + 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; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + 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_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + + 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; + } + + // 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; + + return 0; + } + + int init( + std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, + pix_fmt_e pix_fmt) { + + HRESULT status; + + device_p->AddRef(); + data = device_p; + + this->device_ctx_p = device_ctx_p; + + format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + 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 convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + 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 convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + 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 convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + 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; + } + + color_matrix = make_buffer(device_p, ::video::colors[0]); + if(!color_matrix) { + BOOST_LOG(error) << "Failed to create color matrix 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 + }; + + status = device_p->CreateInputLayout( + &layout_desc, 1, + convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), + &input_layout); + + img.display = std::move(display); + + // 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->IASetInputLayout(input_layout.get()); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); + + return 0; + } + + ~hwdevice_t() override { + if(data) { + ((ID3D11Device *)data)->Release(); + } + } + +private: + void _init_view_port(float x, float y, float width, float height) { + D3D11_VIEWPORT view { + x, y, + width, height, + 0.0f, 1.0f + }; + + device_ctx_p->RSSetViewports(1, &view); + } + + void _init_view_port(float width, float height) { + _init_view_port(0.0f, 0.0f, width, height); + } + +public: + frame_t hwframe; + + ::video::color_t *color_p; + + buf_t info_scene; + buf_t color_matrix; + + input_layout_t input_layout; + + render_target_t nv12_Y_rt; + render_target_t nv12_UV_rt; + + // The image referenced by hwframe + // The resulting image is stored here. + 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; + + DXGI_FORMAT format; + + device_ctx_t::pointer device_ctx_p; +}; + +capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +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; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + 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; + + if(!update_flag) { + return capture_e::timeout; + } + + if(frame_info.PointerShapeBufferSize > 0) { + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; + + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + + auto cursor_img = make_cursor_image(std::move(img_data), shape_info); + + D3D11_SUBRESOURCE_DATA data { + std::begin(cursor_img), + 4 * shape_info.Width, + 0 + }; + + // 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.SampleDesc.Count = 1; + 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); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + // Free resources before allocating on the next line. + cursor.input_res.reset(); + status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + cursor.set_texture(t.Width, t.Height, std::move(texture)); + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + } + + if(frame_update_flag) { + src.reset(); + 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; + } + } + + device_ctx->CopyResource(img->texture.get(), src.get()); + if(cursor.visible) { + D3D11_VIEWPORT view { + 0.0f, 0.0f, + (float)width, (float)height, + 0.0f, 1.0f + }; + + device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &view); + device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); + device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); + device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->RSSetViewports(1, &cursor.cursor_view); + device_ctx->Draw(3, 0); + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + } + + return capture_e::ok; +} + +int display_vram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + 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.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + auto status = device->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; + } + + status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->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; + } + + blend_enable = make_blend(device.get(), true); + blend_disable = make_blend(device.get(), false); + + if(!blend_disable || !blend_enable) { + return -1; + } + + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->PSSetSamplers(0, 1, &sampler_linear); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + return 0; +} + +std::shared_ptr display_vram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->width = width; + img->height = height; + img->display = shared_from_this(); + + auto dummy_data = std::make_unique(img->row_pitch * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), img->row_pitch * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + 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 | D3D11_BIND_RENDER_TARGET; + + auto status = device->CreateTexture2D(&t, &data, &img->texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { + return nullptr; + } + + img->data = (std::uint8_t *)img->texture.get(); + + return img; +} + +int display_vram_t::dummy_img(platf::img_t *img_base) { + auto img = (img_d3d_t *)img_base; + + if(img->texture) { + return 0; + } + + img->row_pitch = width * 4; + auto dummy_data = std::make_unique(width * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), width * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + 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; + + 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 = std::move(tex); + img->data = (std::uint8_t *)img->texture.get(); + + return 0; +} + +std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e 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; + } + + auto hwdevice = std::make_shared(); + + auto ret = hwdevice->init( + shared_from_this(), + device.get(), + device_ctx.get(), + pix_fmt); + + if(ret) { + return nullptr; + } + + return hwdevice; +} + +int init() { + BOOST_LOG(info) << "Compiling shaders..."sv; + scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); + if(!scene_vs_hlsl) { + return -1; + } + + convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); + if(!convert_Y_ps_hlsl) { + return -1; + } + + convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); + if(!convert_UV_ps_hlsl) { + return -1; + } + + 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_SHADERS_DIR "/ScenePS.hlsl"); + if(!scene_ps_hlsl) { + return -1; + } + BOOST_LOG(info) << "Compiled shaders"sv; + + return 0; +} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/input.cpp b/src/platform/windows/input.cpp old mode 100755 new mode 100644 similarity index 100% rename from sunshine/platform/windows/input.cpp rename to src/platform/windows/input.cpp diff --git a/sunshine/platform/windows/misc.cpp b/src/platform/windows/misc.cpp similarity index 96% rename from sunshine/platform/windows/misc.cpp rename to src/platform/windows/misc.cpp index 828e0374597..c314d3df656 100644 --- a/sunshine/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1,123 +1,123 @@ -#include -#include -#include - - -// prevent clang format from "optimizing" the header include order -// clang-format off -#include -#include -#include -#include -#include -// clang-format on - -#include "sunshine/main.h" -#include "sunshine/utility.h" - -using namespace std::literals; -namespace platf { -using adapteraddrs_t = util::c_ptr; - -std::filesystem::path appdata() { - return L"."sv; -} - -std::string from_sockaddr(const sockaddr *const socket_address) { - char data[INET6_ADDRSTRLEN]; - - auto family = socket_address->sa_family; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); - } - - return std::string { data }; -} - -std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; - - auto family = ip_addr->sa_family; - std::uint16_t port; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6 *)ip_addr)->sin6_port; - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in *)ip_addr)->sin_port; - } - - return { port, std::string { data } }; -} - -adapteraddrs_t get_adapteraddrs() { - adapteraddrs_t info { nullptr }; - ULONG size = 0; - - while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { - info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); - } - - return info; -} - -std::string get_mac_address(const std::string_view &address) { - adapteraddrs_t info = get_adapteraddrs(); - for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { - for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { - if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { - std::stringstream mac_addr; - mac_addr << std::hex; - for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { - if(i > 0) { - mac_addr << ':'; - } - mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; - } - return mac_addr.str(); - } - } - } - BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; -} - -HDESK syncThreadDesktop() { - auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if(!hDesk) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; - - return nullptr; - } - - if(!SetThreadDesktop(hDesk)) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; - } - - CloseDesktop(hDesk); - - return hDesk; -} - -void print_status(const std::string_view &prefix, HRESULT status) { - char err_string[1024]; - - DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - status, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - err_string, - sizeof(err_string), - nullptr); - - BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; -} -} // namespace platf \ No newline at end of file +#include +#include +#include + + +// prevent clang format from "optimizing" the header include order +// clang-format off +#include +#include +#include +#include +#include +// clang-format on + +#include "sunshine/main.h" +#include "sunshine/utility.h" + +using namespace std::literals; +namespace platf { +using adapteraddrs_t = util::c_ptr; + +std::filesystem::path appdata() { + return L"."sv; +} + +std::string from_sockaddr(const sockaddr *const socket_address) { + char data[INET6_ADDRSTRLEN]; + + auto family = socket_address->sa_family; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); + } + + return std::string { data }; +} + +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + std::uint16_t port; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; + } + + return { port, std::string { data } }; +} + +adapteraddrs_t get_adapteraddrs() { + adapteraddrs_t info { nullptr }; + ULONG size = 0; + + while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { + info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); + } + + return info; +} + +std::string get_mac_address(const std::string_view &address) { + adapteraddrs_t info = get_adapteraddrs(); + for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { + for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { + if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { + std::stringstream mac_addr; + mac_addr << std::hex; + for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { + if(i > 0) { + mac_addr << ':'; + } + mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; + } + return mac_addr.str(); + } + } + } + BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; +} + +HDESK syncThreadDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if(!hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; + + return nullptr; + } + + if(!SetThreadDesktop(hDesk)) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; + } + + CloseDesktop(hDesk); + + return hDesk; +} + +void print_status(const std::string_view &prefix, HRESULT status) { + char err_string[1024]; + + DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + err_string, + sizeof(err_string), + nullptr); + + BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; +} +} // namespace platf diff --git a/sunshine/platform/windows/misc.h b/src/platform/windows/misc.h similarity index 93% rename from sunshine/platform/windows/misc.h rename to src/platform/windows/misc.h index 4cd8791face..789fc1b52d9 100644 --- a/sunshine/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -1,13 +1,13 @@ -#ifndef SUNSHINE_WINDOWS_MISC_H -#define SUNSHINE_WINDOWS_MISC_H - -#include -#include -#include - -namespace platf { -void print_status(const std::string_view &prefix, HRESULT status); -HDESK syncThreadDesktop(); -} // namespace platf - -#endif \ No newline at end of file +#ifndef SUNSHINE_WINDOWS_MISC_H +#define SUNSHINE_WINDOWS_MISC_H + +#include +#include +#include + +namespace platf { +void print_status(const std::string_view &prefix, HRESULT status); +HDESK syncThreadDesktop(); +} // namespace platf + +#endif diff --git a/sunshine/platform/windows/publish.cpp b/src/platform/windows/publish.cpp similarity index 96% rename from sunshine/platform/windows/publish.cpp rename to src/platform/windows/publish.cpp index 7fdef3fc994..342e4459c09 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -1,195 +1,195 @@ -#include - -#include - -#include -#include - -#include - -#include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/network.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/thread_safe.h" - -#define _FN(x, ret, args) \ - typedef ret(*x##_fn) args; \ - static x##_fn x - -using namespace std::literals; - -#define __SV(quote) L##quote##sv -#define SV(quote) __SV(quote) - -extern "C" { -#ifndef __MINGW32__ -constexpr auto DNS_REQUEST_PENDING = 9506L; -constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; -constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; -#endif - -#define SERVICE_DOMAIN "local" - -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); -constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_INSTANCE { - LPWSTR pszInstanceName; - LPWSTR pszHostName; - - IP4_ADDRESS *ip4Address; - IP6_ADDRESS *ip6Address; - - WORD wPort; - WORD wPriority; - WORD wWeight; - - // Property list - DWORD dwPropertyCount; - - PWSTR *keys; - PWSTR *values; - - DWORD dwInterfaceIndex; -} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; -#endif - -typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( - _In_ DWORD Status, - _In_ PVOID pQueryContext, - _In_ PDNS_SERVICE_INSTANCE pInstance); - -typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_CANCEL { - PVOID reserved; -} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; - -typedef struct _DNS_SERVICE_REGISTER_REQUEST { - ULONG Version; - ULONG InterfaceIndex; - PDNS_SERVICE_INSTANCE pServiceInstance; - PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; - PVOID pQueryContext; - HANDLE hCredentials; - BOOL unicastEnabled; -} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; -#endif - -_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); -_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -} /* extern "C" */ - -namespace platf::publish { -VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { - auto alarm = (safe::alarm_t::element_type *)pQueryContext; - - auto fg = util::fail_guard([&]() { - if(pInstance) { - _DnsServiceFreeInstance(pInstance); - } - }); - - if(status) { - print_status("register_cb()"sv, status); - alarm->ring(-1); - - return; - } - - alarm->ring(0); -} - -static int service(bool enable) { - auto alarm = safe::make_alarm(); - - std::wstring_convert, wchar_t> converter; - - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; - std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); - - DNS_SERVICE_INSTANCE instance {}; - instance.pszInstanceName = name.data(); - instance.wPort = map_port(nvhttp::PORT_HTTP); - instance.pszHostName = host.data(); - - DNS_SERVICE_REGISTER_REQUEST req {}; - req.Version = DNS_QUERY_REQUEST_VERSION1; - req.pQueryContext = alarm.get(); - req.pServiceInstance = &instance; - req.pRegisterCompletionCallback = register_cb; - - DNS_STATUS status {}; - - if(enable) { - status = _DnsServiceRegister(&req, nullptr); - } - else { - status = _DnsServiceDeRegister(&req, nullptr); - } - - alarm->wait(); - - status = *alarm->status(); - if(status) { - BOOST_LOG(error) << "No mDNS service"sv; - return -1; - } - - return 0; -} - -class deinit_t : public ::platf::deinit_t { -public: - ~deinit_t() override { - if(service(false)) { - std::abort(); - } - - BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; - } -}; - -int load_funcs(HMODULE handle) { - auto fg = util::fail_guard([handle]() { - FreeLibrary(handle); - }); - - _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); - _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); - _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); - - if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { - BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; - return -1; - } - - fg.disable(); - return 0; -} - -std::unique_ptr<::platf::deinit_t> start() { - HMODULE handle = LoadLibrary("dnsapi.dll"); - - if(!handle || load_funcs(handle)) { - BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; - return nullptr; - } - - if(service(true)) { - return nullptr; - } - - BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; - - return std::make_unique(); -} -} // namespace platf::publish +#include + +#include + +#include +#include + +#include + +#include "misc.h" +#include "sunshine/config.h" +#include "sunshine/main.h" +#include "sunshine/network.h" +#include "sunshine/nvhttp.h" +#include "sunshine/platform/common.h" +#include "sunshine/thread_safe.h" + +#define _FN(x, ret, args) \ + typedef ret(*x##_fn) args; \ + static x##_fn x + +using namespace std::literals; + +#define __SV(quote) L##quote##sv +#define SV(quote) __SV(quote) + +extern "C" { +#ifndef __MINGW32__ +constexpr auto DNS_REQUEST_PENDING = 9506L; +constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; +constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; +#endif + +#define SERVICE_DOMAIN "local" + +constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); +constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_INSTANCE { + LPWSTR pszInstanceName; + LPWSTR pszHostName; + + IP4_ADDRESS *ip4Address; + IP6_ADDRESS *ip6Address; + + WORD wPort; + WORD wPriority; + WORD wWeight; + + // Property list + DWORD dwPropertyCount; + + PWSTR *keys; + PWSTR *values; + + DWORD dwInterfaceIndex; +} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; +#endif + +typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( + _In_ DWORD Status, + _In_ PVOID pQueryContext, + _In_ PDNS_SERVICE_INSTANCE pInstance); + +typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_CANCEL { + PVOID reserved; +} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; + +typedef struct _DNS_SERVICE_REGISTER_REQUEST { + ULONG Version; + ULONG InterfaceIndex; + PDNS_SERVICE_INSTANCE pServiceInstance; + PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; + PVOID pQueryContext; + HANDLE hCredentials; + BOOL unicastEnabled; +} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; +#endif + +_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); +_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +} /* extern "C" */ + +namespace platf::publish { +VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { + auto alarm = (safe::alarm_t::element_type *)pQueryContext; + + auto fg = util::fail_guard([&]() { + if(pInstance) { + _DnsServiceFreeInstance(pInstance); + } + }); + + if(status) { + print_status("register_cb()"sv, status); + alarm->ring(-1); + + return; + } + + alarm->ring(0); +} + +static int service(bool enable) { + auto alarm = safe::make_alarm(); + + std::wstring_convert, wchar_t> converter; + + std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; + std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; + + auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + + DNS_SERVICE_INSTANCE instance {}; + instance.pszInstanceName = name.data(); + instance.wPort = map_port(nvhttp::PORT_HTTP); + instance.pszHostName = host.data(); + + DNS_SERVICE_REGISTER_REQUEST req {}; + req.Version = DNS_QUERY_REQUEST_VERSION1; + req.pQueryContext = alarm.get(); + req.pServiceInstance = &instance; + req.pRegisterCompletionCallback = register_cb; + + DNS_STATUS status {}; + + if(enable) { + status = _DnsServiceRegister(&req, nullptr); + } + else { + status = _DnsServiceDeRegister(&req, nullptr); + } + + alarm->wait(); + + status = *alarm->status(); + if(status) { + BOOST_LOG(error) << "No mDNS service"sv; + return -1; + } + + return 0; +} + +class deinit_t : public ::platf::deinit_t { +public: + ~deinit_t() override { + if(service(false)) { + std::abort(); + } + + BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; + } +}; + +int load_funcs(HMODULE handle) { + auto fg = util::fail_guard([handle]() { + FreeLibrary(handle); + }); + + _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); + _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); + _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); + + if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { + BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; + return -1; + } + + fg.disable(); + return 0; +} + +std::unique_ptr<::platf::deinit_t> start() { + HMODULE handle = LoadLibrary("dnsapi.dll"); + + if(!handle || load_funcs(handle)) { + BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; + return nullptr; + } + + if(service(true)) { + return nullptr; + } + + BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; + + return std::make_unique(); +} +} // namespace platf::publish diff --git a/sunshine/platform/windows/windows.rs.in b/src/platform/windows/windows.rs.in similarity index 100% rename from sunshine/platform/windows/windows.rs.in rename to src/platform/windows/windows.rs.in diff --git a/sunshine/process.cpp b/src/process.cpp similarity index 100% rename from sunshine/process.cpp rename to src/process.cpp diff --git a/sunshine/process.h b/src/process.h similarity index 100% rename from sunshine/process.h rename to src/process.h diff --git a/sunshine/round_robin.h b/src/round_robin.h similarity index 100% rename from sunshine/round_robin.h rename to src/round_robin.h diff --git a/sunshine/rtsp.cpp b/src/rtsp.cpp similarity index 100% rename from sunshine/rtsp.cpp rename to src/rtsp.cpp diff --git a/sunshine/rtsp.h b/src/rtsp.h similarity index 100% rename from sunshine/rtsp.h rename to src/rtsp.h diff --git a/sunshine/stream.cpp b/src/stream.cpp similarity index 100% rename from sunshine/stream.cpp rename to src/stream.cpp diff --git a/sunshine/stream.h b/src/stream.h similarity index 100% rename from sunshine/stream.h rename to src/stream.h diff --git a/sunshine/sync.h b/src/sync.h similarity index 100% rename from sunshine/sync.h rename to src/sync.h diff --git a/sunshine/task_pool.h b/src/task_pool.h similarity index 100% rename from sunshine/task_pool.h rename to src/task_pool.h diff --git a/sunshine/thread_pool.h b/src/thread_pool.h similarity index 100% rename from sunshine/thread_pool.h rename to src/thread_pool.h diff --git a/sunshine/thread_safe.h b/src/thread_safe.h similarity index 100% rename from sunshine/thread_safe.h rename to src/thread_safe.h diff --git a/sunshine/upnp.cpp b/src/upnp.cpp similarity index 99% rename from sunshine/upnp.cpp rename to src/upnp.cpp index a8fc6f4bdaa..a5a99e86fe1 100644 --- a/sunshine/upnp.cpp +++ b/src/upnp.cpp @@ -181,4 +181,4 @@ std::unique_ptr start() { return std::make_unique(std::move(urls), data, std::move(mappings)); } -} // namespace upnp \ No newline at end of file +} // namespace upnp diff --git a/sunshine/upnp.h b/src/upnp.h similarity index 95% rename from sunshine/upnp.h rename to src/upnp.h index 478d69b1f20..2ab57df6b9a 100644 --- a/sunshine/upnp.h +++ b/src/upnp.h @@ -7,4 +7,4 @@ namespace upnp { [[nodiscard]] std::unique_ptr start(); } -#endif \ No newline at end of file +#endif diff --git a/sunshine/utility.h b/src/utility.h similarity index 100% rename from sunshine/utility.h rename to src/utility.h diff --git a/sunshine/uuid.h b/src/uuid.h similarity index 100% rename from sunshine/uuid.h rename to src/uuid.h diff --git a/sunshine/video.cpp b/src/video.cpp similarity index 100% rename from sunshine/video.cpp rename to src/video.cpp diff --git a/sunshine/video.h b/src/video.h similarity index 100% rename from sunshine/video.h rename to src/video.h diff --git a/sunshine/platform/macos/TPCircularBuffer b/sunshine/platform/macos/TPCircularBuffer deleted file mode 160000 index bce9170d9e8..00000000000 --- a/sunshine/platform/macos/TPCircularBuffer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bce9170d9e8e566fb33d56136ec6e4b97f91ed2d diff --git a/tools/audio.cpp b/tools/audio.cpp index a130d384e73..592170b75c3 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -14,7 +14,7 @@ #include -#include "sunshine/utility.h" +#include "src/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 diff --git a/tools/dxgi.cpp b/tools/dxgi.cpp index 9c5f7307d56..7ecaaa2f5af 100644 --- a/tools/dxgi.cpp +++ b/tools/dxgi.cpp @@ -7,7 +7,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" using namespace std::literals; namespace dxgi {