jim-nvenc-helpers.c 7.7 KB

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