From 4f9470da26e7aefdf98b89f5429f154f24ef7e0c Mon Sep 17 00:00:00 2001 From: qiin2333 <414382190@qq.com> Date: Tue, 12 Aug 2025 20:51:06 +0800 Subject: [PATCH 1/2] build(deps): Update to FFmpeg 8.0 branch (#4143) --- cmake/dependencies/common.cmake | 4 +- src/platform/linux/vaapi.cpp | 102 ++++++++++++++++++++++++++++++-- src/video.cpp | 97 ++++++++++++++++++++++-------- src/video.h | 2 +- 4 files changed, 174 insertions(+), 31 deletions(-) diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index a7003b67861..be92aef1c0c 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -48,10 +48,10 @@ if(NOT DEFINED FFMPEG_PREPARED_BINARIES) endif() set(FFMPEG_LIBRARIES "${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a" "${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a" "${FFMPEG_PREPARED_BINARIES}/lib/libSvtAv1Enc.a" - "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libx264.a" "${FFMPEG_PREPARED_BINARIES}/lib/libx265.a" ${HDR10_PLUS_LIBRARY} @@ -59,9 +59,9 @@ if(NOT DEFINED FFMPEG_PREPARED_BINARIES) else() set(FFMPEG_LIBRARIES "${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a" "${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a" - "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" ${FFMPEG_PLATFORM_LIBRARIES}) endif() diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index ada6370034a..1b1fd1fd59c 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -129,10 +129,104 @@ namespace va { return 0; } - void - init_codec_options(AVCodecContext *ctx, AVDictionary *options) override { - // Don't set the RC buffer size when using H.264 on Intel GPUs. It causes - // major encoding quality degradation. + /** + * @brief Finds a supported VA entrypoint for the given VA profile. + * @param profile The profile to match. + * @return A valid encoding entrypoint or 0 on failure. + */ + VAEntrypoint select_va_entrypoint(VAProfile profile) { + std::vector entrypoints(vaMaxNumEntrypoints(va_display)); + int num_eps; + auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps); + if (status != VA_STATUS_SUCCESS) { + BOOST_LOG(error) << "Failed to query VA entrypoints: "sv << vaErrorStr(status); + return (VAEntrypoint) 0; + } + entrypoints.resize(num_eps); + + // Sorted in order of descending preference + VAEntrypoint ep_preferences[] = { + VAEntrypointEncSliceLP, + VAEntrypointEncSlice, + VAEntrypointEncPicture + }; + for (auto ep_pref : ep_preferences) { + if (std::find(entrypoints.begin(), entrypoints.end(), ep_pref) != entrypoints.end()) { + return ep_pref; + } + } + + return (VAEntrypoint) 0; + } + + /** + * @brief Determines if a given VA profile is supported. + * @param profile The profile to match. + * @return Boolean value indicating if the profile is supported. + */ + bool is_va_profile_supported(VAProfile profile) { + std::vector profiles(vaMaxNumProfiles(va_display)); + int num_profs; + auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs); + if (status != VA_STATUS_SUCCESS) { + BOOST_LOG(error) << "Failed to query VA profiles: "sv << vaErrorStr(status); + return false; + } + profiles.resize(num_profs); + + return std::find(profiles.begin(), profiles.end(), profile) != profiles.end(); + } + + /** + * @brief Determines the matching VA profile for the codec configuration. + * @param ctx The FFmpeg codec context. + * @return The matching VA profile or `VAProfileNone` on failure. + */ + VAProfile get_va_profile(AVCodecContext *ctx) { + if (ctx->codec_id == AV_CODEC_ID_H264) { + // There's no VAAPI profile for H.264 4:4:4 + return VAProfileH264High; + } else if (ctx->codec_id == AV_CODEC_ID_HEVC) { + switch (ctx->profile) { + case AV_PROFILE_HEVC_REXT: + switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) { + case 10: + return VAProfileHEVCMain444_10; + case 8: + return VAProfileHEVCMain444; + } + break; + case AV_PROFILE_HEVC_MAIN_10: + return VAProfileHEVCMain10; + case AV_PROFILE_HEVC_MAIN: + return VAProfileHEVCMain; + } + } else if (ctx->codec_id == AV_CODEC_ID_AV1) { + switch (ctx->profile) { + case AV_PROFILE_AV1_HIGH: + return VAProfileAV1Profile1; + case AV_PROFILE_AV1_MAIN: + return VAProfileAV1Profile0; + } + } + + BOOST_LOG(error) << "Unknown encoder profile: "sv << ctx->profile; + return VAProfileNone; + } + + void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override { + auto va_profile = get_va_profile(ctx); + if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) { + // Don't bother doing anything if the profile isn't supported + return; + } + + auto va_entrypoint = select_va_entrypoint(va_profile); + if (va_entrypoint == 0) { + // It's possible that only decoding is supported for this profile + return; + } + auto vendor = vaQueryVendorString(va_display); if (ctx->codec_id != AV_CODEC_ID_H264 || (vendor && !strstr(vendor, "Intel"))) { ctx->rc_buffer_size = ctx->bit_rate * ctx->framerate.den / ctx->framerate.num; diff --git a/src/video.cpp b/src/video.cpp index c8228b5c621..0faf97507a3 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2,7 +2,7 @@ * @file src/video.cpp * @brief Definitions for video. */ - // standard includes +// standard includes #include #include #include @@ -43,7 +43,8 @@ namespace video { * @brief Check if we can allow probing for the encoders. * @return True if there should be no issues with the probing, false if we should prevent it. */ - bool allow_encoder_probing() { + bool + allow_encoder_probing() { const auto devices { display_device::enum_available_devices() }; // If there are no devices, then either the API is not working correctly or OS does not support the lib. @@ -57,8 +58,8 @@ namespace video { // and also the display device for Windows. So we must have at least 1 active device. const bool at_least_one_device_is_active = std::any_of(std::begin(devices), std::end(devices), [](const auto &device) { // If device has additional info, it is active. - return device.second.device_state == display_device::device_state_e::active || - device.second.device_state == display_device::device_state_e::primary; + return device.second.device_state == display_device::device_state_e::active || + device.second.device_state == display_device::device_state_e::primary; }); if (at_least_one_device_is_active) { @@ -326,6 +327,12 @@ namespace video { avcodec_encode_session_t(avcodec_encode_session_t &&other) noexcept = default; ~avcodec_encode_session_t() { + // Flush any remaining frames in the encoder + if (avcodec_send_frame(avcodec_ctx.get(), nullptr) == 0) { + packet_raw_avcodec pkt; + while (avcodec_receive_packet(avcodec_ctx.get(), pkt.av_packet) == 0); + } + // Order matters here because the context relies on the hwdevice still being valid avcodec_ctx.reset(); device.reset(); @@ -518,7 +525,7 @@ namespace video { {}, // Fallback options "h264_nvenc"s, }, - PARALLEL_ENCODING | REF_FRAMES_INVALIDATION | YUV444_SUPPORT | ASYNC_TEARDOWN // flags + PARALLEL_ENCODING | REF_FRAMES_INVALIDATION | YUV444_SUPPORT | ASYNC_TEARDOWN // flags }; #elif !defined(__APPLE__) encoder_t nvenc { @@ -546,7 +553,7 @@ namespace video { { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "surfaces"s, 1 }, - { "filler_data"s, false }, + { "cbr_padding"s, false }, { "preset"s, &config::video.nv_legacy.preset }, { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, @@ -567,6 +574,7 @@ namespace video { { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "surfaces"s, 1 }, + { "cbr_padding"s, false }, { "preset"s, &config::video.nv_legacy.preset }, { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, @@ -592,6 +600,7 @@ namespace video { { "forced-idr"s, 1 }, { "zerolatency"s, 1 }, { "surfaces"s, 1 }, + { "cbr_padding"s, false }, { "preset"s, &config::video.nv_legacy.preset }, { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, @@ -730,8 +739,11 @@ namespace video { { "filler_data"s, false }, { "forced_idr"s, 1 }, { "latency"s, "lowest_latency"s }, + { "async_depth"s, 1 }, { "skip_frame"s, 0 }, - { "log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; } }, + { "log_to_dbg"s, []() { + return config::sunshine.min_log_level < 2 ? 1 : 0; + } }, { "preencode"s, &config::video.amd.amd_preanalysis }, { "quality"s, &config::video.amd.amd_quality_av1 }, { "rc"s, &config::video.amd.amd_rc_av1 }, @@ -751,8 +763,11 @@ namespace video { { "filler_data"s, false }, { "forced_idr"s, 1 }, { "latency"s, 1 }, + { "async_depth"s, 1 }, { "skip_frame"s, 0 }, - { "log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; } }, + { "log_to_dbg"s, []() { + return config::sunshine.min_log_level < 2 ? 1 : 0; + } }, { "gops_per_idr"s, 1 }, { "header_insertion_mode"s, "idr"s }, { "preencode"s, &config::video.amd.amd_preanalysis }, @@ -761,6 +776,19 @@ namespace video { { "usage"s, &config::video.amd.amd_usage_hevc }, { "vbaq"s, &config::video.amd.amd_vbaq }, { "enforce_hrd"s, &config::video.amd.amd_enforce_hrd }, + { "level"s, [](const config_t &cfg) { + auto size = cfg.width * cfg.height; + // For 4K and below, try to use level 5.1 or 5.2 if possible + if (size <= 8912896) { + if (size * cfg.framerate <= 534773760) { + return "5.1"s; + } + else if (size * cfg.framerate <= 1069547520) { + return "5.2"s; + } + } + return "auto"s; + } }, }, {}, // SDR-specific options {}, // HDR-specific options @@ -775,8 +803,11 @@ namespace video { { "filler_data"s, false }, { "forced_idr"s, 1 }, { "latency"s, 1 }, + { "async_depth"s, 1 }, { "frame_skipping"s, 0 }, - { "log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; } }, + { "log_to_dbg"s, []() { + return config::sunshine.min_log_level < 2 ? 1 : 0; + } }, { "preencode"s, &config::video.amd.amd_preanalysis }, { "quality"s, &config::video.amd.amd_quality_h264 }, { "rc"s, &config::video.amd.amd_rc_h264 }, @@ -1496,23 +1527,23 @@ namespace video { case 0: // 10-bit h264 encoding is not supported by our streaming protocol assert(!config.dynamicRange); - ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_H264_HIGH_444_PREDICTIVE : FF_PROFILE_H264_HIGH; + ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_H264_HIGH_444_PREDICTIVE : AV_PROFILE_H264_HIGH; break; case 1: if (config.chromaSamplingType == 1) { // HEVC uses the same RExt profile for both 8 and 10 bit YUV 4:4:4 encoding - ctx->profile = FF_PROFILE_HEVC_REXT; + ctx->profile = AV_PROFILE_HEVC_REXT; } else { - ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN; + ctx->profile = config.dynamicRange ? AV_PROFILE_HEVC_MAIN_10 : AV_PROFILE_HEVC_MAIN; } break; case 2: // AV1 supports both 8 and 10 bit encoding with the same Main profile // but YUV 4:4:4 sampling requires High profile - ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_AV1_HIGH : FF_PROFILE_AV1_MAIN; + ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_AV1_HIGH : AV_PROFILE_AV1_MAIN; break; } @@ -1624,15 +1655,34 @@ namespace video { ctx->thread_count = ctx->slices; AVDictionary *options { nullptr }; - auto handle_option = [&options](const encoder_t::option_t &option) { + auto handle_option = [&options, &config](const encoder_t::option_t &option) { std::visit( util::overloaded { - [&](int v) { av_dict_set_int(&options, option.name.c_str(), v, 0); }, - [&](int *v) { av_dict_set_int(&options, option.name.c_str(), *v, 0); }, - [&](std::optional *v) { if(*v) av_dict_set_int(&options, option.name.c_str(), **v, 0); }, - [&](std::function v) { av_dict_set_int(&options, option.name.c_str(), v(), 0); }, - [&](const std::string &v) { av_dict_set(&options, option.name.c_str(), v.c_str(), 0); }, - [&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } }, + [&](int v) { + av_dict_set_int(&options, option.name.c_str(), v, 0); + }, + [&](int *v) { + av_dict_set_int(&options, option.name.c_str(), *v, 0); + }, + [&](std::optional *v) { + if (*v) { + av_dict_set_int(&options, option.name.c_str(), **v, 0); + } + }, + [&](const std::function &v) { + av_dict_set_int(&options, option.name.c_str(), v(), 0); + }, + [&](const std::string &v) { + av_dict_set(&options, option.name.c_str(), v.c_str(), 0); + }, + [&](std::string *v) { + if (!v->empty()) { + av_dict_set(&options, option.name.c_str(), v->c_str(), 0); + } + }, + [&](const std::function &v) { + av_dict_set(&options, option.name.c_str(), v(config).c_str(), 0); + } }, option.value); }; @@ -1840,17 +1890,17 @@ namespace video { // streaming to continue without requiring a full restart of Sunshine. auto fail_guard = util::fail_guard([&encoder, &session] { if (encoder.flags & ASYNC_TEARDOWN) { - std::thread encoder_teardown_thread {[session = std::move(session)]() mutable { + std::thread encoder_teardown_thread { [session = std::move(session)]() mutable { BOOST_LOG(info) << "Starting async encoder teardown"; session.reset(); BOOST_LOG(info) << "Async encoder teardown complete"; - }}; + } }; encoder_teardown_thread.detach(); } }); // set minimum frame time based on client-requested target framerate - std::chrono::duration minimum_frame_time {2000.0 / config.framerate}; + std::chrono::duration minimum_frame_time { 2000.0 / config.framerate }; BOOST_LOG(info) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on client-requested target framerate "sv << config.framerate << "."sv; auto shutdown_event = mail->event(mail::shutdown); @@ -1981,7 +2031,6 @@ namespace video { { auto encoder_name = encoder.codec_from_config(config).name; - auto color_coding = colorspace.colorspace == colorspace_e::bt2020 ? "HDR (Rec. 2020 + SMPTE 2084 PQ)" : colorspace.colorspace == colorspace_e::rec601 ? "SDR (Rec. 601)" : colorspace.colorspace == colorspace_e::rec709 ? "SDR (Rec. 709)" : diff --git a/src/video.h b/src/video.h index 3e253cc2346..c948fb523e1 100644 --- a/src/video.h +++ b/src/video.h @@ -151,7 +151,7 @@ namespace video { option_t(const option_t &) = default; std::string name; - std::variant *, std::function, std::string, std::string *> value; + std::variant *, std::function, std::string, std::string *, std::function> value; option_t(std::string &&name, decltype(value) &&value): name { std::move(name) }, value { std::move(value) } {} From fdb42f4a152f40e807b2b6f2bae9706c1141b88f Mon Sep 17 00:00:00 2001 From: qiin2333 <414382190@qq.com> Date: Tue, 12 Aug 2025 21:26:35 +0800 Subject: [PATCH 2/2] build(deps): bump build-deps from 94369e6 to a21ef2e --- third-party/build-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/build-deps b/third-party/build-deps index 94369e63776..a21ef2e3003 160000 --- a/third-party/build-deps +++ b/third-party/build-deps @@ -1 +1 @@ -Subproject commit 94369e63776e3a018df0bdb82992d1a7ba98adf7 +Subproject commit a21ef2e30031628d9e1be3ae250b53d84726cbd8