Reprobe encoders each time streaming begins

Available encoders can change due to driver updates, GPU hotplugging, primary monitor changes, etc.
This commit is contained in:
Cameron Gutman 2023-04-09 16:34:07 -05:00
parent 44f89de33b
commit 6467e10def
4 changed files with 79 additions and 41 deletions

View File

@ -356,8 +356,8 @@ main(int argc, char *argv[]) {
reed_solomon_init(); reed_solomon_init();
auto input_deinit_guard = input::init(); auto input_deinit_guard = input::init();
if (video::init()) { if (video::probe_encoders()) {
BOOST_LOG(error) << "Video failed to initialize"sv; BOOST_LOG(error) << "Video failed to find working encoder"sv;
} }
if (http::init()) { if (http::init()) {

View File

@ -29,6 +29,7 @@
#include "rtsp.h" #include "rtsp.h"
#include "utility.h" #include "utility.h"
#include "uuid.h" #include "uuid.h"
#include "video.h"
using namespace std::literals; using namespace std::literals;
namespace nvhttp { namespace nvhttp {
@ -762,6 +763,19 @@ namespace nvhttp {
return; 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.<xmlattr>.status_code", 503);
tree.put("root.gamesession", 0);
return;
}
}
if (appid > 0) { if (appid > 0) {
auto err = proc::proc.execute(appid); auto err = proc::proc.execute(appid);
if (err) { if (err) {
@ -820,6 +834,19 @@ namespace nvhttp {
return; 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.<xmlattr>.status_code", 503);
return;
}
}
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args)); rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);

View File

@ -710,23 +710,25 @@ namespace video {
}; };
#endif #endif
static std::vector<encoder_t> encoders { static const std::vector<encoder_t *> encoders {
#ifndef __APPLE__ #ifndef __APPLE__
nvenc, &nvenc,
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
quicksync, &quicksync,
amdvce, &amdvce,
#endif #endif
#ifdef __linux__ #ifdef __linux__
vaapi, &vaapi,
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__
videotoolbox, &videotoolbox,
#endif #endif
software &software
}; };
static encoder_t *chosen_encoder;
void void
reset_display(std::shared_ptr<platf::display_t> &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) { reset_display(std::shared_ptr<platf::display_t> &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 // We try this twice, in case we still get an error on reinitialization
@ -1446,7 +1448,7 @@ namespace video {
encode_run_sync( encode_run_sync(
std::vector<std::unique_ptr<sync_session_ctx_t>> &synced_session_ctxs, std::vector<std::unique_ptr<sync_session_ctx_t>> &synced_session_ctxs,
encode_session_ctx_queue_t &encode_session_ctx_queue) { 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)); auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type));
int display_p = 0; int display_p = 0;
@ -1673,7 +1675,7 @@ namespace video {
display = ref->display_wp->lock(); 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 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); auto hwdevice = display->make_hwdevice(pix_fmt);
if (!hwdevice) { if (!hwdevice) {
@ -1709,7 +1711,7 @@ namespace video {
auto idr_events = mail->event<bool>(mail::idr); auto idr_events = mail->event<bool>(mail::idr);
idr_events->raise(true); idr_events->raise(true);
if (encoders.front().flags & PARALLEL_ENCODING) { if (chosen_encoder->flags & PARALLEL_ENCODING) {
capture_async(std::move(mail), config, channel_data); capture_async(std::move(mail), config, channel_data);
} }
else { else {
@ -1927,86 +1929,95 @@ namespace video {
return true; 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 int
init() { probe_encoders() {
bool encoder_found = false; auto encoder_list = encoders;
// Reset encoder selection
chosen_encoder = nullptr;
if (!config::video.encoder.empty()) { if (!config::video.encoder.empty()) {
// If there is a specific encoder specified, use it if it passes validation // 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; 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 // Remove the encoder from the list entirely if it fails validation
if (!validate_encoder(encoder)) { if (!validate_encoder(*encoder)) {
pos = encoders.erase(pos); pos = encoder_list.erase(pos);
break; break;
} }
// If we can't satisfy both the encoder and HDR requirement, prefer the encoder over HDR support // 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; BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HDR on this system"sv;
config::video.hevc_mode = 0; config::video.hevc_mode = 0;
} }
encoders.clear(); chosen_encoder = encoder;
encoders.emplace_back(encoder);
encoder_found = true;
break; break;
} }
pos++; pos++;
}); });
if (!encoder_found) { if (chosen_encoder == nullptr) {
BOOST_LOG(error) << "Couldn't find any working encoder matching ["sv << config::video.encoder << ']'; 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; 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 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) { if (chosen_encoder == nullptr && config::video.hevc_mode == 3) {
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; auto encoder = *pos;
// Remove the encoder from the list entirely if it fails validation // Remove the encoder from the list entirely if it fails validation
if (!validate_encoder(encoder)) { if (!validate_encoder(*encoder)) {
pos = encoders.erase(pos); pos = encoder_list.erase(pos);
continue; continue;
} }
// Skip it if it doesn't support HDR // Skip it if it doesn't support HDR
if (!encoder.hevc[encoder_t::DYNAMIC_RANGE]) { if (!encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
pos++; pos++;
continue; continue;
} }
encoders.clear(); chosen_encoder = encoder;
encoders.emplace_back(encoder);
encoder_found = true;
break; break;
}); });
if (!encoder_found) { if (chosen_encoder == nullptr) {
BOOST_LOG(error) << "Couldn't find any working HDR-capable encoder"sv; 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 // If no encoder was specified or the specified encoder was unusable, keep trying
// the remaining encoders until we find one that passes validation. // the remaining encoders until we find one that passes validation.
if (!encoder_found) { if (chosen_encoder == nullptr) {
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), {
if (!validate_encoder(*pos)) { auto encoder = *pos;
pos = encoders.erase(pos);
if (!validate_encoder(*encoder)) {
pos = encoder_list.erase(pos);
continue; continue;
} }
chosen_encoder = encoder;
break; break;
}); });
} }
if (encoders.empty()) { if (chosen_encoder == nullptr) {
BOOST_LOG(fatal) << "Couldn't find any working encoder"sv; BOOST_LOG(fatal) << "Couldn't find any working encoder"sv;
return -1; return -1;
} }
@ -2015,7 +2026,7 @@ namespace video {
BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant. //"sv; BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant. //"sv;
BOOST_LOG(info); BOOST_LOG(info);
auto &encoder = encoders.front(); auto &encoder = *chosen_encoder;
BOOST_LOG(debug) << "------ h264 ------"sv; BOOST_LOG(debug) << "------ h264 ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) { for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
@ -2147,7 +2158,7 @@ namespace video {
int int
start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { 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.reinit_event.reset();
capture_thread_ctx.capture_ctx_queue = std::make_shared<safe::queue_t<capture_ctx_t>>(30); capture_thread_ctx.capture_ctx_queue = std::make_shared<safe::queue_t<capture_ctx_t>>(30);

View File

@ -97,7 +97,7 @@ namespace video {
void *channel_data); void *channel_data);
int int
init(); probe_encoders();
} // namespace video } // namespace video
#endif // SUNSHINE_VIDEO_H #endif // SUNSHINE_VIDEO_H