Removed Git history due to personal info

This commit is contained in:
loki 2019-12-03 20:23:33 +01:00
commit ae29230f59
30 changed files with 3754 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
build
cmake-build-*
.DS_Store
*.swp
*.kdev4
.idea

6
.gitmodules vendored Normal file
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit d1bf544d9266bdf5b86517e4a70de63216529e5b

BIN
assets/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

22
assets/demoCA/cacert.pem Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit 801aaf43d6124da294a8c97e5b67e966f1b4edbf

521
nvhttp.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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