/** * @file src/main.cpp * @brief Main entry point for Sunshine. */ // standard includes #include #include #include #include #include // local includes #include "audio.h" #include "config.h" #include "globals.h" #include "input.h" #include "interprocess.h" #include "logging.h" #include "platform/common.h" #include "version.h" #include "video.h" #ifdef _WIN32 #include #endif enum StatusCode { NORMAL_EXIT, INVALID_IVSHMEM, NO_ENCODER_AVAILABLE }; enum QueueType { Video, Audio, }; enum EventType { Pointer, Bitrate, Framerate, Idr, Hdr, Stop, BufferOverflow, EventMax }; using namespace std::literals; std::map> signal_handlers; void on_signal_forwarder(int sig) { signal_handlers.at(sig)(); } template void on_signal(int sig, FN &&fn) { signal_handlers.emplace(sig, std::forward(fn)); std::signal(sig, on_signal_forwarder); } std::vector split(std::string s, char delim) { std::vector result; std::stringstream ss(s); std::string item; while (getline(ss, item, delim)) { result.push_back(item); } return result; } std::vector replace(const std::string_view &original, const std::string_view &old, const std::string_view &_new) { std::vector replaced; replaced.reserve(original.size() + _new.size() - old.size()); auto begin = std::begin(original); auto end = std::end(original); auto next = std::search(begin, end, std::begin(old), std::end(old)); std::copy(begin, next, std::back_inserter(replaced)); if (next != end) { std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced)); std::copy(next + old.size(), end, std::back_inserter(replaced)); } return replaced; } int main(int argc, char *argv[]) { #ifdef _WIN32 // Switch default C standard library locale to UTF-8 on Windows 10 1803+ setlocale(LC_ALL, ".UTF-8"); #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // Use UTF-8 conversion for the default C++ locale (used by boost::log) std::locale::global(std::locale(std::locale(), new std::codecvt_utf8)); #pragma GCC diagnostic pop mail::man = std::make_shared(); auto ivshmem = new IVSHMEM(argv[1]); ivshmem->Initialize(); MediaMemory *memory = NULL; if (ivshmem->GetSize() < (UINT64)sizeof(MediaMemory)) { BOOST_LOG(error) << "Invalid ivshmem size: "sv << ivshmem->GetSize(); BOOST_LOG(error) << "Expected ivshmem size: "sv << sizeof(MediaMemory); memory = (MediaMemory *)malloc(sizeof(MediaMemory)); } else { BOOST_LOG(info) << "Found ivshmem shared memory"sv; memory = (MediaMemory *)ivshmem->GetMemory(); } auto log_deinit_guard = logging::init(config::sunshine.min_log_level); if (!log_deinit_guard) { BOOST_LOG(error) << "Logging failed to initialize"sv; } // Create signal handler after logging has been initialized auto process_shutdown_event = mail::man->event(mail::shutdown); on_signal(SIGINT, [process_shutdown_event, ivshmem]() { BOOST_LOG(info) << "Interrupt handler called"sv; logging::log_flush(); process_shutdown_event->raise(true); ivshmem->DeInitialize(); }); on_signal(SIGTERM, [process_shutdown_event, ivshmem]() { BOOST_LOG(info) << "Terminate handler called"sv; logging::log_flush(); process_shutdown_event->raise(true); ivshmem->DeInitialize(); }); // Wait as long as possible to terminate Sunshine.exe during logoff/shutdown SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); auto platf_deinit_guard = platf::init(); if (!platf_deinit_guard) { BOOST_LOG(error) << "Platform failed to initialize"sv; return StatusCode::NO_ENCODER_AVAILABLE; } else if (video::probe_encoders()) { BOOST_LOG(error) << "Video failed to find working encoder"sv; return StatusCode::NO_ENCODER_AVAILABLE; } auto video_capture = [&](safe::mail_t mail, std::string displayin, int codec) { video::capture(mail, video::config_t{displayin, 1920, 1080, 60, 6000, 1, 0, 1, codec, 0}, NULL); }; auto audio_capture = [&](safe::mail_t mail) { audio::capture(mail, audio::config_t{10, 2, 3, 0}, NULL); }; auto mail = std::make_shared(); auto pull = [process_shutdown_event, mail](MediaQueue *queue) { auto timer = platf::create_high_precision_timer(); auto local_shutdown = mail->event(mail::shutdown); auto bitrate = mail->event(mail::bitrate); auto framerate = mail->event(mail::framerate); auto idr = mail->event(mail::idr); auto expected_index = queue->outindex; auto last_bitrate = 6; char buffer[DATA_PACKET_SIZE] = {0}; while (!process_shutdown_event->peek() && !local_shutdown->peek()) { while (expected_index == queue->outindex) timer->sleep_for(1ms); memcpy(buffer, queue->outgoing[expected_index].data, queue->outgoing[expected_index].size); expected_index++; if (expected_index >= OUT_QUEUE_SIZE) expected_index = 0; switch (buffer[0]) { case EventType::Bitrate: if (buffer[1] == 0) break; last_bitrate = buffer[1]; BOOST_LOG(info) << "bitrate changed to " << (buffer[1] * 1000); bitrate->raise(buffer[1] * 1000); break; case EventType::BufferOverflow: if (last_bitrate <= 1) break; last_bitrate--; BOOST_LOG(info) << "overflow, bitrate changed to " << (last_bitrate * 1000); bitrate->raise(last_bitrate * 1000); break; case EventType::Framerate: if (buffer[1] < 20) break; BOOST_LOG(info) << "framerate changed to " << (int)(buffer[1]); framerate->raise(buffer[1]); break; case EventType::Pointer: BOOST_LOG(info) << "pointer changed to " << (bool)(buffer[1] != 0); display_cursor = buffer[1] != 0; break; case EventType::Idr: BOOST_LOG(debug) << "IDR"; idr->raise(true); break; case EventType::Stop: BOOST_LOG(debug) << "Received shutdown event from shm"; process_shutdown_event->raise(true); break; default: BOOST_LOG(info) << "invalid message " << u_int(buffer[0]) << " " << u_int(buffer[1]); break; } } }; auto push_video = [process_shutdown_event](safe::mail_t mail, MediaQueue *queue) { auto video_packets = mail->queue(mail::video_packets); auto audio_packets = mail->queue(mail::audio_packets); auto local_shutdown = mail->event(mail::shutdown); auto touch_port = mail->event(mail::touch_port); #ifdef _WIN32 platf::adjust_thread_priority(platf::thread_priority_e::critical); #endif while (!process_shutdown_event->peek() && !local_shutdown->peek()) { do { uint8_t flags = 0; auto packet = video_packets->pop(); auto findex = packet->frame_index(); std::string_view payload{(char *)packet->data(), packet->data_size()}; std::vector payload_with_replacements; uint64_t utimestamp = packet->frame_timestamp.value().time_since_epoch().count(); if (packet->is_idr() && packet->replacements) { for (auto &replacement : *packet->replacements) { auto frame_old = replacement.old; auto frame_new = replacement._new; payload_with_replacements = replace(payload, frame_old, frame_new); payload = {(char *)payload_with_replacements.data(), payload_with_replacements.size()}; } } if (packet->is_idr()) flags |= (1 << 0); if (packet->after_ref_frame_invalidation) flags |= (1 << 1); auto updated = queue->inindex + 1; if (updated >= IN_QUEUE_SIZE) updated = 0; queue->incoming[queue->inindex].size = 0; copy_to_packet(&queue->incoming[queue->inindex], &findex, sizeof(uint64_t)); copy_to_packet(&queue->incoming[queue->inindex], &utimestamp, sizeof(uint64_t)); copy_to_packet(&queue->incoming[queue->inindex], &flags, sizeof(uint8_t)); copy_to_packet(&queue->incoming[queue->inindex], (void *)payload.data(), payload.size()); queue->inindex = updated; } while (video_packets->peek()); } if (!local_shutdown->peek()) local_shutdown->raise(true); }; auto push_audio = [process_shutdown_event](safe::mail_t mail, DataQueue *queue) { auto video_packets = mail->queue(mail::video_packets); auto audio_packets = mail->queue(mail::audio_packets); auto local_shutdown = mail->event(mail::shutdown); auto touch_port = mail->event(mail::touch_port); #ifdef _WIN32 platf::adjust_thread_priority(platf::thread_priority_e::critical); #endif char sum = 0; uint64_t findex = 0; while (!process_shutdown_event->peek() && !local_shutdown->peek()) { do { auto packet = audio_packets->pop(); char *ptr = (char *)packet->second.begin(); size_t size = packet->second.size(); uint64_t utimestamp = std::chrono::steady_clock::now().time_since_epoch().count(); auto updated = queue->inindex + 1; if (updated >= IN_QUEUE_SIZE) updated = 0; findex++; queue->incoming[updated].size = 0; copy_to_dpacket(&queue->incoming[updated], &findex, sizeof(uint64_t)); copy_to_dpacket(&queue->incoming[updated], &utimestamp, sizeof(uint64_t)); copy_to_dpacket(&queue->incoming[updated], &sum, sizeof(uint8_t)); copy_to_dpacket(&queue->incoming[updated], ptr, size); queue->inindex = updated; } while (audio_packets->peek()); } if (!local_shutdown->peek()) local_shutdown->raise(true); }; auto touch_fun = [mail, process_shutdown_event](QueueMetadata *metadata) { auto timer = platf::create_high_precision_timer(); auto local_shutdown = mail->event(mail::shutdown); auto touch_port = mail->event(mail::touch_port); while (!process_shutdown_event->peek() && !local_shutdown->peek()) { if (!touch_port->peek()) { timer->sleep_for(100ms); } else { auto touch = touch_port->pop(); if (touch.has_value()) { auto value = touch.value(); metadata->client_offsetX = value.client_offsetX; metadata->client_offsetY = value.client_offsetY; metadata->offsetX = value.offset_x; metadata->offsetY = value.offset_y; metadata->env_height = value.env_height; metadata->env_width = value.env_width; metadata->height = value.height; metadata->width = value.width; metadata->scalar_inv = value.scalar_inv; } } } if (!local_shutdown->peek()) local_shutdown->raise(true); }; { auto displays = platf::display_names(platf::mem_type_e::dxgi); for (int i = 0; i < displays.size(); i++) { auto codec = memory->video[i].metadata.codec; auto capture = std::thread{video_capture, mail, displays.at(i), codec}; auto forward = std::thread{push_video, mail, &memory->video[i].internal}; auto touch_thread = std::thread{touch_fun, &memory->video[i].metadata}; auto receive = std::thread{pull, &memory->video[i].internal}; receive.detach(); touch_thread.detach(); capture.detach(); forward.detach(); } } { auto capture = std::thread{audio_capture, mail}; auto forward = std::thread{push_audio, mail, &memory->audio}; capture.detach(); forward.detach(); } auto timer = platf::create_high_precision_timer(); auto local_shutdown = mail->event(mail::shutdown); while (!process_shutdown_event->peek() && !local_shutdown->peek()) timer->sleep_for(100ms); BOOST_LOG(info) << "Closed"; // let other threads to close timer->sleep_for(1s); return StatusCode::NORMAL_EXIT; }