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 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 capture
^^^^^^^ ^^^^^^^

View File

@@ -130,12 +130,19 @@ namespace config {
namespace amd { namespace amd {
#ifdef __APPLE__ #ifdef __APPLE__
// values accurate as of 27/12/2022, but aren't strictly necessary for MacOS build // 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_SPEED 10
#define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY 0 #define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY 0
#define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED 5 #define AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED 5
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED 1 #define AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED 1
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY 2 #define AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY 2
#define AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED 0 #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_CONSTANT_QP 0
#define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR 3 #define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR 3
#define AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR 2 #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_CBR 1
#define AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR 2 #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_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_TRANSCONDING 0
#define AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY 1 #define AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY 1
#define AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY 2 #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_CABAC 1
#define AMF_VIDEO_ENCODER_CALV 2 #define AMF_VIDEO_ENCODER_CALV 2
#else #else
#include <AMF/components/VideoEncoderAV1.h>
#include <AMF/components/VideoEncoderHEVC.h> #include <AMF/components/VideoEncoderHEVC.h>
#include <AMF/components/VideoEncoderVCE.h> #include <AMF/components/VideoEncoderVCE.h>
#endif #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 { enum class quality_hevc_e : int {
speed = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED, speed = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED,
quality = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY, quality = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY,
@@ -172,6 +190,13 @@ namespace config {
balanced = AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED 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 { enum class rc_hevc_e : int {
cqp = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP, cqp = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP,
vbr_latency = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR, 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 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 { enum class usage_hevc_e : int {
transcoding = AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCONDING, transcoding = AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCONDING,
webcam = AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM, webcam = AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM,
@@ -206,10 +238,11 @@ namespace config {
cavlc = AMF_VIDEO_ENCODER_CALV cavlc = AMF_VIDEO_ENCODER_CALV
}; };
template <class T>
std::optional<int> 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) \ #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_(quality);
_CONVERT_(speed); _CONVERT_(speed);
_CONVERT_(balanced); _CONVERT_(balanced);
@@ -217,10 +250,11 @@ namespace config {
return std::nullopt; return std::nullopt;
} }
template <class T>
std::optional<int> std::optional<int>
rc_from_view(const std::string_view &rc, int codec) { rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) \ #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_(cqp);
_CONVERT_(vbr_latency); _CONVERT_(vbr_latency);
_CONVERT_(vbr_peak); _CONVERT_(vbr_peak);
@@ -229,10 +263,11 @@ namespace config {
return std::nullopt; return std::nullopt;
} }
template <class T>
std::optional<int> std::optional<int>
usage_from_view(const std::string_view &rc, int codec) { usage_from_view(const std::string_view &rc) {
#define _CONVERT_(x) \ #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_(transcoding);
_CONVERT_(webcam); _CONVERT_(webcam);
_CONVERT_(lowlatency); _CONVERT_(lowlatency);
@@ -333,15 +368,36 @@ namespace config {
} // namespace vt } // 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 { video_t video {
28, // qp 28, // qp
0, // hevc_mode 0, // hevc_mode
0, // av1_mode
1, // min_threads 1, // min_threads
{ {
"superfast"s, // preset "superfast"s, // preset
"zerolatency"s, // tune "zerolatency"s, // tune
11, // superfast
}, // software }, // software
{ {
@@ -359,10 +415,13 @@ namespace config {
{ {
(int) amd::quality_h264_e::balanced, // quality (h264) (int) amd::quality_h264_e::balanced, // quality (h264)
(int) amd::quality_hevc_e::balanced, // quality (hevc) (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_h264_e::vbr_latency, // rate control (h264)
(int) amd::rc_hevc_e::vbr_latency, // rate control (hevc) (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_h264_e::ultralowlatency, // usage (h264)
(int) amd::usage_hevc_e::ultralowlatency, // usage (hevc) (int) amd::usage_hevc_e::ultralowlatency, // usage (hevc)
(int) amd::usage_av1_e::ultralowlatency, // usage (av1)
0, // preanalysis 0, // preanalysis
1, // vbaq 1, // vbaq
(int) amd::coder_e::_auto, // coder (int) amd::coder_e::_auto, // coder
@@ -924,7 +983,11 @@ namespace config {
int_f(vars, "qp", video.qp); int_f(vars, "qp", video.qp);
int_f(vars, "min_threads", video.min_threads); int_f(vars, "min_threads", video.min_threads);
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 }); 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); 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); 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_preset", video.nv.nv_preset, nv::preset_from_view);
int_f(vars, "nv_tune", video.nv.nv_tune, nv::tune_from_view); int_f(vars, "nv_tune", video.nv.nv_tune, nv::tune_from_view);
@@ -937,23 +1000,26 @@ namespace config {
std::string quality; std::string quality;
string_f(vars, "amd_quality", quality); string_f(vars, "amd_quality", quality);
if (!quality.empty()) { if (!quality.empty()) {
video.amd.amd_quality_h264 = amd::quality_from_view(quality, 1); video.amd.amd_quality_h264 = amd::quality_from_view<amd::quality_h264_e>(quality);
video.amd.amd_quality_hevc = amd::quality_from_view(quality, 0); 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; std::string rc;
string_f(vars, "amd_rc", rc); string_f(vars, "amd_rc", rc);
int_f(vars, "amd_coder", video.amd.amd_coder, amd::coder_from_view); int_f(vars, "amd_coder", video.amd.amd_coder, amd::coder_from_view);
if (!rc.empty()) { if (!rc.empty()) {
video.amd.amd_rc_h264 = amd::rc_from_view(rc, 1); video.amd.amd_rc_h264 = amd::rc_from_view<amd::rc_h264_e>(rc);
video.amd.amd_rc_hevc = amd::rc_from_view(rc, 0); 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; std::string usage;
string_f(vars, "amd_usage", usage); string_f(vars, "amd_usage", usage);
if (!usage.empty()) { if (!usage.empty()) {
video.amd.amd_usage_h264 = amd::usage_from_view(rc, 1); video.amd.amd_usage_h264 = amd::usage_from_view<amd::usage_h264_e>(rc);
video.amd.amd_usage_hevc = amd::usage_from_view(rc, 0); 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); 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 qp; // higher == more compression and less quality
int hevc_mode; int hevc_mode;
int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding int min_threads; // Minimum number of threads/slices for CPU encoding
struct { struct {
std::string sw_preset; std::string sw_preset;
std::string sw_tune; std::string sw_tune;
std::optional<int> svtav1_preset;
} sw; } sw;
struct { struct {
@@ -39,10 +41,13 @@ namespace config {
struct { struct {
std::optional<int> amd_quality_h264; std::optional<int> amd_quality_h264;
std::optional<int> amd_quality_hevc; std::optional<int> amd_quality_hevc;
std::optional<int> amd_quality_av1;
std::optional<int> amd_rc_h264; std::optional<int> amd_rc_h264;
std::optional<int> amd_rc_hevc; std::optional<int> amd_rc_hevc;
std::optional<int> amd_rc_av1;
std::optional<int> amd_usage_h264; std::optional<int> amd_usage_h264;
std::optional<int> amd_usage_hevc; std::optional<int> amd_usage_hevc;
std::optional<int> amd_usage_av1;
std::optional<int> amd_preanalysis; std::optional<int> amd_preanalysis;
std::optional<int> amd_vbaq; std::optional<int> amd_vbaq;
int amd_coder; int amd_coder;

View File

@@ -124,6 +124,11 @@ namespace nvenc {
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID; init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
break; break;
case 2:
// AV1
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
break;
default: default:
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat; BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
return false; return false;
@@ -293,6 +298,33 @@ namespace nvenc {
fill_vui(format_config.hevcVUIParameters); fill_vui(format_config.hevcVUIParameters);
break; 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; init_params.encodeConfig = &enc_config;

View File

@@ -35,6 +35,9 @@ namespace nvenc {
// Min QP value for HEVC when enable_min_qp is selected // Min QP value for HEVC when enable_min_qp is selected
unsigned min_qp_hevc = 23; 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 // Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
bool h264_cavlc = false; 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.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", local_endpoint.address().to_string()); tree.put("root.LocalIP", local_endpoint.address().to_string());
if (video::active_hevc_mode == 3) { uint32_t codec_mode_flags = SCM_H264;
tree.put("root.ServerCodecModeSupport", "3843"); if (video::active_hevc_mode >= 2) {
codec_mode_flags |= SCM_HEVC;
} }
else if (video::active_hevc_mode == 2) { if (video::active_hevc_mode >= 3) {
tree.put("root.ServerCodecModeSupport", "259"); codec_mode_flags |= SCM_HEVC_MAIN10;
} }
else { if (video::active_av1_mode >= 2) {
tree.put("root.ServerCodecModeSupport", "3"); 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; pt::ptree display_nodes;
for (auto &resolution : config::nvhttp.resolutions) { 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; 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) { for (int x = 0; x < audio::MAX_STREAM_CONFIG; ++x) {
auto &stream_config = audio::stream_configs[x]; auto &stream_config = audio::stream_configs[x];
std::uint8_t mapping[platf::speaker::MAX_SPEAKERS]; 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; BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return; 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 session = stream::session::alloc(config, launch_session->gcm_key, launch_session->iv);
auto slot = server->accept(session); auto slot = server->accept(session);

View File

@@ -376,7 +376,7 @@ namespace video {
operator[](flag_e flag) { operator[](flag_e flag) {
return capabilities[(std::size_t) flag]; return capabilities[(std::size_t) flag];
} }
} hevc, h264; } av1, hevc, h264;
uint32_t flags; uint32_t flags;
}; };
@@ -569,6 +569,16 @@ namespace video {
std::make_unique<encoder_platform_formats_nvenc>( std::make_unique<encoder_platform_formats_nvenc>(
platf::mem_type_e::dxgi, platf::mem_type_e::dxgi,
platf::pix_fmt_e::nv12, platf::pix_fmt_e::p010), 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 // Common options
{}, {},
@@ -609,6 +619,23 @@ namespace video {
cuda_init_avcodec_hardware_input_buffer cuda_init_avcodec_hardware_input_buffer
#endif #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 // Common options
{ {
@@ -660,6 +687,22 @@ namespace video {
AV_PIX_FMT_QSV, AV_PIX_FMT_QSV,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
dxgi_init_avcodec_hardware_input_buffer), 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 // Common options
{ {
@@ -714,6 +757,20 @@ namespace video {
AV_PIX_FMT_D3D11, AV_PIX_FMT_D3D11,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
dxgi_init_avcodec_hardware_input_buffer), 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 // Common options
{ {
@@ -762,6 +819,20 @@ namespace video {
AV_PIX_FMT_NONE, AV_PIX_FMT_NONE,
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
nullptr), 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 // 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. // 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_VAAPI,
AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10,
vaapi_init_avcodec_hardware_input_buffer), 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 // Common options
{ {
@@ -836,6 +918,18 @@ namespace video {
AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX,
AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, AV_PIX_FMT_NV12,
nullptr), 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 // Common options
{ {
@@ -883,6 +977,7 @@ namespace video {
static encoder_t *chosen_encoder; static encoder_t *chosen_encoder;
int active_hevc_mode; int active_hevc_mode;
int active_av1_mode;
bool last_encoder_probe_supported_ref_frames_invalidation = false; bool last_encoder_probe_supported_ref_frames_invalidation = false;
void void
@@ -1265,7 +1360,9 @@ namespace video {
bool hardware = platform_formats->avcodec_base_dev_type != AV_HWDEVICE_TYPE_NONE; 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]) { if (!video_format[encoder_t::PASSED]) {
BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv; BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv;
return nullptr; return nullptr;
@@ -1289,14 +1386,19 @@ namespace video {
ctx->time_base = AVRational { 1, config.framerate }; ctx->time_base = AVRational { 1, config.framerate };
ctx->framerate = AVRational { config.framerate, 1 }; ctx->framerate = AVRational { config.framerate, 1 };
if (config.videoFormat == 0) { switch (config.videoFormat) {
ctx->profile = FF_PROFILE_H264_HIGH; case 0:
} ctx->profile = FF_PROFILE_H264_HIGH;
else if (config.dynamicRange == 0) { break;
ctx->profile = FF_PROFILE_HEVC_MAIN;
} case 1:
else { ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN;
ctx->profile = FF_PROFILE_HEVC_MAIN_10; 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 // B-frames delay decoder output, so never use them
@@ -1444,7 +1546,7 @@ namespace video {
} }
if (!(encoder.flags & NO_RC_BUF_LIMIT)) { 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, // 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. // because libx264 can severely degrade quality if the buffer is too small.
// libx265 encounters this issue more frequently, so always scale the // libx265 encounters this issue more frequently, so always scale the
@@ -1540,7 +1642,7 @@ namespace video {
std::move(encode_device_final), std::move(encode_device_final),
// 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc // 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; return session;
} }
@@ -2099,15 +2201,19 @@ namespace video {
} }
int flag = 0; 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; flag |= VUI_PARAMS;
} }
} }
else {
// Don't check it for non-avcodec encoders.
flag |= VUI_PARAMS;
}
return flag; return flag;
} }
@@ -2124,8 +2230,12 @@ namespace video {
auto force_hevc = active_hevc_mode >= 2; auto force_hevc = active_hevc_mode >= 2;
auto test_hevc = force_hevc || (active_hevc_mode == 0 && !(encoder.flags & H264_ONLY)); 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.h264.capabilities.set();
encoder.hevc.capabilities.set(); encoder.hevc.capabilities.set();
encoder.av1.capabilities.set();
// First, test encoder viability // First, test encoder viability
config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 }; 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(); 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 { std::vector<std::pair<encoder_t::flag_e, config_t>> configs {
{ encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } }, { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } },
}; };
@@ -2201,9 +2344,11 @@ namespace video {
for (auto &[flag, config] : configs) { for (auto &[flag, config] : configs) {
auto h264 = config; auto h264 = config;
auto hevc = config; auto hevc = config;
auto av1 = config;
h264.videoFormat = 0; h264.videoFormat = 0;
hevc.videoFormat = 1; hevc.videoFormat = 1;
av1.videoFormat = 2;
// HDR is not supported with H.264. Don't bother even trying it. // 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; 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]) { if (encoder.hevc[encoder_t::PASSED]) {
encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0; 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]; 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; auto previous_encoder = chosen_encoder;
chosen_encoder = nullptr; chosen_encoder = nullptr;
active_hevc_mode = config::video.hevc_mode; active_hevc_mode = config::video.hevc_mode;
active_av1_mode = config::video.av1_mode;
last_encoder_probe_supported_ref_frames_invalidation = false; last_encoder_probe_supported_ref_frames_invalidation = false;
if (!config::video.encoder.empty()) { 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 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]) { 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; 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; chosen_encoder = encoder;
break; 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; 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 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), { KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos; auto encoder = *pos;
@@ -2288,7 +2442,8 @@ namespace video {
} }
// Skip it if it doesn't support HDR // 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++; pos++;
continue; 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) << encoder_t::from_flag(flag) << (encoder.h264[flag] ? ": supported"sv : ": unsupported"sv);
} }
BOOST_LOG(debug) << "-------------------"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]) { if (encoder.hevc[encoder_t::PASSED]) {
BOOST_LOG(debug) << "------ hevc ------"sv; BOOST_LOG(debug) << "------ hevc ------"sv;
@@ -2349,16 +2505,28 @@ namespace video {
} }
BOOST_LOG(debug) << "-------------------"sv; 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) { if (active_hevc_mode == 0) {
active_hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1; 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; return 0;
} }

View File

@@ -137,7 +137,7 @@ namespace video {
SDR encoding colorspace (encoderCscMode >> 1) : 0 - BT.601, 1 - BT.709, 2 - BT.2020 */ SDR encoding colorspace (encoderCscMode >> 1) : 0 - BT.601, 1 - BT.709, 2 - BT.2020 */
int encoderCscMode; 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 /* 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 */ 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_hevc_mode;
extern int active_av1_mode;
extern bool last_encoder_probe_supported_ref_frames_invalidation; extern bool last_encoder_probe_supported_ref_frames_invalidation;
void void

View File

@@ -691,6 +691,28 @@
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
</div> </div>
</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--> <!--Capture-->
<div class="mb-3" v-if="platform === 'linux'"> <div class="mb-3" v-if="platform === 'linux'">
<label for="capture" class="form-label">Force a Specific Capture Method</label> <label for="capture" class="form-label">Force a Specific Capture Method</label>
@@ -1032,6 +1054,7 @@
"fps": "[10,30,60,90,120]", "fps": "[10,30,60,90,120]",
"gamepad": "auto", "gamepad": "auto",
"hevc_mode": 0, "hevc_mode": 0,
"av1_mode": 0,
"key_rightalt_to_key_win": "disabled", "key_rightalt_to_key_win": "disabled",
"keyboard": "enabled", "keyboard": "enabled",
"min_log_level": 2, "min_log_level": 2,