diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index e8b73293c2cea6..ea35e36bd65e2e 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -25,6 +25,8 @@ FilePath="File Path" AMFOpts="AMF/FFmpeg Options" AMFOpts.ToolTip="Use to specify custom AMF or FFmpeg options. For example, \"level=5.2 profile=main\". Check the AMF encoder docs for more details." +AMF.PreAnalysis="Pre-Analysis" +AMF.PreAnalysis.ToolTip="Enables improved encoding quality at the cost of performance by calculating additional metrics such as activity and spatial complexity." GPU="GPU" BFrames="Max B-frames" diff --git a/plugins/obs-ffmpeg/texture-amf.cpp b/plugins/obs-ffmpeg/texture-amf.cpp index efe2d154f37eda..d636108a5f3db1 100644 --- a/plugins/obs-ffmpeg/texture-amf.cpp +++ b/plugins/obs-ffmpeg/texture-amf.cpp @@ -466,6 +466,38 @@ static inline int64_t convert_to_obs_ts(amf_base *enc, int64_t ts) return ts * (int64_t)enc->fps_den / amf_timebase; } +static inline const char *convert_amf_memory_type_to_string(AMF_MEMORY_TYPE type) +{ + switch (type) { + case AMF_MEMORY_UNKNOWN: + return "Unknown"; + case AMF_MEMORY_HOST: + return "Host"; + case AMF_MEMORY_DX9: + return "DX9"; + case AMF_MEMORY_DX11: + return "DX11"; + case AMF_MEMORY_OPENCL: + return "OpenCL"; + case AMF_MEMORY_OPENGL: + return "OpenGL"; + case AMF_MEMORY_XV: + return "XV"; + case AMF_MEMORY_GRALLOC: + return "Gralloc"; + case AMF_MEMORY_COMPUTE_FOR_DX9: + return "Compute For DX9"; + case AMF_MEMORY_COMPUTE_FOR_DX11: + return "Compute For DX11"; + case AMF_MEMORY_VULKAN: + return "Vulkan"; + case AMF_MEMORY_DX12: + return "DX12"; + default: + return "Invalid"; + } +} + static void convert_to_encoder_packet(amf_base *enc, AMFDataPtr &data, encoder_packet *packet) { if (!data) @@ -1082,14 +1114,17 @@ static void check_texture_encode_capability(obs_encoder_t *encoder, amf_codec_ty #include "texture-amf-opts.hpp" -static void amf_defaults(obs_data_t *settings) +/* These are initial recommended settings that may be lowered later + * once we know more info such as the resolution and frame rate. + */ +static void amf_avc_defaults(obs_data_t *settings) { + obs_data_set_default_string(settings, "rate_control", "CBR"); obs_data_set_default_int(settings, "bitrate", 2500); obs_data_set_default_int(settings, "cqp", 20); - obs_data_set_default_string(settings, "rate_control", "CBR"); obs_data_set_default_string(settings, "preset", "quality"); obs_data_set_default_string(settings, "profile", "high"); - obs_data_set_default_int(settings, "bf", 3); + obs_data_set_default_int(settings, "bf", 2); } static bool rate_control_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings) @@ -1156,6 +1191,9 @@ static obs_properties_t *amf_properties_internal(amf_codec_type codec) #undef add_profile } + p = obs_properties_add_bool(props, "pre_analysis", obs_module_text("AMF.PreAnalysis")); + obs_property_set_long_description(p, obs_module_text("AMF.PreAnalysis.ToolTip")); + if (amf_codec_type::AVC == codec) { obs_properties_add_int(props, "bf", obs_module_text("BFrames"), 0, 5, 1); } @@ -1246,8 +1284,17 @@ static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_avc_property(enc, PEAK_BITRATE, bitrate); set_avc_property(enc, VBV_BUFFER_SIZE, bitrate); - if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) { + if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR || + rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR) { set_avc_property(enc, FILLER_DATA_ENABLE, true); + set_avc_property(enc, ENFORCE_HRD, true); + } else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || + rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { + set_avc_property(enc, PEAK_BITRATE, bitrate * 1.5); + set_avc_property(enc, VBV_BUFFER_SIZE, bitrate * 1.5); + } else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR) { + int64_t framerate = enc->fps_num / enc->fps_den; + set_avc_property(enc, VBV_BUFFER_SIZE, (bitrate / framerate) * 1.1); } } else { set_avc_property(enc, QP_I, qp); @@ -1290,16 +1337,37 @@ try { return false; } +static inline void adjust_recommended_avc_defaults(amf_base *enc, obs_data_t *settings) +{ + int64_t framerate = enc->fps_num / enc->fps_den; + if ((enc->cx * enc->cy > 1920 * 1088) || (framerate > 60)) { + // Recommended base defaults + obs_data_set_default_int(settings, "bitrate", 2500); + obs_data_set_default_int(settings, "cqp", 20); + obs_data_set_default_string(settings, "rate_control", "CBR"); + obs_data_set_default_string(settings, "preset", "quality"); + obs_data_set_default_string(settings, "profile", "high"); + obs_data_set_default_int(settings, "bf", 0); + info("Original base default settings were used according to resolution" + " and framerate."); + } +} + static bool amf_avc_init(void *data, obs_data_t *settings) { amf_base *enc = (amf_base *)data; + // Initial high perceptual quality settings are set in the UI. + // Adjust during init based on resolution and framerate. + adjust_recommended_avc_defaults(enc, settings); + int64_t bitrate = obs_data_get_int(settings, "bitrate"); int64_t qp = obs_data_get_int(settings, "cqp"); const char *preset = obs_data_get_string(settings, "preset"); const char *profile = obs_data_get_string(settings, "profile"); const char *rc_str = obs_data_get_string(settings, "rate_control"); int64_t bf = obs_data_get_int(settings, "bf"); + bool pa_enabled = obs_data_get_bool(settings, "pre_analysis"); if (enc->bframes_supported) { set_avc_property(enc, MAX_CONSECUTIVE_BPICTURES, bf); @@ -1309,7 +1377,7 @@ static bool amf_avc_init(void *data, obs_data_t *settings) * as those with high motion. This only takes effect if * Pre-Analysis is enabled. */ - if (bf > 0) { + if (bf > 0 && pa_enabled == true) { set_avc_property(enc, ADAPTIVE_MINIGOP, true); } @@ -1323,12 +1391,14 @@ static bool amf_avc_init(void *data, obs_data_t *settings) int rc = get_avc_rate_control(rc_str); set_avc_property(enc, RATE_CONTROL_METHOD, rc); - if (rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP) + if (rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP && + rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR && + rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR) { set_avc_property(enc, ENABLE_VBAQ, true); + } amf_avc_update_data(enc, rc, bitrate * 1000, qp); - set_avc_property(enc, ENFORCE_HRD, true); set_avc_property(enc, HIGH_MOTION_QUALITY_BOOST_ENABLE, false); int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); @@ -1366,8 +1436,10 @@ static bool amf_avc_init(void *data, obs_data_t *settings) "\tb-frames: %d\n" "\twidth: %d\n" "\theight: %d\n" + "\tpre-analysis: %s\n" "\tparams: %s", - rc_str, bitrate, qp, gop_size, preset, profile, bf, enc->cx, enc->cy, ffmpeg_opts); + rc_str, bitrate, qp, gop_size, preset, profile, bf, enc->cx, enc->cy, pa_enabled ? "true" : "false", + ffmpeg_opts); return true; } @@ -1421,10 +1493,11 @@ static void amf_avc_create_internal(amf_base *enc, obs_data_t *settings) amf_int64 b_max = 0; if (get_avc_property(enc, B_PIC_PATTERN, &b_frames) && - get_avc_property(enc, MAX_CONSECUTIVE_BPICTURES, &b_max)) + get_avc_property(enc, MAX_CONSECUTIVE_BPICTURES, &b_max)){ enc->dts_offset = b_frames + 1; - else + }else{ enc->dts_offset = 0; + } } } @@ -1506,7 +1579,7 @@ static void register_avc() amf_encoder_info.destroy = amf_destroy; amf_encoder_info.update = amf_avc_update; amf_encoder_info.encode_texture = amf_encode_tex; - amf_encoder_info.get_defaults = amf_defaults; + amf_encoder_info.get_defaults = amf_avc_defaults; amf_encoder_info.get_properties = amf_avc_properties; amf_encoder_info.get_extra_data = amf_extra_data; amf_encoder_info.caps = OBS_ENCODER_CAP_PASS_TEXTURE | OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_ROI; @@ -1571,8 +1644,17 @@ static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_hevc_property(enc, PEAK_BITRATE, bitrate); set_hevc_property(enc, VBV_BUFFER_SIZE, bitrate); - if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR) { + if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR || + rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR) { set_hevc_property(enc, FILLER_DATA_ENABLE, true); + set_hevc_property(enc, ENFORCE_HRD, true); + } else if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || + rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { + set_hevc_property(enc, PEAK_BITRATE, bitrate * 1.5); + set_hevc_property(enc, VBV_BUFFER_SIZE, bitrate * 1.5); + } else if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR) { + int64_t framerate = enc->fps_num / enc->fps_den; + set_hevc_property(enc, VBV_BUFFER_SIZE, (bitrate / framerate) * 1.1); } } else { set_hevc_property(enc, QP_I, qp); @@ -1614,24 +1696,45 @@ try { return false; } +static inline void adjust_recommended_hevc_defaults(amf_base *enc, obs_data_t *settings) +{ + const bool is10bit = enc->amf_format == AMF_SURFACE_P010; + const int64_t framerate = enc->fps_num / enc->fps_den; + if ((enc->cx * enc->cy > 1920 * 1088) || is10bit || (framerate > 60)) { + // Recommended base defaults + obs_data_set_default_int(settings, "bitrate", 2500); + obs_data_set_default_int(settings, "cqp", 20); + obs_data_set_default_string(settings, "preset", "quality"); + info("Original base default settings were used according to resolution" + " and framerate."); + } +} + static bool amf_hevc_init(void *data, obs_data_t *settings) { amf_base *enc = (amf_base *)data; + // Initial high perceptual quality settings are set in the UI. + // Adjust during init based on resolution and framerate. + adjust_recommended_hevc_defaults(enc, settings); + int64_t bitrate = obs_data_get_int(settings, "bitrate"); int64_t qp = obs_data_get_int(settings, "cqp"); const char *preset = obs_data_get_string(settings, "preset"); const char *profile = obs_data_get_string(settings, "profile"); const char *rc_str = obs_data_get_string(settings, "rate_control"); int rc = get_hevc_rate_control(rc_str); + bool pa_enabled = obs_data_get_bool(settings, "pre_analysis"); set_hevc_property(enc, RATE_CONTROL_METHOD, rc); - if (rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP) + if (rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP && + rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR && + rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR) { set_hevc_property(enc, ENABLE_VBAQ, true); + } amf_hevc_update_data(enc, rc, bitrate * 1000, qp); - set_hevc_property(enc, ENFORCE_HRD, true); set_hevc_property(enc, HIGH_MOTION_QUALITY_BOOST_ENABLE, false); int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); @@ -1662,8 +1765,10 @@ static bool amf_hevc_init(void *data, obs_data_t *settings) "\tprofile: %s\n" "\twidth: %d\n" "\theight: %d\n" + "\tpre-analysis: %s\n" "\tparams: %s", - rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, ffmpeg_opts); + rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, pa_enabled ? "true" : "false", + ffmpeg_opts); return true; } @@ -1713,6 +1818,7 @@ static void amf_hevc_create_internal(amf_base *enc, obs_data_t *settings) const bool hlg = is_hlg(enc); const bool is_hdr = pq || hlg; const char *preset = obs_data_get_string(settings, "preset"); + obs_data_set_string(settings, "profile", is10bit ? "main10" : "main"); set_hevc_property(enc, FRAMESIZE, AMFConstructSize(enc->cx, enc->cy)); set_hevc_property(enc, USAGE, AMF_VIDEO_ENCODER_USAGE_TRANSCODING); @@ -1823,6 +1929,17 @@ try { return nullptr; } +/* These are initial recommended settings that may be lowered later + * once we know more info such as the resolution and frame rate. + */ +static void amf_hevc_defaults(obs_data_t *settings) +{ + obs_data_set_default_string(settings, "rate_control", "CBR"); + obs_data_set_default_int(settings, "bitrate", 2500); + obs_data_set_default_int(settings, "cqp", 20); + obs_data_set_default_string(settings, "preset", "quality"); +} + static void register_hevc() { struct obs_encoder_info amf_encoder_info = {}; @@ -1834,7 +1951,7 @@ static void register_hevc() amf_encoder_info.destroy = amf_destroy; amf_encoder_info.update = amf_hevc_update; amf_encoder_info.encode_texture = amf_encode_tex; - amf_encoder_info.get_defaults = amf_defaults; + amf_encoder_info.get_defaults = amf_hevc_defaults; amf_encoder_info.get_properties = amf_hevc_properties; amf_encoder_info.get_extra_data = amf_extra_data; amf_encoder_info.caps = OBS_ENCODER_CAP_PASS_TEXTURE | OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_ROI; @@ -1913,11 +2030,17 @@ static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_av1_property(enc, PEAK_BITRATE, bitrate); set_av1_property(enc, VBV_BUFFER_SIZE, bitrate); - if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) { + if (rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR || + rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR) { set_av1_property(enc, FILLER_DATA, true); + set_av1_property(enc, ENFORCE_HRD, true); } else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { set_av1_property(enc, PEAK_BITRATE, bitrate * 1.5); + set_av1_property(enc, VBV_BUFFER_SIZE, bitrate * 1.5); + } else if (rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR) { + int64_t framerate = enc->fps_num / enc->fps_den; + set_av1_property(enc, VBV_BUFFER_SIZE, (bitrate / framerate) * 1.1); } } else { int64_t qp = cq_value * 4; @@ -1960,23 +2083,43 @@ try { return false; } +static inline void adjust_recommended_av1_defaults(amf_base *enc, obs_data_t *settings) +{ + const bool is10bit = enc->amf_format == AMF_SURFACE_P010; + const int64_t framerate = enc->fps_num / enc->fps_den; + if ((enc->cx * enc->cy > 1920 * 1088) || is10bit || (framerate > 60)) { + // Recommended base defaults + obs_data_set_default_int(settings, "bitrate", 2500); + obs_data_set_default_int(settings, "cqp", 20); + obs_data_set_default_string(settings, "preset", "balanced"); + obs_data_set_default_string(settings, "profile", "main"); + info("Original base default settings were used according to resolution" + " and framerate."); + } +} + static bool amf_av1_init(void *data, obs_data_t *settings) { amf_base *enc = (amf_base *)data; + // Initial high perceptual quality settings are set in the UI. + // Adjust during init based on resolution and framerate. + adjust_recommended_av1_defaults(enc, settings); + int64_t bitrate = obs_data_get_int(settings, "bitrate"); int64_t qp = obs_data_get_int(settings, "cqp"); const char *preset = obs_data_get_string(settings, "preset"); const char *profile = obs_data_get_string(settings, "profile"); const char *rc_str = obs_data_get_string(settings, "rate_control"); + bool pa_enabled = obs_data_get_bool(settings, "pre_analysis"); + bool screen_content_tools_enabled = obs_data_get_bool(settings, "screen_content_tools"); + bool palette_mode_enabled = obs_data_get_bool(settings, "palette_mode"); int rc = get_av1_rate_control(rc_str); set_av1_property(enc, RATE_CONTROL_METHOD, rc); amf_av1_update_data(enc, rc, bitrate * 1000, qp); - set_av1_property(enc, ENFORCE_HRD, true); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); int gop_size = (keyint_sec) ? keyint_sec * enc->fps_num / enc->fps_den : 250; set_av1_property(enc, GOP_SIZE, gop_size); @@ -1996,16 +2139,21 @@ static bool amf_av1_init(void *data, obs_data_t *settings) ffmpeg_opts = "(none)"; info("settings:\n" - "\trate_control: %s\n" - "\tbitrate: %d\n" - "\tcqp: %d\n" - "\tkeyint: %d\n" - "\tpreset: %s\n" - "\tprofile: %s\n" - "\twidth: %d\n" - "\theight: %d\n" - "\tparams: %s", - rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, ffmpeg_opts); + "\trate_control: %s\n" + "\tbitrate: %d\n" + "\tcqp: %d\n" + "\tkeyint: %d\n" + "\tpreset: %s\n" + "\tprofile: %s\n" + "\twidth: %d\n" + "\theight: %d\n" + "\tscreen content tools: %s\n" + "\tpalette mode: %s\n" + "\tpre-analysis: %s\n" + "\tparams: %s", + rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, + screen_content_tools_enabled ? "true" : "false", palette_mode_enabled ? "true" : "false", + pa_enabled ? "true" : "false", ffmpeg_opts); return true; } @@ -2028,6 +2176,11 @@ static void amf_av1_create_internal(amf_base *enc, obs_data_t *settings) const bool is10bit = enc->amf_format == AMF_SURFACE_P010; const char *preset = obs_data_get_string(settings, "preset"); + const bool enable_screen_content_tools = true; + const bool enable_palette_mode = true; + obs_data_set_string(settings, "profile", "main"); + obs_data_set_bool(settings, "screen_content_tools", enable_screen_content_tools); + obs_data_set_bool(settings, "palette_mode", enable_palette_mode); set_av1_property(enc, FRAMESIZE, AMFConstructSize(enc->cx, enc->cy)); set_av1_property(enc, USAGE, AMF_VIDEO_ENCODER_USAGE_TRANSCODING); @@ -2040,7 +2193,8 @@ static void amf_av1_create_internal(amf_base *enc, obs_data_t *settings) set_av1_property(enc, OUTPUT_COLOR_PROFILE, enc->amf_color_profile); set_av1_property(enc, OUTPUT_TRANSFER_CHARACTERISTIC, enc->amf_characteristic); set_av1_property(enc, OUTPUT_COLOR_PRIMARIES, enc->amf_primaries); - set_av1_property(enc, FRAMERATE, enc->amf_frame_rate); + set_av1_property(enc, SCREEN_CONTENT_TOOLS, enable_screen_content_tools); + set_av1_property(enc, PALETTE_MODE, enable_palette_mode); amf_av1_init(enc, settings); @@ -2119,13 +2273,15 @@ try { return nullptr; } +/* These are initial recommended settings that may be lowered later + * once we know more info such as the resolution and frame rate. + */ static void amf_av1_defaults(obs_data_t *settings) { obs_data_set_default_int(settings, "bitrate", 2500); obs_data_set_default_int(settings, "cqp", 20); obs_data_set_default_string(settings, "rate_control", "CBR"); - obs_data_set_default_string(settings, "preset", "quality"); - obs_data_set_default_string(settings, "profile", "high"); + obs_data_set_default_string(settings, "preset", "highQuality"); } static void register_av1()