Merge remote-tracking branch 'sunshine/master'
This commit is contained in:
@@ -509,7 +509,6 @@ namespace config {
|
||||
{} // wa
|
||||
}, // display_device
|
||||
|
||||
1, // min_fps_factor
|
||||
0, // max_bitrate
|
||||
|
||||
"1920x1080x60", // fallback_mode
|
||||
@@ -1195,7 +1194,6 @@ namespace config {
|
||||
video.dd.wa.hdr_toggle_delay = std::chrono::milliseconds {value};
|
||||
}
|
||||
|
||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
|
||||
int_f(vars, "max_bitrate", video.max_bitrate);
|
||||
string_f(vars, "fallback_mode", video.fallback_mode);
|
||||
bool_f(vars, "isolated_virtual_display_option", video.isolated_virtual_display_option);
|
||||
@@ -1304,6 +1302,7 @@ namespace config {
|
||||
|
||||
string_restricted_f(vars, "locale", config::sunshine.locale, {
|
||||
"bg"sv, // Bulgarian
|
||||
"cs"sv, // Czech
|
||||
"de"sv, // German
|
||||
"en"sv, // English
|
||||
"en_GB"sv, // English (UK)
|
||||
@@ -1321,6 +1320,7 @@ namespace config {
|
||||
"tr"sv, // Turkish
|
||||
"uk"sv, // Ukrainian
|
||||
"zh"sv, // Chinese
|
||||
"zh_TW"sv, // Chinese (Traditional)
|
||||
});
|
||||
|
||||
std::string log_level_string;
|
||||
|
||||
@@ -143,7 +143,6 @@ namespace config {
|
||||
workarounds_t wa;
|
||||
} dd;
|
||||
|
||||
int min_fps_factor; // Minimum fps target, determines minimum frame time
|
||||
int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client
|
||||
|
||||
std::string fallback_mode;
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
#include "process.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "platform/windows/utils.h"
|
||||
@@ -89,6 +88,8 @@ namespace confighttp {
|
||||
void send_response(resp_https_t response, const nlohmann::json &output_tree) {
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(output_tree.dump(), headers);
|
||||
}
|
||||
|
||||
@@ -106,7 +107,9 @@ namespace confighttp {
|
||||
tree["status"] = false;
|
||||
tree["error"] = "Unauthorized";
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "application/json"}
|
||||
{"Content-Type", "application/json"},
|
||||
{"X-Frame-Options", "DENY"},
|
||||
{"Content-Security-Policy", "frame-ancestors 'none';"}
|
||||
};
|
||||
response->write(code, tree.dump(), headers);
|
||||
}
|
||||
@@ -121,7 +124,9 @@ namespace confighttp {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- redirecting"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Location", path}
|
||||
{"Location", path},
|
||||
{"X-Frame-Options", "DENY"},
|
||||
{"Content-Security-Policy", "frame-ancestors 'none';"}
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||
}
|
||||
@@ -218,6 +223,9 @@ namespace confighttp {
|
||||
tree["error"] = "Not Found";
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
|
||||
response->write(code, tree.dump(), headers);
|
||||
}
|
||||
|
||||
@@ -235,6 +243,9 @@ namespace confighttp {
|
||||
tree["error"] = error_message;
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
|
||||
response->write(code, tree.dump(), headers);
|
||||
}
|
||||
|
||||
@@ -252,10 +263,25 @@ namespace confighttp {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requestContentType->second != contentType) {
|
||||
// Extract the media type part before any parameters (e.g., charset)
|
||||
std::string actualContentType = requestContentType->second;
|
||||
size_t semicolonPos = actualContentType.find(';');
|
||||
if (semicolonPos != std::string::npos) {
|
||||
actualContentType = actualContentType.substr(0, semicolonPos);
|
||||
}
|
||||
|
||||
// Trim whitespace and convert to lowercase for case-insensitive comparison
|
||||
boost::algorithm::trim(actualContentType);
|
||||
boost::algorithm::to_lower(actualContentType);
|
||||
|
||||
std::string expectedContentType(contentType);
|
||||
boost::algorithm::to_lower(expectedContentType);
|
||||
|
||||
if (actualContentType != expectedContentType) {
|
||||
bad_request(response, request, "Content type mismatch");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -273,9 +299,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "index.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -292,9 +319,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "pin.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -311,10 +339,11 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "apps.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"},
|
||||
{"Access-Control-Allow-Origin", "https://images.igdb.com/"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -331,9 +360,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "clients.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -350,9 +380,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "config.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -369,9 +400,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "password.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -393,9 +425,10 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "login.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -413,9 +446,10 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "welcome.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -432,9 +466,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "text/html; charset=utf-8"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
@@ -447,9 +482,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/apollo.ico", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "image/x-icon"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "image/x-icon");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
@@ -465,9 +501,10 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/logo-apollo-45.png", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{"Content-Type", "image/png"}
|
||||
};
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "image/png");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
@@ -518,6 +555,8 @@ namespace confighttp {
|
||||
}
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", mimeType->second);
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
std::ifstream in(filePath.string(), std::ios::binary);
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
@@ -631,6 +670,9 @@ namespace confighttp {
|
||||
* @api_examples{/api/apps/close| POST| null}
|
||||
*/
|
||||
void closeApp(resp_https_t response, req_https_t request) {
|
||||
if (!check_content_type(response, request, "application/json")) {
|
||||
return;
|
||||
}
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
@@ -936,7 +978,7 @@ namespace confighttp {
|
||||
* @api_examples{/api/clients/unpair-all| POST| null}
|
||||
*/
|
||||
void unpairAll(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -964,7 +1006,7 @@ namespace confighttp {
|
||||
nlohmann::json output_tree;
|
||||
output_tree["status"] = true;
|
||||
output_tree["platform"] = SUNSHINE_PLATFORM;
|
||||
output_tree["version"] = PROJECT_VER;
|
||||
output_tree["version"] = PROJECT_VERSION;
|
||||
#ifdef _WIN32
|
||||
output_tree["vdisplayStatus"] = (int)proc::vDisplayDriverStatus;
|
||||
#endif
|
||||
@@ -1109,6 +1151,8 @@ namespace confighttp {
|
||||
contentType += currentCodePageToCharset();
|
||||
#endif
|
||||
headers.emplace("Content-Type", contentType);
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, content, headers);
|
||||
}
|
||||
|
||||
@@ -1264,7 +1308,7 @@ namespace confighttp {
|
||||
* @api_examples{/api/reset-display-device-persistence| POST| null}
|
||||
*/
|
||||
void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1283,7 +1327,7 @@ namespace confighttp {
|
||||
* @api_examples{/api/restart| POST| null}
|
||||
*/
|
||||
void restart(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "logging.h"
|
||||
#include "network.h"
|
||||
#include "platform/common.h"
|
||||
#include "version.h"
|
||||
|
||||
extern "C" {
|
||||
#ifdef _WIN32
|
||||
|
||||
11
src/main.cpp
11
src/main.cpp
@@ -103,6 +103,10 @@ int main(int argc, char *argv[]) {
|
||||
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
// Avoid searching the PATH in case a user has configured their system insecurely
|
||||
// by placing a user-writable directory in the system-wide PATH variable.
|
||||
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
|
||||
setlocale(LC_ALL, "C");
|
||||
#endif
|
||||
|
||||
@@ -129,7 +133,7 @@ int main(int argc, char *argv[]) {
|
||||
// logging can begin at this point
|
||||
// if anything is logged prior to this point, it will appear in stdout, but not in the log viewer in the UI
|
||||
// the version should be printed to the log before anything else
|
||||
BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER;
|
||||
BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VERSION << " commit: " << PROJECT_VERSION_COMMIT;
|
||||
|
||||
// Log publisher metadata
|
||||
log_publisher_data();
|
||||
@@ -389,6 +393,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::thread httpThread {nvhttp::start};
|
||||
std::thread configThread {confighttp::start};
|
||||
std::thread rtspThread {rtsp_stream::start};
|
||||
|
||||
#ifdef _WIN32
|
||||
// If we're using the default port and GameStream is enabled, warn the user
|
||||
@@ -398,10 +403,12 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
#endif
|
||||
|
||||
rtsp_stream::rtpThread();
|
||||
// Wait for shutdown
|
||||
shutdown_event->view();
|
||||
|
||||
httpThread.join();
|
||||
configThread.join();
|
||||
rtspThread.join();
|
||||
|
||||
task_pool.stop();
|
||||
task_pool.join();
|
||||
|
||||
@@ -513,7 +513,7 @@ std::string get_local_ip_for_gateway() {
|
||||
// UDP GSO on Linux currently only supports sending 64K or 64 segments at a time
|
||||
size_t seg_index = 0;
|
||||
const size_t seg_max = 65536 / 1500;
|
||||
struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg] = {};
|
||||
struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg];
|
||||
auto msg_size = send_info.header_size + send_info.payload_size;
|
||||
while (seg_index < send_info.block_count) {
|
||||
int iovlen = 0;
|
||||
@@ -596,10 +596,11 @@ std::string get_local_ip_for_gateway() {
|
||||
|
||||
{
|
||||
// If GSO is not supported, use sendmmsg() instead.
|
||||
struct mmsghdr msgs[send_info.block_count] = {};
|
||||
struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)] = {};
|
||||
struct mmsghdr msgs[send_info.block_count];
|
||||
struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)];
|
||||
int iov_idx = 0;
|
||||
for (size_t i = 0; i < send_info.block_count; i++) {
|
||||
msgs[i].msg_len = 0;
|
||||
msgs[i].msg_hdr.msg_iov = &iovs[iov_idx];
|
||||
msgs[i].msg_hdr.msg_iovlen = send_info.headers ? 2 : 1;
|
||||
|
||||
@@ -617,6 +618,7 @@ std::string get_local_ip_for_gateway() {
|
||||
msgs[i].msg_hdr.msg_namelen = msg.msg_namelen;
|
||||
msgs[i].msg_hdr.msg_control = cmbuf.buf;
|
||||
msgs[i].msg_hdr.msg_controllen = cmbuflen;
|
||||
msgs[i].msg_hdr.msg_flags = 0;
|
||||
}
|
||||
|
||||
// Call sendmmsg() until all messages are sent
|
||||
@@ -709,7 +711,7 @@ std::string get_local_ip_for_gateway() {
|
||||
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
|
||||
struct iovec iovs[2] = {};
|
||||
struct iovec iovs[2];
|
||||
int iovlen = 0;
|
||||
if (send_info.header) {
|
||||
iovs[iovlen].iov_base = (void *) send_info.header;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID {
|
||||
for (NSScreen *screen in [NSScreen screens]) {
|
||||
if (screen.deviceDescription[@"NSScreenNumber"] == [NSNumber numberWithUnsignedInt:displayID]) {
|
||||
if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) {
|
||||
return screen.localizedName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,8 @@ DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x2
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
||||
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
|
||||
|
||||
#if defined(__x86_64) || defined(_M_AMD64)
|
||||
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
|
||||
#define STEAM_DRIVER_SUBDIR L"x64"
|
||||
#elif defined(__i386) || defined(_M_IX86)
|
||||
#define STEAM_DRIVER_SUBDIR L"x86"
|
||||
#else
|
||||
#warning No known Steam audio driver for this architecture
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* @file src/platform/windows/windows.rc.in
|
||||
* @brief Windows resource file template.
|
||||
* @note The final `windows.rc` is generated from this file during the CMake build.
|
||||
* @todo Use CMake definitions directly, instead of configuring this file.
|
||||
* @file src/platform/windows/windows.rc
|
||||
* @brief Windows resource file.
|
||||
*/
|
||||
#include "winver.h"
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
||||
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
||||
FILEVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0
|
||||
PRODUCTVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
@@ -16,13 +18,13 @@ BEGIN
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "SudoMaker\0"
|
||||
VALUE "FileDescription", "Apollo\0"
|
||||
VALUE "FileVersion", "@PROJECT_VERSION@\0"
|
||||
VALUE "InternalName", "Apollo\0"
|
||||
VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE\0"
|
||||
VALUE "ProductName", "Apollo\0"
|
||||
VALUE "ProductVersion", "@PROJECT_VERSION@\0"
|
||||
VALUE "CompanyName", TOSTRING(PROJECT_VENDOR)
|
||||
VALUE "FileDescription", TOSTRING(PROJECT_NAME)
|
||||
VALUE "FileVersion", TOSTRING(PROJECT_VERSION)
|
||||
VALUE "InternalName", TOSTRING(PROJECT_NAME)
|
||||
VALUE "ProductName", TOSTRING(PROJECT_NAME)
|
||||
VALUE "ProductVersion", TOSTRING(PROJECT_VERSION)
|
||||
VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE"
|
||||
END
|
||||
END
|
||||
|
||||
@@ -39,4 +41,4 @@ BEGIN
|
||||
|
||||
END
|
||||
END
|
||||
SuperDuperAmazing ICON DISCARDABLE "@SUNSHINE_ICON_PATH@"
|
||||
SuperDuperAmazing ICON DISCARDABLE PROJECT_ICON_PATH
|
||||
@@ -39,7 +39,7 @@
|
||||
#define gemm DECORATE_FUNC(gemm, ISA_SUFFIX)
|
||||
#define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX)
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
|
||||
|
||||
// Compile a variant for SSSE3
|
||||
#if defined(__clang__)
|
||||
@@ -122,7 +122,7 @@ reed_solomon_decode_t reed_solomon_decode_fn;
|
||||
* @details The streaming code will directly invoke these function pointers during encoding.
|
||||
*/
|
||||
void reed_solomon_init(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
|
||||
if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw")) {
|
||||
reed_solomon_new_fn = reed_solomon_new_avx512;
|
||||
reed_solomon_release_fn = reed_solomon_release_avx512;
|
||||
|
||||
86
src/rtsp.cpp
86
src/rtsp.cpp
@@ -432,11 +432,6 @@ namespace rtsp_stream {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class T, class X>
|
||||
void iterate(std::chrono::duration<T, X> timeout) {
|
||||
io_context.run_one_for(timeout);
|
||||
}
|
||||
|
||||
void handle_msg(tcp::socket &sock, launch_session_t &session, msg_t &&req) {
|
||||
auto func = _map_cmd_cb.find(req->message.request.command);
|
||||
if (func != std::end(_map_cmd_cb)) {
|
||||
@@ -494,15 +489,24 @@ namespace rtsp_stream {
|
||||
* @param launch_session Streaming session information.
|
||||
*/
|
||||
void session_raise(std::shared_ptr<launch_session_t> launch_session) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// If a launch event is still pending, don't overwrite it.
|
||||
if (raised_timeout > now && launch_event.peek()) {
|
||||
if (launch_event.view(0s)) {
|
||||
return;
|
||||
}
|
||||
raised_timeout = now + config::stream.ping_timeout;
|
||||
|
||||
// Raise the new launch session to prepare for the RTSP handshake
|
||||
launch_event.raise(std::move(launch_session));
|
||||
|
||||
// Arm the timer to expire this launch session if the client times out
|
||||
raised_timer.expires_after(config::stream.ping_timeout);
|
||||
raised_timer.async_wait([this](const boost::system::error_code &ec) {
|
||||
if (!ec) {
|
||||
auto discarded = launch_event.pop(0s);
|
||||
if (discarded) {
|
||||
BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,6 +521,7 @@ namespace rtsp_stream {
|
||||
if (launch_session->id != launch_session_id) {
|
||||
BOOST_LOG(error) << "Attempted to clear unexpected session: "sv << launch_session_id << " vs "sv << launch_session->id;
|
||||
} else {
|
||||
raised_timer.cancel();
|
||||
launch_event.pop();
|
||||
}
|
||||
}
|
||||
@@ -541,14 +546,6 @@ namespace rtsp_stream {
|
||||
* @examples_end
|
||||
*/
|
||||
void clear(bool all = true) {
|
||||
// if a launch event timed out --> Remove it.
|
||||
if (raised_timeout < std::chrono::steady_clock::now()) {
|
||||
auto discarded = launch_event.pop(0s);
|
||||
if (discarded) {
|
||||
BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id;
|
||||
}
|
||||
}
|
||||
|
||||
auto lg = _session_slots.lock();
|
||||
|
||||
for (auto i = _session_slots->begin(); i != _session_slots->end();) {
|
||||
@@ -583,6 +580,26 @@ namespace rtsp_stream {
|
||||
BOOST_LOG(info) << "New streaming session started [active sessions: "sv << _session_slots->size() << ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Runs an iteration of the RTSP server loop
|
||||
*/
|
||||
void iterate() {
|
||||
// If we have a session, we will return to the server loop every
|
||||
// 500ms to allow session cleanup to happen.
|
||||
if (session_count() > 0) {
|
||||
io_context.run_one_for(500ms);
|
||||
} else {
|
||||
io_context.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop the RTSP server.
|
||||
*/
|
||||
void stop() {
|
||||
acceptor.close();
|
||||
io_context.stop();
|
||||
clear();
|
||||
std::shared_ptr<stream::session_t>
|
||||
find_session(const std::string_view& uuid) {
|
||||
auto lg = _session_slots.lock();
|
||||
@@ -613,10 +630,9 @@ namespace rtsp_stream {
|
||||
|
||||
sync_util::sync_t<std::set<std::shared_ptr<stream::session_t>>> _session_slots;
|
||||
|
||||
std::chrono::steady_clock::time_point raised_timeout;
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
tcp::acceptor acceptor {io_context};
|
||||
boost::asio::steady_timer raised_timer {io_context};
|
||||
|
||||
std::shared_ptr<socket_t> next_socket;
|
||||
};
|
||||
@@ -1163,9 +1179,8 @@ namespace rtsp_stream {
|
||||
respond(sock, session, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void rtpThread() {
|
||||
void start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
|
||||
|
||||
server.map("OPTIONS"sv, &cmd_option);
|
||||
server.map("DESCRIBE"sv, &cmd_describe);
|
||||
@@ -1181,18 +1196,29 @@ namespace rtsp_stream {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!shutdown_event->peek()) {
|
||||
server.iterate(std::min(500ms, config::stream.ping_timeout));
|
||||
std::thread rtsp_thread {[&shutdown_event] {
|
||||
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
|
||||
|
||||
if (broadcast_shutdown_event->peek()) {
|
||||
server.clear();
|
||||
} else {
|
||||
// cleanup all stopped sessions
|
||||
server.clear(false);
|
||||
while (!shutdown_event->peek()) {
|
||||
server.iterate();
|
||||
|
||||
if (broadcast_shutdown_event->peek()) {
|
||||
server.clear();
|
||||
} else {
|
||||
// cleanup all stopped sessions
|
||||
server.clear(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.clear();
|
||||
server.clear();
|
||||
}};
|
||||
|
||||
// Wait for shutdown
|
||||
shutdown_event->view();
|
||||
|
||||
// Stop the server and join the server thread
|
||||
server.stop();
|
||||
rtsp_thread.join();
|
||||
}
|
||||
|
||||
void print_msg(PRTSP_MESSAGE msg) {
|
||||
|
||||
@@ -88,6 +88,8 @@ namespace rtsp_stream {
|
||||
*/
|
||||
void terminate_sessions();
|
||||
|
||||
void rtpThread();
|
||||
|
||||
/**
|
||||
* @brief Runs the RTSP server loop.
|
||||
*/
|
||||
void start();
|
||||
} // namespace rtsp_stream
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
#include "process.h"
|
||||
#include "network.h"
|
||||
#include "src/entry_handler.h"
|
||||
#include "version.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
* @file src/upnp.cpp
|
||||
* @brief Definitions for UPnP port mapping.
|
||||
*/
|
||||
// standard includes
|
||||
#include <stddef.h> // workaround for type_t error in miniupnpc 2.3.3, see https://github.com/miniupnp/miniupnp/commit/e263ab6f56c382e10fed31347ec68095d691a0e8
|
||||
|
||||
// lib includes
|
||||
#include <miniupnpc/miniupnpc.h>
|
||||
#include <miniupnpc/upnpcommands.h>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @file src/version.h.in
|
||||
* @brief Version definitions for Sunshine.
|
||||
* @note The final `version.h` is generated from this file during the CMake build.
|
||||
* @todo Use CMake definitions directly, instead of configuring this file.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define PROJECT_NAME "@PROJECT_NAME@"
|
||||
#define PROJECT_VER "@PROJECT_VERSION@"
|
||||
#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@"
|
||||
#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@"
|
||||
#define PROJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"
|
||||
@@ -775,6 +775,18 @@ namespace video {
|
||||
{"usage"s, &config::video.amd.amd_usage_hevc},
|
||||
{"vbaq"s, &config::video.amd.amd_vbaq},
|
||||
{"enforce_hrd"s, &config::video.amd.amd_enforce_hrd},
|
||||
{"level"s, [](const config_t &cfg) {
|
||||
auto size = cfg.width * cfg.height;
|
||||
// For 4K and below, try to use level 5.1 or 5.2 if possible
|
||||
if (size <= 8912896) {
|
||||
if (size * cfg.framerate <= 534773760) {
|
||||
return "5.1"s;
|
||||
} else if (size * cfg.framerate <= 1069547520) {
|
||||
return "5.2"s;
|
||||
}
|
||||
}
|
||||
return "auto"s;
|
||||
}},
|
||||
},
|
||||
{}, // SDR-specific options
|
||||
{}, // HDR-specific options
|
||||
@@ -1672,7 +1684,7 @@ namespace video {
|
||||
ctx->thread_count = ctx->slices;
|
||||
|
||||
AVDictionary *options {nullptr};
|
||||
auto handle_option = [&options](const encoder_t::option_t &option) {
|
||||
auto handle_option = [&options, &config](const encoder_t::option_t &option) {
|
||||
std::visit(
|
||||
util::overloaded {
|
||||
[&](int v) {
|
||||
@@ -1686,7 +1698,7 @@ namespace video {
|
||||
av_dict_set_int(&options, option.name.c_str(), **v, 0);
|
||||
}
|
||||
},
|
||||
[&](std::function<int()> v) {
|
||||
[&](const std::function<int()> &v) {
|
||||
av_dict_set_int(&options, option.name.c_str(), v(), 0);
|
||||
},
|
||||
[&](const std::string &v) {
|
||||
@@ -1696,6 +1708,9 @@ namespace video {
|
||||
if (!v->empty()) {
|
||||
av_dict_set(&options, option.name.c_str(), v->c_str(), 0);
|
||||
}
|
||||
},
|
||||
[&](const std::function<const std::string(const config_t &cfg)> &v) {
|
||||
av_dict_set(&options, option.name.c_str(), v(config).c_str(), 0);
|
||||
}
|
||||
},
|
||||
option.value
|
||||
@@ -1907,8 +1922,8 @@ namespace video {
|
||||
}
|
||||
});
|
||||
|
||||
// set minimum frame time, avoiding violation of client-requested target framerate
|
||||
auto minimum_frame_time = std::chrono::milliseconds(1000 / std::min(config.framerate, (config::video.min_fps_factor * 10)));
|
||||
// set minimum frame time based on client-requested target framerate
|
||||
auto minimum_frame_time = std::chrono::milliseconds(1000 / config.framerate);
|
||||
auto frame_threshold = std::chrono::microseconds(1000ms * 1000 / config.encodingFramerate);
|
||||
BOOST_LOG(debug) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on min fps factor of "sv << config::video.min_fps_factor << "."sv;
|
||||
BOOST_LOG(info) << "Frame threshold: "sv << frame_threshold;
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace video {
|
||||
option_t(const option_t &) = default;
|
||||
|
||||
std::string name;
|
||||
std::variant<int, int *, std::optional<int> *, std::function<int()>, std::string, std::string *> value;
|
||||
std::variant<int, int *, std::optional<int> *, std::function<int()>, std::string, std::string *, std::function<const std::string(const config_t &)>> value;
|
||||
|
||||
option_t(std::string &&name, decltype(value) &&value):
|
||||
name {std::move(name)},
|
||||
|
||||
Reference in New Issue
Block a user