瀏覽代碼

obs-ffmpeg: Add AMF AV1 encoder

Roman Huts 3 年之前
父節點
當前提交
927733240c

+ 1 - 0
plugins/obs-ffmpeg/data/locale/en-US.ini

@@ -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"

+ 5 - 0
plugins/obs-ffmpeg/obs-amf-test/obs-amf-test.cpp

@@ -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;

+ 73 - 13
plugins/obs-ffmpeg/texture-amf-opts.hpp

@@ -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) {
 

+ 457 - 32
plugins/obs-ffmpeg/texture-amf.cpp

@@ -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,29 +475,62 @@ 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;
-
-	uint64_t type;
-	data->GetProperty(get_output_type, &type);
-
-	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;
+	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_VIDEO_ENCODER_OUTPUT_DATA_TYPE_P:
-		packet->priority = OBS_NAL_PRIORITY_LOW;
+	case amf_codec_type::HEVC:
+		get_output_type = AMF_VIDEO_ENCODER_HEVC_OUTPUT_DATA_TYPE;
 		break;
-	case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_B:
-		packet->priority = OBS_NAL_PRIORITY_DISPOSABLE;
+	case amf_codec_type::AV1:
+		get_output_type = AMF_VIDEO_ENCODER_AV1_OUTPUT_FRAME_TYPE;
 		break;
 	}
 
+	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);
+
+	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();
 	packet->size = enc->packet_data->GetSize();
 	packet->type = OBS_ENCODER_VIDEO;
@@ -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