From 7ed9fd2e47400c5e9242959582d4824e17ef416f Mon Sep 17 00:00:00 2001 From: Neal Gompa Date: Mon, 7 Jul 2025 22:41:59 -0400 Subject: [PATCH] frontend: Add support for OpenH264 as the worst-case fallback OpenH264 exists as the codec of last resort, so it is implemented such that it is only used as the software codec if x264 is not available. --- frontend/data/locale/en-US.ini | 1 + frontend/settings/OBSBasicSettings.cpp | 15 +++++- frontend/settings/OBSBasicSettings_Stream.cpp | 12 +++-- frontend/utility/SimpleOutput.cpp | 6 ++- frontend/widgets/OBSBasic.cpp | 13 ++++-- frontend/widgets/OBSBasic.hpp | 3 +- frontend/widgets/OBSBasic_Profiles.cpp | 31 +++++++------ frontend/wizards/AutoConfig.cpp | 15 +++++- frontend/wizards/AutoConfig.hpp | 3 ++ frontend/wizards/AutoConfigTestPage.cpp | 46 +++++++++++++------ 10 files changed, 105 insertions(+), 40 deletions(-) diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index da7c09be7a7cc2..18cf1409918dfe 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -1053,6 +1053,7 @@ Basic.Settings.Output.Simple.Warn.Lossless="Warning: Lossless quality generates Basic.Settings.Output.Simple.Warn.Lossless.Msg="Are you sure you want to use lossless quality?" Basic.Settings.Output.Simple.Warn.Lossless.Title="Lossless quality warning!" Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" +Basic.Settings.Output.Simple.Encoder.Software.OpenH264="Software (OpenH264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV.H264="Hardware (QSV, H.264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV.AV1="Hardware (QSV, AV1)" Basic.Settings.Output.Simple.Encoder.Hardware.AMD.H264="Hardware (AMD, H.264)" diff --git a/frontend/settings/OBSBasicSettings.cpp b/frontend/settings/OBSBasicSettings.cpp index 32e2d92debb6c2..e3c73463a1e7ce 100644 --- a/frontend/settings/OBSBasicSettings.cpp +++ b/frontend/settings/OBSBasicSettings.cpp @@ -3343,6 +3343,11 @@ void OBSBasicSettings::SaveOutputSettings() do. This only exists to make sure that the x264 preset doesn't get overwritten with empty data. */ presetType = "ApplePreset"; + else if (encoder == SIMPLE_ENCODER_OPENH264) + /* The OpenH264 encoder does not have presets like the other encoders + do. This only exists to make sure that the x264 preset doesn't + get overwritten with empty data. */ + presetType = "OpenH264Preset"; else presetType = "Preset"; @@ -4714,8 +4719,11 @@ void OBSBasicSettings::FillSimpleRecordingValues() ADD_QUALITY("HQ"); ADD_QUALITY("Lossless"); - ui->simpleOutRecEncoder->addItem(ENCODER_STR("Software"), QString(SIMPLE_ENCODER_X264)); - ui->simpleOutRecEncoder->addItem(ENCODER_STR("SoftwareLowCPU"), QString(SIMPLE_ENCODER_X264_LOWCPU)); + ui->simpleOutRecEncoder->addItem(ENCODER_STR("Software.OpenH264"), QString(SIMPLE_ENCODER_OPENH264)); + if (EncoderAvailable("obs_x264")) { + ui->simpleOutRecEncoder->addItem(ENCODER_STR("Software"), QString(SIMPLE_ENCODER_X264)); + ui->simpleOutRecEncoder->addItem(ENCODER_STR("SoftwareLowCPU"), QString(SIMPLE_ENCODER_X264_LOWCPU)); + } if (EncoderAvailable("obs_qsv11")) ui->simpleOutRecEncoder->addItem(ENCODER_STR("Hardware.QSV.H264"), QString(SIMPLE_ENCODER_QSV)); if (EncoderAvailable("obs_qsv11_av1")) @@ -4864,6 +4872,9 @@ void OBSBasicSettings::SimpleStreamingEncoderChanged() defaultPreset = "balanced"; preset = curAMDAV1Preset; + } else if (encoder == SIMPLE_ENCODER_OPENH264) { + ui->simpleOutPreset->setVisible(false); + ui->simpleOutPresetLabel->setVisible(false); } else { #define PRESET_STR(val) QString(Str("Basic.Settings.Output.EncoderPreset." val)).arg(val) diff --git a/frontend/settings/OBSBasicSettings_Stream.cpp b/frontend/settings/OBSBasicSettings_Stream.cpp index ce74a1cb1babf4..796ac745c817cf 100644 --- a/frontend/settings/OBSBasicSettings_Stream.cpp +++ b/frontend/settings/OBSBasicSettings_Stream.cpp @@ -1471,7 +1471,9 @@ static QString get_adv_fallback(const QString &enc) return "com.apple.videotoolbox.videoencoder.ave.avc"; if (enc == "obs_qsv11_av1") return "obs_qsv11"; - return "obs_x264"; + if (EncoderAvailable("obs_x264")) + return "obs_x264"; + return "ffmpeg_openh264"; } static QString get_adv_audio_fallback(const QString &enc) @@ -1500,7 +1502,9 @@ static QString get_simple_fallback(const QString &enc) return SIMPLE_ENCODER_APPLE_H264; if (enc == SIMPLE_ENCODER_QSV_AV1) return SIMPLE_ENCODER_QSV; - return SIMPLE_ENCODER_X264; + if (EncoderAvailable("obs_x264")) + return SIMPLE_ENCODER_X264; + return SIMPLE_ENCODER_OPENH264; } bool OBSBasicSettings::ServiceSupportsCodecCheck() @@ -1675,7 +1679,9 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) #define ENCODER_STR(str) QTStr("Basic.Settings.Output.Simple.Encoder." str) - ui->simpleOutStrEncoder->addItem(ENCODER_STR("Software"), QString(SIMPLE_ENCODER_X264)); + ui->simpleOutStrEncoder->addItem(ENCODER_STR("Software.OpenH264"), QString(SIMPLE_ENCODER_OPENH264)); + if (service_supports_encoder(vcodecs, "obs_x264")) + ui->simpleOutStrEncoder->addItem(ENCODER_STR("Software"), QString(SIMPLE_ENCODER_X264)); #ifdef _WIN32 if (service_supports_encoder(vcodecs, "obs_qsv11")) ui->simpleOutStrEncoder->addItem(ENCODER_STR("Hardware.QSV.H264"), QString(SIMPLE_ENCODER_QSV)); diff --git a/frontend/utility/SimpleOutput.cpp b/frontend/utility/SimpleOutput.cpp index b62846309c472f..95d1115ac6c1c6 100644 --- a/frontend/utility/SimpleOutput.cpp +++ b/frontend/utility/SimpleOutput.cpp @@ -80,7 +80,9 @@ void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) /* mistakes have been made to lead us to this. */ const char *get_simple_output_encoder(const char *encoder) { - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { + if (strcmp(encoder, SIMPLE_ENCODER_OPENH264) == 0) { + return "ffmpeg_openh264"; + } else if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { return "obs_x264"; } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { return "obs_x264"; @@ -112,7 +114,7 @@ const char *get_simple_output_encoder(const char *encoder) #endif } - return "obs_x264"; + return "ffmpeg_openh264"; } void SimpleOutput::LoadRecordingPreset() diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index e56b7e27791392..48586f001c93ac 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -519,6 +519,8 @@ static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), #define DEFAULT_CONTAINER "hybrid_mp4" #endif +extern bool EncoderAvailable(const char *encoder); + bool OBSBasic::InitBasicConfigDefaults() { QList screens = QGuiApplication::screens(); @@ -680,7 +682,9 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); + + bool useX264 = EncoderAvailable("obs_x264"); + config_set_default_string(activeConfiguration, "AdvOut", "Encoder", (useX264 ? "obs_x264" : "ffmpeg_openh264")); config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); @@ -796,10 +800,13 @@ void OBSBasic::InitBasicConfigDefaults2() bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; + bool useX264 = EncoderAvailable("obs_x264"); + const char *h264_fallback = (useX264 ? SIMPLE_ENCODER_X264 : SIMPLE_ENCODER_OPENH264); + config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); + useNV ? SIMPLE_ENCODER_NVENC : h264_fallback); config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); + useNV ? SIMPLE_ENCODER_NVENC : h264_fallback); const char *aac_default = "ffmpeg_aac"; if (EncoderAvailable("CoreAudio_AAC")) diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 48e9960fa2b091..fece7580b9dbac 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -75,6 +75,7 @@ struct Rect; #define SIMPLE_ENCODER_X264 "x264" #define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" +#define SIMPLE_ENCODER_OPENH264 "ffmpeg_openh264" #define SIMPLE_ENCODER_QSV "qsv" #define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" #define SIMPLE_ENCODER_NVENC "nvenc" @@ -885,7 +886,7 @@ private slots: void UpdateProfileEncoders(); std::vector GetRestartRequirements(const ConfigFile &config) const; void ResetProfileData(); - void CheckForSimpleModeX264Fallback(); + void CheckForSimpleModeH264Fallback(); public: inline const OBSProfileCache &GetProfileCache() const noexcept { return profiles; }; diff --git a/frontend/widgets/OBSBasic_Profiles.cpp b/frontend/widgets/OBSBasic_Profiles.cpp index 4c655915fe1edf..381a8662ed492b 100644 --- a/frontend/widgets/OBSBasic_Profiles.cpp +++ b/frontend/widgets/OBSBasic_Profiles.cpp @@ -734,7 +734,7 @@ void OBSBasic::ActivateProfile(const OBSProfile &profile, bool reset) void OBSBasic::UpdateProfileEncoders() { InitBasicConfigDefaults2(); - CheckForSimpleModeX264Fallback(); + CheckForSimpleModeH264Fallback(); } void OBSBasic::ResetProfileData() @@ -782,10 +782,11 @@ std::vector OBSBasic::GetRestartRequirements(const ConfigFile &conf return result; } -void OBSBasic::CheckForSimpleModeX264Fallback() +void OBSBasic::CheckForSimpleModeH264Fallback() { const char *curStreamEncoder = config_get_string(activeConfiguration, "SimpleOutput", "StreamEncoder"); const char *curRecEncoder = config_get_string(activeConfiguration, "SimpleOutput", "RecEncoder"); + bool x264_supported = false; bool qsv_supported = false; bool qsv_av1_supported = false; bool amd_supported = false; @@ -802,7 +803,9 @@ void OBSBasic::CheckForSimpleModeX264Fallback() const char *id; while (obs_enum_encoder_types(idx++, &id)) { - if (strcmp(id, "h264_texture_amf") == 0) + if (strcmp(id, "obs_x264") == 0) + x264_supported = true; + else if (strcmp(id, "h264_texture_amf") == 0) amd_supported = true; else if (strcmp(id, "obs_qsv11") == 0) qsv_supported = true; @@ -825,69 +828,71 @@ void OBSBasic::CheckForSimpleModeX264Fallback() apple_hevc_supported = true; #endif } + // Check to see whether x264 is available + const char *fallback_encoder_name = (x264_supported ? SIMPLE_ENCODER_X264 : SIMPLE_ENCODER_OPENH264); auto CheckEncoder = [&](const char *&name) { if (strcmp(name, SIMPLE_ENCODER_QSV) == 0) { if (!qsv_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_QSV_AV1) == 0) { if (!qsv_av1_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_NVENC) == 0) { if (!nve_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_NVENC_AV1) == 0) { if (!nve_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } #ifdef ENABLE_HEVC } else if (strcmp(name, SIMPLE_ENCODER_AMD_HEVC) == 0) { if (!amd_hevc_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_NVENC_HEVC) == 0) { if (!nve_hevc_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } #endif } else if (strcmp(name, SIMPLE_ENCODER_AMD) == 0) { if (!amd_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_AMD_AV1) == 0) { if (!amd_av1_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } } else if (strcmp(name, SIMPLE_ENCODER_APPLE_H264) == 0) { if (!apple_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } #ifdef ENABLE_HEVC } else if (strcmp(name, SIMPLE_ENCODER_APPLE_HEVC) == 0) { if (!apple_hevc_supported) { changed = true; - name = SIMPLE_ENCODER_X264; + name = fallback_encoder_name; return false; } #endif diff --git a/frontend/wizards/AutoConfig.cpp b/frontend/wizards/AutoConfig.cpp index 86e6ccea875cae..67f1497dce283a 100644 --- a/frontend/wizards/AutoConfig.cpp +++ b/frontend/wizards/AutoConfig.cpp @@ -176,6 +176,7 @@ AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) streamPage->ui->key->setText(key.c_str()); TestHardwareEncoding(); + TestSoftwareEncoding(); int bitrate = config_get_int(main->Config(), "SimpleOutput", "VBitrate"); bool multitrackVideoEnabled = config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo") @@ -210,6 +211,16 @@ AutoConfig::~AutoConfig() EnableThreadedMessageBoxes(false); } +void AutoConfig::TestSoftwareEncoding() +{ + size_t idx = 0; + const char *id; + while (obs_enum_encoder_types(idx++, &id)) { + if (strcmp(id, "obs_x264") == 0) + x264Available = true; + } +} + void AutoConfig::TestHardwareEncoding() { size_t idx = 0; @@ -285,8 +296,10 @@ inline const char *AutoConfig::GetEncoderId(Encoder enc) return SIMPLE_ENCODER_AMD; case Encoder::Apple: return SIMPLE_ENCODER_APPLE_H264; - default: + case Encoder::x264: return SIMPLE_ENCODER_X264; + default: + return SIMPLE_ENCODER_OPENH264; } }; diff --git a/frontend/wizards/AutoConfig.hpp b/frontend/wizards/AutoConfig.hpp index 92fa568f98ac5c..65cabb8f6774eb 100644 --- a/frontend/wizards/AutoConfig.hpp +++ b/frontend/wizards/AutoConfig.hpp @@ -27,6 +27,7 @@ class AutoConfig : public QWizard { }; enum class Encoder { + OpenH264, x264, NVENC, QSV, @@ -86,6 +87,7 @@ class AutoConfig : public QWizard { bool qsvAvailable = false; bool vceAvailable = false; bool appleAvailable = false; + bool x264Available = false; int startingBitrate = 2500; bool customServer = false; @@ -104,6 +106,7 @@ class AutoConfig : public QWizard { int specificFPSDen = 0; void TestHardwareEncoding(); + void TestSoftwareEncoding(); bool CanTestServer(const char *server); virtual void done(int result) override; diff --git a/frontend/wizards/AutoConfigTestPage.cpp b/frontend/wizards/AutoConfigTestPage.cpp index c6547ba1b14912..3de259a5e15ad0 100644 --- a/frontend/wizards/AutoConfigTestPage.cpp +++ b/frontend/wizards/AutoConfigTestPage.cpp @@ -119,7 +119,8 @@ void AutoConfigTestPage::TestBandwidthThread() const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr); + OBSEncoderAutoRelease vencoder = obs_video_encoder_create((wiz->x264Available ? "obs_x264" : "ffmpeg_openh264"), + "test_h264", nullptr, nullptr); OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr); OBSServiceAutoRelease service = obs_service_create(serverType, "test_service", nullptr, nullptr); @@ -151,10 +152,11 @@ void AutoConfigTestPage::TestBandwidthThread() obs_data_set_string(service_settings, "key", key.c_str()); obs_data_set_int(vencoder_settings, "bitrate", wiz->startingBitrate); - obs_data_set_string(vencoder_settings, "rate_control", "CBR"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); - obs_data_set_int(vencoder_settings, "keyint_sec", 2); - + if (wiz->x264Available) { + obs_data_set_string(vencoder_settings, "rate_control", "CBR"); + obs_data_set_string(vencoder_settings, "preset", "veryfast"); + obs_data_set_int(vencoder_settings, "keyint_sec", 2); + } obs_data_set_int(aencoder_settings, "bitrate", 32); OBSBasic *main = OBSBasic::Get(); @@ -482,7 +484,8 @@ bool AutoConfigTestPage::TestSoftwareEncoding() /* -----------------------------------*/ /* create obs objects */ - OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr); + OBSEncoderAutoRelease vencoder = obs_video_encoder_create((wiz->x264Available ? "obs_x264" : "ffmpeg_openh264"), + "test_h264", nullptr, nullptr); OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr); OBSOutputAutoRelease output = obs_output_create("null_output", "null", nullptr, nullptr); @@ -494,16 +497,20 @@ bool AutoConfigTestPage::TestSoftwareEncoding() obs_data_set_int(aencoder_settings, "bitrate", 32); if (wiz->type != AutoConfig::Type::Recording) { - obs_data_set_int(vencoder_settings, "keyint_sec", 2); + if (wiz->x264Available) { + obs_data_set_int(vencoder_settings, "keyint_sec", 2); + obs_data_set_string(vencoder_settings, "rate_control", "CBR"); + obs_data_set_string(vencoder_settings, "preset", "veryfast"); + } obs_data_set_int(vencoder_settings, "bitrate", wiz->idealBitrate); - obs_data_set_string(vencoder_settings, "rate_control", "CBR"); obs_data_set_string(vencoder_settings, "profile", "main"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); } else { - obs_data_set_int(vencoder_settings, "crf", 20); - obs_data_set_string(vencoder_settings, "rate_control", "CRF"); + if (wiz->x264Available) { + obs_data_set_int(vencoder_settings, "crf", 20); + obs_data_set_string(vencoder_settings, "rate_control", "CRF"); + obs_data_set_string(vencoder_settings, "preset", "veryfast"); + } obs_data_set_string(vencoder_settings, "profile", "high"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); } /* -----------------------------------*/ @@ -850,7 +857,10 @@ void AutoConfigTestPage::TestStreamEncoderThread() else wiz->streamingEncoder = AutoConfig::Encoder::AMD; } else { - wiz->streamingEncoder = AutoConfig::Encoder::x264; + if (wiz->x264Available) + wiz->streamingEncoder = AutoConfig::Encoder::x264; + else + wiz->streamingEncoder = AutoConfig::Encoder::OpenH264; } #ifdef __linux__ @@ -894,7 +904,10 @@ void AutoConfigTestPage::TestRecordingEncoderThread() else wiz->recordingEncoder = AutoConfig::Encoder::AMD; } else { - wiz->recordingEncoder = AutoConfig::Encoder::x264; + if (wiz->x264Available) + wiz->streamingEncoder = AutoConfig::Encoder::x264; + else + wiz->streamingEncoder = AutoConfig::Encoder::OpenH264; } if (wiz->recordingEncoder != AutoConfig::Encoder::NVENC) { @@ -909,6 +922,7 @@ void AutoConfigTestPage::TestRecordingEncoderThread() #define ENCODER_TEXT(x) "Basic.Settings.Output.Simple.Encoder." x #define ENCODER_SOFTWARE ENCODER_TEXT("Software") +#define ENCODER_OPENH264 ENCODER_TEXT("Software.OpenH264") #define ENCODER_NVENC ENCODER_TEXT("Hardware.NVENC.H264") #define ENCODER_QSV ENCODER_TEXT("Hardware.QSV.H264") #define ENCODER_AMD ENCODER_TEXT("Hardware.AMD.H264") @@ -946,6 +960,8 @@ void AutoConfigTestPage::FinalizeResults() auto encName = [](AutoConfig::Encoder enc) -> QString { switch (enc) { + case AutoConfig::Encoder::OpenH264: + return QTStr(ENCODER_OPENH264); case AutoConfig::Encoder::x264: return QTStr(ENCODER_SOFTWARE); case AutoConfig::Encoder::NVENC: @@ -960,7 +976,7 @@ void AutoConfigTestPage::FinalizeResults() return QTStr(QUALITY_SAME); } - return QTStr(ENCODER_SOFTWARE); + return QTStr(ENCODER_OPENH264); }; auto newLabel = [this](const char *str) -> QLabel * {