From 0d684fcd464491cdf9226175c31366273df95325 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:30:27 +0300 Subject: [PATCH] fixup! Add standalone NVENC encoder --- src/nvenc/nvenc_base.cpp | 79 ++++++++++++++++++++++++++++++++-------- src/nvenc/nvenc_base.h | 3 ++ src/nvenc/nvenc_config.h | 16 ++++---- src/stat_trackers.h | 5 +++ src/video.cpp | 23 +++++++++++- 5 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index 9b32883c74b..95bbf6c6ac3 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -3,6 +3,7 @@ #include "src/utility.h" namespace { + GUID quality_preset_guid_from_number(unsigned number) { if (number > 7) number = 7; @@ -192,15 +193,15 @@ namespace nvenc { enc_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR; enc_config.rcParams.zeroReorderDelay = 1; enc_config.rcParams.enableLookahead = 0; - enc_config.rcParams.enableNonRefP = 1; - enc_config.rcParams.lowDelayKeyFrameScale = config.keyframe_vbv_multiplier > 1 ? config.keyframe_vbv_multiplier : 1; - enc_config.rcParams.multiPass = config.multipass == multipass_e::two_pass_full_res ? NV_ENC_TWO_PASS_FULL_RESOLUTION : - config.multipass == multipass_e::two_pass_quarter_res ? NV_ENC_TWO_PASS_QUARTER_RESOLUTION : - NV_ENC_MULTI_PASS_DISABLED; + enc_config.rcParams.lowDelayKeyFrameScale = 1; + enc_config.rcParams.multiPass = config.two_pass ? NV_ENC_TWO_PASS_QUARTER_RESOLUTION : NV_ENC_MULTI_PASS_DISABLED; enc_config.rcParams.averageBitRate = client_config.bitrate * 1000; if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { enc_config.rcParams.vbvBufferSize = client_config.bitrate * 1000 / client_config.framerate; + if (config.increase_vbv) { + enc_config.rcParams.vbvBufferSize *= 1.5; + } } auto set_common_format_config = [&](auto &format_config) { @@ -211,7 +212,7 @@ namespace nvenc { if (buffer_is_yuv444()) { format_config.chromaFormatIDC = 3; } - format_config.enableFillerDataInsertion = config.filler_data_insertion; + format_config.enableFillerDataInsertion = config.insert_filler_data; }; auto fill_vui = [&colorspace](auto &vui_config) { @@ -230,7 +231,12 @@ namespace nvenc { enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID; auto &format_config = enc_config.encodeCodecConfig.h264Config; set_common_format_config(format_config); - format_config.entropyCodingMode = get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC) ? NV_ENC_H264_ENTROPY_CODING_MODE_CABAC : NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; + if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) { + format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; + } + else { + format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; + } if (client_config.numRefFrames > 0) { format_config.maxNumRefFrames = client_config.numRefFrames; } @@ -242,6 +248,11 @@ namespace nvenc { supporting_ref_frame_invalidation = false; } format_config.numRefL0 = NV_ENC_NUM_REF_FRAMES_1; + if (config.enable_min_qp) { + enc_config.rcParams.enableMinQP = 1; + enc_config.rcParams.minQP.qpInterP = config.h264_min_qp; + enc_config.rcParams.minQP.qpInterP = config.h264_min_qp; + } fill_vui(format_config.h264VUIParameters); break; } @@ -264,6 +275,11 @@ namespace nvenc { supporting_ref_frame_invalidation = false; } format_config.numRefL0 = NV_ENC_NUM_REF_FRAMES_1; + if (config.enable_min_qp) { + enc_config.rcParams.enableMinQP = 1; + enc_config.rcParams.minQP.qpInterP = config.hevc_min_qp; + enc_config.rcParams.minQP.qpInterP = config.hevc_min_qp; + } fill_vui(format_config.hevcVUIParameters); break; } @@ -287,9 +303,26 @@ namespace nvenc { return false; } - fail_guard.disable(); - BOOST_LOG(info) << "Created NvENC encoder at " << quality_preset_string_from_guid(init_params.presetGUID); + { + auto f = stat_trackers::one_digit_after_decimal(); + BOOST_LOG(info) << "NvEnc: requested encoded frame size " << f % (client_config.bitrate / 8. / client_config.framerate) << "kB"; + frame_size_tracker.reset(); + } + { + std::string extra; + if (config.two_pass) extra += " two-pass"; + if (config.increase_vbv) extra += " vbv+"; + if (config.enable_min_qp) { + extra += " qpmin="; + extra += client_config.videoFormat == 0 ? std::to_string(config.h264_min_qp) : + client_config.videoFormat == 1 ? std::to_string(config.hevc_min_qp) : + std::string("unknown"); + } + BOOST_LOG(info) << "NvEnc: created encoder " << quality_preset_string_from_guid(init_params.presetGUID) << extra; + } + + fail_guard.disable(); return true; } @@ -325,15 +358,24 @@ namespace nvenc { assert(registered_input_buffer); assert(output_bitstream); + NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { NV_ENC_MAP_INPUT_RESOURCE_VER }; + mapped_input_buffer.registeredResource = registered_input_buffer; + + if (nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer) != NV_ENC_SUCCESS) { + BOOST_LOG(error) << "MvEncMapInputResource failed: " << nvenc->nvEncGetLastErrorString(encoder); + return {}; + } + auto unmap_guard = util::fail_guard([&] { nvenc->nvEncUnmapInputResource(encoder, &mapped_input_buffer); }); + NV_ENC_PIC_PARAMS pic_params = { NV_ENC_PIC_PARAMS_VER }; pic_params.inputWidth = width; pic_params.inputHeight = height; pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0; pic_params.inputTimeStamp = frame_index; - pic_params.inputBuffer = registered_input_buffer; - pic_params.outputBitstream = output_bitstream; - pic_params.bufferFmt = buffer_format; pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; + pic_params.inputBuffer = mapped_input_buffer.mappedResource; + pic_params.bufferFmt = mapped_input_buffer.mappedBufferFmt; + pic_params.outputBitstream = output_bitstream; if (nvenc->nvEncEncodePicture(encoder, &pic_params) != NV_ENC_SUCCESS) { BOOST_LOG(error) << "NvEncEncodePicture failed: " << nvenc->nvEncGetLastErrorString(encoder); @@ -372,6 +414,13 @@ namespace nvenc { BOOST_LOG(error) << "NvEncUnlockBitstream failed: " << nvenc->nvEncGetLastErrorString(encoder); } + auto callback = [&](float stat_min, float stat_max, double stat_avg) { + auto f = stat_trackers::one_digit_after_decimal(); + BOOST_LOG(info) << "NvEnc: encoded frame sizes (min max avg) kB: " << f % stat_min << " " << f % stat_max << " " << f % stat_avg; + }; + using namespace std::literals; + frame_size_tracker.collect_and_callback_on_interval(encoded_frame.data.size() / 1000., callback, 10s); + return encoded_frame; } @@ -380,16 +429,16 @@ namespace nvenc { if (!encoder || !supporting_ref_frame_invalidation) return false; if (last_frame < first_frame || last_encoded_frame_index < first_frame || last_encoded_frame_index > first_frame + 100) { - BOOST_LOG(error) << "NvEnc: invalidate_ref_frames " << first_frame << "-" << last_frame << " invalid range (current frame " << last_encoded_frame_index << ")"; + BOOST_LOG(error) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " invalid range (current frame " << last_encoded_frame_index << ")"; return false; } if (first_frame >= last_ref_frame_invalidation_range.first && last_frame <= last_ref_frame_invalidation_range.second) { - BOOST_LOG(info) << "NvEnc: invalidate_ref_frames " << first_frame << "-" << last_frame << " predicted"; + BOOST_LOG(info) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " already done"; return true; } - BOOST_LOG(info) << "NvEnc: invalidate_ref_frames " << first_frame << "-" << last_frame << " predicting " << first_frame << "-" << last_encoded_frame_index; + BOOST_LOG(info) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " expanding to " << first_frame << "-" << last_encoded_frame_index; ref_frame_invalidation_requested = true; last_ref_frame_invalidation_range = { first_frame, last_encoded_frame_index }; diff --git a/src/nvenc/nvenc_base.h b/src/nvenc/nvenc_base.h index c33a2146125..b8bbbc9d497 100644 --- a/src/nvenc/nvenc_base.h +++ b/src/nvenc/nvenc_base.h @@ -4,6 +4,7 @@ #include "nvenc_config.h" #include "nvenc_encoded_frame.h" +#include "src/stat_trackers.h" #include "src/video.h" #include @@ -59,6 +60,8 @@ namespace nvenc { bool supporting_ref_frame_invalidation = true; bool ref_frame_invalidation_requested = false; std::pair last_ref_frame_invalidation_range; + + stat_trackers::min_max_avg_tracker frame_size_tracker; }; } // namespace nvenc diff --git a/src/nvenc/nvenc_config.h b/src/nvenc/nvenc_config.h index ba4144503f1..bf05a2d9fb3 100644 --- a/src/nvenc/nvenc_config.h +++ b/src/nvenc/nvenc_config.h @@ -2,17 +2,15 @@ namespace nvenc { - enum class multipass_e { - one_pass, - two_pass_quarter_res, - two_pass_full_res, - }; - struct nvenc_config { unsigned quality_preset = 1; // Quality preset from 1 to 7 - unsigned keyframe_vbv_multiplier = 1; // Allows I-frames to break normal VBV constraints - multipass_e multipass = multipass_e::one_pass; - bool filler_data_insertion = false; + bool two_pass = false; // Add preliminary pass to better distribute bitrate across the macroblocks and not overshoot the VBV, uses CUDA cores + bool enable_min_qp = true; + unsigned h264_min_qp = 19; + bool h264_cavlc = false; + unsigned hevc_min_qp = 23; + bool increase_vbv = true; + bool insert_filler_data = false; // Add dummy data to encoded frames when below the target bitrate, mainly for testing }; } // namespace nvenc diff --git a/src/stat_trackers.h b/src/stat_trackers.h index c26c8f455f6..62df7778cc6 100644 --- a/src/stat_trackers.h +++ b/src/stat_trackers.h @@ -32,6 +32,11 @@ namespace stat_trackers { data.calls += 1; } + void + reset() { + data = {}; + } + private: struct { std::chrono::steady_clock::steady_clock::time_point last_callback_time = std::chrono::steady_clock::now(); diff --git a/src/video.cpp b/src/video.cpp index 7cd05fd4144..9525ba722b5 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1327,8 +1327,6 @@ namespace video { auto avcodec_colorspace = avcodec_colorspace_from_sunshine_colorspace(colorspace); ctx->color_range = avcodec_colorspace.range; - BOOST_LOG(info) << "Color range: ["sv << (ctx->color_range == AVCOL_RANGE_JPEG ? "JPEG"sv : "MPEG"sv) << ']'; - ctx->color_primaries = avcodec_colorspace.primaries; ctx->color_trc = avcodec_colorspace.transfer_function; ctx->colorspace = avcodec_colorspace.matrix; @@ -1555,6 +1553,27 @@ namespace video { std::unique_ptr make_encode_session(platf::display_t *disp, const encoder_t &encoder, const config_t &config, int width, int height, std::unique_ptr encode_device) { + if (encode_device) { + switch (encode_device->colorspace.colorspace) { + case colorspace_e::bt2020: + BOOST_LOG(info) << "HDR color coding [Rec. 2020 + SMPTE 2084 PQ]"sv; + break; + + case colorspace_e::rec601: + BOOST_LOG(info) << "SDR color coding [Rec. 601]"sv; + break; + + case colorspace_e::rec709: + BOOST_LOG(info) << "SDR color coding [Rec. 709]"sv; + break; + + case colorspace_e::bt2020sdr: + BOOST_LOG(info) << "SDR color coding [Rec. 2020]"sv; + break; + } + BOOST_LOG(info) << "Color range: ["sv << (encode_device->colorspace.full_range ? "JPEG"sv : "MPEG"sv) << ']'; + } + if (dynamic_cast(encode_device.get())) { auto avcodec_encode_device = boost::dynamic_pointer_cast(std::move(encode_device)); return make_avcodec_encode_session(disp, encoder, config, width, height, std::move(avcodec_encode_device));