Skip to content

Commit

Permalink
fixup! Add standalone NVENC encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
ns6089 committed Jul 18, 2023
1 parent 2a7b835 commit 0d684fc
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 26 deletions.
79 changes: 64 additions & 15 deletions src/nvenc/nvenc_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "src/utility.h"

namespace {

GUID
quality_preset_guid_from_number(unsigned number) {
if (number > 7) number = 7;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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 };
Expand Down
3 changes: 3 additions & 0 deletions src/nvenc/nvenc_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"

#include "src/stat_trackers.h"
#include "src/video.h"

#include <ffnvcodec/nvEncodeAPI.h>
Expand Down Expand Up @@ -59,6 +60,8 @@ namespace nvenc {
bool supporting_ref_frame_invalidation = true;
bool ref_frame_invalidation_requested = false;
std::pair<uint64_t, uint64_t> last_ref_frame_invalidation_range;

stat_trackers::min_max_avg_tracker<float> frame_size_tracker;
};

} // namespace nvenc
16 changes: 7 additions & 9 deletions src/nvenc/nvenc_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions src/stat_trackers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
23 changes: 21 additions & 2 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1555,6 +1553,27 @@ namespace video {

std::unique_ptr<encode_session_t>
make_encode_session(platf::display_t *disp, const encoder_t &encoder, const config_t &config, int width, int height, std::unique_ptr<platf::encode_device_t> 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<platf::avcodec_encode_device_t *>(encode_device.get())) {
auto avcodec_encode_device = boost::dynamic_pointer_cast<platf::avcodec_encode_device_t>(std::move(encode_device));
return make_avcodec_encode_session(disp, encoder, config, width, height, std::move(avcodec_encode_device));
Expand Down

0 comments on commit 0d684fc

Please sign in to comment.