diff --git a/src/main.cpp b/src/main.cpp index 941dcfc1..4c1ae2ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -356,8 +356,8 @@ main(int argc, char *argv[]) { reed_solomon_init(); auto input_deinit_guard = input::init(); - if (video::init()) { - BOOST_LOG(error) << "Video failed to initialize"sv; + if (video::probe_encoders()) { + BOOST_LOG(error) << "Video failed to find working encoder"sv; } if (http::init()) { diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 15fc1f34..d2cdd6a8 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -29,6 +29,7 @@ #include "rtsp.h" #include "utility.h" #include "uuid.h" +#include "video.h" using namespace std::literals; namespace nvhttp { @@ -762,6 +763,19 @@ namespace nvhttp { return; } + // Probe encoders again before streaming to ensure our chosen + // encoder matches the active GPU (which could have changed + // due to hotplugging, driver crash, primary monitor change, + // or any number of other factors). + if (rtsp_stream::session_count() == 0) { + if (video::probe_encoders()) { + tree.put("root..status_code", 503); + tree.put("root.gamesession", 0); + + return; + } + } + if (appid > 0) { auto err = proc::proc.execute(appid); if (err) { @@ -820,6 +834,19 @@ namespace nvhttp { return; } + // Probe encoders again before streaming to ensure our chosen + // encoder matches the active GPU (which could have changed + // due to hotplugging, driver crash, primary monitor change, + // or any number of other factors). + if (rtsp_stream::session_count() == 0) { + if (video::probe_encoders()) { + tree.put("root.resume", 0); + tree.put("root..status_code", 503); + + return; + } + } + rtsp_stream::launch_session_raise(make_launch_session(host_audio, args)); tree.put("root..status_code", 200); diff --git a/src/video.cpp b/src/video.cpp index 0eb0e6a2..1b8b682f 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -710,23 +710,25 @@ namespace video { }; #endif - static std::vector encoders { + static const std::vector encoders { #ifndef __APPLE__ - nvenc, + &nvenc, #endif #ifdef _WIN32 - quicksync, - amdvce, + &quicksync, + &amdvce, #endif #ifdef __linux__ - vaapi, + &vaapi, #endif #ifdef __APPLE__ - videotoolbox, + &videotoolbox, #endif - software + &software }; + static encoder_t *chosen_encoder; + void reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) { // We try this twice, in case we still get an error on reinitialization @@ -1446,7 +1448,7 @@ namespace video { encode_run_sync( std::vector> &synced_session_ctxs, encode_session_ctx_queue_t &encode_session_ctx_queue) { - const auto &encoder = encoders.front(); + const auto &encoder = *chosen_encoder; auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type)); int display_p = 0; @@ -1673,7 +1675,7 @@ namespace video { display = ref->display_wp->lock(); } - auto &encoder = encoders.front(); + auto &encoder = *chosen_encoder; auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); auto hwdevice = display->make_hwdevice(pix_fmt); if (!hwdevice) { @@ -1709,7 +1711,7 @@ namespace video { auto idr_events = mail->event(mail::idr); idr_events->raise(true); - if (encoders.front().flags & PARALLEL_ENCODING) { + if (chosen_encoder->flags & PARALLEL_ENCODING) { capture_async(std::move(mail), config, channel_data); } else { @@ -1927,86 +1929,95 @@ namespace video { return true; } + /* + This is called once at startup and each time a stream is launched to + ensure the best encoder is selected. Encoder availablility can change + at runtime due to all sorts of things from driver updates to eGPUs. + + This is only safe to call when there is no client actively streaming. + */ int - init() { - bool encoder_found = false; + probe_encoders() { + auto encoder_list = encoders; + + // Reset encoder selection + chosen_encoder = nullptr; + if (!config::video.encoder.empty()) { // If there is a specific encoder specified, use it if it passes validation - KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { + KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), { auto encoder = *pos; - if (encoder.name == config::video.encoder) { + if (encoder->name == config::video.encoder) { // Remove the encoder from the list entirely if it fails validation - if (!validate_encoder(encoder)) { - pos = encoders.erase(pos); + if (!validate_encoder(*encoder)) { + pos = encoder_list.erase(pos); break; } // If we can't satisfy both the encoder and HDR requirement, prefer the encoder over HDR support - if (config::video.hevc_mode == 3 && !encoder.hevc[encoder_t::DYNAMIC_RANGE]) { + if (config::video.hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) { BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HDR on this system"sv; config::video.hevc_mode = 0; } - encoders.clear(); - encoders.emplace_back(encoder); - encoder_found = true; + chosen_encoder = encoder; break; } pos++; }); - if (!encoder_found) { + if (chosen_encoder == nullptr) { BOOST_LOG(error) << "Couldn't find any working encoder matching ["sv << config::video.encoder << ']'; - config::video.encoder.clear(); } } BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. You can safely ignore those errors. //"sv; // If we haven't found an encoder yet, but we want one with HDR support, search for that now. - if (!encoder_found && config::video.hevc_mode == 3) { - KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { + if (chosen_encoder == nullptr && config::video.hevc_mode == 3) { + KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), { auto encoder = *pos; // Remove the encoder from the list entirely if it fails validation - if (!validate_encoder(encoder)) { - pos = encoders.erase(pos); + if (!validate_encoder(*encoder)) { + pos = encoder_list.erase(pos); continue; } // Skip it if it doesn't support HDR - if (!encoder.hevc[encoder_t::DYNAMIC_RANGE]) { + if (!encoder->hevc[encoder_t::DYNAMIC_RANGE]) { pos++; continue; } - encoders.clear(); - encoders.emplace_back(encoder); - encoder_found = true; + chosen_encoder = encoder; break; }); - if (!encoder_found) { + if (chosen_encoder == nullptr) { BOOST_LOG(error) << "Couldn't find any working HDR-capable encoder"sv; } } // If no encoder was specified or the specified encoder was unusable, keep trying // the remaining encoders until we find one that passes validation. - if (!encoder_found) { - KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { - if (!validate_encoder(*pos)) { - pos = encoders.erase(pos); + if (chosen_encoder == nullptr) { + KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), { + auto encoder = *pos; + + if (!validate_encoder(*encoder)) { + pos = encoder_list.erase(pos); continue; } + chosen_encoder = encoder; break; }); } - if (encoders.empty()) { + if (chosen_encoder == nullptr) { BOOST_LOG(fatal) << "Couldn't find any working encoder"sv; return -1; } @@ -2015,7 +2026,7 @@ namespace video { BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant. //"sv; BOOST_LOG(info); - auto &encoder = encoders.front(); + auto &encoder = *chosen_encoder; BOOST_LOG(debug) << "------ h264 ------"sv; for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) { @@ -2147,7 +2158,7 @@ namespace video { int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { - capture_thread_ctx.encoder_p = &encoders.front(); + capture_thread_ctx.encoder_p = chosen_encoder; capture_thread_ctx.reinit_event.reset(); capture_thread_ctx.capture_ctx_queue = std::make_shared>(30); diff --git a/src/video.h b/src/video.h index 03bf218b..c02c317e 100644 --- a/src/video.h +++ b/src/video.h @@ -97,7 +97,7 @@ namespace video { void *channel_data); int - init(); + probe_encoders(); } // namespace video #endif // SUNSHINE_VIDEO_H