Merge remote-tracking branch 'sunshine/master'

This commit is contained in:
Yukino Song
2025-07-13 23:16:01 +08:00
93 changed files with 2952 additions and 493 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -16,7 +16,6 @@
#include "logging.h"
#include "network.h"
#include "platform/common.h"
#include "version.h"
extern "C" {
#ifdef _WIN32

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -88,6 +88,8 @@ namespace rtsp_stream {
*/
void terminate_sessions();
void rtpThread();
/**
* @brief Runs the RTSP server loop.
*/
void start();
} // namespace rtsp_stream

View File

@@ -51,7 +51,6 @@
#include "process.h"
#include "network.h"
#include "src/entry_handler.h"
#include "version.h"
using namespace std::literals;

View File

@@ -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>

View File

@@ -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@"

View File

@@ -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;

View File

@@ -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)},