From 1c76169821ae8e6e2152c14a80f9c1a904b6a0b3 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Tue, 16 May 2023 03:52:25 +0200 Subject: [PATCH] code: Migrate encoder::ffmpeg::nvenc to new loader --- CMakeLists.txt | 8 +- .../ffmpeg/{nvenc_shared.cpp => nvenc.cpp} | 589 +++++++++++++++--- source/encoders/ffmpeg/nvenc.hpp | 95 +++ source/encoders/ffmpeg/nvenc_h264_handler.cpp | 179 ------ source/encoders/ffmpeg/nvenc_h264_handler.hpp | 56 -- source/encoders/ffmpeg/nvenc_hevc_handler.cpp | 210 ------- source/encoders/ffmpeg/nvenc_hevc_handler.hpp | 56 -- source/encoders/ffmpeg/nvenc_shared.hpp | 40 -- 8 files changed, 584 insertions(+), 649 deletions(-) rename source/encoders/ffmpeg/{nvenc_shared.cpp => nvenc.cpp} (73%) create mode 100644 source/encoders/ffmpeg/nvenc.hpp delete mode 100644 source/encoders/ffmpeg/nvenc_h264_handler.cpp delete mode 100644 source/encoders/ffmpeg/nvenc_h264_handler.hpp delete mode 100644 source/encoders/ffmpeg/nvenc_hevc_handler.cpp delete mode 100644 source/encoders/ffmpeg/nvenc_hevc_handler.hpp delete mode 100644 source/encoders/ffmpeg/nvenc_shared.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cec9a87..32e9073 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1171,12 +1171,8 @@ if(T_CHECK) is_feature_enabled(ENCODER_FFMPEG_NVENC T_CHECK) if(T_CHECK) list(APPEND PROJECT_PRIVATE_SOURCE - "source/encoders/ffmpeg/nvenc_shared.hpp" - "source/encoders/ffmpeg/nvenc_shared.cpp" - "source/encoders/ffmpeg/nvenc_h264_handler.hpp" - "source/encoders/ffmpeg/nvenc_h264_handler.cpp" - "source/encoders/ffmpeg/nvenc_hevc_handler.hpp" - "source/encoders/ffmpeg/nvenc_hevc_handler.cpp" + "source/encoders/ffmpeg/nvenc.hpp" + "source/encoders/ffmpeg/nvenc.cpp" ) list(APPEND PROJECT_DEFINITIONS ENABLE_ENCODER_FFMPEG_NVENC diff --git a/source/encoders/ffmpeg/nvenc_shared.cpp b/source/encoders/ffmpeg/nvenc.cpp similarity index 73% rename from source/encoders/ffmpeg/nvenc_shared.cpp rename to source/encoders/ffmpeg/nvenc.cpp index 3678247..f202c41 100644 --- a/source/encoders/ffmpeg/nvenc_shared.cpp +++ b/source/encoders/ffmpeg/nvenc.cpp @@ -1,17 +1,22 @@ // AUTOGENERATED COPYRIGHT HEADER START +// AUTOGENERATED COPYRIGHT HEADER END // Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks // Copyright (C) 2022 lainon -// AUTOGENERATED COPYRIGHT HEADER END -#include "nvenc_shared.hpp" +#include "nvenc.hpp" +#include "common.hpp" +#include "strings.hpp" +#include "encoders/codecs/h264.hpp" +#include "encoders/codecs/hevc.hpp" #include "encoders/encoder-ffmpeg.hpp" #include "ffmpeg/tools.hpp" +#include "plugin.hpp" -extern "C" { #include "warning-disable.hpp" +extern "C" { #include -#include "warning-enable.hpp" } +#include "warning-enable.hpp" #define ST_I18N_PRESET "Encoder.FFmpeg.NVENC.Preset" #define ST_I18N_PRESET_(x) ST_I18N_PRESET "." D_VSTR(x) @@ -77,7 +82,15 @@ extern "C" { #define ST_I18N_OTHER_LOWDELAYKEYFRAMESCALE ST_I18N_OTHER ".LowDelayKeyFrameScale" #define ST_KEY_OTHER_LOWDELAYKEYFRAMESCALE "Other.LowDelayKeyFrameScale" -using namespace streamfx::encoder::ffmpeg::handler; +#define ST_KEY_H264_PROFILE "H264.Profile" +#define ST_KEY_H264_LEVEL "H264.Level" + +#define ST_KEY_H265_PROFILE "H265.Profile" +#define ST_KEY_H265_TIER "H265.Tier" +#define ST_KEY_H264_LEVEL "H265.Level" + +using namespace streamfx::encoder::ffmpeg; +using namespace streamfx::encoder::codec; inline bool is_cqp(std::string_view rc) { @@ -94,7 +107,7 @@ inline bool is_vbr(std::string_view rc) return std::string_view("vbr") == rc; } -bool streamfx::encoder::ffmpeg::handler::nvenc::is_available() +bool nvenc::is_available() { #if defined(D_PLATFORM_WINDOWS) #if defined(D_PLATFORM_64BIT) @@ -113,37 +126,7 @@ bool streamfx::encoder::ffmpeg::handler::nvenc::is_available() } } -void nvenc::override_update(ffmpeg_instance* instance, obs_data_t*) -{ - AVCodecContext* context = const_cast(instance->get_avcodeccontext()); - - int64_t rclookahead = 0; - int64_t surfaces = 0; - int64_t async_depth = 0; - - av_opt_get_int(context, "rc-lookahead", AV_OPT_SEARCH_CHILDREN, &rclookahead); - av_opt_get_int(context, "surfaces", AV_OPT_SEARCH_CHILDREN, &surfaces); - av_opt_get_int(context, "async_depth", AV_OPT_SEARCH_CHILDREN, &async_depth); - - // Calculate and set the number of surfaces to allocate (if not user overridden). - if (surfaces == 0) { - surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); - if (rclookahead > 0) { - surfaces = std::max(1ll, std::max(surfaces, rclookahead + (context->max_b_frames + 5ll))); - } else if (context->max_b_frames > 0) { - surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); - } else { - surfaces = 4; - } - - av_opt_set_int(context, "surfaces", surfaces, AV_OPT_SEARCH_CHILDREN); - } - - // Set delay - context->delay = std::min(std::max(static_cast(async_depth), 3), static_cast(surfaces - 1)); -} - -void nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*) +void nvenc::defaults(ffmpeg_factory* factory, obs_data_t* settings) { obs_data_set_default_string(settings, ST_KEY_PRESET, "default"); obs_data_set_default_string(settings, ST_I18N_TUNE, "hq"); @@ -232,8 +215,10 @@ static bool modified_aq(obs_properties_t* props, obs_property_t*, obs_data_t* se return true; } -void nvenc::get_properties_pre(obs_properties_t* props, const AVCodec*, const AVCodecContext* context) +void nvenc::properties_before(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props, AVCodecContext* context) { + auto codec = factory->get_avcodec(); + { auto p = obs_properties_add_list(props, ST_KEY_PRESET, D_TRANSLATE(ST_I18N_PRESET), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "preset", [&p](const AVOption* opt) { @@ -253,8 +238,10 @@ void nvenc::get_properties_pre(obs_properties_t* props, const AVCodec*, const AV } } -void nvenc::get_properties_post(obs_properties_t* props, const AVCodec* codec, const AVCodecContext* context) +void nvenc::properies_after(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props, AVCodecContext* context) { + auto codec = factory->get_avcodec(); + { // Rate Control obs_properties_t* grp = props; if (!streamfx::util::are_property_groups_broken()) { @@ -420,7 +407,7 @@ void nvenc::get_properties_post(obs_properties_t* props, const AVCodec* codec, c } } -void nvenc::get_runtime_properties(obs_properties_t* props, const AVCodec*, AVCodecContext*) +void nvenc::properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) { obs_property_set_enabled(obs_properties_get(props, ST_KEY_PRESET), false); obs_property_set_enabled(obs_properties_get(props, ST_KEY_TUNE), false); @@ -456,8 +443,94 @@ void nvenc::get_runtime_properties(obs_properties_t* props, const AVCodec*, AVCo obs_property_set_enabled(obs_properties_get(props, ST_KEY_OTHER_LOWDELAYKEYFRAMESCALE), false); } -void nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +void nvenc::migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) { + // Only test for A.B.C in A.B.C.D + version = version & STREAMFX_MASK_UPDATE; + +#define COPY_UNSET(TYPE, FROM, TO) \ + if (obs_data_has_user_value(settings, FROM)) { \ + obs_data_set_##TYPE(settings, TO, obs_data_get_##TYPE(settings, FROM)); \ + obs_data_unset_user_value(settings, FROM); \ + } + + if (version <= STREAMFX_MAKE_VERSION(0, 8, 0, 0)) { + COPY_UNSET(int, "RateControl.Bitrate.Target", ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET); + COPY_UNSET(int, "RateControl.Bitrate.Maximum", ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET); + COPY_UNSET(int, "RateControl.BufferSize", ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE); + COPY_UNSET(int, "RateControl.Quality.Minimum", ST_KEY_RATECONTROL_QP_MINIMUM); + COPY_UNSET(int, "RateControl.Quality.Maximum", ST_KEY_RATECONTROL_QP_MAXIMUM); + COPY_UNSET(double, "RateControl.Quality.Target", ST_KEY_RATECONTROL_LIMITS_QUALITY); + } + + if (version < STREAMFX_MAKE_VERSION(0, 11, 0, 0)) { + obs_data_unset_user_value(settings, "Other.AccessUnitDelimiter"); + obs_data_unset_user_value(settings, "Other.DecodedPictureBufferSize"); + } + + if (version < STREAMFX_MAKE_VERSION(0, 11, 1, 0)) { + // Preset + if (auto v = obs_data_get_int(settings, ST_KEY_PRESET); v != -1) { + std::map preset{ + {0, "default"}, {1, "slow"}, {2, "medium"}, {3, "fast"}, {4, "hp"}, {5, "hq"}, {6, "bd"}, {7, "ll"}, {8, "llhq"}, {9, "llhp"}, {10, "lossless"}, {11, "losslesshp"}, + }; + if (auto k = preset.find(v); k != preset.end()) { + obs_data_set_string(settings, ST_KEY_PRESET, k->second.data()); + } + } + + // Rate Control Mode + if (auto v = obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE); v != -1) { + if (!obs_data_has_user_value(settings, ST_KEY_RATECONTROL_MODE)) + v = 4; + + switch (v) { + case 0: // CQP + obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "constqp"); + break; + case 2: // VBR_HQ + obs_data_set_int(settings, ST_KEY_RATECONTROL_TWOPASS, 1); + obs_data_set_string(settings, ST_KEY_RATECONTROL_MULTIPASS, "qres"); + case 1: // VBR + obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "vbr"); + break; + case 5: // CBR_LD_HQ + obs_data_set_int(settings, ST_KEY_OTHER_LOWDELAYKEYFRAMESCALE, 1); + case 4: // CBR_HQ + obs_data_set_int(settings, ST_KEY_RATECONTROL_TWOPASS, 1); + obs_data_set_string(settings, ST_KEY_RATECONTROL_MULTIPASS, "qres"); + case 3: // CBR + obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "cbr"); + break; + } + } + + // Target Quality + if (auto v = obs_data_get_double(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY); v > 0) { + obs_data_set_double(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY, (v / 100.) * 51.); + } + + // B-Frame Reference Modes + if (auto v = obs_data_get_int(settings, ST_KEY_OTHER_BFRAMEREFERENCEMODE); v != -1) { + std::map preset{ + {0, "default"}, + {1, "each"}, + {2, "middle"}, + }; + if (auto k = preset.find(v); k != preset.end()) { + obs_data_set_string(settings, ST_KEY_OTHER_BFRAMEREFERENCEMODE, k->second.data()); + } + } + } + +#undef COPY_UNSET +} + +void nvenc::update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + if (const char* v = obs_data_get_string(settings, ST_KEY_PRESET); !context->internal && (v != nullptr) && (v[0] != '\0')) { av_opt_set(context->priv_data, "preset", v, AV_OPT_SEARCH_CHILDREN); } @@ -667,8 +740,41 @@ void nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* c } } -void nvenc::log_options(obs_data_t*, const AVCodec* codec, AVCodecContext* context) +void nvenc::override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) { + AVCodecContext* context = const_cast(instance->get_avcodeccontext()); + + int64_t rclookahead = 0; + int64_t surfaces = 0; + int64_t async_depth = 0; + + av_opt_get_int(context, "rc-lookahead", AV_OPT_SEARCH_CHILDREN, &rclookahead); + av_opt_get_int(context, "surfaces", AV_OPT_SEARCH_CHILDREN, &surfaces); + av_opt_get_int(context, "async_depth", AV_OPT_SEARCH_CHILDREN, &async_depth); + + // Calculate and set the number of surfaces to allocate (if not user overridden). + if (surfaces == 0) { + surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); + if (rclookahead > 0) { + surfaces = std::max(1ll, std::max(surfaces, rclookahead + (context->max_b_frames + 5ll))); + } else if (context->max_b_frames > 0) { + surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); + } else { + surfaces = 4; + } + + av_opt_set_int(context, "surfaces", surfaces, AV_OPT_SEARCH_CHILDREN); + } + + // Set delay + context->delay = std::min(std::max(static_cast(async_depth), 3), static_cast(surfaces - 1)); +} + +void nvenc::log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + using namespace ::streamfx::ffmpeg; DLOG_INFO("[%s] NVIDIA NVENC:", codec->name); @@ -729,85 +835,364 @@ void nvenc::log_options(obs_data_t*, const AVCodec* codec, AVCodecContext* conte tools::print_av_option_bool(context, "constrained-encoding", " Constrained Encoding"); } -void streamfx::encoder::ffmpeg::handler::nvenc::migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context) +// H264/AVC Handler +//------------------- + +nvenc_h264::nvenc_h264() : handler("h264_nvenc"){}; + +nvenc_h264::~nvenc_h264(){}; + +bool nvenc_h264::has_keyframes(ffmpeg_factory*) { - // Only test for A.B.C in A.B.C.D - version = version & STREAMFX_MASK_UPDATE; + return true; +} -#define COPY_UNSET(TYPE, FROM, TO) \ - if (obs_data_has_user_value(settings, FROM)) { \ - obs_data_set_##TYPE(settings, TO, obs_data_get_##TYPE(settings, FROM)); \ - obs_data_unset_user_value(settings, FROM); \ - } +bool nvenc_h264::has_threading(ffmpeg_factory*) +{ + return false; +} - if (version <= STREAMFX_MAKE_VERSION(0, 8, 0, 0)) { - COPY_UNSET(int, "RateControl.Bitrate.Target", ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET); - COPY_UNSET(int, "RateControl.Bitrate.Maximum", ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET); - COPY_UNSET(int, "RateControl.BufferSize", ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE); - COPY_UNSET(int, "RateControl.Quality.Minimum", ST_KEY_RATECONTROL_QP_MINIMUM); - COPY_UNSET(int, "RateControl.Quality.Maximum", ST_KEY_RATECONTROL_QP_MAXIMUM); - COPY_UNSET(double, "RateControl.Quality.Target", ST_KEY_RATECONTROL_LIMITS_QUALITY); - } +bool nvenc_h264::is_hardware(ffmpeg_factory*) +{ + return true; +} - if (version < STREAMFX_MAKE_VERSION(0, 11, 0, 0)) { - obs_data_unset_user_value(settings, "Other.AccessUnitDelimiter"); - obs_data_unset_user_value(settings, "Other.DecodedPictureBufferSize"); +bool nvenc_h264::is_reconfigurable(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes) +{ + threads = false; + gpu = false; + keyframes = false; + return true; +} + +void nvenc_h264::adjust_info(ffmpeg_factory* factory, std::string& id, std::string& name, std::string& codec) +{ + name = "NVIDIA NVENC H.264/AVC (via FFmpeg)"; + if (!nvenc::is_available()) // If we don't have NVENC, don't even allow listing it. + factory->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; +} + +void nvenc_h264::defaults(ffmpeg_factory* factory, obs_data_t* settings) +{ + nvenc::defaults(factory, settings); + + obs_data_set_default_string(settings, ST_KEY_H264_PROFILE, ""); + obs_data_set_default_string(settings, ST_KEY_H264_LEVEL, "auto"); +} + +void nvenc_h264::properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + if (!instance) { + this->properties_encoder(factory, instance, props); + } else { + this->properties_runtime(factory, instance, props); } +} + +void nvenc_h264::migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) +{ + nvenc::migrate(factory, instance, settings, version); if (version < STREAMFX_MAKE_VERSION(0, 11, 1, 0)) { - // Preset - if (auto v = obs_data_get_int(settings, ST_KEY_PRESET); v != -1) { + // Profile + if (auto v = obs_data_get_int(settings, ST_KEY_H264_PROFILE); v != -1) { + if (!obs_data_has_user_value(settings, ST_KEY_H264_PROFILE)) + v = 3; + std::map preset{ - {0, "default"}, {1, "slow"}, {2, "medium"}, {3, "fast"}, {4, "hp"}, {5, "hq"}, {6, "bd"}, {7, "ll"}, {8, "llhq"}, {9, "llhp"}, {10, "lossless"}, {11, "losslesshp"}, + {0, "baseline"}, {1, "baseline"}, {2, "main"}, {3, "high"}, {4, "high444p"}, }; if (auto k = preset.find(v); k != preset.end()) { - obs_data_set_string(settings, ST_KEY_PRESET, k->second.data()); + obs_data_set_string(settings, ST_KEY_H264_PROFILE, k->second.data()); } } - // Rate Control Mode - if (auto v = obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE); v != -1) { - if (!obs_data_has_user_value(settings, ST_KEY_RATECONTROL_MODE)) - v = 4; + // Level + obs_data_set_string(settings, ST_KEY_H264_LEVEL, "auto"); + } +} - switch (v) { - case 0: // CQP - obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "constqp"); - break; - case 2: // VBR_HQ - obs_data_set_int(settings, ST_KEY_RATECONTROL_TWOPASS, 1); - obs_data_set_string(settings, ST_KEY_RATECONTROL_MULTIPASS, "qres"); - case 1: // VBR - obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "vbr"); - break; - case 5: // CBR_LD_HQ - obs_data_set_int(settings, ST_KEY_OTHER_LOWDELAYKEYFRAMESCALE, 1); - case 4: // CBR_HQ - obs_data_set_int(settings, ST_KEY_RATECONTROL_TWOPASS, 1); - obs_data_set_string(settings, ST_KEY_RATECONTROL_MULTIPASS, "qres"); - case 3: // CBR - obs_data_set_string(settings, ST_KEY_RATECONTROL_MODE, "cbr"); - break; - } +void nvenc_h264::update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + + nvenc::update(factory, instance, settings); + + if (!context->internal) { + if (const char* v = obs_data_get_string(settings, ST_KEY_H264_PROFILE); v && (v[0] != '\0')) { + av_opt_set(context->priv_data, "profile", v, AV_OPT_SEARCH_CHILDREN); + } + if (const char* v = obs_data_get_string(settings, ST_KEY_H264_LEVEL); v && (v[0] != '\0')) { + av_opt_set(context->priv_data, "level", v, AV_OPT_SEARCH_CHILDREN); + } + } +} + +void nvenc_h264::override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + nvenc::override_update(factory, instance, settings); +} + +void nvenc_h264::log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + + nvenc::log(factory, instance, settings); + + DLOG_INFO("[%s] H.264/AVC:", codec->name); + ::streamfx::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "profile", " Profile", [](int64_t v, std::string_view o) { return std::string(o); }); + ::streamfx::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "level", " Level", [](int64_t v, std::string_view o) { return std::string(o); }); +} + +void nvenc_h264::properties_encoder(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + auto codec = factory->get_avcodec(); + + AVCodecContext* context = avcodec_alloc_context3(codec); + if (!context->priv_data) { + avcodec_free_context(&context); + return; + } + + nvenc::properties_before(factory, instance, props, context); + + { + obs_properties_t* grp = props; + if (!streamfx::util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, S_CODEC_H264, D_TRANSLATE(S_CODEC_H264), OBS_GROUP_NORMAL, grp); } - // Target Quality - if (auto v = obs_data_get_double(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY); v > 0) { - obs_data_set_double(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY, (v / 100.) * 51.); + { + auto p = obs_properties_add_list(grp, ST_KEY_H264_PROFILE, D_TRANSLATE(S_CODEC_H264_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, D_TRANSLATE(S_STATE_DEFAULT), ""); + streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "profile", [&p](const AVOption* opt) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_H264_PROFILE, opt->name); + obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); + }); } + { + auto p = obs_properties_add_list(grp, ST_KEY_H264_LEVEL, D_TRANSLATE(S_CODEC_H264_LEVEL), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - // B-Frame Reference Modes - if (auto v = obs_data_get_int(settings, ST_KEY_OTHER_BFRAMEREFERENCEMODE); v != -1) { - std::map preset{ - {0, "default"}, - {1, "each"}, - {2, "middle"}, - }; - if (auto k = preset.find(v); k != preset.end()) { - obs_data_set_string(settings, ST_KEY_OTHER_BFRAMEREFERENCEMODE, k->second.data()); - } + streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "level", [&p](const AVOption* opt) { + if (opt->default_val.i64 == 0) { + obs_property_list_add_string(p, D_TRANSLATE(S_STATE_AUTOMATIC), "auto"); + } else { + obs_property_list_add_string(p, opt->name, opt->name); + } + }); } } -#undef COPY_UNSET + nvenc::properies_after(factory, instance, props, context); + + if (context) { + avcodec_free_context(&context); + } } + +void nvenc_h264::properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + nvenc::properties_runtime(factory, instance, props); +} + +static auto inst_h264 = nvenc_h264(); + +// H265/HEVC Handler +//------------------- + +nvenc_hevc::nvenc_hevc() : handler("hevc_nvenc"){}; + +nvenc_hevc::~nvenc_hevc(){}; + +bool nvenc_hevc::has_keyframes(ffmpeg_factory*) +{ + return true; +} + +bool nvenc_hevc::has_threading(ffmpeg_factory* instance) +{ + return false; +} + +bool nvenc_hevc::is_hardware(ffmpeg_factory* instance) +{ + return true; +} + +bool nvenc_hevc::is_reconfigurable(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes) +{ + threads = false; + gpu = false; + keyframes = false; + return true; +} + +void nvenc_hevc::adjust_info(ffmpeg_factory* factory, std::string& id, std::string& name, std::string& codec) +{ + name = "NVIDIA NVENC H.265/HEVC (via FFmpeg)"; + if (!nvenc::is_available()) + factory->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED; +} + +void nvenc_hevc::defaults(ffmpeg_factory* factory, obs_data_t* settings) +{ + nvenc::defaults(factory, settings); + + obs_data_set_default_string(settings, ST_KEY_H265_PROFILE, ""); + obs_data_set_default_string(settings, ST_KEY_H265_TIER, ""); + obs_data_set_default_string(settings, ST_KEY_H264_LEVEL, "auto"); +} + +void nvenc_hevc::properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + if (!instance) { + this->properties_encoder(factory, instance, props); + } else { + this->properties_runtime(factory, instance, props); + } +} + +void nvenc_hevc::migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) +{ + nvenc::migrate(factory, instance, settings, version); + + if (version < STREAMFX_MAKE_VERSION(0, 11, 1, 0)) { + // Profile + if (auto v = obs_data_get_int(settings, ST_KEY_H265_PROFILE); v != -1) { + if (!obs_data_has_user_value(settings, ST_KEY_H265_PROFILE)) + v = 0; + + std::map preset{ + {0, "main"}, + {1, "main10"}, + {2, "rext"}, + }; + if (auto k = preset.find(v); k != preset.end()) { + obs_data_set_string(settings, ST_KEY_H265_PROFILE, k->second.data()); + } + } + + // Tier + if (auto v = obs_data_get_int(settings, ST_KEY_H265_TIER); v != -1) { + if (!obs_data_has_user_value(settings, ST_KEY_H265_TIER)) + v = 0; + + std::map preset{ + {0, "main"}, + {1, "high"}, + }; + if (auto k = preset.find(v); k != preset.end()) { + obs_data_set_string(settings, ST_KEY_H265_TIER, k->second.data()); + } + } + + // Level + obs_data_set_string(settings, ST_KEY_H264_LEVEL, "auto"); + } +} + +void nvenc_hevc::update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + + nvenc::update(factory, instance, settings); + + if (!context->internal) { + if (const char* v = obs_data_get_string(settings, ST_KEY_H265_PROFILE); v && (v[0] != '\0')) { + av_opt_set(context->priv_data, "profile", v, AV_OPT_SEARCH_CHILDREN); + } + if (const char* v = obs_data_get_string(settings, ST_KEY_H265_TIER); v && (v[0] != '\0')) { + av_opt_set(context->priv_data, "tier", v, AV_OPT_SEARCH_CHILDREN); + } + if (const char* v = obs_data_get_string(settings, ST_KEY_H264_LEVEL); v && (v[0] != '\0')) { + av_opt_set(context->priv_data, "level", v, AV_OPT_SEARCH_CHILDREN); + } + } +} + +void nvenc_hevc::override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + nvenc::override_update(factory, instance, settings); +} + +void nvenc_hevc::log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) +{ + auto codec = factory->get_avcodec(); + auto context = instance->get_avcodeccontext(); + + nvenc::log(factory, instance, settings); + + DLOG_INFO("[%s] H.265/HEVC:", codec->name); + ::streamfx::ffmpeg::tools::print_av_option_string2(context, "profile", " Profile", [](int64_t v, std::string_view o) { return std::string(o); }); + ::streamfx::ffmpeg::tools::print_av_option_string2(context, "level", " Level", [](int64_t v, std::string_view o) { return std::string(o); }); + ::streamfx::ffmpeg::tools::print_av_option_string2(context, "tier", " Tier", [](int64_t v, std::string_view o) { return std::string(o); }); +} + +void nvenc_hevc::properties_encoder(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + auto codec = factory->get_avcodec(); + + AVCodecContext* context = avcodec_alloc_context3(codec); + if (!context->priv_data) { + avcodec_free_context(&context); + return; + } + + nvenc::properties_before(factory, instance, props, context); + + { + obs_properties_t* grp = props; + if (!streamfx::util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, S_CODEC_HEVC, D_TRANSLATE(S_CODEC_HEVC), OBS_GROUP_NORMAL, grp); + } + + { + auto p = obs_properties_add_list(grp, ST_KEY_H265_PROFILE, D_TRANSLATE(S_CODEC_HEVC_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); + streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "profile", [&p](const AVOption* opt) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_HEVC_PROFILE, opt->name); + obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); + }); + } + { + auto p = obs_properties_add_list(grp, ST_KEY_H265_TIER, D_TRANSLATE(S_CODEC_HEVC_TIER), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); + streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "tier", [&p](const AVOption* opt) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_HEVC_TIER, opt->name); + obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); + }); + } + { + auto p = obs_properties_add_list(grp, ST_KEY_H264_LEVEL, D_TRANSLATE(S_CODEC_HEVC_LEVEL), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + + streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "level", [&p](const AVOption* opt) { + if (opt->default_val.i64 == 0) { + obs_property_list_add_string(p, D_TRANSLATE(S_STATE_AUTOMATIC), "auto"); + } else { + obs_property_list_add_string(p, opt->name, opt->name); + } + }); + } + } + + nvenc::properies_after(factory, instance, props, context); + + if (context) { + avcodec_free_context(&context); + } +} + +void nvenc_hevc::properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) +{ + nvenc::properties_runtime(factory, instance, props); +} + +static auto inst_hevc = nvenc_hevc(); diff --git a/source/encoders/ffmpeg/nvenc.hpp b/source/encoders/ffmpeg/nvenc.hpp new file mode 100644 index 0000000..06920ad --- /dev/null +++ b/source/encoders/ffmpeg/nvenc.hpp @@ -0,0 +1,95 @@ +// AUTOGENERATED COPYRIGHT HEADER START +// AUTOGENERATED COPYRIGHT HEADER END +// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks + +#pragma once +#include "encoders/encoder-ffmpeg.hpp" +#include "encoders/ffmpeg/handler.hpp" + +#include "warning-disable.hpp" +#include +#include +extern "C" { +#include +} +#include "warning-enable.hpp" + +/* NVENC has multiple compression modes: +- CBR: Constant Bitrate (rc=cbr) +- VBR: Variable Bitrate (rc=vbr) +- CQP: Constant QP (rc=cqp) +- CQ: Constant Quality (rc=vbr b=0 maxrate=0 qmin=0 qmax=51 cq=qp), this is basically CRF in X264. +*/ + +namespace streamfx::encoder::ffmpeg { + namespace nvenc { + bool is_available(); + + void defaults(ffmpeg_factory* factory, obs_data_t* settings); + void properties_before(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props, AVCodecContext* context); + void properies_after(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props, AVCodecContext* context); + void properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props); + void migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version); + void update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings); + void override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings); + void log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings); + } // namespace nvenc + + class nvenc_h264 : public handler { + public: + nvenc_h264(); + virtual ~nvenc_h264(); + + bool has_keyframes(ffmpeg_factory* factory) override; + bool has_threading(ffmpeg_factory* factory) override; + bool is_hardware(ffmpeg_factory* factory) override; + bool is_reconfigurable(ffmpeg_factory* factory, bool& threads, bool& gpu, bool& keyframes) override; + + void adjust_info(ffmpeg_factory* factory, std::string& id, std::string& name, std::string& codec) override; + + std::string help(ffmpeg_factory* factory) override + { + return "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-FFmpeg-NVENC"; + }; + + void defaults(ffmpeg_factory* factory, obs_data_t* settings) override; + void properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) override; + void migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) override; + void update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + void override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + void log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + + private: + void properties_encoder(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props); + void properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props); + }; + + class nvenc_hevc : public handler { + public: + nvenc_hevc(); + virtual ~nvenc_hevc(); + + bool has_keyframes(ffmpeg_factory* factory) override; + bool has_threading(ffmpeg_factory* factory) override; + bool is_hardware(ffmpeg_factory* factory) override; + bool is_reconfigurable(ffmpeg_factory* factory, bool& threads, bool& gpu, bool& keyframes) override; + + void adjust_info(ffmpeg_factory* factory, std::string& id, std::string& name, std::string& codec) override; + + std::string help(ffmpeg_factory* factory) override + { + return "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-FFmpeg-NVENC"; + }; + + void defaults(ffmpeg_factory* factory, obs_data_t* settings) override; + void properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) override; + void migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) override; + void update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + void override_update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + void log(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override; + + private: + void properties_encoder(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props); + void properties_runtime(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props); + }; +} // namespace streamfx::encoder::ffmpeg diff --git a/source/encoders/ffmpeg/nvenc_h264_handler.cpp b/source/encoders/ffmpeg/nvenc_h264_handler.cpp deleted file mode 100644 index 1989578..0000000 --- a/source/encoders/ffmpeg/nvenc_h264_handler.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// AUTOGENERATED COPYRIGHT HEADER START -// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks -// Copyright (C) 2022 lainon -// AUTOGENERATED COPYRIGHT HEADER END - -#include "nvenc_h264_handler.hpp" -#include "common.hpp" -#include "strings.hpp" -#include "../codecs/h264.hpp" -#include "../encoder-ffmpeg.hpp" -#include "ffmpeg/tools.hpp" -#include "nvenc_shared.hpp" -#include "plugin.hpp" - -extern "C" { -#include "warning-disable.hpp" -#include -#include "warning-enable.hpp" -} - -#define ST_KEY_PROFILE "H264.Profile" -#define ST_KEY_LEVEL "H264.Level" - -using namespace streamfx::encoder::ffmpeg::handler; -using namespace streamfx::encoder::codec::h264; - -void nvenc_h264_handler::adjust_info(ffmpeg_factory* fac, const AVCodec*, std::string&, std::string& name, std::string&) -{ - name = "NVIDIA NVENC H.264/AVC (via FFmpeg)"; - if (!nvenc::is_available()) - fac->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED; -} - -void nvenc_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool) -{ - nvenc::get_defaults(settings, codec, context); - - obs_data_set_default_string(settings, ST_KEY_PROFILE, ""); - obs_data_set_default_string(settings, ST_KEY_LEVEL, "auto"); -} - -bool nvenc_h264_handler::has_keyframe_support(ffmpeg_factory*) -{ - return true; -} - -bool nvenc_h264_handler::is_hardware_encoder(ffmpeg_factory*) -{ - return true; -} - -bool nvenc_h264_handler::has_threading_support(ffmpeg_factory*) -{ - return false; -} - -bool nvenc_h264_handler::has_pixel_format_support(ffmpeg_factory*) -{ - return true; -} - -void nvenc_h264_handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context, bool) -{ - if (!context) { - this->get_encoder_properties(props, codec); - } else { - this->get_runtime_properties(props, codec, context); - } -} - -void nvenc_h264_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::update(settings, codec, context); - - if (!context->internal) { - if (const char* v = obs_data_get_string(settings, ST_KEY_PROFILE); v && (v[0] != '\0')) { - av_opt_set(context->priv_data, "profile", v, AV_OPT_SEARCH_CHILDREN); - } - if (const char* v = obs_data_get_string(settings, ST_KEY_LEVEL); v && (v[0] != '\0')) { - av_opt_set(context->priv_data, "level", v, AV_OPT_SEARCH_CHILDREN); - } - } -} - -void nvenc_h264_handler::override_update(ffmpeg_instance* instance, obs_data_t* settings) -{ - nvenc::override_update(instance, settings); -} - -void nvenc_h264_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::log_options(settings, codec, context); - - DLOG_INFO("[%s] H.264/AVC:", codec->name); - ::streamfx::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "profile", " Profile", [](int64_t v, std::string_view o) { return std::string(o); }); - ::streamfx::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "level", " Level", [](int64_t v, std::string_view o) { return std::string(o); }); -} - -void nvenc_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec) -{ - AVCodecContext* context = avcodec_alloc_context3(codec); - if (!context->priv_data) { - avcodec_free_context(&context); - return; - } - - nvenc::get_properties_pre(props, codec, context); - - { - obs_properties_t* grp = props; - if (!streamfx::util::are_property_groups_broken()) { - grp = obs_properties_create(); - obs_properties_add_group(props, S_CODEC_H264, D_TRANSLATE(S_CODEC_H264), OBS_GROUP_NORMAL, grp); - } - - { - auto p = obs_properties_add_list(grp, ST_KEY_PROFILE, D_TRANSLATE(S_CODEC_H264_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - obs_property_list_add_string(p, D_TRANSLATE(S_STATE_DEFAULT), ""); - streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "profile", [&p](const AVOption* opt) { - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_H264_PROFILE, opt->name); - obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); - }); - } - { - auto p = obs_properties_add_list(grp, ST_KEY_LEVEL, D_TRANSLATE(S_CODEC_H264_LEVEL), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - - streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "level", [&p](const AVOption* opt) { - if (opt->default_val.i64 == 0) { - obs_property_list_add_string(p, D_TRANSLATE(S_STATE_AUTOMATIC), "auto"); - } else { - obs_property_list_add_string(p, opt->name, opt->name); - } - }); - } - } - - nvenc::get_properties_post(props, codec, context); - - if (context) { - avcodec_free_context(&context); - } -} - -void nvenc_h264_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::get_runtime_properties(props, codec, context); -} - -void streamfx::encoder::ffmpeg::handler::nvenc_h264_handler::migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::migrate(settings, version, codec, context); - - if (version < STREAMFX_MAKE_VERSION(0, 11, 1, 0)) { - // Profile - if (auto v = obs_data_get_int(settings, ST_KEY_PROFILE); v != -1) { - if (!obs_data_has_user_value(settings, ST_KEY_PROFILE)) - v = 3; - - std::map preset{ - {0, "baseline"}, {1, "baseline"}, {2, "main"}, {3, "high"}, {4, "high444p"}, - }; - if (auto k = preset.find(v); k != preset.end()) { - obs_data_set_string(settings, ST_KEY_PROFILE, k->second.data()); - } - } - - // Level - obs_data_set_string(settings, ST_KEY_LEVEL, "auto"); - } -} - -bool nvenc_h264_handler::supports_reconfigure(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes) -{ - threads = false; - gpu = false; - keyframes = false; - return true; -} diff --git a/source/encoders/ffmpeg/nvenc_h264_handler.hpp b/source/encoders/ffmpeg/nvenc_h264_handler.hpp deleted file mode 100644 index 0e31665..0000000 --- a/source/encoders/ffmpeg/nvenc_h264_handler.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// AUTOGENERATED COPYRIGHT HEADER START -// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks -// AUTOGENERATED COPYRIGHT HEADER END - -#pragma once -#include "handler.hpp" - -extern "C" { -#include "warning-disable.hpp" -#include -#include "warning-enable.hpp" -} - -namespace streamfx::encoder::ffmpeg::handler { - class nvenc_h264_handler : public handler { - public: - virtual ~nvenc_h264_handler(){}; - - public /*factory*/: - virtual void adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name, std::string& codec_id); - - virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode); - - virtual std::string_view get_help_url(const AVCodec* codec) override - { - return "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-FFmpeg-NVENC"; - }; - - public /*support tests*/: - virtual bool has_keyframe_support(ffmpeg_factory* instance); - - virtual bool is_hardware_encoder(ffmpeg_factory* instance); - - virtual bool has_threading_support(ffmpeg_factory* instance); - - virtual bool has_pixel_format_support(ffmpeg_factory* instance); - - virtual bool supports_reconfigure(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes); - - public /*settings*/: - virtual void get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context, bool hw_encode); - - virtual void migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context); - - virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - virtual void override_update(ffmpeg_instance* instance, obs_data_t* settings); - - virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - private: - void get_encoder_properties(obs_properties_t* props, const AVCodec* codec); - - void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context); - }; -} // namespace streamfx::encoder::ffmpeg::handler diff --git a/source/encoders/ffmpeg/nvenc_hevc_handler.cpp b/source/encoders/ffmpeg/nvenc_hevc_handler.cpp deleted file mode 100644 index 28996f5..0000000 --- a/source/encoders/ffmpeg/nvenc_hevc_handler.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// AUTOGENERATED COPYRIGHT HEADER START -// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks -// Copyright (C) 2022 lainon -// AUTOGENERATED COPYRIGHT HEADER END - -#include "nvenc_hevc_handler.hpp" -#include "common.hpp" -#include "strings.hpp" -#include "../codecs/hevc.hpp" -#include "../encoder-ffmpeg.hpp" -#include "ffmpeg/tools.hpp" -#include "nvenc_shared.hpp" -#include "plugin.hpp" - -extern "C" { -#include "warning-disable.hpp" -#include -#include "warning-enable.hpp" -} - -#define ST_KEY_PROFILE "H265.Profile" -#define ST_KEY_TIER "H265.Tier" -#define ST_KEY_LEVEL "H265.Level" - -using namespace streamfx::encoder::ffmpeg::handler; -using namespace streamfx::encoder::codec::hevc; - -void nvenc_hevc_handler::adjust_info(ffmpeg_factory* fac, const AVCodec*, std::string&, std::string& name, std::string&) -{ - name = "NVIDIA NVENC H.265/HEVC (via FFmpeg)"; - if (!nvenc::is_available()) - fac->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED; -} - -void nvenc_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool) -{ - nvenc::get_defaults(settings, codec, context); - - obs_data_set_default_string(settings, ST_KEY_PROFILE, ""); - obs_data_set_default_string(settings, ST_KEY_TIER, ""); - obs_data_set_default_string(settings, ST_KEY_LEVEL, "auto"); -} - -bool nvenc_hevc_handler::has_keyframe_support(ffmpeg_factory*) -{ - return true; -} - -bool nvenc_hevc_handler::is_hardware_encoder(ffmpeg_factory* instance) -{ - return true; -} - -bool nvenc_hevc_handler::has_threading_support(ffmpeg_factory* instance) -{ - return false; -} - -bool nvenc_hevc_handler::has_pixel_format_support(ffmpeg_factory* instance) -{ - return true; -} - -void nvenc_hevc_handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context, bool) -{ - if (!context) { - this->get_encoder_properties(props, codec); - } else { - this->get_runtime_properties(props, codec, context); - } -} - -void nvenc_hevc_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::update(settings, codec, context); - - if (!context->internal) { - if (const char* v = obs_data_get_string(settings, ST_KEY_PROFILE); v && (v[0] != '\0')) { - av_opt_set(context->priv_data, "profile", v, AV_OPT_SEARCH_CHILDREN); - } - if (const char* v = obs_data_get_string(settings, ST_KEY_TIER); v && (v[0] != '\0')) { - av_opt_set(context->priv_data, "tier", v, AV_OPT_SEARCH_CHILDREN); - } - if (const char* v = obs_data_get_string(settings, ST_KEY_LEVEL); v && (v[0] != '\0')) { - av_opt_set(context->priv_data, "level", v, AV_OPT_SEARCH_CHILDREN); - } - } -} - -void nvenc_hevc_handler::override_update(ffmpeg_instance* instance, obs_data_t* settings) -{ - nvenc::override_update(instance, settings); -} - -void nvenc_hevc_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::log_options(settings, codec, context); - - DLOG_INFO("[%s] H.265/HEVC:", codec->name); - ::streamfx::ffmpeg::tools::print_av_option_string2(context, "profile", " Profile", [](int64_t v, std::string_view o) { return std::string(o); }); - ::streamfx::ffmpeg::tools::print_av_option_string2(context, "level", " Level", [](int64_t v, std::string_view o) { return std::string(o); }); - ::streamfx::ffmpeg::tools::print_av_option_string2(context, "tier", " Tier", [](int64_t v, std::string_view o) { return std::string(o); }); -} - -void nvenc_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec) -{ - AVCodecContext* context = avcodec_alloc_context3(codec); - if (!context->priv_data) { - avcodec_free_context(&context); - return; - } - - nvenc::get_properties_pre(props, codec, context); - - { - obs_properties_t* grp = props; - if (!streamfx::util::are_property_groups_broken()) { - grp = obs_properties_create(); - obs_properties_add_group(props, S_CODEC_HEVC, D_TRANSLATE(S_CODEC_HEVC), OBS_GROUP_NORMAL, grp); - } - - { - auto p = obs_properties_add_list(grp, ST_KEY_PROFILE, D_TRANSLATE(S_CODEC_HEVC_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); - streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "profile", [&p](const AVOption* opt) { - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_HEVC_PROFILE, opt->name); - obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); - }); - } - { - auto p = obs_properties_add_list(grp, ST_KEY_TIER, D_TRANSLATE(S_CODEC_HEVC_TIER), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); - streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "tier", [&p](const AVOption* opt) { - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "%s.%s", S_CODEC_HEVC_TIER, opt->name); - obs_property_list_add_string(p, D_TRANSLATE(buffer), opt->name); - }); - } - { - auto p = obs_properties_add_list(grp, ST_KEY_LEVEL, D_TRANSLATE(S_CODEC_HEVC_LEVEL), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - - streamfx::ffmpeg::tools::avoption_list_add_entries(context->priv_data, "level", [&p](const AVOption* opt) { - if (opt->default_val.i64 == 0) { - obs_property_list_add_string(p, D_TRANSLATE(S_STATE_AUTOMATIC), "auto"); - } else { - obs_property_list_add_string(p, opt->name, opt->name); - } - }); - } - } - - nvenc::get_properties_post(props, codec, context); - - if (context) { - avcodec_free_context(&context); - } -} - -void nvenc_hevc_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::get_runtime_properties(props, codec, context); -} - -void streamfx::encoder::ffmpeg::handler::nvenc_hevc_handler::migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context) -{ - nvenc::migrate(settings, version, codec, context); - - if (version < STREAMFX_MAKE_VERSION(0, 11, 1, 0)) { - // Profile - if (auto v = obs_data_get_int(settings, ST_KEY_PROFILE); v != -1) { - if (!obs_data_has_user_value(settings, ST_KEY_PROFILE)) - v = 0; - - std::map preset{ - {0, "main"}, - {1, "main10"}, - {2, "rext"}, - }; - if (auto k = preset.find(v); k != preset.end()) { - obs_data_set_string(settings, ST_KEY_PROFILE, k->second.data()); - } - } - - // Tier - if (auto v = obs_data_get_int(settings, ST_KEY_TIER); v != -1) { - if (!obs_data_has_user_value(settings, ST_KEY_TIER)) - v = 0; - - std::map preset{ - {0, "main"}, - {1, "high"}, - }; - if (auto k = preset.find(v); k != preset.end()) { - obs_data_set_string(settings, ST_KEY_TIER, k->second.data()); - } - } - - // Level - obs_data_set_string(settings, ST_KEY_LEVEL, "auto"); - } -} - -bool nvenc_hevc_handler::supports_reconfigure(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes) -{ - threads = false; - gpu = false; - keyframes = false; - return true; -} diff --git a/source/encoders/ffmpeg/nvenc_hevc_handler.hpp b/source/encoders/ffmpeg/nvenc_hevc_handler.hpp deleted file mode 100644 index f08f8f3..0000000 --- a/source/encoders/ffmpeg/nvenc_hevc_handler.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// AUTOGENERATED COPYRIGHT HEADER START -// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks -// AUTOGENERATED COPYRIGHT HEADER END - -#pragma once -#include "handler.hpp" - -extern "C" { -#include "warning-disable.hpp" -#include -#include "warning-enable.hpp" -} - -namespace streamfx::encoder::ffmpeg::handler { - class nvenc_hevc_handler : public handler { - public: - virtual ~nvenc_hevc_handler(){}; - - public /*factory*/: - virtual void adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name, std::string& codec_id); - - virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode); - - virtual std::string_view get_help_url(const AVCodec* codec) override - { - return "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-FFmpeg-NVENC"; - }; - - public /*support tests*/: - virtual bool has_keyframe_support(ffmpeg_factory* instance); - - virtual bool is_hardware_encoder(ffmpeg_factory* instance); - - virtual bool has_threading_support(ffmpeg_factory* instance); - - virtual bool has_pixel_format_support(ffmpeg_factory* instance); - - virtual bool supports_reconfigure(ffmpeg_factory* instance, bool& threads, bool& gpu, bool& keyframes); - - public /*settings*/: - virtual void get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context, bool hw_encode); - - virtual void migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context); - - virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - virtual void override_update(ffmpeg_instance* instance, obs_data_t* settings); - - virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - private: - void get_encoder_properties(obs_properties_t* props, const AVCodec* codec); - - void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context); - }; -} // namespace streamfx::encoder::ffmpeg::handler diff --git a/source/encoders/ffmpeg/nvenc_shared.hpp b/source/encoders/ffmpeg/nvenc_shared.hpp deleted file mode 100644 index 4a7a1d1..0000000 --- a/source/encoders/ffmpeg/nvenc_shared.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// AUTOGENERATED COPYRIGHT HEADER START -// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks -// AUTOGENERATED COPYRIGHT HEADER END - -#pragma once -#include "common.hpp" -#include "handler.hpp" - -extern "C" { -#include "warning-disable.hpp" -#include -#include "warning-enable.hpp" -} - -/* NVENC has multiple compression modes: -- CBR: Constant Bitrate (rc=cbr) -- VBR: Variable Bitrate (rc=vbr) -- CQP: Constant QP (rc=cqp) -- CQ: Constant Quality (rc=vbr b=0 maxrate=0 qmin=0 qmax=51 cq=qp), this is basically CRF in X264. -*/ - -namespace streamfx::encoder::ffmpeg::handler::nvenc { - bool is_available(); - - void override_update(ffmpeg_instance* instance, obs_data_t* settings); - - void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - void get_properties_pre(obs_properties_t* props, const AVCodec* codec, const AVCodecContext* context); - - void get_properties_post(obs_properties_t* props, const AVCodec* codec, const AVCodecContext* context); - - void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context); - - void migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context); - - void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); - - void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); -} // namespace streamfx::encoder::ffmpeg::handler::nvenc