From 0223b4c4e0f1a9673916fd386fc5aa179d99cf15 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Tue, 17 Feb 2026 11:15:48 -0600 Subject: [PATCH 01/12] Consolidate encoder processing Move encoder processing to its own file so ensure all source files are using the same encoders/settings/logic. --- obs-studio-server/source/nodeobs_api.cpp | 2 +- .../source/nodeobs_autoconfig.cpp | 1 + .../source/nodeobs_configManager.cpp | 1 + obs-studio-server/source/nodeobs_service.cpp | 132 ++--- obs-studio-server/source/nodeobs_service.h | 44 +- obs-studio-server/source/nodeobs_settings.cpp | 490 +++--------------- .../source/osn-advanced-recording.cpp | 27 +- .../source/osn-advanced-recording.hpp | 1 + .../source/osn-advanced-streaming.cpp | 47 +- .../source/osn-advanced-streaming.hpp | 1 + obs-studio-server/source/osn-encoders.cpp | 453 ++++++++++++++++ obs-studio-server/source/osn-encoders.hpp | 210 ++++++++ obs-studio-server/source/osn-recording.cpp | 11 + obs-studio-server/source/osn-recording.hpp | 2 + .../source/osn-simple-recording.cpp | 110 ++-- .../source/osn-simple-recording.hpp | 4 +- .../source/osn-simple-streaming.cpp | 93 ++-- .../source/osn-simple-streaming.hpp | 4 +- obs-studio-server/source/osn-streaming.cpp | 6 + obs-studio-server/source/osn-streaming.hpp | 2 + .../source/osn-video-encoder.cpp | 15 +- .../source/osn-video-encoder.hpp | 2 +- 22 files changed, 964 insertions(+), 694 deletions(-) create mode 100644 obs-studio-server/source/osn-encoders.cpp create mode 100644 obs-studio-server/source/osn-encoders.hpp diff --git a/obs-studio-server/source/nodeobs_api.cpp b/obs-studio-server/source/nodeobs_api.cpp index 34fffa7e9..f7e1d9007 100644 --- a/obs-studio-server/source/nodeobs_api.cpp +++ b/obs-studio-server/source/nodeobs_api.cpp @@ -1008,7 +1008,7 @@ void OBS_API::OBS_API_initAPI(void *data, const int64_t id, const std::vector #include "osn-error.hpp" #include "shared.hpp" +#include "osn-encoders.hpp" enum class Type { Invalid, Streaming, Recording }; diff --git a/obs-studio-server/source/nodeobs_configManager.cpp b/obs-studio-server/source/nodeobs_configManager.cpp index ad855d6d4..61c0594f3 100644 --- a/obs-studio-server/source/nodeobs_configManager.cpp +++ b/obs-studio-server/source/nodeobs_configManager.cpp @@ -26,6 +26,7 @@ #include #include "shared.hpp" #include "nodeobs_service.h" +#include "osn-encoders.hpp" void ConfigManager::setAppdataPath(const std::string &path) { diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index cbab155e0..ee8ff85d5 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -27,6 +27,7 @@ #include "utility.hpp" #include #include "osn-vcam.hpp" +#include "osn-encoders.hpp" #include "osn-multitrack-video-configuration.hpp" #include "osn-audio-bitrate.hpp" @@ -827,7 +828,8 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) encoderId = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } - if (encoderId == NULL || !EncoderAvailable(encoderId) || isInvalidEncoder(encoderId)) { + //TODO - does this cause issues because settings won't match? + if (encoderId == NULL || !osn::EncoderUtils::isEncoderRegistered(encoderId) || osn::EncoderUtils::isInvalidAppleEncoder(encoderId)) { encoderId = ADVANCED_ENCODER_X264; } @@ -840,6 +842,7 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) obs_data_t *settings = obs_encoder_defaults(encoderId); obs_data_apply(settings, data); + blog(LOG_INFO, "MLH create encoder type %s", encoderId); obs_encoder_t *new_encoder = obs_video_encoder_create(encoderId, encoder_name.c_str(), settings, nullptr); OBS_service::setStreamingEncoder(new_encoder, serviceId); @@ -1029,7 +1032,7 @@ static void remove_reserved_file_characters(std::string &s) replace(s.begin(), s.end(), '<', '_'); } -bool OBS_service::createVideoRecordingEncoder() +bool OBS_service::createDefaultSimpleVideoRecordingEncoder() { std::string encoderName = GetVideoEncoderName(StreamServiceId::Main, true, true, ADVANCED_ENCODER_X264); obs_encoder_t *newRecordingEncoder = obs_video_encoder_create(ADVANCED_ENCODER_X264, encoderName.c_str(), nullptr, nullptr); @@ -1549,46 +1552,15 @@ void OBS_service::LoadRecordingPreset_Lossy(const char *encoderId) throw "Failed to create video recording encoder (simple output)"; } -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return ADVANCED_ENCODER_X264; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return ADVANCED_ENCODER_X264; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return ADVANCED_ENCODER_AMD; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return ADVANCED_ENCODER_AMD_HEVC; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if ((strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) || (strcmp(encoder, ENCODER_NVENC_H264_TEX) == 0)) { - return EncoderAvailable(ENCODER_NVENC_H264_TEX) ? ENCODER_NVENC_H264_TEX : ADVANCED_ENCODER_NVENC; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable(ENCODER_NVENC_HEVC_TEX) ? ENCODER_NVENC_HEVC_TEX : "ffmpeg_hevc_nvenc"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return ENCODER_NVENC_AV1_TEX; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return APPLE_HARDWARE_VIDEO_ENCODER_M1; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; - } - - blog(LOG_WARNING, "get_simple_output_encoder - encoder %s is not found, creating a default one", encoder); - - return ADVANCED_ENCODER_X264; -} - void OBS_service::updateVideoRecordingEncoder(bool isSimpleMode) { if (isRecording && rpUsesRec) return; + const char *section = isSimpleMode ? "SimpleOutput" : "AdvOut"; + const char *quality = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); + const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), section, "RecEncoder"); videoEncoder = encoder; videoQuality = quality; @@ -1598,12 +1570,11 @@ void OBS_service::updateVideoRecordingEncoder(bool isSimpleMode) lowCPUx264 = false; if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); + LoadRecordingPreset_Lossy((osn::EncoderUtils::getInternalEncoderFromSimple(encoder)).c_str()); usingRecordingPreset = true; updateVideoRecordingEncoderSettings(); } else { - const char *recordingEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); - if (recordingEncoder && strcmp(recordingEncoder, ENCODER_NVENC_H264_TEX) != 0) { + if (encoder && strcmp(encoder, ENCODER_NVENC_H264_TEX) != 0) { unsigned int cx = 0; unsigned int cy = 0; @@ -2036,39 +2007,25 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId bool enforceBitrate = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "EnforceBitrate"); const char *custom = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings"); const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); - const char *encoderID = nullptr; - const char *presetType = nullptr; + std::string encoderID = ""; + std::string presetType = ""; const char *preset = nullptr; if (encoder != NULL) { - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0 || strcmp(encoder, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encoderID = ADVANCED_ENCODER_QSV; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0 || strcmp(encoder, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - UpdateStreamingSettings_amd(h264Settings, videoBitrate); - encoderID = ADVANCED_ENCODER_AMD; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encoder, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encoderID = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encoder, ENCODER_NVENC_H264_TEX) == 0) { - presetType = "NVENCPreset"; - encoderID = ENCODER_NVENC_H264_TEX; - } else if (strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER) == 0) { - encoderID = APPLE_HARDWARE_VIDEO_ENCODER; - } else if (strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - encoderID = APPLE_HARDWARE_VIDEO_ENCODER_M1; - } else { - presetType = "Preset"; - encoderID = ADVANCED_ENCODER_X264; - } - if (presetType) - preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType); + std::string presetType = osn::EncoderUtils::getEncoderPreset(encoder); + //TODO do we need to check low CPU here before we convert? + encoderID = osn::EncoderUtils::getInternalEncoderFromSimple(encoder); + + //TODO - do we need to handle PresetNvenc/2 here? + + if (!presetType.empty()) + preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); // Here and in other places we repeat the same pattern. // Avoiding case when to an output there might not be any attached video encoder which can lead to crash. - std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID); - obs_encoder_t *streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), nullptr, nullptr); + blog(LOG_INFO, "MLH create encoder type %s", encoderID.c_str()); + std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID.c_str()); + obs_encoder_t *streamingEncoder = obs_video_encoder_create(encoderID.c_str(), encoder_name.c_str(), nullptr, nullptr); setStreamingEncoder(streamingEncoder, serviceId); } @@ -2109,8 +2066,7 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId obs_encoder_set_preferred_video_format(videoStreamingEncoder[serviceId], VIDEO_FORMAT_NV12); } - if (strcmp(encoder, APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { + if (osn::EncoderUtils::getEncoderFamily(encoderID.c_str()) == FAMILY_APPLE) { const char *profile = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "Profile"); if (profile) obs_data_set_string(h264Settings, "profile", profile); @@ -2575,29 +2531,22 @@ void OBS_service::updateVideoRecordingEncoderSettings() { bool ultra_hq = (videoQuality == "HQ"); int crf = CalcCRF(ultra_hq ? 16 : 23); + std::string encFamily = osn::EncoderUtils::getEncoderFamily(videoEncoder.c_str()); - if (videoEncoder.compare(SIMPLE_ENCODER_X264) == 0 || videoEncoder.compare(ADVANCED_ENCODER_X264) == 0 || - videoEncoder.compare(SIMPLE_ENCODER_X264_LOWCPU) == 0) { + if (encFamily == FAMILY_OBS) UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_QSV) == 0 || videoEncoder.compare(ADVANCED_ENCODER_QSV) == 0) { - UpdateRecordingSettings_qsv11(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_AMD) == 0 || videoEncoder.compare(SIMPLE_ENCODER_AMD_HEVC) == 0 || - videoEncoder.compare(ADVANCED_ENCODER_AMD) == 0) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_NVENC) == 0 || videoEncoder.compare(ADVANCED_ENCODER_NVENC) == 0) { - UpdateRecordingSettings_nvenc(crf); - } else if (videoEncoder.compare(ENCODER_NVENC_H264_TEX) == 0) { + else if (encFamily == FAMILY_NVENC) UpdateRecordingSettings_nvenc(crf); - } else if (videoEncoder.compare(SIMPLE_ENCODER_NVENC_HEVC) == 0) { + else if (encFamily == FAMILY_NVENC_HEVC) UpdateRecordingSettings_nvenc_hevc(crf); - } else if (videoEncoder.compare(APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || videoEncoder.compare(APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - videoEncoder.compare(APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - /* These are magic numbers. 0 - 100, more is better. */ + else if (encFamily == FAMILY_QSV) + UpdateRecordingSettings_qsv11(crf); + else if (encFamily == FAMILY_AMD) + UpdateRecordingSettings_amd_cqp(crf); + else if (encFamily == FAMILY_APPLE) UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); - } + else + blog(LOG_WARNING, "Unable to update settings with unknown encoder family."); } obs_encoder_t *OBS_service::getStreamingEncoder(StreamServiceId serviceId) @@ -3351,17 +3300,6 @@ void OBS_service::setupVodTrack(bool isSimpleMode) } } } - -bool OBS_service::isInvalidEncoder(const char *encoderID) -{ -#if defined(__APPLE__) - // disable this encoder; not functioning properly - return strcmp(encoderID, APPLE_SOFTWARE_VIDEO_ENCODER) == 0; -#else - return false; -#endif -} - std::string GetFormatExt(const std::string container) { std::string ext = container; diff --git a/obs-studio-server/source/nodeobs_service.h b/obs-studio-server/source/nodeobs_service.h index f29dc974b..adcb1fe88 100644 --- a/obs-studio-server/source/nodeobs_service.h +++ b/obs-studio-server/source/nodeobs_service.h @@ -44,48 +44,7 @@ #include #endif -#ifdef WIN32 -#define SIMPLE_ENCODER_X264 "x264" -#elif __APPLE__ -#define SIMPLE_ENCODER_X264 "obs_x264" -#endif -#define SIMPLE_ENCODER_X264 "x264" -#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" -#define SIMPLE_ENCODER_QSV "qsv" -#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" -#define SIMPLE_ENCODER_NVENC "nvenc" -#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" -#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" -#define SIMPLE_ENCODER_AMD "amd" -#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" -#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" -#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" -#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" - -#define ADVANCED_ENCODER_X264 "obs_x264" -#define ADVANCED_ENCODER_QSV "obs_qsv11" -#define ADVANCED_ENCODER_NVENC "ffmpeg_nvenc" -#define ADVANCED_ENCODER_AMD "h264_texture_amf" -#define ADVANCED_ENCODER_AMD_HEVC "h265_texture_amf" - -#define ENCODER_NVENC_H264_TEX "obs_nvenc_h264_tex" -#define ENCODER_NVENC_HEVC_TEX "obs_nvenc_hevc_tex" -#define ENCODER_NVENC_AV1_TEX "obs_nvenc_av1_tex" - -// These 3 are deprecated -#define ENCODER_JIM_NVENC "jim_nvenc" -#define ENCODER_JIM_HEVC_NVENC "jim_hevc_nvenc" -#define ENCODER_JIM_AV1_NVENC "jim_av1_nvenc" - -#define ENCODER_AV1_SVT_FFMPEG "ffmpeg_svt_av1" -#define ENCODER_AV1_AOM_FFMPEG "ffmpeg_aom_av1" - -#define APPLE_SOFTWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264" -#define APPLE_HARDWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264.gva" -#define APPLE_HARDWARE_VIDEO_ENCODER_M1 "com.apple.videotoolbox.videoencoder.ave.avc" - #define ARCHIVE_NAME "archive_aac" - #define SIMPLE_AUDIO_ENCODER_AAC "ffmpeg_aac" #define SIMPLE_AUDIO_ENCODER_OPUS "ffmpeg_opus" @@ -211,7 +170,7 @@ class OBS_service { static bool createVideoStreamingEncoder(StreamServiceId serviceId); static std::string GetVideoEncoderName(StreamServiceId serviceId, bool isSimpleMode, bool recording, const char *encoder); static void createAudioStreamingEncoder(StreamServiceId serviceId, bool isSimpleMode, const std::string &encoder_id); - static bool createVideoRecordingEncoder(); + static bool createDefaultSimpleVideoRecordingEncoder(); static obs_encoder_t *getStreamingEncoder(StreamServiceId serviceId); static void setStreamingEncoder(obs_encoder_t *encoder, StreamServiceId serviceId); static obs_encoder_t *getRecordingEncoder(void); @@ -293,5 +252,4 @@ class OBS_service { static void stopAllOutputs(void); static void setupVodTrack(bool isSimpleMode); static void clearArchiveVodEncoder(); - static bool isInvalidEncoder(const char *encoderID); }; diff --git a/obs-studio-server/source/nodeobs_settings.cpp b/obs-studio-server/source/nodeobs_settings.cpp index f1928f9f0..23a0ff6bc 100644 --- a/obs-studio-server/source/nodeobs_settings.cpp +++ b/obs-studio-server/source/nodeobs_settings.cpp @@ -22,6 +22,7 @@ #include "shared.hpp" #include "memory-manager.h" #include "osn-video.hpp" +#include "osn-encoders.hpp" #ifdef WIN32 #include @@ -45,9 +46,6 @@ std::vector tabStreamTypes; const char *currentServiceName; std::vector currentAudioSettings; -bool update_nvenc_presets(obs_data_t *data, const char *encoderId); -const char *convert_nvenc_simple_preset(const char *old_preset); - /* some nice default output resolution vals */ static const double vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0}; @@ -293,7 +291,6 @@ SubCategory OBS_settings::serializeSettingsData(const std::string &nameSubCatego config_t *config, const std::string §ion, bool isVisible, bool isEnabled) { SubCategory sc; - for (int i = 0; i < entries.size(); i++) { Parameter param; @@ -328,13 +325,14 @@ SubCategory OBS_settings::serializeSettingsData(const std::string &nameSubCatego currentValue = config_get_string(config, "AdvVideo", param.name.c_str()); } } else if (section.compare("SimpleOutput") == 0) { - if (param.name.compare("NVENCPreset2") == 0) { + if (param.name.compare(PRESET_NVENC) == 0) { currentValue = config_get_string(config, "SimpleOutput", param.name.c_str()); + blog(LOG_INFO, "MLH serializeSettingsData: currentValue %s", currentValue); if (currentValue == NULL) { - const char *oldParamName = "NVENCPreset"; + const char *oldParamName = PRESET_NVENC_DEP; const char *oldValue = config_get_string(config, "SimpleOutput", oldParamName); if (oldValue != NULL) { - currentValue = convert_nvenc_simple_preset(oldValue); + currentValue = osn::EncoderUtils::convertNvencSimplePreset(oldValue); blog(LOG_INFO, "NVENC presets converted from %s to %s", oldValue, currentValue); } } @@ -947,213 +945,35 @@ bool OBS_settings::saveStreamSettings(std::vector streamSettings, S return true; } -bool EncoderAvailable(const std::string &encoder) -{ - const char *val; - int i = 0; - - while (obs_enum_encoder_types(i++, &val)) { - if (val == nullptr) - continue; - if (std::string(val) == encoder) - return true; - } - - return false; -} - -static bool isEncoderAvailableForStreaming(const char *encoder, obs_service_t *service) -{ - if (!encoder || !service) - return false; - - auto supportedCodecs = obs_service_get_supported_video_codecs(service); - auto encoderCodec = obs_get_encoder_codec(encoder); - - if (!supportedCodecs || !encoderCodec) - return false; - - while (*supportedCodecs) { - if (strcmp(*supportedCodecs, encoderCodec) == 0) - return true; - supportedCodecs++; - } - - return false; -} - -// Codect/Container support check. -// from OBS code UI\window-basic-settings.cpp -static const std::unordered_map> codec_compat = { - // Technically our muxer supports HEVC and AV1 as well, but nothing else does - {"flv", {"h264", "aac"}}, - {"mpegts", {"h264", "hevc", "aac", "opus"}}, - {"hls", {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support - {"mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, - {"mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, - {"fragmented_mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, - {"fragmented_mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, - // MKV supports everything - {"mkv", {}}, -}; - -static bool ContainerSupportsCodec(const std::string &container, const std::string &codec) -{ - auto iter = codec_compat.find(container); - if (iter == codec_compat.end()) - return false; - - auto codecs = iter->second; - // Assume everything is supported - if (codecs.empty()) - return true; - return codecs.count(codec) > 0; -} - -static bool isNvencAvailableForSimpleMode() -{ - // Only available if config already uses it - const char *current_stream_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); - const char *current_rec_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); - bool nvenc_used_streaming = (current_stream_encoder && strcmp(current_stream_encoder, SIMPLE_ENCODER_NVENC) == 0); - bool nvenc_used_recording = (current_rec_encoder && strcmp(current_rec_encoder, SIMPLE_ENCODER_NVENC) == 0); - - return (nvenc_used_streaming || nvenc_used_recording) && EncoderAvailable(ADVANCED_ENCODER_NVENC); -} void OBS_settings::getAvailableAudioEncoders(std::vector> *encoders, bool simple, bool recording, const std::string &container) { - if (EncoderAvailable(SIMPLE_AUDIO_ENCODER_AAC)) + if (osn::EncoderUtils::isEncoderRegistered(SIMPLE_AUDIO_ENCODER_AAC)) encoders->push_back(std::make_pair("AAC (Default)", ipc::value(SIMPLE_AUDIO_ENCODER_AAC))); - if (recording && EncoderAvailable(SIMPLE_AUDIO_ENCODER_OPUS)) + if (recording && osn::EncoderUtils::isEncoderRegistered(SIMPLE_AUDIO_ENCODER_OPUS)) encoders->push_back(std::make_pair("Opus", ipc::value(SIMPLE_AUDIO_ENCODER_OPUS))); } -class EncoderSettings { -public: - std::string advanced_title; - std::string advanced_name; - std::string simple_title; - std::string simple_name; - std::string simple_intenal_name; - bool recording; - bool streaming; - bool check_availability; - bool check_availability_streaming; - bool check_availability_format; - bool only_for_reuse_simple; - const std::string getSimpleName() const { return simple_intenal_name.empty() ? simple_name : simple_intenal_name; } -}; - -std::vector encoders_set = { - // Software x264 - {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, true, true, false, false, true, false}, - // Software x264 low CPU (only for recording) - {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, true, false, false, false, - true, false}, - // QuickSync H.264 (v1, deprecated) - // This line left here for reference - // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, - // QuickSync H.264 (v2, new) - {"QuickSync H.264", "obs_qsv11_v2", "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, "obs_qsv11_v2", true, true, true, false, true, false}, - // QuickSync AV1 - {"QuickSync AV1", "obs_qsv11_av1", "", "", "", true, true, true, false, true, false}, - // QuickSync HEVC - {"QuickSync HEVC", "obs_qsv11_hevc", "", "", "", true, true, true, false, true, false}, - // NVIDIA NVENC H.264 - {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ADVANCED_ENCODER_NVENC, true, true, true, false, true, true}, - // NVIDIA NVENC H.264 (new) - {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", true, true, true, false, true, false}, - // NVIDIA NVENC HEVC - {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, true, true, true, true, true, - false}, - // NVIDIA NVENC AV1 - {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "Hardware (NVENC, AV1)", ENCODER_NVENC_AV1_TEX, "", true, true, true, true, true, false}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", true, true, true, false, - true, false}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, - false, true, false}, - // AMD HW H.264 - {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, true, true, true, false, true, false}, - // AMD HW H.265 (HEVC) - {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, true, true, true, true, - true, false}, - // AMD HW AV1 - {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", "av1", SIMPLE_ENCODER_AMD_AV1, true, true, true, true, true, false}, - // AOM AV1 - {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", true, true, true, false, true, false}, - // SVT-AV1 - {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", true, true, true, false, true, false}}; - void OBS_settings::getSimpleAvailableEncoders(std::vector> *list, bool recording, const std::string &container) { - for (const auto &encoderSetting : encoders_set) { - if (encoderSetting.simple_name.empty()) - continue; - - if (!recording && !encoderSetting.streaming) - continue; - - if (recording && !encoderSetting.recording) - continue; - - if (encoderSetting.check_availability && !EncoderAvailable(encoderSetting.getSimpleName())) - continue; - - if (!recording && encoderSetting.check_availability_streaming && - !isEncoderAvailableForStreaming(encoderSetting.getSimpleName().c_str(), OBS_service::getService(StreamServiceId::Main))) - continue; - - if (encoderSetting.only_for_reuse_simple && !isNvencAvailableForSimpleMode()) - continue; - - if (recording && encoderSetting.check_availability_format) { - const char *codec = obs_get_encoder_codec(encoderSetting.getSimpleName().c_str()); - if (!codec) { - blog(LOG_DEBUG, "[ENCODER_SKIPPED] codec is null"); - continue; - } - if (!ContainerSupportsCodec(container, codec)) - continue; + for (int i = 0; i < osn::EncoderUtils::videoEncoderOptions.size(); i++) { + if (osn::EncoderUtils::isEncoderCompatible(osn::EncoderUtils::videoEncoderOptions[i].getSimpleName(), + OBS_service::getService(StreamServiceId::Main), true, recording, container, i)) { + list->push_back(std::make_pair(osn::EncoderUtils::videoEncoderOptions[i].simple_title, + ipc::value(osn::EncoderUtils::videoEncoderOptions[i].simple_name))); } - - list->push_back(std::make_pair(encoderSetting.simple_title, ipc::value(encoderSetting.simple_name))); } } void OBS_settings::getAdvancedAvailableEncoders(std::vector> *list, bool recording, const std::string &container) { - for (const auto &encoderSetting : encoders_set) { - if (encoderSetting.advanced_name.empty()) - continue; - - if (!recording && !encoderSetting.streaming) - continue; - - if (recording && !encoderSetting.recording) - continue; - - if (encoderSetting.check_availability && !EncoderAvailable(encoderSetting.advanced_name)) - continue; - - if (!recording && encoderSetting.check_availability_streaming && - !isEncoderAvailableForStreaming(encoderSetting.advanced_name.c_str(), OBS_service::getService(StreamServiceId::Main))) - continue; - - if (recording && encoderSetting.check_availability_format) { - const char *codec = obs_get_encoder_codec(encoderSetting.advanced_name.c_str()); - if (!codec) { - blog(LOG_WARNING, "[SUPPORTED_CODECS] codec is null for %s", encoderSetting.advanced_name.c_str()); - continue; - } - if (!ContainerSupportsCodec(container, codec)) - continue; + for (int i = 0; i < osn::EncoderUtils::videoEncoderOptions.size(); i++) { + if (osn::EncoderUtils::isEncoderCompatible(osn::EncoderUtils::videoEncoderOptions[i].advanced_name, + OBS_service::getService(StreamServiceId::Main), false, recording, container, i)) { + list->push_back(std::make_pair(osn::EncoderUtils::videoEncoderOptions[i].advanced_title, + ipc::value(osn::EncoderUtils::videoEncoderOptions[i].advanced_name))); } - - list->push_back(std::make_pair(encoderSetting.advanced_title, ipc::value(encoderSetting.advanced_name))); } } @@ -1173,59 +993,12 @@ static const char *translate_macvth264_encoder(std::string encoder) } #endif -static bool isOldJimNvencEncoder(const std::string &encoderId) -{ - return encoderId == ENCODER_JIM_NVENC || encoderId == ENCODER_JIM_HEVC_NVENC || encoderId == ENCODER_JIM_AV1_NVENC; -} - -// This code should be removed when JIM_ encoders will be removed from OBS -static void converOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, - const std::string &recordingEncoderSetting) -{ - const std::string streamEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), streamEncoderSetting.c_str())); - if (isOldJimNvencEncoder(streamEncoder)) { - blog(LOG_INFO, "Converting stream encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), streamEncoder.c_str(), - ENCODER_NVENC_H264_TEX); - config_set_string(config, configSection.c_str(), streamEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); - } - - const std::string recordingEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), recordingEncoderSetting.c_str())); - if (isOldJimNvencEncoder(recordingEncoder)) { - blog(LOG_INFO, "Converting recording encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), recordingEncoder.c_str(), - ENCODER_NVENC_H264_TEX); - config_set_string(config, configSection.c_str(), recordingEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); - } -} - -static bool validateEncoderForService(StreamServiceId serviceId, const char *encoderToFind) -{ - bool validEncoder = false; - - //have encoder - find in encoders_set, validate 'streaming' flag and check availability based on 'check_availability_streaming' flag - for (int i = 0; i < encoders_set.size(); i++) { - if (std::string(encoderToFind) == encoders_set[i].simple_name || std::string(encoderToFind) == encoders_set[i].advanced_name) { - if (encoders_set[i].streaming) { - if (encoders_set[i].check_availability_streaming) { - if (isEncoderAvailableForStreaming(encoderToFind, OBS_service::getService(serviceId))) { - validEncoder = true; - break; - } - } else { - validEncoder = true; - } - } - break; - } - } - - return validEncoder; -} - void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { const char *mode = NULL; const char *curEncoder = NULL; bool validEncoder = false; + bool simpleMode = false; std::string serviceToCheck = args[0].value_str; //get mode and configured encoder @@ -1233,18 +1006,21 @@ void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, con if (mode == NULL) { mode = "Simple"; } - if (strcmp(mode, "Advanced") == 0) { + simpleMode = (strcmp(mode, "Simple") == 0); + + if (!simpleMode) { curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } else { curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamingEncoder"); } if (serviceToCheck == "Both") { - validEncoder = validateEncoderForService(StreamServiceId::Main, curEncoder) && validateEncoderForService(StreamServiceId::Second, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Main), curEncoder, simpleMode) && + osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Second), curEncoder, simpleMode); } else if (serviceToCheck == "Stream") { - validEncoder = validateEncoderForService(StreamServiceId::Main, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Main), curEncoder, simpleMode); } else if (serviceToCheck == "StreamSecond") { - validEncoder = validateEncoderForService(StreamServiceId::Second, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Second), curEncoder, simpleMode); } rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); @@ -1253,7 +1029,7 @@ void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, con void OBS_settings::getSimpleOutputSettings(std::vector *outputSettings, config_t *config, bool isCategoryEnabled) { - converOldJimNvencEncoder(config, "SimpleOutput", "StreamEncoder", "RecEncoder"); + osn::EncoderUtils::convertOldJimNvencEncoder(config, "SimpleOutput", "StreamEncoder", "RecEncoder"); std::vector>> entries; @@ -1301,17 +1077,18 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti std::vector> preset; - if (encoder == SIMPLE_ENCODER_QSV || encoder == ADVANCED_ENCODER_QSV) { - preset = createSettingEntry("QSVPreset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + std::string presetName = osn::EncoderUtils::getEncoderPreset(encoder.c_str()); + if (presetName == PRESET_QSV) { + preset = createSettingEntry(PRESET_QSV, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"Speed", ipc::value("speed")}); preset.push_back({"Balanced", ipc::value("balanced")}); preset.push_back({"Quality", ipc::value("quality")}); entries.push_back(preset); defaultPreset = "balanced"; - - } else if (encoder == SIMPLE_ENCODER_NVENC || encoder == ADVANCED_ENCODER_NVENC || encoder == ENCODER_NVENC_H264_TEX || - encoder == ENCODER_NVENC_HEVC_TEX) { - preset = createSettingEntry("NVENCPreset2", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == PRESET_NVENC) { + blog(LOG_INFO, "MLH: NvencPreset2: getting NVENC presets from OBS for encoder %s", encoder.c_str()); + preset = createSettingEntry(PRESET_NVENC, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); obs_properties_t *props = obs_get_encoder_properties(ADVANCED_ENCODER_NVENC); @@ -1320,7 +1097,7 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti for (size_t i = 0; i < num; i++) { const char *name = obs_property_list_item_name(p, i); const char *val = obs_property_list_item_string(p, i); - + blog(LOG_INFO, "MLH adding entry to NvencPreset2: name=%s, value=%s", name, val); preset.push_back(std::make_pair(name, ipc::value(val))); } @@ -1328,22 +1105,25 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti defaultPreset = "p5"; entries.push_back(preset); - } else if (encoder == SIMPLE_ENCODER_AMD || encoder == ADVANCED_ENCODER_AMD) { - preset = createSettingEntry("AMDPreset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == PRESET_AMD) { + preset = createSettingEntry(PRESET_AMD, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"Speed", ipc::value("speed")}); preset.push_back({"Balanced", ipc::value("balanced")}); preset.push_back({"Quality", ipc::value("quality")}); entries.push_back(preset); defaultPreset = "balanced"; - } else if (encoder == APPLE_SOFTWARE_VIDEO_ENCODER || encoder == APPLE_HARDWARE_VIDEO_ENCODER || encoder == APPLE_HARDWARE_VIDEO_ENCODER_M1) { - preset = createSettingEntry("Profile", "OBS_PROPERTY_LIST", "", "OBS_COMBO_FORMAT_STRING"); + } else if (presetName == PRESET_APPLE) { + preset = createSettingEntry(PRESET_APPLE, "OBS_PROPERTY_LIST", "", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"(None)", ipc::value("")}); preset.push_back({"baseline", ipc::value("baseline")}); preset.push_back({"main", ipc::value("main")}); preset.push_back({"high", ipc::value("high")}); entries.push_back(preset); - } else { - preset = createSettingEntry("Preset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == DEFAULT_PRESET) + { + preset = createSettingEntry(DEFAULT_PRESET, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"ultrafast", ipc::value("ultrafast")}); preset.push_back({"superfast", ipc::value("superfast")}); preset.push_back({"veryfast", ipc::value("veryfast")}); @@ -1723,7 +1503,7 @@ void OBS_settings::getEncoderSettings(const obs_encoder_t *encoder, obs_data_t * SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, bool isCategoryEnabled) { - converOldJimNvencEncoder(config, "AdvOut", "Encoder", "RecEncoder"); + osn::EncoderUtils::convertOldJimNvencEncoder(config, "AdvOut", "Encoder", "RecEncoder"); int index = 0; @@ -2005,7 +1785,7 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b // Encoder settings const char *encoderID = config_get_string(config, "AdvOut", "Encoder"); - if (encoderID == NULL || OBS_service::isInvalidEncoder(encoderID)) { + if (encoderID == NULL || osn::EncoderUtils::isInvalidAppleEncoder(encoderID)) { encoderID = ADVANCED_ENCODER_X264; config_set_string(config, "AdvOut", "Encoder", encoderID); config_save_safe(config, "tmp", nullptr); @@ -2041,6 +1821,7 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b std::string encoder_name = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, false, encoderID); if (!fileExist) { + blog(LOG_INFO, "MLH create encoder type %s", encoderID); streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), nullptr, nullptr); OBS_service::setStreamingEncoder(streamingEncoder, StreamServiceId::Main); @@ -2050,9 +1831,10 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b } else { obs_data_t *data = obs_data_create_from_json_file_safe(streamConfigFile.c_str(), "bak"); - update_nvenc_presets(data, encoderID); + osn::EncoderUtils::updateNvencPresets(data, encoderID); obs_data_apply(settings, data); + blog(LOG_INFO, "MLH create encoder type %s", encoderID); streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), settings, nullptr); OBS_service::setStreamingEncoder(streamingEncoder, StreamServiceId::Main); } @@ -2214,6 +1996,9 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete memcpy(recEncoder.currentValue.data(), recEncoderCurrentValue, strlen(recEncoderCurrentValue)); recEncoder.sizeOfCurrentValue = strlen(recEncoderCurrentValue); + blog(LOG_INFO, "MLH get advanced RECORDING encoder option for current service"); + std::vector> encoderValues3; + std::vector> Encoder; Encoder.push_back(std::make_pair("Use stream encoder", ipc::value("none"))); getAdvancedAvailableEncoders(&Encoder, true, recFormatCurrentValue); @@ -2538,7 +2323,7 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete if (obs_output_active(recordOutput)) { settings = obs_encoder_get_settings(recordingEncoder); } else if (!recordingEncoder || (recordingEncoder && !obs_encoder_active(recordingEncoder))) { - std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, true, true, recEncoderCurrentValue); + std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, recEncoderCurrentValue); if (!fileExist) { recordingEncoder = obs_video_encoder_create(recEncoderCurrentValue, recEncoderName.c_str(), nullptr, nullptr); OBS_service::setRecordingEncoder(recordingEncoder); @@ -2548,7 +2333,7 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete } } else if (strcmp(recEncoderCurrentValue, "none") != 0) { obs_data_t *data = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); - update_nvenc_presets(data, recEncoderCurrentValue); + osn::EncoderUtils::updateNvencPresets(data, recEncoderCurrentValue); obs_data_apply(settings, data); recordingEncoder = obs_video_encoder_create(recEncoderCurrentValue, recEncoderName.c_str(), settings, nullptr); OBS_service::setRecordingEncoder(recordingEncoder); @@ -3008,8 +2793,16 @@ void OBS_settings::saveAdvancedOutputRecordingSettings(std::vector int ret = config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); if (newEncoderType) { + //this is called immediately on encoder change so no other settings have been changed - start with defaults encoderSettings = obs_encoder_defaults(config_get_string(ConfigManager::getInstance().getBasic(), section.c_str(), "RecEncoder")); - OBS_service::createVideoRecordingEncoder(); + + //this defaults to obs_x264 so create the correct encoder with default settings - getSettings called immediately after and will update it correctly but + //do it right here just in case + //OBS_service::createDefaultSimpleVideoRecordingEncoder(); + const char *curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); + std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, curEncoder); + obs_encoder_t *recordingEncoder = obs_video_encoder_create(curEncoder, recEncoderName.c_str(), encoderSettings, nullptr); + OBS_service::setRecordingEncoder(recordingEncoder); } else { obs_encoder_update(encoder, encoderSettings); } @@ -4194,162 +3987,3 @@ void OBS_settings::OBS_settings_setEnhancedBroadcasting(void *data, const int64_ AUTO_DEBUG; } - -void convert_nvenc_h264_presets(obs_data_t *data) -{ - const char *preset = obs_data_get_string(data, "preset"); - const char *rc = obs_data_get_string(data, "rate_control"); - - // If already using SDK10+ preset, return early. - if (astrcmpi_n(preset, "p", 1) == 0) { - obs_data_set_string(data, "preset2", preset); - return; - } - - if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "mq") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "qres"); - - } else if (astrcmpi(preset, "hq") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "default") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "hp") == 0) { - obs_data_set_string(data, "preset2", "p1"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "ll") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhq") == 0) { - obs_data_set_string(data, "preset2", "p4"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhp") == 0) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - } -} - -void convert_nvenc_hevc_presets(obs_data_t *data) -{ - const char *preset = obs_data_get_string(data, "preset"); - const char *rc = obs_data_get_string(data, "rate_control"); - - // If already using SDK10+ preset, return early. - if (astrcmpi_n(preset, "p", 1) == 0) { - obs_data_set_string(data, "preset2", preset); - return; - } - - if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "mq") == 0) { - obs_data_set_string(data, "preset2", "p6"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "qres"); - - } else if (astrcmpi(preset, "hq") == 0) { - obs_data_set_string(data, "preset2", "p6"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "default") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "hp") == 0) { - obs_data_set_string(data, "preset2", "p1"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "ll") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhq") == 0) { - obs_data_set_string(data, "preset2", "p4"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhp") == 0) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - } -} - -const char *convert_nvenc_simple_preset(const char *old_preset) -{ - if (astrcmpi(old_preset, "mq") == 0) { - return "p5"; - } else if (astrcmpi(old_preset, "hq") == 0) { - return "p5"; - } else if (astrcmpi(old_preset, "default") == 0) { - return "p3"; - } else if (astrcmpi(old_preset, "hp") == 0) { - return "p1"; - } else if (astrcmpi(old_preset, "ll") == 0) { - return "p3"; - } else if (astrcmpi(old_preset, "llhq") == 0) { - return "p4"; - } else if (astrcmpi(old_preset, "llhp") == 0) { - return "p2"; - } - return "p5"; -} - -bool update_nvenc_presets(obs_data_t *data, const char *encoderId) -{ - bool modified = false; - if (astrcmpi(encoderId, ENCODER_NVENC_H264_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC) == 0) { - if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { - convert_nvenc_h264_presets(data); - - modified = true; - } - } else if (astrcmpi(encoderId, ENCODER_NVENC_HEVC_TEX) == 0 || astrcmpi(encoderId, "ffmpeg_hevc_nvenc") == 0) { - - if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { - convert_nvenc_hevc_presets(data); - - modified = true; - } - } - if (modified) - blog(LOG_INFO, "Updated nvenc preset for %s", encoderId); - - return modified; -} diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index a4a949849..11641c511 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -21,6 +21,7 @@ #include "shared.hpp" #include "osn-audio-track.hpp" #include "osn-file-output.hpp" +#include void osn::IAdvancedRecording::Register(ipc::server &srv) { @@ -185,6 +186,7 @@ bool osn::AdvancedRecording::UpdateEncoders() if (!videoEncoder) return false; + //TODO this is done in streaming->updateEncoders - put this in an else? unless canvas is different - comes from OutputSignals so check if that is diff for streaming/recording if (obs_get_multiple_rendering()) { obs_encoder_set_video_mix(videoEncoder, obs_video_mix_get(canvas, OBS_RECORDING_VIDEO_RENDERING)); } else { @@ -224,6 +226,9 @@ void osn::IAdvancedRecording::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, false)) + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + obs_output_set_video_encoder(recording->output, recording->videoEncoder); std::string path = recording->path; @@ -347,9 +352,27 @@ void osn::IAdvancedRecording::GetLegacySettings(void *data, const int64_t id, co recording->useStreamEncoders = encId.compare("") == 0 || encId.compare("none") == 0; if (!recording->useStreamEncoders) { - obs_data_t *videoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); - recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", videoEncSettings, nullptr); + obs_data_t *existingVideoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); + obs_data_t *newSettings = obs_encoder_defaults(encId.c_str()); + + //old API gets defaults, reads recordEncoder.json if exists, converts if it does, then creates - need to handle null settings from missing config file + if (existingVideoEncSettings != nullptr) { + osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId.c_str()); + obs_data_apply(newSettings, existingVideoEncSettings); + } + //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleRecording(encId.c_str(), recording->fileFormat, false)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} + recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); + }else { + //TODO validate streaming encoder is valid for recording or wait until start? don't know if streaming is even set yet here and unsure how often this will be called so just check in SetVideoEncoder and Start + //if (recording->streaming) { + // if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->streaming->videoEncoder), recording->fileFormat, false)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified streaming video encoder is not valid for recording."); + // } + //} } recording->enableFileSplit = config_get_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile"); diff --git a/obs-studio-server/source/osn-advanced-recording.hpp b/obs-studio-server/source/osn-advanced-recording.hpp index ba2f71a83..675e8ccaf 100644 --- a/obs-studio-server/source/osn-advanced-recording.hpp +++ b/obs-studio-server/source/osn-advanced-recording.hpp @@ -34,6 +34,7 @@ class AdvancedRecording : public Recording { outputHeight = 720; useStreamEncoders = true; streaming = nullptr; + simple = false; } ~AdvancedRecording() {} diff --git a/obs-studio-server/source/osn-advanced-streaming.cpp b/obs-studio-server/source/osn-advanced-streaming.cpp index 265ac9d49..bf9958b30 100644 --- a/obs-studio-server/source/osn-advanced-streaming.cpp +++ b/obs-studio-server/source/osn-advanced-streaming.cpp @@ -23,6 +23,7 @@ #include "shared.hpp" #include "nodeobs_audio_encoders.h" #include "osn-audio-track.hpp" +#include "osn-encoders.hpp" void osn::IAdvancedStreaming::Register(ipc::server &srv) { @@ -223,14 +224,15 @@ void osn::IAdvancedStreaming::SetOutputHeight(void *data, const int64_t id, cons AUTO_DEBUG; } -static obs_encoder_t *createAudioEncoder(uint32_t bitrate) -{ - obs_encoder_t *audioEncoder = nullptr; - - audioEncoder = obs_audio_encoder_create(GetAACEncoderForBitrate(bitrate), "audio", nullptr, 0, nullptr); - - return audioEncoder; -} +//TODO - unused, delete it? +//static obs_encoder_t *createAudioEncoder(uint32_t bitrate) +//{ +// obs_encoder_t *audioEncoder = nullptr; +// +// audioEncoder = obs_audio_encoder_create(GetAACEncoderForBitrate(bitrate), "audio", nullptr, 0, nullptr); +// +// return audioEncoder; +//} static bool setAudioEncoder(osn::AdvancedStreaming *streaming) { @@ -394,6 +396,11 @@ void osn::IAdvancedStreaming::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Error while creating the video encoder."); } + //make sure the encoder is valid for the current service + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The provided encoder is not valid for the current service."); + } + if (!setAudioEncoder(streaming)) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Error while creating the audio encoder."); } @@ -467,8 +474,28 @@ void osn::IAdvancedStreaming::GetLegacySettings(void *data, const int64_t id, co { osn::AdvancedStreaming *streaming = new osn::AdvancedStreaming(); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder")); - obs_data_t *videoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getStream().c_str(), "bak"); - streaming->videoEncoder = obs_video_encoder_create(encId, "video-encoder", videoEncSettings, nullptr); + + //TODO - from old API - check for bad encoder ID and reset to x264 if needed is this going to mess up settings? should this also be in osn-advanced-recording? + if ((strlen(encId) == 0) || osn::EncoderUtils::isInvalidAppleEncoder(encId)) { + encId = ADVANCED_ENCODER_X264; + config_set_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder", encId); + config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); + } + + obs_data_t *existingVideoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getStream().c_str(), "bak"); + obs_data_t *newSettings = obs_encoder_defaults(encId); + + //old API gets defaults, reads streamEncoder.json if exists, converts if it does, then creates - need to handle null settings from missing config file + if (existingVideoEncSettings != nullptr) { + osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId); + obs_data_apply(newSettings, existingVideoEncSettings); + } + //TODO - do we need to create descriptive encoder name like in old API? + //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} + streaming->videoEncoder = obs_video_encoder_create(encId, "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(streaming->videoEncoder); streaming->audioTrack = static_cast(config_get_int(ConfigManager::getInstance().getBasic(), "AdvOut", "TrackIndex") - 1); diff --git a/obs-studio-server/source/osn-advanced-streaming.hpp b/obs-studio-server/source/osn-advanced-streaming.hpp index dd7c0a7b9..8d716f5fa 100644 --- a/obs-studio-server/source/osn-advanced-streaming.hpp +++ b/obs-studio-server/source/osn-advanced-streaming.hpp @@ -35,6 +35,7 @@ class AdvancedStreaming : public Streaming { rescaling = false; outputWidth = 1280; outputHeight = 720; + simple = false; } ~AdvancedStreaming() {} diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp new file mode 100644 index 000000000..a719899de --- /dev/null +++ b/obs-studio-server/source/osn-encoders.cpp @@ -0,0 +1,453 @@ +/****************************************************************************** + Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +******************************************************************************/ + +#include "osn-encoders.hpp" +#include "obs.h" +#include +#include +#include +#include "utility.hpp" + +static bool isNvencAvailableForSimpleMode(); +static bool containerSupportsCodec(const std::string &container, const std::string &codec); +static void convert_nvenc_h264_presets(obs_data_t *data); +static void convert_nvenc_hevc_presets(obs_data_t *data); + +bool osn::EncoderUtils::isEncoderRegistered(const std::string &encoder) +{ + const char *val; + int i = 0; + + while (obs_enum_encoder_types(i++, &val)) { + if (val == nullptr) + continue; + if (std::string(val) == encoder) + return true; + } + + return false; +} + +bool osn::EncoderUtils::isCodecAvailableForService(const char *encoder, obs_service_t *service) +{ + if (!encoder || !service) + return false; + + auto supportedCodecs = obs_service_get_supported_video_codecs(service); + auto encoderCodec = obs_get_encoder_codec(encoder); + + if (!supportedCodecs || !encoderCodec) + return false; + + while (*supportedCodecs) { + if (strcmp(*supportedCodecs, encoderCodec) == 0) + return true; + supportedCodecs++; + } + + return false; +} + +bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service_t *service, bool simpleMode, bool recording, const std::string &container, + int checkIndex) +{ + if (encoderName.empty()) + return false; + + if (!recording && !videoEncoderOptions[checkIndex].streaming) + return false; + + if (recording && !videoEncoderOptions[checkIndex].recording) + return false; + + if (videoEncoderOptions[checkIndex].check_availability && !isEncoderRegistered(encoderName)) + return false; + + if (!recording && videoEncoderOptions[checkIndex].check_availability_streaming && + !isCodecAvailableForService(encoderName.c_str(), service)) + return false; + + if (simpleMode) { + if (videoEncoderOptions[checkIndex].only_for_reuse_simple && !isNvencAvailableForSimpleMode()) + return false; + } + + if (recording && videoEncoderOptions[checkIndex].check_availability_format) { + const char *codec = obs_get_encoder_codec(encoderName.c_str()); + if (!codec) { + blog(LOG_DEBUG, "[ENCODER_SKIPPED] codec is null"); + return false; + } + if (!containerSupportsCodec(container, codec)) + return false; + } + blog(LOG_INFO, "MLH encoder compatible for simpleMode = %d with current settings: %s", simpleMode, encoderName.c_str()); + + return true; +} + +bool osn::EncoderUtils::isEncoderCompatibleStreaming(obs_service_t *service, const char *encoderToFind, bool simpleMode) +{ + bool validEncoder = false; + std::string curEncoder = ""; + + //find the encoder in the set and then check compatibility + for (int i = 0; i < videoEncoderOptions.size(); i++) { + curEncoder = simpleMode ? videoEncoderOptions[i].getSimpleName() : videoEncoderOptions[i].advanced_name; + if (curEncoder.compare(encoderToFind) == 0) { + if (isEncoderCompatible(encoderToFind, service, simpleMode, false, "", i)) { + validEncoder = true; + break; + } + } + } + + return validEncoder; +} + +bool osn::EncoderUtils::isEncoderCompatibleRecording(const char *encoderToFind, const std::string &container, bool simpleMode) +{ + bool validEncoder = false; + + std::string curEncoder = ""; + + //find the encoder in the set and then check compatibility + for (int i = 0; i < videoEncoderOptions.size(); i++) { + curEncoder = simpleMode ? videoEncoderOptions[i].getSimpleName() : videoEncoderOptions[i].advanced_name; + if (curEncoder.compare(encoderToFind) == 0) { + if (isEncoderCompatible(encoderToFind, NULL, simpleMode, true, "", i)) { + validEncoder = true; + break; + } + } + } + + return validEncoder; +} + +bool osn::EncoderUtils::isInvalidAppleEncoder(const char *encoderID) +{ +#if defined(__APPLE__) + // disable this encoder; not functioning properly + return strcmp(encoderID, APPLE_SOFTWARE_VIDEO_ENCODER) == 0; +#else + return false; +#endif +} + +//replacing logic of get_simple_output_encoder +std::string osn::EncoderUtils::getInternalEncoderFromSimple(const char *encoder) +{ + std::string encoderName = ADVANCED_ENCODER_X264; + bool found = false; + + for (const auto curEnc : videoEncoderOptions) { + //if there is a backup, check if simple_internal_name is available, return backup if not + //else if no simple_internal_name, return simple_name + //else return simple_internal_name + //blog(LOG_INFO, "MLH ConvertSimpleEncoder - checking encoder %s internal %s backup %s", curEnc.simple_name.c_str(), + // curEnc.simple_internal_name.c_str(), curEnc.backup.c_str()); + if (encoder == curEnc.simple_name) { + if (!curEnc.backup.empty() && !isEncoderRegistered(curEnc.simple_internal_name)) + encoderName = curEnc.backup; + else { + if (curEnc.simple_internal_name.empty()) + encoderName = curEnc.simple_name; + else + encoderName = curEnc.simple_internal_name; + } + found = true; + break; + } + } + if (!found) + blog(LOG_WARNING, "GetAdvancedEncoderFromSimple - encoder %s is not found, returning default encoder.", encoder); + + return encoderName; +} + +std::string osn::EncoderUtils::getSimpleEncoderFromInternal(const char *encoder) +{ + //this defaults to advanced b/c that's how it's done in osn-simple-streaming where this is used....WHY + std::string encoderName = ADVANCED_ENCODER_X264; + bool found = false; + + //blog(LOG_INFO, "MLH ConvertSimpleEncoder - converting simple encoder %s", encoder); + + for (const auto curEnc : videoEncoderOptions) { + if (encoder == curEnc.simple_internal_name) { + encoderName = curEnc.simple_name; + found = true; + break; + } + } + if (!found) + blog(LOG_WARNING, "GetSimpleEncoderFromAdvanced - encoder %s is not found, returning default encoder.", encoder); + + return encoderName; +} + +std::string osn::EncoderUtils::getEncoderPreset(const char *encoder) +{ + std::string preset = DEFAULT_PRESET; + bool found = false; + + //blog(LOG_INFO, "MLH GetEncoderPreset for encoder %s", encoder); + + for (const auto curEnc : videoEncoderOptions) { + //blog(LOG_INFO, "MLH GetEncoderPreset - checking encoder %s (advanced) %s (simple) preset %s", curEnc.advanced_name.c_str(), + // curEnc.simple_name.c_str(), curEnc.simple_name.c_str(), curEnc.preset.c_str()); + if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { + preset = curEnc.preset; + found = true; + break; + } + } + + if (!found) + blog(LOG_WARNING, "GetEncoderPreset - encoder %s is not found, returning default preset.", encoder); + + return preset; +} + +std::string osn::EncoderUtils::getEncoderFamily(const char *encoder) +{ + std::string family = ""; + bool found = false; + + //match on advanced_name or simple_name...backup or internal? + for (const auto curEnc : videoEncoderOptions) { + if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { + family = curEnc.family; + found = true; + break; + } + } + + if (!found) + blog(LOG_WARNING, "GetEncoderFamily - encoder %s is not found.", encoder); + + return family; +} + +bool osn::EncoderUtils::isOldJimNvencEncoder(const std::string &encoderId) +{ + return encoderId == ENCODER_JIM_NVENC || encoderId == ENCODER_JIM_HEVC_NVENC || encoderId == ENCODER_JIM_AV1_NVENC; +} + +// This code should be removed when JIM_ encoders will be removed from OBS +void osn::EncoderUtils::convertOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, + const std::string &recordingEncoderSetting) +{ + const std::string streamEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), streamEncoderSetting.c_str())); + if (osn::EncoderUtils::isOldJimNvencEncoder(streamEncoder)) { + blog(LOG_INFO, "Converting stream encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), streamEncoder.c_str(), + ENCODER_NVENC_H264_TEX); + config_set_string(config, configSection.c_str(), streamEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); + } + + const std::string recordingEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), recordingEncoderSetting.c_str())); + if (osn::EncoderUtils::isOldJimNvencEncoder(recordingEncoder)) { + blog(LOG_INFO, "Converting recording encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), recordingEncoder.c_str(), + ENCODER_NVENC_H264_TEX); + config_set_string(config, configSection.c_str(), recordingEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); + } +} + +bool osn::EncoderUtils::updateNvencPresets(obs_data_t *data, const char *encoderId) +{ + bool modified = false; + if (astrcmpi(encoderId, ENCODER_NVENC_H264_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC) == 0) { + if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { + convert_nvenc_h264_presets(data); + + modified = true; + } + } else if (astrcmpi(encoderId, ENCODER_NVENC_HEVC_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC_HEVC) == 0) { + + if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { + convert_nvenc_hevc_presets(data); + + modified = true; + } + } + if (modified) + blog(LOG_INFO, "Updated nvenc preset for %s", encoderId); + + return modified; +} + +const char *osn::EncoderUtils::convertNvencSimplePreset(const char *old_preset) +{ + if (astrcmpi(old_preset, "mq") == 0) { + return "p5"; + } else if (astrcmpi(old_preset, "hq") == 0) { + return "p5"; + } else if (astrcmpi(old_preset, "default") == 0) { + return "p3"; + } else if (astrcmpi(old_preset, "hp") == 0) { + return "p1"; + } else if (astrcmpi(old_preset, "ll") == 0) { + return "p3"; + } else if (astrcmpi(old_preset, "llhq") == 0) { + return "p4"; + } else if (astrcmpi(old_preset, "llhp") == 0) { + return "p2"; + } + return "p5"; +} + +static bool isNvencAvailableForSimpleMode() +{ + // Only available if config already uses it + const char *current_stream_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); + const char *current_rec_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); + bool nvenc_used_streaming = (current_stream_encoder && strcmp(current_stream_encoder, SIMPLE_ENCODER_NVENC) == 0); + bool nvenc_used_recording = (current_rec_encoder && strcmp(current_rec_encoder, SIMPLE_ENCODER_NVENC) == 0); + + return (nvenc_used_streaming || nvenc_used_recording) && osn::EncoderUtils::isEncoderRegistered(ADVANCED_ENCODER_NVENC); +} + +static bool containerSupportsCodec(const std::string &container, const std::string &codec) +{ + auto iter = osn::EncoderUtils::codecsForContainers.find(container); + if (iter == osn::EncoderUtils::codecsForContainers.end()) + return false; + + auto codecs = iter->second; + // Assume everything is supported + if (codecs.empty()) + return true; + return codecs.count(codec) > 0; +} + +static void convert_nvenc_h264_presets(obs_data_t *data) +{ + const char *preset = obs_data_get_string(data, "preset"); + const char *rc = obs_data_get_string(data, "rate_control"); + + // If already using SDK10+ preset, return early. + if (astrcmpi_n(preset, "p", 1) == 0) { + obs_data_set_string(data, "preset2", preset); + return; + } + + if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "mq") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "qres"); + + } else if (astrcmpi(preset, "hq") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "default") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "hp") == 0) { + obs_data_set_string(data, "preset2", "p1"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "ll") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhq") == 0) { + obs_data_set_string(data, "preset2", "p4"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhp") == 0) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + } +} + +static void convert_nvenc_hevc_presets(obs_data_t *data) +{ + const char *preset = obs_data_get_string(data, "preset"); + const char *rc = obs_data_get_string(data, "rate_control"); + + // If already using SDK10+ preset, return early. + if (astrcmpi_n(preset, "p", 1) == 0) { + obs_data_set_string(data, "preset2", preset); + return; + } + + if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "mq") == 0) { + obs_data_set_string(data, "preset2", "p6"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "qres"); + + } else if (astrcmpi(preset, "hq") == 0) { + obs_data_set_string(data, "preset2", "p6"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "default") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "hp") == 0) { + obs_data_set_string(data, "preset2", "p1"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "ll") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhq") == 0) { + obs_data_set_string(data, "preset2", "p4"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhp") == 0) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + } +} \ No newline at end of file diff --git a/obs-studio-server/source/osn-encoders.hpp b/obs-studio-server/source/osn-encoders.hpp new file mode 100644 index 000000000..347f3c437 --- /dev/null +++ b/obs-studio-server/source/osn-encoders.hpp @@ -0,0 +1,210 @@ +/****************************************************************************** + Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "obs.h" +#include "nodeobs_configManager.hpp" + +//obs-x264 plugin +#ifdef WIN32 +#define SIMPLE_ENCODER_X264 "x264" +#elif __APPLE__ +#define SIMPLE_ENCODER_X264 "obs_x264" +#endif +#define ADVANCED_ENCODER_X264 "obs_x264" + +//special case for recording +#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" + +//generic values for simple mode to convert to specific encoders +#define SIMPLE_ENCODER_NVENC "nvenc" //h264 - obs-nvenc plugin +#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" //av1 - obs-nvenc plugin +#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" //hevc - obs-nvenc plugin +#define SIMPLE_ENCODER_AMD "amd" //h264 - obs-ffmpeg plugin +#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" //hevc - obs-ffmpeg plugin +#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" //av1 - obs-ffmpeg plugin +#define SIMPLE_ENCODER_QSV "qsv" //h264 - obs-qsv11 plugin +#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" //av1 - obs-qsv11 plugin +#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" //h264 - apple encoder +#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" //hevc - apple encoder + +//obs-qsv11 plugin (QuickSync) +#define ADVANCED_ENCODER_QSV "obs_qsv11" //h264 +#define ADVANCED_ENCODER_QSV_V2 "obs_qsv11_v2" //h264 +#define ADVANCED_ENCODER_QSV_AV1 "obs_qsv11_av1" //av1 +#define ADVANCED_ENCODER_QSV_HEVC "obs_qsv11_hevc" //hevc + +//obs-nvenc plugin - deprecated jim encoders +#define ENCODER_JIM_NVENC "jim_nvenc" //h264 +#define ENCODER_JIM_HEVC_NVENC "jim_hevc_nvenc" //hevc +#define ENCODER_JIM_AV1_NVENC "jim_av1_nvenc" //av1 + +//obs-nvenc plugin (NVIDIA) +#define ENCODER_NVENC_H264_TEX "obs_nvenc_h264_tex" //h264 +#define ENCODER_NVENC_HEVC_TEX "obs_nvenc_hevc_tex" //hevc +#define ENCODER_NVENC_AV1_TEX "obs_nvenc_av1_tex" //av1 +#define ADVANCED_ENCODER_NVENC "ffmpeg_nvenc" //h264 - if REGISTER_FFMPEG_IDS +#define ADVANCED_ENCODER_NVENC_HEVC "ffmpeg_hevc_nvenc" //hevc - if REGISTER_FFMPEG_IDS and ENABLE_HEVC + +//obs-ffmpeg plugin +#define ENCODER_AV1_SVT_FFMPEG "ffmpeg_svt_av1" //av1 +#define ENCODER_AV1_AOM_FFMPEG "ffmpeg_aom_av1" //av1 +#define ADVANCED_ENCODER_AMD "h264_texture_amf" //h264 +#define ADVANCED_ENCODER_AMD_HEVC "h265_texture_amf" //hevc +#define ADVANCED_ENCODER_AMD_AV1 "av1_texture_amf" //av1 + +//Apple encoders +#define APPLE_SOFTWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264" +#define APPLE_HARDWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264.gva" +#define APPLE_HARDWARE_VIDEO_ENCODER_M1 "com.apple.videotoolbox.videoencoder.ave.avc" + +#define SIMPLE_AUDIO_ENCODER_AAC "ffmpeg_aac" +#define SIMPLE_AUDIO_ENCODER_OPUS "ffmpeg_opus" + +//presets +#define PRESET_NVENC "NVENCPreset2" +#define PRESET_NVENC_DEP "NVENCPreset" +#define PRESET_QSV "QSVPreset" +#define PRESET_AMD "AMDPreset" +#define PRESET_APPLE "Profile" +#define DEFAULT_PRESET "Preset" + +//encoder families +#define FAMILY_OBS "family_obs" +#define FAMILY_QSV "family_qsv" +#define FAMILY_NVENC "family_nvenc" +#define FAMILY_NVENC_HEVC "family_nvenc_hevc" +#define FAMILY_AMD "family_amd" +#define FAMILY_APPLE "family_apple" +#define FAMILY_FFMPEG "family_ffmpeg" + +namespace osn { +namespace EncoderUtils { + +bool isEncoderRegistered(const std::string &encoder); +bool isCodecAvailableForService(const char *encoder, obs_service_t *service); +bool isInvalidAppleEncoder(const char *encoderID); +std::string getInternalEncoderFromSimple(const char *encoder); +std::string getSimpleEncoderFromInternal(const char *encoder); +std::string getEncoderFamily(const char *encoder); +std::string getEncoderPreset(const char *encoder); +bool isOldJimNvencEncoder(const std::string &encoderId); +void convertOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, + const std::string &recordingEncoderSetting); +bool isEncoderCompatible(std::string encoderName, obs_service_t *service, bool simpleMode, bool recording, const std::string &container, int checkIndex); +bool isEncoderCompatibleStreaming(obs_service_t *service, const char *encoderToFind, bool simpleMode); +bool isEncoderCompatibleRecording(const char *encoderToFind, const std::string &container, bool simpleMode); +bool updateNvencPresets(obs_data_t *data, const char *encoderId); +const char *convertNvencSimplePreset(const char *old_preset); + +class EncoderSettings { +public: + std::string advanced_title; + std::string advanced_name; + std::string simple_title; + std::string simple_name; + std::string simple_internal_name; + std::string backup; + bool recording; + bool streaming; + bool check_availability; + bool check_availability_streaming; + bool check_availability_format; + bool only_for_reuse_simple; + std::string preset; + std::string family; + const std::string getSimpleName() const { return simple_internal_name.empty() ? simple_name : simple_internal_name; } +}; + +static std::vector videoEncoderOptions = { + // Software x264 + {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, "", true, true, false, false, true, false, + "Preset", FAMILY_OBS}, + // Software x264 low CPU (only for recording) + {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, "", true, false, false, false, + true, false, "Preset", FAMILY_OBS}, + // QuickSync H.264 (v1, deprecated) + // This line left here for reference + // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, + // QuickSync H.264 (v2, new) + {"QuickSync H.264", ADVANCED_ENCODER_QSV_V2, "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV_V2, "", true, true, true, false, true, + false, "QSVPreset", FAMILY_QSV}, + // QuickSync AV1 + {"QuickSync AV1", ADVANCED_ENCODER_QSV_AV1, "Hardware (QSV, AV1)", SIMPLE_ENCODER_QSV_AV1, ADVANCED_ENCODER_QSV_AV1, "", true, true, true, false, true, + false, "QSVPreset", FAMILY_QSV}, + // QuickSync HEVC + {"QuickSync HEVC", ADVANCED_ENCODER_QSV_HEVC, "", "", "", "", true, true, true, false, true, false, "QSVPreset", FAMILY_QSV}, + // NVIDIA NVENC H.264 + {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ENCODER_NVENC_H264_TEX, ADVANCED_ENCODER_NVENC, true, true, + true, false, true, true, "NVENCPreset", FAMILY_NVENC}, + // NVIDIA NVENC H.264 (new) + {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", "", true, true, true, false, true, false, + "NVENCPreset", FAMILY_NVENC}, + // NVIDIA NVENC HEVC + {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, ADVANCED_ENCODER_NVENC_HEVC, + true, true, true, true, true, false, "Preset", FAMILY_NVENC_HEVC}, + // NVIDIA NVENC AV1 + {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "", "", true, true, true, true, true, false, "NVENCPreset", + FAMILY_NVENC}, + // Apple VT H264 Hardware Encoder + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", "", true, true, true, + false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // Apple VT H264 Hardware Encoder + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER_M1, "", "", true, true, + true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // AMD HW H.264 + {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, + "AMDPreset", FAMILY_AMD}, + // AMD HW H.265 (HEVC) + {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, "", true, true, true, + true, true, false, "AMDPreset", FAMILY_AMD}, + // AMD HW AV1 + {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", SIMPLE_ENCODER_AMD_AV1, ADVANCED_ENCODER_AMD_AV1, "", true, true, true, true, true, false, + "AMDPreset", FAMILY_AMD}, + // AOM AV1 + {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}, + // SVT-AV1 + {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}}; + +//} else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { +// return APPLE_HARDWARE_VIDEO_ENCODER_M1; +//} else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { +// return "com.apple.videotoolbox.videoencoder.ave.hevc"; + +// Codect/Container support check. +// from OBS code UI\window-basic-settings.cpp +static const std::unordered_map> codecsForContainers = { + // Technically our muxer supports HEVC and AV1 as well, but nothing else does + {"flv", {"h264", "aac"}}, + {"mpegts", {"h264", "hevc", "aac", "opus"}}, + {"hls", {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support + {"mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, + {"mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, + {"fragmented_mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, + {"fragmented_mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, + // MKV supports everything + {"mkv", {}}, +}; + +} +} diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 77da3582d..0d5057137 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -18,9 +18,11 @@ #include "osn-recording.hpp" #include "osn-video-encoder.hpp" +#include "osn-audio-encoder.hpp" #include "osn-error.hpp" #include "shared.hpp" #include "util/platform.h" +#include "osn-encoders.hpp" extern char *osn_generate_formatted_filename(const char *extension, bool space, const char *format, int width, int height); @@ -55,6 +57,11 @@ void osn::IRecording::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); } + //verify the encoder is compatible before setting it + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(encoder), recording->fileFormat, recording->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + recording->videoEncoder = encoder; rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); @@ -143,8 +150,12 @@ obs_encoder_t *osn::IRecording::duplicate_encoder(obs_encoder_t *src, uint64_t t if (obs_encoder_get_type(src) == OBS_ENCODER_AUDIO) { dst = obs_audio_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), trackIndex, nullptr); + //TODO added so it eventually gets released...is this correct? dont' even include osn-audio-encoder here + osn::AudioEncoder::Manager::GetInstance().allocate(dst); } else if (obs_encoder_get_type(src) == OBS_ENCODER_VIDEO) { dst = obs_video_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), nullptr); + //TODO added so it eventually gets released...is this correct? + osn::VideoEncoder::Manager::GetInstance().allocate(dst); } return dst; diff --git a/obs-studio-server/source/osn-recording.hpp b/obs-studio-server/source/osn-recording.hpp index 5a5f0930d..678dd87ff 100644 --- a/obs-studio-server/source/osn-recording.hpp +++ b/obs-studio-server/source/osn-recording.hpp @@ -36,6 +36,7 @@ class Recording : public FileOutput { splitTime = 15; splitSize = 2048; fileResetTimestamps = true; + simple = true; } virtual ~Recording(); @@ -46,6 +47,7 @@ class Recording : public FileOutput { uint32_t splitTime; uint32_t splitSize; bool fileResetTimestamps; + bool simple; void ConfigureRecFileSplitting(); }; diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 27addc6d3..9d6adb506 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -23,6 +23,7 @@ #include "shared.hpp" #include "nodeobs_audio_encoders.h" #include "osn-file-output.hpp" +#include "osn-encoders.hpp" void osn::ISimpleRecording::Register(ipc::server &srv) { @@ -272,23 +273,22 @@ static void UpdateRecordingSettings_crf(enum osn::RecQuality quality, osn::Simpl int crf = CalcCRF(ultra_hq ? 16 : 23); obs_data_t *settings = nullptr; - if (id.compare(SIMPLE_ENCODER_X264) == 0 || id.compare(ADVANCED_ENCODER_X264) == 0 || id.compare(SIMPLE_ENCODER_X264_LOWCPU) == 0) { - settings = UpdateRecordingSettings_x264_crf(CalcCRF(crf, recording->lowCPU), recording->lowCPU); - } else if (id.compare(SIMPLE_ENCODER_NVENC) == 0 || id.compare(ADVANCED_ENCODER_NVENC) == 0 || id.compare(ENCODER_NVENC_H264_TEX) == 0) { - settings = UpdateRecordingSettings_nvenc(CalcCRF(crf)); - } else if (id.compare(SIMPLE_ENCODER_NVENC_HEVC) == 0) { - settings = UpdateRecordingSettings_nvenc_hevc(CalcCRF(crf)); - } else if (id.compare(SIMPLE_ENCODER_QSV) == 0 || id.compare(ADVANCED_ENCODER_QSV) == 0) { - settings = UpdateRecordingSettings_qsv11(CalcCRF(crf), recording->videoEncoder); - } else if (id.compare(SIMPLE_ENCODER_AMD) == 0 || id.compare(SIMPLE_ENCODER_AMD_HEVC) == 0 || id.compare(ADVANCED_ENCODER_AMD) == 0) { - settings = UpdateRecordingSettings_amd_cqp(CalcCRF(crf)); - } else if (id.compare(APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || id.compare(APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - id.compare(APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); - } else { - return; - } + std::string encFamily = osn::EncoderUtils::getEncoderFamily(id.c_str()); + + if (encFamily == FAMILY_OBS) + settings = UpdateRecordingSettings_x264_crf(crf, recording->lowCPU); + else if (encFamily == FAMILY_NVENC) + settings = UpdateRecordingSettings_nvenc(crf); + else if (encFamily == FAMILY_NVENC_HEVC) + settings = UpdateRecordingSettings_nvenc_hevc(crf); + else if (encFamily == FAMILY_QSV) + settings = UpdateRecordingSettings_qsv11(crf, recording->videoEncoder); + else if (encFamily == FAMILY_AMD) + settings = UpdateRecordingSettings_amd_cqp(crf); + else if (encFamily == FAMILY_APPLE) + settings = UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); + else + blog(LOG_WARNING, "Unable to update settings with unknown encoder family."); if (!settings) return; @@ -317,16 +317,20 @@ void osn::SimpleRecording::UpdateEncoders() videoEncoder = streaming->videoEncoder; audioEncoder = streaming->audioEncoder; if (obs_get_multiple_rendering()) { - obs_encoder_t *videoEncDup = osn::IRecording::duplicate_encoder(videoEncoder); - videoEncoder = videoEncDup; + //TODO this doesn't register with the manager, should it? + videoEncoder = osn::IRecording::duplicate_encoder(videoEncoder); } break; } case RecQuality::HighQuality: { + if (!videoEncoder) + return; UpdateRecordingSettings_crf(RecQuality::HighQuality, this); break; } case RecQuality::HigherQuality: { + if (!videoEncoder) + return; UpdateRecordingSettings_crf(RecQuality::HigherQuality, this); break; } @@ -362,10 +366,16 @@ void osn::ISimpleRecording::Start(void *data, const int64_t id, const std::vecto } else { recording->UpdateEncoders(); + //TODO - does update not create an encoder if not using streaming and it doesn't exist? should it? + if (!recording->videoEncoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, true)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + if (!recording->audioEncoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid audio encoder."); } @@ -448,43 +458,37 @@ void osn::ISimpleRecording::SetLowCPU(void *data, const int64_t id, const std::v AUTO_DEBUG; } -obs_encoder_t *osn::ISimpleRecording::GetLegacyVideoEncoderSettings() +obs_encoder_t *osn::ISimpleRecording::CreateLegacyVideoEncoder() { + osn::EncoderUtils::convertOldJimNvencEncoder(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", "RecEncoder"); + obs_encoder_t *videoEncoder = nullptr; std::string simpleQuality = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecQuality")); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder")); - const char *encIdOBS = nullptr; - if (strcmp(encId, SIMPLE_ENCODER_X264) == 0 || strcmp(encId, ADVANCED_ENCODER_X264) == 0) { - encIdOBS = ADVANCED_ENCODER_X264; - } else if (strcmp(encId, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - encIdOBS = ADVANCED_ENCODER_X264; - } else if (strcmp(encId, SIMPLE_ENCODER_QSV) == 0 || strcmp(encId, ADVANCED_ENCODER_QSV) == 0) { - encIdOBS = ADVANCED_ENCODER_QSV; - } else if (strcmp(encId, SIMPLE_ENCODER_AMD) == 0 || strcmp(encId, ADVANCED_ENCODER_AMD) == 0) { - encIdOBS = ADVANCED_ENCODER_AMD; - } else if (strcmp(encId, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encId, ADVANCED_ENCODER_NVENC) == 0) { - encIdOBS = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encId, ENCODER_NVENC_H264_TEX) == 0 || strcmp(encId, ENCODER_JIM_NVENC) == 0 || strcmp(encId, ENCODER_JIM_AV1_NVENC) == 0 || - strcmp(encId, ENCODER_JIM_HEVC_NVENC) == 0) { - encIdOBS = ENCODER_NVENC_H264_TEX; - } + //TODO do we need to check low CPU here before we convert? + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + //don't create the encoder if using the streaming encoder if (simpleQuality.compare("Stream") != 0) { - videoEncoder = obs_video_encoder_create(encIdOBS, "video-encoder", nullptr, nullptr); + //TODO should there be a descriptive name? why aren't we using settings? + videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", nullptr, nullptr); + osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); } return videoEncoder; } -obs_encoder_t *osn::ISimpleRecording::GetLegacyAudioEncoderSettings() +obs_encoder_t *osn::ISimpleRecording::CreateLegacyAudioEncoder() { obs_data_t *audioEncSettings = obs_data_create(); obs_data_set_int(audioEncSettings, "bitrate", 192); // Hardcoded default value obs_encoder_t *audioEncoder = obs_audio_encoder_create("ffmpeg_aac", "audio-encoder", audioEncSettings, 0, nullptr); obs_data_release(audioEncSettings); + osn::AudioEncoder::Manager::GetInstance().allocate(audioEncoder); + return audioEncoder; } @@ -519,12 +523,14 @@ void osn::ISimpleRecording::GetLegacySettings(void *data, const int64_t id, cons recording->lowCPU = true; if (recording->quality != RecQuality::Stream) { - recording->videoEncoder = GetLegacyVideoEncoderSettings(); - osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); + recording->videoEncoder = CreateLegacyVideoEncoder(); + //TODO do we want to check here and fail and not return the settings? or wait until start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, true)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} } - recording->audioEncoder = GetLegacyAudioEncoderSettings(); - osn::AudioEncoder::Manager::GetInstance().allocate(recording->audioEncoder); + recording->audioEncoder = CreateLegacyAudioEncoder(); recording->enableFileSplit = config_get_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile"); const char *splitFileType = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFileType"); @@ -621,22 +627,16 @@ void osn::ISimpleRecording::SetLegacySettings(void *data, const int64_t id, cons config_set_bool(ConfigManager::getInstance().getBasic(), "Output", "OverwriteIfExists", recording->overwrite); config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "MuxerCustom", recording->muxerSettings.c_str()); - if (recording->videoEncoder) { - const char *encId = nullptr; + //don't save the encoder if using the streaming encoder, set it to empty string in that case + if (recording->quality != RecQuality::Stream && recording->videoEncoder) { const char *encIdOBS = obs_encoder_get_id(recording->videoEncoder); - if (strcmp(encIdOBS, ADVANCED_ENCODER_X264) == 0 && !recording->lowCPU) { - encId = SIMPLE_ENCODER_X264; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_X264) == 0 && recording->lowCPU) { + std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); + if ((encId == SIMPLE_ENCODER_X264) == 0 && recording->lowCPU) { encId = SIMPLE_ENCODER_X264_LOWCPU; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_QSV) == 0) { - encId = SIMPLE_ENCODER_QSV; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_AMD) == 0) { - encId = SIMPLE_ENCODER_AMD; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_NVENC) == 0) { - encId = SIMPLE_ENCODER_NVENC; - } - - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId); + } + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId.c_str()); + } else { + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", ""); } config_set_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile", recording->enableFileSplit); diff --git a/obs-studio-server/source/osn-simple-recording.hpp b/obs-studio-server/source/osn-simple-recording.hpp index a4e2f5523..42ddec940 100644 --- a/obs-studio-server/source/osn-simple-recording.hpp +++ b/obs-studio-server/source/osn-simple-recording.hpp @@ -62,8 +62,8 @@ class ISimpleRecording : public IRecording { static void SetLowCPU(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static obs_encoder_t *GetLegacyVideoEncoderSettings(); - static obs_encoder_t *GetLegacyAudioEncoderSettings(); + static obs_encoder_t *CreateLegacyVideoEncoder(); + static obs_encoder_t *CreateLegacyAudioEncoder(); static void GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval); }; diff --git a/obs-studio-server/source/osn-simple-streaming.cpp b/obs-studio-server/source/osn-simple-streaming.cpp index a50e3bbcc..dfab4ea7e 100644 --- a/obs-studio-server/source/osn-simple-streaming.cpp +++ b/obs-studio-server/source/osn-simple-streaming.cpp @@ -22,6 +22,7 @@ #include "osn-error.hpp" #include "shared.hpp" #include "nodeobs_audio_encoders.h" +#include "osn-encoders.hpp" void osn::ISimpleStreaming::Register(ipc::server &srv) { @@ -277,6 +278,7 @@ void osn::SimpleStreaming::UpdateEncoders() int aBitrate = static_cast(obs_data_get_int(audioEncSettings, "bitrate")); std::string id = obs_encoder_get_id(videoEncoder); + //TODO why just AMD here? if (id.compare(ADVANCED_ENCODER_AMD) == 0) UpdateStreamingSettings_amd(videoEncSettings, vBitrate); @@ -353,6 +355,11 @@ void osn::ISimpleStreaming::Start(void *data, const int64_t id, const std::vecto PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid audio encoder."); } + //make sure the encoder is valid for the current service + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The provided encoder is not valid for the current service."); + } + streaming->UpdateEncoders(); obs_encoder_set_audio(streaming->audioEncoder, obs_get_audio()); obs_output_set_audio_encoder(streaming->output, streaming->audioEncoder, 0); @@ -420,10 +427,11 @@ void osn::ISimpleStreaming::Stop(void *data, const int64_t id, const std::vector AUTO_DEBUG; } -obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() +obs_encoder_t *osn::ISimpleStreaming::CreateLegacyVideoEncoder() { + osn::EncoderUtils::convertOldJimNvencEncoder(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", "RecEncoder"); + const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder")); - const char *encIdOBS = nullptr; obs_data_t *videoEncData = obs_data_create(); obs_data_set_string(videoEncData, "rate_control", "CBR"); @@ -433,26 +441,24 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() const char *custom = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings")); const char *preset = nullptr; - const char *presetType = nullptr; - if (strcmp(encId, SIMPLE_ENCODER_QSV) == 0 || strcmp(encId, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encIdOBS = ADVANCED_ENCODER_QSV; - } else if (strcmp(encId, SIMPLE_ENCODER_AMD) == 0 || strcmp(encId, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - encIdOBS = ADVANCED_ENCODER_AMD; - } else if (strcmp(encId, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encId, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encIdOBS = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encId, ENCODER_NVENC_H264_TEX) == 0 || strcmp(encId, ENCODER_JIM_NVENC) == 0 || strcmp(encId, ENCODER_JIM_AV1_NVENC) == 0 || - strcmp(encId, ENCODER_JIM_HEVC_NVENC) == 0) { - presetType = "NVENCPreset"; - encIdOBS = ENCODER_NVENC_H264_TEX; - } else { - presetType = "Preset"; - encIdOBS = ADVANCED_ENCODER_X264; + + std::string presetType = osn::EncoderUtils::getEncoderPreset(encId); + //TODO do we need to check low CPU here before we convert? + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + + preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str())); + + //TODO this conversion is from old API - is it needed? SetLegacySettings still used NVENCPreset instead of 2 but changed it - is this correct? + if (presetType == PRESET_NVENC) { + if (strlen(preset) == 0) { + const char *oldParamName = PRESET_NVENC_DEP; + const char *oldValue = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", oldParamName)); + if (strlen(oldValue) != 0) { + preset = osn::EncoderUtils::convertNvencSimplePreset(oldValue); + blog(LOG_INFO, "NVENC preset converted from %s to %s", oldValue, preset); + } + } } - if (presetType) - preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType)); if (advanced) { obs_data_set_string(videoEncData, "preset", preset); @@ -465,19 +471,21 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() obs_data_set_int(videoEncData, "bitrate", config_get_uint(ConfigManager::getInstance().getBasic(), "SimpleOutput", "VBitrate")); } - if (strcmp(encId, APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || strcmp(encId, APPLE_HARDWARE_VIDEO_ENCODER) == 0) { + if (osn::EncoderUtils::getEncoderFamily(encId) == FAMILY_APPLE) { const char *profile = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "Profile")); if (profile) obs_data_set_string(videoEncData, "profile", profile); } - obs_encoder_t *videoEncoder = obs_video_encoder_create(encIdOBS, "video-encoder", videoEncData, nullptr); + obs_encoder_t *videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", videoEncData, nullptr); obs_data_release(videoEncData); + osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); + return videoEncoder; } -obs_encoder_t *osn::ISimpleStreaming::GetLegacyAudioEncoderSettings() +obs_encoder_t *osn::ISimpleStreaming::CreateLegacyAudioEncoder() { obs_data_t *audioEncData = obs_data_create(); obs_data_set_string(audioEncData, "rate_control", "CBR"); @@ -495,6 +503,8 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyAudioEncoderSettings() obs_encoder_t *audioEncoder = obs_audio_encoder_create("ffmpeg_aac", "audio", audioEncData, 0, nullptr); obs_data_release(audioEncData); + osn::AudioEncoder::Manager::GetInstance().allocate(audioEncoder); + return audioEncoder; } @@ -502,12 +512,8 @@ void osn::ISimpleStreaming::GetLegacySettings(void *data, const int64_t id, cons { osn::SimpleStreaming *streaming = new osn::SimpleStreaming(); - streaming->videoEncoder = GetLegacyVideoEncoderSettings(); - - osn::VideoEncoder::Manager::GetInstance().allocate(streaming->videoEncoder); - - streaming->audioEncoder = GetLegacyAudioEncoderSettings(); - osn::AudioEncoder::Manager::GetInstance().allocate(streaming->audioEncoder); + streaming->videoEncoder = CreateLegacyVideoEncoder(); + streaming->audioEncoder = CreateLegacyAudioEncoder(); streaming->useAdvanced = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "UseAdvanced"); streaming->enableTwitchVOD = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "VodTrackEnabled"); @@ -533,7 +539,7 @@ void osn::ISimpleStreaming::GetLegacySettings(void *data, const int64_t id, cons void osn::ISimpleStreaming::SetLegacyVideoEncoderSettings(obs_encoder_t *encoder) { - const char *encId = nullptr; + //const char *encId = nullptr; const char *encIdOBS = obs_encoder_get_id(encoder); obs_data_t *settings = obs_encoder_get_settings(encoder); @@ -543,27 +549,14 @@ void osn::ISimpleStreaming::SetLegacyVideoEncoderSettings(obs_encoder_t *encoder const char *custom = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings")); const char *preset = nullptr; - const char *presetType = nullptr; - if (strcmp(encIdOBS, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encId = SIMPLE_ENCODER_QSV; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - encId = SIMPLE_ENCODER_AMD; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encId = SIMPLE_ENCODER_NVENC; - } else if (strcmp(encIdOBS, ENCODER_NVENC_H264_TEX) == 0) { - presetType = "NVENCPreset"; - encId = ENCODER_NVENC_H264_TEX; - } else { - presetType = "Preset"; - encId = ADVANCED_ENCODER_X264; - } - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", encId); + + std::string presetType = osn::EncoderUtils::getEncoderPreset(encIdOBS); + std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); + + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", encId.c_str()); preset = obs_data_get_string(settings, "preset"); - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType, preset); + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str(), preset); obs_data_release(settings); } diff --git a/obs-studio-server/source/osn-simple-streaming.hpp b/obs-studio-server/source/osn-simple-streaming.hpp index f8eafe82c..a78a945a2 100644 --- a/obs-studio-server/source/osn-simple-streaming.hpp +++ b/obs-studio-server/source/osn-simple-streaming.hpp @@ -59,8 +59,8 @@ class ISimpleStreaming : public IStreaming { static void Start(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void Stop(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static obs_encoder_t *GetLegacyVideoEncoderSettings(); - static obs_encoder_t *GetLegacyAudioEncoderSettings(); + static obs_encoder_t *CreateLegacyVideoEncoder(); + static obs_encoder_t *CreateLegacyAudioEncoder(); static void SetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetLegacyVideoEncoderSettings(obs_encoder_t *encoder); static void SetLegacyAudioEncoderSettings(obs_encoder_t *encoder); diff --git a/obs-studio-server/source/osn-streaming.cpp b/obs-studio-server/source/osn-streaming.cpp index f447c28c3..54570f114 100644 --- a/obs-studio-server/source/osn-streaming.cpp +++ b/obs-studio-server/source/osn-streaming.cpp @@ -21,6 +21,7 @@ #include "osn-error.hpp" #include "shared.hpp" #include +#include "osn-encoders.hpp" //os_gettime_ns #include @@ -125,6 +126,11 @@ void osn::IStreaming::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); } + //verify the encoder is compatible before setting it if the service has been set + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + streaming->videoEncoder = encoder; rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); diff --git a/obs-studio-server/source/osn-streaming.hpp b/obs-studio-server/source/osn-streaming.hpp index e1b0366fb..8cd1d616d 100644 --- a/obs-studio-server/source/osn-streaming.hpp +++ b/obs-studio-server/source/osn-streaming.hpp @@ -46,6 +46,7 @@ class Streaming : public OutputSignals { network = new Network(); lastBytesSent = 0; lastBytesSentTime = 0; + simple = true; } virtual ~Streaming(); @@ -63,6 +64,7 @@ class Streaming : public OutputSignals { Network *network; uint64_t lastBytesSent; uint64_t lastBytesSentTime; + bool simple; bool isTwitchVODSupported(); void getDelayLegacySettings(); diff --git a/obs-studio-server/source/osn-video-encoder.cpp b/obs-studio-server/source/osn-video-encoder.cpp index 71a52e480..c1c041a9b 100644 --- a/obs-studio-server/source/osn-video-encoder.cpp +++ b/obs-studio-server/source/osn-video-encoder.cpp @@ -19,13 +19,14 @@ #include "osn-video-encoder.hpp" #include "osn-error.hpp" #include "shared.hpp" +#include "osn-encoders.hpp" void osn::VideoEncoder::Register(ipc::server &srv) { std::shared_ptr cls = std::make_shared("VideoEncoder"); cls->register_function( std::make_shared("Create", std::vector{ipc::type::String, ipc::type::String, ipc::type::String}, Create)); - cls->register_function(std::make_shared("GetTypes", std::vector{}, GeTypes)); + cls->register_function(std::make_shared("GetTypes", std::vector{}, GetTypes)); cls->register_function(std::make_shared("GetName", std::vector{ipc::type::UInt64}, GetName)); cls->register_function(std::make_shared("SetName", std::vector{ipc::type::UInt64, ipc::type::String}, SetName)); cls->register_function(std::make_shared("GetType", std::vector{ipc::type::UInt64}, GetType)); @@ -49,7 +50,15 @@ void osn::VideoEncoder::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +void osn::VideoEncoder::GetTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval) { rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); const char *typeId = nullptr; diff --git a/obs-studio-server/source/osn-video-encoder.hpp b/obs-studio-server/source/osn-video-encoder.hpp index 7d2cb3dff..25b8fa45a 100644 --- a/obs-studio-server/source/osn-video-encoder.hpp +++ b/obs-studio-server/source/osn-video-encoder.hpp @@ -42,7 +42,7 @@ class VideoEncoder { static void Register(ipc::server &); static void Create(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static void GeTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void GetTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetName(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetName(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetType(void *data, const int64_t id, const std::vector &args, std::vector &rval); From 11543a6a78c575a616fb9768e09fbc59414e9ee9 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Wed, 18 Feb 2026 14:28:26 -0600 Subject: [PATCH 02/12] Update CMakeLists.txt to add osn-encoders --- obs-studio-server/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/obs-studio-server/CMakeLists.txt b/obs-studio-server/CMakeLists.txt index 3f91439fc..14d4ae087 100644 --- a/obs-studio-server/CMakeLists.txt +++ b/obs-studio-server/CMakeLists.txt @@ -347,6 +347,8 @@ SET(osn-server_SOURCES "${PROJECT_SOURCE_DIR}/source/osn-advanced-replay-buffer.hpp" "${PROJECT_SOURCE_DIR}/source/osn-output-signals.cpp" "${PROJECT_SOURCE_DIR}/source/osn-output-signals.hpp" + "${PROJECT_SOURCE_DIR}/source/osn-encoders.cpp" + "${PROJECT_SOURCE_DIR}/source/osn-encoders.hpp" ###### utlity graphics ###### "${PROJECT_SOURCE_DIR}/source/gs-limits.h" From 69a0ea9201d0a6394a871c2ea6d92b09ca3f77d5 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Thu, 19 Feb 2026 15:18:05 -0600 Subject: [PATCH 03/12] Updates --- .../source/osn-advanced-recording.cpp | 2 +- obs-studio-server/source/osn-encoders.cpp | 4 ++-- obs-studio-server/source/osn-encoders.hpp | 14 +++++++------- .../source/osn-multitrack-video-output.cpp | 4 ++-- obs-studio-server/source/osn-recording.cpp | 2 +- obs-studio-server/source/osn-simple-recording.cpp | 2 +- obs-studio-server/source/osn-streaming.cpp | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index 11641c511..fec08245b 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -226,7 +226,7 @@ void osn::IAdvancedRecording::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } - if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, false)) + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->videoEncoder), recording->format, false)) PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); obs_output_set_video_encoder(recording->output, recording->videoEncoder); diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index a719899de..f0b14b22b 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -112,8 +112,8 @@ bool osn::EncoderUtils::isEncoderCompatibleStreaming(obs_service_t *service, con if (curEncoder.compare(encoderToFind) == 0) { if (isEncoderCompatible(encoderToFind, service, simpleMode, false, "", i)) { validEncoder = true; - break; } + break; } } @@ -132,8 +132,8 @@ bool osn::EncoderUtils::isEncoderCompatibleRecording(const char *encoderToFind, if (curEncoder.compare(encoderToFind) == 0) { if (isEncoderCompatible(encoderToFind, NULL, simpleMode, true, "", i)) { validEncoder = true; - break; } + break; } } diff --git a/obs-studio-server/source/osn-encoders.hpp b/obs-studio-server/source/osn-encoders.hpp index 347f3c437..835caa882 100644 --- a/obs-studio-server/source/osn-encoders.hpp +++ b/obs-studio-server/source/osn-encoders.hpp @@ -77,6 +77,7 @@ #define APPLE_SOFTWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264" #define APPLE_HARDWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264.gva" #define APPLE_HARDWARE_VIDEO_ENCODER_M1 "com.apple.videotoolbox.videoencoder.ave.avc" +#define APPLE_HARDWARE_VIDEO_ENCODER_HEVC "com.apple.videotoolbox.videoencoder.ave.hevc" #define SIMPLE_AUDIO_ENCODER_AAC "ffmpeg_aac" #define SIMPLE_AUDIO_ENCODER_OPUS "ffmpeg_opus" @@ -169,9 +170,13 @@ static std::vector videoEncoderOptions = { // Apple VT H264 Hardware Encoder {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER_M1, "", "", true, true, + // Apple VT H264 Hardware Encoder - get_simple_output_encoder RETURNED M1 FOR SIMPLE_ENCODER_APPLE_H264 SO MAKE THAT THE SIMPLE NAME AND M1 INTERNAL NAME + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", SIMPLE_ENCODER_APPLE_H264, + APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above + {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, + APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, // AMD HW H.264 {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, "AMDPreset", FAMILY_AMD}, @@ -186,11 +191,6 @@ static std::vector videoEncoderOptions = { // SVT-AV1 {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}}; -//} else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { -// return APPLE_HARDWARE_VIDEO_ENCODER_M1; -//} else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { -// return "com.apple.videotoolbox.videoencoder.ave.hevc"; - // Codect/Container support check. // from OBS code UI\window-basic-settings.cpp static const std::unordered_map> codecsForContainers = { diff --git a/obs-studio-server/source/osn-multitrack-video-output.cpp b/obs-studio-server/source/osn-multitrack-video-output.cpp index 3aed318e9..c5e8f1638 100644 --- a/obs-studio-server/source/osn-multitrack-video-output.cpp +++ b/obs-studio-server/source/osn-multitrack-video-output.cpp @@ -65,8 +65,8 @@ static void adjust_video_encoder_scaling(const obs_video_info &ovi, obs_encoder_ obs_encoder_set_scaled_size(video_encoder, requested_width, requested_height); obs_encoder_set_gpu_scale_type(video_encoder, encoder_config.gpu_scale_type.value_or(OBS_SCALE_BICUBIC)); obs_encoder_set_preferred_video_format(video_encoder, encoder_config.format.value_or(VIDEO_FORMAT_NV12)); - obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); - obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); + //obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); + //obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); } static uint32_t closest_divisor(const obs_video_info &ovi, const media_frames_per_second &target_fps) diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 0d5057137..5e05bc5bf 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -58,7 +58,7 @@ void osn::IRecording::SetVideoEncoder(void *data, const int64_t id, const std::v } //verify the encoder is compatible before setting it - if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(encoder), recording->fileFormat, recording->simple)) { + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(encoder), recording->format, recording->simple)) { PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); } diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 9d6adb506..917672d6a 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -372,7 +372,7 @@ void osn::ISimpleRecording::Start(void *data, const int64_t id, const std::vecto PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } - if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, true)) { + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->videoEncoder), recording->format, true)) { PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); } diff --git a/obs-studio-server/source/osn-streaming.cpp b/obs-studio-server/source/osn-streaming.cpp index 54570f114..bf3997756 100644 --- a/obs-studio-server/source/osn-streaming.cpp +++ b/obs-studio-server/source/osn-streaming.cpp @@ -126,8 +126,8 @@ void osn::IStreaming::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); } - //verify the encoder is compatible before setting it if the service has been set - if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + //verify the encoder is compatible before setting it + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(encoder), streaming->simple)) { PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); } From 284a9c87563ef36f791edec89bd07cd1474a7787 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Wed, 25 Feb 2026 09:52:49 -0600 Subject: [PATCH 04/12] Update nodeobs_service.cpp --- obs-studio-server/source/nodeobs_service.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index ee8ff85d5..296415bea 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -842,7 +842,6 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) obs_data_t *settings = obs_encoder_defaults(encoderId); obs_data_apply(settings, data); - blog(LOG_INFO, "MLH create encoder type %s", encoderId); obs_encoder_t *new_encoder = obs_video_encoder_create(encoderId, encoder_name.c_str(), settings, nullptr); OBS_service::setStreamingEncoder(new_encoder, serviceId); @@ -2023,7 +2022,6 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId // Here and in other places we repeat the same pattern. // Avoiding case when to an output there might not be any attached video encoder which can lead to crash. - blog(LOG_INFO, "MLH create encoder type %s", encoderID.c_str()); std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID.c_str()); obs_encoder_t *streamingEncoder = obs_video_encoder_create(encoderID.c_str(), encoder_name.c_str(), nullptr, nullptr); setStreamingEncoder(streamingEncoder, serviceId); From accca08607eb910a94a1277340b50095fb3b04fc Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Wed, 25 Feb 2026 10:37:16 -0600 Subject: [PATCH 05/12] Remove logging --- obs-studio-server/source/nodeobs_settings.cpp | 6 ------ obs-studio-server/source/osn-encoders.cpp | 9 --------- 2 files changed, 15 deletions(-) diff --git a/obs-studio-server/source/nodeobs_settings.cpp b/obs-studio-server/source/nodeobs_settings.cpp index 23a0ff6bc..856a07fe6 100644 --- a/obs-studio-server/source/nodeobs_settings.cpp +++ b/obs-studio-server/source/nodeobs_settings.cpp @@ -327,7 +327,6 @@ SubCategory OBS_settings::serializeSettingsData(const std::string &nameSubCatego } else if (section.compare("SimpleOutput") == 0) { if (param.name.compare(PRESET_NVENC) == 0) { currentValue = config_get_string(config, "SimpleOutput", param.name.c_str()); - blog(LOG_INFO, "MLH serializeSettingsData: currentValue %s", currentValue); if (currentValue == NULL) { const char *oldParamName = PRESET_NVENC_DEP; const char *oldValue = config_get_string(config, "SimpleOutput", oldParamName); @@ -1087,7 +1086,6 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti defaultPreset = "balanced"; } else if (presetName == PRESET_NVENC) { - blog(LOG_INFO, "MLH: NvencPreset2: getting NVENC presets from OBS for encoder %s", encoder.c_str()); preset = createSettingEntry(PRESET_NVENC, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); obs_properties_t *props = obs_get_encoder_properties(ADVANCED_ENCODER_NVENC); @@ -1097,7 +1095,6 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti for (size_t i = 0; i < num; i++) { const char *name = obs_property_list_item_name(p, i); const char *val = obs_property_list_item_string(p, i); - blog(LOG_INFO, "MLH adding entry to NvencPreset2: name=%s, value=%s", name, val); preset.push_back(std::make_pair(name, ipc::value(val))); } @@ -1821,7 +1818,6 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b std::string encoder_name = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, false, encoderID); if (!fileExist) { - blog(LOG_INFO, "MLH create encoder type %s", encoderID); streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), nullptr, nullptr); OBS_service::setStreamingEncoder(streamingEncoder, StreamServiceId::Main); @@ -1834,7 +1830,6 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b osn::EncoderUtils::updateNvencPresets(data, encoderID); obs_data_apply(settings, data); - blog(LOG_INFO, "MLH create encoder type %s", encoderID); streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), settings, nullptr); OBS_service::setStreamingEncoder(streamingEncoder, StreamServiceId::Main); } @@ -1996,7 +1991,6 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete memcpy(recEncoder.currentValue.data(), recEncoderCurrentValue, strlen(recEncoderCurrentValue)); recEncoder.sizeOfCurrentValue = strlen(recEncoderCurrentValue); - blog(LOG_INFO, "MLH get advanced RECORDING encoder option for current service"); std::vector> encoderValues3; std::vector> Encoder; diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index f0b14b22b..1edab79b9 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -96,7 +96,6 @@ bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service if (!containerSupportsCodec(container, codec)) return false; } - blog(LOG_INFO, "MLH encoder compatible for simpleMode = %d with current settings: %s", simpleMode, encoderName.c_str()); return true; } @@ -160,8 +159,6 @@ std::string osn::EncoderUtils::getInternalEncoderFromSimple(const char *encoder) //if there is a backup, check if simple_internal_name is available, return backup if not //else if no simple_internal_name, return simple_name //else return simple_internal_name - //blog(LOG_INFO, "MLH ConvertSimpleEncoder - checking encoder %s internal %s backup %s", curEnc.simple_name.c_str(), - // curEnc.simple_internal_name.c_str(), curEnc.backup.c_str()); if (encoder == curEnc.simple_name) { if (!curEnc.backup.empty() && !isEncoderRegistered(curEnc.simple_internal_name)) encoderName = curEnc.backup; @@ -187,8 +184,6 @@ std::string osn::EncoderUtils::getSimpleEncoderFromInternal(const char *encoder) std::string encoderName = ADVANCED_ENCODER_X264; bool found = false; - //blog(LOG_INFO, "MLH ConvertSimpleEncoder - converting simple encoder %s", encoder); - for (const auto curEnc : videoEncoderOptions) { if (encoder == curEnc.simple_internal_name) { encoderName = curEnc.simple_name; @@ -207,11 +202,7 @@ std::string osn::EncoderUtils::getEncoderPreset(const char *encoder) std::string preset = DEFAULT_PRESET; bool found = false; - //blog(LOG_INFO, "MLH GetEncoderPreset for encoder %s", encoder); - for (const auto curEnc : videoEncoderOptions) { - //blog(LOG_INFO, "MLH GetEncoderPreset - checking encoder %s (advanced) %s (simple) preset %s", curEnc.advanced_name.c_str(), - // curEnc.simple_name.c_str(), curEnc.simple_name.c_str(), curEnc.preset.c_str()); if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { preset = curEnc.preset; found = true; From 970d8f139ee872d16115debc3a2a353c6fdc9bdb Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Tue, 17 Feb 2026 11:15:48 -0600 Subject: [PATCH 06/12] Updates Remove logging Update nodeobs_service.cpp Update CMakeLists.txt to add osn-encoders Consolidate encoder processing Move encoder processing to its own file so ensure all source files are using the same encoders/settings/logic. --- obs-studio-server/CMakeLists.txt | 2 + obs-studio-server/source/nodeobs_api.cpp | 2 +- .../source/nodeobs_autoconfig.cpp | 1 + .../source/nodeobs_configManager.cpp | 1 + obs-studio-server/source/nodeobs_service.cpp | 130 ++--- obs-studio-server/source/nodeobs_service.h | 44 +- obs-studio-server/source/nodeobs_settings.cpp | 484 ++---------------- .../source/osn-advanced-recording.cpp | 27 +- .../source/osn-advanced-recording.hpp | 1 + .../source/osn-advanced-streaming.cpp | 47 +- .../source/osn-advanced-streaming.hpp | 1 + obs-studio-server/source/osn-encoders.cpp | 444 ++++++++++++++++ obs-studio-server/source/osn-encoders.hpp | 210 ++++++++ .../source/osn-multitrack-video-output.cpp | 4 +- obs-studio-server/source/osn-recording.cpp | 11 + obs-studio-server/source/osn-recording.hpp | 2 + .../source/osn-simple-recording.cpp | 110 ++-- .../source/osn-simple-recording.hpp | 4 +- .../source/osn-simple-streaming.cpp | 93 ++-- .../source/osn-simple-streaming.hpp | 4 +- obs-studio-server/source/osn-streaming.cpp | 6 + obs-studio-server/source/osn-streaming.hpp | 2 + .../source/osn-video-encoder.cpp | 15 +- .../source/osn-video-encoder.hpp | 2 +- 24 files changed, 951 insertions(+), 696 deletions(-) create mode 100644 obs-studio-server/source/osn-encoders.cpp create mode 100644 obs-studio-server/source/osn-encoders.hpp diff --git a/obs-studio-server/CMakeLists.txt b/obs-studio-server/CMakeLists.txt index 3f91439fc..14d4ae087 100644 --- a/obs-studio-server/CMakeLists.txt +++ b/obs-studio-server/CMakeLists.txt @@ -347,6 +347,8 @@ SET(osn-server_SOURCES "${PROJECT_SOURCE_DIR}/source/osn-advanced-replay-buffer.hpp" "${PROJECT_SOURCE_DIR}/source/osn-output-signals.cpp" "${PROJECT_SOURCE_DIR}/source/osn-output-signals.hpp" + "${PROJECT_SOURCE_DIR}/source/osn-encoders.cpp" + "${PROJECT_SOURCE_DIR}/source/osn-encoders.hpp" ###### utlity graphics ###### "${PROJECT_SOURCE_DIR}/source/gs-limits.h" diff --git a/obs-studio-server/source/nodeobs_api.cpp b/obs-studio-server/source/nodeobs_api.cpp index 34fffa7e9..f7e1d9007 100644 --- a/obs-studio-server/source/nodeobs_api.cpp +++ b/obs-studio-server/source/nodeobs_api.cpp @@ -1008,7 +1008,7 @@ void OBS_API::OBS_API_initAPI(void *data, const int64_t id, const std::vector #include "osn-error.hpp" #include "shared.hpp" +#include "osn-encoders.hpp" enum class Type { Invalid, Streaming, Recording }; diff --git a/obs-studio-server/source/nodeobs_configManager.cpp b/obs-studio-server/source/nodeobs_configManager.cpp index ad855d6d4..61c0594f3 100644 --- a/obs-studio-server/source/nodeobs_configManager.cpp +++ b/obs-studio-server/source/nodeobs_configManager.cpp @@ -26,6 +26,7 @@ #include #include "shared.hpp" #include "nodeobs_service.h" +#include "osn-encoders.hpp" void ConfigManager::setAppdataPath(const std::string &path) { diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index cbab155e0..296415bea 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -27,6 +27,7 @@ #include "utility.hpp" #include #include "osn-vcam.hpp" +#include "osn-encoders.hpp" #include "osn-multitrack-video-configuration.hpp" #include "osn-audio-bitrate.hpp" @@ -827,7 +828,8 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) encoderId = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } - if (encoderId == NULL || !EncoderAvailable(encoderId) || isInvalidEncoder(encoderId)) { + //TODO - does this cause issues because settings won't match? + if (encoderId == NULL || !osn::EncoderUtils::isEncoderRegistered(encoderId) || osn::EncoderUtils::isInvalidAppleEncoder(encoderId)) { encoderId = ADVANCED_ENCODER_X264; } @@ -1029,7 +1031,7 @@ static void remove_reserved_file_characters(std::string &s) replace(s.begin(), s.end(), '<', '_'); } -bool OBS_service::createVideoRecordingEncoder() +bool OBS_service::createDefaultSimpleVideoRecordingEncoder() { std::string encoderName = GetVideoEncoderName(StreamServiceId::Main, true, true, ADVANCED_ENCODER_X264); obs_encoder_t *newRecordingEncoder = obs_video_encoder_create(ADVANCED_ENCODER_X264, encoderName.c_str(), nullptr, nullptr); @@ -1549,46 +1551,15 @@ void OBS_service::LoadRecordingPreset_Lossy(const char *encoderId) throw "Failed to create video recording encoder (simple output)"; } -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return ADVANCED_ENCODER_X264; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return ADVANCED_ENCODER_X264; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return ADVANCED_ENCODER_AMD; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return ADVANCED_ENCODER_AMD_HEVC; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if ((strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) || (strcmp(encoder, ENCODER_NVENC_H264_TEX) == 0)) { - return EncoderAvailable(ENCODER_NVENC_H264_TEX) ? ENCODER_NVENC_H264_TEX : ADVANCED_ENCODER_NVENC; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable(ENCODER_NVENC_HEVC_TEX) ? ENCODER_NVENC_HEVC_TEX : "ffmpeg_hevc_nvenc"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return ENCODER_NVENC_AV1_TEX; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return APPLE_HARDWARE_VIDEO_ENCODER_M1; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; - } - - blog(LOG_WARNING, "get_simple_output_encoder - encoder %s is not found, creating a default one", encoder); - - return ADVANCED_ENCODER_X264; -} - void OBS_service::updateVideoRecordingEncoder(bool isSimpleMode) { if (isRecording && rpUsesRec) return; + const char *section = isSimpleMode ? "SimpleOutput" : "AdvOut"; + const char *quality = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); + const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), section, "RecEncoder"); videoEncoder = encoder; videoQuality = quality; @@ -1598,12 +1569,11 @@ void OBS_service::updateVideoRecordingEncoder(bool isSimpleMode) lowCPUx264 = false; if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); + LoadRecordingPreset_Lossy((osn::EncoderUtils::getInternalEncoderFromSimple(encoder)).c_str()); usingRecordingPreset = true; updateVideoRecordingEncoderSettings(); } else { - const char *recordingEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); - if (recordingEncoder && strcmp(recordingEncoder, ENCODER_NVENC_H264_TEX) != 0) { + if (encoder && strcmp(encoder, ENCODER_NVENC_H264_TEX) != 0) { unsigned int cx = 0; unsigned int cy = 0; @@ -2036,39 +2006,24 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId bool enforceBitrate = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "EnforceBitrate"); const char *custom = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings"); const char *encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); - const char *encoderID = nullptr; - const char *presetType = nullptr; + std::string encoderID = ""; + std::string presetType = ""; const char *preset = nullptr; if (encoder != NULL) { - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0 || strcmp(encoder, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encoderID = ADVANCED_ENCODER_QSV; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0 || strcmp(encoder, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - UpdateStreamingSettings_amd(h264Settings, videoBitrate); - encoderID = ADVANCED_ENCODER_AMD; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encoder, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encoderID = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encoder, ENCODER_NVENC_H264_TEX) == 0) { - presetType = "NVENCPreset"; - encoderID = ENCODER_NVENC_H264_TEX; - } else if (strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER) == 0) { - encoderID = APPLE_HARDWARE_VIDEO_ENCODER; - } else if (strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - encoderID = APPLE_HARDWARE_VIDEO_ENCODER_M1; - } else { - presetType = "Preset"; - encoderID = ADVANCED_ENCODER_X264; - } - if (presetType) - preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType); + std::string presetType = osn::EncoderUtils::getEncoderPreset(encoder); + //TODO do we need to check low CPU here before we convert? + encoderID = osn::EncoderUtils::getInternalEncoderFromSimple(encoder); + + //TODO - do we need to handle PresetNvenc/2 here? + + if (!presetType.empty()) + preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); // Here and in other places we repeat the same pattern. // Avoiding case when to an output there might not be any attached video encoder which can lead to crash. - std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID); - obs_encoder_t *streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), nullptr, nullptr); + std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID.c_str()); + obs_encoder_t *streamingEncoder = obs_video_encoder_create(encoderID.c_str(), encoder_name.c_str(), nullptr, nullptr); setStreamingEncoder(streamingEncoder, serviceId); } @@ -2109,8 +2064,7 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId obs_encoder_set_preferred_video_format(videoStreamingEncoder[serviceId], VIDEO_FORMAT_NV12); } - if (strcmp(encoder, APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - strcmp(encoder, APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { + if (osn::EncoderUtils::getEncoderFamily(encoderID.c_str()) == FAMILY_APPLE) { const char *profile = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "Profile"); if (profile) obs_data_set_string(h264Settings, "profile", profile); @@ -2575,29 +2529,22 @@ void OBS_service::updateVideoRecordingEncoderSettings() { bool ultra_hq = (videoQuality == "HQ"); int crf = CalcCRF(ultra_hq ? 16 : 23); + std::string encFamily = osn::EncoderUtils::getEncoderFamily(videoEncoder.c_str()); - if (videoEncoder.compare(SIMPLE_ENCODER_X264) == 0 || videoEncoder.compare(ADVANCED_ENCODER_X264) == 0 || - videoEncoder.compare(SIMPLE_ENCODER_X264_LOWCPU) == 0) { + if (encFamily == FAMILY_OBS) UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_QSV) == 0 || videoEncoder.compare(ADVANCED_ENCODER_QSV) == 0) { - UpdateRecordingSettings_qsv11(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_AMD) == 0 || videoEncoder.compare(SIMPLE_ENCODER_AMD_HEVC) == 0 || - videoEncoder.compare(ADVANCED_ENCODER_AMD) == 0) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder.compare(SIMPLE_ENCODER_NVENC) == 0 || videoEncoder.compare(ADVANCED_ENCODER_NVENC) == 0) { - UpdateRecordingSettings_nvenc(crf); - } else if (videoEncoder.compare(ENCODER_NVENC_H264_TEX) == 0) { + else if (encFamily == FAMILY_NVENC) UpdateRecordingSettings_nvenc(crf); - } else if (videoEncoder.compare(SIMPLE_ENCODER_NVENC_HEVC) == 0) { + else if (encFamily == FAMILY_NVENC_HEVC) UpdateRecordingSettings_nvenc_hevc(crf); - } else if (videoEncoder.compare(APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || videoEncoder.compare(APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - videoEncoder.compare(APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - /* These are magic numbers. 0 - 100, more is better. */ + else if (encFamily == FAMILY_QSV) + UpdateRecordingSettings_qsv11(crf); + else if (encFamily == FAMILY_AMD) + UpdateRecordingSettings_amd_cqp(crf); + else if (encFamily == FAMILY_APPLE) UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); - } + else + blog(LOG_WARNING, "Unable to update settings with unknown encoder family."); } obs_encoder_t *OBS_service::getStreamingEncoder(StreamServiceId serviceId) @@ -3351,17 +3298,6 @@ void OBS_service::setupVodTrack(bool isSimpleMode) } } } - -bool OBS_service::isInvalidEncoder(const char *encoderID) -{ -#if defined(__APPLE__) - // disable this encoder; not functioning properly - return strcmp(encoderID, APPLE_SOFTWARE_VIDEO_ENCODER) == 0; -#else - return false; -#endif -} - std::string GetFormatExt(const std::string container) { std::string ext = container; diff --git a/obs-studio-server/source/nodeobs_service.h b/obs-studio-server/source/nodeobs_service.h index f29dc974b..adcb1fe88 100644 --- a/obs-studio-server/source/nodeobs_service.h +++ b/obs-studio-server/source/nodeobs_service.h @@ -44,48 +44,7 @@ #include #endif -#ifdef WIN32 -#define SIMPLE_ENCODER_X264 "x264" -#elif __APPLE__ -#define SIMPLE_ENCODER_X264 "obs_x264" -#endif -#define SIMPLE_ENCODER_X264 "x264" -#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" -#define SIMPLE_ENCODER_QSV "qsv" -#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" -#define SIMPLE_ENCODER_NVENC "nvenc" -#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" -#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" -#define SIMPLE_ENCODER_AMD "amd" -#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" -#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" -#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" -#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" - -#define ADVANCED_ENCODER_X264 "obs_x264" -#define ADVANCED_ENCODER_QSV "obs_qsv11" -#define ADVANCED_ENCODER_NVENC "ffmpeg_nvenc" -#define ADVANCED_ENCODER_AMD "h264_texture_amf" -#define ADVANCED_ENCODER_AMD_HEVC "h265_texture_amf" - -#define ENCODER_NVENC_H264_TEX "obs_nvenc_h264_tex" -#define ENCODER_NVENC_HEVC_TEX "obs_nvenc_hevc_tex" -#define ENCODER_NVENC_AV1_TEX "obs_nvenc_av1_tex" - -// These 3 are deprecated -#define ENCODER_JIM_NVENC "jim_nvenc" -#define ENCODER_JIM_HEVC_NVENC "jim_hevc_nvenc" -#define ENCODER_JIM_AV1_NVENC "jim_av1_nvenc" - -#define ENCODER_AV1_SVT_FFMPEG "ffmpeg_svt_av1" -#define ENCODER_AV1_AOM_FFMPEG "ffmpeg_aom_av1" - -#define APPLE_SOFTWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264" -#define APPLE_HARDWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264.gva" -#define APPLE_HARDWARE_VIDEO_ENCODER_M1 "com.apple.videotoolbox.videoencoder.ave.avc" - #define ARCHIVE_NAME "archive_aac" - #define SIMPLE_AUDIO_ENCODER_AAC "ffmpeg_aac" #define SIMPLE_AUDIO_ENCODER_OPUS "ffmpeg_opus" @@ -211,7 +170,7 @@ class OBS_service { static bool createVideoStreamingEncoder(StreamServiceId serviceId); static std::string GetVideoEncoderName(StreamServiceId serviceId, bool isSimpleMode, bool recording, const char *encoder); static void createAudioStreamingEncoder(StreamServiceId serviceId, bool isSimpleMode, const std::string &encoder_id); - static bool createVideoRecordingEncoder(); + static bool createDefaultSimpleVideoRecordingEncoder(); static obs_encoder_t *getStreamingEncoder(StreamServiceId serviceId); static void setStreamingEncoder(obs_encoder_t *encoder, StreamServiceId serviceId); static obs_encoder_t *getRecordingEncoder(void); @@ -293,5 +252,4 @@ class OBS_service { static void stopAllOutputs(void); static void setupVodTrack(bool isSimpleMode); static void clearArchiveVodEncoder(); - static bool isInvalidEncoder(const char *encoderID); }; diff --git a/obs-studio-server/source/nodeobs_settings.cpp b/obs-studio-server/source/nodeobs_settings.cpp index f1928f9f0..856a07fe6 100644 --- a/obs-studio-server/source/nodeobs_settings.cpp +++ b/obs-studio-server/source/nodeobs_settings.cpp @@ -22,6 +22,7 @@ #include "shared.hpp" #include "memory-manager.h" #include "osn-video.hpp" +#include "osn-encoders.hpp" #ifdef WIN32 #include @@ -45,9 +46,6 @@ std::vector tabStreamTypes; const char *currentServiceName; std::vector currentAudioSettings; -bool update_nvenc_presets(obs_data_t *data, const char *encoderId); -const char *convert_nvenc_simple_preset(const char *old_preset); - /* some nice default output resolution vals */ static const double vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0}; @@ -293,7 +291,6 @@ SubCategory OBS_settings::serializeSettingsData(const std::string &nameSubCatego config_t *config, const std::string §ion, bool isVisible, bool isEnabled) { SubCategory sc; - for (int i = 0; i < entries.size(); i++) { Parameter param; @@ -328,13 +325,13 @@ SubCategory OBS_settings::serializeSettingsData(const std::string &nameSubCatego currentValue = config_get_string(config, "AdvVideo", param.name.c_str()); } } else if (section.compare("SimpleOutput") == 0) { - if (param.name.compare("NVENCPreset2") == 0) { + if (param.name.compare(PRESET_NVENC) == 0) { currentValue = config_get_string(config, "SimpleOutput", param.name.c_str()); if (currentValue == NULL) { - const char *oldParamName = "NVENCPreset"; + const char *oldParamName = PRESET_NVENC_DEP; const char *oldValue = config_get_string(config, "SimpleOutput", oldParamName); if (oldValue != NULL) { - currentValue = convert_nvenc_simple_preset(oldValue); + currentValue = osn::EncoderUtils::convertNvencSimplePreset(oldValue); blog(LOG_INFO, "NVENC presets converted from %s to %s", oldValue, currentValue); } } @@ -947,213 +944,35 @@ bool OBS_settings::saveStreamSettings(std::vector streamSettings, S return true; } -bool EncoderAvailable(const std::string &encoder) -{ - const char *val; - int i = 0; - - while (obs_enum_encoder_types(i++, &val)) { - if (val == nullptr) - continue; - if (std::string(val) == encoder) - return true; - } - - return false; -} - -static bool isEncoderAvailableForStreaming(const char *encoder, obs_service_t *service) -{ - if (!encoder || !service) - return false; - - auto supportedCodecs = obs_service_get_supported_video_codecs(service); - auto encoderCodec = obs_get_encoder_codec(encoder); - - if (!supportedCodecs || !encoderCodec) - return false; - - while (*supportedCodecs) { - if (strcmp(*supportedCodecs, encoderCodec) == 0) - return true; - supportedCodecs++; - } - - return false; -} - -// Codect/Container support check. -// from OBS code UI\window-basic-settings.cpp -static const std::unordered_map> codec_compat = { - // Technically our muxer supports HEVC and AV1 as well, but nothing else does - {"flv", {"h264", "aac"}}, - {"mpegts", {"h264", "hevc", "aac", "opus"}}, - {"hls", {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support - {"mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, - {"mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, - {"fragmented_mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, - {"fragmented_mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, - // MKV supports everything - {"mkv", {}}, -}; - -static bool ContainerSupportsCodec(const std::string &container, const std::string &codec) -{ - auto iter = codec_compat.find(container); - if (iter == codec_compat.end()) - return false; - - auto codecs = iter->second; - // Assume everything is supported - if (codecs.empty()) - return true; - return codecs.count(codec) > 0; -} - -static bool isNvencAvailableForSimpleMode() -{ - // Only available if config already uses it - const char *current_stream_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); - const char *current_rec_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); - bool nvenc_used_streaming = (current_stream_encoder && strcmp(current_stream_encoder, SIMPLE_ENCODER_NVENC) == 0); - bool nvenc_used_recording = (current_rec_encoder && strcmp(current_rec_encoder, SIMPLE_ENCODER_NVENC) == 0); - - return (nvenc_used_streaming || nvenc_used_recording) && EncoderAvailable(ADVANCED_ENCODER_NVENC); -} void OBS_settings::getAvailableAudioEncoders(std::vector> *encoders, bool simple, bool recording, const std::string &container) { - if (EncoderAvailable(SIMPLE_AUDIO_ENCODER_AAC)) + if (osn::EncoderUtils::isEncoderRegistered(SIMPLE_AUDIO_ENCODER_AAC)) encoders->push_back(std::make_pair("AAC (Default)", ipc::value(SIMPLE_AUDIO_ENCODER_AAC))); - if (recording && EncoderAvailable(SIMPLE_AUDIO_ENCODER_OPUS)) + if (recording && osn::EncoderUtils::isEncoderRegistered(SIMPLE_AUDIO_ENCODER_OPUS)) encoders->push_back(std::make_pair("Opus", ipc::value(SIMPLE_AUDIO_ENCODER_OPUS))); } -class EncoderSettings { -public: - std::string advanced_title; - std::string advanced_name; - std::string simple_title; - std::string simple_name; - std::string simple_intenal_name; - bool recording; - bool streaming; - bool check_availability; - bool check_availability_streaming; - bool check_availability_format; - bool only_for_reuse_simple; - const std::string getSimpleName() const { return simple_intenal_name.empty() ? simple_name : simple_intenal_name; } -}; - -std::vector encoders_set = { - // Software x264 - {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, true, true, false, false, true, false}, - // Software x264 low CPU (only for recording) - {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, true, false, false, false, - true, false}, - // QuickSync H.264 (v1, deprecated) - // This line left here for reference - // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, - // QuickSync H.264 (v2, new) - {"QuickSync H.264", "obs_qsv11_v2", "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, "obs_qsv11_v2", true, true, true, false, true, false}, - // QuickSync AV1 - {"QuickSync AV1", "obs_qsv11_av1", "", "", "", true, true, true, false, true, false}, - // QuickSync HEVC - {"QuickSync HEVC", "obs_qsv11_hevc", "", "", "", true, true, true, false, true, false}, - // NVIDIA NVENC H.264 - {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ADVANCED_ENCODER_NVENC, true, true, true, false, true, true}, - // NVIDIA NVENC H.264 (new) - {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", true, true, true, false, true, false}, - // NVIDIA NVENC HEVC - {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, true, true, true, true, true, - false}, - // NVIDIA NVENC AV1 - {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "Hardware (NVENC, AV1)", ENCODER_NVENC_AV1_TEX, "", true, true, true, true, true, false}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", true, true, true, false, - true, false}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, - false, true, false}, - // AMD HW H.264 - {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, true, true, true, false, true, false}, - // AMD HW H.265 (HEVC) - {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, true, true, true, true, - true, false}, - // AMD HW AV1 - {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", "av1", SIMPLE_ENCODER_AMD_AV1, true, true, true, true, true, false}, - // AOM AV1 - {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", true, true, true, false, true, false}, - // SVT-AV1 - {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", true, true, true, false, true, false}}; - void OBS_settings::getSimpleAvailableEncoders(std::vector> *list, bool recording, const std::string &container) { - for (const auto &encoderSetting : encoders_set) { - if (encoderSetting.simple_name.empty()) - continue; - - if (!recording && !encoderSetting.streaming) - continue; - - if (recording && !encoderSetting.recording) - continue; - - if (encoderSetting.check_availability && !EncoderAvailable(encoderSetting.getSimpleName())) - continue; - - if (!recording && encoderSetting.check_availability_streaming && - !isEncoderAvailableForStreaming(encoderSetting.getSimpleName().c_str(), OBS_service::getService(StreamServiceId::Main))) - continue; - - if (encoderSetting.only_for_reuse_simple && !isNvencAvailableForSimpleMode()) - continue; - - if (recording && encoderSetting.check_availability_format) { - const char *codec = obs_get_encoder_codec(encoderSetting.getSimpleName().c_str()); - if (!codec) { - blog(LOG_DEBUG, "[ENCODER_SKIPPED] codec is null"); - continue; - } - if (!ContainerSupportsCodec(container, codec)) - continue; + for (int i = 0; i < osn::EncoderUtils::videoEncoderOptions.size(); i++) { + if (osn::EncoderUtils::isEncoderCompatible(osn::EncoderUtils::videoEncoderOptions[i].getSimpleName(), + OBS_service::getService(StreamServiceId::Main), true, recording, container, i)) { + list->push_back(std::make_pair(osn::EncoderUtils::videoEncoderOptions[i].simple_title, + ipc::value(osn::EncoderUtils::videoEncoderOptions[i].simple_name))); } - - list->push_back(std::make_pair(encoderSetting.simple_title, ipc::value(encoderSetting.simple_name))); } } void OBS_settings::getAdvancedAvailableEncoders(std::vector> *list, bool recording, const std::string &container) { - for (const auto &encoderSetting : encoders_set) { - if (encoderSetting.advanced_name.empty()) - continue; - - if (!recording && !encoderSetting.streaming) - continue; - - if (recording && !encoderSetting.recording) - continue; - - if (encoderSetting.check_availability && !EncoderAvailable(encoderSetting.advanced_name)) - continue; - - if (!recording && encoderSetting.check_availability_streaming && - !isEncoderAvailableForStreaming(encoderSetting.advanced_name.c_str(), OBS_service::getService(StreamServiceId::Main))) - continue; - - if (recording && encoderSetting.check_availability_format) { - const char *codec = obs_get_encoder_codec(encoderSetting.advanced_name.c_str()); - if (!codec) { - blog(LOG_WARNING, "[SUPPORTED_CODECS] codec is null for %s", encoderSetting.advanced_name.c_str()); - continue; - } - if (!ContainerSupportsCodec(container, codec)) - continue; + for (int i = 0; i < osn::EncoderUtils::videoEncoderOptions.size(); i++) { + if (osn::EncoderUtils::isEncoderCompatible(osn::EncoderUtils::videoEncoderOptions[i].advanced_name, + OBS_service::getService(StreamServiceId::Main), false, recording, container, i)) { + list->push_back(std::make_pair(osn::EncoderUtils::videoEncoderOptions[i].advanced_title, + ipc::value(osn::EncoderUtils::videoEncoderOptions[i].advanced_name))); } - - list->push_back(std::make_pair(encoderSetting.advanced_title, ipc::value(encoderSetting.advanced_name))); } } @@ -1173,59 +992,12 @@ static const char *translate_macvth264_encoder(std::string encoder) } #endif -static bool isOldJimNvencEncoder(const std::string &encoderId) -{ - return encoderId == ENCODER_JIM_NVENC || encoderId == ENCODER_JIM_HEVC_NVENC || encoderId == ENCODER_JIM_AV1_NVENC; -} - -// This code should be removed when JIM_ encoders will be removed from OBS -static void converOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, - const std::string &recordingEncoderSetting) -{ - const std::string streamEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), streamEncoderSetting.c_str())); - if (isOldJimNvencEncoder(streamEncoder)) { - blog(LOG_INFO, "Converting stream encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), streamEncoder.c_str(), - ENCODER_NVENC_H264_TEX); - config_set_string(config, configSection.c_str(), streamEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); - } - - const std::string recordingEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), recordingEncoderSetting.c_str())); - if (isOldJimNvencEncoder(recordingEncoder)) { - blog(LOG_INFO, "Converting recording encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), recordingEncoder.c_str(), - ENCODER_NVENC_H264_TEX); - config_set_string(config, configSection.c_str(), recordingEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); - } -} - -static bool validateEncoderForService(StreamServiceId serviceId, const char *encoderToFind) -{ - bool validEncoder = false; - - //have encoder - find in encoders_set, validate 'streaming' flag and check availability based on 'check_availability_streaming' flag - for (int i = 0; i < encoders_set.size(); i++) { - if (std::string(encoderToFind) == encoders_set[i].simple_name || std::string(encoderToFind) == encoders_set[i].advanced_name) { - if (encoders_set[i].streaming) { - if (encoders_set[i].check_availability_streaming) { - if (isEncoderAvailableForStreaming(encoderToFind, OBS_service::getService(serviceId))) { - validEncoder = true; - break; - } - } else { - validEncoder = true; - } - } - break; - } - } - - return validEncoder; -} - void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { const char *mode = NULL; const char *curEncoder = NULL; bool validEncoder = false; + bool simpleMode = false; std::string serviceToCheck = args[0].value_str; //get mode and configured encoder @@ -1233,18 +1005,21 @@ void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, con if (mode == NULL) { mode = "Simple"; } - if (strcmp(mode, "Advanced") == 0) { + simpleMode = (strcmp(mode, "Simple") == 0); + + if (!simpleMode) { curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } else { curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamingEncoder"); } if (serviceToCheck == "Both") { - validEncoder = validateEncoderForService(StreamServiceId::Main, curEncoder) && validateEncoderForService(StreamServiceId::Second, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Main), curEncoder, simpleMode) && + osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Second), curEncoder, simpleMode); } else if (serviceToCheck == "Stream") { - validEncoder = validateEncoderForService(StreamServiceId::Main, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Main), curEncoder, simpleMode); } else if (serviceToCheck == "StreamSecond") { - validEncoder = validateEncoderForService(StreamServiceId::Second, curEncoder); + validEncoder = osn::EncoderUtils::isEncoderCompatibleStreaming(OBS_service::getService(StreamServiceId::Second), curEncoder, simpleMode); } rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); @@ -1253,7 +1028,7 @@ void OBS_settings::OBS_settings_isValidEncoder(void *data, const int64_t id, con void OBS_settings::getSimpleOutputSettings(std::vector *outputSettings, config_t *config, bool isCategoryEnabled) { - converOldJimNvencEncoder(config, "SimpleOutput", "StreamEncoder", "RecEncoder"); + osn::EncoderUtils::convertOldJimNvencEncoder(config, "SimpleOutput", "StreamEncoder", "RecEncoder"); std::vector>> entries; @@ -1301,17 +1076,17 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti std::vector> preset; - if (encoder == SIMPLE_ENCODER_QSV || encoder == ADVANCED_ENCODER_QSV) { - preset = createSettingEntry("QSVPreset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + std::string presetName = osn::EncoderUtils::getEncoderPreset(encoder.c_str()); + if (presetName == PRESET_QSV) { + preset = createSettingEntry(PRESET_QSV, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"Speed", ipc::value("speed")}); preset.push_back({"Balanced", ipc::value("balanced")}); preset.push_back({"Quality", ipc::value("quality")}); entries.push_back(preset); defaultPreset = "balanced"; - - } else if (encoder == SIMPLE_ENCODER_NVENC || encoder == ADVANCED_ENCODER_NVENC || encoder == ENCODER_NVENC_H264_TEX || - encoder == ENCODER_NVENC_HEVC_TEX) { - preset = createSettingEntry("NVENCPreset2", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == PRESET_NVENC) { + preset = createSettingEntry(PRESET_NVENC, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); obs_properties_t *props = obs_get_encoder_properties(ADVANCED_ENCODER_NVENC); @@ -1320,7 +1095,6 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti for (size_t i = 0; i < num; i++) { const char *name = obs_property_list_item_name(p, i); const char *val = obs_property_list_item_string(p, i); - preset.push_back(std::make_pair(name, ipc::value(val))); } @@ -1328,22 +1102,25 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti defaultPreset = "p5"; entries.push_back(preset); - } else if (encoder == SIMPLE_ENCODER_AMD || encoder == ADVANCED_ENCODER_AMD) { - preset = createSettingEntry("AMDPreset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == PRESET_AMD) { + preset = createSettingEntry(PRESET_AMD, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"Speed", ipc::value("speed")}); preset.push_back({"Balanced", ipc::value("balanced")}); preset.push_back({"Quality", ipc::value("quality")}); entries.push_back(preset); defaultPreset = "balanced"; - } else if (encoder == APPLE_SOFTWARE_VIDEO_ENCODER || encoder == APPLE_HARDWARE_VIDEO_ENCODER || encoder == APPLE_HARDWARE_VIDEO_ENCODER_M1) { - preset = createSettingEntry("Profile", "OBS_PROPERTY_LIST", "", "OBS_COMBO_FORMAT_STRING"); + } else if (presetName == PRESET_APPLE) { + preset = createSettingEntry(PRESET_APPLE, "OBS_PROPERTY_LIST", "", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"(None)", ipc::value("")}); preset.push_back({"baseline", ipc::value("baseline")}); preset.push_back({"main", ipc::value("main")}); preset.push_back({"high", ipc::value("high")}); entries.push_back(preset); - } else { - preset = createSettingEntry("Preset", "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); + } + else if (presetName == DEFAULT_PRESET) + { + preset = createSettingEntry(DEFAULT_PRESET, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"ultrafast", ipc::value("ultrafast")}); preset.push_back({"superfast", ipc::value("superfast")}); preset.push_back({"veryfast", ipc::value("veryfast")}); @@ -1723,7 +1500,7 @@ void OBS_settings::getEncoderSettings(const obs_encoder_t *encoder, obs_data_t * SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, bool isCategoryEnabled) { - converOldJimNvencEncoder(config, "AdvOut", "Encoder", "RecEncoder"); + osn::EncoderUtils::convertOldJimNvencEncoder(config, "AdvOut", "Encoder", "RecEncoder"); int index = 0; @@ -2005,7 +1782,7 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b // Encoder settings const char *encoderID = config_get_string(config, "AdvOut", "Encoder"); - if (encoderID == NULL || OBS_service::isInvalidEncoder(encoderID)) { + if (encoderID == NULL || osn::EncoderUtils::isInvalidAppleEncoder(encoderID)) { encoderID = ADVANCED_ENCODER_X264; config_set_string(config, "AdvOut", "Encoder", encoderID); config_save_safe(config, "tmp", nullptr); @@ -2050,7 +1827,7 @@ SubCategory OBS_settings::getAdvancedOutputStreamingSettings(config_t *config, b } else { obs_data_t *data = obs_data_create_from_json_file_safe(streamConfigFile.c_str(), "bak"); - update_nvenc_presets(data, encoderID); + osn::EncoderUtils::updateNvencPresets(data, encoderID); obs_data_apply(settings, data); streamingEncoder = obs_video_encoder_create(encoderID, encoder_name.c_str(), settings, nullptr); @@ -2214,6 +1991,8 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete memcpy(recEncoder.currentValue.data(), recEncoderCurrentValue, strlen(recEncoderCurrentValue)); recEncoder.sizeOfCurrentValue = strlen(recEncoderCurrentValue); + std::vector> encoderValues3; + std::vector> Encoder; Encoder.push_back(std::make_pair("Use stream encoder", ipc::value("none"))); getAdvancedAvailableEncoders(&Encoder, true, recFormatCurrentValue); @@ -2538,7 +2317,7 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete if (obs_output_active(recordOutput)) { settings = obs_encoder_get_settings(recordingEncoder); } else if (!recordingEncoder || (recordingEncoder && !obs_encoder_active(recordingEncoder))) { - std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, true, true, recEncoderCurrentValue); + std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, recEncoderCurrentValue); if (!fileExist) { recordingEncoder = obs_video_encoder_create(recEncoderCurrentValue, recEncoderName.c_str(), nullptr, nullptr); OBS_service::setRecordingEncoder(recordingEncoder); @@ -2548,7 +2327,7 @@ void OBS_settings::getStandardRecordingSettings(SubCategory *subCategoryParamete } } else if (strcmp(recEncoderCurrentValue, "none") != 0) { obs_data_t *data = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); - update_nvenc_presets(data, recEncoderCurrentValue); + osn::EncoderUtils::updateNvencPresets(data, recEncoderCurrentValue); obs_data_apply(settings, data); recordingEncoder = obs_video_encoder_create(recEncoderCurrentValue, recEncoderName.c_str(), settings, nullptr); OBS_service::setRecordingEncoder(recordingEncoder); @@ -3008,8 +2787,16 @@ void OBS_settings::saveAdvancedOutputRecordingSettings(std::vector int ret = config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); if (newEncoderType) { + //this is called immediately on encoder change so no other settings have been changed - start with defaults encoderSettings = obs_encoder_defaults(config_get_string(ConfigManager::getInstance().getBasic(), section.c_str(), "RecEncoder")); - OBS_service::createVideoRecordingEncoder(); + + //this defaults to obs_x264 so create the correct encoder with default settings - getSettings called immediately after and will update it correctly but + //do it right here just in case + //OBS_service::createDefaultSimpleVideoRecordingEncoder(); + const char *curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); + std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, curEncoder); + obs_encoder_t *recordingEncoder = obs_video_encoder_create(curEncoder, recEncoderName.c_str(), encoderSettings, nullptr); + OBS_service::setRecordingEncoder(recordingEncoder); } else { obs_encoder_update(encoder, encoderSettings); } @@ -4194,162 +3981,3 @@ void OBS_settings::OBS_settings_setEnhancedBroadcasting(void *data, const int64_ AUTO_DEBUG; } - -void convert_nvenc_h264_presets(obs_data_t *data) -{ - const char *preset = obs_data_get_string(data, "preset"); - const char *rc = obs_data_get_string(data, "rate_control"); - - // If already using SDK10+ preset, return early. - if (astrcmpi_n(preset, "p", 1) == 0) { - obs_data_set_string(data, "preset2", preset); - return; - } - - if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "mq") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "qres"); - - } else if (astrcmpi(preset, "hq") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "default") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "hp") == 0) { - obs_data_set_string(data, "preset2", "p1"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "ll") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhq") == 0) { - obs_data_set_string(data, "preset2", "p4"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhp") == 0) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - } -} - -void convert_nvenc_hevc_presets(obs_data_t *data) -{ - const char *preset = obs_data_get_string(data, "preset"); - const char *rc = obs_data_get_string(data, "rate_control"); - - // If already using SDK10+ preset, return early. - if (astrcmpi_n(preset, "p", 1) == 0) { - obs_data_set_string(data, "preset2", preset); - return; - } - - if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "lossless"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "mq") == 0) { - obs_data_set_string(data, "preset2", "p6"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "qres"); - - } else if (astrcmpi(preset, "hq") == 0) { - obs_data_set_string(data, "preset2", "p6"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "default") == 0) { - obs_data_set_string(data, "preset2", "p5"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "hp") == 0) { - obs_data_set_string(data, "preset2", "p1"); - obs_data_set_string(data, "tune", "hq"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "ll") == 0) { - obs_data_set_string(data, "preset2", "p3"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhq") == 0) { - obs_data_set_string(data, "preset2", "p4"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - - } else if (astrcmpi(preset, "llhp") == 0) { - obs_data_set_string(data, "preset2", "p2"); - obs_data_set_string(data, "tune", "ll"); - obs_data_set_string(data, "multipass", "disabled"); - } -} - -const char *convert_nvenc_simple_preset(const char *old_preset) -{ - if (astrcmpi(old_preset, "mq") == 0) { - return "p5"; - } else if (astrcmpi(old_preset, "hq") == 0) { - return "p5"; - } else if (astrcmpi(old_preset, "default") == 0) { - return "p3"; - } else if (astrcmpi(old_preset, "hp") == 0) { - return "p1"; - } else if (astrcmpi(old_preset, "ll") == 0) { - return "p3"; - } else if (astrcmpi(old_preset, "llhq") == 0) { - return "p4"; - } else if (astrcmpi(old_preset, "llhp") == 0) { - return "p2"; - } - return "p5"; -} - -bool update_nvenc_presets(obs_data_t *data, const char *encoderId) -{ - bool modified = false; - if (astrcmpi(encoderId, ENCODER_NVENC_H264_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC) == 0) { - if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { - convert_nvenc_h264_presets(data); - - modified = true; - } - } else if (astrcmpi(encoderId, ENCODER_NVENC_HEVC_TEX) == 0 || astrcmpi(encoderId, "ffmpeg_hevc_nvenc") == 0) { - - if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { - convert_nvenc_hevc_presets(data); - - modified = true; - } - } - if (modified) - blog(LOG_INFO, "Updated nvenc preset for %s", encoderId); - - return modified; -} diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index a4a949849..fec08245b 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -21,6 +21,7 @@ #include "shared.hpp" #include "osn-audio-track.hpp" #include "osn-file-output.hpp" +#include void osn::IAdvancedRecording::Register(ipc::server &srv) { @@ -185,6 +186,7 @@ bool osn::AdvancedRecording::UpdateEncoders() if (!videoEncoder) return false; + //TODO this is done in streaming->updateEncoders - put this in an else? unless canvas is different - comes from OutputSignals so check if that is diff for streaming/recording if (obs_get_multiple_rendering()) { obs_encoder_set_video_mix(videoEncoder, obs_video_mix_get(canvas, OBS_RECORDING_VIDEO_RENDERING)); } else { @@ -224,6 +226,9 @@ void osn::IAdvancedRecording::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->videoEncoder), recording->format, false)) + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + obs_output_set_video_encoder(recording->output, recording->videoEncoder); std::string path = recording->path; @@ -347,9 +352,27 @@ void osn::IAdvancedRecording::GetLegacySettings(void *data, const int64_t id, co recording->useStreamEncoders = encId.compare("") == 0 || encId.compare("none") == 0; if (!recording->useStreamEncoders) { - obs_data_t *videoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); - recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", videoEncSettings, nullptr); + obs_data_t *existingVideoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getRecord().c_str(), "bak"); + obs_data_t *newSettings = obs_encoder_defaults(encId.c_str()); + + //old API gets defaults, reads recordEncoder.json if exists, converts if it does, then creates - need to handle null settings from missing config file + if (existingVideoEncSettings != nullptr) { + osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId.c_str()); + obs_data_apply(newSettings, existingVideoEncSettings); + } + //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleRecording(encId.c_str(), recording->fileFormat, false)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} + recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); + }else { + //TODO validate streaming encoder is valid for recording or wait until start? don't know if streaming is even set yet here and unsure how often this will be called so just check in SetVideoEncoder and Start + //if (recording->streaming) { + // if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->streaming->videoEncoder), recording->fileFormat, false)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified streaming video encoder is not valid for recording."); + // } + //} } recording->enableFileSplit = config_get_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile"); diff --git a/obs-studio-server/source/osn-advanced-recording.hpp b/obs-studio-server/source/osn-advanced-recording.hpp index ba2f71a83..675e8ccaf 100644 --- a/obs-studio-server/source/osn-advanced-recording.hpp +++ b/obs-studio-server/source/osn-advanced-recording.hpp @@ -34,6 +34,7 @@ class AdvancedRecording : public Recording { outputHeight = 720; useStreamEncoders = true; streaming = nullptr; + simple = false; } ~AdvancedRecording() {} diff --git a/obs-studio-server/source/osn-advanced-streaming.cpp b/obs-studio-server/source/osn-advanced-streaming.cpp index 265ac9d49..bf9958b30 100644 --- a/obs-studio-server/source/osn-advanced-streaming.cpp +++ b/obs-studio-server/source/osn-advanced-streaming.cpp @@ -23,6 +23,7 @@ #include "shared.hpp" #include "nodeobs_audio_encoders.h" #include "osn-audio-track.hpp" +#include "osn-encoders.hpp" void osn::IAdvancedStreaming::Register(ipc::server &srv) { @@ -223,14 +224,15 @@ void osn::IAdvancedStreaming::SetOutputHeight(void *data, const int64_t id, cons AUTO_DEBUG; } -static obs_encoder_t *createAudioEncoder(uint32_t bitrate) -{ - obs_encoder_t *audioEncoder = nullptr; - - audioEncoder = obs_audio_encoder_create(GetAACEncoderForBitrate(bitrate), "audio", nullptr, 0, nullptr); - - return audioEncoder; -} +//TODO - unused, delete it? +//static obs_encoder_t *createAudioEncoder(uint32_t bitrate) +//{ +// obs_encoder_t *audioEncoder = nullptr; +// +// audioEncoder = obs_audio_encoder_create(GetAACEncoderForBitrate(bitrate), "audio", nullptr, 0, nullptr); +// +// return audioEncoder; +//} static bool setAudioEncoder(osn::AdvancedStreaming *streaming) { @@ -394,6 +396,11 @@ void osn::IAdvancedStreaming::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Error while creating the video encoder."); } + //make sure the encoder is valid for the current service + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The provided encoder is not valid for the current service."); + } + if (!setAudioEncoder(streaming)) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Error while creating the audio encoder."); } @@ -467,8 +474,28 @@ void osn::IAdvancedStreaming::GetLegacySettings(void *data, const int64_t id, co { osn::AdvancedStreaming *streaming = new osn::AdvancedStreaming(); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder")); - obs_data_t *videoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getStream().c_str(), "bak"); - streaming->videoEncoder = obs_video_encoder_create(encId, "video-encoder", videoEncSettings, nullptr); + + //TODO - from old API - check for bad encoder ID and reset to x264 if needed is this going to mess up settings? should this also be in osn-advanced-recording? + if ((strlen(encId) == 0) || osn::EncoderUtils::isInvalidAppleEncoder(encId)) { + encId = ADVANCED_ENCODER_X264; + config_set_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder", encId); + config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); + } + + obs_data_t *existingVideoEncSettings = obs_data_create_from_json_file_safe(ConfigManager::getInstance().getStream().c_str(), "bak"); + obs_data_t *newSettings = obs_encoder_defaults(encId); + + //old API gets defaults, reads streamEncoder.json if exists, converts if it does, then creates - need to handle null settings from missing config file + if (existingVideoEncSettings != nullptr) { + osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId); + obs_data_apply(newSettings, existingVideoEncSettings); + } + //TODO - do we need to create descriptive encoder name like in old API? + //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} + streaming->videoEncoder = obs_video_encoder_create(encId, "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(streaming->videoEncoder); streaming->audioTrack = static_cast(config_get_int(ConfigManager::getInstance().getBasic(), "AdvOut", "TrackIndex") - 1); diff --git a/obs-studio-server/source/osn-advanced-streaming.hpp b/obs-studio-server/source/osn-advanced-streaming.hpp index dd7c0a7b9..8d716f5fa 100644 --- a/obs-studio-server/source/osn-advanced-streaming.hpp +++ b/obs-studio-server/source/osn-advanced-streaming.hpp @@ -35,6 +35,7 @@ class AdvancedStreaming : public Streaming { rescaling = false; outputWidth = 1280; outputHeight = 720; + simple = false; } ~AdvancedStreaming() {} diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp new file mode 100644 index 000000000..1edab79b9 --- /dev/null +++ b/obs-studio-server/source/osn-encoders.cpp @@ -0,0 +1,444 @@ +/****************************************************************************** + Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +******************************************************************************/ + +#include "osn-encoders.hpp" +#include "obs.h" +#include +#include +#include +#include "utility.hpp" + +static bool isNvencAvailableForSimpleMode(); +static bool containerSupportsCodec(const std::string &container, const std::string &codec); +static void convert_nvenc_h264_presets(obs_data_t *data); +static void convert_nvenc_hevc_presets(obs_data_t *data); + +bool osn::EncoderUtils::isEncoderRegistered(const std::string &encoder) +{ + const char *val; + int i = 0; + + while (obs_enum_encoder_types(i++, &val)) { + if (val == nullptr) + continue; + if (std::string(val) == encoder) + return true; + } + + return false; +} + +bool osn::EncoderUtils::isCodecAvailableForService(const char *encoder, obs_service_t *service) +{ + if (!encoder || !service) + return false; + + auto supportedCodecs = obs_service_get_supported_video_codecs(service); + auto encoderCodec = obs_get_encoder_codec(encoder); + + if (!supportedCodecs || !encoderCodec) + return false; + + while (*supportedCodecs) { + if (strcmp(*supportedCodecs, encoderCodec) == 0) + return true; + supportedCodecs++; + } + + return false; +} + +bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service_t *service, bool simpleMode, bool recording, const std::string &container, + int checkIndex) +{ + if (encoderName.empty()) + return false; + + if (!recording && !videoEncoderOptions[checkIndex].streaming) + return false; + + if (recording && !videoEncoderOptions[checkIndex].recording) + return false; + + if (videoEncoderOptions[checkIndex].check_availability && !isEncoderRegistered(encoderName)) + return false; + + if (!recording && videoEncoderOptions[checkIndex].check_availability_streaming && + !isCodecAvailableForService(encoderName.c_str(), service)) + return false; + + if (simpleMode) { + if (videoEncoderOptions[checkIndex].only_for_reuse_simple && !isNvencAvailableForSimpleMode()) + return false; + } + + if (recording && videoEncoderOptions[checkIndex].check_availability_format) { + const char *codec = obs_get_encoder_codec(encoderName.c_str()); + if (!codec) { + blog(LOG_DEBUG, "[ENCODER_SKIPPED] codec is null"); + return false; + } + if (!containerSupportsCodec(container, codec)) + return false; + } + + return true; +} + +bool osn::EncoderUtils::isEncoderCompatibleStreaming(obs_service_t *service, const char *encoderToFind, bool simpleMode) +{ + bool validEncoder = false; + std::string curEncoder = ""; + + //find the encoder in the set and then check compatibility + for (int i = 0; i < videoEncoderOptions.size(); i++) { + curEncoder = simpleMode ? videoEncoderOptions[i].getSimpleName() : videoEncoderOptions[i].advanced_name; + if (curEncoder.compare(encoderToFind) == 0) { + if (isEncoderCompatible(encoderToFind, service, simpleMode, false, "", i)) { + validEncoder = true; + } + break; + } + } + + return validEncoder; +} + +bool osn::EncoderUtils::isEncoderCompatibleRecording(const char *encoderToFind, const std::string &container, bool simpleMode) +{ + bool validEncoder = false; + + std::string curEncoder = ""; + + //find the encoder in the set and then check compatibility + for (int i = 0; i < videoEncoderOptions.size(); i++) { + curEncoder = simpleMode ? videoEncoderOptions[i].getSimpleName() : videoEncoderOptions[i].advanced_name; + if (curEncoder.compare(encoderToFind) == 0) { + if (isEncoderCompatible(encoderToFind, NULL, simpleMode, true, "", i)) { + validEncoder = true; + } + break; + } + } + + return validEncoder; +} + +bool osn::EncoderUtils::isInvalidAppleEncoder(const char *encoderID) +{ +#if defined(__APPLE__) + // disable this encoder; not functioning properly + return strcmp(encoderID, APPLE_SOFTWARE_VIDEO_ENCODER) == 0; +#else + return false; +#endif +} + +//replacing logic of get_simple_output_encoder +std::string osn::EncoderUtils::getInternalEncoderFromSimple(const char *encoder) +{ + std::string encoderName = ADVANCED_ENCODER_X264; + bool found = false; + + for (const auto curEnc : videoEncoderOptions) { + //if there is a backup, check if simple_internal_name is available, return backup if not + //else if no simple_internal_name, return simple_name + //else return simple_internal_name + if (encoder == curEnc.simple_name) { + if (!curEnc.backup.empty() && !isEncoderRegistered(curEnc.simple_internal_name)) + encoderName = curEnc.backup; + else { + if (curEnc.simple_internal_name.empty()) + encoderName = curEnc.simple_name; + else + encoderName = curEnc.simple_internal_name; + } + found = true; + break; + } + } + if (!found) + blog(LOG_WARNING, "GetAdvancedEncoderFromSimple - encoder %s is not found, returning default encoder.", encoder); + + return encoderName; +} + +std::string osn::EncoderUtils::getSimpleEncoderFromInternal(const char *encoder) +{ + //this defaults to advanced b/c that's how it's done in osn-simple-streaming where this is used....WHY + std::string encoderName = ADVANCED_ENCODER_X264; + bool found = false; + + for (const auto curEnc : videoEncoderOptions) { + if (encoder == curEnc.simple_internal_name) { + encoderName = curEnc.simple_name; + found = true; + break; + } + } + if (!found) + blog(LOG_WARNING, "GetSimpleEncoderFromAdvanced - encoder %s is not found, returning default encoder.", encoder); + + return encoderName; +} + +std::string osn::EncoderUtils::getEncoderPreset(const char *encoder) +{ + std::string preset = DEFAULT_PRESET; + bool found = false; + + for (const auto curEnc : videoEncoderOptions) { + if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { + preset = curEnc.preset; + found = true; + break; + } + } + + if (!found) + blog(LOG_WARNING, "GetEncoderPreset - encoder %s is not found, returning default preset.", encoder); + + return preset; +} + +std::string osn::EncoderUtils::getEncoderFamily(const char *encoder) +{ + std::string family = ""; + bool found = false; + + //match on advanced_name or simple_name...backup or internal? + for (const auto curEnc : videoEncoderOptions) { + if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { + family = curEnc.family; + found = true; + break; + } + } + + if (!found) + blog(LOG_WARNING, "GetEncoderFamily - encoder %s is not found.", encoder); + + return family; +} + +bool osn::EncoderUtils::isOldJimNvencEncoder(const std::string &encoderId) +{ + return encoderId == ENCODER_JIM_NVENC || encoderId == ENCODER_JIM_HEVC_NVENC || encoderId == ENCODER_JIM_AV1_NVENC; +} + +// This code should be removed when JIM_ encoders will be removed from OBS +void osn::EncoderUtils::convertOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, + const std::string &recordingEncoderSetting) +{ + const std::string streamEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), streamEncoderSetting.c_str())); + if (osn::EncoderUtils::isOldJimNvencEncoder(streamEncoder)) { + blog(LOG_INFO, "Converting stream encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), streamEncoder.c_str(), + ENCODER_NVENC_H264_TEX); + config_set_string(config, configSection.c_str(), streamEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); + } + + const std::string recordingEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), recordingEncoderSetting.c_str())); + if (osn::EncoderUtils::isOldJimNvencEncoder(recordingEncoder)) { + blog(LOG_INFO, "Converting recording encoder for mode '%s' from encoder '%s' to '%s'", configSection.c_str(), recordingEncoder.c_str(), + ENCODER_NVENC_H264_TEX); + config_set_string(config, configSection.c_str(), recordingEncoderSetting.c_str(), ENCODER_NVENC_H264_TEX); + } +} + +bool osn::EncoderUtils::updateNvencPresets(obs_data_t *data, const char *encoderId) +{ + bool modified = false; + if (astrcmpi(encoderId, ENCODER_NVENC_H264_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC) == 0) { + if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { + convert_nvenc_h264_presets(data); + + modified = true; + } + } else if (astrcmpi(encoderId, ENCODER_NVENC_HEVC_TEX) == 0 || astrcmpi(encoderId, ADVANCED_ENCODER_NVENC_HEVC) == 0) { + + if (obs_data_has_user_value(data, "preset") && !obs_data_has_user_value(data, "preset2")) { + convert_nvenc_hevc_presets(data); + + modified = true; + } + } + if (modified) + blog(LOG_INFO, "Updated nvenc preset for %s", encoderId); + + return modified; +} + +const char *osn::EncoderUtils::convertNvencSimplePreset(const char *old_preset) +{ + if (astrcmpi(old_preset, "mq") == 0) { + return "p5"; + } else if (astrcmpi(old_preset, "hq") == 0) { + return "p5"; + } else if (astrcmpi(old_preset, "default") == 0) { + return "p3"; + } else if (astrcmpi(old_preset, "hp") == 0) { + return "p1"; + } else if (astrcmpi(old_preset, "ll") == 0) { + return "p3"; + } else if (astrcmpi(old_preset, "llhq") == 0) { + return "p4"; + } else if (astrcmpi(old_preset, "llhp") == 0) { + return "p2"; + } + return "p5"; +} + +static bool isNvencAvailableForSimpleMode() +{ + // Only available if config already uses it + const char *current_stream_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder"); + const char *current_rec_encoder = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder"); + bool nvenc_used_streaming = (current_stream_encoder && strcmp(current_stream_encoder, SIMPLE_ENCODER_NVENC) == 0); + bool nvenc_used_recording = (current_rec_encoder && strcmp(current_rec_encoder, SIMPLE_ENCODER_NVENC) == 0); + + return (nvenc_used_streaming || nvenc_used_recording) && osn::EncoderUtils::isEncoderRegistered(ADVANCED_ENCODER_NVENC); +} + +static bool containerSupportsCodec(const std::string &container, const std::string &codec) +{ + auto iter = osn::EncoderUtils::codecsForContainers.find(container); + if (iter == osn::EncoderUtils::codecsForContainers.end()) + return false; + + auto codecs = iter->second; + // Assume everything is supported + if (codecs.empty()) + return true; + return codecs.count(codec) > 0; +} + +static void convert_nvenc_h264_presets(obs_data_t *data) +{ + const char *preset = obs_data_get_string(data, "preset"); + const char *rc = obs_data_get_string(data, "rate_control"); + + // If already using SDK10+ preset, return early. + if (astrcmpi_n(preset, "p", 1) == 0) { + obs_data_set_string(data, "preset2", preset); + return; + } + + if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "mq") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "qres"); + + } else if (astrcmpi(preset, "hq") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "default") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "hp") == 0) { + obs_data_set_string(data, "preset2", "p1"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "ll") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhq") == 0) { + obs_data_set_string(data, "preset2", "p4"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhp") == 0) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + } +} + +static void convert_nvenc_hevc_presets(obs_data_t *data) +{ + const char *preset = obs_data_get_string(data, "preset"); + const char *rc = obs_data_get_string(data, "rate_control"); + + // If already using SDK10+ preset, return early. + if (astrcmpi_n(preset, "p", 1) == 0) { + obs_data_set_string(data, "preset2", preset); + return; + } + + if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "mq")) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(rc, "lossless") == 0 && astrcmpi(preset, "hp")) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "lossless"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "mq") == 0) { + obs_data_set_string(data, "preset2", "p6"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "qres"); + + } else if (astrcmpi(preset, "hq") == 0) { + obs_data_set_string(data, "preset2", "p6"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "default") == 0) { + obs_data_set_string(data, "preset2", "p5"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "hp") == 0) { + obs_data_set_string(data, "preset2", "p1"); + obs_data_set_string(data, "tune", "hq"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "ll") == 0) { + obs_data_set_string(data, "preset2", "p3"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhq") == 0) { + obs_data_set_string(data, "preset2", "p4"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + + } else if (astrcmpi(preset, "llhp") == 0) { + obs_data_set_string(data, "preset2", "p2"); + obs_data_set_string(data, "tune", "ll"); + obs_data_set_string(data, "multipass", "disabled"); + } +} \ No newline at end of file diff --git a/obs-studio-server/source/osn-encoders.hpp b/obs-studio-server/source/osn-encoders.hpp new file mode 100644 index 000000000..835caa882 --- /dev/null +++ b/obs-studio-server/source/osn-encoders.hpp @@ -0,0 +1,210 @@ +/****************************************************************************** + Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "obs.h" +#include "nodeobs_configManager.hpp" + +//obs-x264 plugin +#ifdef WIN32 +#define SIMPLE_ENCODER_X264 "x264" +#elif __APPLE__ +#define SIMPLE_ENCODER_X264 "obs_x264" +#endif +#define ADVANCED_ENCODER_X264 "obs_x264" + +//special case for recording +#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" + +//generic values for simple mode to convert to specific encoders +#define SIMPLE_ENCODER_NVENC "nvenc" //h264 - obs-nvenc plugin +#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" //av1 - obs-nvenc plugin +#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" //hevc - obs-nvenc plugin +#define SIMPLE_ENCODER_AMD "amd" //h264 - obs-ffmpeg plugin +#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" //hevc - obs-ffmpeg plugin +#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" //av1 - obs-ffmpeg plugin +#define SIMPLE_ENCODER_QSV "qsv" //h264 - obs-qsv11 plugin +#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" //av1 - obs-qsv11 plugin +#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" //h264 - apple encoder +#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" //hevc - apple encoder + +//obs-qsv11 plugin (QuickSync) +#define ADVANCED_ENCODER_QSV "obs_qsv11" //h264 +#define ADVANCED_ENCODER_QSV_V2 "obs_qsv11_v2" //h264 +#define ADVANCED_ENCODER_QSV_AV1 "obs_qsv11_av1" //av1 +#define ADVANCED_ENCODER_QSV_HEVC "obs_qsv11_hevc" //hevc + +//obs-nvenc plugin - deprecated jim encoders +#define ENCODER_JIM_NVENC "jim_nvenc" //h264 +#define ENCODER_JIM_HEVC_NVENC "jim_hevc_nvenc" //hevc +#define ENCODER_JIM_AV1_NVENC "jim_av1_nvenc" //av1 + +//obs-nvenc plugin (NVIDIA) +#define ENCODER_NVENC_H264_TEX "obs_nvenc_h264_tex" //h264 +#define ENCODER_NVENC_HEVC_TEX "obs_nvenc_hevc_tex" //hevc +#define ENCODER_NVENC_AV1_TEX "obs_nvenc_av1_tex" //av1 +#define ADVANCED_ENCODER_NVENC "ffmpeg_nvenc" //h264 - if REGISTER_FFMPEG_IDS +#define ADVANCED_ENCODER_NVENC_HEVC "ffmpeg_hevc_nvenc" //hevc - if REGISTER_FFMPEG_IDS and ENABLE_HEVC + +//obs-ffmpeg plugin +#define ENCODER_AV1_SVT_FFMPEG "ffmpeg_svt_av1" //av1 +#define ENCODER_AV1_AOM_FFMPEG "ffmpeg_aom_av1" //av1 +#define ADVANCED_ENCODER_AMD "h264_texture_amf" //h264 +#define ADVANCED_ENCODER_AMD_HEVC "h265_texture_amf" //hevc +#define ADVANCED_ENCODER_AMD_AV1 "av1_texture_amf" //av1 + +//Apple encoders +#define APPLE_SOFTWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264" +#define APPLE_HARDWARE_VIDEO_ENCODER "com.apple.videotoolbox.videoencoder.h264.gva" +#define APPLE_HARDWARE_VIDEO_ENCODER_M1 "com.apple.videotoolbox.videoencoder.ave.avc" +#define APPLE_HARDWARE_VIDEO_ENCODER_HEVC "com.apple.videotoolbox.videoencoder.ave.hevc" + +#define SIMPLE_AUDIO_ENCODER_AAC "ffmpeg_aac" +#define SIMPLE_AUDIO_ENCODER_OPUS "ffmpeg_opus" + +//presets +#define PRESET_NVENC "NVENCPreset2" +#define PRESET_NVENC_DEP "NVENCPreset" +#define PRESET_QSV "QSVPreset" +#define PRESET_AMD "AMDPreset" +#define PRESET_APPLE "Profile" +#define DEFAULT_PRESET "Preset" + +//encoder families +#define FAMILY_OBS "family_obs" +#define FAMILY_QSV "family_qsv" +#define FAMILY_NVENC "family_nvenc" +#define FAMILY_NVENC_HEVC "family_nvenc_hevc" +#define FAMILY_AMD "family_amd" +#define FAMILY_APPLE "family_apple" +#define FAMILY_FFMPEG "family_ffmpeg" + +namespace osn { +namespace EncoderUtils { + +bool isEncoderRegistered(const std::string &encoder); +bool isCodecAvailableForService(const char *encoder, obs_service_t *service); +bool isInvalidAppleEncoder(const char *encoderID); +std::string getInternalEncoderFromSimple(const char *encoder); +std::string getSimpleEncoderFromInternal(const char *encoder); +std::string getEncoderFamily(const char *encoder); +std::string getEncoderPreset(const char *encoder); +bool isOldJimNvencEncoder(const std::string &encoderId); +void convertOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, + const std::string &recordingEncoderSetting); +bool isEncoderCompatible(std::string encoderName, obs_service_t *service, bool simpleMode, bool recording, const std::string &container, int checkIndex); +bool isEncoderCompatibleStreaming(obs_service_t *service, const char *encoderToFind, bool simpleMode); +bool isEncoderCompatibleRecording(const char *encoderToFind, const std::string &container, bool simpleMode); +bool updateNvencPresets(obs_data_t *data, const char *encoderId); +const char *convertNvencSimplePreset(const char *old_preset); + +class EncoderSettings { +public: + std::string advanced_title; + std::string advanced_name; + std::string simple_title; + std::string simple_name; + std::string simple_internal_name; + std::string backup; + bool recording; + bool streaming; + bool check_availability; + bool check_availability_streaming; + bool check_availability_format; + bool only_for_reuse_simple; + std::string preset; + std::string family; + const std::string getSimpleName() const { return simple_internal_name.empty() ? simple_name : simple_internal_name; } +}; + +static std::vector videoEncoderOptions = { + // Software x264 + {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, "", true, true, false, false, true, false, + "Preset", FAMILY_OBS}, + // Software x264 low CPU (only for recording) + {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, "", true, false, false, false, + true, false, "Preset", FAMILY_OBS}, + // QuickSync H.264 (v1, deprecated) + // This line left here for reference + // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, + // QuickSync H.264 (v2, new) + {"QuickSync H.264", ADVANCED_ENCODER_QSV_V2, "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV_V2, "", true, true, true, false, true, + false, "QSVPreset", FAMILY_QSV}, + // QuickSync AV1 + {"QuickSync AV1", ADVANCED_ENCODER_QSV_AV1, "Hardware (QSV, AV1)", SIMPLE_ENCODER_QSV_AV1, ADVANCED_ENCODER_QSV_AV1, "", true, true, true, false, true, + false, "QSVPreset", FAMILY_QSV}, + // QuickSync HEVC + {"QuickSync HEVC", ADVANCED_ENCODER_QSV_HEVC, "", "", "", "", true, true, true, false, true, false, "QSVPreset", FAMILY_QSV}, + // NVIDIA NVENC H.264 + {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ENCODER_NVENC_H264_TEX, ADVANCED_ENCODER_NVENC, true, true, + true, false, true, true, "NVENCPreset", FAMILY_NVENC}, + // NVIDIA NVENC H.264 (new) + {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", "", true, true, true, false, true, false, + "NVENCPreset", FAMILY_NVENC}, + // NVIDIA NVENC HEVC + {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, ADVANCED_ENCODER_NVENC_HEVC, + true, true, true, true, true, false, "Preset", FAMILY_NVENC_HEVC}, + // NVIDIA NVENC AV1 + {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "", "", true, true, true, true, true, false, "NVENCPreset", + FAMILY_NVENC}, + // Apple VT H264 Hardware Encoder + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", "", true, true, true, + false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // Apple VT H264 Hardware Encoder - get_simple_output_encoder RETURNED M1 FOR SIMPLE_ENCODER_APPLE_H264 SO MAKE THAT THE SIMPLE NAME AND M1 INTERNAL NAME + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", SIMPLE_ENCODER_APPLE_H264, + APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, + true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above + {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, + APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // AMD HW H.264 + {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, + "AMDPreset", FAMILY_AMD}, + // AMD HW H.265 (HEVC) + {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, "", true, true, true, + true, true, false, "AMDPreset", FAMILY_AMD}, + // AMD HW AV1 + {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", SIMPLE_ENCODER_AMD_AV1, ADVANCED_ENCODER_AMD_AV1, "", true, true, true, true, true, false, + "AMDPreset", FAMILY_AMD}, + // AOM AV1 + {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}, + // SVT-AV1 + {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}}; + +// Codect/Container support check. +// from OBS code UI\window-basic-settings.cpp +static const std::unordered_map> codecsForContainers = { + // Technically our muxer supports HEVC and AV1 as well, but nothing else does + {"flv", {"h264", "aac"}}, + {"mpegts", {"h264", "hevc", "aac", "opus"}}, + {"hls", {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support + {"mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, + {"mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, + {"fragmented_mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, + {"fragmented_mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, + // MKV supports everything + {"mkv", {}}, +}; + +} +} diff --git a/obs-studio-server/source/osn-multitrack-video-output.cpp b/obs-studio-server/source/osn-multitrack-video-output.cpp index 3aed318e9..c5e8f1638 100644 --- a/obs-studio-server/source/osn-multitrack-video-output.cpp +++ b/obs-studio-server/source/osn-multitrack-video-output.cpp @@ -65,8 +65,8 @@ static void adjust_video_encoder_scaling(const obs_video_info &ovi, obs_encoder_ obs_encoder_set_scaled_size(video_encoder, requested_width, requested_height); obs_encoder_set_gpu_scale_type(video_encoder, encoder_config.gpu_scale_type.value_or(OBS_SCALE_BICUBIC)); obs_encoder_set_preferred_video_format(video_encoder, encoder_config.format.value_or(VIDEO_FORMAT_NV12)); - obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); - obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); + //obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); + //obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); } static uint32_t closest_divisor(const obs_video_info &ovi, const media_frames_per_second &target_fps) diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 77da3582d..5e05bc5bf 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -18,9 +18,11 @@ #include "osn-recording.hpp" #include "osn-video-encoder.hpp" +#include "osn-audio-encoder.hpp" #include "osn-error.hpp" #include "shared.hpp" #include "util/platform.h" +#include "osn-encoders.hpp" extern char *osn_generate_formatted_filename(const char *extension, bool space, const char *format, int width, int height); @@ -55,6 +57,11 @@ void osn::IRecording::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); } + //verify the encoder is compatible before setting it + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(encoder), recording->format, recording->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + recording->videoEncoder = encoder; rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); @@ -143,8 +150,12 @@ obs_encoder_t *osn::IRecording::duplicate_encoder(obs_encoder_t *src, uint64_t t if (obs_encoder_get_type(src) == OBS_ENCODER_AUDIO) { dst = obs_audio_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), trackIndex, nullptr); + //TODO added so it eventually gets released...is this correct? dont' even include osn-audio-encoder here + osn::AudioEncoder::Manager::GetInstance().allocate(dst); } else if (obs_encoder_get_type(src) == OBS_ENCODER_VIDEO) { dst = obs_video_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), nullptr); + //TODO added so it eventually gets released...is this correct? + osn::VideoEncoder::Manager::GetInstance().allocate(dst); } return dst; diff --git a/obs-studio-server/source/osn-recording.hpp b/obs-studio-server/source/osn-recording.hpp index 5a5f0930d..678dd87ff 100644 --- a/obs-studio-server/source/osn-recording.hpp +++ b/obs-studio-server/source/osn-recording.hpp @@ -36,6 +36,7 @@ class Recording : public FileOutput { splitTime = 15; splitSize = 2048; fileResetTimestamps = true; + simple = true; } virtual ~Recording(); @@ -46,6 +47,7 @@ class Recording : public FileOutput { uint32_t splitTime; uint32_t splitSize; bool fileResetTimestamps; + bool simple; void ConfigureRecFileSplitting(); }; diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 27addc6d3..917672d6a 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -23,6 +23,7 @@ #include "shared.hpp" #include "nodeobs_audio_encoders.h" #include "osn-file-output.hpp" +#include "osn-encoders.hpp" void osn::ISimpleRecording::Register(ipc::server &srv) { @@ -272,23 +273,22 @@ static void UpdateRecordingSettings_crf(enum osn::RecQuality quality, osn::Simpl int crf = CalcCRF(ultra_hq ? 16 : 23); obs_data_t *settings = nullptr; - if (id.compare(SIMPLE_ENCODER_X264) == 0 || id.compare(ADVANCED_ENCODER_X264) == 0 || id.compare(SIMPLE_ENCODER_X264_LOWCPU) == 0) { - settings = UpdateRecordingSettings_x264_crf(CalcCRF(crf, recording->lowCPU), recording->lowCPU); - } else if (id.compare(SIMPLE_ENCODER_NVENC) == 0 || id.compare(ADVANCED_ENCODER_NVENC) == 0 || id.compare(ENCODER_NVENC_H264_TEX) == 0) { - settings = UpdateRecordingSettings_nvenc(CalcCRF(crf)); - } else if (id.compare(SIMPLE_ENCODER_NVENC_HEVC) == 0) { - settings = UpdateRecordingSettings_nvenc_hevc(CalcCRF(crf)); - } else if (id.compare(SIMPLE_ENCODER_QSV) == 0 || id.compare(ADVANCED_ENCODER_QSV) == 0) { - settings = UpdateRecordingSettings_qsv11(CalcCRF(crf), recording->videoEncoder); - } else if (id.compare(SIMPLE_ENCODER_AMD) == 0 || id.compare(SIMPLE_ENCODER_AMD_HEVC) == 0 || id.compare(ADVANCED_ENCODER_AMD) == 0) { - settings = UpdateRecordingSettings_amd_cqp(CalcCRF(crf)); - } else if (id.compare(APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || id.compare(APPLE_HARDWARE_VIDEO_ENCODER) == 0 || - id.compare(APPLE_HARDWARE_VIDEO_ENCODER_M1) == 0) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); - } else { - return; - } + std::string encFamily = osn::EncoderUtils::getEncoderFamily(id.c_str()); + + if (encFamily == FAMILY_OBS) + settings = UpdateRecordingSettings_x264_crf(crf, recording->lowCPU); + else if (encFamily == FAMILY_NVENC) + settings = UpdateRecordingSettings_nvenc(crf); + else if (encFamily == FAMILY_NVENC_HEVC) + settings = UpdateRecordingSettings_nvenc_hevc(crf); + else if (encFamily == FAMILY_QSV) + settings = UpdateRecordingSettings_qsv11(crf, recording->videoEncoder); + else if (encFamily == FAMILY_AMD) + settings = UpdateRecordingSettings_amd_cqp(crf); + else if (encFamily == FAMILY_APPLE) + settings = UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); + else + blog(LOG_WARNING, "Unable to update settings with unknown encoder family."); if (!settings) return; @@ -317,16 +317,20 @@ void osn::SimpleRecording::UpdateEncoders() videoEncoder = streaming->videoEncoder; audioEncoder = streaming->audioEncoder; if (obs_get_multiple_rendering()) { - obs_encoder_t *videoEncDup = osn::IRecording::duplicate_encoder(videoEncoder); - videoEncoder = videoEncDup; + //TODO this doesn't register with the manager, should it? + videoEncoder = osn::IRecording::duplicate_encoder(videoEncoder); } break; } case RecQuality::HighQuality: { + if (!videoEncoder) + return; UpdateRecordingSettings_crf(RecQuality::HighQuality, this); break; } case RecQuality::HigherQuality: { + if (!videoEncoder) + return; UpdateRecordingSettings_crf(RecQuality::HigherQuality, this); break; } @@ -362,10 +366,16 @@ void osn::ISimpleRecording::Start(void *data, const int64_t id, const std::vecto } else { recording->UpdateEncoders(); + //TODO - does update not create an encoder if not using streaming and it doesn't exist? should it? + if (!recording->videoEncoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } + if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->videoEncoder), recording->format, true)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + if (!recording->audioEncoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid audio encoder."); } @@ -448,43 +458,37 @@ void osn::ISimpleRecording::SetLowCPU(void *data, const int64_t id, const std::v AUTO_DEBUG; } -obs_encoder_t *osn::ISimpleRecording::GetLegacyVideoEncoderSettings() +obs_encoder_t *osn::ISimpleRecording::CreateLegacyVideoEncoder() { + osn::EncoderUtils::convertOldJimNvencEncoder(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", "RecEncoder"); + obs_encoder_t *videoEncoder = nullptr; std::string simpleQuality = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecQuality")); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder")); - const char *encIdOBS = nullptr; - if (strcmp(encId, SIMPLE_ENCODER_X264) == 0 || strcmp(encId, ADVANCED_ENCODER_X264) == 0) { - encIdOBS = ADVANCED_ENCODER_X264; - } else if (strcmp(encId, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - encIdOBS = ADVANCED_ENCODER_X264; - } else if (strcmp(encId, SIMPLE_ENCODER_QSV) == 0 || strcmp(encId, ADVANCED_ENCODER_QSV) == 0) { - encIdOBS = ADVANCED_ENCODER_QSV; - } else if (strcmp(encId, SIMPLE_ENCODER_AMD) == 0 || strcmp(encId, ADVANCED_ENCODER_AMD) == 0) { - encIdOBS = ADVANCED_ENCODER_AMD; - } else if (strcmp(encId, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encId, ADVANCED_ENCODER_NVENC) == 0) { - encIdOBS = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encId, ENCODER_NVENC_H264_TEX) == 0 || strcmp(encId, ENCODER_JIM_NVENC) == 0 || strcmp(encId, ENCODER_JIM_AV1_NVENC) == 0 || - strcmp(encId, ENCODER_JIM_HEVC_NVENC) == 0) { - encIdOBS = ENCODER_NVENC_H264_TEX; - } + //TODO do we need to check low CPU here before we convert? + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + //don't create the encoder if using the streaming encoder if (simpleQuality.compare("Stream") != 0) { - videoEncoder = obs_video_encoder_create(encIdOBS, "video-encoder", nullptr, nullptr); + //TODO should there be a descriptive name? why aren't we using settings? + videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", nullptr, nullptr); + osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); } return videoEncoder; } -obs_encoder_t *osn::ISimpleRecording::GetLegacyAudioEncoderSettings() +obs_encoder_t *osn::ISimpleRecording::CreateLegacyAudioEncoder() { obs_data_t *audioEncSettings = obs_data_create(); obs_data_set_int(audioEncSettings, "bitrate", 192); // Hardcoded default value obs_encoder_t *audioEncoder = obs_audio_encoder_create("ffmpeg_aac", "audio-encoder", audioEncSettings, 0, nullptr); obs_data_release(audioEncSettings); + osn::AudioEncoder::Manager::GetInstance().allocate(audioEncoder); + return audioEncoder; } @@ -519,12 +523,14 @@ void osn::ISimpleRecording::GetLegacySettings(void *data, const int64_t id, cons recording->lowCPU = true; if (recording->quality != RecQuality::Stream) { - recording->videoEncoder = GetLegacyVideoEncoderSettings(); - osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); + recording->videoEncoder = CreateLegacyVideoEncoder(); + //TODO do we want to check here and fail and not return the settings? or wait until start? unsure how often this will be called so just check in SetVideoEncoder and Start + //if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, true)) { + // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + //} } - recording->audioEncoder = GetLegacyAudioEncoderSettings(); - osn::AudioEncoder::Manager::GetInstance().allocate(recording->audioEncoder); + recording->audioEncoder = CreateLegacyAudioEncoder(); recording->enableFileSplit = config_get_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile"); const char *splitFileType = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFileType"); @@ -621,22 +627,16 @@ void osn::ISimpleRecording::SetLegacySettings(void *data, const int64_t id, cons config_set_bool(ConfigManager::getInstance().getBasic(), "Output", "OverwriteIfExists", recording->overwrite); config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "MuxerCustom", recording->muxerSettings.c_str()); - if (recording->videoEncoder) { - const char *encId = nullptr; + //don't save the encoder if using the streaming encoder, set it to empty string in that case + if (recording->quality != RecQuality::Stream && recording->videoEncoder) { const char *encIdOBS = obs_encoder_get_id(recording->videoEncoder); - if (strcmp(encIdOBS, ADVANCED_ENCODER_X264) == 0 && !recording->lowCPU) { - encId = SIMPLE_ENCODER_X264; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_X264) == 0 && recording->lowCPU) { + std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); + if ((encId == SIMPLE_ENCODER_X264) == 0 && recording->lowCPU) { encId = SIMPLE_ENCODER_X264_LOWCPU; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_QSV) == 0) { - encId = SIMPLE_ENCODER_QSV; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_AMD) == 0) { - encId = SIMPLE_ENCODER_AMD; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_NVENC) == 0) { - encId = SIMPLE_ENCODER_NVENC; - } - - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId); + } + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId.c_str()); + } else { + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", ""); } config_set_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile", recording->enableFileSplit); diff --git a/obs-studio-server/source/osn-simple-recording.hpp b/obs-studio-server/source/osn-simple-recording.hpp index a4e2f5523..42ddec940 100644 --- a/obs-studio-server/source/osn-simple-recording.hpp +++ b/obs-studio-server/source/osn-simple-recording.hpp @@ -62,8 +62,8 @@ class ISimpleRecording : public IRecording { static void SetLowCPU(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static obs_encoder_t *GetLegacyVideoEncoderSettings(); - static obs_encoder_t *GetLegacyAudioEncoderSettings(); + static obs_encoder_t *CreateLegacyVideoEncoder(); + static obs_encoder_t *CreateLegacyAudioEncoder(); static void GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval); }; diff --git a/obs-studio-server/source/osn-simple-streaming.cpp b/obs-studio-server/source/osn-simple-streaming.cpp index a50e3bbcc..dfab4ea7e 100644 --- a/obs-studio-server/source/osn-simple-streaming.cpp +++ b/obs-studio-server/source/osn-simple-streaming.cpp @@ -22,6 +22,7 @@ #include "osn-error.hpp" #include "shared.hpp" #include "nodeobs_audio_encoders.h" +#include "osn-encoders.hpp" void osn::ISimpleStreaming::Register(ipc::server &srv) { @@ -277,6 +278,7 @@ void osn::SimpleStreaming::UpdateEncoders() int aBitrate = static_cast(obs_data_get_int(audioEncSettings, "bitrate")); std::string id = obs_encoder_get_id(videoEncoder); + //TODO why just AMD here? if (id.compare(ADVANCED_ENCODER_AMD) == 0) UpdateStreamingSettings_amd(videoEncSettings, vBitrate); @@ -353,6 +355,11 @@ void osn::ISimpleStreaming::Start(void *data, const int64_t id, const std::vecto PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid audio encoder."); } + //make sure the encoder is valid for the current service + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The provided encoder is not valid for the current service."); + } + streaming->UpdateEncoders(); obs_encoder_set_audio(streaming->audioEncoder, obs_get_audio()); obs_output_set_audio_encoder(streaming->output, streaming->audioEncoder, 0); @@ -420,10 +427,11 @@ void osn::ISimpleStreaming::Stop(void *data, const int64_t id, const std::vector AUTO_DEBUG; } -obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() +obs_encoder_t *osn::ISimpleStreaming::CreateLegacyVideoEncoder() { + osn::EncoderUtils::convertOldJimNvencEncoder(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", "RecEncoder"); + const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder")); - const char *encIdOBS = nullptr; obs_data_t *videoEncData = obs_data_create(); obs_data_set_string(videoEncData, "rate_control", "CBR"); @@ -433,26 +441,24 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() const char *custom = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings")); const char *preset = nullptr; - const char *presetType = nullptr; - if (strcmp(encId, SIMPLE_ENCODER_QSV) == 0 || strcmp(encId, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encIdOBS = ADVANCED_ENCODER_QSV; - } else if (strcmp(encId, SIMPLE_ENCODER_AMD) == 0 || strcmp(encId, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - encIdOBS = ADVANCED_ENCODER_AMD; - } else if (strcmp(encId, SIMPLE_ENCODER_NVENC) == 0 || strcmp(encId, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encIdOBS = ADVANCED_ENCODER_NVENC; - } else if (strcmp(encId, ENCODER_NVENC_H264_TEX) == 0 || strcmp(encId, ENCODER_JIM_NVENC) == 0 || strcmp(encId, ENCODER_JIM_AV1_NVENC) == 0 || - strcmp(encId, ENCODER_JIM_HEVC_NVENC) == 0) { - presetType = "NVENCPreset"; - encIdOBS = ENCODER_NVENC_H264_TEX; - } else { - presetType = "Preset"; - encIdOBS = ADVANCED_ENCODER_X264; + + std::string presetType = osn::EncoderUtils::getEncoderPreset(encId); + //TODO do we need to check low CPU here before we convert? + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + + preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str())); + + //TODO this conversion is from old API - is it needed? SetLegacySettings still used NVENCPreset instead of 2 but changed it - is this correct? + if (presetType == PRESET_NVENC) { + if (strlen(preset) == 0) { + const char *oldParamName = PRESET_NVENC_DEP; + const char *oldValue = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", oldParamName)); + if (strlen(oldValue) != 0) { + preset = osn::EncoderUtils::convertNvencSimplePreset(oldValue); + blog(LOG_INFO, "NVENC preset converted from %s to %s", oldValue, preset); + } + } } - if (presetType) - preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType)); if (advanced) { obs_data_set_string(videoEncData, "preset", preset); @@ -465,19 +471,21 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyVideoEncoderSettings() obs_data_set_int(videoEncData, "bitrate", config_get_uint(ConfigManager::getInstance().getBasic(), "SimpleOutput", "VBitrate")); } - if (strcmp(encId, APPLE_SOFTWARE_VIDEO_ENCODER) == 0 || strcmp(encId, APPLE_HARDWARE_VIDEO_ENCODER) == 0) { + if (osn::EncoderUtils::getEncoderFamily(encId) == FAMILY_APPLE) { const char *profile = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "Profile")); if (profile) obs_data_set_string(videoEncData, "profile", profile); } - obs_encoder_t *videoEncoder = obs_video_encoder_create(encIdOBS, "video-encoder", videoEncData, nullptr); + obs_encoder_t *videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", videoEncData, nullptr); obs_data_release(videoEncData); + osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); + return videoEncoder; } -obs_encoder_t *osn::ISimpleStreaming::GetLegacyAudioEncoderSettings() +obs_encoder_t *osn::ISimpleStreaming::CreateLegacyAudioEncoder() { obs_data_t *audioEncData = obs_data_create(); obs_data_set_string(audioEncData, "rate_control", "CBR"); @@ -495,6 +503,8 @@ obs_encoder_t *osn::ISimpleStreaming::GetLegacyAudioEncoderSettings() obs_encoder_t *audioEncoder = obs_audio_encoder_create("ffmpeg_aac", "audio", audioEncData, 0, nullptr); obs_data_release(audioEncData); + osn::AudioEncoder::Manager::GetInstance().allocate(audioEncoder); + return audioEncoder; } @@ -502,12 +512,8 @@ void osn::ISimpleStreaming::GetLegacySettings(void *data, const int64_t id, cons { osn::SimpleStreaming *streaming = new osn::SimpleStreaming(); - streaming->videoEncoder = GetLegacyVideoEncoderSettings(); - - osn::VideoEncoder::Manager::GetInstance().allocate(streaming->videoEncoder); - - streaming->audioEncoder = GetLegacyAudioEncoderSettings(); - osn::AudioEncoder::Manager::GetInstance().allocate(streaming->audioEncoder); + streaming->videoEncoder = CreateLegacyVideoEncoder(); + streaming->audioEncoder = CreateLegacyAudioEncoder(); streaming->useAdvanced = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "UseAdvanced"); streaming->enableTwitchVOD = config_get_bool(ConfigManager::getInstance().getBasic(), "SimpleOutput", "VodTrackEnabled"); @@ -533,7 +539,7 @@ void osn::ISimpleStreaming::GetLegacySettings(void *data, const int64_t id, cons void osn::ISimpleStreaming::SetLegacyVideoEncoderSettings(obs_encoder_t *encoder) { - const char *encId = nullptr; + //const char *encId = nullptr; const char *encIdOBS = obs_encoder_get_id(encoder); obs_data_t *settings = obs_encoder_get_settings(encoder); @@ -543,27 +549,14 @@ void osn::ISimpleStreaming::SetLegacyVideoEncoderSettings(obs_encoder_t *encoder const char *custom = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "x264Settings")); const char *preset = nullptr; - const char *presetType = nullptr; - if (strcmp(encIdOBS, ADVANCED_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - encId = SIMPLE_ENCODER_QSV; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - encId = SIMPLE_ENCODER_AMD; - } else if (strcmp(encIdOBS, ADVANCED_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset"; - encId = SIMPLE_ENCODER_NVENC; - } else if (strcmp(encIdOBS, ENCODER_NVENC_H264_TEX) == 0) { - presetType = "NVENCPreset"; - encId = ENCODER_NVENC_H264_TEX; - } else { - presetType = "Preset"; - encId = ADVANCED_ENCODER_X264; - } - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", encId); + + std::string presetType = osn::EncoderUtils::getEncoderPreset(encIdOBS); + std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); + + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", encId.c_str()); preset = obs_data_get_string(settings, "preset"); - config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType, preset); + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str(), preset); obs_data_release(settings); } diff --git a/obs-studio-server/source/osn-simple-streaming.hpp b/obs-studio-server/source/osn-simple-streaming.hpp index f8eafe82c..a78a945a2 100644 --- a/obs-studio-server/source/osn-simple-streaming.hpp +++ b/obs-studio-server/source/osn-simple-streaming.hpp @@ -59,8 +59,8 @@ class ISimpleStreaming : public IStreaming { static void Start(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void Stop(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static obs_encoder_t *GetLegacyVideoEncoderSettings(); - static obs_encoder_t *GetLegacyAudioEncoderSettings(); + static obs_encoder_t *CreateLegacyVideoEncoder(); + static obs_encoder_t *CreateLegacyAudioEncoder(); static void SetLegacySettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetLegacyVideoEncoderSettings(obs_encoder_t *encoder); static void SetLegacyAudioEncoderSettings(obs_encoder_t *encoder); diff --git a/obs-studio-server/source/osn-streaming.cpp b/obs-studio-server/source/osn-streaming.cpp index f447c28c3..bf3997756 100644 --- a/obs-studio-server/source/osn-streaming.cpp +++ b/obs-studio-server/source/osn-streaming.cpp @@ -21,6 +21,7 @@ #include "osn-error.hpp" #include "shared.hpp" #include +#include "osn-encoders.hpp" //os_gettime_ns #include @@ -125,6 +126,11 @@ void osn::IStreaming::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); } + //verify the encoder is compatible before setting it + if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(encoder), streaming->simple)) { + PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); + } + streaming->videoEncoder = encoder; rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); diff --git a/obs-studio-server/source/osn-streaming.hpp b/obs-studio-server/source/osn-streaming.hpp index e1b0366fb..8cd1d616d 100644 --- a/obs-studio-server/source/osn-streaming.hpp +++ b/obs-studio-server/source/osn-streaming.hpp @@ -46,6 +46,7 @@ class Streaming : public OutputSignals { network = new Network(); lastBytesSent = 0; lastBytesSentTime = 0; + simple = true; } virtual ~Streaming(); @@ -63,6 +64,7 @@ class Streaming : public OutputSignals { Network *network; uint64_t lastBytesSent; uint64_t lastBytesSentTime; + bool simple; bool isTwitchVODSupported(); void getDelayLegacySettings(); diff --git a/obs-studio-server/source/osn-video-encoder.cpp b/obs-studio-server/source/osn-video-encoder.cpp index 71a52e480..c1c041a9b 100644 --- a/obs-studio-server/source/osn-video-encoder.cpp +++ b/obs-studio-server/source/osn-video-encoder.cpp @@ -19,13 +19,14 @@ #include "osn-video-encoder.hpp" #include "osn-error.hpp" #include "shared.hpp" +#include "osn-encoders.hpp" void osn::VideoEncoder::Register(ipc::server &srv) { std::shared_ptr cls = std::make_shared("VideoEncoder"); cls->register_function( std::make_shared("Create", std::vector{ipc::type::String, ipc::type::String, ipc::type::String}, Create)); - cls->register_function(std::make_shared("GetTypes", std::vector{}, GeTypes)); + cls->register_function(std::make_shared("GetTypes", std::vector{}, GetTypes)); cls->register_function(std::make_shared("GetName", std::vector{ipc::type::UInt64}, GetName)); cls->register_function(std::make_shared("SetName", std::vector{ipc::type::UInt64, ipc::type::String}, SetName)); cls->register_function(std::make_shared("GetType", std::vector{ipc::type::UInt64}, GetType)); @@ -49,7 +50,15 @@ void osn::VideoEncoder::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +void osn::VideoEncoder::GetTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval) { rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); const char *typeId = nullptr; diff --git a/obs-studio-server/source/osn-video-encoder.hpp b/obs-studio-server/source/osn-video-encoder.hpp index 7d2cb3dff..25b8fa45a 100644 --- a/obs-studio-server/source/osn-video-encoder.hpp +++ b/obs-studio-server/source/osn-video-encoder.hpp @@ -42,7 +42,7 @@ class VideoEncoder { static void Register(ipc::server &); static void Create(void *data, const int64_t id, const std::vector &args, std::vector &rval); - static void GeTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void GetTypes(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetName(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetName(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetType(void *data, const int64_t id, const std::vector &args, std::vector &rval); From ee9a056c8f89618e8fbb21edc6e643944e04918c Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Wed, 25 Feb 2026 12:18:39 -0600 Subject: [PATCH 07/12] formatting --- obs-studio-server/source/nodeobs_settings.cpp | 17 ++++++----------- .../source/osn-advanced-recording.cpp | 2 +- obs-studio-server/source/osn-encoders.cpp | 9 ++++----- obs-studio-server/source/osn-encoders.hpp | 3 +-- obs-studio-server/source/osn-recording.cpp | 2 +- .../source/osn-simple-recording.cpp | 2 +- obs-studio-server/source/osn-video-encoder.cpp | 4 ++-- 7 files changed, 16 insertions(+), 23 deletions(-) diff --git a/obs-studio-server/source/nodeobs_settings.cpp b/obs-studio-server/source/nodeobs_settings.cpp index 856a07fe6..c3dc3f2f6 100644 --- a/obs-studio-server/source/nodeobs_settings.cpp +++ b/obs-studio-server/source/nodeobs_settings.cpp @@ -944,7 +944,6 @@ bool OBS_settings::saveStreamSettings(std::vector streamSettings, S return true; } - void OBS_settings::getAvailableAudioEncoders(std::vector> *encoders, bool simple, bool recording, const std::string &container) { @@ -1084,8 +1083,7 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti preset.push_back({"Quality", ipc::value("quality")}); entries.push_back(preset); defaultPreset = "balanced"; - } - else if (presetName == PRESET_NVENC) { + } else if (presetName == PRESET_NVENC) { preset = createSettingEntry(PRESET_NVENC, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); obs_properties_t *props = obs_get_encoder_properties(ADVANCED_ENCODER_NVENC); @@ -1102,8 +1100,7 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti defaultPreset = "p5"; entries.push_back(preset); - } - else if (presetName == PRESET_AMD) { + } else if (presetName == PRESET_AMD) { preset = createSettingEntry(PRESET_AMD, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"Speed", ipc::value("speed")}); preset.push_back({"Balanced", ipc::value("balanced")}); @@ -1117,9 +1114,7 @@ void OBS_settings::getSimpleOutputSettings(std::vector *outputSetti preset.push_back({"main", ipc::value("main")}); preset.push_back({"high", ipc::value("high")}); entries.push_back(preset); - } - else if (presetName == DEFAULT_PRESET) - { + } else if (presetName == DEFAULT_PRESET) { preset = createSettingEntry(DEFAULT_PRESET, "OBS_PROPERTY_LIST", "Encoder Preset (higher = less CPU)", "OBS_COMBO_FORMAT_STRING"); preset.push_back({"ultrafast", ipc::value("ultrafast")}); preset.push_back({"superfast", ipc::value("superfast")}); @@ -2789,9 +2784,9 @@ void OBS_settings::saveAdvancedOutputRecordingSettings(std::vector if (newEncoderType) { //this is called immediately on encoder change so no other settings have been changed - start with defaults encoderSettings = obs_encoder_defaults(config_get_string(ConfigManager::getInstance().getBasic(), section.c_str(), "RecEncoder")); - - //this defaults to obs_x264 so create the correct encoder with default settings - getSettings called immediately after and will update it correctly but - //do it right here just in case + + //this defaults to obs_x264 so create the correct encoder with default settings - getSettings called immediately after and will update it correctly but + //do it right here just in case //OBS_service::createDefaultSimpleVideoRecordingEncoder(); const char *curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, curEncoder); diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index fec08245b..179ce0350 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -366,7 +366,7 @@ void osn::IAdvancedRecording::GetLegacySettings(void *data, const int64_t id, co //} recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); - }else { + } else { //TODO validate streaming encoder is valid for recording or wait until start? don't know if streaming is even set yet here and unsure how often this will be called so just check in SetVideoEncoder and Start //if (recording->streaming) { // if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->streaming->videoEncoder), recording->fileFormat, false)) { diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 1edab79b9..4d1fd4f4d 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -78,8 +78,7 @@ bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service if (videoEncoderOptions[checkIndex].check_availability && !isEncoderRegistered(encoderName)) return false; - if (!recording && videoEncoderOptions[checkIndex].check_availability_streaming && - !isCodecAvailableForService(encoderName.c_str(), service)) + if (!recording && videoEncoderOptions[checkIndex].check_availability_streaming && !isCodecAvailableForService(encoderName.c_str(), service)) return false; if (simpleMode) { @@ -186,7 +185,7 @@ std::string osn::EncoderUtils::getSimpleEncoderFromInternal(const char *encoder) for (const auto curEnc : videoEncoderOptions) { if (encoder == curEnc.simple_internal_name) { - encoderName = curEnc.simple_name; + encoderName = curEnc.simple_name; found = true; break; } @@ -206,7 +205,7 @@ std::string osn::EncoderUtils::getEncoderPreset(const char *encoder) if ((encoder == curEnc.advanced_name) || (encoder == curEnc.simple_name)) { preset = curEnc.preset; found = true; - break; + break; } } @@ -243,7 +242,7 @@ bool osn::EncoderUtils::isOldJimNvencEncoder(const std::string &encoderId) // This code should be removed when JIM_ encoders will be removed from OBS void osn::EncoderUtils::convertOldJimNvencEncoder(config_t *config, const std::string &configSection, const std::string &streamEncoderSetting, - const std::string &recordingEncoderSetting) + const std::string &recordingEncoderSetting) { const std::string streamEncoder = utility::GetSafeString(config_get_string(config, configSection.c_str(), streamEncoderSetting.c_str())); if (osn::EncoderUtils::isOldJimNvencEncoder(streamEncoder)) { diff --git a/obs-studio-server/source/osn-encoders.hpp b/obs-studio-server/source/osn-encoders.hpp index 835caa882..18b03d944 100644 --- a/obs-studio-server/source/osn-encoders.hpp +++ b/obs-studio-server/source/osn-encoders.hpp @@ -172,8 +172,7 @@ static std::vector videoEncoderOptions = { false, true, false, PRESET_APPLE, FAMILY_APPLE}, // Apple VT H264 Hardware Encoder - get_simple_output_encoder RETURNED M1 FOR SIMPLE_ENCODER_APPLE_H264 SO MAKE THAT THE SIMPLE NAME AND M1 INTERNAL NAME {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", SIMPLE_ENCODER_APPLE_H264, - APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, - true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 5e05bc5bf..3281be4bf 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -154,7 +154,7 @@ obs_encoder_t *osn::IRecording::duplicate_encoder(obs_encoder_t *src, uint64_t t osn::AudioEncoder::Manager::GetInstance().allocate(dst); } else if (obs_encoder_get_type(src) == OBS_ENCODER_VIDEO) { dst = obs_video_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), nullptr); - //TODO added so it eventually gets released...is this correct? + //TODO added so it eventually gets released...is this correct? osn::VideoEncoder::Manager::GetInstance().allocate(dst); } diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 917672d6a..379808159 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -633,7 +633,7 @@ void osn::ISimpleRecording::SetLegacySettings(void *data, const int64_t id, cons std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); if ((encId == SIMPLE_ENCODER_X264) == 0 && recording->lowCPU) { encId = SIMPLE_ENCODER_X264_LOWCPU; - } + } config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId.c_str()); } else { config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", ""); diff --git a/obs-studio-server/source/osn-video-encoder.cpp b/obs-studio-server/source/osn-video-encoder.cpp index c1c041a9b..54d7769c2 100644 --- a/obs-studio-server/source/osn-video-encoder.cpp +++ b/obs-studio-server/source/osn-video-encoder.cpp @@ -52,11 +52,11 @@ void osn::VideoEncoder::Create(void *data, const int64_t id, const std::vector Date: Wed, 25 Feb 2026 13:39:25 -0600 Subject: [PATCH 08/12] Update osn-encoders.cpp --- obs-studio-server/source/osn-encoders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 4d1fd4f4d..40f17c6b4 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -128,7 +128,7 @@ bool osn::EncoderUtils::isEncoderCompatibleRecording(const char *encoderToFind, for (int i = 0; i < videoEncoderOptions.size(); i++) { curEncoder = simpleMode ? videoEncoderOptions[i].getSimpleName() : videoEncoderOptions[i].advanced_name; if (curEncoder.compare(encoderToFind) == 0) { - if (isEncoderCompatible(encoderToFind, NULL, simpleMode, true, "", i)) { + if (isEncoderCompatible(encoderToFind, NULL, simpleMode, true, container, i)) { validEncoder = true; } break; From c45512e7fc2dd850c1ce5365a7d8c99ef11ae792 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Thu, 26 Feb 2026 14:46:52 -0600 Subject: [PATCH 09/12] Code review updates --- obs-studio-server/source/nodeobs_service.cpp | 17 +++++- obs-studio-server/source/nodeobs_settings.cpp | 3 - obs-studio-server/source/osn-encoders.cpp | 57 ++++++++++++++++++- obs-studio-server/source/osn-encoders.hpp | 53 +---------------- .../source/osn-multitrack-video-output.cpp | 4 +- .../source/osn-simple-recording.cpp | 2 +- .../source/osn-simple-streaming.cpp | 3 - 7 files changed, 73 insertions(+), 66 deletions(-) diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index 296415bea..6c063a8ce 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -2015,10 +2015,21 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId //TODO do we need to check low CPU here before we convert? encoderID = osn::EncoderUtils::getInternalEncoderFromSimple(encoder); - //TODO - do we need to handle PresetNvenc/2 here? - - if (!presetType.empty()) + if (!presetType.empty()) { preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); + //if this calls fails and preset type is NVENC, use legacy NVENC preset for backward compatibility + if (preset == NULL && presetType == PRESET_NVENC) { + presetType = PRESET_NVENC_DEP; + preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); + if (preset != NULL) { + //convert the old preset to new + const char *oldValue = preset; + preset = osn::EncoderUtils::convertNvencSimplePreset(oldValue); + blog(LOG_INFO, "NVENC preset converted from %s to %s", oldValue, preset); + } + } + } + // Here and in other places we repeat the same pattern. // Avoiding case when to an output there might not be any attached video encoder which can lead to crash. diff --git a/obs-studio-server/source/nodeobs_settings.cpp b/obs-studio-server/source/nodeobs_settings.cpp index c3dc3f2f6..3634c82e1 100644 --- a/obs-studio-server/source/nodeobs_settings.cpp +++ b/obs-studio-server/source/nodeobs_settings.cpp @@ -2785,9 +2785,6 @@ void OBS_settings::saveAdvancedOutputRecordingSettings(std::vector //this is called immediately on encoder change so no other settings have been changed - start with defaults encoderSettings = obs_encoder_defaults(config_get_string(ConfigManager::getInstance().getBasic(), section.c_str(), "RecEncoder")); - //this defaults to obs_x264 so create the correct encoder with default settings - getSettings called immediately after and will update it correctly but - //do it right here just in case - //OBS_service::createDefaultSimpleVideoRecordingEncoder(); const char *curEncoder = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "RecEncoder"); std::string recEncoderName = OBS_service::GetVideoEncoderName(StreamServiceId::Main, false, true, curEncoder); obs_encoder_t *recordingEncoder = obs_video_encoder_create(curEncoder, recEncoderName.c_str(), encoderSettings, nullptr); diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 40f17c6b4..0ca0a0b25 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -28,6 +28,59 @@ static bool containerSupportsCodec(const std::string &container, const std::stri static void convert_nvenc_h264_presets(obs_data_t *data); static void convert_nvenc_hevc_presets(obs_data_t *data); +std::vector videoEncoderOptions = { + // Software x264 + {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, "", true, true, false, false, true, false, + DEFAULT_PRESET, FAMILY_OBS}, + // Software x264 low CPU (only for recording) + {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, "", true, false, false, false, + true, false, DEFAULT_PRESET, FAMILY_OBS}, + // QuickSync H.264 (v1, deprecated) + // This line left here for reference + // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, + // QuickSync H.264 (v2, new) + {"QuickSync H.264", ADVANCED_ENCODER_QSV_V2, "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV_V2, "", true, true, true, false, true, + false, PRESET_QSV, FAMILY_QSV}, + // QuickSync AV1 + {"QuickSync AV1", ADVANCED_ENCODER_QSV_AV1, "Hardware (QSV, AV1)", SIMPLE_ENCODER_QSV_AV1, ADVANCED_ENCODER_QSV_AV1, "", true, true, true, false, true, + false, PRESET_QSV, FAMILY_QSV}, + // QuickSync HEVC + {"QuickSync HEVC", ADVANCED_ENCODER_QSV_HEVC, "", "", "", "", true, true, true, false, true, false, PRESET_QSV, FAMILY_QSV}, + // NVIDIA NVENC H.264 + {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ENCODER_NVENC_H264_TEX, ADVANCED_ENCODER_NVENC, true, true, + true, false, true, true, PRESET_NVENC, FAMILY_NVENC}, + // NVIDIA NVENC H.264 (new) + {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", "", true, true, true, false, true, false, + PRESET_NVENC, FAMILY_NVENC}, + // NVIDIA NVENC HEVC + {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, ADVANCED_ENCODER_NVENC_HEVC, + true, true, true, true, true, false, DEFAULT_PRESET, FAMILY_NVENC_HEVC}, + // NVIDIA NVENC AV1 + {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "", "", true, true, true, true, true, false, PRESET_NVENC, + FAMILY_NVENC}, + // Apple VT H264 Hardware Encoder + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", "", true, true, true, + false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // Apple VT H264 Hardware Encoder - get_simple_output_encoder RETURNED M1 FOR SIMPLE_ENCODER_APPLE_H264 SO MAKE THAT THE SIMPLE NAME AND M1 INTERNAL NAME + {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", SIMPLE_ENCODER_APPLE_H264, + APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above + {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, + APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + // AMD HW H.264 + {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, + PRESET_AMD, FAMILY_AMD}, + // AMD HW H.265 (HEVC) + {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, "", true, true, true, + true, true, false, PRESET_AMD, FAMILY_AMD}, + // AMD HW AV1 + {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", SIMPLE_ENCODER_AMD_AV1, ADVANCED_ENCODER_AMD_AV1, "", true, true, true, true, true, false, + PRESET_AMD, FAMILY_AMD}, + // AOM AV1 + {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", "", true, true, true, false, true, false, DEFAULT_PRESET, FAMILY_FFMPEG}, + // SVT-AV1 + {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", "", true, true, true, false, true, false, DEFAULT_PRESET, FAMILY_FFMPEG}}; + bool osn::EncoderUtils::isEncoderRegistered(const std::string &encoder) { const char *val; @@ -179,12 +232,12 @@ std::string osn::EncoderUtils::getInternalEncoderFromSimple(const char *encoder) std::string osn::EncoderUtils::getSimpleEncoderFromInternal(const char *encoder) { - //this defaults to advanced b/c that's how it's done in osn-simple-streaming where this is used....WHY + //this defaults to advanced b/c that's how it's done in osn-simple-streaming where this is used std::string encoderName = ADVANCED_ENCODER_X264; bool found = false; for (const auto curEnc : videoEncoderOptions) { - if (encoder == curEnc.simple_internal_name) { + if ((encoder == curEnc.simple_internal_name) || (!curEnc.backup.empty() && encoder == curEnc.backup)) { encoderName = curEnc.simple_name; found = true; break; diff --git a/obs-studio-server/source/osn-encoders.hpp b/obs-studio-server/source/osn-encoders.hpp index 18b03d944..ca7e0b133 100644 --- a/obs-studio-server/source/osn-encoders.hpp +++ b/obs-studio-server/source/osn-encoders.hpp @@ -137,58 +137,7 @@ class EncoderSettings { const std::string getSimpleName() const { return simple_internal_name.empty() ? simple_name : simple_internal_name; } }; -static std::vector videoEncoderOptions = { - // Software x264 - {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, "", true, true, false, false, true, false, - "Preset", FAMILY_OBS}, - // Software x264 low CPU (only for recording) - {"", "", "Software (x264 low CPU usage preset, increases file size)", SIMPLE_ENCODER_X264_LOWCPU, ADVANCED_ENCODER_X264, "", true, false, false, false, - true, false, "Preset", FAMILY_OBS}, - // QuickSync H.264 (v1, deprecated) - // This line left here for reference - // {"QuickSync H.264 (v1 deprecated)", ADVANCED_ENCODER_QSV, "(Deprecated v1) Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV, true, true, true, false, true, false}, - // QuickSync H.264 (v2, new) - {"QuickSync H.264", ADVANCED_ENCODER_QSV_V2, "Hardware (QSV, H.264)", SIMPLE_ENCODER_QSV, ADVANCED_ENCODER_QSV_V2, "", true, true, true, false, true, - false, "QSVPreset", FAMILY_QSV}, - // QuickSync AV1 - {"QuickSync AV1", ADVANCED_ENCODER_QSV_AV1, "Hardware (QSV, AV1)", SIMPLE_ENCODER_QSV_AV1, ADVANCED_ENCODER_QSV_AV1, "", true, true, true, false, true, - false, "QSVPreset", FAMILY_QSV}, - // QuickSync HEVC - {"QuickSync HEVC", ADVANCED_ENCODER_QSV_HEVC, "", "", "", "", true, true, true, false, true, false, "QSVPreset", FAMILY_QSV}, - // NVIDIA NVENC H.264 - {"NVIDIA NVENC H.264", ADVANCED_ENCODER_NVENC, "NVIDIA NVENC H.264", SIMPLE_ENCODER_NVENC, ENCODER_NVENC_H264_TEX, ADVANCED_ENCODER_NVENC, true, true, - true, false, true, true, "NVENCPreset", FAMILY_NVENC}, - // NVIDIA NVENC H.264 (new) - {"NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "NVIDIA NVENC H.264 (new)", ENCODER_NVENC_H264_TEX, "", "", true, true, true, false, true, false, - "NVENCPreset", FAMILY_NVENC}, - // NVIDIA NVENC HEVC - {"NVIDIA NVENC HEVC", ENCODER_NVENC_HEVC_TEX, "Hardware (NVENC, HEVC)", SIMPLE_ENCODER_NVENC_HEVC, ENCODER_NVENC_HEVC_TEX, ADVANCED_ENCODER_NVENC_HEVC, - true, true, true, true, true, false, "Preset", FAMILY_NVENC_HEVC}, - // NVIDIA NVENC AV1 - {"NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "NVIDIA NVENC AV1", ENCODER_NVENC_AV1_TEX, "", "", true, true, true, true, true, false, "NVENCPreset", - FAMILY_NVENC}, - // Apple VT H264 Hardware Encoder - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER, "Hardware (Apple, H.264)", APPLE_HARDWARE_VIDEO_ENCODER, "", "", true, true, true, - false, true, false, PRESET_APPLE, FAMILY_APPLE}, - // Apple VT H264 Hardware Encoder - get_simple_output_encoder RETURNED M1 FOR SIMPLE_ENCODER_APPLE_H264 SO MAKE THAT THE SIMPLE NAME AND M1 INTERNAL NAME - {"Apple VT H264 Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_M1, "Hardware (Apple, H.264)", SIMPLE_ENCODER_APPLE_H264, - APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, - // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above - {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, - APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, - // AMD HW H.264 - {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, - "AMDPreset", FAMILY_AMD}, - // AMD HW H.265 (HEVC) - {"AMD HW H.265 (HEVC)", ADVANCED_ENCODER_AMD_HEVC, "Hardware (AMD, HEVC)", SIMPLE_ENCODER_AMD_HEVC, ADVANCED_ENCODER_AMD_HEVC, "", true, true, true, - true, true, false, "AMDPreset", FAMILY_AMD}, - // AMD HW AV1 - {"AMD HW AV1", SIMPLE_ENCODER_AMD_AV1, "Hardware (AMD, AV1)", SIMPLE_ENCODER_AMD_AV1, ADVANCED_ENCODER_AMD_AV1, "", true, true, true, true, true, false, - "AMDPreset", FAMILY_AMD}, - // AOM AV1 - {"AOM AV1", ENCODER_AV1_AOM_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}, - // SVT-AV1 - {"SVT-AV1", ENCODER_AV1_SVT_FFMPEG, "", "", "", "", true, true, true, false, true, false, "Preset", FAMILY_FFMPEG}}; +extern const std::vector videoEncoderOptions; // Codect/Container support check. // from OBS code UI\window-basic-settings.cpp diff --git a/obs-studio-server/source/osn-multitrack-video-output.cpp b/obs-studio-server/source/osn-multitrack-video-output.cpp index c5e8f1638..3aed318e9 100644 --- a/obs-studio-server/source/osn-multitrack-video-output.cpp +++ b/obs-studio-server/source/osn-multitrack-video-output.cpp @@ -65,8 +65,8 @@ static void adjust_video_encoder_scaling(const obs_video_info &ovi, obs_encoder_ obs_encoder_set_scaled_size(video_encoder, requested_width, requested_height); obs_encoder_set_gpu_scale_type(video_encoder, encoder_config.gpu_scale_type.value_or(OBS_SCALE_BICUBIC)); obs_encoder_set_preferred_video_format(video_encoder, encoder_config.format.value_or(VIDEO_FORMAT_NV12)); - //obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); - //obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); + obs_encoder_set_preferred_color_space(video_encoder, encoder_config.colorspace.value_or(VIDEO_CS_709)); + obs_encoder_set_preferred_range(video_encoder, encoder_config.range.value_or(VIDEO_RANGE_PARTIAL)); } static uint32_t closest_divisor(const obs_video_info &ovi, const media_frames_per_second &target_fps) diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 379808159..aac4fdaae 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -631,7 +631,7 @@ void osn::ISimpleRecording::SetLegacySettings(void *data, const int64_t id, cons if (recording->quality != RecQuality::Stream && recording->videoEncoder) { const char *encIdOBS = obs_encoder_get_id(recording->videoEncoder); std::string encId = osn::EncoderUtils::getSimpleEncoderFromInternal(encIdOBS); - if ((encId == SIMPLE_ENCODER_X264) == 0 && recording->lowCPU) { + if (encId == SIMPLE_ENCODER_X264 && recording->lowCPU) { encId = SIMPLE_ENCODER_X264_LOWCPU; } config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder", encId.c_str()); diff --git a/obs-studio-server/source/osn-simple-streaming.cpp b/obs-studio-server/source/osn-simple-streaming.cpp index dfab4ea7e..da5e15659 100644 --- a/obs-studio-server/source/osn-simple-streaming.cpp +++ b/obs-studio-server/source/osn-simple-streaming.cpp @@ -278,7 +278,6 @@ void osn::SimpleStreaming::UpdateEncoders() int aBitrate = static_cast(obs_data_get_int(audioEncSettings, "bitrate")); std::string id = obs_encoder_get_id(videoEncoder); - //TODO why just AMD here? if (id.compare(ADVANCED_ENCODER_AMD) == 0) UpdateStreamingSettings_amd(videoEncSettings, vBitrate); @@ -443,12 +442,10 @@ obs_encoder_t *osn::ISimpleStreaming::CreateLegacyVideoEncoder() const char *preset = nullptr; std::string presetType = osn::EncoderUtils::getEncoderPreset(encId); - //TODO do we need to check low CPU here before we convert? std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str())); - //TODO this conversion is from old API - is it needed? SetLegacySettings still used NVENCPreset instead of 2 but changed it - is this correct? if (presetType == PRESET_NVENC) { if (strlen(preset) == 0) { const char *oldParamName = PRESET_NVENC_DEP; From e6bf282dc23701f5952b3f5eb48f6149a2eb37a3 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Thu, 26 Feb 2026 14:49:04 -0600 Subject: [PATCH 10/12] formatting --- obs-studio-server/source/nodeobs_service.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index 6c063a8ce..3a74a0ba2 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -2017,12 +2017,12 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId if (!presetType.empty()) { preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); - //if this calls fails and preset type is NVENC, use legacy NVENC preset for backward compatibility + //if this calls fails and preset type is NVENC, use legacy NVENC preset for backward compatibility if (preset == NULL && presetType == PRESET_NVENC) { presetType = PRESET_NVENC_DEP; preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str()); if (preset != NULL) { - //convert the old preset to new + //convert the old preset to new const char *oldValue = preset; preset = osn::EncoderUtils::convertNvencSimplePreset(oldValue); blog(LOG_INFO, "NVENC preset converted from %s to %s", oldValue, preset); @@ -2030,7 +2030,6 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId } } - // Here and in other places we repeat the same pattern. // Avoiding case when to an output there might not be any attached video encoder which can lead to crash. std::string encoder_name = GetVideoEncoderName(serviceId, true, false, encoderID.c_str()); From ab8b5880c4225096ef9f1d768add7519cd62c583 Mon Sep 17 00:00:00 2001 From: mhoyer-streamlabs Date: Fri, 27 Feb 2026 10:46:13 -0600 Subject: [PATCH 11/12] Code review updates --- obs-studio-server/source/nodeobs_service.cpp | 2 -- .../source/osn-advanced-recording.cpp | 12 ------------ .../source/osn-advanced-streaming.cpp | 18 ++---------------- obs-studio-server/source/osn-encoders.cpp | 2 +- obs-studio-server/source/osn-recording.cpp | 4 ++-- .../source/osn-simple-recording.cpp | 16 ++-------------- .../source/osn-simple-streaming.cpp | 8 ++++++++ obs-studio-server/source/osn-video-encoder.cpp | 1 - 8 files changed, 15 insertions(+), 48 deletions(-) diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index 3a74a0ba2..a04c2d9b1 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -828,7 +828,6 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) encoderId = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } - //TODO - does this cause issues because settings won't match? if (encoderId == NULL || !osn::EncoderUtils::isEncoderRegistered(encoderId) || osn::EncoderUtils::isInvalidAppleEncoder(encoderId)) { encoderId = ADVANCED_ENCODER_X264; } @@ -2012,7 +2011,6 @@ void OBS_service::updateVideoStreamingEncoder(bool isSimpleMode, StreamServiceId if (encoder != NULL) { std::string presetType = osn::EncoderUtils::getEncoderPreset(encoder); - //TODO do we need to check low CPU here before we convert? encoderID = osn::EncoderUtils::getInternalEncoderFromSimple(encoder); if (!presetType.empty()) { diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index 179ce0350..a5a97cfa4 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -186,7 +186,6 @@ bool osn::AdvancedRecording::UpdateEncoders() if (!videoEncoder) return false; - //TODO this is done in streaming->updateEncoders - put this in an else? unless canvas is different - comes from OutputSignals so check if that is diff for streaming/recording if (obs_get_multiple_rendering()) { obs_encoder_set_video_mix(videoEncoder, obs_video_mix_get(canvas, OBS_RECORDING_VIDEO_RENDERING)); } else { @@ -360,19 +359,8 @@ void osn::IAdvancedRecording::GetLegacySettings(void *data, const int64_t id, co osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId.c_str()); obs_data_apply(newSettings, existingVideoEncSettings); } - //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start - //if (!osn::EncoderUtils::isEncoderCompatibleRecording(encId.c_str(), recording->fileFormat, false)) { - // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); - //} recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); - } else { - //TODO validate streaming encoder is valid for recording or wait until start? don't know if streaming is even set yet here and unsure how often this will be called so just check in SetVideoEncoder and Start - //if (recording->streaming) { - // if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_id(recording->streaming->videoEncoder), recording->fileFormat, false)) { - // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified streaming video encoder is not valid for recording."); - // } - //} } recording->enableFileSplit = config_get_bool(ConfigManager::getInstance().getBasic(), "AdvOut", "RecSplitFile"); diff --git a/obs-studio-server/source/osn-advanced-streaming.cpp b/obs-studio-server/source/osn-advanced-streaming.cpp index bf9958b30..8496d9345 100644 --- a/obs-studio-server/source/osn-advanced-streaming.cpp +++ b/obs-studio-server/source/osn-advanced-streaming.cpp @@ -224,16 +224,6 @@ void osn::IAdvancedStreaming::SetOutputHeight(void *data, const int64_t id, cons AUTO_DEBUG; } -//TODO - unused, delete it? -//static obs_encoder_t *createAudioEncoder(uint32_t bitrate) -//{ -// obs_encoder_t *audioEncoder = nullptr; -// -// audioEncoder = obs_audio_encoder_create(GetAACEncoderForBitrate(bitrate), "audio", nullptr, 0, nullptr); -// -// return audioEncoder; -//} - static bool setAudioEncoder(osn::AdvancedStreaming *streaming) { osn::AudioTrack *audioTrack = osn::IAudioTrack::audioTracks[streaming->audioTrack - 1]; @@ -475,8 +465,9 @@ void osn::IAdvancedStreaming::GetLegacySettings(void *data, const int64_t id, co osn::AdvancedStreaming *streaming = new osn::AdvancedStreaming(); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder")); - //TODO - from old API - check for bad encoder ID and reset to x264 if needed is this going to mess up settings? should this also be in osn-advanced-recording? + //from old API - check for missing/bad encoder ID and reset to x264 if needed if ((strlen(encId) == 0) || osn::EncoderUtils::isInvalidAppleEncoder(encId)) { + blog(LOG_WARNING, "Invalid or missing encoder ID in basic.ini, defaulting to x264."); encId = ADVANCED_ENCODER_X264; config_set_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder", encId); config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); @@ -490,11 +481,6 @@ void osn::IAdvancedStreaming::GetLegacySettings(void *data, const int64_t id, co osn::EncoderUtils::updateNvencPresets(existingVideoEncSettings, encId); obs_data_apply(newSettings, existingVideoEncSettings); } - //TODO - do we need to create descriptive encoder name like in old API? - //TODO do we want to check and fail here without returning settings or just check on start? unsure how often this will be called so just check in SetVideoEncoder and Start - //if (!osn::EncoderUtils::isEncoderCompatibleStreaming(streaming->service, obs_encoder_get_id(streaming->videoEncoder), streaming->simple)) { - // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); - //} streaming->videoEncoder = obs_video_encoder_create(encId, "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(streaming->videoEncoder); diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 0ca0a0b25..2823b2f86 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -28,7 +28,7 @@ static bool containerSupportsCodec(const std::string &container, const std::stri static void convert_nvenc_h264_presets(obs_data_t *data); static void convert_nvenc_hevc_presets(obs_data_t *data); -std::vector videoEncoderOptions = { +const std::vector osn::EncoderUtils::videoEncoderOptions = { // Software x264 {"Software (x264)", ADVANCED_ENCODER_X264, "Software (x264)", SIMPLE_ENCODER_X264, ADVANCED_ENCODER_X264, "", true, true, false, false, true, false, DEFAULT_PRESET, FAMILY_OBS}, diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 3281be4bf..a098b6002 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -150,11 +150,11 @@ obs_encoder_t *osn::IRecording::duplicate_encoder(obs_encoder_t *src, uint64_t t if (obs_encoder_get_type(src) == OBS_ENCODER_AUDIO) { dst = obs_audio_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), trackIndex, nullptr); - //TODO added so it eventually gets released...is this correct? dont' even include osn-audio-encoder here + //this allows shutdown code in nodeobs_api to release the object osn::AudioEncoder::Manager::GetInstance().allocate(dst); } else if (obs_encoder_get_type(src) == OBS_ENCODER_VIDEO) { dst = obs_video_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), nullptr); - //TODO added so it eventually gets released...is this correct? + //this allows shutdown code in nodeobs_api to release the object osn::VideoEncoder::Manager::GetInstance().allocate(dst); } diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index aac4fdaae..84a9bbb85 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -317,7 +317,6 @@ void osn::SimpleRecording::UpdateEncoders() videoEncoder = streaming->videoEncoder; audioEncoder = streaming->audioEncoder; if (obs_get_multiple_rendering()) { - //TODO this doesn't register with the manager, should it? videoEncoder = osn::IRecording::duplicate_encoder(videoEncoder); } break; @@ -366,8 +365,6 @@ void osn::ISimpleRecording::Start(void *data, const int64_t id, const std::vecto } else { recording->UpdateEncoders(); - //TODO - does update not create an encoder if not using streaming and it doesn't exist? should it? - if (!recording->videoEncoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid video encoder."); } @@ -467,15 +464,10 @@ obs_encoder_t *osn::ISimpleRecording::CreateLegacyVideoEncoder() std::string simpleQuality = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecQuality")); const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "RecEncoder")); - //TODO do we need to check low CPU here before we convert? std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); - //don't create the encoder if using the streaming encoder - if (simpleQuality.compare("Stream") != 0) { - //TODO should there be a descriptive name? why aren't we using settings? - videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", nullptr, nullptr); - osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); - } + videoEncoder = obs_video_encoder_create(encIdOBS.c_str(), "video-encoder", nullptr, nullptr); + osn::VideoEncoder::Manager::GetInstance().allocate(videoEncoder); return videoEncoder; } @@ -524,10 +516,6 @@ void osn::ISimpleRecording::GetLegacySettings(void *data, const int64_t id, cons if (recording->quality != RecQuality::Stream) { recording->videoEncoder = CreateLegacyVideoEncoder(); - //TODO do we want to check here and fail and not return the settings? or wait until start? unsure how often this will be called so just check in SetVideoEncoder and Start - //if (!osn::EncoderUtils::isEncoderCompatibleRecording(obs_encoder_get_name(recording->videoEncoder), recording->fileFormat, true)) { - // PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); - //} } recording->audioEncoder = CreateLegacyAudioEncoder(); diff --git a/obs-studio-server/source/osn-simple-streaming.cpp b/obs-studio-server/source/osn-simple-streaming.cpp index da5e15659..3a35040a2 100644 --- a/obs-studio-server/source/osn-simple-streaming.cpp +++ b/obs-studio-server/source/osn-simple-streaming.cpp @@ -432,6 +432,14 @@ obs_encoder_t *osn::ISimpleStreaming::CreateLegacyVideoEncoder() const char *encId = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder")); + //check for missing/bad encoder ID and reset to x264 if needed + if ((strlen(encId) == 0) || osn::EncoderUtils::isInvalidAppleEncoder(encId)) { + blog(LOG_WARNING, "Invalid or missing encoder ID in basic.ini, defaulting to x264."); + encId = SIMPLE_ENCODER_X264; + config_set_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", "StreamEncoder", encId); + config_save_safe(ConfigManager::getInstance().getBasic(), "tmp", nullptr); + } + obs_data_t *videoEncData = obs_data_create(); obs_data_set_string(videoEncData, "rate_control", "CBR"); obs_data_set_int(videoEncData, "bitrate", config_get_uint(ConfigManager::getInstance().getBasic(), "SimpleOutput", "VBitrate")); diff --git a/obs-studio-server/source/osn-video-encoder.cpp b/obs-studio-server/source/osn-video-encoder.cpp index 54d7769c2..0f2c22771 100644 --- a/obs-studio-server/source/osn-video-encoder.cpp +++ b/obs-studio-server/source/osn-video-encoder.cpp @@ -51,7 +51,6 @@ void osn::VideoEncoder::Create(void *data, const int64_t id, const std::vector Date: Fri, 27 Feb 2026 10:50:27 -0600 Subject: [PATCH 12/12] formatting --- obs-studio-server/source/osn-recording.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index a098b6002..e1b850423 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -150,11 +150,11 @@ obs_encoder_t *osn::IRecording::duplicate_encoder(obs_encoder_t *src, uint64_t t if (obs_encoder_get_type(src) == OBS_ENCODER_AUDIO) { dst = obs_audio_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), trackIndex, nullptr); - //this allows shutdown code in nodeobs_api to release the object + //this allows shutdown code in nodeobs_api to release the object osn::AudioEncoder::Manager::GetInstance().allocate(dst); } else if (obs_encoder_get_type(src) == OBS_ENCODER_VIDEO) { dst = obs_video_encoder_create(obs_encoder_get_id(src), name.c_str(), obs_encoder_get_settings(src), nullptr); - //this allows shutdown code in nodeobs_api to release the object + //this allows shutdown code in nodeobs_api to release the object osn::VideoEncoder::Manager::GetInstance().allocate(dst); }