video_encoder.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #include "config.h"
  2. #include "cuda_helper.hpp"
  3. #include "video_encoder.h"
  4. #include <nvEncodeAPI.h>
  5. bool check_nvenc_api_call(NVENCSTATUS api_ret, unsigned int line_number,
  6. const char *file_name, const char *api_call_str) {
  7. if (api_ret == NV_ENC_SUCCESS) [[likely]] return true;
  8. SPDLOG_ERROR("NvEnc api call {} failed at {}:{} with error 0x{:x}.",
  9. api_call_str, file_name, line_number, api_ret);
  10. RET_ERROR;
  11. }
  12. #define NVENC_API_CHECK(api_call) \
  13. if (!check_nvenc_api_call( \
  14. api_call, __LINE__, __FILE__, #api_call)) [[unlikely]] \
  15. return false
  16. struct video_encoder::impl {
  17. static constexpr auto frame_buffer_type = NV_ENC_BUFFER_FORMAT_ARGB;
  18. NV_ENCODE_API_FUNCTION_LIST api = {NV_ENCODE_API_FUNCTION_LIST_VER};
  19. CUcontext cuda_ctx = nullptr;
  20. void *encoder = nullptr;
  21. NV_ENC_OUTPUT_PTR output_buf = nullptr;
  22. int frame_width = image_width * 2, frame_height = image_height;
  23. int frame_pitch = frame_width * 4; // ARGB image
  24. int frame_rate = default_camera_fps;
  25. int frame_bitrate = default_video_stream_bitrate;
  26. // frame related
  27. void *frame_ptr = nullptr, **output_ptr = nullptr;
  28. size_t *output_size = nullptr;
  29. NV_ENC_REGISTERED_PTR frame_reg_ptr = nullptr;
  30. bool idr_flag = false;
  31. bool initialize() {
  32. NVENC_API_CHECK(NvEncodeAPICreateInstance(&api));
  33. CUDA_API_CHECK(cuCtxGetCurrent(&cuda_ctx));
  34. return true;
  35. }
  36. bool start_encode() {
  37. // constant params
  38. auto codec_guid = NV_ENC_CODEC_HEVC_GUID;
  39. auto preset_guid = NV_ENC_PRESET_P3_GUID;
  40. // auto tuning_info = NV_ENC_TUNING_INFO_LOW_LATENCY;
  41. auto tuning_info = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
  42. // create encoder
  43. NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
  44. session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA;
  45. session_params.device = cuda_ctx;
  46. session_params.apiVersion = NVENCAPI_VERSION;
  47. NVENC_API_CHECK(api.nvEncOpenEncodeSessionEx(&session_params, &encoder));
  48. // get preset config
  49. NV_ENC_PRESET_CONFIG preset_config = {NV_ENC_PRESET_CONFIG_VER, {NV_ENC_CONFIG_VER}};
  50. NVENC_API_CHECK(api.nvEncGetEncodePresetConfigEx(
  51. encoder, codec_guid, preset_guid, tuning_info, &preset_config));
  52. auto &encode_config = preset_config.presetCfg;
  53. encode_config.gopLength = NVENC_INFINITE_GOPLENGTH;
  54. encode_config.frameIntervalP = 1;
  55. auto &rc_params = encode_config.rcParams;
  56. rc_params.rateControlMode = NV_ENC_PARAMS_RC_CBR;
  57. rc_params.averageBitRate = frame_bitrate;
  58. rc_params.enableAQ = true;
  59. rc_params.multiPass = NV_ENC_TWO_PASS_QUARTER_RESOLUTION;
  60. // TODO; fine tune encoder config
  61. // start_encode encoder
  62. NV_ENC_INITIALIZE_PARAMS init_params = {NV_ENC_INITIALIZE_PARAMS_VER};
  63. init_params.encodeGUID = codec_guid;
  64. init_params.presetGUID = preset_guid;
  65. init_params.encodeWidth = frame_width;
  66. init_params.encodeHeight = frame_height;
  67. init_params.darWidth = frame_width; // TODO; learn more about this
  68. init_params.darHeight = frame_height; // TODO; learn more about this
  69. init_params.frameRateNum = frame_rate;
  70. init_params.frameRateDen = 1;
  71. init_params.enablePTD = 1;
  72. init_params.encodeConfig = &preset_config.presetCfg;
  73. init_params.maxEncodeWidth = frame_width;
  74. init_params.maxEncodeHeight = frame_height;
  75. init_params.tuningInfo = tuning_info;
  76. init_params.bufferFormat = frame_buffer_type;
  77. NVENC_API_CHECK(api.nvEncInitializeEncoder(encoder, &init_params));
  78. // create output buffer
  79. NV_ENC_CREATE_BITSTREAM_BUFFER buffer_config = {NV_ENC_CREATE_BITSTREAM_BUFFER_VER};
  80. NVENC_API_CHECK(api.nvEncCreateBitstreamBuffer(encoder, &buffer_config));
  81. output_buf = buffer_config.bitstreamBuffer;
  82. SPDLOG_INFO("Video encoder started.");
  83. return true;
  84. }
  85. void cleanup() {
  86. if (encoder == nullptr) return;
  87. // notify the end of stream
  88. NV_ENC_PIC_PARAMS pic_params = {NV_ENC_PIC_PARAMS_VER};
  89. pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS;
  90. api.nvEncEncodePicture(encoder, &pic_params);
  91. // releasing resources
  92. if (frame_ptr != nullptr) {
  93. unregister_frame_ptr();
  94. frame_ptr = nullptr;
  95. }
  96. api.nvEncDestroyBitstreamBuffer(encoder, output_buf);
  97. // close encoder
  98. api.nvEncDestroyEncoder(encoder);
  99. encoder = nullptr;
  100. SPDLOG_INFO("Video encoder stopped.");
  101. }
  102. bool register_frame_ptr() {
  103. NV_ENC_REGISTER_RESOURCE reg_params = {NV_ENC_REGISTER_RESOURCE_VER};
  104. reg_params.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
  105. reg_params.width = frame_width;
  106. reg_params.height = frame_height;
  107. reg_params.pitch = frame_pitch;
  108. reg_params.resourceToRegister = (void *) frame_ptr;
  109. reg_params.bufferFormat = frame_buffer_type;
  110. reg_params.bufferUsage = NV_ENC_INPUT_IMAGE;
  111. NVENC_API_CHECK(api.nvEncRegisterResource(encoder, &reg_params));
  112. frame_reg_ptr = reg_params.registeredResource;
  113. return true;
  114. }
  115. bool unregister_frame_ptr() {
  116. if (frame_reg_ptr == nullptr) return true;
  117. NVENC_API_CHECK(api.nvEncUnregisterResource(encoder, frame_reg_ptr));
  118. frame_reg_ptr = nullptr;
  119. return true;
  120. }
  121. bool encode_frame() {
  122. // map input resource
  123. NV_ENC_MAP_INPUT_RESOURCE map_params = {NV_ENC_MAP_INPUT_RESOURCE_VER};
  124. map_params.registeredResource = frame_reg_ptr;
  125. NVENC_API_CHECK(api.nvEncMapInputResource(encoder, &map_params));
  126. assert(map_params.mappedBufferFmt == frame_buffer_type);
  127. // encode frame
  128. NV_ENC_PIC_PARAMS pic_params = {NV_ENC_PIC_PARAMS_VER};
  129. pic_params.inputWidth = frame_width;
  130. pic_params.inputHeight = frame_height;
  131. pic_params.inputPitch = frame_pitch;
  132. if (idr_flag) { // request for IDR frame
  133. pic_params.encodePicFlags = NV_ENC_PIC_FLAG_FORCEIDR | NV_ENC_PIC_FLAG_OUTPUT_SPSPPS;
  134. idr_flag = false;
  135. } else {
  136. pic_params.encodePicFlags = 0;
  137. }
  138. pic_params.inputBuffer = map_params.mappedResource;
  139. pic_params.outputBitstream = output_buf;
  140. pic_params.bufferFmt = frame_buffer_type;
  141. pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; // TODO; learn more about this
  142. NVENC_API_CHECK(api.nvEncEncodePicture(encoder, &pic_params));
  143. // get encoded bitstream
  144. NV_ENC_LOCK_BITSTREAM lock_config = {NV_ENC_LOCK_BITSTREAM_VER};
  145. lock_config.doNotWait = false; // block until encode completed.
  146. lock_config.outputBitstream = output_buf;
  147. NVENC_API_CHECK(api.nvEncLockBitstream(encoder, &lock_config));
  148. // copy bitstream
  149. assert(output_ptr != nullptr);
  150. assert(output_size != nullptr);
  151. *output_ptr = malloc(lock_config.bitstreamSizeInBytes);
  152. *output_size = lock_config.bitstreamSizeInBytes;
  153. memcpy(*output_ptr, lock_config.bitstreamBufferPtr, lock_config.bitstreamSizeInBytes);
  154. // cleanup
  155. NVENC_API_CHECK(api.nvEncUnlockBitstream(encoder, output_buf));
  156. NVENC_API_CHECK(api.nvEncUnmapInputResource(encoder, map_params.mappedResource));
  157. return true;
  158. }
  159. };
  160. video_encoder::video_encoder()
  161. : pimpl(std::make_unique<impl>()) {}
  162. video_encoder::~video_encoder() {
  163. stop_encode();
  164. };
  165. bool video_encoder::initialize() {
  166. return pimpl->initialize();
  167. }
  168. bool video_encoder::start_encode(int width, int height, int fps, int bitrate) {
  169. pimpl->frame_width = width;
  170. pimpl->frame_height = height;
  171. pimpl->frame_pitch = width * 4; // ARGB image
  172. pimpl->frame_rate = fps;
  173. pimpl->frame_bitrate = bitrate;
  174. return pimpl->start_encode();
  175. }
  176. void video_encoder::stop_encode() {
  177. pimpl->cleanup();
  178. }
  179. bool video_encoder::encode_frame(void *frame_ptr, void **output_ptr, size_t *output_size) {
  180. // register frame ptr
  181. if (pimpl->frame_ptr != frame_ptr) {
  182. pimpl->unregister_frame_ptr();
  183. pimpl->frame_ptr = frame_ptr;
  184. pimpl->register_frame_ptr();
  185. }
  186. pimpl->output_ptr = output_ptr;
  187. pimpl->output_size = output_size;
  188. return pimpl->encode_frame();
  189. }
  190. bool video_encoder::encode_frame(cudaGraphicsResource *res, void **output_ptr, size_t *output_size) {
  191. void *pbo_ptr;
  192. size_t pbo_size;
  193. CUDA_API_CHECK(cudaGraphicsMapResources(1, &res));
  194. CUDA_API_CHECK(cudaGraphicsResourceGetMappedPointer(&pbo_ptr, &pbo_size, res));
  195. assert(pbo_size == pimpl->frame_pitch * pimpl->frame_height);
  196. CALL_CHECK(encode_frame(pbo_ptr, output_ptr, output_size));
  197. CUDA_API_CHECK(cudaGraphicsUnmapResources(1, &res));
  198. return true;
  199. }
  200. bool video_encoder::is_encoding() {
  201. return pimpl->encoder != nullptr;
  202. }
  203. void video_encoder::refresh() {
  204. pimpl->idr_flag = true;
  205. }