jim-nvenc-helpers.c 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #include "jim-nvenc.h"
  2. #include <util/platform.h>
  3. #include <util/threading.h>
  4. #include <util/config-file.h>
  5. #include <util/dstr.h>
  6. #include <util/pipe.h>
  7. static void *nvenc_lib = NULL;
  8. static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
  9. NV_ENCODE_API_FUNCTION_LIST nv = {NV_ENCODE_API_FUNCTION_LIST_VER};
  10. NV_CREATE_INSTANCE_FUNC nv_create_instance = NULL;
  11. #define error(format, ...) blog(LOG_ERROR, "[jim-nvenc] " format, ##__VA_ARGS__)
  12. bool nv_fail2(obs_encoder_t *encoder, void *session, const char *format, ...)
  13. {
  14. struct dstr message = {0};
  15. struct dstr error_message = {0};
  16. va_list args;
  17. va_start(args, format);
  18. dstr_vprintf(&message, format, args);
  19. va_end(args);
  20. dstr_printf(&error_message, "NVENC Error: %s", message.array);
  21. obs_encoder_set_last_error(encoder, error_message.array);
  22. error("%s", error_message.array);
  23. dstr_free(&error_message);
  24. dstr_free(&message);
  25. return true;
  26. }
  27. bool nv_failed2(obs_encoder_t *encoder, void *session, NVENCSTATUS err,
  28. const char *func, const char *call)
  29. {
  30. struct dstr error_message = {0};
  31. switch (err) {
  32. case NV_ENC_SUCCESS:
  33. return false;
  34. case NV_ENC_ERR_OUT_OF_MEMORY:
  35. obs_encoder_set_last_error(
  36. encoder, obs_module_text("NVENC.TooManySessions"));
  37. break;
  38. case NV_ENC_ERR_UNSUPPORTED_DEVICE:
  39. obs_encoder_set_last_error(
  40. encoder, obs_module_text("NVENC.UnsupportedDevice"));
  41. break;
  42. case NV_ENC_ERR_INVALID_VERSION:
  43. obs_encoder_set_last_error(
  44. encoder, obs_module_text("NVENC.OutdatedDriver"));
  45. break;
  46. default:
  47. dstr_printf(&error_message,
  48. "NVENC Error: %s: %s failed: %d (%s)", func, call,
  49. (int)err, nv_error_name(err));
  50. obs_encoder_set_last_error(encoder, error_message.array);
  51. dstr_free(&error_message);
  52. break;
  53. }
  54. if (session) {
  55. error("%s: %s failed: %d (%s): %s", func, call, (int)err,
  56. nv_error_name(err), nv.nvEncGetLastErrorString(session));
  57. } else {
  58. error("%s: %s failed: %d (%s)", func, call, (int)err,
  59. nv_error_name(err));
  60. }
  61. return true;
  62. }
  63. #define NV_FAILED(e, x) nv_failed2(e, NULL, x, __FUNCTION__, #x)
  64. bool load_nvenc_lib(void)
  65. {
  66. const char *const file = (sizeof(void *) == 8) ? "nvEncodeAPI64.dll"
  67. : "nvEncodeAPI.dll";
  68. nvenc_lib = os_dlopen(file);
  69. return nvenc_lib != NULL;
  70. }
  71. static void *load_nv_func(const char *func)
  72. {
  73. void *func_ptr = os_dlsym(nvenc_lib, func);
  74. if (!func_ptr) {
  75. error("Could not load function: %s", func);
  76. }
  77. return func_ptr;
  78. }
  79. typedef NVENCSTATUS(NVENCAPI *NV_MAX_VER_FUNC)(uint32_t *);
  80. uint32_t get_nvenc_ver()
  81. {
  82. static NV_MAX_VER_FUNC nv_max_ver = NULL;
  83. static bool failed = false;
  84. if (!nv_max_ver) {
  85. if (failed)
  86. return 0;
  87. nv_max_ver = (NV_MAX_VER_FUNC)load_nv_func(
  88. "NvEncodeAPIGetMaxSupportedVersion");
  89. if (!nv_max_ver) {
  90. failed = true;
  91. return 0;
  92. }
  93. }
  94. uint32_t ver = 0;
  95. if (nv_max_ver(&ver) != NV_ENC_SUCCESS) {
  96. return 0;
  97. }
  98. return ver;
  99. }
  100. const char *nv_error_name(NVENCSTATUS err)
  101. {
  102. #define RETURN_CASE(x) \
  103. case x: \
  104. return #x
  105. switch (err) {
  106. RETURN_CASE(NV_ENC_SUCCESS);
  107. RETURN_CASE(NV_ENC_ERR_NO_ENCODE_DEVICE);
  108. RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_DEVICE);
  109. RETURN_CASE(NV_ENC_ERR_INVALID_ENCODERDEVICE);
  110. RETURN_CASE(NV_ENC_ERR_INVALID_DEVICE);
  111. RETURN_CASE(NV_ENC_ERR_DEVICE_NOT_EXIST);
  112. RETURN_CASE(NV_ENC_ERR_INVALID_PTR);
  113. RETURN_CASE(NV_ENC_ERR_INVALID_EVENT);
  114. RETURN_CASE(NV_ENC_ERR_INVALID_PARAM);
  115. RETURN_CASE(NV_ENC_ERR_INVALID_CALL);
  116. RETURN_CASE(NV_ENC_ERR_OUT_OF_MEMORY);
  117. RETURN_CASE(NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
  118. RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_PARAM);
  119. RETURN_CASE(NV_ENC_ERR_LOCK_BUSY);
  120. RETURN_CASE(NV_ENC_ERR_NOT_ENOUGH_BUFFER);
  121. RETURN_CASE(NV_ENC_ERR_INVALID_VERSION);
  122. RETURN_CASE(NV_ENC_ERR_MAP_FAILED);
  123. RETURN_CASE(NV_ENC_ERR_NEED_MORE_INPUT);
  124. RETURN_CASE(NV_ENC_ERR_ENCODER_BUSY);
  125. RETURN_CASE(NV_ENC_ERR_EVENT_NOT_REGISTERD);
  126. RETURN_CASE(NV_ENC_ERR_GENERIC);
  127. RETURN_CASE(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY);
  128. RETURN_CASE(NV_ENC_ERR_UNIMPLEMENTED);
  129. RETURN_CASE(NV_ENC_ERR_RESOURCE_REGISTER_FAILED);
  130. RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_REGISTERED);
  131. RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_MAPPED);
  132. }
  133. #undef RETURN_CASE
  134. return "Unknown Error";
  135. }
  136. static inline bool init_nvenc_internal(obs_encoder_t *encoder)
  137. {
  138. static bool initialized = false;
  139. static bool success = false;
  140. if (initialized)
  141. return success;
  142. initialized = true;
  143. uint32_t ver = get_nvenc_ver();
  144. if (ver == 0) {
  145. obs_encoder_set_last_error(
  146. encoder,
  147. "Missing NvEncodeAPIGetMaxSupportedVersion, check "
  148. "your video card drivers are up to date.");
  149. return false;
  150. }
  151. uint32_t supported_ver = (NVENC_COMPAT_MAJOR_VER << 4) |
  152. NVENC_COMPAT_MINOR_VER;
  153. if (supported_ver > ver) {
  154. obs_encoder_set_last_error(
  155. encoder, obs_module_text("NVENC.OutdatedDriver"));
  156. error("Current driver version does not support this NVENC "
  157. "version, please upgrade your driver");
  158. return false;
  159. }
  160. nv_create_instance = (NV_CREATE_INSTANCE_FUNC)load_nv_func(
  161. "NvEncodeAPICreateInstance");
  162. if (!nv_create_instance) {
  163. obs_encoder_set_last_error(
  164. encoder, "Missing NvEncodeAPICreateInstance, check "
  165. "your video card drivers are up to date.");
  166. return false;
  167. }
  168. if (NV_FAILED(encoder, nv_create_instance(&nv))) {
  169. return false;
  170. }
  171. success = true;
  172. return true;
  173. }
  174. bool init_nvenc(obs_encoder_t *encoder)
  175. {
  176. bool success;
  177. pthread_mutex_lock(&init_mutex);
  178. success = init_nvenc_internal(encoder);
  179. pthread_mutex_unlock(&init_mutex);
  180. return success;
  181. }
  182. extern struct obs_encoder_info h264_nvenc_info;
  183. #ifdef ENABLE_HEVC
  184. extern struct obs_encoder_info hevc_nvenc_info;
  185. #endif
  186. extern struct obs_encoder_info av1_nvenc_info;
  187. static bool av1_supported(void)
  188. {
  189. char *test_exe = os_get_executable_path_ptr("obs-nvenc-test.exe");
  190. struct dstr caps_str = {0};
  191. bool av1_supported = false;
  192. config_t *config = NULL;
  193. os_process_pipe_t *pp = os_process_pipe_create(test_exe, "r");
  194. if (!pp) {
  195. blog(LOG_WARNING, "[NVENC] Failed to launch the NVENC "
  196. "test process I guess");
  197. goto fail;
  198. }
  199. for (;;) {
  200. char data[2048];
  201. size_t len =
  202. os_process_pipe_read(pp, (uint8_t *)data, sizeof(data));
  203. if (!len)
  204. break;
  205. dstr_ncat(&caps_str, data, len);
  206. }
  207. os_process_pipe_destroy(pp);
  208. if (dstr_is_empty(&caps_str)) {
  209. blog(LOG_WARNING,
  210. "[NVENC] Seems the NVENC test subprocess crashed. "
  211. "Better there than here I guess. Let's just "
  212. "skip NVENC AV1 detection then I suppose.");
  213. goto fail;
  214. }
  215. if (config_open_string(&config, caps_str.array) != 0) {
  216. blog(LOG_WARNING, "[NVENC] Failed to open config string");
  217. goto fail;
  218. }
  219. const char *error = config_get_string(config, "error", "string");
  220. if (error) {
  221. blog(LOG_WARNING, "[NVENC] AV1 test process failed: %s", error);
  222. goto fail;
  223. }
  224. uint32_t adapter_count = (uint32_t)config_num_sections(config);
  225. bool avc_supported = false;
  226. bool hevc_supported = false;
  227. /* for now, just check AV1 support on device 0 */
  228. av1_supported = config_get_bool(config, "0", "supports_av1");
  229. fail:
  230. if (config)
  231. config_close(config);
  232. dstr_free(&caps_str);
  233. if (test_exe)
  234. bfree(test_exe);
  235. return av1_supported;
  236. }
  237. void jim_nvenc_load(bool h264, bool hevc, bool av1)
  238. {
  239. pthread_mutex_init(&init_mutex, NULL);
  240. if (h264)
  241. obs_register_encoder(&h264_nvenc_info);
  242. #ifdef ENABLE_HEVC
  243. if (hevc)
  244. obs_register_encoder(&hevc_nvenc_info);
  245. #endif
  246. if (av1 && av1_supported())
  247. obs_register_encoder(&av1_nvenc_info);
  248. else
  249. blog(LOG_WARNING, "[NVENC] AV1 is not supported");
  250. }
  251. void jim_nvenc_unload(void)
  252. {
  253. pthread_mutex_destroy(&init_mutex);
  254. }