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

View File

@ -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.<xmlattr>.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.<xmlattr>.status_code", 503);
return;
}
}
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200);

View File

@ -710,23 +710,25 @@ namespace video {
};
#endif
static std::vector<encoder_t> encoders {
static const std::vector<encoder_t *> 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<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
@ -1446,7 +1448,7 @@ namespace video {
encode_run_sync(
std::vector<std::unique_ptr<sync_session_ctx_t>> &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<bool>(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<safe::queue_t<capture_ctx_t>>(30);

View File

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