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..a04c2d9b1 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,7 @@ bool OBS_service::createVideoStreamingEncoder(StreamServiceId serviceId) encoderId = config_get_string(ConfigManager::getInstance().getBasic(), "AdvOut", "Encoder"); } - if (encoderId == NULL || !EncoderAvailable(encoderId) || isInvalidEncoder(encoderId)) { + if (encoderId == NULL || !osn::EncoderUtils::isEncoderRegistered(encoderId) || osn::EncoderUtils::isInvalidAppleEncoder(encoderId)) { encoderId = ADVANCED_ENCODER_X264; } @@ -1029,7 +1030,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 +1550,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 +1568,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 +2005,33 @@ 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; + std::string presetType = osn::EncoderUtils::getEncoderPreset(encoder); + encoderID = osn::EncoderUtils::getInternalEncoderFromSimple(encoder); + + 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); + } + } } - if (presetType) - preset = config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType); // 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 +2072,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 +2537,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) { + else if (encFamily == FAMILY_NVENC) UpdateRecordingSettings_nvenc(crf); - } else if (videoEncoder.compare(ENCODER_NVENC_H264_TEX) == 0) { - 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 +3306,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..3634c82e1 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,34 @@ 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 +991,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 +1004,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 +1027,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 +1075,16 @@ 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 +1093,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 +1100,22 @@ 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 +1495,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 +1777,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 +1822,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 +1986,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 +2312,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 +2322,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 +2782,13 @@ 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(); + + 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 +3973,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..a5a97cfa4 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) { @@ -224,6 +225,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,8 +351,15 @@ 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); + } + recording->videoEncoder = obs_video_encoder_create(encId.c_str(), "video-encoder", newSettings, nullptr); osn::VideoEncoder::Manager::GetInstance().allocate(recording->videoEncoder); } 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..8496d9345 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,15 +224,6 @@ 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; -} - static bool setAudioEncoder(osn::AdvancedStreaming *streaming) { osn::AudioTrack *audioTrack = osn::IAudioTrack::audioTracks[streaming->audioTrack - 1]; @@ -394,6 +386,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 +464,24 @@ 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); + + //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); + } + + 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); + } + 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..2823b2f86 --- /dev/null +++ b/obs-studio-server/source/osn-encoders.cpp @@ -0,0 +1,496 @@ +/****************************************************************************** + 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); + +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}, + // 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; + 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, container, 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 + std::string encoderName = ADVANCED_ENCODER_X264; + bool found = false; + + for (const auto curEnc : videoEncoderOptions) { + if ((encoder == curEnc.simple_internal_name) || (!curEnc.backup.empty() && encoder == curEnc.backup)) { + 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..ca7e0b133 --- /dev/null +++ b/obs-studio-server/source/osn-encoders.hpp @@ -0,0 +1,158 @@ +/****************************************************************************** + 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; } +}; + +extern const std::vector videoEncoderOptions; + +// 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..e1b850423 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); + //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 + 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..84a9bbb85 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,19 @@ 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; + 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; } @@ -366,6 +369,10 @@ 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_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 +455,32 @@ 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; - } - - if (simpleQuality.compare("Stream") != 0) { - videoEncoder = obs_video_encoder_create(encIdOBS, "video-encoder", nullptr, nullptr); - } + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + + 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 +515,10 @@ 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(); } - 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 +615,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 && 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..3a35040a2 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) { @@ -353,6 +354,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 +426,19 @@ 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; + + //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"); @@ -433,26 +448,22 @@ 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); + std::string encIdOBS = osn::EncoderUtils::getInternalEncoderFromSimple(encId); + + preset = utility::GetSafeString(config_get_string(ConfigManager::getInstance().getBasic(), "SimpleOutput", presetType.c_str())); + + 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 +476,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 +508,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 +517,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 +544,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 +554,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..0f2c22771 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,14 @@ 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);