Moved source files to seperate folder
This commit is contained in:
104
sunshine/audio.cpp
Normal file
104
sunshine/audio.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#include "utility.h"
|
||||
#include "queue.h"
|
||||
#include "audio.h"
|
||||
|
||||
namespace audio {
|
||||
using namespace std::literals;
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
|
||||
struct opus_stream_config_t {
|
||||
std::int32_t sampleRate;
|
||||
int channelCount;
|
||||
int streams;
|
||||
int coupledStreams;
|
||||
const std::uint8_t *mapping;
|
||||
};
|
||||
|
||||
constexpr std::uint8_t map_stereo[] { 0, 1 };
|
||||
constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3};
|
||||
constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5};
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
static opus_stream_config_t stereo = {
|
||||
SAMPLE_RATE,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
map_stereo
|
||||
};
|
||||
|
||||
static opus_stream_config_t Surround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
4,
|
||||
2,
|
||||
map_surround51
|
||||
};
|
||||
|
||||
static opus_stream_config_t HighSurround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
6,
|
||||
0,
|
||||
map_high_surround51
|
||||
};
|
||||
|
||||
void encodeThread(std::shared_ptr<safe::queue_t<packet_t>> packets, std::shared_ptr<safe::queue_t<platf::audio_t>> samples, config_t config) {
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
stream->sampleRate,
|
||||
stream->channelCount,
|
||||
stream->streams,
|
||||
stream->coupledStreams,
|
||||
stream->mapping,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
nullptr)
|
||||
};
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
while(auto sample = samples->pop()) {
|
||||
packet_t packet { 16*1024 }; // 16KB
|
||||
|
||||
int bytes = opus_multistream_encode(opus.get(), platf::audio_data(sample), frame_size, std::begin(packet), packet.size());
|
||||
if(bytes < 0) {
|
||||
std::cout << "Error: "sv << opus_strerror(bytes) << std::endl;
|
||||
exit(7);
|
||||
}
|
||||
|
||||
packet.fake_resize(bytes);
|
||||
packets->push(std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config) {
|
||||
auto samples = std::make_shared<safe::queue_t<platf::audio_t>>();
|
||||
|
||||
auto mic = platf::microphone();
|
||||
if(!mic) {
|
||||
std::cout << "Error creating audio input"sv << std::endl;
|
||||
}
|
||||
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
int bytes_per_frame = frame_size * sizeof(std::int16_t) * stream->channelCount;
|
||||
|
||||
std::thread thread { encodeThread, packets, samples, config };
|
||||
while(packets->running()) {
|
||||
auto sample = platf::audio(mic, bytes_per_frame);
|
||||
|
||||
samples->push(std::move(sample));
|
||||
}
|
||||
|
||||
samples->stop();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
17
sunshine/audio.h
Normal file
17
sunshine/audio.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SUNSHINE_AUDIO_H
|
||||
#define SUNSHINE_AUDIO_H
|
||||
|
||||
#include "utility.h"
|
||||
#include "queue.h"
|
||||
namespace audio {
|
||||
struct config_t {
|
||||
int packetDuration;
|
||||
int channels;
|
||||
int mask;
|
||||
};
|
||||
|
||||
using packet_t = util::buffer_t<std::uint8_t>;
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config);
|
||||
}
|
||||
|
||||
#endif
|
||||
142
sunshine/config.cpp
Normal file
142
sunshine/config.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <streambuf>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "utility.h"
|
||||
#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
|
||||
|
||||
4, // threads
|
||||
|
||||
"baseline"s, // profile
|
||||
"superfast"s, // preset
|
||||
"zerolatency"s // tune
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
bool whitespace(char ch) {
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
std::string to_string(const char *begin, const char *end) {
|
||||
return { begin, (std::size_t)(end - begin) };
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
begin = std::find_if(begin, end, std::not_fn(whitespace));
|
||||
end = std::find(begin, end, '#');
|
||||
end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
||||
|
||||
auto eq = std::find(begin, end, '=');
|
||||
if(eq == end || eq == begin) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto end_name = std::find_if(std::make_reverse_iterator(eq - 1), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
||||
auto begin_val = std::find_if(eq + 1, end, std::not_fn(whitespace));
|
||||
|
||||
return std::pair { to_string(begin, end_name), to_string(begin_val, end) };
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content) {
|
||||
std::unordered_map<std::string, std::string> vars;
|
||||
|
||||
auto pos = std::begin(file_content) - 1;
|
||||
auto end = std::end(file_content);
|
||||
|
||||
while(pos <= end) {
|
||||
auto newline = std::find(pos, end, '\n');
|
||||
auto var = parse_line(pos, *(newline - 1) == '\r' ? newline - 1 : newline);
|
||||
|
||||
pos = newline + 1;
|
||||
if(!var) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vars.emplace(std::move(*var));
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
auto it = vars.find(name);
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
input = std::move(it->second);
|
||||
}
|
||||
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &val = it->second;
|
||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
||||
}
|
||||
|
||||
void parse_file(const char *file) {
|
||||
std::ifstream in(file);
|
||||
|
||||
auto vars = parse_config(std::string {
|
||||
// Quick and dirty
|
||||
std::istreambuf_iterator<char>(in),
|
||||
std::istreambuf_iterator<char>()
|
||||
});
|
||||
|
||||
for(auto &[name, val] : vars) {
|
||||
std::cout << "["sv << name << "] -- ["sv << val << "]"sv << std::endl;
|
||||
}
|
||||
|
||||
int_f(vars, "max_b_frames", video.max_b_frames);
|
||||
int_f(vars, "gop_size", video.gop_size);
|
||||
int_f(vars, "crf", video.crf);
|
||||
int_f(vars, "threads", video.threads);
|
||||
string_f(vars, "profile", video.profile);
|
||||
string_f(vars, "preset", video.preset);
|
||||
string_f(vars, "tune", video.tune);
|
||||
|
||||
string_f(vars, "pkey", nvhttp.pkey);
|
||||
string_f(vars, "cert", nvhttp.cert);
|
||||
string_f(vars, "unique_id", nvhttp.unique_id);
|
||||
string_f(vars, "file_devices", nvhttp.file_devices);
|
||||
string_f(vars, "external_ip", nvhttp.external_ip);
|
||||
|
||||
int to = -1;
|
||||
int_f(vars, "ping_timeout", to);
|
||||
if(to > 0) {
|
||||
stream.ping_timeout = std::chrono::milliseconds(to);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
42
sunshine/config.h
Normal file
42
sunshine/config.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#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
|
||||
|
||||
int threads; // Number threads used by ffmpeg
|
||||
|
||||
std::string profile;
|
||||
std::string preset;
|
||||
std::string tune;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
void parse_file(const char *file);
|
||||
}
|
||||
|
||||
#endif
|
||||
231
sunshine/crypto.cpp
Normal file
231
sunshine/crypto.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// Created by loki on 5/31/19.
|
||||
//
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include "crypto.h"
|
||||
namespace crypto {
|
||||
cipher_t::cipher_t(const crypto::aes_t &key) : ctx { EVP_CIPHER_CTX_new() }, key { key }, padding { true } {}
|
||||
int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
auto size = (int)plaintext.size();
|
||||
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
|
||||
std::vector<std::uint8_t> &plaintext) {
|
||||
auto cipher = tagged_cipher.substr(16);
|
||||
auto tag = tagged_cipher.substr(0, 16);
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
|
||||
int size;
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char*>(tag.data())) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = size;
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(size + len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
cipher.resize((plaintext.size() + 15) / 16 * 16);
|
||||
auto size = (int)cipher.size();
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t*)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key;
|
||||
|
||||
std::string salt_pin;
|
||||
salt_pin.reserve(salt.size() + pin.size());
|
||||
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
|
||||
|
||||
auto hsh = hash(salt_pin);
|
||||
|
||||
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, plaintext.data(), plaintext.size());
|
||||
SHA256_Final(hsh.data(), &sha256);
|
||||
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
X509 *p = nullptr;
|
||||
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return x509_t { p };
|
||||
}
|
||||
|
||||
pkey_t pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
EVP_PKEY *p = nullptr;
|
||||
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return pkey_t { p };
|
||||
}
|
||||
|
||||
|
||||
std::string_view signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char*)asn1->data, (std::size_t)asn1->length };
|
||||
}
|
||||
|
||||
std::string rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
RAND_bytes((uint8_t*)r.data(), r.size());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slen = digest_size;
|
||||
|
||||
std::vector<uint8_t> digest;
|
||||
digest.resize(slen);
|
||||
|
||||
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
}
|
||||
64
sunshine/crypto.h
Normal file
64
sunshine/crypto.h
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Created by loki on 6/1/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_CRYPTO_H
|
||||
#define SUNSHINE_CRYPTO_H
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
constexpr std::size_t digest_size = 256;
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
using aes_t = std::array<std::uint8_t, 16>;
|
||||
using x509_t = util::safe_ptr<X509, X509_free>;
|
||||
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
||||
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
|
||||
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
|
||||
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext);
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
|
||||
x509_t x509(const std::string_view &x);
|
||||
pkey_t pkey(const std::string_view &k);
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
|
||||
std::string_view signature(const x509_t &x);
|
||||
|
||||
std::string rand(std::size_t bytes);
|
||||
|
||||
class cipher_t {
|
||||
public:
|
||||
cipher_t(const aes_t &key);
|
||||
cipher_t(cipher_t&&) noexcept = default;
|
||||
cipher_t &operator=(cipher_t&&) noexcept = default;
|
||||
|
||||
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
|
||||
int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
private:
|
||||
cipher_ctx_t ctx;
|
||||
aes_t key;
|
||||
|
||||
public:
|
||||
bool padding;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_CRYPTO_H
|
||||
164
sunshine/input.cpp
Normal file
164
sunshine/input.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Input.h>
|
||||
}
|
||||
|
||||
#include <cstring>
|
||||
#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::input_t &input, PNV_MOUSE_MOVE_PACKET packet) {
|
||||
platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
||||
}
|
||||
|
||||
void passthrough(platf::input_t &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x09;
|
||||
|
||||
platf::button_mouse(input, util::endian::big(packet->button), packet->action == BUTTON_RELEASED);
|
||||
}
|
||||
|
||||
void passthrough(platf::input_t &input, PNV_KEYBOARD_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x04;
|
||||
|
||||
platf::keyboard(input, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED);
|
||||
}
|
||||
|
||||
void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
|
||||
platf::scroll(input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
|
||||
void passthrough(platf::input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
std::uint16_t bf;
|
||||
|
||||
static_assert(sizeof(bf) == sizeof(packet->buttonFlags), "Can't memcpy :(");
|
||||
std::memcpy(&bf, &packet->buttonFlags, sizeof(std::uint16_t));
|
||||
platf::gamepad_state_t gamepad_state {
|
||||
bf,
|
||||
packet->leftTrigger,
|
||||
packet->rightTrigger,
|
||||
packet->leftStickX,
|
||||
packet->leftStickY,
|
||||
packet->rightStickX,
|
||||
packet->rightStickY
|
||||
};
|
||||
|
||||
platf::gamepad(input, gamepad_state);
|
||||
}
|
||||
|
||||
void passthrough(platf::input_t &input, void *payload) {
|
||||
int input_type = util::endian::big(*(int*)payload);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_MOUSE_MOVE:
|
||||
passthrough(input, (PNV_MOUSE_MOVE_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
||||
{
|
||||
char *tmp_input = (char*)payload + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
passthrough(input, (PNV_SCROLL_PACKET)payload);
|
||||
}
|
||||
else {
|
||||
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_MULTI_CONTROLLER:
|
||||
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
sunshine/input.h
Normal file
16
sunshine/input.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_INPUT_H
|
||||
#define SUNSHINE_INPUT_H
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
namespace input {
|
||||
void print(void *input);
|
||||
|
||||
void passthrough(platf::input_t &, void *input);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_INPUT_H
|
||||
31
sunshine/main.cpp
Normal file
31
sunshine/main.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by loki on 5/30/19.
|
||||
//
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "nvhttp.h"
|
||||
#include "stream.h"
|
||||
|
||||
extern "C" {
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
#include "config.h"
|
||||
#include "platform/common.h"
|
||||
using namespace std::literals;
|
||||
int main(int argc, char *argv[]) {
|
||||
if(argc > 1) {
|
||||
config::parse_file(argv[1]);
|
||||
}
|
||||
|
||||
reed_solomon_init();
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread rtpThread { stream::rtpThread };
|
||||
|
||||
httpThread.join();
|
||||
rtpThread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
561
sunshine/nvhttp.cpp
Normal file
561
sunshine/nvhttp.cpp
Normal file
@@ -0,0 +1,561 @@
|
||||
//
|
||||
// Created by loki on 6/3/19.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
#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"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
|
||||
|
||||
namespace nvhttp {
|
||||
using namespace std::literals;
|
||||
constexpr auto PORT_HTTP = 47989;
|
||||
constexpr auto PORT_HTTPS = 47984;
|
||||
|
||||
constexpr auto VERSION = "7.1.400.0";
|
||||
constexpr auto GFE_VERSION = "2.0.0.1";
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
std::string read_file(const char *path);
|
||||
|
||||
std::string local_ip;
|
||||
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;
|
||||
|
||||
struct {
|
||||
util::Either<
|
||||
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>,
|
||||
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>
|
||||
> response;
|
||||
std::string salt;
|
||||
} async_insert_pin;
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
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() {
|
||||
auto file_devices = fs::current_path() / config::nvhttp.file_devices;
|
||||
|
||||
if(!fs::exists(file_devices)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 std::string &pin) {
|
||||
auto salt = util::from_hex<std::array<uint8_t, 16>>(sess.async_insert_pin.salt, true);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 args = request->parse_query_string();
|
||||
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;
|
||||
|
||||
ptr->second.async_insert_pin.salt = std::move(args.at("salt"s));
|
||||
ptr->second.async_insert_pin.response = std::move(response);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void pin(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;
|
||||
|
||||
auto &sess = std::begin(map_id_sess)->second;
|
||||
getservercert(sess, tree, request->path_match[1]);
|
||||
|
||||
// response to the request for pin
|
||||
std::ostringstream data;
|
||||
pt::write_xml(data, tree);
|
||||
|
||||
auto &async_response = sess.async_insert_pin.response;
|
||||
if(async_response.left()) {
|
||||
async_response.left()->write(data.str());
|
||||
}
|
||||
else {
|
||||
async_response.right()->write(data.str());
|
||||
}
|
||||
|
||||
async_response = std::decay_t<decltype(async_response.left())>();
|
||||
// response to the current request
|
||||
response->write(""s);
|
||||
}
|
||||
|
||||
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", local_ip);
|
||||
|
||||
if(config::nvhttp.external_ip.empty()) {
|
||||
tree.put("root.ExternalIP", local_ip);
|
||||
}
|
||||
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() {
|
||||
local_ip = platf::get_local_ip();
|
||||
if(local_ip.empty()) {
|
||||
std::cout << "Error: Could not determine the local ip-address"sv << std::endl;
|
||||
|
||||
exit(8);
|
||||
}
|
||||
|
||||
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.resource["^/pin/([0-9]+)$"]["GET"] = pin<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.resource["^/pin/([0-9]+)$"]["GET"] = pin<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
sunshine/nvhttp.h
Normal file
19
sunshine/nvhttp.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by loki on 6/3/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_NVHTTP_H
|
||||
#define SUNSHINE_NVHTTP_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA"
|
||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
namespace nvhttp {
|
||||
void start();
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_NVHTTP_H
|
||||
56
sunshine/platform/common.h
Normal file
56
sunshine/platform/common.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_COMMON_H
|
||||
#define SUNSHINE_COMMON_H
|
||||
|
||||
#include <string>
|
||||
#include "sunshine/utility.h"
|
||||
|
||||
namespace platf {
|
||||
|
||||
void freeDisplay(void*);
|
||||
void freeImage(void*);
|
||||
void freeAudio(void*);
|
||||
void freeMic(void*);
|
||||
void freeInput(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>;
|
||||
using input_t = util::safe_ptr<void, freeInput>;
|
||||
|
||||
struct gamepad_state_t {
|
||||
std::uint16_t buttonFlags;
|
||||
std::uint8_t lt;
|
||||
std::uint8_t rt;
|
||||
std::int16_t lsX;
|
||||
std::int16_t lsY;
|
||||
std::int16_t rsX;
|
||||
std::int16_t rsY;
|
||||
};
|
||||
|
||||
std::string get_local_ip();
|
||||
|
||||
mic_t microphone();
|
||||
audio_t audio(mic_t &mic, std::uint32_t sample_size);
|
||||
|
||||
display_t display();
|
||||
img_t snapshot(display_t &display);
|
||||
int32_t img_width(img_t &);
|
||||
int32_t img_height(img_t &);
|
||||
|
||||
uint8_t *img_data(img_t &);
|
||||
int16_t *audio_data(audio_t &);
|
||||
|
||||
input_t input();
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
void button_mouse(input_t &input, int button, bool release);
|
||||
void scroll(input_t &input, int distance);
|
||||
void keyboard(input_t &input, uint16_t modcode, bool release);
|
||||
void gamepad(input_t &input, const gamepad_state_t &gamepad_state);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_COMMON_H
|
||||
219
sunshine/platform/linux.cpp
Normal file
219
sunshine/platform/linux.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <bitset>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
|
||||
|
||||
ifaddr_t get_ifaddrs() {
|
||||
ifaddrs *p { nullptr };
|
||||
|
||||
getifaddrs(&p);
|
||||
|
||||
return ifaddr_t { p };
|
||||
}
|
||||
|
||||
std::string from_sockaddr(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN];
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
if(family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
if(family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
}
|
||||
|
||||
std::string get_local_ip(int family) {
|
||||
std::bitset<2> family_f {};
|
||||
|
||||
if(family == 0) {
|
||||
family_f[0] = true;
|
||||
family_f[1] = true;
|
||||
}
|
||||
|
||||
if(family == AF_INET) {
|
||||
family_f[0] = true;
|
||||
}
|
||||
|
||||
if(family == AF_INET6) {
|
||||
family_f[1] = true;
|
||||
}
|
||||
|
||||
|
||||
std::string ip_addr;
|
||||
auto ifaddr = get_ifaddrs();
|
||||
for(auto pos = ifaddr.get(); pos != nullptr; pos = pos->ifa_next) {
|
||||
if(pos->ifa_addr && pos->ifa_flags & IFF_UP && !(pos->ifa_flags & IFF_LOOPBACK)) {
|
||||
if(
|
||||
(family_f[0] && pos->ifa_addr->sa_family == AF_INET) ||
|
||||
(family_f[1] && pos->ifa_addr->sa_family == AF_INET6)
|
||||
){
|
||||
ip_addr = from_sockaddr(pos->ifa_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip_addr;
|
||||
}
|
||||
|
||||
std::string get_local_ip() { return get_local_ip(AF_INET); }
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
364
sunshine/platform/linux_evdev.cpp
Normal file
364
sunshine/platform/linux_evdev.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <libevdev/libevdev-uinput.h>
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "sunshine/utility.h"
|
||||
|
||||
namespace platf {
|
||||
constexpr std::uint16_t DPAD_UP = 0x0001;
|
||||
constexpr std::uint16_t DPAD_DOWN = 0x0002;
|
||||
constexpr std::uint16_t DPAD_LEFT = 0x0004;
|
||||
constexpr std::uint16_t DPAD_RIGHT = 0x0008;
|
||||
constexpr std::uint16_t START = 0x0010;
|
||||
constexpr std::uint16_t BACK = 0x0020;
|
||||
constexpr std::uint16_t LEFT_STICK = 0x0040;
|
||||
constexpr std::uint16_t RIGHT_STICK = 0x0080;
|
||||
constexpr std::uint16_t LEFT_BUTTON = 0x0100;
|
||||
constexpr std::uint16_t RIGHT_BUTTON = 0x0200;
|
||||
constexpr std::uint16_t HOME = 0x0400;
|
||||
constexpr std::uint16_t A = 0x1000;
|
||||
constexpr std::uint16_t B = 0x2000;
|
||||
constexpr std::uint16_t X = 0x4000;
|
||||
constexpr std::uint16_t Y = 0x8000;
|
||||
|
||||
using namespace std::literals;
|
||||
using evdev_t = util::safe_ptr<libevdev, libevdev_free>;
|
||||
using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>;
|
||||
|
||||
struct input_raw_t {
|
||||
evdev_t dev;
|
||||
uinput_t uinput;
|
||||
display_t display;
|
||||
|
||||
gamepad_state_t gamepad_state;
|
||||
};
|
||||
|
||||
//TODO: Use libevdev for keyboard and mouse, then any mention of X11 can be removed from linux_input.cpp
|
||||
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;
|
||||
};
|
||||
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get());
|
||||
|
||||
XWarpPointer(disp.display, None, None, 0, 0, 0, 0, deltaX, deltaY);
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void button_mouse(input_t &input, int button, bool release) {
|
||||
auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get());
|
||||
|
||||
XTestFakeButtonEvent(disp.display, button, !release, CurrentTime);
|
||||
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void scroll(input_t &input, int distance) {
|
||||
auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get());
|
||||
|
||||
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(input_t &input, uint16_t modcode, bool release) {
|
||||
auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get());
|
||||
KeyCode kc = XKeysymToKeycode(disp.display, keysym(modcode));
|
||||
|
||||
if(!kc) {
|
||||
return;
|
||||
}
|
||||
|
||||
XTestFakeKeyEvent(disp.display, kc, !release, 0);
|
||||
|
||||
XSync(disp.display, 0);
|
||||
XFlush(disp.display);
|
||||
}
|
||||
|
||||
void gamepad(input_t &input, const gamepad_state_t &gamepad_state) {
|
||||
auto &gp = *(input_raw_t*)input.get();
|
||||
|
||||
auto bf = gamepad_state.buttonFlags ^ gp.gamepad_state.buttonFlags;
|
||||
auto bf_new = gamepad_state.buttonFlags;
|
||||
|
||||
if(bf) {
|
||||
// up pressed == -1, down pressed == 1, else 0
|
||||
if(DPAD_UP & bf) {
|
||||
int val = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0Y, val);
|
||||
}
|
||||
else if(DPAD_DOWN & bf) {
|
||||
int val = bf_new & DPAD_DOWN ? 1 : 0;
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0Y, val);
|
||||
}
|
||||
|
||||
if(DPAD_LEFT & bf) {
|
||||
int val = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0X, val);
|
||||
}
|
||||
else if(DPAD_RIGHT & bf) {
|
||||
int val = bf_new & DPAD_RIGHT ? 1 : 0;
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0X, val);
|
||||
}
|
||||
|
||||
if(START & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0);
|
||||
if(BACK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
|
||||
if(LEFT_STICK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
|
||||
if(RIGHT_STICK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
|
||||
if(LEFT_BUTTON & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
|
||||
if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
|
||||
if(HOME & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
|
||||
if(A & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
|
||||
if(B & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
|
||||
if(X & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
|
||||
if(Y & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.lt != gamepad_state.lt) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_Z, gamepad_state.lt);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.rt != gamepad_state.rt) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RZ, gamepad_state.rt);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.lsX != gamepad_state.lsX) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_X, gamepad_state.lsX);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.lsY != gamepad_state.lsY) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_Y, -gamepad_state.lsY);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.rsX != gamepad_state.rsX) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RX, gamepad_state.rsX);
|
||||
}
|
||||
|
||||
if(gp.gamepad_state.rsY != gamepad_state.rsY) {
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RY, -gamepad_state.rsY);
|
||||
}
|
||||
|
||||
gp.gamepad_state = gamepad_state;
|
||||
libevdev_uinput_write_event(gp.uinput.get(), EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
input_t input() {
|
||||
input_t result { new input_raw_t() };
|
||||
auto &gp = *(input_raw_t*)result.get();
|
||||
|
||||
gp.dev.reset(libevdev_new());
|
||||
|
||||
input_absinfo stick {
|
||||
0,
|
||||
-32768, 32767,
|
||||
16,
|
||||
128,
|
||||
0
|
||||
};
|
||||
|
||||
input_absinfo trigger {
|
||||
0,
|
||||
0, 255,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
input_absinfo dpad {
|
||||
0,
|
||||
-1, 1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
libevdev_set_uniq(gp.dev.get(), "Sunshine Gamepad");
|
||||
libevdev_set_id_product(gp.dev.get(), 0x28E);
|
||||
libevdev_set_id_vendor(gp.dev.get(), 0x45E);
|
||||
libevdev_set_id_bustype(gp.dev.get(), 0x3);
|
||||
libevdev_set_id_version(gp.dev.get(), 0x110);
|
||||
libevdev_set_name(gp.dev.get(), "Microsoft X-Box 360 pad");
|
||||
|
||||
libevdev_enable_event_type(gp.dev.get(), EV_KEY);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_WEST, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_EAST, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_NORTH, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_SOUTH, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_THUMBL, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_THUMBR, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_TR, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_TL, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_SELECT, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_MODE, nullptr);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_START, nullptr);
|
||||
|
||||
libevdev_enable_event_type(gp.dev.get(), EV_ABS);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_HAT0Y, &dpad);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_HAT0X, &dpad);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_Z, &trigger);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RZ, &trigger);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_X, &stick);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RX, &stick);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_Y, &stick);
|
||||
libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RY, &stick);
|
||||
|
||||
libevdev_uinput *buf;
|
||||
int err = libevdev_uinput_create_from_device(gp.dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
|
||||
|
||||
gp.uinput.reset(buf);
|
||||
if(err) {
|
||||
std::cout << "Could not create Sunshine Gamepad: "sv << strerror(-err) << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gp.display = display();
|
||||
return result;
|
||||
}
|
||||
|
||||
void freeInput(void *p) {
|
||||
auto *input = (input_raw_t*)p;
|
||||
delete input;
|
||||
}
|
||||
}
|
||||
87
sunshine/queue.h
Normal file
87
sunshine/queue.h
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Created by loki on 6/10/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_QUEUE_H
|
||||
#define SUNSHINE_QUEUE_H
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace safe {
|
||||
|
||||
template<class T>
|
||||
class queue_t {
|
||||
using status_t = util::either_t<
|
||||
(std::is_same_v<T, bool> ||
|
||||
util::instantiation_of_v<std::unique_ptr, T> ||
|
||||
util::instantiation_of_v<std::shared_ptr, T> ||
|
||||
std::is_pointer_v<T>),
|
||||
T, std::optional<T>>;
|
||||
|
||||
public:
|
||||
template<class ...Args>
|
||||
void push(Args &&... args) {
|
||||
std::lock_guard lg{_lock};
|
||||
|
||||
if(!_continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.emplace_back(std::forward<Args>(args)...);
|
||||
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
status_t pop() {
|
||||
std::unique_lock ul{_lock};
|
||||
|
||||
if (!_continue) {
|
||||
return util::false_v<status_t>;
|
||||
}
|
||||
|
||||
while (_queue.empty()) {
|
||||
_cv.wait(ul);
|
||||
|
||||
if (!_continue) {
|
||||
return util::false_v<status_t>;
|
||||
}
|
||||
}
|
||||
|
||||
auto val = std::move(_queue.front());
|
||||
_queue.erase(std::begin(_queue));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
std::vector<T> &unsafe() {
|
||||
return _queue;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
std::lock_guard lg{_lock};
|
||||
|
||||
_continue = false;
|
||||
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
bool running() const {
|
||||
return _continue;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool _continue{true};
|
||||
|
||||
std::mutex _lock;
|
||||
std::condition_variable _cv;
|
||||
std::vector<T> _queue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_QUEUE_H
|
||||
1002
sunshine/stream.cpp
Normal file
1002
sunshine/stream.cpp
Normal file
File diff suppressed because it is too large
Load Diff
19
sunshine/stream.h
Normal file
19
sunshine/stream.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by loki on 6/5/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_STREAM_H
|
||||
#define SUNSHINE_STREAM_H
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
namespace stream {
|
||||
|
||||
extern crypto::aes_t gcm_key;
|
||||
extern crypto::aes_t iv;
|
||||
|
||||
void rtpThread();
|
||||
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_STREAM_H
|
||||
643
sunshine/utility.h
Normal file
643
sunshine/utility.h
Normal file
@@ -0,0 +1,643 @@
|
||||
#ifndef UTILITY_H
|
||||
#define UTILITY_H
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <string_view>
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR(x)\
|
||||
x(x&&) noexcept = default;\
|
||||
x&operator=(x&&) noexcept = default;\
|
||||
x() = default;
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR_THROW(x)\
|
||||
x(x&&) = default;\
|
||||
x&operator=(x&&) = default;\
|
||||
x() = default;
|
||||
|
||||
#define TUPLE_2D(a,b, expr)\
|
||||
decltype(expr) a##_##b = expr;\
|
||||
auto &a = std::get<0>(a##_##b);\
|
||||
auto &b = std::get<1>(a##_##b)
|
||||
|
||||
#define TUPLE_2D_REF(a,b, expr)\
|
||||
auto &a##_##b = expr;\
|
||||
auto &a = std::get<0>(a##_##b);\
|
||||
auto &b = std::get<1>(a##_##b)
|
||||
|
||||
#define TUPLE_3D(a,b,c, expr)\
|
||||
decltype(expr) a##_##b##_##c = expr;\
|
||||
auto &a = std::get<0>(a##_##b##_##c);\
|
||||
auto &b = std::get<1>(a##_##b##_##c);\
|
||||
auto &c = std::get<2>(a##_##b##_##c)
|
||||
|
||||
#define TUPLE_3D_REF(a,b,c, expr)\
|
||||
auto &a##_##b##_##c = expr;\
|
||||
auto &a = std::get<0>(a##_##b##_##c);\
|
||||
auto &b = std::get<1>(a##_##b##_##c);\
|
||||
auto &c = std::get<2>(a##_##b##_##c)
|
||||
|
||||
namespace util {
|
||||
|
||||
template<template<typename...> class X, class...Y>
|
||||
struct __instantiation_of : public std::false_type {};
|
||||
|
||||
template<template<typename...> class X, class... Y>
|
||||
struct __instantiation_of<X, X<Y...>> : public std::true_type {};
|
||||
|
||||
template<template<typename...> class X, class T, class...Y>
|
||||
static constexpr auto instantiation_of_v = __instantiation_of<X, T, Y...>::value;
|
||||
|
||||
template<bool V, class X, class Y>
|
||||
struct __either;
|
||||
|
||||
template<class X, class Y>
|
||||
struct __either<true, X, Y> {
|
||||
using type = X;
|
||||
};
|
||||
|
||||
template<class X, class Y>
|
||||
struct __either<false, X, Y> {
|
||||
using type = Y;
|
||||
};
|
||||
|
||||
template<bool V, class X, class Y>
|
||||
using either_t = typename __either<V, X, Y>::type;
|
||||
|
||||
template<class T, class V = void>
|
||||
struct __false_v;
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
|
||||
static constexpr std::nullopt_t value = std::nullopt;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<
|
||||
(std::is_pointer_v<T> || instantiation_of_v<std::unique_ptr, T> || instantiation_of_v<std::shared_ptr, T>)
|
||||
>> {
|
||||
static constexpr std::nullptr_t value = nullptr;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
static constexpr auto false_v = __false_v<T>::value;
|
||||
|
||||
template<class T>
|
||||
class FailGuard {
|
||||
public:
|
||||
FailGuard() = delete;
|
||||
FailGuard(T && f) noexcept : _func { std::forward<T>(f) } {}
|
||||
FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } {
|
||||
this->failure = other.failure;
|
||||
|
||||
other.failure = false;
|
||||
}
|
||||
|
||||
FailGuard(const FailGuard &) = delete;
|
||||
|
||||
FailGuard &operator=(const FailGuard &) = delete;
|
||||
FailGuard &operator=(FailGuard &&other) = delete;
|
||||
|
||||
~FailGuard() noexcept {
|
||||
if(failure) {
|
||||
_func();
|
||||
}
|
||||
}
|
||||
|
||||
void disable() { failure = false; }
|
||||
bool failure { true };
|
||||
private:
|
||||
T _func;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
auto fail_guard(T && f) {
|
||||
return FailGuard<T> { std::forward<T>(f) };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void append_struct(std::vector<uint8_t> &buf, const T &_struct) {
|
||||
constexpr size_t data_len = sizeof(_struct);
|
||||
|
||||
buf.reserve(data_len);
|
||||
|
||||
auto *data = (uint8_t *) & _struct;
|
||||
|
||||
for (size_t x = 0; x < data_len; ++x) {
|
||||
buf.push_back(data[x]);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
class Hex {
|
||||
public:
|
||||
typedef T elem_type;
|
||||
private:
|
||||
const char _bits[16] {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
char _hex[sizeof(elem_type) * 2];
|
||||
public:
|
||||
Hex(const elem_type &elem, bool rev) {
|
||||
if(!rev) {
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem) + sizeof(elem_type) - 1;
|
||||
for (auto it = begin(); it < cend();) {
|
||||
*it++ = _bits[*data / 16];
|
||||
*it++ = _bits[*data-- % 16];
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem);
|
||||
for (auto it = begin(); it < cend();) {
|
||||
*it++ = _bits[*data / 16];
|
||||
*it++ = _bits[*data++ % 16];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *begin() { return _hex; }
|
||||
char *end() { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
const char *begin() const { return _hex; }
|
||||
const char *end() const { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
const char *cbegin() const { return _hex; }
|
||||
const char *cend() const { return _hex + sizeof(elem_type) * 2; }
|
||||
|
||||
std::string to_string() const {
|
||||
return { begin(), end() };
|
||||
}
|
||||
|
||||
std::string_view to_string_view() const {
|
||||
return { begin(), sizeof(elem_type) * 2 };
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
Hex<T> hex(const T &elem, bool rev = false) {
|
||||
return Hex<T>(elem, rev);
|
||||
}
|
||||
|
||||
template<class It>
|
||||
std::string hex_vec(It begin, It end, bool rev = false) {
|
||||
auto str_size = 2*std::distance(begin, end);
|
||||
|
||||
|
||||
std::string hex;
|
||||
hex.resize(str_size);
|
||||
|
||||
const char _bits[16] {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
if(rev) {
|
||||
for (auto it = std::begin(hex); it < std::end(hex);) {
|
||||
*it++ = _bits[((uint8_t)*begin) / 16];
|
||||
*it++ = _bits[((uint8_t)*begin++) % 16];
|
||||
}
|
||||
}
|
||||
else {
|
||||
--end;
|
||||
for (auto it = std::begin(hex); it < std::end(hex);) {
|
||||
*it++ = _bits[((uint8_t)*end) / 16];
|
||||
*it++ = _bits[((uint8_t)*end--) % 16];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
template<class C>
|
||||
std::string hex_vec(C&& c, bool rev = false) {
|
||||
return hex_vec(std::begin(c), std::end(c), rev);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
std::uint8_t buf[sizeof(T)];
|
||||
|
||||
static char constexpr shift_bit = 'a' - 'A';
|
||||
auto is_convertable = [] (char ch) -> bool {
|
||||
if(isdigit(ch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ch |= shift_bit;
|
||||
|
||||
if('a' > ch || ch > 'z') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||
if(buf_size != sizeof(T)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char *data = hex.data() + hex.size() -1;
|
||||
|
||||
auto convert = [] (char ch) -> std::uint8_t {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return (std::uint8_t)ch - '0';
|
||||
}
|
||||
|
||||
return (std::uint8_t)(ch | (char)32) - 'a' + (char)10;
|
||||
};
|
||||
|
||||
for(auto &el : buf) {
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_r = convert(*data--);
|
||||
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_l = convert(*data--);
|
||||
|
||||
el = (ch_l << 4) | ch_r;
|
||||
}
|
||||
|
||||
if(rev) {
|
||||
std::reverse(std::begin(buf), std::end(buf));
|
||||
}
|
||||
|
||||
return *reinterpret_cast<T *>(buf);
|
||||
}
|
||||
|
||||
inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
|
||||
std::string buf;
|
||||
|
||||
static char constexpr shift_bit = 'a' - 'A';
|
||||
auto is_convertable = [] (char ch) -> bool {
|
||||
if(isdigit(ch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ch |= shift_bit;
|
||||
|
||||
if('a' > ch || ch > 'z') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||
buf.resize(buf_size);
|
||||
|
||||
const char *data = hex.data() + hex.size() -1;
|
||||
|
||||
auto convert = [] (char ch) -> std::uint8_t {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return (std::uint8_t)ch - '0';
|
||||
}
|
||||
|
||||
return (std::uint8_t)(ch | (char)32) - 'a' + (char)10;
|
||||
};
|
||||
|
||||
for(auto &el : buf) {
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_r = convert(*data--);
|
||||
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_l = convert(*data--);
|
||||
|
||||
el = (ch_l << 4) | ch_r;
|
||||
}
|
||||
|
||||
if(rev) {
|
||||
std::reverse(std::begin(buf), std::end(buf));
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
class hash {
|
||||
public:
|
||||
using value_type = T;
|
||||
std::size_t operator()(const value_type &value) const {
|
||||
const auto *p = reinterpret_cast<const char *>(&value);
|
||||
|
||||
return std::hash<std::string_view>{}(std::string_view { p, sizeof(value_type) });
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
auto enm(const T& val) -> const std::underlying_type_t<T>& {
|
||||
return *reinterpret_cast<const std::underlying_type_t<T>*>(&val);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto enm(T& val) -> std::underlying_type_t<T>& {
|
||||
return *reinterpret_cast<std::underlying_type_t<T>*>(&val);
|
||||
}
|
||||
|
||||
template<class ReturnType, class ...Args>
|
||||
struct Function {
|
||||
typedef ReturnType (*type)(Args...);
|
||||
};
|
||||
|
||||
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
|
||||
struct Destroy {
|
||||
typedef T pointer;
|
||||
|
||||
void operator()(pointer p) {
|
||||
function(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T, typename Function<void, T*>::type function>
|
||||
using safe_ptr = std::unique_ptr<T, Destroy<T*, void, function>>;
|
||||
|
||||
// You cannot specialize an alias
|
||||
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
|
||||
using safe_ptr_v2 = std::unique_ptr<T, Destroy<T*, ReturnType, function>>;
|
||||
|
||||
template<class T>
|
||||
void c_free(T *p) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
using c_ptr = safe_ptr<T, c_free<T>>;
|
||||
|
||||
template<class T>
|
||||
class FakeContainer {
|
||||
typedef T pointer;
|
||||
|
||||
pointer _begin;
|
||||
pointer _end;
|
||||
|
||||
public:
|
||||
FakeContainer(pointer begin, pointer end) : _begin(begin), _end(end) {}
|
||||
|
||||
pointer begin() { return _begin; }
|
||||
pointer end() { return _end; }
|
||||
|
||||
const pointer begin() const { return _begin; }
|
||||
const pointer end() const { return _end; }
|
||||
|
||||
const pointer cbegin() const { return _begin; }
|
||||
const pointer cend() const { return _end; }
|
||||
|
||||
pointer data() { return begin(); }
|
||||
const pointer data() const { return cbegin(); }
|
||||
|
||||
std::size_t size() const { return std::distance(begin(), end()); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T> toContainer(T begin, T end) {
|
||||
return { begin, end };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T> toContainer(T begin, std::size_t end) {
|
||||
return { begin, begin + end };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
FakeContainer<T*> toContainer(T * const begin) {
|
||||
T *end = begin;
|
||||
|
||||
auto default_val = T();
|
||||
while(*end != default_val) {
|
||||
++end;
|
||||
}
|
||||
|
||||
return toContainer(begin, end);
|
||||
}
|
||||
|
||||
template<class T, class H>
|
||||
struct _init_helper;
|
||||
|
||||
template<template<class...> class T, class H, class... Args>
|
||||
struct _init_helper<T<Args...>, H> {
|
||||
using type = T<Args...>;
|
||||
|
||||
static type move(Args&&... args, H&&) {
|
||||
return std::make_tuple(std::move(args)...);
|
||||
}
|
||||
|
||||
static type copy(const Args&... args, const H&) {
|
||||
return std::make_tuple(args...);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::int64_t from_chars(const char *begin, const char *end) {
|
||||
std::int64_t res {};
|
||||
std::int64_t mul = 1;
|
||||
while(begin != --end) {
|
||||
res += (std::int64_t)(*end - '0') * mul;
|
||||
|
||||
mul *= 10;
|
||||
}
|
||||
|
||||
return *begin != '-' ? res + (std::int64_t)(*begin - '0') * mul : -res;
|
||||
}
|
||||
|
||||
inline std::int64_t from_view(const std::string_view &number) {
|
||||
return from_chars(std::begin(number), std::end(number));
|
||||
}
|
||||
|
||||
template<class X, class Y>
|
||||
class Either : public std::variant<X, Y> {
|
||||
public:
|
||||
using std::variant<X, Y>::variant;
|
||||
|
||||
constexpr bool has_left() const {
|
||||
return std::holds_alternative<X>(*this);
|
||||
}
|
||||
constexpr bool has_right() const {
|
||||
return std::holds_alternative<Y>(*this);
|
||||
}
|
||||
|
||||
X &left() {
|
||||
return std::get<X>(*this);
|
||||
}
|
||||
|
||||
Y &right() {
|
||||
return std::get<Y>(*this);
|
||||
}
|
||||
|
||||
const X &left() const {
|
||||
return std::get<X>(*this);
|
||||
}
|
||||
|
||||
const Y &right() const {
|
||||
return std::get<Y>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class buffer_t {
|
||||
public:
|
||||
buffer_t() : _els { 0 } {};
|
||||
buffer_t(buffer_t&&) noexcept = default;
|
||||
buffer_t &operator=(buffer_t&& other) noexcept {
|
||||
std::swap(_els, other._els);
|
||||
|
||||
_buf = std::move(other._buf);
|
||||
|
||||
return *this;
|
||||
};
|
||||
|
||||
explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {}
|
||||
explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {
|
||||
std::fill_n(_buf.get(), elements, t);
|
||||
}
|
||||
|
||||
T &operator[](size_t el) {
|
||||
return _buf[el];
|
||||
}
|
||||
|
||||
const T &operator[](size_t el) const {
|
||||
return _buf[el];
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _els;
|
||||
}
|
||||
|
||||
void fake_resize(std::size_t els) {
|
||||
_els = els;
|
||||
}
|
||||
|
||||
T *begin() {
|
||||
return _buf.get();
|
||||
}
|
||||
|
||||
const T *begin() const {
|
||||
return _buf.get();
|
||||
}
|
||||
|
||||
T *end() {
|
||||
return _buf.get() + _els;
|
||||
}
|
||||
|
||||
const T *end() const {
|
||||
return _buf.get() + _els;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _els;
|
||||
std::unique_ptr<T[]> _buf;
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
T either(std::optional<T> &&l, T &&r) {
|
||||
if(l) {
|
||||
return std::move(*l);
|
||||
}
|
||||
|
||||
return std::forward<T>(r);
|
||||
}
|
||||
|
||||
namespace endian {
|
||||
template<class T = void>
|
||||
struct endianness {
|
||||
enum : bool {
|
||||
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
|
||||
defined(__BIG_ENDIAN__) || \
|
||||
defined(__ARMEB__) || \
|
||||
defined(__THUMBEB__) || \
|
||||
defined(__AARCH64EB__) || \
|
||||
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
|
||||
// It's a big-endian target architecture
|
||||
little = false,
|
||||
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
|
||||
defined(__LITTLE_ENDIAN__) || \
|
||||
defined(__ARMEL__) || \
|
||||
defined(__THUMBEL__) || \
|
||||
defined(__AARCH64EL__) || \
|
||||
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
|
||||
// It's a little-endian target architecture
|
||||
little = true,
|
||||
#else
|
||||
#error "Unknown Endianness"
|
||||
#endif
|
||||
big = !little
|
||||
};
|
||||
};
|
||||
|
||||
template<class T, class S = void>
|
||||
struct endian_helper { };
|
||||
|
||||
template<class T>
|
||||
struct endian_helper<T, std::enable_if_t<
|
||||
!(instantiation_of_v<std::optional, T>)
|
||||
>> {
|
||||
static inline T big(T x) {
|
||||
if constexpr (endianness<T>::little) {
|
||||
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
|
||||
|
||||
std::reverse(data, data + sizeof(x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline T little(T x) {
|
||||
if constexpr (endianness<T>::big) {
|
||||
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
|
||||
|
||||
std::reverse(data, data + sizeof(x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct endian_helper<T, std::enable_if_t<
|
||||
instantiation_of_v<std::optional, T>
|
||||
>> {
|
||||
static inline T little(T x) {
|
||||
if(!x) return x;
|
||||
|
||||
if constexpr (endianness<T>::big) {
|
||||
auto *data = reinterpret_cast<uint8_t*>(&*x);
|
||||
|
||||
std::reverse(data, data + sizeof(*x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
static inline T big(T x) {
|
||||
if(!x) return x;
|
||||
|
||||
if constexpr (endianness<T>::big) {
|
||||
auto *data = reinterpret_cast<uint8_t*>(&*x);
|
||||
|
||||
std::reverse(data, data + sizeof(*x));
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline auto little(T x) { return endian_helper<T>::little(x); }
|
||||
|
||||
template<class T>
|
||||
inline auto big(T x) { return endian_helper<T>::big(x); }
|
||||
} /* endian */
|
||||
|
||||
} /* util */
|
||||
#endif
|
||||
50
sunshine/uuid.h
Normal file
50
sunshine/uuid.h
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by loki on 8-2-19.
|
||||
//
|
||||
|
||||
#ifndef T_MAN_UUID_H
|
||||
#define T_MAN_UUID_H
|
||||
|
||||
#include <random>
|
||||
|
||||
union uuid_t {
|
||||
std::uint8_t b8[16];
|
||||
std::uint16_t b16[8];
|
||||
std::uint32_t b32[4];
|
||||
std::uint64_t b64[2];
|
||||
|
||||
static uuid_t generate(std::default_random_engine &engine) {
|
||||
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max());
|
||||
|
||||
uuid_t buf;
|
||||
for(auto &el : buf.b8) {
|
||||
el = dist(engine);
|
||||
}
|
||||
|
||||
buf.b8[7] &= (std::uint8_t) 0b00101111;
|
||||
buf.b8[9] &= (std::uint8_t) 0b10011111;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static uuid_t generate() {
|
||||
std::random_device r;
|
||||
|
||||
std::default_random_engine engine { r() };
|
||||
|
||||
return generate(engine);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const uuid_t &other) const {
|
||||
return b64[0] == other.b64[0] && b64[1] == other.b64[1];
|
||||
}
|
||||
|
||||
constexpr bool operator<(const uuid_t &other) const {
|
||||
return (b64[0] < other.b64[0] || (b64[0] == other.b64[0] && b64[1] < other.b64[1]));
|
||||
}
|
||||
|
||||
constexpr bool operator>(const uuid_t &other) const {
|
||||
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1]));
|
||||
}
|
||||
};
|
||||
#endif //T_MAN_UUID_H
|
||||
177
sunshine/video.cpp
Normal file
177
sunshine/video.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// 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 = config::video.threads;
|
||||
|
||||
|
||||
AVDictionary *options {nullptr};
|
||||
av_dict_set(&options, "profile", config::video.profile.c_str(), 0);
|
||||
av_dict_set(&options, "preset", config::video.preset.c_str(), 0);
|
||||
av_dict_set(&options, "tune", config::video.tune.c_str(), 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
sunshine/video.h
Normal file
27
sunshine/video.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by loki on 6/9/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_VIDEO_H
|
||||
#define SUNSHINE_VIDEO_H
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
struct AVPacket;
|
||||
namespace video {
|
||||
void free_packet(AVPacket *packet);
|
||||
|
||||
using packet_t = util::safe_ptr<AVPacket, free_packet>;
|
||||
|
||||
struct config_t {
|
||||
int width;
|
||||
int height;
|
||||
int framerate;
|
||||
int bitrate;
|
||||
int slicesPerFrame;
|
||||
};
|
||||
|
||||
void capture_display(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_VIDEO_H
|
||||
Reference in New Issue
Block a user