Skip to content

Commit

Permalink
UI: Show warning on startup if selected encoder is missing
Browse files Browse the repository at this point in the history
  • Loading branch information
derrod committed Oct 5, 2024
1 parent 123986e commit 75b14b1
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
26 changes: 26 additions & 0 deletions UI/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1600,3 +1600,29 @@ MultitrackVideo.IncompatibleSettings.UpdateAndStartStreaming="Update Settings an
MultitrackVideo.IncompatibleSettings.AudioChannels="%1 is not currently compatible with [Audio → General → Channels] set to '%2', %3"
MultitrackVideo.IncompatibleSettings.AudioChannelsSingle="[Audio → General → Channels] needs to be set to '%1'"
MultitrackVideo.IncompatibleSettings.AudioChannelsMultiple="%1 requires multiple different settings for [Audio → General → Channels]"

# Missing Encoder Warning
EncoderMissing.Title="Missing Encoders"
EncoderMissing.TextBegin="The following encoders are missing:\n<ul>\n"
EncoderMissing.ListItem="<li><code>%1</code> - %2</li>\n"
EncoderMissing.TextEnd="\n</ul>\nSee the <a href=\"%1\">Knowledge Base Article</a> for further information."
EncoderMissing.Unknown="Unknown"

## Nvenc specific errors
EncoderMissing.NVENC.TestProgramFailedStartup="NVENC check program start failure"
EncoderMissing.NVENC.TestProgramExitWithError="NVENC check process failure, exit code: %1"
EncoderMissing.NVENC.TestProgramReadFailure="NVENC check process output read failure, exit code: %1"
EncoderMissing.NVENC.TestProgramError="NVENC check failed with reason: %1 (code: %2)"
EncoderMissing.NVENC.NoDevices="No NVIDIA GPUs found"
EncoderMissing.NVENC.Unsupported.Kepler="Your GPU's (%1) architecture (Kepler) is no longer supported. Please use OBS 30.2 or earlier instead."
EncoderMissing.NVENC.Reason.NvmlLoad="Failed loading NVML library"
EncoderMissing.NVENC.Reason.NvmlInit="Failed initializing NVML"
EncoderMissing.NVENC.Reason.NvencLoad="Failed loading NVENC library"
EncoderMissing.NVENC.Reason.NvencInit="Failed initializing NVENC"
EncoderMissing.NVENC.Reason.NvencVersion="NVENC version empty"
EncoderMissing.NVENC.Reason.CudaLoad="Failed loading CUDA library"
EncoderMissing.NVENC.Reason.CudaInit="Failed initializing CUDA"
EncoderMissing.NVENC.Reason.CudaVersion="Invalid/Missing CUDA version"
EncoderMissing.NVENC.Reason.DriverOutdated="Outdated driver"
EncoderMissing.NVENC.Reason.NoSupportedDevices="No NVIDIA devices with NVENC support found"
EncoderMissing.NVENC.Reason.SessionLimitExceeded="Encoder session limit exceeded"
155 changes: 155 additions & 0 deletions UI/window-basic-main-profiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
#include <string>
#include <map>
#include <tuple>
#include <unordered_set>
#include <obs.hpp>
#include <util/pipe.h>
#include <util/platform.h>
#include <util/util.hpp>
#include <QMessageBox>
#include <QVariant>
#include <QFileDialog>
#include <qt-wrappers.hpp>

#include "window-basic-main.hpp"
#include "window-basic-auto-config.hpp"
#include "window-namedialog.hpp"
Expand Down Expand Up @@ -671,6 +674,7 @@ void OBSBasic::ActivateProfile(const OBSProfile &profile, bool reset)
}

CheckForSimpleModeX264Fallback();
CheckForAdvancedModeEncoders();

RefreshProfiles();

Expand Down Expand Up @@ -863,3 +867,154 @@ void OBSBasic::CheckForSimpleModeX264Fallback()
activeConfiguration.SaveSafe("tmp");
}
}

#ifndef __APPLE__
/* These may also be useful for QSV/AMF tests so keep them here. */
static auto args_deleter = [](os_process_args_t *args) {
os_process_args_destroy(args);
};
using OsProcessArgs = std::unique_ptr<os_process_args_t, decltype(args_deleter)>;

static std::optional<std::pair<QStringView, QString>> CheckNVENCVersion()
{
const std::vector<std::pair<std::string_view, std::string_view>> nvencFailureReasons = {
{"nvml_lib", "EncoderMissing.NVENC.Reason.NvmlLoad"},
// Some error messages are a prefix + numeric error code
{"nvml_init", "EncoderMissing.NVENC.Reason.NvmlInit"},
{"nvenc_lib", "EncoderMissing.NVENC.Reason.NvencLoad"},
{"nvenc_init", "EncoderMissing.NVENC.Reason.NvencInit"},
{"cuda_lib", "EncoderMissing.NVENC.Reason.CudaLoad"},
{"cuda_init", "EncoderMissing.NVENC.Reason.CudaInit"},
{"no_cuda_version", "EncoderMissing.NVENC.Reason.CudaVersion"},
{"no_devices", "EncoderMissing.NVENC.NoDevices"},
{"no_nvenc_version", "EncoderMissing.NVENC.Reason.NvencVersion"},
{"outdated_driver", "EncoderMissing.NVENC.Reason.DriverOutdated"},
{"no_supported_devices", "EncoderMissing.NVENC.Reason.NoSupportedDevices"},
{"session_limit", "EncoderMissing.NVENC.Reason.SessionLimitExceeded"},
};

#ifdef _WIN32
BPtr test_exe = os_get_executable_path_ptr("obs-nvenc-test.exe");
#else
BPtr test_exe = os_get_executable_path_ptr("obs-nvenc-test");
#endif

OsProcessArgs args{os_process_args_create(test_exe), args_deleter};

os_process_pipe_t *pp = os_process_pipe_create2(args.get(), "r");
if (!pp)
return std::make_pair(u"", QTStr("EncoderMissing.NVENC.TestProgramFailedStartup"));

std::string caps_result;
for (;;) {
char data[2048];
size_t len = os_process_pipe_read(pp, (uint8_t *)data, sizeof(data));
if (!len)
break;

caps_result.append(data, len);
}

int exit_code = os_process_pipe_destroy(pp);

if (caps_result.empty())
return std::make_pair(u"", QTStr("EncoderMissing.NVENC.TestProgramExitWithError").arg(exit_code));

auto config = ConfigFile();
if (config.OpenString(caps_result.c_str()) != CONFIG_SUCCESS)
return std::make_pair(u"", QTStr("EncoderMissing.NVENC.TestProgramReadFailure").arg(exit_code));

/* Read devices and attempt to find reason for failure */
auto numDevices = config_get_uint(config, "general", "cuda_devices");
if (!numDevices)
return std::make_pair(u"", QTStr("EncoderMissing.NVENC.NoDevices"));

/* Check device generation (architecture) */
for (uint64_t i = 0; i < numDevices; i++) {
std::string section = "device." + std::to_string(i);

if (!config_has_user_value(config, section.c_str(), "name"))
continue;

const QString name(config_get_string(config, section.c_str(), "name"));
const std::string_view arch = config_get_string(config, section.c_str(), "architecture_name");

if (arch == "Kepler")
return std::make_pair(u"kepler", QTStr("EncoderMissing.NVENC.Unsupported.Kepler").arg(name));
}

const std::string_view reason = config_get_string(config, "general", "reason");

/* All generic errors */
if (!reason.empty()) {
QString ret, crumb;

for (auto &[code, desc] : nvencFailureReasons) {
if (reason.find(code) == std::string_view::npos)
continue;

ret = QTStr("EncoderMissing.NVENC.TestProgramError").arg(QTStr(desc.data())).arg(code.data());
crumb = QString::fromUtf8(code.data());
break;
}

return std::make_pair(crumb, ret);
}

return std::nullopt;
}
#endif

void OBSBasic::CheckForAdvancedModeEncoders()
{
#ifndef __APPLE__
constexpr QStringView kbURL = u"https://obsproject.com/kb/encoder-missing#%1";
const std::string_view curStreamEncoder = config_get_string(activeConfiguration, "AdvOut", "Encoder");
const std::string_view curRecEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder");
std::unordered_set<std::string_view> available_encoders;

size_t idx = 0;
const char *id;
while (obs_enum_encoder_types(idx++, &id))
available_encoders.insert(id);

auto is_encoder_missing =
[&](const std::string_view &encoder) -> std::optional<std::pair<QStringView, QString>> {
if (available_encoders.count(encoder))
return std::nullopt;

if (encoder.find("nvenc") != std::string_view::npos) {
if (auto res = CheckNVENCVersion())
return res;
}

return std::make_pair(u"", QTStr("EncoderMissing.Unknown"));
};

auto streamEncError = is_encoder_missing(curStreamEncoder);
auto recEncError = curStreamEncoder == curRecEncoder ? std::nullopt : is_encoder_missing(curRecEncoder);

// Show warning message
if (streamEncError || recEncError) {
QString encoderMissingMessage = QTStr("EncoderMissing.TextBegin");
QStringView crumb;

if (streamEncError) {
encoderMissingMessage += QTStr("EncoderMissing.ListItem")
.arg(curStreamEncoder.data())
.arg(streamEncError->second);
crumb = streamEncError->first;
}
if (recEncError) {
encoderMissingMessage +=
QTStr("EncoderMissing.ListItem").arg(curRecEncoder.data()).arg(recEncError->second);
if (crumb.isEmpty())
crumb = recEncError->first;
}

encoderMissingMessage += QTStr("EncoderMissing.TextEnd").arg(kbURL.arg(crumb));

OBSMessageBox::warning(this, QTStr("EncoderMissing.Title"), encoderMissingMessage, true);
}
#endif
}
1 change: 1 addition & 0 deletions UI/window-basic-main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,7 @@ void OBSBasic::OBSInit()
InitBasicConfigDefaults2();

CheckForSimpleModeX264Fallback();
CheckForAdvancedModeEncoders();

LogEncoders();

Expand Down
1 change: 1 addition & 0 deletions UI/window-basic-main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,7 @@ public slots:
std::vector<std::string> GetRestartRequirements(const ConfigFile &config) const;
void ResetProfileData();
void CheckForSimpleModeX264Fallback();
void CheckForAdvancedModeEncoders();

public:
inline const OBSProfileCache &GetProfileCache() const noexcept { return profiles; };
Expand Down

0 comments on commit 75b14b1

Please sign in to comment.