nvenc-helpers.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #include "obs-nvenc.h"
  2. #include "nvenc-helpers.h"
  3. #include <util/platform.h>
  4. #include <util/threading.h>
  5. #include <util/config-file.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. /* Will be populated with results from obs-nvenc-test */
  13. static struct encoder_caps encoder_capabilities[3];
  14. static bool codec_supported[3];
  15. static int num_devices;
  16. static int driver_version_major;
  17. static int driver_version_minor;
  18. #define error(format, ...) blog(LOG_ERROR, "[obs-nvenc] " format, ##__VA_ARGS__)
  19. bool nv_fail2(obs_encoder_t *encoder, void *session, const char *format, ...)
  20. {
  21. UNUSED_PARAMETER(session);
  22. struct dstr message = {0};
  23. struct dstr error_message = {0};
  24. va_list args;
  25. va_start(args, format);
  26. dstr_vprintf(&message, format, args);
  27. va_end(args);
  28. dstr_printf(&error_message, "NVENC Error: %s", message.array);
  29. obs_encoder_set_last_error(encoder, error_message.array);
  30. error("%s", error_message.array);
  31. dstr_free(&error_message);
  32. dstr_free(&message);
  33. return true;
  34. }
  35. bool nv_failed2(obs_encoder_t *encoder, void *session, NVENCSTATUS err, const char *func, const char *call)
  36. {
  37. struct dstr error_message = {0};
  38. const char *nvenc_error = NULL;
  39. if (err == NV_ENC_SUCCESS)
  40. return false;
  41. if (session) {
  42. nvenc_error = nv.nvEncGetLastErrorString(session);
  43. if (nvenc_error) {
  44. // Some NVENC errors begin with :: which looks
  45. // odd to users. Strip it off.
  46. while (*nvenc_error == ':')
  47. nvenc_error++;
  48. }
  49. }
  50. switch (err) {
  51. case NV_ENC_ERR_OUT_OF_MEMORY:
  52. case NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY:
  53. obs_encoder_set_last_error(encoder, obs_module_text("TooManySessions"));
  54. break;
  55. case NV_ENC_ERR_NO_ENCODE_DEVICE:
  56. case NV_ENC_ERR_UNSUPPORTED_DEVICE:
  57. obs_encoder_set_last_error(encoder, obs_module_text("UnsupportedDevice"));
  58. break;
  59. case NV_ENC_ERR_INVALID_VERSION:
  60. obs_encoder_set_last_error(encoder, obs_module_text("OutdatedDriver"));
  61. break;
  62. default:
  63. if (nvenc_error && *nvenc_error) {
  64. dstr_printf(&error_message, "NVENC Error: %s (%s)", nvenc_error, nv_error_name(err));
  65. } else {
  66. dstr_printf(&error_message, "NVENC Error: %s: %s failed: %d (%s)", func, call, (int)err,
  67. nv_error_name(err));
  68. }
  69. obs_encoder_set_last_error(encoder, error_message.array);
  70. dstr_free(&error_message);
  71. break;
  72. }
  73. if (nvenc_error && *nvenc_error) {
  74. error("%s: %s failed: %d (%s): %s", func, call, (int)err, nv_error_name(err), nvenc_error);
  75. } else {
  76. error("%s: %s failed: %d (%s)", func, call, (int)err, 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. #ifdef _WIN32
  84. nvenc_lib = os_dlopen("nvEncodeAPI64.dll");
  85. #else
  86. nvenc_lib = os_dlopen("libnvidia-encode.so.1");
  87. #endif
  88. return nvenc_lib != NULL;
  89. }
  90. static void *load_nv_func(const char *func)
  91. {
  92. void *func_ptr = os_dlsym(nvenc_lib, func);
  93. if (!func_ptr) {
  94. error("Could not load function: %s", func);
  95. }
  96. return func_ptr;
  97. }
  98. typedef NVENCSTATUS(NVENCAPI *NV_MAX_VER_FUNC)(uint32_t *);
  99. static uint32_t get_nvenc_ver(void)
  100. {
  101. static NV_MAX_VER_FUNC nv_max_ver = NULL;
  102. static bool failed = false;
  103. static uint32_t ver = 0;
  104. if (!failed && ver)
  105. return ver;
  106. if (!nv_max_ver) {
  107. if (failed)
  108. return 0;
  109. nv_max_ver = (NV_MAX_VER_FUNC)load_nv_func("NvEncodeAPIGetMaxSupportedVersion");
  110. if (!nv_max_ver) {
  111. failed = true;
  112. return 0;
  113. }
  114. }
  115. if (nv_max_ver(&ver) != NV_ENC_SUCCESS) {
  116. return 0;
  117. }
  118. return ver;
  119. }
  120. const char *nv_error_name(NVENCSTATUS err)
  121. {
  122. #define RETURN_CASE(x) \
  123. case x: \
  124. return #x
  125. switch (err) {
  126. RETURN_CASE(NV_ENC_SUCCESS);
  127. RETURN_CASE(NV_ENC_ERR_NO_ENCODE_DEVICE);
  128. RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_DEVICE);
  129. RETURN_CASE(NV_ENC_ERR_INVALID_ENCODERDEVICE);
  130. RETURN_CASE(NV_ENC_ERR_INVALID_DEVICE);
  131. RETURN_CASE(NV_ENC_ERR_DEVICE_NOT_EXIST);
  132. RETURN_CASE(NV_ENC_ERR_INVALID_PTR);
  133. RETURN_CASE(NV_ENC_ERR_INVALID_EVENT);
  134. RETURN_CASE(NV_ENC_ERR_INVALID_PARAM);
  135. RETURN_CASE(NV_ENC_ERR_INVALID_CALL);
  136. RETURN_CASE(NV_ENC_ERR_OUT_OF_MEMORY);
  137. RETURN_CASE(NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
  138. RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_PARAM);
  139. RETURN_CASE(NV_ENC_ERR_LOCK_BUSY);
  140. RETURN_CASE(NV_ENC_ERR_NOT_ENOUGH_BUFFER);
  141. RETURN_CASE(NV_ENC_ERR_INVALID_VERSION);
  142. RETURN_CASE(NV_ENC_ERR_MAP_FAILED);
  143. RETURN_CASE(NV_ENC_ERR_NEED_MORE_INPUT);
  144. RETURN_CASE(NV_ENC_ERR_ENCODER_BUSY);
  145. RETURN_CASE(NV_ENC_ERR_EVENT_NOT_REGISTERD);
  146. RETURN_CASE(NV_ENC_ERR_GENERIC);
  147. RETURN_CASE(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY);
  148. RETURN_CASE(NV_ENC_ERR_UNIMPLEMENTED);
  149. RETURN_CASE(NV_ENC_ERR_RESOURCE_REGISTER_FAILED);
  150. RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_REGISTERED);
  151. RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_MAPPED);
  152. RETURN_CASE(NV_ENC_ERR_NEED_MORE_OUTPUT);
  153. }
  154. #undef RETURN_CASE
  155. return "Unknown Error";
  156. }
  157. static inline bool init_nvenc_internal(obs_encoder_t *encoder)
  158. {
  159. static bool initialized = false;
  160. static bool success = false;
  161. if (initialized)
  162. return success;
  163. initialized = true;
  164. uint32_t ver = get_nvenc_ver();
  165. if (ver == 0) {
  166. obs_encoder_set_last_error(encoder, "Missing NvEncodeAPIGetMaxSupportedVersion, check "
  167. "your video card drivers are up to date.");
  168. return false;
  169. }
  170. if (ver < NVCODEC_CONFIGURED_VERSION) {
  171. obs_encoder_set_last_error(encoder, obs_module_text("OutdatedDriver"));
  172. error("Current driver version does not support this NVENC "
  173. "version, please upgrade your driver");
  174. return false;
  175. }
  176. nv_create_instance = (NV_CREATE_INSTANCE_FUNC)load_nv_func("NvEncodeAPICreateInstance");
  177. if (!nv_create_instance) {
  178. obs_encoder_set_last_error(encoder, "Missing NvEncodeAPICreateInstance, check "
  179. "your video card drivers are up to date.");
  180. return false;
  181. }
  182. if (NV_FAILED(encoder, nv_create_instance(&nv))) {
  183. return false;
  184. }
  185. success = true;
  186. return true;
  187. }
  188. bool init_nvenc(obs_encoder_t *encoder)
  189. {
  190. bool success;
  191. pthread_mutex_lock(&init_mutex);
  192. success = init_nvenc_internal(encoder);
  193. pthread_mutex_unlock(&init_mutex);
  194. return success;
  195. }
  196. struct encoder_caps *get_encoder_caps(enum codec_type codec)
  197. {
  198. struct encoder_caps *caps = &encoder_capabilities[codec];
  199. return caps;
  200. }
  201. int num_encoder_devices(void)
  202. {
  203. return num_devices;
  204. }
  205. bool is_codec_supported(enum codec_type codec)
  206. {
  207. return codec_supported[codec];
  208. }
  209. bool has_broken_split_encoding(void)
  210. {
  211. /* CBR padding and tearing artifacts with split encoding are fixed in
  212. * driver versions 555+, previous ones should be considered broken. */
  213. return driver_version_major < 555;
  214. }
  215. static void read_codec_caps(config_t *config, enum codec_type codec, const char *section)
  216. {
  217. struct encoder_caps *caps = &encoder_capabilities[codec];
  218. codec_supported[codec] = config_get_bool(config, section, "codec_supported");
  219. if (!codec_supported[codec])
  220. return;
  221. caps->bframes = (int)config_get_int(config, section, "bframes");
  222. caps->bref_modes = (int)config_get_int(config, section, "bref");
  223. caps->engines = (int)config_get_int(config, section, "engines");
  224. caps->max_width = (int)config_get_int(config, section, "max_width");
  225. caps->max_height = (int)config_get_int(config, section, "max_height");
  226. caps->temporal_filter = (int)config_get_int(config, section, "temporal_filter");
  227. caps->lookahead_level = (int)config_get_int(config, section, "lookahead_level");
  228. caps->dyn_bitrate = config_get_bool(config, section, "dynamic_bitrate");
  229. caps->lookahead = config_get_bool(config, section, "lookahead");
  230. caps->lossless = config_get_bool(config, section, "lossless");
  231. caps->temporal_aq = config_get_bool(config, section, "temporal_aq");
  232. caps->ten_bit = config_get_bool(config, section, "10bit");
  233. caps->four_four_four = config_get_bool(config, section, "yuv_444");
  234. caps->four_two_two = config_get_bool(config, section, "yuv_422");
  235. caps->uhq = config_get_bool(config, section, "uhq");
  236. }
  237. static bool nvenc_check(void)
  238. {
  239. #ifdef _WIN32
  240. char *test_exe = os_get_executable_path_ptr("obs-nvenc-test.exe");
  241. #else
  242. char *test_exe = os_get_executable_path_ptr("obs-nvenc-test");
  243. #endif
  244. os_process_args_t *args;
  245. struct dstr caps_str = {0};
  246. config_t *config = NULL;
  247. bool success = false;
  248. args = os_process_args_create(test_exe);
  249. os_process_pipe_t *pp = os_process_pipe_create2(args, "r");
  250. if (!pp) {
  251. blog(LOG_WARNING, "[NVENC] Failed to launch the NVENC "
  252. "test process I guess");
  253. goto fail;
  254. }
  255. for (;;) {
  256. char data[2048];
  257. size_t len = os_process_pipe_read(pp, (uint8_t *)data, sizeof(data));
  258. if (!len)
  259. break;
  260. dstr_ncat(&caps_str, data, len);
  261. }
  262. os_process_pipe_destroy(pp);
  263. if (dstr_is_empty(&caps_str)) {
  264. blog(LOG_WARNING, "[NVENC] Seems the NVENC test subprocess crashed. "
  265. "Better there than here I guess. ");
  266. goto fail;
  267. }
  268. if (config_open_string(&config, caps_str.array) != 0) {
  269. blog(LOG_WARNING, "[NVENC] Failed to open config string");
  270. goto fail;
  271. }
  272. success = config_get_bool(config, "general", "nvenc_supported");
  273. if (!success) {
  274. const char *error = config_get_string(config, "general", "reason");
  275. blog(LOG_WARNING, "[NVENC] Test process failed: %s", error ? error : "unknown");
  276. goto fail;
  277. }
  278. num_devices = (int)config_get_int(config, "general", "nvenc_devices");
  279. read_codec_caps(config, CODEC_H264, "h264");
  280. read_codec_caps(config, CODEC_HEVC, "hevc");
  281. read_codec_caps(config, CODEC_AV1, "av1");
  282. const char *nvenc_ver = config_get_string(config, "general", "nvenc_ver");
  283. const char *cuda_ver = config_get_string(config, "general", "cuda_ver");
  284. const char *driver_ver = config_get_string(config, "general", "driver_ver");
  285. /* Parse out major/minor for some brokenness checks */
  286. sscanf(driver_ver, "%d.%d", &driver_version_major, &driver_version_minor);
  287. blog(LOG_INFO,
  288. "[obs-nvenc] NVENC version: %d.%d (compiled) / %s (driver), "
  289. "CUDA driver version: %s, AV1 supported: %s",
  290. NVCODEC_CONFIGURED_VERSION >> 4, NVCODEC_CONFIGURED_VERSION & 0xf, nvenc_ver, cuda_ver,
  291. codec_supported[CODEC_AV1] ? "true" : "false");
  292. fail:
  293. if (config)
  294. config_close(config);
  295. bfree(test_exe);
  296. dstr_free(&caps_str);
  297. os_process_args_destroy(args);
  298. return success;
  299. }
  300. static const char *nvenc_check_name = "nvenc_check";
  301. bool nvenc_supported(void)
  302. {
  303. bool success;
  304. profile_start(nvenc_check_name);
  305. success = load_nvenc_lib() && nvenc_check();
  306. profile_end(nvenc_check_name);
  307. return success;
  308. }
  309. void obs_nvenc_load(void)
  310. {
  311. pthread_mutex_init(&init_mutex, NULL);
  312. register_encoders();
  313. register_compat_encoders();
  314. }
  315. void obs_nvenc_unload(void)
  316. {
  317. pthread_mutex_destroy(&init_mutex);
  318. }