jim-nvenc-helpers.c 8.1 KB

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