Implement AV1 support

This commit is contained in:
Cameron Gutman 2023-08-13 15:51:48 -05:00
parent 67c1fa6da7
commit 69e720b44b
10 changed files with 389 additions and 44 deletions

View File

@ -810,6 +810,37 @@ hevc_mode
hevc_mode = 2
av1_mode
^^^^^^^^^
**Description**
Allows the client to request AV1 Main 8-bit or 10-bit video streams.
.. Warning:: AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software
encoding.
**Choices**
.. table::
:widths: auto
===== ===========
Value Description
===== ===========
0 advertise support for AV1 based on encoder
1 do not advertise support for AV1
2 advertise support for AV1 Main 8-bit profile
3 advertise support for AV1 Main 8-bit and 10-bit (HDR) profiles
===== ===========
**Default**
``0``
**Example**
.. code-block:: text
av1_mode = 2
capture
^^^^^^^

View File

@ -130,12 +130,19 @@ namespace config {
namespace amd {
#ifdef __APPLE__
// values accurate as of 27/12/2022, but aren't strictly necessary for MacOS build
#define AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED 100
#define AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY 30
#define AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED 70
#define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED 10
#define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY 0
#define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED 5
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED 1
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY 2
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED 0
#define AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP 0
#define AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR 3
#define AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR 2
#define AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR 1
#define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP 0
#define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR 3
#define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR 2
@ -144,6 +151,10 @@ namespace config {
#define AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR 1
#define AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR 2
#define AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR 3
#define AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING 0
#define AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY 1
#define AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY 2
#define AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM 3
#define AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCONDING 0
#define AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY 1
#define AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY 2
@ -156,10 +167,17 @@ namespace config {
#define AMF_VIDEO_ENCODER_CABAC 1
#define AMF_VIDEO_ENCODER_CALV 2
#else
#include <AMF/components/VideoEncoderAV1.h>
#include <AMF/components/VideoEncoderHEVC.h>
#include <AMF/components/VideoEncoderVCE.h>
#endif
enum class quality_av1_e : int {
speed = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED,
quality = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY,
balanced = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED
};
enum class quality_hevc_e : int {
speed = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED,
quality = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY,
@ -172,6 +190,13 @@ namespace config {
balanced = AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED
};
enum class rc_av1_e : int {
cqp = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP,
vbr_latency = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR,
vbr_peak = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR,
cbr = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR
};
enum class rc_hevc_e : int {
cqp = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP,
vbr_latency = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR,
@ -186,6 +211,13 @@ namespace config {
cbr = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR
};
enum class usage_av1_e : int {
transcoding = AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING,
webcam = AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM,
lowlatency = AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY,
ultralowlatency = AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY
};
enum class usage_hevc_e : int {
transcoding = AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCONDING,
webcam = AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM,
@ -206,10 +238,11 @@ namespace config {
cavlc = AMF_VIDEO_ENCODER_CALV
};
template <class T>
std::optional<int>
quality_from_view(const std::string_view &quality_type, int codec) {
quality_from_view(const std::string_view &quality_type) {
#define _CONVERT_(x) \
if (quality_type == #x##sv) return codec == 0 ? (int) quality_hevc_e::x : (int) quality_h264_e::x
if (quality_type == #x##sv) return (int) T::x
_CONVERT_(quality);
_CONVERT_(speed);
_CONVERT_(balanced);
@ -217,10 +250,11 @@ namespace config {
return std::nullopt;
}
template <class T>
std::optional<int>
rc_from_view(const std::string_view &rc, int codec) {
rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) \
if (rc == #x##sv) return codec == 0 ? (int) rc_hevc_e::x : (int) rc_h264_e::x
if (rc == #x##sv) return (int) T::x
_CONVERT_(cqp);
_CONVERT_(vbr_latency);
_CONVERT_(vbr_peak);
@ -229,10 +263,11 @@ namespace config {
return std::nullopt;
}
template <class T>
std::optional<int>
usage_from_view(const std::string_view &rc, int codec) {
usage_from_view(const std::string_view &rc) {
#define _CONVERT_(x) \
if (rc == #x##sv) return codec == 0 ? (int) usage_hevc_e::x : (int) usage_h264_e::x
if (rc == #x##sv) return (int) T::x
_CONVERT_(transcoding);
_CONVERT_(webcam);
_CONVERT_(lowlatency);
@ -333,15 +368,36 @@ namespace config {
} // namespace vt
namespace sw {
int
svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \
if (preset == #x##sv) return y
_CONVERT_(veryslow, 1);
_CONVERT_(slower, 2);
_CONVERT_(slow, 4);
_CONVERT_(medium, 5);
_CONVERT_(fast, 7);
_CONVERT_(faster, 9);
_CONVERT_(veryfast, 10);
_CONVERT_(superfast, 11);
_CONVERT_(ultrafast, 12);
#undef _CONVERT_
return 11; // Default to superfast
}
} // namespace sw
video_t video {
28, // qp
0, // hevc_mode
0, // av1_mode
1, // min_threads
{
"superfast"s, // preset
"zerolatency"s, // tune
11, // superfast
}, // software
{
@ -359,10 +415,13 @@ namespace config {
{
(int) amd::quality_h264_e::balanced, // quality (h264)
(int) amd::quality_hevc_e::balanced, // quality (hevc)
(int) amd::quality_av1_e::balanced, // quality (av1)
(int) amd::rc_h264_e::vbr_latency, // rate control (h264)
(int) amd::rc_hevc_e::vbr_latency, // rate control (hevc)
(int) amd::rc_av1_e::vbr_latency, // rate control (av1)
(int) amd::usage_h264_e::ultralowlatency, // usage (h264)
(int) amd::usage_hevc_e::ultralowlatency, // usage (hevc)
(int) amd::usage_av1_e::ultralowlatency, // usage (av1)
0, // preanalysis
1, // vbaq
(int) amd::coder_e::_auto, // coder
@ -924,7 +983,11 @@ namespace config {
int_f(vars, "qp", video.qp);
int_f(vars, "min_threads", video.min_threads);
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
int_between_f(vars, "av1_mode", video.av1_mode, { 0, 3 });
string_f(vars, "sw_preset", video.sw.sw_preset);
if (!video.sw.sw_preset.empty()) {
video.sw.svtav1_preset = sw::svtav1_preset_from_view(video.sw.sw_preset);
}
string_f(vars, "sw_tune", video.sw.sw_tune);
int_f(vars, "nv_preset", video.nv.nv_preset, nv::preset_from_view);
int_f(vars, "nv_tune", video.nv.nv_tune, nv::tune_from_view);
@ -937,23 +1000,26 @@ namespace config {
std::string quality;
string_f(vars, "amd_quality", quality);
if (!quality.empty()) {
video.amd.amd_quality_h264 = amd::quality_from_view(quality, 1);
video.amd.amd_quality_hevc = amd::quality_from_view(quality, 0);
video.amd.amd_quality_h264 = amd::quality_from_view<amd::quality_h264_e>(quality);
video.amd.amd_quality_hevc = amd::quality_from_view<amd::quality_hevc_e>(quality);
video.amd.amd_quality_av1 = amd::quality_from_view<amd::quality_av1_e>(quality);
}
std::string rc;
string_f(vars, "amd_rc", rc);
int_f(vars, "amd_coder", video.amd.amd_coder, amd::coder_from_view);
if (!rc.empty()) {
video.amd.amd_rc_h264 = amd::rc_from_view(rc, 1);
video.amd.amd_rc_hevc = amd::rc_from_view(rc, 0);
video.amd.amd_rc_h264 = amd::rc_from_view<amd::rc_h264_e>(rc);
video.amd.amd_rc_hevc = amd::rc_from_view<amd::rc_hevc_e>(rc);
video.amd.amd_rc_av1 = amd::rc_from_view<amd::rc_av1_e>(rc);
}
std::string usage;
string_f(vars, "amd_usage", usage);
if (!usage.empty()) {
video.amd.amd_usage_h264 = amd::usage_from_view(rc, 1);
video.amd.amd_usage_hevc = amd::usage_from_view(rc, 0);
video.amd.amd_usage_h264 = amd::usage_from_view<amd::usage_h264_e>(rc);
video.amd.amd_usage_hevc = amd::usage_from_view<amd::usage_hevc_e>(rc);
video.amd.amd_usage_av1 = amd::usage_from_view<amd::usage_av1_e>(rc);
}
bool_f(vars, "amd_preanalysis", (bool &) video.amd.amd_preanalysis);

View File

@ -17,11 +17,13 @@ namespace config {
int qp; // higher == more compression and less quality
int hevc_mode;
int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string sw_preset;
std::string sw_tune;
std::optional<int> svtav1_preset;
} sw;
struct {
@ -39,10 +41,13 @@ namespace config {
struct {
std::optional<int> amd_quality_h264;
std::optional<int> amd_quality_hevc;
std::optional<int> amd_quality_av1;
std::optional<int> amd_rc_h264;
std::optional<int> amd_rc_hevc;
std::optional<int> amd_rc_av1;
std::optional<int> amd_usage_h264;
std::optional<int> amd_usage_hevc;
std::optional<int> amd_usage_av1;
std::optional<int> amd_preanalysis;
std::optional<int> amd_vbaq;
int amd_coder;

View File

@ -124,6 +124,11 @@ namespace nvenc {
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
break;
case 2:
// AV1
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
break;
default:
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
return false;
@ -293,6 +298,33 @@ namespace nvenc {
fill_vui(format_config.hevcVUIParameters);
break;
}
case 2: {
// AV1
auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
format_config.chromaFormatIDC = 1; // YUV444 not supported by NVENC yet
format_config.enableBitstreamPadding = config.insert_filler_data;
if (buffer_is_10bit()) {
format_config.inputPixelBitDepthMinus8 = 2;
format_config.pixelBitDepthMinus8 = 2;
}
format_config.colorPrimaries = colorspace.primaries;
format_config.transferCharacteristics = colorspace.tranfer_function;
format_config.matrixCoefficients = colorspace.matrix;
format_config.colorRange = colorspace.full_range;
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8);
set_minqp_if_enabled(config.min_qp_av1);
if (client_config.slicesPerFrame > 1) {
// NVENC only supports slice counts that are powers of two, so we'll pick powers of two
// with bias to rows due to hopefully more similar macroblocks with a row vs a column.
format_config.numTileRows = std::pow(2, std::ceil(std::log2(client_config.slicesPerFrame) / 2));
format_config.numTileColumns = std::pow(2, std::floor(std::log2(client_config.slicesPerFrame) / 2));
}
break;
}
}
init_params.encodeConfig = &enc_config;

View File

@ -35,6 +35,9 @@ namespace nvenc {
// Min QP value for HEVC when enable_min_qp is selected
unsigned min_qp_hevc = 23;
// Min QP value for AV1 when enable_min_qp is selected
unsigned min_qp_av1 = 23;
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
bool h264_cavlc = false;

View File

@ -643,15 +643,20 @@ namespace nvhttp {
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", local_endpoint.address().to_string());
if (video::active_hevc_mode == 3) {
tree.put("root.ServerCodecModeSupport", "3843");
uint32_t codec_mode_flags = SCM_H264;
if (video::active_hevc_mode >= 2) {
codec_mode_flags |= SCM_HEVC;
}
else if (video::active_hevc_mode == 2) {
tree.put("root.ServerCodecModeSupport", "259");
if (video::active_hevc_mode >= 3) {
codec_mode_flags |= SCM_HEVC_MAIN10;
}
else {
tree.put("root.ServerCodecModeSupport", "3");
if (video::active_av1_mode >= 2) {
codec_mode_flags |= SCM_AV1_MAIN8;
}
if (video::active_av1_mode >= 3) {
codec_mode_flags |= SCM_AV1_MAIN10;
}
tree.put("root.ServerCodecModeSupport", codec_mode_flags);
pt::ptree display_nodes;
for (auto &resolution : config::nvhttp.resolutions) {

View File

@ -504,6 +504,10 @@ namespace rtsp_stream {
ss << "x-nv-video[0].refPicInvalidation=1"sv << std::endl;
}
if (video::active_av1_mode != 1) {
ss << "a=rtpmap:98 AV1/90000"sv << std::endl;
}
for (int x = 0; x < audio::MAX_STREAM_CONFIG; ++x) {
auto &stream_config = audio::stream_configs[x];
std::uint8_t mapping[platf::speaker::MAX_SPEAKERS];
@ -701,13 +705,20 @@ namespace rtsp_stream {
}
}
if (config.monitor.videoFormat != 0 && video::active_hevc_mode == 1) {
if (config.monitor.videoFormat == 1 && video::active_hevc_mode == 1) {
BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
}
if (config.monitor.videoFormat == 2 && video::active_av1_mode == 1) {
BOOST_LOG(warning) << "AV1 is disabled, yet the client requested AV1"sv;
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
}
auto session = stream::session::alloc(config, launch_session->gcm_key, launch_session->iv);
auto slot = server->accept(session);

View File

@ -376,7 +376,7 @@ namespace video {
operator[](flag_e flag) {
return capabilities[(std::size_t) flag];
}
} hevc, h264;
} av1, hevc, h264;
uint32_t flags;
};
@ -569,6 +569,16 @@ namespace video {
std::make_unique<encoder_platform_formats_nvenc>(
platf::mem_type_e::dxgi,
platf::pix_fmt_e::nv12, platf::pix_fmt_e::p010),
{
// Common options
{},
// SDR-specific options
{},
// HDR-specific options
{},
std::nullopt, // QP
"av1_nvenc"s,
},
{
// Common options
{},
@ -609,6 +619,23 @@ namespace video {
cuda_init_avcodec_hardware_input_buffer
#endif
),
{
// Common options
{
{ "delay"s, 0 },
{ "forced-idr"s, 1 },
{ "zerolatency"s, 1 },
{ "preset"s, &config::video.nv.nv_preset },
{ "tune"s, &config::video.nv.nv_tune },
{ "rc"s, &config::video.nv.nv_rc },
},
// SDR-specific options
{},
// HDR-specific options
{},
std::nullopt,
"av1_nvenc"s,
},
{
// Common options
{
@ -660,6 +687,22 @@ namespace video {
AV_PIX_FMT_QSV,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
dxgi_init_avcodec_hardware_input_buffer),
{
// Common options
{
{ "preset"s, &config::video.qsv.qsv_preset },
{ "forced_idr"s, 1 },
{ "async_depth"s, 1 },
{ "low_delay_brc"s, 1 },
{ "low_power"s, 1 },
},
// SDR-specific options
{},
// HDR-specific options
{},
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
"av1_qsv"s,
},
{
// Common options
{
@ -714,6 +757,20 @@ namespace video {
AV_PIX_FMT_D3D11,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
dxgi_init_avcodec_hardware_input_buffer),
{
// Common options
{
{ "filler_data"s, true },
{ "preanalysis"s, &config::video.amd.amd_preanalysis },
{ "quality"s, &config::video.amd.amd_quality_av1 },
{ "rc"s, &config::video.amd.amd_rc_av1 },
{ "usage"s, &config::video.amd.amd_usage_av1 },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::make_optional<encoder_t::option_t>({ "qp_p"s, &config::video.qp }),
"av1_amf"s,
},
{
// Common options
{
@ -762,6 +819,20 @@ namespace video {
AV_PIX_FMT_NONE,
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
nullptr),
{
// libsvtav1 takes different presets than libx264/libx265.
// We set an infinite GOP length, use a low delay prediction structure,
// force I frames to be key frames, and set max bitrate to default to work
// around a FFmpeg bug with CBR mode.
{
{ "svtav1-params"s, "keyint=-1:pred-struct=1:force-key-frames=1:mbr=0"s },
{ "preset"s, &config::video.sw.svtav1_preset },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
"libsvtav1"s,
},
{
// x265's Info SEI is so long that it causes the IDR picture data to be
// kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic.
@ -800,6 +871,17 @@ namespace video {
AV_PIX_FMT_VAAPI,
AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10,
vaapi_init_avcodec_hardware_input_buffer),
{
// Common options
{
{ "async_depth"s, 1 },
{ "idr_interval"s, std::numeric_limits<int>::max() },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
"av1_vaapi"s,
},
{
// Common options
{
@ -836,6 +918,18 @@ namespace video {
AV_PIX_FMT_VIDEOTOOLBOX,
AV_PIX_FMT_NV12, AV_PIX_FMT_NV12,
nullptr),
{
// Common options
{
{ "allow_sw"s, &config::video.vt.vt_allow_sw },
{ "require_sw"s, &config::video.vt.vt_require_sw },
{ "realtime"s, &config::video.vt.vt_realtime },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::nullopt,
"av1_videotoolbox"s,
},
{
// Common options
{
@ -883,6 +977,7 @@ namespace video {
static encoder_t *chosen_encoder;
int active_hevc_mode;
int active_av1_mode;
bool last_encoder_probe_supported_ref_frames_invalidation = false;
void
@ -1265,7 +1360,9 @@ namespace video {
bool hardware = platform_formats->avcodec_base_dev_type != AV_HWDEVICE_TYPE_NONE;
auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc;
auto &video_format = config.videoFormat == 0 ? encoder.h264 :
config.videoFormat == 1 ? encoder.hevc :
encoder.av1;
if (!video_format[encoder_t::PASSED]) {
BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv;
return nullptr;
@ -1289,14 +1386,19 @@ namespace video {
ctx->time_base = AVRational { 1, config.framerate };
ctx->framerate = AVRational { config.framerate, 1 };
if (config.videoFormat == 0) {
ctx->profile = FF_PROFILE_H264_HIGH;
}
else if (config.dynamicRange == 0) {
ctx->profile = FF_PROFILE_HEVC_MAIN;
}
else {
ctx->profile = FF_PROFILE_HEVC_MAIN_10;
switch (config.videoFormat) {
case 0:
ctx->profile = FF_PROFILE_H264_HIGH;
break;
case 1:
ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN;
break;
case 2:
// AV1 supports both 8 and 10 bit encoding with the same Main profile
ctx->profile = FF_PROFILE_AV1_MAIN;
break;
}
// B-frames delay decoder output, so never use them
@ -1444,7 +1546,7 @@ namespace video {
}
if (!(encoder.flags & NO_RC_BUF_LIMIT)) {
if (!hardware && (ctx->slices > 1 || config.videoFormat != 0)) {
if (!hardware && (ctx->slices > 1 || config.videoFormat == 1)) {
// Use a larger rc_buffer_size for software encoding when slices are enabled,
// because libx264 can severely degrade quality if the buffer is too small.
// libx265 encounters this issue more frequently, so always scale the
@ -1540,7 +1642,7 @@ namespace video {
std::move(encode_device_final),
// 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc
(1 - (int) video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat));
config.videoFormat <= 1 ? (1 - (int) video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat) : 0);
return session;
}
@ -2099,15 +2201,19 @@ namespace video {
}
int flag = 0;
if (auto packet_avcodec = dynamic_cast<packet_raw_avcodec *>(packet.get())) {
if (cbs::validate_sps(packet_avcodec->av_packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) {
// This check only applies for H.264 and HEVC
if (config.videoFormat <= 1) {
if (auto packet_avcodec = dynamic_cast<packet_raw_avcodec *>(packet.get())) {
if (cbs::validate_sps(packet_avcodec->av_packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) {
flag |= VUI_PARAMS;
}
}
else {
// Don't check it for non-avcodec encoders.
flag |= VUI_PARAMS;
}
}
else {
// Don't check it for non-avcodec encoders.
flag |= VUI_PARAMS;
}
return flag;
}
@ -2124,8 +2230,12 @@ namespace video {
auto force_hevc = active_hevc_mode >= 2;
auto test_hevc = force_hevc || (active_hevc_mode == 0 && !(encoder.flags & H264_ONLY));
auto force_av1 = active_av1_mode >= 2;
auto test_av1 = force_av1 || (active_av1_mode == 0 && !(encoder.flags & H264_ONLY));
encoder.h264.capabilities.set();
encoder.hevc.capabilities.set();
encoder.av1.capabilities.set();
// First, test encoder viability
config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 };
@ -2194,6 +2304,39 @@ namespace video {
encoder.hevc.capabilities.reset();
}
if (test_av1) {
config_max_ref_frames.videoFormat = 2;
config_autoselect.videoFormat = 2;
retry_av1:
auto max_ref_frames_av1 = validate_config(disp, encoder, config_max_ref_frames);
auto autoselect_av1 = max_ref_frames_av1 >= 0 ? max_ref_frames_av1 : validate_config(disp, encoder, config_autoselect);
if (autoselect_av1 < 0) {
if (encoder.av1.qp && encoder.av1[encoder_t::CBR]) {
// It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
encoder.av1.capabilities.set();
encoder.av1[encoder_t::CBR] = false;
goto retry_av1;
}
// If AV1 must be supported, but it is not supported
if (force_av1) {
return false;
}
}
for (auto [validate_flag, encoder_flag] : packet_deficiencies) {
encoder.av1[encoder_flag] = (max_ref_frames_av1 & validate_flag && autoselect_av1 & validate_flag);
}
encoder.av1[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_av1 >= 0;
encoder.av1[encoder_t::PASSED] = max_ref_frames_av1 >= 0 || autoselect_av1 >= 0;
}
else {
// Clear all cap bits for AV1 if we didn't probe it
encoder.av1.capabilities.reset();
}
std::vector<std::pair<encoder_t::flag_e, config_t>> configs {
{ encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } },
};
@ -2201,9 +2344,11 @@ namespace video {
for (auto &[flag, config] : configs) {
auto h264 = config;
auto hevc = config;
auto av1 = config;
h264.videoFormat = 0;
hevc.videoFormat = 1;
av1.videoFormat = 2;
// HDR is not supported with H.264. Don't bother even trying it.
encoder.h264[flag] = flag != encoder_t::DYNAMIC_RANGE && validate_config(disp, encoder, h264) >= 0;
@ -2211,6 +2356,10 @@ namespace video {
if (encoder.hevc[encoder_t::PASSED]) {
encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0;
}
if (encoder.av1[encoder_t::PASSED]) {
encoder.av1[flag] = validate_config(disp, encoder, av1) >= 0;
}
}
encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
@ -2242,6 +2391,7 @@ namespace video {
auto previous_encoder = chosen_encoder;
chosen_encoder = nullptr;
active_hevc_mode = config::video.hevc_mode;
active_av1_mode = config::video.av1_mode;
last_encoder_probe_supported_ref_frames_invalidation = false;
if (!config::video.encoder.empty()) {
@ -2258,9 +2408,13 @@ namespace video {
// If we can't satisfy both the encoder and HDR requirement, prefer the encoder over HDR support
if (active_hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HDR on this system"sv;
BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HEVC Main10 on this system"sv;
active_hevc_mode = 0;
}
if (active_av1_mode == 3 && !encoder->av1[encoder_t::DYNAMIC_RANGE]) {
BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support AV1 Main10 on this system"sv;
active_av1_mode = 0;
}
chosen_encoder = encoder;
break;
@ -2277,7 +2431,7 @@ namespace video {
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. You can safely ignore those errors. //"sv;
// If we haven't found an encoder yet, but we want one with HDR support, search for that now.
if (chosen_encoder == nullptr && active_hevc_mode == 3) {
if (chosen_encoder == nullptr && (active_hevc_mode == 3 || active_av1_mode == 3)) {
KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos;
@ -2288,7 +2442,8 @@ namespace video {
}
// Skip it if it doesn't support HDR
if (!encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
if ((active_hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) ||
(active_av1_mode == 3 && !encoder->av1[encoder_t::DYNAMIC_RANGE])) {
pos++;
continue;
}
@ -2340,6 +2495,7 @@ namespace video {
BOOST_LOG(debug) << encoder_t::from_flag(flag) << (encoder.h264[flag] ? ": supported"sv : ": unsupported"sv);
}
BOOST_LOG(debug) << "-------------------"sv;
BOOST_LOG(info) << "Found H.264 encoder: "sv << encoder.h264.name << " ["sv << encoder.name << ']';
if (encoder.hevc[encoder_t::PASSED]) {
BOOST_LOG(debug) << "------ hevc ------"sv;
@ -2349,16 +2505,28 @@ namespace video {
}
BOOST_LOG(debug) << "-------------------"sv;
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']';
BOOST_LOG(info) << "Found HEVC encoder: "sv << encoder.hevc.name << " ["sv << encoder.name << ']';
}
else {
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']';
if (encoder.av1[encoder_t::PASSED]) {
BOOST_LOG(debug) << "------ av1 ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
auto flag = (encoder_t::flag_e) x;
BOOST_LOG(debug) << encoder_t::from_flag(flag) << (encoder.av1[flag] ? ": supported"sv : ": unsupported"sv);
}
BOOST_LOG(debug) << "-------------------"sv;
BOOST_LOG(info) << "Found AV1 encoder: "sv << encoder.av1.name << " ["sv << encoder.name << ']';
}
if (active_hevc_mode == 0) {
active_hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1;
}
if (active_av1_mode == 0) {
active_av1_mode = encoder.av1[encoder_t::PASSED] ? (encoder.av1[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1;
}
return 0;
}

View File

@ -137,7 +137,7 @@ namespace video {
SDR encoding colorspace (encoderCscMode >> 1) : 0 - BT.601, 1 - BT.709, 2 - BT.2020 */
int encoderCscMode;
int videoFormat; // 0 - H.264, 1 - HEVC
int videoFormat; // 0 - H.264, 1 - HEVC, 2 - AV1
/* Encoding color depth (bit depth): 0 - 8-bit, 1 - 10-bit
HDR encoding activates when color depth is higher than 8-bit and the display which is being captured is operating in HDR mode */
@ -145,6 +145,7 @@ namespace video {
};
extern int active_hevc_mode;
extern int active_av1_mode;
extern bool last_encoder_probe_supported_ref_frames_invalidation;
void

View File

@ -691,6 +691,28 @@
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
</div>
</div>
<!--AV1 Support -->
<div class="mb-3">
<label for="av1_mode" class="form-label">AV1 Support</label>
<select id="av1_mode" class="form-select" v-model="config.av1_mode">
<option value="0">
Sunshine will specify support for AV1 based on encoder
</option>
<option value="1">
Sunshine will not advertise support for AV1
</option>
<option value="2">
Sunshine will advertise support for AV1 Main 8-bit profile
</option>
<option value="3">
Sunshine will advertise support for AV1 Main 8-bit and 10-bit (HDR) profiles
</option>
</select>
<div class="form-text">
Allows the client to request AV1 Main 8-bit or 10-bit video streams.<br />
AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
</div>
</div>
<!--Capture-->
<div class="mb-3" v-if="platform === 'linux'">
<label for="capture" class="form-label">Force a Specific Capture Method</label>
@ -1032,6 +1054,7 @@
"fps": "[10,30,60,90,120]",
"gamepad": "auto",
"hevc_mode": 0,
"av1_mode": 0,
"key_rightalt_to_key_win": "disabled",
"keyboard": "enabled",
"min_log_level": 2,