mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-04 12:39:56 +00:00
Removed Git history due to personal info
This commit is contained in:
commit
ae29230f59
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
build
|
||||
cmake-build-*
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
*.kdev4
|
||||
|
||||
.idea
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[submodule "moonlight-common-c"]
|
||||
path = moonlight-common-c
|
||||
url = git@github.com:moonlight-stream/moonlight-common-c.git
|
||||
[submodule "Simple-Web-Server"]
|
||||
path = Simple-Web-Server
|
||||
url = git@github.com:loki-47-6F-64/Simple-Web-Server.git
|
92
CMakeLists.txt
Normal file
92
CMakeLists.txt
Normal file
@ -0,0 +1,92 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
project(Sunshine)
|
||||
# set up include-directories
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_subdirectory(Simple-Web-Server)
|
||||
add_subdirectory(moonlight-common-c/enet)
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
moonlight-common-c/reedsolomon/rs.c
|
||||
moonlight-common-c/reedsolomon/rs.h
|
||||
moonlight-common-c/src/AudioStream.c
|
||||
moonlight-common-c/src/ByteBuffer.c
|
||||
moonlight-common-c/src/ByteBuffer.h
|
||||
moonlight-common-c/src/Connection.c
|
||||
moonlight-common-c/src/ControlStream.c
|
||||
moonlight-common-c/src/FakeCallbacks.c
|
||||
moonlight-common-c/src/Input.h
|
||||
moonlight-common-c/src/InputStream.c
|
||||
moonlight-common-c/src/Limelight.h
|
||||
moonlight-common-c/src/Limelight-internal.h
|
||||
moonlight-common-c/src/LinkedBlockingQueue.c
|
||||
moonlight-common-c/src/LinkedBlockingQueue.h
|
||||
moonlight-common-c/src/Misc.c
|
||||
moonlight-common-c/src/Platform.c
|
||||
moonlight-common-c/src/Platform.h
|
||||
moonlight-common-c/src/PlatformSockets.c
|
||||
moonlight-common-c/src/PlatformSockets.h
|
||||
moonlight-common-c/src/PlatformThreads.h
|
||||
moonlight-common-c/src/RtpFecQueue.c
|
||||
moonlight-common-c/src/RtpFecQueue.h
|
||||
moonlight-common-c/src/RtpReorderQueue.c
|
||||
moonlight-common-c/src/RtpReorderQueue.h
|
||||
moonlight-common-c/src/RtspConnection.c
|
||||
moonlight-common-c/src/Rtsp.h
|
||||
moonlight-common-c/src/RtspParser.c
|
||||
moonlight-common-c/src/SdpGenerator.c
|
||||
moonlight-common-c/src/SimpleStun.c
|
||||
moonlight-common-c/src/VideoDepacketizer.c
|
||||
moonlight-common-c/src/Video.h
|
||||
moonlight-common-c/src/VideoStream.c
|
||||
utility.h
|
||||
uuid.h
|
||||
config.h config.cpp
|
||||
main.cpp crypto.cpp crypto.h nvhttp.cpp nvhttp.h stream.cpp stream.h video.cpp video.h queue.h input.cpp input.h audio.cpp audio.h platform/linux.cpp platform/common.h)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Simple-Web-Server
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/reedsolomon
|
||||
${X11_INCLUDE_DIR}
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(FFmpeg REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized)
|
||||
string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE)
|
||||
if("x${BUILD_TYPE}" STREQUAL "xDEBUG")
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -pedantic -ggdb3)
|
||||
elseif("x${BUILD_TYPE}" STREQUAL "xRELEASE")
|
||||
add_definitions(-DNDEBUG)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O3)
|
||||
endif()
|
||||
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${OPENSSL_LIBRARIES}
|
||||
enet
|
||||
Xfixes
|
||||
Xtst
|
||||
${X11_LIBRARIES}
|
||||
${FFMPEG_LIBRARIES}
|
||||
|
||||
#FIXME: libpulse is linux only
|
||||
pulse
|
||||
pulse-simple
|
||||
|
||||
#libpulse should be found with package_find
|
||||
opus)
|
||||
|
||||
add_definitions(-DSUNSHINE_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
||||
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17)
|
||||
|
||||
target_compile_options(sunshine PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
144
FindFFmpeg.cmake
Normal file
144
FindFFmpeg.cmake
Normal file
@ -0,0 +1,144 @@
|
||||
# - Try to find FFMPEG
|
||||
# Once done this will define
|
||||
# FFMPEG_FOUND - System has FFMPEG
|
||||
# FFMPEG_INCLUDE_DIRS - The FFMPEG include directories
|
||||
# FFMPEG_LIBRARIES - The libraries needed to use FFMPEG
|
||||
# FFMPEG_LIBRARY_DIRS - The directory to find FFMPEG libraries
|
||||
#
|
||||
# written by Roy Shilkrot 2013 http://www.morethantechnical.com/
|
||||
#
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
|
||||
MACRO(FFMPEG_FIND varname shortname headername)
|
||||
|
||||
IF(NOT WIN32)
|
||||
PKG_CHECK_MODULES(PC_${varname} ${shortname})
|
||||
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}"
|
||||
HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
ELSE()
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}")
|
||||
ENDIF()
|
||||
|
||||
IF(${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
message(STATUS "look for newer strcture")
|
||||
IF(NOT WIN32)
|
||||
PKG_CHECK_MODULES(PC_${varname} "lib${shortname}")
|
||||
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}"
|
||||
HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
ELSE()
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}")
|
||||
IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
#Desperate times call for desperate measures
|
||||
MESSAGE(STATUS "globbing...")
|
||||
FILE(GLOB_RECURSE ${varname}_INCLUDE_DIR "/ffmpeg*/${headername}")
|
||||
MESSAGE(STATUS "found: ${${varname}_INCLUDE_DIR}")
|
||||
IF(${varname}_INCLUDE_DIR)
|
||||
GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH)
|
||||
GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH)
|
||||
ELSE()
|
||||
SET(${varname}_INCLUDE_DIR "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
MESSAGE(STATUS "Can't find includes for ${shortname}...")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Found ${shortname} include dirs: ${${varname}_INCLUDE_DIR}")
|
||||
|
||||
# GET_DIRECTORY_PROPERTY(FFMPEG_PARENT DIRECTORY ${${varname}_INCLUDE_DIR} PARENT_DIRECTORY)
|
||||
GET_FILENAME_COMPONENT(FFMPEG_PARENT ${${varname}_INCLUDE_DIR} PATH)
|
||||
MESSAGE(STATUS "Using FFMpeg dir parent as hint: ${FFMPEG_PARENT}")
|
||||
|
||||
IF(NOT WIN32)
|
||||
FIND_LIBRARY(${varname}_LIBRARIES NAMES ${shortname}
|
||||
HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT})
|
||||
ELSE()
|
||||
# FIND_PATH(${varname}_LIBRARIES "${shortname}.dll.a" HINTS ${FFMPEG_PARENT})
|
||||
FILE(GLOB_RECURSE ${varname}_LIBRARIES "${FFMPEG_PARENT}/*${shortname}.lib")
|
||||
# GLOBing is very bad... but windows sux, this is the only thing that works
|
||||
ENDIF()
|
||||
|
||||
IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND")
|
||||
MESSAGE(STATUS "look for newer structure for library")
|
||||
FIND_LIBRARY(${varname}_LIBRARIES NAMES lib${shortname}
|
||||
HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT})
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND")
|
||||
MESSAGE(STATUS "Can't find lib for ${shortname}...")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Found ${shortname} libs: ${${varname}_LIBRARIES}")
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(NOT ${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND"
|
||||
AND NOT ${varname}_LIBRARIES STREQUAL ${varname}_LIBRARIES-NOTFOUND)
|
||||
|
||||
MESSAGE(STATUS "found ${shortname}: include ${${varname}_INCLUDE_DIR} lib ${${varname}_LIBRARIES}")
|
||||
SET(FFMPEG_${varname}_FOUND 1)
|
||||
SET(FFMPEG_${varname}_INCLUDE_DIRS ${${varname}_INCLUDE_DIR})
|
||||
SET(FFMPEG_${varname}_LIBS ${${varname}_LIBRARIES})
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Can't find ${shortname}")
|
||||
ENDIF()
|
||||
|
||||
ENDIF()
|
||||
|
||||
ENDMACRO(FFMPEG_FIND)
|
||||
|
||||
FFMPEG_FIND(LIBAVFORMAT avformat avformat.h)
|
||||
FFMPEG_FIND(LIBAVDEVICE avdevice avdevice.h)
|
||||
FFMPEG_FIND(LIBAVCODEC avcodec avcodec.h)
|
||||
FFMPEG_FIND(LIBAVUTIL avutil avutil.h)
|
||||
FFMPEG_FIND(LIBSWSCALE swscale swscale.h)
|
||||
|
||||
SET(FFMPEG_FOUND "NO")
|
||||
IF (FFMPEG_LIBAVFORMAT_FOUND AND
|
||||
FFMPEG_LIBAVDEVICE_FOUND AND
|
||||
FFMPEG_LIBAVCODEC_FOUND AND
|
||||
FFMPEG_LIBAVUTIL_FOUND AND
|
||||
FFMPEG_LIBSWSCALE_FOUND
|
||||
)
|
||||
|
||||
|
||||
SET(FFMPEG_FOUND "YES")
|
||||
|
||||
SET(FFMPEG_INCLUDE_DIRS ${FFMPEG_LIBAVFORMAT_INCLUDE_DIRS})
|
||||
|
||||
SET(FFMPEG_LIBRARY_DIRS ${FFMPEG_LIBAVFORMAT_LIBRARY_DIRS})
|
||||
|
||||
SET(FFMPEG_LIBRARIES
|
||||
${FFMPEG_LIBAVFORMAT_LIBS}
|
||||
${FFMPEG_LIBAVDEVICE_LIBS}
|
||||
${FFMPEG_LIBAVCODEC_LIBS}
|
||||
${FFMPEG_LIBAVUTIL_LIBS}
|
||||
${FFMPEG_LIBSWSCALE_LIBS}
|
||||
)
|
||||
|
||||
ELSE ()
|
||||
|
||||
MESSAGE(STATUS "Could not find FFMPEG")
|
||||
|
||||
ENDIF()
|
||||
|
||||
message(STATUS ${FFMPEG_LIBRARIES} ${FFMPEG_LIBAVFORMAT_LIBRARIES})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set FFMPEG_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(FFMPEG DEFAULT_MSG
|
||||
FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARY_DIRS FFMPEG_LIBRARIES)
|
1
Simple-Web-Server
Submodule
1
Simple-Web-Server
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d1bf544d9266bdf5b86517e4a70de63216529e5b
|
BIN
assets/box.png
Normal file
BIN
assets/box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
22
assets/demoCA/cacert.pem
Normal file
22
assets/demoCA/cacert.pem
Normal file
@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDsTCCApmgAwIBAgIULnRRHDUzdg4a9dwWi0/yV5LADLIwDQYJKoZIhvcNAQEL
|
||||
BQAwaDELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEhMB8GCSqGSIb3DQEJARYSbG9raUBm
|
||||
YWtlZW1haWwuY29tMB4XDTE5MDYwMzEwMzY0N1oXDTI5MDUzMTEwMzY0N1owaDEL
|
||||
MAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVy
|
||||
bmV0IFdpZGdpdHMgUHR5IEx0ZDEhMB8GCSqGSIb3DQEJARYSbG9raUBmYWtlZW1h
|
||||
aWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4cyNQ7uH6tAE
|
||||
EAD99oxR1CVYlOgRDEUqJCjkpIoVF5uE3/kfuvEJ+l7/WL51lqbq35uL5ta7EbJH
|
||||
BvVyNH00FN2D/Yl0n/X+TlhRP88MS5F0d0rwyQEAh4wEGcUhdSoW9TfybmLaHeCC
|
||||
bZlxC3kBWxr0e6YDlV9deM6j+OjCerQwkiiGwgE9dVrVCj1dLyzBhWnYGpYBvY+3
|
||||
6kEy0Vmf2spGCB6meCAMrAMz75fUDuk8YRF0umb+SLA44AB/U6d6GXU2EjpTuPww
|
||||
OMkUr8EmdbAI3l1tmWJTAkhFQ7681AyIWYOspc1biXZdBrvNBTV8kbDGlomNj19V
|
||||
QhhN44d4ywIDAQABo1MwUTAdBgNVHQ4EFgQUFRktN33zyW4MR9Cy1Vcn+B+EdYAw
|
||||
HwYDVR0jBBgwFoAUFRktN33zyW4MR9Cy1Vcn+B+EdYAwDwYDVR0TAQH/BAUwAwEB
|
||||
/zANBgkqhkiG9w0BAQsFAAOCAQEAkpU+ALElNz+5jOnVAPyYXYsJKfevmKK9uK4v
|
||||
V+l7GrFLIEhC2qr26S8Nd2pLkrgesCD4xfoOONiVOceU5igh9acFA3+NyOSFLdRN
|
||||
bSdy0jvCuoiK46ieDAagQtdt0G7HGV4u+jWz0jaKUQI9zJqznOHdJV6RZFIqTYLG
|
||||
KHnGP+mtXjW3E1djU9vFreYcB6UY+Ai1KB33dnBK9Es2fIQhikKZUPTh6BYsRZT6
|
||||
U7c6fh+01fRhRPo/SCFmY993857NtoOHMeP0M2V65CG4VjpAPR0msChVQVv7csca
|
||||
TvBvB23dFRTLbo5PUSWC9bhBrMjzJ7yylt1CNBBHv7ycw8yIPw==
|
||||
-----END CERTIFICATE-----
|
28
assets/demoCA/cakey.pem
Normal file
28
assets/demoCA/cakey.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDhzI1Du4fq0AQQ
|
||||
AP32jFHUJViU6BEMRSokKOSkihUXm4Tf+R+68Qn6Xv9YvnWWpurfm4vm1rsRskcG
|
||||
9XI0fTQU3YP9iXSf9f5OWFE/zwxLkXR3SvDJAQCHjAQZxSF1Khb1N/JuYtod4IJt
|
||||
mXELeQFbGvR7pgOVX114zqP46MJ6tDCSKIbCAT11WtUKPV0vLMGFadgalgG9j7fq
|
||||
QTLRWZ/aykYIHqZ4IAysAzPvl9QO6TxhEXS6Zv5IsDjgAH9Tp3oZdTYSOlO4/DA4
|
||||
yRSvwSZ1sAjeXW2ZYlMCSEVDvrzUDIhZg6ylzVuJdl0Gu80FNXyRsMaWiY2PX1VC
|
||||
GE3jh3jLAgMBAAECggEAdnHgoGkc8RXRK7v5fH0653f3sZTSbIdThchVt+IfElUo
|
||||
LHz4Ig4S191BQQIXmMFSb52ek6aMVsoX7BSQpewPh+pzNGoIXWiiz+IQLNKldnaE
|
||||
i5cqG6aE6pWOCR6ZYGaFyHhimXkNRaLhiDB3VjdReML5AGujcZWm6Jos9YLTkZ07
|
||||
pYXs2S+/5oNbfDdAE8dgdD7vD9lNGrbtmJ9+J+VvgPOYM/4LaEAfZU0ALDm2Hl3n
|
||||
CqkZCp+eWbQK3MC2+6Y4yS+jo7e5/nKX1De9StkX6KumAEIpxkHPZF2EnWXW64oD
|
||||
k40tKXT+oMXF1RLb4scnv+J+uR4bdl+Xq/VTzKrtsQKBgQDyoOjrv+g5l0s0KKDJ
|
||||
qPkLQNRCJpzU7Km3CjPuk8DOlmBCr4KUeVfjbYPxQQMkeNL9zFz/QYBmabTCE6Ih
|
||||
E8BbzT/RQzVPSOOh//Hh4eN4umrYaOIkY4Bv2R2X3H0ELFq+G7pKdvTAkusGHcWu
|
||||
OAUb2HO5rTiRYq8DvHy8b+qH7wKBgQDuPjR+DCVtkBXmBNf0FZkl7OW/EUQBq4+T
|
||||
WoaYleq8Gd7ubJgz2Gud+0+L41VjFD9W3hkJAb/3wkASseQ+2hk3Sv/BzpAIdS9e
|
||||
tN/xxp/8NK7tJGL63m6tAfX+Hi/kSDEp45Zp3PoOD08MEK0yIrpf7S8uJ++u49K4
|
||||
tKXkGCIg5QKBgFujvPW2AQ8nfqcPpVMleBLxBHqLvPaLALr6poy4z7z3fRoS0j4j
|
||||
6rcimRAZHwe6fu6PLpzWb5m+2R/obHcTz7acujreqJbuj9OTKRfIyrLBrjNYwfk+
|
||||
f7c/CPdftvRJkGh3bpBLh7vogc5Ilm5sCDnxMhxyOYhn/nRpz68YkjuPAoGBAOQX
|
||||
6DfZtyfLcDvV3U/SMdsOkPO6OwsCTya73+tMdP18I2TP0XSpunb5ebIrh7+hTfcE
|
||||
EqH96+XwM1nyuNy4ALZgdrb95gZC84RP1axsBxX29pcSZDVdKkc3fmW6Tw3XVEKP
|
||||
o51dNIarf3nEqZ07hIZ81dPx5lbhxgiS49SaimpFAoGBAKamKZFAfUHlaHV/Na1C
|
||||
3SZji7PaDSj1EFmRkCySK9VqD7Tbh1abrpC2ImdhYHn5TcQQE2eidB+F0Nf6IhKN
|
||||
upBTofg0ebaslo+BYAqAsRKnUQGDToGIIIdXJ6DnO3wxWu9GY4nKdl3jxqAv2A2x
|
||||
d8SETw4wqlFFRO33opycuFS5
|
||||
-----END PRIVATE KEY-----
|
104
audio.cpp
Normal file
104
audio.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#include "utility.h"
|
||||
#include "queue.h"
|
||||
#include "audio.h"
|
||||
|
||||
namespace audio {
|
||||
using namespace std::literals;
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
|
||||
struct opus_stream_config_t {
|
||||
std::int32_t sampleRate;
|
||||
int channelCount;
|
||||
int streams;
|
||||
int coupledStreams;
|
||||
const std::uint8_t *mapping;
|
||||
};
|
||||
|
||||
constexpr std::uint8_t map_stereo[] { 0, 1 };
|
||||
constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3};
|
||||
constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5};
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
static opus_stream_config_t stereo = {
|
||||
SAMPLE_RATE,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
map_stereo
|
||||
};
|
||||
|
||||
static opus_stream_config_t Surround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
4,
|
||||
2,
|
||||
map_surround51
|
||||
};
|
||||
|
||||
static opus_stream_config_t HighSurround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
6,
|
||||
0,
|
||||
map_high_surround51
|
||||
};
|
||||
|
||||
void encodeThread(std::shared_ptr<safe::queue_t<packet_t>> packets, std::shared_ptr<safe::queue_t<platf::audio_t>> samples, config_t config) {
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
stream->sampleRate,
|
||||
stream->channelCount,
|
||||
stream->streams,
|
||||
stream->coupledStreams,
|
||||
stream->mapping,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
nullptr)
|
||||
};
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
while(auto sample = samples->pop()) {
|
||||
packet_t packet { 16*1024 }; // 16KB
|
||||
|
||||
int bytes = opus_multistream_encode(opus.get(), platf::audio_data(sample), frame_size, std::begin(packet), packet.size());
|
||||
if(bytes < 0) {
|
||||
std::cout << "Error: "sv << opus_strerror(bytes) << std::endl;
|
||||
exit(7);
|
||||
}
|
||||
|
||||
packet.fake_resize(bytes);
|
||||
packets->push(std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config) {
|
||||
auto samples = std::make_shared<safe::queue_t<platf::audio_t>>();
|
||||
|
||||
auto mic = platf::microphone();
|
||||
if(!mic) {
|
||||
std::cout << "Error creating audio input"sv << std::endl;
|
||||
}
|
||||
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
int bytes_per_frame = frame_size * sizeof(std::int16_t) * stream->channelCount;
|
||||
|
||||
std::thread thread { encodeThread, packets, samples, config };
|
||||
while(packets->running()) {
|
||||
auto sample = platf::audio(mic, bytes_per_frame);
|
||||
|
||||
samples->push(std::move(sample));
|
||||
}
|
||||
|
||||
samples->stop();
|
||||
thread.join();
|
||||
}
|
||||
}
|
17
audio.h
Normal file
17
audio.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef SUNSHINE_AUDIO_H
|
||||
#define SUNSHINE_AUDIO_H
|
||||
|
||||
#include "utility.h"
|
||||
#include "queue.h"
|
||||
namespace audio {
|
||||
struct config_t {
|
||||
int packetDuration;
|
||||
int channels;
|
||||
int mask;
|
||||
};
|
||||
|
||||
using packet_t = util::buffer_t<std::uint8_t>;
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config);
|
||||
}
|
||||
|
||||
#endif
|
27
config.cpp
Normal file
27
config.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include "config.h"
|
||||
|
||||
#define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA"
|
||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
|
||||
namespace config {
|
||||
using namespace std::literals;
|
||||
video_t video {
|
||||
16, // max_b_frames
|
||||
24, // gop_size
|
||||
35, // crf
|
||||
};
|
||||
|
||||
stream_t stream {
|
||||
2s // ping_timeout
|
||||
};
|
||||
|
||||
nvhttp_t nvhttp {
|
||||
PRIVATE_KEY_FILE,
|
||||
CERTIFICATE_FILE,
|
||||
|
||||
"03904e64-51da-4fb3-9afd-a9f7ff70fea4", // unique_id
|
||||
"devices.xml" // file_devices
|
||||
};
|
||||
}
|
34
config.h
Normal file
34
config.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef SUNSHINE_CONFIG_H
|
||||
#define SUNSHINE_CONFIG_H
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace config {
|
||||
struct video_t {
|
||||
// ffmpeg params
|
||||
int max_b_frames;
|
||||
int gop_size;
|
||||
int crf; // higher == more compression and less quality
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
std::chrono::milliseconds ping_timeout;
|
||||
};
|
||||
|
||||
struct nvhttp_t {
|
||||
std::string pkey; // must be 2048 bits
|
||||
std::string cert; // must be signed with a key of 2048 bits
|
||||
|
||||
std::string unique_id; //UUID
|
||||
std::string file_devices;
|
||||
|
||||
std::string external_ip;
|
||||
};
|
||||
|
||||
extern video_t video;
|
||||
extern stream_t stream;
|
||||
extern nvhttp_t nvhttp;
|
||||
}
|
||||
|
||||
#endif
|
231
crypto.cpp
Normal file
231
crypto.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
//
|
||||
// Created by loki on 5/31/19.
|
||||
//
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include "crypto.h"
|
||||
namespace crypto {
|
||||
cipher_t::cipher_t(const crypto::aes_t &key) : ctx { EVP_CIPHER_CTX_new() }, key { key }, padding { true } {}
|
||||
int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
auto size = (int)plaintext.size();
|
||||
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
|
||||
std::vector<std::uint8_t> &plaintext) {
|
||||
auto cipher = tagged_cipher.substr(16);
|
||||
auto tag = tagged_cipher.substr(0, 16);
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
|
||||
int size;
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char*>(tag.data())) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = size;
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(size + len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
cipher.resize((plaintext.size() + 15) / 16 * 16);
|
||||
auto size = (int)cipher.size();
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t*)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key;
|
||||
|
||||
std::string salt_pin;
|
||||
salt_pin.reserve(salt.size() + pin.size());
|
||||
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
|
||||
|
||||
auto hsh = hash(salt_pin);
|
||||
|
||||
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, plaintext.data(), plaintext.size());
|
||||
SHA256_Final(hsh.data(), &sha256);
|
||||
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
X509 *p = nullptr;
|
||||
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return x509_t { p };
|
||||
}
|
||||
|
||||
pkey_t pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
EVP_PKEY *p = nullptr;
|
||||
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return pkey_t { p };
|
||||
}
|
||||
|
||||
|
||||
std::string_view signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char*)asn1->data, (std::size_t)asn1->length };
|
||||
}
|
||||
|
||||
std::string rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
RAND_bytes((uint8_t*)r.data(), r.size());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slen = digest_size;
|
||||
|
||||
std::vector<uint8_t> digest;
|
||||
digest.resize(slen);
|
||||
|
||||
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
}
|
64
crypto.h
Normal file
64
crypto.h
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Created by loki on 6/1/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_CRYPTO_H
|
||||
#define SUNSHINE_CRYPTO_H
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
constexpr std::size_t digest_size = 256;
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
using aes_t = std::array<std::uint8_t, 16>;
|
||||
using x509_t = util::safe_ptr<X509, X509_free>;
|
||||
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
||||
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
|
||||
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
|
||||
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext);
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
|
||||
x509_t x509(const std::string_view &x);
|
||||
pkey_t pkey(const std::string_view &k);
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
|
||||
std::string_view signature(const x509_t &x);
|
||||
|
||||
std::string rand(std::size_t bytes);
|
||||
|
||||
class cipher_t {
|
||||
public:
|
||||
cipher_t(const aes_t &key);
|
||||
cipher_t(cipher_t&&) noexcept = default;
|
||||
cipher_t &operator=(cipher_t&&) noexcept = default;
|
||||
|
||||
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
|
||||
int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
private:
|
||||
cipher_ctx_t ctx;
|
||||
aes_t key;
|
||||
|
||||
public:
|
||||
bool padding;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_CRYPTO_H
|
142
input.cpp
Normal file
142
input.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Input.h>
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "input.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace input {
|
||||
using namespace std::literals;
|
||||
|
||||
void print(PNV_MOUSE_MOVE_PACKET packet) {
|
||||
std::cout << "--begin mouse move packet--"sv << std::endl;
|
||||
|
||||
std::cout << "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl;
|
||||
std::cout << "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl;
|
||||
|
||||
std::cout << "--end mouse move packet--"sv << std::endl;
|
||||
}
|
||||
|
||||
void print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
std::cout << "--begin mouse button packet--"sv << std::endl;
|
||||
|
||||
std::cout << "action ["sv << util::hex(packet->action).to_string_view() << ']' << std::endl;
|
||||
std::cout << "button ["sv << util::hex(packet->button).to_string_view() << ']' << std::endl;
|
||||
|
||||
std::cout << "--end mouse button packet--"sv << std::endl;
|
||||
}
|
||||
|
||||
void print(PNV_SCROLL_PACKET packet) {
|
||||
std::cout << "--begin mouse scroll packet--"sv << std::endl;
|
||||
|
||||
std::cout << "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl;
|
||||
|
||||
std::cout << "--end mouse scroll packet--"sv << std::endl;
|
||||
}
|
||||
|
||||
void print(PNV_KEYBOARD_PACKET packet) {
|
||||
std::cout << "--begin keyboard packet--"sv << std::endl;
|
||||
|
||||
std::cout << "keyAction ["sv << util::hex(packet->keyAction).to_string_view() << ']' << std::endl;
|
||||
std::cout << "keyCode ["sv << util::hex(packet->keyCode).to_string_view() << ']' << std::endl;
|
||||
std::cout << "modifiers ["sv << util::hex(packet->modifiers).to_string_view() << ']' << std::endl;
|
||||
|
||||
std::cout << "--end keyboard packet--"sv << std::endl;
|
||||
}
|
||||
|
||||
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
std::cout << "--begin controller packet--"sv << std::endl;
|
||||
|
||||
std::cout << "controllerNumber ["sv << packet->controllerNumber << ']' << std::endl;
|
||||
std::cout << "activeGamepadMask ["sv << util::hex(packet->activeGamepadMask).to_string_view() << ']' << std::endl;
|
||||
std::cout << "buttonFlags ["sv << util::hex(packet->buttonFlags).to_string_view() << ']' << std::endl;
|
||||
std::cout << "leftTrigger ["sv << util::hex(packet->leftTrigger).to_string_view() << ']' << std::endl;
|
||||
std::cout << "rightTrigger ["sv << util::hex(packet->rightTrigger).to_string_view() << ']' << std::endl;
|
||||
std::cout << "leftStickX ["sv << packet->leftStickX << ']' << std::endl;
|
||||
std::cout << "leftStickY ["sv << packet->leftStickY << ']' << std::endl;
|
||||
std::cout << "rightStickX ["sv << packet->rightStickX << ']' << std::endl;
|
||||
std::cout << "rightStickY ["sv << packet->rightStickY << ']' << std::endl;
|
||||
|
||||
std::cout << "--end controller packet--"sv << std::endl;
|
||||
}
|
||||
|
||||
constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL;
|
||||
void print(void *input) {
|
||||
int input_type = util::endian::big(*(int*)input);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_MOUSE_MOVE:
|
||||
print((PNV_MOUSE_MOVE_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
print((PNV_MOUSE_BUTTON_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
||||
{
|
||||
char *tmp_input = (char*)input + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
print((PNV_SCROLL_PACKET)input);
|
||||
}
|
||||
else {
|
||||
print((PNV_KEYBOARD_PACKET)input);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_MULTI_CONTROLLER:
|
||||
print((PNV_MULTI_CONTROLLER_PACKET)input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, PNV_MOUSE_MOVE_PACKET packet) {
|
||||
platf::move_mouse(display, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
||||
}
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x09;
|
||||
|
||||
platf::button_mouse(display, util::endian::big(packet->button), packet->action == BUTTON_RELEASED);
|
||||
}
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, PNV_KEYBOARD_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x04;
|
||||
|
||||
platf::keyboard(display, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED);
|
||||
}
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, PNV_SCROLL_PACKET packet) {
|
||||
platf::scroll(display, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, void *input) {
|
||||
int input_type = util::endian::big(*(int*)input);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_MOUSE_MOVE:
|
||||
passthrough(display, (PNV_MOUSE_MOVE_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
passthrough(display, (PNV_MOUSE_BUTTON_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
||||
{
|
||||
char *tmp_input = (char*)input + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
passthrough(display, (PNV_SCROLL_PACKET)input);
|
||||
}
|
||||
else {
|
||||
passthrough(display, (PNV_KEYBOARD_PACKET)input);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
input.h
Normal file
16
input.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_INPUT_H
|
||||
#define SUNSHINE_INPUT_H
|
||||
|
||||
#include <platform/common.h>
|
||||
|
||||
namespace input {
|
||||
void print(void *input);
|
||||
|
||||
void passthrough(platf::display_t::element_type *display, void *input);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_INPUT_H
|
28
main.cpp
Normal file
28
main.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by loki on 5/30/19.
|
||||
//
|
||||
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
|
||||
#include "nvhttp.h"
|
||||
#include "stream.h"
|
||||
|
||||
extern "C" {
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
using namespace std::literals;
|
||||
int main() {
|
||||
reed_solomon_init();
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread rtpThread { stream::rtpThread };
|
||||
|
||||
httpThread.join();
|
||||
rtpThread.join();
|
||||
|
||||
return 0;
|
||||
}
|
1
moonlight-common-c
Submodule
1
moonlight-common-c
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 801aaf43d6124da294a8c97e5b67e966f1b4edbf
|
521
nvhttp.cpp
Normal file
521
nvhttp.cpp
Normal file
@ -0,0 +1,521 @@
|
||||
//
|
||||
// Created by loki on 6/3/19.
|
||||
//
|
||||
|
||||
#include "nvhttp.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
|
||||
#include "uuid.h"
|
||||
#include "config.h"
|
||||
#include "utility.h"
|
||||
#include "stream.h"
|
||||
|
||||
namespace nvhttp {
|
||||
using namespace std::literals;
|
||||
constexpr auto PORT_HTTP = 47989;
|
||||
constexpr auto PORT_HTTPS = 47984;
|
||||
|
||||
constexpr auto VERSION = "7.1.415.0";
|
||||
constexpr auto GFE_VERSION = "2.0.0.1";
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
std::string read_file(const char *path);
|
||||
|
||||
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
|
||||
using http_server_t = SimpleWeb::Server<SimpleWeb::HTTP>;
|
||||
|
||||
struct conf_intern_t {
|
||||
std::string servercert;
|
||||
std::string pkey;
|
||||
} conf_intern;
|
||||
|
||||
struct client_t {
|
||||
std::string uniqueID;
|
||||
std::string cert;
|
||||
};
|
||||
|
||||
struct pair_session_t {
|
||||
client_t client;
|
||||
|
||||
std::unique_ptr<crypto::aes_t> cipher_key;
|
||||
std::vector<uint8_t> clienthash;
|
||||
|
||||
std::string serversecret;
|
||||
std::string serverchallenge;
|
||||
};
|
||||
|
||||
// uniqueID, session
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess;
|
||||
std::unordered_map<std::string, client_t> map_id_client;
|
||||
|
||||
using args_t = SimpleWeb::CaseInsensitiveMultimap;
|
||||
|
||||
enum class op_e {
|
||||
ADD,
|
||||
REMOVE
|
||||
};
|
||||
|
||||
std::string get_pin() {
|
||||
std::cout << "Please insert PIN: ";
|
||||
std::string pin;
|
||||
std::getline(std::cin, pin);
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
void save_devices() {
|
||||
pt::ptree root;
|
||||
|
||||
auto &nodes = root.add_child("root.devices", pt::ptree {});
|
||||
for(auto &[_,client] : map_id_client) {
|
||||
pt::ptree node;
|
||||
|
||||
node.put("uniqueid"s, client.uniqueID);
|
||||
node.put("cert"s, client.cert);
|
||||
|
||||
nodes.push_back(std::make_pair("", node));
|
||||
}
|
||||
|
||||
pt::write_json(config::nvhttp.file_devices, root);
|
||||
}
|
||||
|
||||
void load_devices() {
|
||||
pt::ptree root;
|
||||
try {
|
||||
pt::read_json(config::nvhttp.file_devices, root);
|
||||
} catch (std::exception &e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto nodes = root.get_child("root.devices");
|
||||
|
||||
for(auto &[_,node] : nodes) {
|
||||
auto uniqID = node.get<std::string>("uniqueid");
|
||||
auto &client = map_id_client.emplace(uniqID, client_t {}).first->second;
|
||||
|
||||
client.uniqueID = uniqID;
|
||||
client.cert = node.get<std::string>("cert");
|
||||
}
|
||||
}
|
||||
|
||||
void update_id_client(client_t &client, op_e op) {
|
||||
switch(op) {
|
||||
case op_e::ADD:
|
||||
{
|
||||
auto uniqID = client.uniqueID;
|
||||
map_id_client.emplace(std::move(uniqID), std::move(client));
|
||||
}
|
||||
break;
|
||||
case op_e::REMOVE:
|
||||
map_id_client.erase(client.uniqueID);
|
||||
break;
|
||||
}
|
||||
|
||||
save_devices();
|
||||
}
|
||||
|
||||
void getservercert(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
|
||||
auto salt = util::from_hex<std::array<uint8_t, 16>>(args.at("salt"s), true);
|
||||
|
||||
auto pin = get_pin();
|
||||
|
||||
auto key = crypto::gen_aes_key(*salt, pin);
|
||||
sess.cipher_key = std::make_unique<crypto::aes_t>(key);
|
||||
|
||||
tree.put("root.paired", 1);
|
||||
tree.put("root.plaincert", util::hex_vec(conf_intern.servercert, true));
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
|
||||
auto encrypted_response = util::from_hex_vec(args.at("serverchallengeresp"s), true);
|
||||
|
||||
std::vector<uint8_t> decrypted;
|
||||
crypto::cipher_t cipher(*sess.cipher_key);
|
||||
cipher.padding = false;
|
||||
|
||||
cipher.decrypt(encrypted_response, decrypted);
|
||||
|
||||
sess.clienthash = std::move(decrypted);
|
||||
|
||||
auto serversecret = sess.serversecret;
|
||||
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
|
||||
|
||||
serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign));
|
||||
|
||||
tree.put("root.pairingsecret", util::hex_vec(serversecret, true));
|
||||
tree.put("root.paired", 1);
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
|
||||
auto challenge = util::from_hex_vec(args.at("clientchallenge"s), true);
|
||||
|
||||
crypto::cipher_t cipher(*sess.cipher_key);
|
||||
cipher.padding = false;
|
||||
|
||||
std::vector<uint8_t> decrypted;
|
||||
cipher.decrypt(challenge, decrypted);
|
||||
|
||||
auto x509 = crypto::x509(conf_intern.servercert);
|
||||
auto sign = crypto::signature(x509);
|
||||
auto serversecret = crypto::rand(16);
|
||||
|
||||
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
|
||||
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
|
||||
|
||||
auto hash = crypto::hash({ (char*)decrypted.data(), decrypted.size() });
|
||||
auto serverchallenge = crypto::rand(16);
|
||||
|
||||
std::string plaintext;
|
||||
plaintext.reserve(hash.size() + serverchallenge.size());
|
||||
|
||||
plaintext.insert(std::end(plaintext), std::begin(hash), std::end(hash));
|
||||
plaintext.insert(std::end(plaintext), std::begin(serverchallenge), std::end(serverchallenge));
|
||||
|
||||
std::vector<uint8_t> encrypted;
|
||||
cipher.encrypt(plaintext, encrypted);
|
||||
|
||||
sess.serversecret = std::move(serversecret);
|
||||
sess.serverchallenge = std::move(serverchallenge);
|
||||
|
||||
tree.put("root.paired", 1);
|
||||
tree.put("root.challengeresponse", util::hex_vec(encrypted, true));
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
void clientpairingsecret(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
|
||||
auto &client = sess.client;
|
||||
|
||||
auto pairingsecret = util::from_hex_vec(args.at("clientpairingsecret"), true);
|
||||
|
||||
std::string_view secret { pairingsecret.data(), 16 };
|
||||
std::string_view sign { pairingsecret.data() + secret.size(), crypto::digest_size };
|
||||
|
||||
assert((secret.size() + sign.size()) == pairingsecret.size());
|
||||
|
||||
auto x509 = crypto::x509(sess.client.cert);
|
||||
auto x509_sign = crypto::signature(x509);
|
||||
|
||||
std::string data;
|
||||
data.reserve(sess.serverchallenge.size() + x509_sign.size() + secret.size());
|
||||
|
||||
data.insert(std::end(data), std::begin(sess.serverchallenge), std::end(sess.serverchallenge));
|
||||
data.insert(std::end(data), std::begin(x509_sign), std::end(x509_sign));
|
||||
data.insert(std::end(data), std::begin(secret), std::end(secret));
|
||||
|
||||
auto hash = crypto::hash(data);
|
||||
|
||||
// if hash not correct, probably MITM
|
||||
if(std::memcmp(hash.data(), sess.clienthash.data(), hash.size())) {
|
||||
//TODO: log
|
||||
|
||||
map_id_sess.erase(client.uniqueID);
|
||||
tree.put("root.paired", 0);
|
||||
}
|
||||
|
||||
if(crypto::verify256(crypto::x509(client.cert), secret, sign)) {
|
||||
tree.put("root.paired", 1);
|
||||
|
||||
auto it = map_id_sess.find(client.uniqueID);
|
||||
|
||||
auto uniqID = client.uniqueID;
|
||||
update_id_client(client, op_e::ADD);
|
||||
map_id_sess.erase(it);
|
||||
}
|
||||
else {
|
||||
map_id_sess.erase(client.uniqueID);
|
||||
tree.put("root.paired", 0);
|
||||
}
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
pt::ptree pair_xml(args_t &&args) {
|
||||
auto uniqID { std::move(args.at("uniqueid"s)) };
|
||||
auto sess_it = map_id_sess.find(uniqID);
|
||||
|
||||
pt::ptree tree;
|
||||
|
||||
args_t::const_iterator it;
|
||||
if(it = args.find("phrase"); it != std::end(args)) {
|
||||
if(it->second == "getservercert"sv) {
|
||||
pair_session_t sess;
|
||||
|
||||
sess.client.uniqueID = std::move(uniqID);
|
||||
sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true);
|
||||
|
||||
std::cout << sess.client.cert;
|
||||
|
||||
auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first;
|
||||
getservercert(ptr->second, tree, args);
|
||||
}
|
||||
else if(it->second == "pairchallenge"sv) {
|
||||
tree.put("root.paired", 1);
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
}
|
||||
else if(it = args.find("clientchallenge"); it != std::end(args)) {
|
||||
clientchallenge(sess_it->second, tree, args);
|
||||
}
|
||||
else if(it = args.find("serverchallengeresp"); it != std::end(args)) {
|
||||
serverchallengeresp(sess_it->second, tree, args);
|
||||
}
|
||||
else if(it = args.find("clientpairingsecret"); it != std::end(args)) {
|
||||
clientpairingsecret(sess_it->second, tree, args);
|
||||
}
|
||||
else {
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
struct tunnel;
|
||||
|
||||
template<>
|
||||
struct tunnel<SimpleWeb::HTTPS> {
|
||||
static auto constexpr to_string = "HTTPS"sv;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct tunnel<SimpleWeb::HTTP> {
|
||||
static auto constexpr to_string = "NONE"sv;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
std::cout << "TUNNEL :: "sv << tunnel<T>::to_string << std::endl;
|
||||
|
||||
std::cout << "METHOD :: "sv << request->method << std::endl;
|
||||
std::cout << "DESTINATION :: "sv << request->path << std::endl;
|
||||
|
||||
for(auto &[name, val] : request->header) {
|
||||
std::cout << name << " -- " << val << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
for(auto &[name, val] : request->parse_query_string()) {
|
||||
std::cout << name << " -- " << val << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
|
||||
*response << "HTTP/1.1 404 NOT FOUND\r\n" << data.str();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void pair(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
auto tree = pair_xml(request->parse_query_string());
|
||||
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
auto clientID = args.find("uniqueid"s);
|
||||
|
||||
int pair_status = 0;
|
||||
|
||||
if(clientID != std::end(args)) {
|
||||
if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) {
|
||||
pair_status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree tree;
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
tree.put("root.hostname", "loki-pc");
|
||||
|
||||
tree.put("root.appversion", VERSION);
|
||||
tree.put("root.GfeVersion", GFE_VERSION);
|
||||
tree.put("root.uniqueid", config::nvhttp.unique_id);
|
||||
tree.put("root.mac", "42:45:F0:65:D6:F4");
|
||||
tree.put("root.LocalIP", "192.168.0.195"); //FIXME: Should be determined at runtime
|
||||
|
||||
if(config::nvhttp.external_ip.empty()) {
|
||||
tree.put("root.ExternalIP", "192.168.0.195");
|
||||
}
|
||||
else {
|
||||
tree.put("root.ExternalIP", config::nvhttp.external_ip);
|
||||
}
|
||||
|
||||
tree.put("root.PairStatus", pair_status);
|
||||
tree.put("root.currentgame", 0);
|
||||
tree.put("root.state", "_SERVER_BUSY");
|
||||
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void applist(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
auto clientID = args.at("uniqueid"s);
|
||||
|
||||
pt::ptree tree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
auto client = map_id_client.find(clientID);
|
||||
if(client == std::end(map_id_client)) {
|
||||
tree.put("root.<xmlattr>.status_code", 501);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto &apps = tree.add_child("root", pt::ptree {});
|
||||
pt::ptree desktop;
|
||||
pt::ptree fakegame;
|
||||
|
||||
apps.put("<xmlattr>.status_code", 200);
|
||||
desktop.put("IsHdrSupported"s, 0);
|
||||
desktop.put("AppTitle"s, "Desktop");
|
||||
desktop.put("ID"s, 1);
|
||||
|
||||
fakegame.put("IsHdrSupported"s, 0);
|
||||
fakegame.put("AppTitle"s, "FakeGame");
|
||||
fakegame.put("ID"s, 2);
|
||||
|
||||
apps.push_back(std::make_pair("App", desktop));
|
||||
apps.push_back(std::make_pair("App", fakegame));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void launch(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
auto clientID = args.at("uniqueid"s);
|
||||
auto aesKey = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
|
||||
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
|
||||
auto prepend_iv_p = (uint8_t*)&prepend_iv;
|
||||
|
||||
std::copy(std::begin(aesKey), std::end(aesKey), std::begin(stream::gcm_key));
|
||||
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(stream::iv));
|
||||
std::fill(next, std::end(stream::iv), 0);
|
||||
|
||||
pt::ptree tree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
/*
|
||||
bool sops = args.at("sops"s) == "1";
|
||||
std::optional<int> gcmap { std::nullopt };
|
||||
if(auto it = args.find("gcmap"s); it != std::end(args)) {
|
||||
gcmap = std::stoi(it->second);
|
||||
}
|
||||
*/
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
tree.put("root.gamesession", 1);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void appasset(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
std::ifstream in(SUNSHINE_ASSETS_DIR "/box.png");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in);
|
||||
}
|
||||
|
||||
void start() {
|
||||
load_devices();
|
||||
|
||||
conf_intern.pkey = read_file(config::nvhttp.pkey.c_str());
|
||||
conf_intern.servercert = read_file(config::nvhttp.cert.c_str());
|
||||
|
||||
https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey };
|
||||
http_server_t http_server;
|
||||
|
||||
https_server.default_resource = not_found<SimpleWeb::HTTPS>;
|
||||
https_server.resource["^/serverinfo"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
|
||||
https_server.resource["^/pair"]["GET"] = pair<SimpleWeb::HTTPS>;
|
||||
https_server.resource["^/applist"]["GET"] = applist<SimpleWeb::HTTPS>;
|
||||
https_server.resource["^/appasset"]["GET"] = appasset<SimpleWeb::HTTPS>;
|
||||
https_server.resource["^/launch"]["GET"] = launch<SimpleWeb::HTTPS>;
|
||||
|
||||
https_server.config.reuse_address = true;
|
||||
https_server.config.address = "0.0.0.0"s;
|
||||
https_server.config.port = PORT_HTTPS;
|
||||
|
||||
http_server.default_resource = not_found<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/serverinfo"]["GET"] = serverinfo<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/pair"]["GET"] = pair<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/applist"]["GET"] = applist<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/appasset"]["GET"] = appasset<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/launch"]["GET"] = launch<SimpleWeb::HTTP>;
|
||||
|
||||
http_server.config.reuse_address = true;
|
||||
http_server.config.address = "0.0.0.0"s;
|
||||
http_server.config.port = PORT_HTTP;
|
||||
|
||||
std::thread ssl { &https_server_t::start, &https_server };
|
||||
std::thread tcp { &http_server_t::start, &http_server };
|
||||
|
||||
ssl.join();
|
||||
tcp.join();
|
||||
}
|
||||
|
||||
std::string read_file(const char *path) {
|
||||
std::ifstream in(path);
|
||||
|
||||
std::string input;
|
||||
std::string base64_cert;
|
||||
|
||||
while(!in.eof()) {
|
||||
std::getline(in, input);
|
||||
base64_cert += input + '\n';
|
||||
}
|
||||
|
||||
return base64_cert;
|
||||
}
|
||||
}
|
19
nvhttp.h
Normal file
19
nvhttp.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by loki on 6/3/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_NVHTTP_H
|
||||
#define SUNSHINE_NVHTTP_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA"
|
||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
namespace nvhttp {
|
||||
void start();
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_NVHTTP_H
|
40
platform/common.h
Normal file
40
platform/common.h
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_COMMON_H
|
||||
#define SUNSHINE_COMMON_H
|
||||
|
||||
#include <utility.h>
|
||||
|
||||
namespace platf {
|
||||
|
||||
void freeDisplay(void*);
|
||||
void freeImage(void*);
|
||||
void freeAudio(void*);
|
||||
void freeMic(void*);
|
||||
|
||||
using display_t = util::safe_ptr<void, freeDisplay>;
|
||||
using img_t = util::safe_ptr<void, freeImage>;
|
||||
using mic_t = util::safe_ptr<void, freeMic>;
|
||||
using audio_t = util::safe_ptr<void, freeAudio>;
|
||||
|
||||
display_t display();
|
||||
img_t snapshot(display_t &display);
|
||||
mic_t microphone();
|
||||
audio_t audio(mic_t &mic, std::uint32_t sample_size);
|
||||
|
||||
int32_t img_width(img_t &);
|
||||
int32_t img_height(img_t &);
|
||||
|
||||
uint8_t *img_data(img_t &);
|
||||
int16_t *audio_data(audio_t &);
|
||||
|
||||
void move_mouse(display_t::element_type *display, int deltaX, int deltaY);
|
||||
void button_mouse(display_t::element_type *display, int button, bool release);
|
||||
void scroll(display_t::element_type *display, int distance);
|
||||
void keyboard(display_t::element_type *display, uint16_t modcode, bool release);
|
||||
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_COMMON_H
|
314
platform/linux.cpp
Normal file
314
platform/linux.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
struct display_attr_t {
|
||||
display_attr_t() : display { XOpenDisplay(nullptr) }, window { DefaultRootWindow(display) }, attr {} {
|
||||
XGetWindowAttributes(display, window, &attr);
|
||||
}
|
||||
|
||||
~display_attr_t() {
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
||||
Display *display;
|
||||
Window window;
|
||||
XWindowAttributes attr;
|
||||
};
|
||||
|
||||
struct mic_attr_t {
|
||||
pa_sample_spec ss;
|
||||
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
||||
};
|
||||
|
||||
display_t display() {
|
||||
return display_t { new display_attr_t {} };
|
||||
}
|
||||
|
||||
img_t snapshot(display_t &display_void) {
|
||||
auto &display = *((display_attr_t*)display_void.get());
|
||||
|
||||
XImage *img { XGetImage(
|
||||
display.display,
|
||||
display.window,
|
||||
0, 0,
|
||||
display.attr.width, display.attr.height,
|
||||
AllPlanes, ZPixmap)
|
||||
};
|
||||
|
||||
XFixesCursorImage *overlay = XFixesGetCursorImage(display.display);
|
||||
|
||||
auto pixels = (int*)img->data;
|
||||
|
||||
auto screen_height = display.attr.height;
|
||||
auto screen_width = display.attr.width;
|
||||
|
||||
auto delta_height = std::min<uint16_t>(overlay->height, std::abs(overlay->y - screen_height));
|
||||
auto delta_width = std::min<uint16_t>(overlay->width, std::abs(overlay->x - screen_width));
|
||||
for(auto y = 0; y < delta_height; ++y) {
|
||||
|
||||
auto overlay_begin = &overlay->pixels[y * overlay->width];
|
||||
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
|
||||
|
||||
auto pixels_begin = &pixels[(y + overlay->y - 1) * screen_width + overlay->x - 1];
|
||||
std::for_each(overlay_begin, overlay_end, [&](long pixel) {
|
||||
int *pixel_p = (int*)&pixel;
|
||||
|
||||
if(pixel_p[0] != 0) {
|
||||
*pixels_begin = pixel_p[0];
|
||||
}
|
||||
++pixels_begin;
|
||||
});
|
||||
}
|
||||
|
||||
return img_t { img };
|
||||
}
|
||||
|
||||
uint8_t *img_data(img_t &img) {
|
||||
return (uint8_t*)((XImage*)img.get())->data;
|
||||
}
|
||||
|
||||
int32_t img_width(img_t &img) {
|
||||
return ((XImage*)img.get())->width;
|
||||
}
|
||||
|
||||
int32_t img_height(img_t &img) {
|
||||
return ((XImage*)img.get())->height;
|
||||
}
|
||||
|
||||
//FIXME: Pass frame_rate instead of hard coding it
|
||||
mic_t microphone() {
|
||||
mic_t mic {
|
||||
new mic_attr_t {
|
||||
{ PA_SAMPLE_S16LE, 48000, 2 },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
int error;
|
||||
mic_attr_t *mic_attr = (mic_attr_t*)mic.get();
|
||||
mic_attr->mic.reset(
|
||||
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, nullptr, "sunshine_record", &mic_attr->ss, nullptr, nullptr, &error)
|
||||
);
|
||||
|
||||
if(!mic_attr->mic) {
|
||||
auto err_str = pa_strerror(error);
|
||||
std::cout << "pa_simple_new() failed: "sv << err_str << std::endl;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return mic;
|
||||
}
|
||||
|
||||
audio_t audio(mic_t &mic, std::uint32_t buf_size) {
|
||||
auto mic_attr = (mic_attr_t*)mic.get();
|
||||
|
||||
audio_t result { new std::uint8_t[buf_size] };
|
||||
|
||||
auto buf = (std::uint8_t*)result.get();
|
||||
int error;
|
||||
if(pa_simple_read(mic_attr->mic.get(), buf, buf_size, &error)) {
|
||||
std::cout << "pa_simple_read() failed: "sv << pa_strerror(error) << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::int16_t *audio_data(audio_t &audio) {
|
||||
return (int16_t*)audio.get();
|
||||
}
|
||||
|
||||
|
||||
void move_mouse(display_t::element_type *display, int deltaX, int deltaY) {
|
||||
auto &disp = *((display_attr_t*)display);
|
||||
|
||||
XWarpPointer(disp.display, None, None, 0, 0, 0, 0, deltaX, deltaY);
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void button_mouse(display_t::element_type *display, int button, bool release) {
|
||||
auto &disp = *((display_attr_t *) display);
|
||||
|
||||
XTestFakeButtonEvent(disp.display, button, !release, CurrentTime);
|
||||
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void scroll(display_t::element_type *display, int distance) {
|
||||
auto &disp = *((display_attr_t *) display);
|
||||
|
||||
int button = distance > 0 ? 4 : 5;
|
||||
|
||||
distance = std::abs(distance / 120);
|
||||
while(distance > 0) {
|
||||
--distance;
|
||||
|
||||
XTestFakeButtonEvent(disp.display, button, True, CurrentTime);
|
||||
XTestFakeButtonEvent(disp.display, button, False, CurrentTime);
|
||||
|
||||
XSync(disp.display, 0);
|
||||
}
|
||||
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
uint16_t keysym(uint16_t modcode) {
|
||||
constexpr auto VK_NUMPAD = 0x60;
|
||||
constexpr auto VK_F1 = 0x70;
|
||||
|
||||
if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) {
|
||||
return XK_KP_0 + (modcode - VK_NUMPAD);
|
||||
}
|
||||
|
||||
if(modcode >= VK_F1 && modcode < VK_F1 + 13) {
|
||||
return XK_F1 + (modcode - VK_F1);
|
||||
}
|
||||
|
||||
|
||||
switch(modcode) {
|
||||
case 0x08:
|
||||
return XK_BackSpace;
|
||||
case 0x09:
|
||||
return XK_Tab;
|
||||
case 0x0D:
|
||||
return XK_Return;
|
||||
case 0x13:
|
||||
return XK_Pause;
|
||||
case 0x14:
|
||||
return XK_Caps_Lock;
|
||||
case 0x1B:
|
||||
return XK_Escape;
|
||||
case 0x21:
|
||||
return XK_Page_Up;
|
||||
case 0x22:
|
||||
return XK_Page_Down;
|
||||
case 0x23:
|
||||
return XK_End;
|
||||
case 0x24:
|
||||
return XK_Home;
|
||||
case 0x25:
|
||||
return XK_Left;
|
||||
case 0x26:
|
||||
return XK_Up;
|
||||
case 0x27:
|
||||
return XK_Right;
|
||||
case 0x28:
|
||||
return XK_Down;
|
||||
case 0x29:
|
||||
return XK_Select;
|
||||
case 0x2B:
|
||||
return XK_Execute;
|
||||
case 0x2C:
|
||||
return XK_Print; //FIXME: is this correct? (printscreen)
|
||||
case 0x2D:
|
||||
return XK_Insert;
|
||||
case 0x2E:
|
||||
return XK_Delete;
|
||||
case 0x2F:
|
||||
return XK_Help;
|
||||
case 0x6A:
|
||||
return XK_KP_Multiply;
|
||||
case 0x6B:
|
||||
return XK_KP_Add;
|
||||
case 0x6C:
|
||||
return XK_KP_Decimal; //FIXME: is this correct? (Comma)
|
||||
case 0x6D:
|
||||
return XK_KP_Subtract;
|
||||
case 0x6E:
|
||||
return XK_KP_Separator; //FIXME: is this correct? (Period)
|
||||
case 0x6F:
|
||||
return XK_KP_Divide;
|
||||
case 0x90:
|
||||
return XK_Num_Lock; //FIXME: is this correct: (NumlockClear)
|
||||
case 0x91:
|
||||
return XK_Scroll_Lock;
|
||||
case 0xA0:
|
||||
return XK_Shift_L;
|
||||
case 0xA1:
|
||||
return XK_Shift_R;
|
||||
case 0xA2:
|
||||
return XK_Control_L;
|
||||
case 0xA3:
|
||||
return XK_Control_R;
|
||||
case 0xA4:
|
||||
return XK_Alt_L;
|
||||
case 0xA5: /* return XK_Alt_R; */
|
||||
return XK_Super_L;
|
||||
case 0xBA:
|
||||
return XK_semicolon;
|
||||
case 0xBB:
|
||||
return XK_equal;
|
||||
case 0xBC:
|
||||
return XK_comma;
|
||||
case 0xBD:
|
||||
return XK_minus;
|
||||
case 0xBE:
|
||||
return XK_period;
|
||||
case 0xBF:
|
||||
return XK_slash;
|
||||
case 0xC0:
|
||||
return XK_grave;
|
||||
case 0xDB:
|
||||
return XK_bracketleft;
|
||||
case 0xDC:
|
||||
return XK_backslash;
|
||||
case 0xDD:
|
||||
return XK_bracketright;
|
||||
case 0xDE:
|
||||
return XK_apostrophe;
|
||||
case 0x01: //FIXME: Moonlight doesn't support Super key
|
||||
return XK_Super_L;
|
||||
case 0x02:
|
||||
return XK_Super_R;
|
||||
}
|
||||
|
||||
return modcode;
|
||||
}
|
||||
|
||||
void keyboard(display_t::element_type *display, uint16_t modcode, bool release) {
|
||||
auto &disp = *((display_attr_t *) display);
|
||||
KeyCode kc = XKeysymToKeycode(disp.display, keysym(modcode));
|
||||
|
||||
if(!kc) {
|
||||
return;
|
||||
}
|
||||
|
||||
XTestFakeKeyEvent(disp.display, kc, !release, 0);
|
||||
|
||||
XSync(disp.display, 0);
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void freeDisplay(void*p) {
|
||||
delete (display_attr_t*)p;
|
||||
}
|
||||
|
||||
void freeImage(void*p) {
|
||||
XDestroyImage((XImage*)p);
|
||||
}
|
||||
|
||||
void freeMic(void*p) {
|
||||
delete (mic_attr_t*)p;
|
||||
}
|
||||
|
||||
void freeAudio(void*p) {
|
||||
delete[] (std::uint8_t*)p;
|
||||
}
|
||||
}
|
87
queue.h
Normal file
87
queue.h
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// Created by loki on 6/10/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_QUEUE_H
|
||||
#define SUNSHINE_QUEUE_H
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace safe {
|
||||
|
||||
template<class T>
|
||||
class queue_t {
|
||||
using status_t = util::either_t<
|
||||
(std::is_same_v<T, bool> ||
|
||||
util::instantiation_of_v<std::unique_ptr, T> ||
|
||||
util::instantiation_of_v<std::shared_ptr, T> ||
|
||||
std::is_pointer_v<T>),
|
||||
T, std::optional<T>>;
|
||||
|
||||
public:
|
||||
template<class ...Args>
|
||||
void push(Args &&... args) {
|
||||
std::lock_guard lg{_lock};
|
||||
|
||||
if(!_continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.emplace_back(std::forward<Args>(args)...);
|
||||
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
status_t pop() {
|
||||
std::unique_lock ul{_lock};
|
||||
|
||||
if (!_continue) {
|
||||
return util::false_v<status_t>;
|
||||
}
|
||||
|
||||
while (_queue.empty()) {
|
||||
_cv.wait(ul);
|
||||
|
||||
if (!_continue) {
|
||||
return util::false_v<status_t>;
|
||||
}
|
||||
}
|
||||
|
||||
auto val = std::move(_queue.front());
|
||||
_queue.erase(std::begin(_queue));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
std::vector<T> &unsafe() {
|
||||
return _queue;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
std::lock_guard lg{_lock};
|
||||
|
||||
_continue = false;
|
||||
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
bool running() const {
|
||||
return _continue;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool _continue{true};
|
||||
|
||||
std::mutex _lock;
|
||||
std::condition_variable _cv;
|
||||
std::vector<T> _queue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_QUEUE_H
|
864
stream.cpp
Normal file
864
stream.cpp
Normal file
@ -0,0 +1,864 @@
|
||||
//
|
||||
// Created by loki on 6/5/19.
|
||||
//
|
||||
|
||||
#include <queue>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <moonlight-common-c/enet/include/enet/enet.h>
|
||||
#include <fstream>
|
||||
#include <openssl/err.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <moonlight-common-c/src/Video.h>
|
||||
#include <moonlight-common-c/src/Rtsp.h>
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
#include "config.h"
|
||||
#include "utility.h"
|
||||
#include "stream.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
#include "queue.h"
|
||||
#include "crypto.h"
|
||||
#include "input.h"
|
||||
|
||||
#define IDX_START_A 0
|
||||
#define IDX_REQUEST_IDR_FRAME 0
|
||||
#define IDX_START_B 1
|
||||
#define IDX_INVALIDATE_REF_FRAMES 2
|
||||
#define IDX_LOSS_STATS 3
|
||||
#define IDX_INPUT_DATA 5
|
||||
#define IDX_RUMBLE_DATA 6
|
||||
#define IDX_TERMINATION 7
|
||||
|
||||
static const short packetTypes[] = {
|
||||
0x0305, // Start A
|
||||
0x0307, // Start B
|
||||
0x0301, // Invalidate reference frames
|
||||
0x0201, // Loss Stats
|
||||
0x0204, // Frame Stats (unused)
|
||||
0x0206, // Input data
|
||||
0x010b, // Rumble data
|
||||
0x0100, // Termination
|
||||
};
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace sys = boost::system;
|
||||
|
||||
using asio::ip::tcp;
|
||||
using asio::ip::udp;
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace stream {
|
||||
|
||||
constexpr auto RTSP_SETUP_PORT = 48010;
|
||||
constexpr auto VIDEO_STREAM_PORT = 47998;
|
||||
constexpr auto CONTROL_PORT = 47999;
|
||||
constexpr auto AUDIO_STREAM_PORT = 48000;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct video_packet_raw_t {
|
||||
uint8_t *payload() {
|
||||
return (uint8_t *)(this + 1);
|
||||
}
|
||||
|
||||
RTP_PACKET rtp;
|
||||
NV_VIDEO_PACKET packet;
|
||||
};
|
||||
|
||||
struct audio_packet_raw_t {
|
||||
uint8_t *payload() {
|
||||
return (uint8_t *)(this + 1);
|
||||
}
|
||||
|
||||
RTP_PACKET rtp;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
crypto::aes_t gcm_key;
|
||||
crypto::aes_t iv;
|
||||
|
||||
struct config_t {
|
||||
audio::config_t audio;
|
||||
video::config_t monitor;
|
||||
int packetsize;
|
||||
|
||||
bool sops;
|
||||
std::optional<int> gcmap;
|
||||
};
|
||||
|
||||
struct session_t {
|
||||
config_t config;
|
||||
|
||||
std::thread audioThread;
|
||||
std::thread videoThread;
|
||||
std::thread controlThread;
|
||||
|
||||
std::chrono::steady_clock::time_point pingTimeout;
|
||||
int client_state;
|
||||
|
||||
crypto::aes_t gcm_key;
|
||||
crypto::aes_t iv;
|
||||
} session;
|
||||
|
||||
void free_msg(PRTSP_MESSAGE msg) {
|
||||
freeMessage(msg);
|
||||
|
||||
delete msg;
|
||||
}
|
||||
|
||||
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>;
|
||||
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
|
||||
using host_t = util::safe_ptr<ENetHost, enet_host_destroy>;
|
||||
using rh_t = util::safe_ptr<reed_solomon, reed_solomon_release>;
|
||||
using video_packet_t = util::safe_ptr<video_packet_raw_t, util::c_free>;
|
||||
using audio_packet_t = util::safe_ptr<audio_packet_raw_t, util::c_free>;
|
||||
|
||||
host_t host_create(ENetAddress &addr, std::uint16_t port) {
|
||||
enet_address_set_host(&addr, "0.0.0.0");
|
||||
enet_address_set_port(&addr, port);
|
||||
|
||||
return host_t { enet_host_create(PF_INET, &addr, 1, 1, 0, 0) };
|
||||
}
|
||||
|
||||
class server_t {
|
||||
public:
|
||||
server_t(server_t &&) noexcept = default;
|
||||
server_t &operator=(server_t &&) noexcept = default;
|
||||
|
||||
explicit server_t(std::uint16_t port) : _host { host_create(_addr, port) } {}
|
||||
|
||||
template<class T, class X>
|
||||
void iterate(std::chrono::duration<T, X> timeout) {
|
||||
ENetEvent event;
|
||||
auto res = enet_host_service(_host.get(), &event, std::chrono::floor<std::chrono::milliseconds>(timeout).count());
|
||||
|
||||
if(res > 0) {
|
||||
switch(event.type) {
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
{
|
||||
packet_t packet { event.packet };
|
||||
|
||||
std::uint16_t *type = (std::uint16_t *)packet->data;
|
||||
std::string_view payload { (char*)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) };
|
||||
|
||||
|
||||
auto cb = _map_type_cb.find(*type);
|
||||
if(cb == std::end(_map_type_cb)) {
|
||||
std::cout << "type [Unknown] { " << util::hex(*type).to_string_view() << " }" << std::endl;
|
||||
std::cout << "---data---" << std::endl << util::hex_vec(payload) << std::endl << "---end data---" << std::endl;
|
||||
}
|
||||
|
||||
else {
|
||||
cb->second(payload);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
std::cout << "CLIENT CONNECTED" << std::endl;
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
std::cout << "CLIENT DISCONNECTED" << std::endl;
|
||||
break;
|
||||
case ENET_EVENT_TYPE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void map(uint16_t type, std::function<void(const std::string_view&)> cb);
|
||||
private:
|
||||
std::unordered_map<std::uint8_t, std::function<void(const std::string_view&)>> _map_type_cb;
|
||||
ENetAddress _addr;
|
||||
host_t _host;
|
||||
};
|
||||
|
||||
namespace fec {
|
||||
using rs_t = util::safe_ptr<reed_solomon, reed_solomon_release>;
|
||||
|
||||
struct fec_t {
|
||||
size_t data_shards;
|
||||
size_t nr_shards;
|
||||
size_t percentage;
|
||||
|
||||
size_t blocksize;
|
||||
util::buffer_t<char> shards;
|
||||
|
||||
std::string_view operator[](size_t el) const {
|
||||
return { &shards[el*blocksize], blocksize };
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return nr_shards;
|
||||
}
|
||||
};
|
||||
|
||||
fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage) {
|
||||
auto payload_size = payload.size();
|
||||
|
||||
auto pad = payload_size % blocksize != 0;
|
||||
|
||||
auto data_shards = payload_size / blocksize + (pad ? 1 : 0);
|
||||
auto parity_shards = (data_shards * fecpercentage + 99) / 100;
|
||||
auto nr_shards = data_shards + parity_shards;
|
||||
|
||||
if(nr_shards > DATA_SHARDS_MAX) {
|
||||
std::cerr << "Error: number of fragments for reed solomon exceeds DATA_SHARDS_MAX"sv << std::endl;
|
||||
std::cerr << nr_shards << " > "sv << DATA_SHARDS_MAX << std::endl;
|
||||
exit(9);
|
||||
}
|
||||
|
||||
util::buffer_t<char> shards { nr_shards * blocksize };
|
||||
util::buffer_t<uint8_t*> shards_p { nr_shards };
|
||||
|
||||
// copy payload + padding
|
||||
auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards));
|
||||
std::fill(next, std::end(shards), 0); // padding with zero
|
||||
|
||||
for(auto x = 0; x < nr_shards; ++x) {
|
||||
shards_p[x] = (uint8_t*)&shards[x * blocksize];
|
||||
}
|
||||
|
||||
// packets = parity_shards + data_shards
|
||||
rs_t rs { reed_solomon_new(data_shards, parity_shards) };
|
||||
|
||||
reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize);
|
||||
|
||||
return {
|
||||
data_shards,
|
||||
nr_shards,
|
||||
fecpercentage,
|
||||
blocksize,
|
||||
std::move(shards)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template<class F>
|
||||
std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data, F &&f) {
|
||||
auto pad = data.size() % slice_size != 0;
|
||||
auto elements = data.size() / slice_size + (pad ? 1 : 0);
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
result.resize(elements * insert_size + data.size());
|
||||
|
||||
auto next = std::begin(data);
|
||||
for(auto x = 0; x < elements - 1; ++x) {
|
||||
void *p = &result[x*(insert_size + slice_size)];
|
||||
|
||||
f(p, x, elements);
|
||||
|
||||
std::copy(next, next + slice_size, (char*)p + insert_size);
|
||||
next += slice_size;
|
||||
}
|
||||
|
||||
if(pad) {
|
||||
auto x = elements - 1;
|
||||
void *p = &result[x*(insert_size + slice_size)];
|
||||
|
||||
f(p, x, elements);
|
||||
|
||||
std::copy(next, std::end(data), (char*)p + insert_size);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void print_msg(PRTSP_MESSAGE msg) {
|
||||
std::string_view type = msg->type == TYPE_RESPONSE ? "RESPONSE"sv : "REQUEST"sv;
|
||||
|
||||
std::string_view payload { msg->payload, (size_t)msg->payloadLength };
|
||||
std::string_view protocol { msg->protocol };
|
||||
auto seqnm = msg->sequenceNumber;
|
||||
std::string_view messageBuffer { msg->messageBuffer };
|
||||
|
||||
std::cout << "type ["sv << type << ']' << std::endl;
|
||||
std::cout << "sequence number ["sv << seqnm << ']' << std::endl;
|
||||
std::cout << "protocol :: "sv << protocol << std::endl;
|
||||
std::cout << "payload :: "sv << payload << std::endl;
|
||||
|
||||
if(msg->type == TYPE_RESPONSE) {
|
||||
auto &resp = msg->message.response;
|
||||
|
||||
auto statuscode = resp.statusCode;
|
||||
std::string_view status { resp.statusString };
|
||||
|
||||
std::cout << "statuscode :: "sv << statuscode << std::endl;
|
||||
std::cout << "status :: "sv << status << std::endl;
|
||||
}
|
||||
else {
|
||||
auto& req = msg->message.request;
|
||||
|
||||
std::string_view command { req.command };
|
||||
std::string_view target { req.target };
|
||||
|
||||
std::cout << "command :: "sv << command << std::endl;
|
||||
std::cout << "target :: "sv << target << std::endl;
|
||||
}
|
||||
|
||||
for(auto option = msg->options; option != nullptr; option = option->next) {
|
||||
std::string_view content { option->content };
|
||||
std::string_view name { option->option };
|
||||
|
||||
std::cout << name << " :: "sv << content << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "---Begin MessageBuffer---"sv << std::endl << messageBuffer << std::endl << "---End MessageBuffer---"sv << std::endl << std::endl;
|
||||
}
|
||||
|
||||
using frame_queue_t = std::vector<video::packet_t>;
|
||||
video::packet_t next_packet(uint16_t &frame, std::shared_ptr<safe::queue_t<video::packet_t>> &packets, frame_queue_t &packet_queue) {
|
||||
auto packet = packets->pop();
|
||||
|
||||
if(!packet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(packet->pts >= frame);
|
||||
|
||||
auto comp = [](const video::packet_t &l, const video::packet_t &r) {
|
||||
return l->pts > r->pts;
|
||||
};
|
||||
|
||||
if(packet->pts > frame) {
|
||||
packet_queue.emplace_back(std::move(packet));
|
||||
std::push_heap(std::begin(packet_queue), std::end(packet_queue), comp);
|
||||
|
||||
if (packet_queue.front()->pts != frame) {
|
||||
return next_packet(frame, packets, packet_queue);
|
||||
}
|
||||
|
||||
std::pop_heap(std::begin(packet_queue), std::end(packet_queue), comp);
|
||||
packet = std::move(packet_queue.back());
|
||||
packet_queue.pop_back();
|
||||
}
|
||||
|
||||
++frame;
|
||||
return packet;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> replace(const std::string_view &original, const std::string_view &old, const std::string_view &_new) {
|
||||
std::vector<uint8_t> replaced;
|
||||
|
||||
auto search = [&](auto it) {
|
||||
return std::search(it, std::end(original), std::begin(old), std::end(old));
|
||||
};
|
||||
|
||||
auto begin = std::begin(original);
|
||||
for(auto next = search(begin); next != std::end(original); next = search(++next)) {
|
||||
std::copy(begin, next, std::back_inserter(replaced));
|
||||
std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced));
|
||||
|
||||
next = begin = next + old.size();
|
||||
}
|
||||
|
||||
std::copy(begin, std::end(original), std::back_inserter(replaced));
|
||||
|
||||
return replaced;
|
||||
}
|
||||
|
||||
void server_t::map(uint16_t type, std::function<void(const std::string_view &)> cb) {
|
||||
_map_type_cb.emplace(type, std::move(cb));
|
||||
}
|
||||
|
||||
void controlThread() {
|
||||
server_t server { CONTROL_PORT };
|
||||
|
||||
std::shared_ptr display = platf::display();
|
||||
server.map(packetTypes[IDX_START_A], [](const std::string_view &payload) {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
std::cout << "type [IDX_START_A]"sv << std::endl;
|
||||
});
|
||||
|
||||
server.map(packetTypes[IDX_START_B], [](const std::string_view &payload) {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
std::cout << "type [IDX_START_B]"sv << std::endl;
|
||||
});
|
||||
|
||||
server.map(packetTypes[IDX_LOSS_STATS], [](const std::string_view &payload) {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
/* std::cout << "type [IDX_LOSS_STATS]"sv << std::endl;
|
||||
|
||||
int32_t *stats = (int32_t*)payload.data();
|
||||
auto count = stats[0];
|
||||
std::chrono::milliseconds t { stats[1] };
|
||||
|
||||
auto lastGoodFrame = stats[3];
|
||||
|
||||
std::cout << "---begin stats---" << std::endl;
|
||||
std::cout << "loss count since last report [" << count << ']' << std::endl;
|
||||
std::cout << "time in milli since last report [" << t.count() << ']' << std::endl;
|
||||
std::cout << "last good frame [" << lastGoodFrame << ']' << std::endl;
|
||||
std::cout << "---end stats---" << std::endl; */
|
||||
});
|
||||
|
||||
server.map(packetTypes[IDX_INVALIDATE_REF_FRAMES], [](const std::string_view &payload) {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
std::cout << "type [IDX_INVALIDATE_REF_FRAMES]"sv << std::endl;
|
||||
|
||||
std::int64_t *frames = (std::int64_t *)payload.data();
|
||||
auto firstFrame = frames[0];
|
||||
auto lastFrame = frames[1];
|
||||
|
||||
std::cout << "firstFrame [" << firstFrame << ']' << std::endl;
|
||||
std::cout << "lastFrame [" << lastFrame << ']' << std::endl;
|
||||
});
|
||||
|
||||
server.map(packetTypes[IDX_INPUT_DATA], [display](const std::string_view &payload) mutable {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
std::cout << "type [IDX_INPUT_DATA]"sv << std::endl;
|
||||
|
||||
int32_t tagged_cipher_length = util::endian::big(*(int32_t*)payload.data());
|
||||
std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length };
|
||||
|
||||
crypto::cipher_t cipher { session.gcm_key };
|
||||
cipher.padding = false;
|
||||
|
||||
std::vector<uint8_t> plaintext;
|
||||
if(cipher.decrypt_gcm(session.iv, tagged_cipher, plaintext)) {
|
||||
// something went wrong :(
|
||||
|
||||
std::cout << "failed to verify tag"sv << std::endl;
|
||||
session.client_state = 0;
|
||||
}
|
||||
|
||||
if(tagged_cipher_length >= 16 + session.iv.size()) {
|
||||
std::copy(payload.end() - 16, payload.end(), std::begin(session.iv));
|
||||
}
|
||||
|
||||
input::print(plaintext.data());
|
||||
input::passthrough(display.get(), plaintext.data());
|
||||
});
|
||||
|
||||
while(session.client_state > 0) {
|
||||
if(std::chrono::steady_clock::now() > session.pingTimeout) {
|
||||
session.client_state = 0;
|
||||
}
|
||||
|
||||
server.iterate(2s);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<udp::endpoint> recv_peer(udp::socket &sock) {
|
||||
std::array<char, 2048> buf;
|
||||
|
||||
char ping[] = {
|
||||
0x50, 0x49, 0x4E, 0x47
|
||||
};
|
||||
|
||||
udp::endpoint peer;
|
||||
while (session.client_state > 0) {
|
||||
asio::deadline_timer timer { sock.get_executor() };
|
||||
timer.expires_from_now(boost::posix_time::seconds(2));
|
||||
timer.async_wait([&](sys::error_code c){
|
||||
sock.cancel();
|
||||
});
|
||||
|
||||
sys::error_code ping_error;
|
||||
auto len = sock.receive_from(asio::buffer(buf), peer, 0, ping_error);
|
||||
if(ping_error == sys::errc::make_error_code(sys::errc::operation_canceled)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
timer.cancel();
|
||||
|
||||
if (len == 4 && !std::memcmp(ping, buf.data(), sizeof(ping))) {
|
||||
std::cout << "PING from ["sv << peer.address().to_string() << ':' << peer.port() << ']' << std::endl;
|
||||
|
||||
return std::make_optional(std::move(peer));;
|
||||
}
|
||||
|
||||
std::cout << "Unknown transmission: "sv << util::hex_vec(std::string_view{buf.data(), len}) << std::endl;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void audioThread() {
|
||||
auto &config = session.config;
|
||||
|
||||
asio::io_service io;
|
||||
udp::socket sock{io, udp::endpoint(udp::v6(), AUDIO_STREAM_PORT)};
|
||||
|
||||
auto peer = recv_peer(sock);
|
||||
if(!peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<safe::queue_t<audio::packet_t>> packets{new safe::queue_t<audio::packet_t>};
|
||||
|
||||
std::thread captureThread{audio::capture, packets, config.audio};
|
||||
|
||||
uint16_t frame{1};
|
||||
|
||||
while (auto packet = packets->pop()) {
|
||||
if(session.client_state == 0) {
|
||||
packets->stop();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
audio_packet_t audio_packet { (audio_packet_raw_t*)malloc(sizeof(audio_packet_raw_t) + packet->size()) };
|
||||
|
||||
audio_packet->rtp.sequenceNumber = util::endian::big(frame++);
|
||||
audio_packet->rtp.packetType = 97;
|
||||
std::copy(std::begin(*packet), std::end(*packet), audio_packet->payload());
|
||||
|
||||
sock.send_to(asio::buffer((char*)audio_packet.get(), sizeof(audio_packet_raw_t) + packet->size()), *peer);
|
||||
// std::cout << "Audio ["sv << frame << "] :: send..."sv << std::endl;
|
||||
}
|
||||
|
||||
captureThread.join();
|
||||
}
|
||||
|
||||
void videoThread() {
|
||||
auto &config = session.config;
|
||||
|
||||
int lowseq = 0;
|
||||
|
||||
asio::io_service io;
|
||||
udp::socket sock{io, udp::endpoint(udp::v6(), VIDEO_STREAM_PORT)};
|
||||
|
||||
auto peer = recv_peer(sock);
|
||||
if(!peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<safe::queue_t<video::packet_t>> packets{new safe::queue_t<video::packet_t>};
|
||||
|
||||
std::thread captureThread{video::capture_display, packets, config.monitor};
|
||||
|
||||
frame_queue_t packet_queue;
|
||||
uint16_t frame{1};
|
||||
|
||||
while (auto packet = next_packet(frame, packets, packet_queue)) {
|
||||
if(session.client_state == 0) {
|
||||
packets->stop();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
std::string_view payload{(char *) packet->data, (size_t) packet->size};
|
||||
std::vector<uint8_t> payload_new;
|
||||
|
||||
auto nv_packet_header = "\0017charss"sv;
|
||||
std::copy(std::begin(nv_packet_header), std::end(nv_packet_header), std::back_inserter(payload_new));
|
||||
std::copy(std::begin(payload), std::end(payload), std::back_inserter(payload_new));
|
||||
|
||||
payload = {(char *) payload_new.data(), payload_new.size()};
|
||||
|
||||
// make sure moonlight recognizes the nalu code for IDR frames
|
||||
if (packet->flags & AV_PKT_FLAG_KEY) {
|
||||
//TODO: Not all encoders encode their IDR frames with `"\000\000\001e"`
|
||||
auto seq_i_frame_old = "\000\000\001e"sv;
|
||||
auto seq_i_frame = "\000\000\000\001e"sv;
|
||||
|
||||
assert(std::search(std::begin(payload), std::end(payload), std::begin(seq_i_frame), std::end(seq_i_frame)) ==
|
||||
std::end(payload));
|
||||
payload_new = replace(payload, seq_i_frame_old, seq_i_frame);
|
||||
|
||||
payload = {(char *) payload_new.data(), payload_new.size()};
|
||||
}
|
||||
|
||||
// insert packet headers
|
||||
auto blocksize = config.packetsize + MAX_RTP_HEADER_SIZE;
|
||||
auto payload_blocksize = blocksize - sizeof(video_packet_raw_t);
|
||||
|
||||
auto fecpercentage { 25 };
|
||||
|
||||
payload_new = insert(sizeof(video_packet_raw_t), payload_blocksize,
|
||||
payload, [&](void *p, int fecIndex, int end) {
|
||||
video_packet_raw_t *video_packet = (video_packet_raw_t *)p;
|
||||
|
||||
video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA;
|
||||
video_packet->packet.frameIndex = packet->pts;
|
||||
video_packet->packet.streamPacketIndex = ((uint32_t)lowseq + fecIndex) << 8;
|
||||
video_packet->packet.fecInfo = (
|
||||
fecIndex << 12 |
|
||||
end << 22 |
|
||||
fecpercentage << 4
|
||||
);
|
||||
|
||||
if(fecIndex == 0) {
|
||||
video_packet->packet.flags |= FLAG_SOF;
|
||||
}
|
||||
|
||||
if(fecIndex == end - 1) {
|
||||
video_packet->packet.flags |= FLAG_EOF;
|
||||
}
|
||||
|
||||
video_packet->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + fecIndex);
|
||||
});
|
||||
|
||||
payload = {(char *) payload_new.data(), payload_new.size()};
|
||||
|
||||
auto shards = fec::encode(payload, blocksize, 25);
|
||||
|
||||
for (auto x = shards.data_shards; x < shards.size(); ++x) {
|
||||
video_packet_raw_t *inspect = (video_packet_raw_t *)shards[x].data();
|
||||
|
||||
inspect->packet.flags = FLAG_CONTAINS_PIC_DATA;
|
||||
inspect->packet.streamPacketIndex = ((uint32_t)(lowseq + x)) << 8;
|
||||
inspect->packet.frameIndex = packet->pts;
|
||||
inspect->packet.fecInfo = (
|
||||
x << 12 |
|
||||
shards.data_shards << 22 |
|
||||
fecpercentage << 4
|
||||
);
|
||||
|
||||
inspect->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + x);
|
||||
}
|
||||
|
||||
for (auto x = 0; x < shards.size(); ++x) {
|
||||
sock.send_to(asio::buffer(shards[x]), *peer);
|
||||
}
|
||||
|
||||
// std::cout << "Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv << std::endl;
|
||||
lowseq += shards.size();
|
||||
|
||||
}
|
||||
|
||||
captureThread.join();
|
||||
}
|
||||
|
||||
void respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) {
|
||||
RTSP_MESSAGE resp {};
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
freeMessage(&resp);
|
||||
});
|
||||
|
||||
createRtspResponse(&resp, nullptr, 0, const_cast<char*>("RTSP/1.0"), statuscode, const_cast<char*>(status_msg), seqn, options, const_cast<char*>(payload.data()), (int)payload.size());
|
||||
|
||||
int serialized_len;
|
||||
util::c_ptr<char> raw_resp { serializeRtspMessage(&resp, &serialized_len) };
|
||||
|
||||
std::string_view tmp_resp { raw_resp.get(), (size_t)serialized_len };
|
||||
std::cout << "---Begin Response---" << std::endl << tmp_resp << "---End Response---" << std::endl << std::endl;
|
||||
|
||||
asio::write(sock, asio::buffer(tmp_resp));
|
||||
}
|
||||
|
||||
void cmd_not_found(tcp::socket &&sock, msg_t&& req) {
|
||||
respond(sock, nullptr, 404, "NOT FOUND", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void cmd_option(tcp::socket &&sock, msg_t&& req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char*>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void cmd_describe(tcp::socket &&sock, msg_t&& req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char*>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
// FIXME: Moonlight will accept the payload, but the value of the option is not correct
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, "surround-params=NONE"sv);
|
||||
}
|
||||
|
||||
void cmd_setup(tcp::socket &&sock, msg_t &&req) {
|
||||
OPTION_ITEM options[2] {};
|
||||
|
||||
auto &seqn = options[0];
|
||||
auto &session_option = options[1];
|
||||
|
||||
seqn.option = const_cast<char*>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
seqn.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
if(session.client_state >= 0) {
|
||||
// already streaming
|
||||
|
||||
respond(sock, &seqn, 503, "Service Unavailable", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view target { req->message.request.target };
|
||||
auto begin = std::find(std::begin(target), std::end(target), '=') + 1;
|
||||
auto end = std::find(begin, std::end(target), '/');
|
||||
std::string_view type { begin, (size_t)std::distance(begin, end) };
|
||||
|
||||
if(type == "audio"sv) {
|
||||
seqn.next = &session_option;
|
||||
|
||||
session_option.option = const_cast<char*>("Session");
|
||||
session_option.content = const_cast<char*>("DEADBEEFCAFE;timeout = 90");
|
||||
}
|
||||
else if(type != "video"sv && type != "control"sv) {
|
||||
cmd_not_found(std::move(sock), std::move(req));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
respond(sock, &seqn, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void cmd_announce(tcp::socket &&sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char*>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
if(session.client_state >= 0) {
|
||||
// already streaming
|
||||
|
||||
respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view payload { req->payload, (size_t)req->payloadLength };
|
||||
std::vector<std::string_view> lines;
|
||||
|
||||
auto whitespace = [](char ch) {
|
||||
return ch == '\n' || ch == '\r';
|
||||
};
|
||||
|
||||
{
|
||||
auto pos = std::begin(payload);
|
||||
auto begin = pos;
|
||||
while (pos != std::end(payload)) {
|
||||
if (whitespace(*pos++)) {
|
||||
lines.emplace_back(begin, pos - begin - 1);
|
||||
|
||||
while(whitespace(*pos)) { ++pos; }
|
||||
begin = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view client;
|
||||
std::unordered_map<std::string_view, std::string_view> args;
|
||||
|
||||
for(auto line : lines) {
|
||||
auto type = line.substr(0, 2);
|
||||
if(type == "s="sv) {
|
||||
client = line.substr(2);
|
||||
}
|
||||
else if(type == "a=") {
|
||||
auto pos = line.find(':');
|
||||
|
||||
auto name = line.substr(2, pos - 2);
|
||||
auto val = line.substr(pos + 1);
|
||||
|
||||
if(val[val.size() -1] == ' ') {
|
||||
val = val.substr(0, val.size() -1);
|
||||
}
|
||||
args.emplace(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
auto &config = session.config;
|
||||
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
|
||||
config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv));
|
||||
config.monitor.framerate = util::from_view(args.at("x-nv-video[0].maxFPS"sv));
|
||||
config.monitor.bitrate = util::from_view(args.at("x-nv-video[0].initialBitrateKbps"sv));
|
||||
config.monitor.slicesPerFrame = util::from_view(args.at("x-nv-video[0].videoEncoderSlicesPerFrame"sv));
|
||||
|
||||
config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv));
|
||||
config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv));
|
||||
config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv));
|
||||
|
||||
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
|
||||
|
||||
std::copy(std::begin(gcm_key), std::end(gcm_key), std::begin(session.gcm_key));
|
||||
std::copy(std::begin(iv), std::end(iv), std::begin(session.iv));
|
||||
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
session.client_state = 1;
|
||||
|
||||
session.audioThread = std::thread {audioThread};
|
||||
session.videoThread = std::thread {videoThread};
|
||||
session.controlThread = std::thread {controlThread};
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void cmd_play(tcp::socket &&sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char*>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void rtpThread() {
|
||||
session.client_state = -1;
|
||||
|
||||
asio::io_service io;
|
||||
|
||||
tcp::acceptor acceptor { io, tcp::endpoint { tcp::v6(), RTSP_SETUP_PORT } };
|
||||
|
||||
std::unordered_map<std::string_view, std::function<void(tcp::socket &&, msg_t &&)>> map_cmd_func;
|
||||
map_cmd_func.emplace("OPTIONS"sv, &cmd_option);
|
||||
map_cmd_func.emplace("DESCRIBE"sv, &cmd_describe);
|
||||
map_cmd_func.emplace("SETUP"sv, &cmd_setup);
|
||||
map_cmd_func.emplace("ANNOUNCE"sv, &cmd_announce);
|
||||
|
||||
map_cmd_func.emplace("PLAY"sv, &cmd_play);
|
||||
|
||||
while(true) {
|
||||
tcp::socket sock { io };
|
||||
|
||||
acceptor.accept(sock);
|
||||
sock.set_option(tcp::no_delay(true));
|
||||
|
||||
std::array<char, 2048> buf;
|
||||
|
||||
auto len = sock.read_some(asio::buffer(buf));
|
||||
buf[std::min(buf.size(), len)] = '\0';
|
||||
|
||||
msg_t req { new RTSP_MESSAGE {} };
|
||||
|
||||
parseRtspMessage(req.get(), buf.data(), len);
|
||||
|
||||
print_msg(req.get());
|
||||
|
||||
auto func = map_cmd_func.find(req->message.request.command);
|
||||
if(func == std::end(map_cmd_func)) {
|
||||
cmd_not_found(std::move(sock), std::move(req));
|
||||
}
|
||||
else {
|
||||
func->second(std::move(sock), std::move(req));
|
||||
}
|
||||
|
||||
if(session.client_state == 0) {
|
||||
session.audioThread.join();
|
||||
session.videoThread.join();
|
||||
session.controlThread.join();
|
||||
|
||||
session.client_state = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
stream.h
Normal file
19
stream.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by loki on 6/5/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_STREAM_H
|
||||
#define SUNSHINE_STREAM_H
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
namespace stream {
|
||||
|
||||
extern crypto::aes_t gcm_key;
|
||||
extern crypto::aes_t iv;
|
||||
|
||||
void rtpThread();
|
||||
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_STREAM_H
|
29
sunshine.conf
Normal file
29
sunshine.conf
Normal file
@ -0,0 +1,29 @@
|
||||
# Pretty self-explanatory
|
||||
# If no external IP address is given, the local IP address is used
|
||||
# external_ip = 123.456.789.12
|
||||
|
||||
# The private key must be 2048 bits
|
||||
# pkey = /dir/pkey.pem
|
||||
|
||||
# The certificate must be signed with a 2048 bit key
|
||||
# cert = /dir/cert.pem
|
||||
|
||||
# Pretty self-explanatory
|
||||
unique_id = 03904e64-51da-4fb3-9afd-a9f7ff70fea4
|
||||
|
||||
# The file where info on paired devices is stored
|
||||
file_devices = devices.xml
|
||||
|
||||
# How long to wait in milliseconds for data from moonlight before shutting down the stream
|
||||
ping_timeout = 2000
|
||||
|
||||
###############################################
|
||||
# FFmpeg software encoding parameters
|
||||
# Honestly, I have no idea what the optimal values would be.
|
||||
# Play around with this :)
|
||||
max_b_frames = 16
|
||||
gop_size = 24
|
||||
|
||||
# Constant Rate Factor. Between 1 and 52.
|
||||
# Higher value means more compression, but less quality
|
||||
crf = 35
|
643
utility.h
Normal file
643
utility.h
Normal file
@ -0,0 +1,643 @@
|
||||
#ifndef UTILITY_H
|
||||
#define UTILITY_H
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <string_view>
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR(x)\
|
||||
x(x&&) noexcept = default;\
|
||||
x&operator=(x&&) noexcept = default;\
|
||||
x() = default;
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR_THROW(x)\
|
||||
x(x&&) = default;\
|
||||
x&operator=(x&&) = default;\
|
||||
x() = default;
|
||||
|
||||
#define TUPLE_2D(a,b, expr)\
|
||||
decltype(expr) a##_##b = expr;\
|
||||
auto &a = std::get<0>(a##_##b);\
|
||||
auto &b = std::get<1>(a##_##b)
|
||||
|
||||
#define TUPLE_2D_REF(a,b, expr)\
|
||||
auto &a##_##b = expr;\
|
||||
auto &a = std::get<0>(a##_##b);\
|
||||
auto &b = std::get<1>(a##_##b)
|
||||
|
||||
#define TUPLE_3D(a,b,c, expr)\
|
||||
decltype(expr) a##_##b##_##c = expr;\
|
||||
auto &a = std::get<0>(a##_##b##_##c);\
|
||||
auto &b = std::get<1>(a##_##b##_##c);\
|
||||
auto &c = std::get<2>(a##_##b##_##c)
|
||||
|
||||
#define TUPLE_3D_REF(a,b,c, expr)\
|
||||
auto &a##_##b##_##c = expr;\
|
||||
auto &a = std::get<0>(a##_##b##_##c);\
|
||||
auto &b = std::get<1>(a##_##b##_##c);\
|
||||
auto &c = std::get<2>(a##_##b##_##c)
|
||||
|
||||
namespace util {
|
||||
|
||||
template<template<typename...> class X, class...Y>
|
||||
struct __instantiation_of : public std::false_type {};
|
||||
|
||||
template<template<typename...> class X, class... Y>
|
||||
struct __instantiation_of<X, X<Y...>> : public std::true_type {};
|
||||
|
||||
template<template<typename...> class X, class T, class...Y>
|
||||
static constexpr auto instantiation_of_v = __instantiation_of<X, T, Y...>::value;
|
||||
|
||||
template<bool V, class X, class Y>
|
||||
struct __either;
|
||||
|
||||
template<class X, class Y>
|
||||
struct __either<true, X, Y> {
|
||||
using type = X;
|
||||
};
|
||||
|
||||
template<class X, class Y>
|
||||
struct __either<false, X, Y> {
|
||||
using type = Y;
|
||||
};
|
||||
|
||||
template<bool V, class X, class Y>
|
||||
using either_t = typename __either<V, X, Y>::type;
|
||||
|
||||
template<class T, class V = void>
|
||||
struct __false_v;
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
|
||||
static constexpr std::nullopt_t value = std::nullopt;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<
|
||||
(std::is_pointer_v<T> || instantiation_of_v<std::unique_ptr, T> || instantiation_of_v<std::shared_ptr, T>)
|
||||
>> {
|
||||
static constexpr std::nullptr_t value = nullptr;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
static constexpr auto false_v = __false_v<T>::value;
|
||||
|
||||
template<class T>
|
||||
class FailGuard {
|
||||
public:
|
||||
FailGuard() = delete;
|
||||
FailGuard(T && f) noexcept : _func { std::forward<T>(f) } {}
|
||||
FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } {
|
||||
this->failure = other.failure;
|
||||
|
||||
other.failure = false;
|
||||
}
|
||||
|
||||
FailGuard(const FailGuard &) = delete;
|
||||
|
||||
FailGuard &operator=(const FailGuard &) = delete;
|
||||
FailGuard &operator=(FailGuard &&other) = delete;
|
||||
|
||||
~FailGuard() noexcept {
|
||||
if(failure) {
|
||||
_func();
|
||||
}
|
||||
}
|
||||
|
||||
void disable() { failure = false; }
|
||||
bool failure { true };
|
||||
private:
|
||||
T _func;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
auto fail_guard(T && f) {
|
||||
return FailGuard<T> { std::forward<T>(f) };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void append_struct(std::vector<uint8_t> &buf, const T &_struct) {
|
||||
constexpr size_t data_len = sizeof(_struct);
|
||||
|
||||
buf.reserve(data_len);
|
||||
|
||||
auto *data = (uint8_t *) & _struct;
|
||||
|
||||
for (size_t x = 0; x < data_len; ++x) {
|
||||
buf.push_back(data[x]);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
class Hex {
|
||||
public:
|
||||
typedef T elem_type;
|
||||
private:
|
||||
const char _bits[16] {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
char _hex[sizeof(elem_type) * 2];
|
||||
public:
|
||||
Hex(const elem_type &elem, bool rev) {
|
||||
if(!rev) {
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem) + sizeof(elem_type) - 1;
|
||||
for (auto it = begin(); it < cend();) {
|
||||
*it++ = _bits[*data / 16];
|
||||
*it++ = _bits[*data-- % 16];
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem);
|
||||
for (auto it = begin(); it < cend();) {
|
||||
*it++ = _bits[*data / 16];
|
||||
*it++ = _bits[*data++ % 16];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *begin() { return _hex; }
|
||||
char *end() { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
const char *begin() const { return _hex; }
|
||||
const char *end() const { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
const char *cbegin() const { return _hex; }
|
||||
const char *cend() const { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
std::string to_string() const {
|
||||
return { begin(), end() };
|
||||
}
|
||||
|
||||
std::string_view to_string_view() const {
|
||||
return { begin(), sizeof(elem_type) * 2 };
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
Hex<T> hex(const T &elem, bool rev = false) {
|
||||
return Hex<T>(elem, rev);
|
||||
}
|
||||
|
||||
template<class It>
|
||||
std::string hex_vec(It begin, It end, bool rev = false) {
|
||||
auto str_size = 2*std::distance(begin, end);
|
||||
|
||||
|
||||
std::string hex;
|
||||
hex.resize(str_size);
|
||||
|
||||
const char _bits[16] {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
if(rev) {
|
||||
for (auto it = std::begin(hex); it < std::end(hex);) {
|
||||
*it++ = _bits[((uint8_t)*begin) / 16];
|
||||
*it++ = _bits[((uint8_t)*begin++) % 16];
|
||||
}
|
||||
}
|
||||
else {
|
||||
--end;
|
||||
for (auto it = std::begin(hex); it < std::end(hex);) {
|
||||
*it++ = _bits[((uint8_t)*end) / 16];
|
||||
*it++ = _bits[((uint8_t)*end--) % 16];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
template<class C>
|
||||
std::string hex_vec(C&& c, bool rev = false) {
|
||||
return hex_vec(std::begin(c), std::end(c), rev);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
std::uint8_t buf[sizeof(T)];
|
||||
|
||||
static char constexpr shift_bit = 'a' - 'A';
|
||||
auto is_convertable = [] (char ch) -> bool {
|
||||
if(isdigit(ch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ch |= shift_bit;
|
||||
|
||||
if('a' > ch || ch > 'z') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||
if(buf_size != sizeof(T)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char *data = hex.data() + hex.size() -1;
|
||||
|
||||
auto convert = [] (char ch) -> std::uint8_t {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return (std::uint8_t)ch - '0';
|
||||
}
|
||||
|
||||
return (std::uint8_t)(ch | (char)32) - 'a' + (char)10;
|
||||
};
|
||||
|
||||
for(auto &el : buf) {
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_r = convert(*data--);
|
||||
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_l = convert(*data--);
|
||||
|
||||
el = (ch_l << 4) | ch_r;
|
||||
}
|
||||
|
||||
if(rev) {
|
||||
std::reverse(std::begin(buf), std::end(buf));
|
||||
}
|
||||
|
||||
return *reinterpret_cast<T *>(buf);
|
||||
}
|
||||
|
||||
inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
|
||||
std::string buf;
|
||||
|
||||
static char constexpr shift_bit = 'a' - 'A';
|
||||
auto is_convertable = [] (char ch) -> bool {
|
||||
if(isdigit(ch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ch |= shift_bit;
|
||||
|
||||
if('a' > ch || ch > 'z') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||
buf.resize(buf_size);
|
||||
|
||||
const char *data = hex.data() + hex.size() -1;
|
||||
|
||||
auto convert = [] (char ch) -> std::uint8_t {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return (std::uint8_t)ch - '0';
|
||||
}
|
||||
|
||||
return (std::uint8_t)(ch | (char)32) - 'a' + (char)10;
|
||||
};
|
||||
|
||||
for(auto &el : buf) {
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_r = convert(*data--);
|
||||
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_l = convert(*data--);
|
||||
|
||||
el = (ch_l << 4) | ch_r;
|
||||
}
|
||||
|
||||
if(rev) {
|
||||
std::reverse(std::begin(buf), std::end(buf));
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
class hash {
|
||||
public:
|
||||
using value_type = T;
|
||||
std::size_t operator()(const value_type &value) const {
|
||||
const auto *p = reinterpret_cast<const char *>(&value);
|
||||
|
||||
return std::hash<std::string_view>{}(std::string_view { p, sizeof(value_type) });
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
auto enm(const T& val) -> const std::underlying_type_t<T>& {
|
||||
return *reinterpret_cast<const std::underlying_type_t<T>*>(&val);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto enm(T& val) -> std::underlying_type_t<T>& {
|
||||
return *reinterpret_cast<std::underlying_type_t<T>*>(&val);
|
||||
}
|
||||
|
||||
template<class ReturnType, class ...Args>
|
||||
struct Function {
|
||||
typedef ReturnType (*type)(Args...);
|
||||
};
|
||||
|
||||
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
|
||||
struct Destroy {
|
||||
typedef T pointer;
|
||||
|
||||
void operator()(pointer p) {
|
||||
function(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T, typename Function<void, T*>::type function>
|
||||
using safe_ptr = std::unique_ptr<T, Destroy<T*, void, function>>;
|
||||
|
||||
// You cannot specialize an alias
|
||||
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
|
||||
using safe_ptr_v2 = std::unique_ptr<T, Destroy<T*, ReturnType, function>>;
|
||||
|
||||
template<class T>
|
||||
void c_free(T *p) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
using c_ptr = safe_ptr<T, c_free<T>>;
|
||||
|
||||
template<class T>
|
||||
class FakeContainer {
|
||||
typedef T pointer;
|
||||
|
||||
pointer _begin;
|
||||
pointer _end;
|
||||
|
||||
public:
|
||||
FakeContainer(pointer begin, pointer end) : _begin(begin), _end(end) {}
|
||||
|
||||
pointer begin() { return _begin; }
|
||||
pointer end() { return _end; }
|
||||
|
||||
const pointer begin() const { return _begin; }
|
||||
const pointer end() const { return _end; }
|
||||
|
||||
const pointer cbegin() const { return _begin; }
|
||||
const pointer cend() const { return _end; }
|
||||
|
||||
pointer data() { return begin(); }
|
||||
const pointer data() const { return cbegin(); }
|
||||
|
||||
std::size_t size() const { return std::distance(begin(), end()); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T> toContainer(T begin, T end) {
|
||||
return { begin, end };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T> toContainer(T begin, std::size_t end) {
|
||||
return { begin, begin + end };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T*> toContainer(T * const begin) {
|
||||
T *end = begin;
|
||||
|
||||
auto default_val = T();
|
||||
while(*end != default_val) {
|
||||
++end;
|
||||
}
|
||||
|
||||
return toContainer(begin, end);
|
||||
}
|
||||
|
||||
template<class T, class H>
|
||||
struct _init_helper;
|
||||
|
||||
template<template<class...> class T, class H, class... Args>
|
||||
struct _init_helper<T<Args...>, H> {
|
||||
using type = T<Args...>;
|
||||
|
||||
static type move(Args&&... args, H&&) {
|
||||
return std::make_tuple(std::move(args)...);
|
||||
}
|
||||
|
||||
static type copy(const Args&... args, const H&) {
|
||||
return std::make_tuple(args...);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::int64_t from_chars(const char *begin, const char *end) {
|
||||
std::int64_t res {};
|
||||
std::int64_t mul = 1;
|
||||
while(begin != --end) {
|
||||
res += (std::int64_t)(*end - '0') * mul;
|
||||
|
||||
mul *= 10;
|
||||
}
|
||||
|
||||
return *begin != '-' ? res + (std::int64_t)(*begin - '0') * mul : -res;
|
||||
}
|
||||
|
||||
inline std::int64_t from_view(const std::string_view &number) {
|
||||
return from_chars(std::begin(number), std::end(number));
|
||||
}
|
||||
|
||||
template<class X, class Y>
|
||||
class Either : public std::variant<X, Y> {
|
||||
public:
|
||||
using std::variant<X, Y>::variant;
|
||||
|
||||
constexpr bool has_left() const {
|
||||
return std::holds_alternative<X>(*this);
|
||||
}
|
||||
constexpr bool has_right() const {
|
||||
return std::holds_alternative<Y>(*this);
|
||||
}
|
||||
|
||||
X &left() {
|
||||
return std::get<X>(*this);
|
||||
}
|
||||
|
||||
Y &right() {
|
||||
return std::get<Y>(*this);
|
||||
}
|
||||
|
||||
const X &left() const {
|
||||
return std::get<X>(*this);
|
||||
}
|
||||
|
||||
const Y &right() const {
|
||||
return std::get<Y>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class buffer_t {
|
||||
public:
|
||||
buffer_t() : _els { 0 } {};
|
||||
buffer_t(buffer_t&&) noexcept = default;
|
||||
buffer_t &operator=(buffer_t&& other) noexcept {
|
||||
std::swap(_els, other._els);
|
||||
|
||||
_buf = std::move(other._buf);
|
||||
|
||||
return *this;
|
||||
};
|
||||
|
||||
explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {}
|
||||
explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {
|
||||
std::fill_n(_buf.get(), elements, t);
|
||||
}
|
||||
|
||||
T &operator[](size_t el) {
|
||||
return _buf[el];
|
||||
}
|
||||
|
||||
const T &operator[](size_t el) const {
|
||||
return _buf[el];
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _els;
|
||||
}
|
||||
|
||||
void fake_resize(std::size_t els) {
|
||||
_els = els;
|
||||
}
|
||||
|
||||
T *begin() {
|
||||
return _buf.get();
|
||||
}
|
||||
|
||||
const T *begin() const {
|
||||
return _buf.get();
|
||||
}
|
||||
|
||||
T *end() {
|
||||
return _buf.get() + _els;
|
||||
}
|
||||
|
||||
const T *end() const {
|
||||
return _buf.get() + _els;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _els;
|
||||
std::unique_ptr<T[]> _buf;
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
T either(std::optional<T> &&l, T &&r) {
|
||||
if(l) {
|
||||
return std::move(*l);
|
||||
}
|
||||
|
||||
return std::forward<T>(r);
|
||||
}
|
||||
|
||||
namespace endian {
|
||||
template<class T = void>
|
||||
struct endianness {
|
||||
enum : bool {
|
||||
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
|
||||
defined(__BIG_ENDIAN__) || \
|
||||
defined(__ARMEB__) || \
|
||||
defined(__THUMBEB__) || \
|
||||
defined(__AARCH64EB__) || \
|
||||
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
|
||||
// It's a big-endian target architecture
|
||||
little = false,
|
||||
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
|
||||
defined(__LITTLE_ENDIAN__) || \
|
||||
defined(__ARMEL__) || \
|
||||
defined(__THUMBEL__) || \
|
||||
defined(__AARCH64EL__) || \
|
||||
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
|
||||
// It's a little-endian target architecture
|
||||
little = true,
|
||||
#else
|
||||
#error "Unknown Endianness"
|
||||
#endif
|
||||
big = !little
|
||||
};
|
||||
};
|
||||
|
||||
template<class T, class S = void>
|
||||
struct endian_helper { };
|
||||
|
||||
template<class T>
|
||||
struct endian_helper<T, std::enable_if_t<
|
||||
!(instantiation_of_v<std::optional, T>)
|
||||
>> {
|
||||
static inline T big(T x) {
|
||||
if constexpr (endianness<T>::little) {
|
||||
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
|
||||
|
||||
std::reverse(data, data + sizeof(x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline T little(T x) {
|
||||
if constexpr (endianness<T>::big) {
|
||||
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
|
||||
|
||||
std::reverse(data, data + sizeof(x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct endian_helper<T, std::enable_if_t<
|
||||
instantiation_of_v<std::optional, T>
|
||||
>> {
|
||||
static inline T little(T x) {
|
||||
if(!x) return x;
|
||||
|
||||
if constexpr (endianness<T>::big) {
|
||||
auto *data = reinterpret_cast<uint8_t*>(&*x);
|
||||
|
||||
std::reverse(data, data + sizeof(*x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
static inline T big(T x) {
|
||||
if(!x) return x;
|
||||
|
||||
if constexpr (endianness<T>::big) {
|
||||
auto *data = reinterpret_cast<uint8_t*>(&*x);
|
||||
|
||||
std::reverse(data, data + sizeof(*x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline auto little(T x) { return endian_helper<T>::little(x); }
|
||||
|
||||
template<class T>
|
||||
inline auto big(T x) { return endian_helper<T>::big(x); }
|
||||
} /* endian */
|
||||
|
||||
} /* util */
|
||||
#endif
|
50
uuid.h
Normal file
50
uuid.h
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by loki on 8-2-19.
|
||||
//
|
||||
|
||||
#ifndef T_MAN_UUID_H
|
||||
#define T_MAN_UUID_H
|
||||
|
||||
#include <random>
|
||||
|
||||
union uuid_t {
|
||||
std::uint8_t b8[16];
|
||||
std::uint16_t b16[8];
|
||||
std::uint32_t b32[4];
|
||||
std::uint64_t b64[2];
|
||||
|
||||
static uuid_t generate(std::default_random_engine &engine) {
|
||||
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max());
|
||||
|
||||
uuid_t buf;
|
||||
for(auto &el : buf.b8) {
|
||||
el = dist(engine);
|
||||
}
|
||||
|
||||
buf.b8[7] &= (std::uint8_t) 0b00101111;
|
||||
buf.b8[9] &= (std::uint8_t) 0b10011111;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static uuid_t generate() {
|
||||
std::random_device r;
|
||||
|
||||
std::default_random_engine engine { r() };
|
||||
|
||||
return generate(engine);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const uuid_t &other) const {
|
||||
return b64[0] == other.b64[0] && b64[1] == other.b64[1];
|
||||
}
|
||||
|
||||
constexpr bool operator<(const uuid_t &other) const {
|
||||
return (b64[0] < other.b64[0] || (b64[0] == other.b64[0] && b64[1] < other.b64[1]));
|
||||
}
|
||||
|
||||
constexpr bool operator>(const uuid_t &other) const {
|
||||
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1]));
|
||||
}
|
||||
};
|
||||
#endif //T_MAN_UUID_H
|
176
video.cpp
Normal file
176
video.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
//
|
||||
// Created by loki on 6/6/19.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
#include <platform/common.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "config.h"
|
||||
#include "video.h"
|
||||
|
||||
namespace video {
|
||||
using namespace std::literals;
|
||||
|
||||
void free_ctx(AVCodecContext *ctx) {
|
||||
avcodec_free_context(&ctx);
|
||||
}
|
||||
|
||||
void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
void free_packet(AVPacket *packet) {
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
|
||||
using ctx_t = util::safe_ptr<AVCodecContext, free_ctx>;
|
||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||
|
||||
using sws_t = util::safe_ptr<SwsContext, sws_freeContext>;
|
||||
|
||||
auto open_codec(ctx_t &ctx, AVCodec *codec, AVDictionary **options) {
|
||||
avcodec_open2(ctx.get(), codec, options);
|
||||
|
||||
return util::fail_guard([&]() {
|
||||
avcodec_close(ctx.get());
|
||||
});
|
||||
}
|
||||
|
||||
void encode(int64_t frame, ctx_t &ctx, sws_t &sws, frame_t &yuv_frame, platf::img_t &img, std::shared_ptr<safe::queue_t<packet_t>> &packets) {
|
||||
av_frame_make_writable(yuv_frame.get());
|
||||
|
||||
const int linesizes[2] {
|
||||
(int)(platf::img_width(img) * sizeof(int)), 0
|
||||
};
|
||||
|
||||
auto data = platf::img_data(img);
|
||||
int ret = sws_scale(sws.get(), (uint8_t*const*)&data, linesizes, 0, platf::img_height(img), yuv_frame->data, yuv_frame->linesize);
|
||||
|
||||
if(ret <= 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
yuv_frame->pts = frame;
|
||||
|
||||
/* send the frame to the encoder */
|
||||
ret = avcodec_send_frame(ctx.get(), yuv_frame.get());
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "error sending a frame for encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (ret >= 0) {
|
||||
packet_t packet { av_packet_alloc() };
|
||||
|
||||
ret = avcodec_receive_packet(ctx.get(), packet.get());
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return;
|
||||
else if (ret < 0) {
|
||||
fprintf(stderr, "error during encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
packets->push(std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void encodeThread(
|
||||
std::shared_ptr<safe::queue_t<platf::img_t>> images,
|
||||
std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config) {
|
||||
int framerate = config.framerate;
|
||||
|
||||
auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
|
||||
ctx_t ctx{avcodec_alloc_context3(codec)};
|
||||
|
||||
frame_t yuv_frame{av_frame_alloc()};
|
||||
|
||||
ctx->width = config.width;
|
||||
ctx->height = config.height;
|
||||
ctx->bit_rate = config.bitrate;
|
||||
ctx->time_base = AVRational{1, framerate};
|
||||
ctx->framerate = AVRational{framerate, 1};
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
ctx->max_b_frames = config::video.max_b_frames;
|
||||
ctx->gop_size = config::video.gop_size;
|
||||
|
||||
ctx->slices = config.slicesPerFrame;
|
||||
ctx->thread_type = FF_THREAD_SLICE;
|
||||
ctx->thread_count = std::min(config.slicesPerFrame, 4);
|
||||
|
||||
AVDictionary *options {nullptr};
|
||||
av_dict_set(&options, "preset", "ultrafast", 0);
|
||||
// av_dict_set(&options, "tune", "fastdecode", 0);
|
||||
av_dict_set(&options, "profile", "baseline", 0);
|
||||
|
||||
av_dict_set_int(&options, "crf", config::video.crf, 0);
|
||||
|
||||
ctx->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
|
||||
ctx->flags2 |= AV_CODEC_FLAG2_FAST;
|
||||
|
||||
auto fromformat = AV_PIX_FMT_BGR0;
|
||||
auto lg = open_codec(ctx, codec, &options);
|
||||
|
||||
yuv_frame->format = ctx->pix_fmt;
|
||||
yuv_frame->width = ctx->width;
|
||||
yuv_frame->height = ctx->height;
|
||||
|
||||
av_frame_get_buffer(yuv_frame.get(), 0);
|
||||
|
||||
int64_t frame = 1;
|
||||
|
||||
// Initiate scaling context with correct height and width
|
||||
sws_t sws;
|
||||
if(auto img = images->pop()) {
|
||||
sws.reset(
|
||||
sws_getContext(
|
||||
platf::img_width(img), platf::img_height(img), fromformat,
|
||||
ctx->width, ctx->height, ctx->pix_fmt,
|
||||
SWS_LANCZOS | SWS_ACCURATE_RND,
|
||||
nullptr, nullptr, nullptr));
|
||||
}
|
||||
|
||||
while (auto img = images->pop()) {
|
||||
encode(frame++, ctx, sws, yuv_frame, img, packets);
|
||||
}
|
||||
|
||||
packets->stop();
|
||||
}
|
||||
|
||||
void capture_display(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config) {
|
||||
int framerate = config.framerate;
|
||||
|
||||
std::shared_ptr<safe::queue_t<platf::img_t>> images { new safe::queue_t<platf::img_t> };
|
||||
|
||||
std::thread encoderThread { &encodeThread, images, packets, config };
|
||||
|
||||
auto disp = platf::display();
|
||||
|
||||
auto time_span = std::chrono::floor<std::chrono::nanoseconds>(1s) / framerate;
|
||||
while(packets->running()) {
|
||||
auto next_snapshot = std::chrono::steady_clock::now() + time_span;
|
||||
auto img = platf::snapshot(disp);
|
||||
|
||||
images->push(std::move(img));
|
||||
img.reset();
|
||||
|
||||
auto t = std::chrono::steady_clock::now();
|
||||
if(t > next_snapshot) {
|
||||
std::cout << "Taking snapshot took "sv << std::chrono::floor<std::chrono::milliseconds>(t - next_snapshot).count() << " milliseconds too long"sv << std::endl;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(next_snapshot);
|
||||
}
|
||||
|
||||
images->stop();
|
||||
encoderThread.join();
|
||||
}
|
||||
}
|
27
video.h
Normal file
27
video.h
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by loki on 6/9/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_VIDEO_H
|
||||
#define SUNSHINE_VIDEO_H
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
struct AVPacket;
|
||||
namespace video {
|
||||
void free_packet(AVPacket *packet);
|
||||
|
||||
using packet_t = util::safe_ptr<AVPacket, free_packet>;
|
||||
|
||||
struct config_t {
|
||||
int width;
|
||||
int height;
|
||||
int framerate;
|
||||
int bitrate;
|
||||
int slicesPerFrame;
|
||||
};
|
||||
|
||||
void capture_display(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_VIDEO_H
|
Loading…
x
Reference in New Issue
Block a user