diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 88e00059..1d9b4c57 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -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 ^^^^^^^ diff --git a/src/config.cpp b/src/config.cpp index 6e4c93e3..8d02933e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -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 #include #include #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 std::optional - 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 std::optional - 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 std::optional - 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(quality); + video.amd.amd_quality_hevc = amd::quality_from_view(quality); + video.amd.amd_quality_av1 = amd::quality_from_view(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(rc); + video.amd.amd_rc_hevc = amd::rc_from_view(rc); + video.amd.amd_rc_av1 = amd::rc_from_view(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(rc); + video.amd.amd_usage_hevc = amd::usage_from_view(rc); + video.amd.amd_usage_av1 = amd::usage_from_view(rc); } bool_f(vars, "amd_preanalysis", (bool &) video.amd.amd_preanalysis); diff --git a/src/config.h b/src/config.h index 2c32e7af..a4bac3b6 100644 --- a/src/config.h +++ b/src/config.h @@ -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 svtav1_preset; } sw; struct { @@ -39,10 +41,13 @@ namespace config { struct { std::optional amd_quality_h264; std::optional amd_quality_hevc; + std::optional amd_quality_av1; std::optional amd_rc_h264; std::optional amd_rc_hevc; + std::optional amd_rc_av1; std::optional amd_usage_h264; std::optional amd_usage_hevc; + std::optional amd_usage_av1; std::optional amd_preanalysis; std::optional amd_vbaq; int amd_coder; diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index 9f727d87..071cf709 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -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; diff --git a/src/nvenc/nvenc_config.h b/src/nvenc/nvenc_config.h index a60179d7..a7e480c7 100644 --- a/src/nvenc/nvenc_config.h +++ b/src/nvenc/nvenc_config.h @@ -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; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index a0c91beb..f52ddc07 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -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) { diff --git a/src/rtsp.cpp b/src/rtsp.cpp index a00fbd4a..d0552fae 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -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); diff --git a/src/video.cpp b/src/video.cpp index 7294cd76..e95c31f8 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -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( 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({ "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({ "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("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::max() }, + }, + {}, // SDR-specific options + {}, // HDR-specific options + std::make_optional("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.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.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> 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; } diff --git a/src/video.h b/src/video.h index fe0d5f4e..fec5c38b 100644 --- a/src/video.h +++ b/src/video.h @@ -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 diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 489398fc..9b32f01e 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -691,6 +691,28 @@ HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. + +
+ + +
+ Allows the client to request AV1 Main 8-bit or 10-bit video streams.
+ AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. +
+
@@ -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,