encoder_nvenc.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #include "cuda_helper.hpp"
  2. #include "simple_mq.h"
  3. #include "third_party/scope_guard.hpp"
  4. #include "variable_defs.h"
  5. #include "encoder_nvenc.h"
  6. #include <nvEncodeAPI.h>
  7. #ifdef _MSC_VER
  8. #include <format>
  9. #define fmt std
  10. #else
  11. #include <fmt/chrono.h>
  12. #endif
  13. bool check_nvenc_api_call(NVENCSTATUS api_ret, unsigned int line_number,
  14. const char *file_name, const char *api_call_str) {
  15. if (api_ret == NV_ENC_SUCCESS) [[likely]] return true;
  16. SPDLOG_ERROR("NvEnc api call {} failed at {}:{} with error 0x{:x}.",
  17. api_call_str, file_name, line_number, (int) api_ret);
  18. RET_ERROR_B;
  19. }
  20. #define API_CHECK(api_call) \
  21. check_nvenc_api_call( \
  22. api_call, __LINE__, __FILE__, #api_call)
  23. #define API_CHECK_P(api_call) \
  24. if (!check_nvenc_api_call( \
  25. api_call, __LINE__, __FILE__, #api_call)) [[unlikely]] \
  26. return nullptr
  27. namespace video_encoder_impl {
  28. constexpr auto frame_buffer_type = NV_ENC_BUFFER_FORMAT_ARGB;
  29. static auto codec_guid = NV_ENC_CODEC_HEVC_GUID;
  30. static auto preset_guid = NV_ENC_PRESET_P3_GUID;
  31. constexpr auto tuning_info = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
  32. std::unique_ptr<NV_ENCODE_API_FUNCTION_LIST> api;
  33. }
  34. using namespace video_encoder_impl;
  35. using namespace simple_mq_singleton;
  36. struct encoder_nvenc::impl {
  37. std::unique_ptr<NV_ENC_PRESET_CONFIG> preset_config;
  38. std::unique_ptr<NV_ENC_INITIALIZE_PARAMS> init_params;
  39. void *encoder;
  40. NV_ENC_OUTPUT_PTR output_buf;
  41. cv::Size frame_size;
  42. FILE *save_file = nullptr;
  43. bool save_length;
  44. void *last_frame_ptr = nullptr;
  45. NV_ENC_REGISTERED_PTR last_reg_ptr = nullptr;
  46. ~impl() {
  47. // notify the end of stream
  48. NV_ENC_PIC_PARAMS pic_params = {NV_ENC_PIC_PARAMS_VER};
  49. pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS;
  50. API_CHECK(api->nvEncEncodePicture(encoder, &pic_params));
  51. // releasing resources
  52. unregister_frame_ptr();
  53. API_CHECK(api->nvEncDestroyBitstreamBuffer(encoder, output_buf));
  54. // close encoder
  55. API_CHECK(api->nvEncDestroyEncoder(encoder));
  56. // close save file
  57. if (save_file != nullptr) {
  58. fclose(save_file);
  59. }
  60. SPDLOG_INFO("Video encoder stopped.");
  61. }
  62. static impl *create(const nvenc_config &conf) {
  63. // initialize api
  64. if (api == nullptr) [[unlikely]] {
  65. api = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>(
  66. NV_ENCODE_API_FUNCTION_LIST_VER);
  67. API_CHECK_P(NvEncodeAPICreateInstance(api.get()));
  68. }
  69. // get cuda context
  70. // CUcontext cuda_ctx;
  71. // CUDA_API_CHECK(cuCtxGetCurrent(&cuda_ctx));
  72. auto cuda_ctx = mq().query_variable<CUcontext>(CUDA_CONTEXT);
  73. // create encoder
  74. auto ret = new impl;
  75. ret->frame_size = conf.frame_size;
  76. auto closer = sg::make_scope_guard([&] {
  77. if (ret->save_file != nullptr) {
  78. fclose(ret->save_file);
  79. }
  80. delete ret;
  81. });
  82. NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {
  83. NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
  84. session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA;
  85. session_params.device = cuda_ctx;
  86. session_params.apiVersion = NVENCAPI_VERSION;
  87. API_CHECK_P(api->nvEncOpenEncodeSessionEx(&session_params, &ret->encoder));
  88. // get preset config
  89. ret->preset_config = std::make_unique<NV_ENC_PRESET_CONFIG>();
  90. auto &preset_config = *ret->preset_config;
  91. preset_config.version = NV_ENC_PRESET_CONFIG_VER;
  92. preset_config.presetCfg.version = NV_ENC_CONFIG_VER;
  93. API_CHECK_P(api->nvEncGetEncodePresetConfigEx(
  94. ret->encoder, codec_guid, preset_guid, tuning_info, &preset_config));
  95. auto &encode_config = preset_config.presetCfg;
  96. encode_config.gopLength = NVENC_INFINITE_GOPLENGTH;
  97. encode_config.frameIntervalP = 1;
  98. auto &rc_params = encode_config.rcParams;
  99. rc_params.rateControlMode = NV_ENC_PARAMS_RC_CBR;
  100. rc_params.averageBitRate = conf.bitrate_mbps * 1e6;
  101. rc_params.enableAQ = true;
  102. rc_params.multiPass = NV_ENC_TWO_PASS_QUARTER_RESOLUTION;
  103. // TODO; fine tune encoder config
  104. // start_encode encoder
  105. ret->init_params =
  106. std::make_unique<NV_ENC_INITIALIZE_PARAMS>(NV_ENC_INITIALIZE_PARAMS_VER);
  107. auto &init_params = *ret->init_params;
  108. init_params.encodeGUID = codec_guid;
  109. init_params.presetGUID = preset_guid;
  110. init_params.encodeWidth = conf.frame_size.width;
  111. init_params.encodeHeight = conf.frame_size.height;
  112. init_params.darWidth = conf.frame_size.width; // TODO; learn more about this
  113. init_params.darHeight = conf.frame_size.height; // TODO; learn more about this
  114. init_params.frameRateNum = conf.frame_rate;
  115. init_params.frameRateDen = 1;
  116. init_params.enablePTD = 1;
  117. init_params.encodeConfig = &preset_config.presetCfg;
  118. init_params.maxEncodeWidth = conf.frame_size.width;
  119. init_params.maxEncodeHeight = conf.frame_size.height;
  120. init_params.tuningInfo = tuning_info;
  121. init_params.bufferFormat = frame_buffer_type;
  122. API_CHECK_P(api->nvEncInitializeEncoder(ret->encoder, &init_params));
  123. // create output buffer
  124. NV_ENC_CREATE_BITSTREAM_BUFFER buffer_config = {
  125. NV_ENC_CREATE_BITSTREAM_BUFFER_VER};
  126. API_CHECK_P(api->nvEncCreateBitstreamBuffer(ret->encoder, &buffer_config));
  127. ret->output_buf = buffer_config.bitstreamBuffer;
  128. // create save file
  129. if (conf.save_file) {
  130. auto file_name = fmt::format("record_{:%Y_%m_%d_%H_%M_%S}.{}",
  131. std::chrono::system_clock::now(),
  132. conf.save_length ? "dat" : "hevc");
  133. ret->save_file = fopen(file_name.c_str(), "wb");
  134. ret->save_length = conf.save_length;
  135. }
  136. SPDLOG_INFO("Video encoder started.");
  137. closer.dismiss();
  138. return ret;
  139. }
  140. void unregister_frame_ptr() {
  141. if (last_reg_ptr == nullptr) return;
  142. API_CHECK(api->nvEncUnregisterResource(encoder, last_reg_ptr));
  143. last_reg_ptr = nullptr;
  144. }
  145. void register_frame_ptr(const cv::cuda::GpuMat &img) {
  146. NV_ENC_REGISTER_RESOURCE reg_params = {NV_ENC_REGISTER_RESOURCE_VER};
  147. reg_params.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
  148. reg_params.width = img.size().width;
  149. reg_params.height = img.size().height;
  150. reg_params.pitch = img.step;
  151. reg_params.resourceToRegister = img.cudaPtr();
  152. reg_params.bufferFormat = frame_buffer_type;
  153. reg_params.bufferUsage = NV_ENC_INPUT_IMAGE;
  154. API_CHECK(api->nvEncRegisterResource(encoder, &reg_params));
  155. last_reg_ptr = reg_params.registeredResource;
  156. }
  157. void encode(const cv::cuda::GpuMat &img, video_nal *out, bool force_idr) {
  158. // register pointer if needed
  159. if (img.cudaPtr() != last_frame_ptr) [[unlikely]] {
  160. assert(img.size() == frame_size);
  161. unregister_frame_ptr();
  162. register_frame_ptr(img);
  163. }
  164. // map input resource
  165. NV_ENC_MAP_INPUT_RESOURCE map_params = {
  166. NV_ENC_MAP_INPUT_RESOURCE_VER};
  167. map_params.registeredResource = last_reg_ptr;
  168. API_CHECK(api->nvEncMapInputResource(encoder, &map_params));
  169. assert(map_params.mappedBufferFmt == frame_buffer_type);
  170. // encode frame
  171. NV_ENC_PIC_PARAMS pic_params = {NV_ENC_PIC_PARAMS_VER};
  172. pic_params.inputWidth = img.size().width;
  173. pic_params.inputHeight = img.size().height;
  174. pic_params.inputPitch = img.step;
  175. if (force_idr) { // request for IDR frame
  176. pic_params.encodePicFlags = NV_ENC_PIC_FLAG_FORCEIDR | NV_ENC_PIC_FLAG_OUTPUT_SPSPPS;
  177. } else {
  178. pic_params.encodePicFlags = 0;
  179. }
  180. pic_params.inputBuffer = map_params.mappedResource;
  181. pic_params.outputBitstream = output_buf;
  182. pic_params.bufferFmt = frame_buffer_type;
  183. pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; // TODO; learn more about this
  184. API_CHECK(api->nvEncEncodePicture(encoder, &pic_params));
  185. // get encoded bitstream
  186. NV_ENC_LOCK_BITSTREAM lock_config = {NV_ENC_LOCK_BITSTREAM_VER};
  187. lock_config.doNotWait = false; // block until encode completed.
  188. lock_config.outputBitstream = output_buf;
  189. API_CHECK(api->nvEncLockBitstream(encoder, &lock_config));
  190. // copy bitstream
  191. out->create(lock_config.bitstreamBufferPtr,
  192. lock_config.bitstreamSizeInBytes,
  193. lock_config.pictureType == NV_ENC_PIC_TYPE_IDR);
  194. // save bitstream
  195. if (save_file != nullptr) {
  196. if (save_length) {
  197. fwrite(&out->length, sizeof(size_t), 1, save_file);
  198. }
  199. fwrite(out->ptr, out->length, 1, save_file);
  200. }
  201. // cleanup
  202. API_CHECK(api->nvEncUnlockBitstream(encoder, output_buf));
  203. API_CHECK(api->nvEncUnmapInputResource(encoder, map_params.mappedResource));
  204. }
  205. void change_config(const nvenc_config &conf) {
  206. assert(conf.frame_size == frame_size);
  207. assert(conf.save_file == (save_file != nullptr));
  208. assert(conf.save_length == save_length);
  209. NV_ENC_RECONFIGURE_PARAMS params = {NV_ENC_RECONFIGURE_PARAMS_VER};
  210. init_params->frameRateNum = conf.frame_rate;
  211. init_params->encodeConfig->rcParams.averageBitRate = conf.bitrate_mbps * 1e6;
  212. params.reInitEncodeParams = *init_params;
  213. params.resetEncoder = true;
  214. params.forceIDR = true;
  215. API_CHECK(api->nvEncReconfigureEncoder(encoder, &params));
  216. }
  217. };
  218. encoder_nvenc::~encoder_nvenc() = default;
  219. encoder_nvenc *encoder_nvenc::create(const nvenc_config &conf) {
  220. auto pimpl = impl::create(conf);
  221. if (pimpl == nullptr) return nullptr;
  222. auto ret = new encoder_nvenc;
  223. ret->pimpl.reset(pimpl);
  224. return ret;
  225. }
  226. void encoder_nvenc::encode(const cv::cuda::GpuMat &img, video_nal *out, bool force_idr) {
  227. mq().update_variable(ENCODER_BUSY, true);
  228. pimpl->encode(img, out, force_idr);
  229. mq().update_variable(ENCODER_BUSY, false);
  230. }
  231. void encoder_nvenc::change_config(const nvenc_config &conf) {
  232. pimpl->change_config(conf);
  233. }