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:
parent
a8fc9226f8
commit
927733240c
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user