0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-19 20:32:15 +02:00

obs-ffmpeg: Add AMF AV1 encoder

This commit is contained in:
Roman Huts 2022-11-07 23:50:27 -08:00 committed by jp9000
parent a8fc9226f8
commit 927733240c
4 changed files with 537 additions and 46 deletions

View File

@ -48,6 +48,7 @@ NVENC.Multipass.fullres="Two Passes (Full Resolution)"
AMF.Preset.speed="Speed"
AMF.Preset.balanced="Balanced"
AMF.Preset.quality="Quality"
AMF.Preset.highQuality="High Quality"
FFmpegSource="Media Source"
LocalFile="Local File"

View File

@ -2,6 +2,7 @@
#include "../external/AMF/include/core/Trace.h"
#include "../external/AMF/include/components/VideoEncoderVCE.h"
#include "../external/AMF/include/components/VideoEncoderHEVC.h"
#include "../external/AMF/include/components/VideoEncoderAV1.h"
#include <util/windows/ComPtr.hpp>
@ -24,6 +25,7 @@ struct adapter_caps {
bool is_amd = false;
bool supports_avc = false;
bool supports_hevc = false;
bool supports_av1 = false;
};
static AMFFactory *amf_factory = nullptr;
@ -81,6 +83,7 @@ static bool get_adapter_caps(IDXGIFactory *factory, uint32_t adapter_idx)
caps.supports_avc = has_encoder(amf_context, AMFVideoEncoderVCE_AVC);
caps.supports_hevc = has_encoder(amf_context, AMFVideoEncoder_HEVC);
caps.supports_av1 = has_encoder(amf_context, AMFVideoEncoder_AV1);
return true;
}
@ -145,6 +148,8 @@ try {
caps.supports_avc ? "true" : "false");
printf("supports_hevc=%s\n",
caps.supports_hevc ? "true" : "false");
printf("supports_av1=%s\n",
caps.supports_av1 ? "true" : "false");
}
return 0;

View File

@ -17,25 +17,40 @@ static void amf_apply_opt(amf_base *enc, obs_option *opt)
{
bool avc = enc->codec == amf_codec_type::AVC;
bool hevc = enc->codec == amf_codec_type::HEVC;
bool av1 = enc->codec == amf_codec_type::AV1;
if (strcmp(opt->name, "g") == 0 || strcmp(opt->name, "keyint") == 0) {
int val = atoi(opt->value);
if (enc->codec == amf_codec_type::AVC)
if (avc)
set_avc_opt(IDR_PERIOD, val);
else
else if (hevc)
set_hevc_opt(NUM_GOPS_PER_IDR, val);
else if (av1)
set_av1_opt(GOP_SIZE, val);
} else if (strcmp(opt->name, "usage") == 0) {
if (strcmp(opt->value, "transcoding") == 0) {
set_enum_opt(USAGE, TRANSCODING);
} else if (strcmp(opt->value, "ultralowlatency") == 0) {
set_enum_opt(USAGE, ULTRA_LOW_LATENCY);
if (avc)
set_avc_enum(USAGE, ULTRA_LOW_LATENCY);
else if (hevc)
set_hevc_enum(USAGE, ULTRA_LOW_LATENCY);
else
warn("Invalid value for %s: %s", opt->name,
opt->value);
} else if (strcmp(opt->value, "lowlatency") == 0) {
set_enum_opt(USAGE, LOW_LATENCY);
} else if (strcmp(opt->value, "webcam") == 0) {
set_enum_opt(USAGE, WEBCAM);
if (avc)
set_avc_enum(USAGE, WEBCAM);
else if (hevc)
set_hevc_enum(USAGE, WEBCAM);
else
warn("Invalid value for %s: %s", opt->name,
opt->value);
} else {
warn("Invalid value for %s: %s", opt->name, opt->value);
}
@ -69,7 +84,12 @@ static void amf_apply_opt(amf_base *enc, obs_option *opt)
val.erase(pos, 1);
int level = std::stoi(val);
set_opt(PROFILE_LEVEL, level);
if (avc)
set_avc_opt(PROFILE_LEVEL, level);
else if (hevc)
set_hevc_opt(PROFILE_LEVEL, level);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "quality") == 0) {
@ -106,42 +126,82 @@ static void amf_apply_opt(amf_base *enc, obs_option *opt)
} else if (strcmp(opt->name, "filler_data") == 0) {
bool val = str_to_bool(opt->value);
set_opt(FILLER_DATA_ENABLE, val);
if (avc)
set_avc_opt(FILLER_DATA_ENABLE, val);
else if (hevc)
set_hevc_opt(FILLER_DATA_ENABLE, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "vbaq") == 0) {
bool val = str_to_bool(opt->value);
set_opt(ENABLE_VBAQ, val);
if (avc)
set_avc_opt(ENABLE_VBAQ, val);
else if (hevc)
set_hevc_opt(ENABLE_VBAQ, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "qp_i") == 0) {
int val = atoi(opt->value);
set_opt(QP_I, val);
if (avc)
set_avc_opt(QP_I, val);
else if (hevc)
set_hevc_opt(QP_I, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "qp_p") == 0) {
int val = atoi(opt->value);
set_opt(QP_P, val);
if (avc)
set_avc_opt(QP_P, val);
else if (hevc)
set_hevc_opt(QP_P, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "me_half_pel") == 0) {
bool val = str_to_bool(opt->value);
set_opt(MOTION_HALF_PIXEL, val);
if (avc)
set_avc_opt(MOTION_HALF_PIXEL, val);
else if (hevc)
set_hevc_opt(MOTION_HALF_PIXEL, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "me_quarter_pel") == 0) {
bool val = str_to_bool(opt->value);
set_opt(MOTION_QUARTERPIXEL, val);
if (avc)
set_avc_opt(MOTION_QUARTERPIXEL, val);
else if (hevc)
set_hevc_opt(MOTION_QUARTERPIXEL, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "aud") == 0) {
bool val = str_to_bool(opt->value);
set_opt(INSERT_AUD, val);
if (avc)
set_avc_opt(INSERT_AUD, val);
else if (hevc)
set_hevc_opt(INSERT_AUD, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (strcmp(opt->name, "max_au_size") == 0) {
int val = atoi(opt->value);
set_opt(MAX_AU_SIZE, val);
if (avc)
set_avc_opt(MAX_AU_SIZE, val);
else if (hevc)
set_hevc_opt(MAX_AU_SIZE, val);
else
warn("Invalid value for %s: %s", opt->name, opt->value);
} else if (avc && strcmp(opt->name, "preanalysis") == 0) {

View File

@ -16,6 +16,7 @@
#include "external/AMF/include/components/VideoEncoderHEVC.h"
#include "external/AMF/include/components/VideoEncoderVCE.h"
#include "external/AMF/include/components/VideoEncoderAV1.h"
#include "external/AMF/include/core/Factory.h"
#include "external/AMF/include/core/Trace.h"
@ -63,6 +64,7 @@ struct adapter_caps {
bool is_amd = false;
bool supports_avc = false;
bool supports_hevc = false;
bool supports_av1 = false;
};
/* ------------------------------------------------------------------------- */
@ -80,6 +82,7 @@ static uint64_t amf_version = 0;
enum class amf_codec_type {
AVC,
HEVC,
AV1,
};
struct amf_base {
@ -213,25 +216,34 @@ static void set_amf_property(amf_base *enc, const wchar_t *name, const T &value)
set_amf_property(enc, AMF_VIDEO_ENCODER_##name, value)
#define set_hevc_property(enc, name, value) \
set_amf_property(enc, AMF_VIDEO_ENCODER_HEVC_##name, value)
#define set_av1_property(enc, name, value) \
set_amf_property(enc, AMF_VIDEO_ENCODER_AV1_##name, value)
#define get_avc_property(enc, name, value) \
get_amf_property(enc, AMF_VIDEO_ENCODER_##name, value)
#define get_hevc_property(enc, name, value) \
get_amf_property(enc, AMF_VIDEO_ENCODER_HEVC_##name, value)
#define get_av1_property(enc, name, value) \
get_amf_property(enc, AMF_VIDEO_ENCODER_AV1_##name, value)
#define get_opt_name(name) \
((enc->codec == amf_codec_type::AVC) ? AMF_VIDEO_ENCODER_##name \
: AMF_VIDEO_ENCODER_HEVC_##name)
: (enc->codec == amf_codec_type::HEVC) \
? AMF_VIDEO_ENCODER_HEVC_##name \
: AMF_VIDEO_ENCODER_AV1_##name)
#define set_opt(name, value) set_amf_property(enc, get_opt_name(name), value)
#define get_opt(name, value) get_amf_property(enc, get_opt_name(name), value)
#define set_avc_opt(name, value) set_avc_property(enc, name, value)
#define set_hevc_opt(name, value) set_hevc_property(enc, name, value)
#define set_av1_opt(name, value) set_av1_property(enc, name, value)
#define set_enum_opt(name, value) \
set_amf_property(enc, get_opt_name(name), get_opt_name(name##_##value))
#define set_avc_enum(name, value) \
set_avc_property(enc, name, AMF_VIDEO_ENCODER_##name##_##value)
#define set_hevc_enum(name, value) \
set_hevc_property(enc, name, AMF_VIDEO_ENCODER_HEVC_##name##_##value)
#define set_av1_enum(name, value) \
set_av1_property(enc, name, AMF_VIDEO_ENCODER_AV1_##name##_##value)
/* ------------------------------------------------------------------------- */
/* Implementation */
@ -400,11 +412,27 @@ static inline void calc_throughput(amf_base *enc)
static inline void check_preset_compatibility(amf_base *enc,
const char *&preset)
{
/* 1.8 * current base throughput == quality,
/* 1.9 * current base throughput == highQuality,
* 1.8 * current base throughput == quality,
* 1.1 * current base throughput == balanced */
static constexpr amf_int64 throughput_high_quality_mul = 19;
static constexpr amf_int64 throughput_quality_mul = 18;
static constexpr amf_int64 throughput_balanced_mul = 11;
/* if the throughput * 1.9 is lower than the max throughput, switch to
* a lower preset */
if (astrcmpi(preset, "highQuality") == 0) {
if (!enc->max_throughput) {
preset = "balanced";
} else {
amf_int64 req_throughput = enc->throughput *
throughput_high_quality_mul /
10;
if (enc->max_throughput < req_throughput)
preset = "quality";
}
}
/* if the throughput * 1.8 is lower than the max throughput, switch to
* a lower preset */
if (astrcmpi(preset, "quality") == 0) {
@ -447,27 +475,60 @@ static void convert_to_encoder_packet(amf_base *enc, AMFDataPtr &data,
enc->packet_data = AMFBufferPtr(data);
data->GetProperty(L"PTS", &packet->pts);
bool hevc = enc->codec == amf_codec_type::HEVC;
const wchar_t *get_output_type =
hevc ? AMF_VIDEO_ENCODER_HEVC_OUTPUT_DATA_TYPE
: AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE;
const wchar_t *get_output_type;
switch (enc->codec) {
case amf_codec_type::AVC:
get_output_type = AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE;
break;
case amf_codec_type::HEVC:
get_output_type = AMF_VIDEO_ENCODER_HEVC_OUTPUT_DATA_TYPE;
break;
case amf_codec_type::AV1:
get_output_type = AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE;
break;
}
uint64_t type;
data->GetProperty(get_output_type, &type);
uint64_t type = 0;
AMF_RESULT res = data->GetProperty(get_output_type, &type);
if (res != AMF_OK)
throw amf_error("Failed to GetProperty(): encoder output "
"data type",
res);
switch (type) {
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_IDR:
packet->priority = OBS_NAL_PRIORITY_HIGHEST;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_I:
packet->priority = OBS_NAL_PRIORITY_HIGH;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_P:
packet->priority = OBS_NAL_PRIORITY_LOW;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_B:
packet->priority = OBS_NAL_PRIORITY_DISPOSABLE;
break;
if (enc->codec == amf_codec_type::AVC ||
enc->codec == amf_codec_type::HEVC) {
switch (type) {
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_IDR:
packet->priority = OBS_NAL_PRIORITY_HIGHEST;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_I:
packet->priority = OBS_NAL_PRIORITY_HIGH;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_P:
packet->priority = OBS_NAL_PRIORITY_LOW;
break;
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_B:
packet->priority = OBS_NAL_PRIORITY_DISPOSABLE;
break;
}
} else if (enc->codec == amf_codec_type::AV1) {
switch (type) {
case AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE_KEY:
packet->priority = OBS_NAL_PRIORITY_HIGHEST;
break;
case AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE_INTRA_ONLY:
packet->priority = OBS_NAL_PRIORITY_HIGH;
break;
case AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE_INTER:
packet->priority = OBS_NAL_PRIORITY_LOW;
break;
case AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE_SWITCH:
packet->priority = OBS_NAL_PRIORITY_DISPOSABLE;
break;
case AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE_SHOW_EXISTING:
packet->priority = OBS_NAL_PRIORITY_DISPOSABLE;
break;
}
}
packet->data = (uint8_t *)enc->packet_data->GetNative();
@ -769,6 +830,23 @@ static void h265_video_info_fallback(void *, struct video_scale_info *info)
}
}
static void av1_video_info_fallback(void *, struct video_scale_info *info)
{
switch (info->format) {
case VIDEO_FORMAT_RGBA:
case VIDEO_FORMAT_BGRA:
case VIDEO_FORMAT_BGRX:
info->format = VIDEO_FORMAT_RGBA;
break;
case VIDEO_FORMAT_I010:
case VIDEO_FORMAT_P010:
info->format = VIDEO_FORMAT_P010;
break;
default:
info->format = VIDEO_FORMAT_NV12;
}
}
static bool amf_create_encoder(amf_base *enc)
try {
AMF_RESULT res;
@ -787,8 +865,10 @@ try {
if (enc->fallback) {
if (enc->codec == amf_codec_type::AVC)
h264_video_info_fallback(NULL, &info);
else
else if (enc->codec == amf_codec_type::HEVC)
h265_video_info_fallback(NULL, &info);
else
av1_video_info_fallback(NULL, &info);
}
enc->cx = obs_encoder_get_width(enc->encoder);
@ -868,10 +948,21 @@ try {
enc->init();
res = amf_factory->CreateComponent(enc->amf_context,
enc->codec == amf_codec_type::HEVC
? AMFVideoEncoder_HEVC
: AMFVideoEncoderVCE_AVC,
const wchar_t *codec = nullptr;
switch (enc->codec) {
case (amf_codec_type::AVC):
codec = AMFVideoEncoderVCE_AVC;
break;
case (amf_codec_type::HEVC):
codec = AMFVideoEncoder_HEVC;
break;
case (amf_codec_type::AV1):
codec = AMFVideoEncoder_AV1;
break;
default:
codec = AMFVideoEncoder_HEVC;
}
res = amf_factory->CreateComponent(enc->amf_context, codec,
&enc->amf_encoder);
if (res != AMF_OK)
throw amf_error("CreateComponent failed", res);
@ -898,10 +989,12 @@ static void check_texture_encode_capability(obs_encoder_t *encoder,
obs_get_video_info(&ovi);
bool avc = amf_codec_type::AVC == codec;
bool hevc = amf_codec_type::HEVC == codec;
bool av1 = amf_codec_type::AV1 == codec;
if (obs_encoder_scaling_enabled(encoder))
throw "Encoder scaling is active";
if (hevc) {
if (hevc || av1) {
if (!obs_nv12_tex_active() && !obs_p010_tex_active())
throw "NV12/P010 textures aren't active";
} else if (!obs_nv12_tex_active()) {
@ -923,7 +1016,8 @@ static void check_texture_encode_capability(obs_encoder_t *encoder,
}
if ((avc && !caps[ovi.adapter].supports_avc) ||
(hevc && !caps[ovi.adapter].supports_hevc))
(hevc && !caps[ovi.adapter].supports_hevc) ||
(av1 && !caps[ovi.adapter].supports_av1))
throw "Wrong adapter";
}
@ -963,6 +1057,12 @@ static obs_properties_t *amf_properties_internal(amf_codec_type codec)
obs_property_list_add_string(p, "CBR", "CBR");
obs_property_list_add_string(p, "CQP", "CQP");
obs_property_list_add_string(p, "VBR", "VBR");
if (amf_codec_type::AV1 == codec) {
obs_property_list_add_string(p, "VBR_LAT", "VBR_LAT");
obs_property_list_add_string(p, "QVBR", "QVBR");
obs_property_list_add_string(p, "HQVBR", "HQVBR");
obs_property_list_add_string(p, "HQCBR", "HQCBR");
}
obs_property_set_modified_callback(p, rate_control_modified);
@ -971,7 +1071,7 @@ static obs_properties_t *amf_properties_internal(amf_codec_type codec)
obs_property_int_set_suffix(p, " Kbps");
obs_properties_add_int(props, "cqp", obs_module_text("NVENC.CQLevel"),
0, 51, 1);
0, codec == amf_codec_type::AV1 ? 63 : 51, 1);
p = obs_properties_add_int(props, "keyint_sec",
obs_module_text("KeyframeIntervalSec"), 0,
@ -984,12 +1084,15 @@ static obs_properties_t *amf_properties_internal(amf_codec_type codec)
#define add_preset(val) \
obs_property_list_add_string(p, obs_module_text("AMF.Preset." val), val)
if (amf_codec_type::AV1 == codec) {
add_preset("highQuality");
}
add_preset("quality");
add_preset("balanced");
add_preset("speed");
#undef add_preset
if (amf_codec_type::AVC == codec) {
if (amf_codec_type::AVC == codec || amf_codec_type::AV1 == codec) {
p = obs_properties_add_list(props, "profile",
obs_module_text("Profile"),
OBS_COMBO_TYPE_LIST,
@ -1030,6 +1133,12 @@ static obs_properties_t *amf_hevc_properties(void *unused)
return amf_properties_internal(amf_codec_type::HEVC);
}
static obs_properties_t *amf_av1_properties(void *unused)
{
UNUSED_PARAMETER(unused);
return amf_properties_internal(amf_codec_type::AV1);
}
/* ========================================================================= */
/* AVC Implementation */
@ -1684,6 +1793,316 @@ static void register_hevc()
#endif //ENABLE_HEVC
/* ========================================================================= */
/* AV1 Implementation */
static const char *amf_av1_get_name(void *)
{
return "AMD HW AV1";
}
static inline int get_av1_preset(amf_base *enc, obs_data_t *settings)
{
const char *preset = obs_data_get_string(settings, "preset");
check_preset_compatibility(enc, preset);
if (astrcmpi(preset, "highquality") == 0)
return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_HIGH_QUALITY;
else if (astrcmpi(preset, "quality") == 0)
return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY;
else if (astrcmpi(preset, "balanced") == 0)
return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED;
else if (astrcmpi(preset, "speed") == 0)
return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED;
return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED;
}
static inline int get_av1_rate_control(const char *rc_str)
{
if (astrcmpi(rc_str, "cqp") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP;
else if (astrcmpi(rc_str, "vbr_lat") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR;
else if (astrcmpi(rc_str, "vbr") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR;
else if (astrcmpi(rc_str, "cbr") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR;
else if (astrcmpi(rc_str, "qvbr") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_QUALITY_VBR;
else if (astrcmpi(rc_str, "hqvbr") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR;
else if (astrcmpi(rc_str, "hqcbr") == 0)
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_CBR;
return AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR;
}
static inline int get_av1_profile(obs_data_t *settings)
{
const char *profile = obs_data_get_string(settings, "profile");
if (astrcmpi(profile, "main") == 0)
return AMF_VIDEO_ENCODER_AV1_PROFILE_MAIN;
return AMF_VIDEO_ENCODER_AV1_PROFILE_MAIN;
}
static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate,
int64_t cq_value)
{
if (rc != AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP) {
set_av1_property(enc, TARGET_BITRATE, bitrate);
set_av1_property(enc, PEAK_BITRATE, bitrate);
set_av1_property(enc, VBV_BUFFER_SIZE, bitrate);
if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) {
set_av1_property(enc, FILLER_DATA, 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);
}
} else {
int64_t qp = cq_value * 4;
set_av1_property(enc, Q_INDEX_INTRA, qp);
set_av1_property(enc, Q_INDEX_INTER, qp);
}
}
static bool amf_av1_update(void *data, obs_data_t *settings)
try {
amf_base *enc = (amf_base *)data;
if (enc->first_update) {
enc->first_update = false;
return true;
}
int64_t bitrate = obs_data_get_int(settings, "bitrate");
int64_t cq_level = obs_data_get_int(settings, "cqp");
const char *rc_str = obs_data_get_string(settings, "rate_control");
int rc = get_av1_rate_control(rc_str);
amf_av1_update_data(enc, rc, bitrate * 1000, cq_level);
AMF_RESULT res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
if (res != AMF_OK)
throw amf_error("AMFComponent::Init failed", res);
return true;
} catch (const amf_error &err) {
amf_base *enc = (amf_base *)data;
error("%s: %s: %ls", __FUNCTION__, err.str,
amf_trace->GetResultText(err.res));
return false;
}
static bool amf_av1_init(void *data, obs_data_t *settings)
{
amf_base *enc = (amf_base *)data;
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");
check_preset_compatibility(enc, preset);
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);
const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts");
if (ffmpeg_opts && *ffmpeg_opts) {
struct obs_options opts = obs_parse_options(ffmpeg_opts);
for (size_t i = 0; i < opts.count; i++) {
amf_apply_opt(enc, &opts.options[i]);
}
obs_free_options(opts);
}
if (!ffmpeg_opts || !*ffmpeg_opts)
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);
return true;
}
static void amf_av1_create_internal(amf_base *enc, obs_data_t *settings)
{
enc->codec = amf_codec_type::AV1;
if (!amf_create_encoder(enc))
throw "Failed to create encoder";
AMFCapsPtr caps;
AMF_RESULT res = enc->amf_encoder->GetCaps(&caps);
if (res == AMF_OK) {
caps->GetProperty(AMF_VIDEO_ENCODER_AV1_CAP_MAX_THROUGHPUT,
&enc->max_throughput);
}
const bool is10bit = enc->amf_format == AMF_SURFACE_P010;
set_av1_property(enc, FRAMESIZE, AMFConstructSize(enc->cx, enc->cy));
set_av1_property(enc, USAGE, AMF_VIDEO_ENCODER_USAGE_TRANSCODING);
set_av1_property(enc, ALIGNMENT_MODE,
AMF_VIDEO_ENCODER_AV1_ALIGNMENT_MODE_NO_RESTRICTIONS);
set_av1_property(enc, QUALITY_PRESET, get_av1_preset(enc, settings));
set_av1_property(enc, COLOR_BIT_DEPTH,
is10bit ? AMF_COLOR_BIT_DEPTH_10
: AMF_COLOR_BIT_DEPTH_8);
set_av1_property(enc, PROFILE, get_av1_profile(settings));
set_av1_property(enc, ENCODING_LATENCY_MODE,
AMF_VIDEO_ENCODER_AV1_ENCODING_LATENCY_MODE_NONE);
// set_av1_property(enc, RATE_CONTROL_PREENCODE, true);
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);
amf_av1_init(enc, settings);
res = enc->amf_encoder->Init(enc->amf_format, enc->cx, enc->cy);
if (res != AMF_OK)
throw amf_error("AMFComponent::Init failed", res);
set_av1_property(enc, FRAMERATE, enc->amf_frame_rate);
AMFVariant p;
res = enc->amf_encoder->GetProperty(AMF_VIDEO_ENCODER_AV1_EXTRA_DATA,
&p);
if (res == AMF_OK && p.type == AMF_VARIANT_INTERFACE)
enc->header = AMFBufferPtr(p.pInterface);
}
static void *amf_av1_create_texencode(obs_data_t *settings,
obs_encoder_t *encoder)
try {
check_texture_encode_capability(encoder, amf_codec_type::AV1);
std::unique_ptr<amf_texencode> enc = std::make_unique<amf_texencode>();
enc->encoder = encoder;
enc->encoder_str = "texture-amf-av1";
if (!amf_init_d3d11(enc.get()))
throw "Failed to create D3D11";
amf_av1_create_internal(enc.get(), settings);
return enc.release();
} catch (const amf_error &err) {
blog(LOG_ERROR, "[texture-amf-av1] %s: %s: %ls", __FUNCTION__, err.str,
amf_trace->GetResultText(err.res));
return obs_encoder_create_rerouted(encoder, "av1_fallback_amf");
} catch (const char *err) {
blog(LOG_ERROR, "[texture-amf-av1] %s: %s", __FUNCTION__, err);
return obs_encoder_create_rerouted(encoder, "av1_fallback_amf");
}
static void *amf_av1_create_fallback(obs_data_t *settings,
obs_encoder_t *encoder)
try {
std::unique_ptr<amf_fallback> enc = std::make_unique<amf_fallback>();
enc->encoder = encoder;
enc->encoder_str = "fallback-amf-av1";
video_t *video = obs_encoder_video(encoder);
const struct video_output_info *voi = video_output_get_info(video);
switch (voi->format) {
case VIDEO_FORMAT_I010:
case VIDEO_FORMAT_P010: {
break;
}
default:
switch (voi->colorspace) {
case VIDEO_CS_2100_PQ:
case VIDEO_CS_2100_HLG: {
const char *const text =
obs_module_text("AMF.8bitUnsupportedHdr");
obs_encoder_set_last_error(encoder, text);
throw text;
}
}
}
amf_av1_create_internal(enc.get(), settings);
return enc.release();
} catch (const amf_error &err) {
blog(LOG_ERROR, "[fallback-amf-av1] %s: %s: %ls", __FUNCTION__, err.str,
amf_trace->GetResultText(err.res));
return nullptr;
} catch (const char *err) {
blog(LOG_ERROR, "[fallback-amf-av1] %s: %s", __FUNCTION__, err);
return nullptr;
}
static void amf_av1_defaults(obs_data_t *settings)
{
obs_data_set_default_int(settings, "bitrate", 2500);
obs_data_set_default_int(settings, "cqp", 7);
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");
}
static void register_av1()
{
struct obs_encoder_info amf_encoder_info = {};
amf_encoder_info.id = "av1_texture_amf";
amf_encoder_info.type = OBS_ENCODER_VIDEO;
amf_encoder_info.codec = "av1";
amf_encoder_info.get_name = amf_av1_get_name;
amf_encoder_info.create = amf_av1_create_texencode;
amf_encoder_info.destroy = amf_destroy;
amf_encoder_info.update = amf_av1_update;
amf_encoder_info.encode_texture = amf_encode_tex;
amf_encoder_info.get_defaults = amf_av1_defaults;
amf_encoder_info.get_properties = amf_av1_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_register_encoder(&amf_encoder_info);
amf_encoder_info.id = "av1_fallback_amf";
amf_encoder_info.caps = OBS_ENCODER_CAP_INTERNAL |
OBS_ENCODER_CAP_DYN_BITRATE;
amf_encoder_info.encode_texture = nullptr;
amf_encoder_info.create = amf_av1_create_fallback;
amf_encoder_info.encode = amf_encode_fallback;
amf_encoder_info.get_video_info = av1_video_info_fallback;
obs_register_encoder(&amf_encoder_info);
}
/* ========================================================================= */
/* Global Stuff */
@ -1701,7 +2120,7 @@ try {
FreeLibrary(amf_module_test);
/* ----------------------------------- */
/* Check for AVC/HEVC support */
/* Check for supported codecs */
BPtr<char> test_exe = os_get_executable_path_ptr("obs-amf-test.exe");
std::string caps_str;
@ -1738,6 +2157,7 @@ try {
uint32_t adapter_count = (uint32_t)config_num_sections(config);
bool avc_supported = false;
bool hevc_supported = false;
bool av1_supported = false;
for (uint32_t i = 0; i < adapter_count; i++) {
std::string section = std::to_string(i);
@ -1749,13 +2169,16 @@ try {
"supports_avc");
info.supports_hevc = config_get_bool(config, section.c_str(),
"supports_hevc");
info.supports_av1 = config_get_bool(config, section.c_str(),
"supports_av1");
avc_supported |= info.supports_avc;
hevc_supported |= info.supports_hevc;
av1_supported |= info.supports_av1;
}
if (!avc_supported && !hevc_supported)
throw "Neither AVC nor HEVC are supported by any devices";
if (!avc_supported && !hevc_supported && !av1_supported)
throw "Neither AVC, HEVC, nor AV1 are supported by any devices";
/* ----------------------------------- */
/* Init AMF */
@ -1800,6 +2223,8 @@ try {
if (hevc_supported)
register_hevc();
#endif
if (av1_supported)
register_av1();
} catch (const std::string &str) {
/* doing debug here because string exceptions indicate the user is