Browse Source

obs-qsv11: Add adapter checks for Linux

This moves the existing adapter checking into the platform layer and
moves the Windows implementation to its platform implementation and adds
a Linux implementation based on directly querying VA-API.

Unlike Windows, this check takes ~1ms so we have no need to spin out
another thread to perform the work. This also fixes up some of the CPP/C
mixing going on in common_utils."h" to allow us to call common functions
from C files.
Kurt Kartaltepe 2 years ago
parent
commit
b8ccaf5649

+ 0 - 11
plugins/obs-qsv11/QSV_Encoder.h

@@ -89,23 +89,12 @@ static const char *const qsv_latency_names[] = {"ultra-low", "low", "normal",
 						0};
 typedef struct qsv_t qsv_t;
 
-struct adapter_info {
-	bool is_intel;
-	bool is_dgpu;
-	bool supports_av1;
-	bool supports_hevc;
-};
-
 enum qsv_codec {
 	QSV_CODEC_AVC,
 	QSV_CODEC_AV1,
 	QSV_CODEC_HEVC,
 };
 
-#define MAX_ADAPTERS 10
-extern struct adapter_info adapters[MAX_ADAPTERS];
-extern size_t adapter_count;
-
 typedef struct {
 	mfxU16 nTargetUsage; /* 1 through 7, 1 being best quality and 7
 				being the best speed */

+ 3 - 0
plugins/obs-qsv11/common_utils.cpp

@@ -4,6 +4,9 @@
 // Utility functions, not directly tied to Intel Media SDK functionality
 //
 
+struct adapter_info adapters[MAX_ADAPTERS] = {0};
+size_t adapter_count = 0;
+
 void PrintErrString(int err, const char *filestr, int line)
 {
 	switch (err) {

+ 24 - 1
plugins/obs-qsv11/common_utils.h

@@ -2,6 +2,9 @@
 
 #include <stdio.h>
 
+// Most of this file shouldnt be accessed from C.
+#ifdef __cplusplus
+
 #include <mfxvideo++.h>
 
 // =================================================================
@@ -143,4 +146,24 @@ void mfxGetTime(mfxTime *timestamp);
 
 //void mfxInitTime();  might need this for Windows
 double TimeDiffMsec(mfxTime tfinish, mfxTime tstart);
-extern "C" void util_cpuid(int cpuinfo[4], int flags);
+
+extern "C" {
+#endif // __cplusplus
+
+struct adapter_info {
+	bool is_intel;
+	bool is_dgpu;
+	bool supports_av1;
+	bool supports_hevc;
+};
+
+#define MAX_ADAPTERS 10
+extern struct adapter_info adapters[MAX_ADAPTERS];
+extern size_t adapter_count;
+
+void util_cpuid(int cpuinfo[4], int flags);
+void check_adapters(struct adapter_info *adapters, size_t *adapter_count);
+
+#ifdef __cplusplus
+}
+#endif

+ 179 - 0
plugins/obs-qsv11/common_utils_linux.cpp

@@ -1,7 +1,17 @@
 #include "common_utils.h"
+
 #include <time.h>
 #include <cpuid.h>
 #include <util/c99defs.h>
+#include <util/dstr.h>
+#include <va/va_drm.h>
+#include <va/va_str.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <dirent.h>
 
 mfxStatus simple_alloc(mfxHDL pthis, mfxFrameAllocRequest *request,
 		       mfxFrameAllocResponse *response)
@@ -98,3 +108,172 @@ extern "C" void util_cpuid(int cpuinfo[4], int level)
 		    (unsigned int *)&cpuinfo[1], (unsigned int *)&cpuinfo[2],
 		    (unsigned int *)&cpuinfo[3]);
 }
+
+struct vaapi_device {
+	int fd;
+	VADisplay display;
+	const char *driver;
+};
+
+void vaapi_open(char *device_path, struct vaapi_device *device)
+{
+	int fd = open(device_path, O_RDWR);
+	if (fd < 0) {
+		return;
+	}
+
+	VADisplay display = vaGetDisplayDRM(fd);
+	if (!display) {
+		close(fd);
+		return;
+	}
+
+	// VA-API is noisy by default.
+	vaSetInfoCallback(display, nullptr, nullptr);
+	vaSetErrorCallback(display, nullptr, nullptr);
+
+	int major;
+	int minor;
+	if (vaInitialize(display, &major, &minor) != VA_STATUS_SUCCESS) {
+		vaTerminate(display);
+		close(fd);
+		return;
+	}
+
+	const char *driver = vaQueryVendorString(display);
+	if (strstr(driver, "Intel i965 driver") != nullptr) {
+		blog(LOG_WARNING,
+		     "Legacy intel-vaapi-driver detected, incompatible with QSV");
+		vaTerminate(display);
+		close(fd);
+		return;
+	}
+
+	device->fd = fd;
+	device->display = display;
+	device->driver = driver;
+}
+
+void vaapi_close(struct vaapi_device *device)
+{
+	vaTerminate(device->display);
+	close(device->fd);
+}
+
+static uint32_t vaapi_check_support(VADisplay display, VAProfile profile,
+				    VAEntrypoint entrypoint)
+{
+	bool ret = false;
+	VAConfigAttrib attrib[1];
+	attrib->type = VAConfigAttribRateControl;
+
+	VAStatus va_status =
+		vaGetConfigAttributes(display, profile, entrypoint, attrib, 1);
+
+	uint32_t rc = 0;
+	switch (va_status) {
+	case VA_STATUS_SUCCESS:
+		rc = attrib->value;
+		break;
+	case VA_STATUS_ERROR_UNSUPPORTED_PROFILE:
+	case VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT:
+	default:
+		break;
+	}
+
+	return (rc & VA_RC_CBR || rc & VA_RC_CQP || rc & VA_RC_VBR);
+}
+
+bool vaapi_supports_h264(VADisplay display)
+{
+	bool ret = false;
+	ret |= vaapi_check_support(display, VAProfileH264ConstrainedBaseline,
+				   VAEntrypointEncSlice);
+	ret |= vaapi_check_support(display, VAProfileH264Main,
+				   VAEntrypointEncSlice);
+	ret |= vaapi_check_support(display, VAProfileH264High,
+				   VAEntrypointEncSlice);
+
+	if (!ret) {
+		ret |= vaapi_check_support(display,
+					   VAProfileH264ConstrainedBaseline,
+					   VAEntrypointEncSliceLP);
+		ret |= vaapi_check_support(display, VAProfileH264Main,
+					   VAEntrypointEncSliceLP);
+		ret |= vaapi_check_support(display, VAProfileH264High,
+					   VAEntrypointEncSliceLP);
+	}
+
+	return ret;
+}
+
+bool vaapi_supports_av1(VADisplay display)
+{
+	bool ret = false;
+	// Are there any devices with non-LowPower entrypoints?
+	ret |= vaapi_check_support(display, VAProfileAV1Profile0,
+				   VAEntrypointEncSlice);
+	ret |= vaapi_check_support(display, VAProfileAV1Profile0,
+				   VAEntrypointEncSliceLP);
+	return ret;
+}
+
+bool vaapi_supports_hevc(VADisplay display)
+{
+	bool ret = false;
+	ret |= vaapi_check_support(display, VAProfileHEVCMain,
+				   VAEntrypointEncSlice);
+	ret |= vaapi_check_support(display, VAProfileHEVCMain,
+				   VAEntrypointEncSliceLP);
+	return ret;
+}
+
+void check_adapters(struct adapter_info *adapters, size_t *adapter_count)
+{
+	struct dstr full_path;
+	struct dirent **namelist;
+	int no;
+	int adapter_idx;
+	const char *base_dir = "/dev/dri/";
+
+	dstr_init(&full_path);
+	if ((no = scandir(base_dir, &namelist, 0, alphasort)) > 0) {
+		for (int i = 0; i < no; i++) {
+			struct adapter_info *adapter;
+			struct dirent *dp;
+			struct vaapi_device device = {0};
+
+			dp = namelist[i];
+			if (strstr(dp->d_name, "renderD") == nullptr)
+				goto next_entry;
+
+			adapter_idx = atoi(&dp->d_name[7]) - 128;
+			if (adapter_idx >= (ssize_t)*adapter_count ||
+			    adapter_idx < 0)
+				goto next_entry;
+
+			*adapter_count = adapter_idx + 1;
+			dstr_copy(&full_path, base_dir);
+			dstr_cat(&full_path, dp->d_name);
+			vaapi_open(full_path.array, &device);
+			if (!device.display)
+				goto next_entry;
+
+			adapter = &adapters[adapter_idx];
+			adapter->is_intel = strstr(device.driver, "Intel") !=
+					    nullptr;
+			// This is currently only used for LowPower coding which is busted on VA-API anyway.
+			adapter->is_dgpu = false;
+			adapter->supports_av1 =
+				vaapi_supports_av1(device.display);
+			adapter->supports_hevc =
+				vaapi_supports_hevc(device.display);
+			vaapi_close(&device);
+
+		next_entry:
+			free(dp);
+		}
+		free(namelist);
+	}
+	dstr_free(&full_path);
+}

+ 89 - 0
plugins/obs-qsv11/common_utils_windows.cpp

@@ -9,7 +9,14 @@
 #include "common_directx9.h"
 #endif
 
+#include <util/windows/device-enum.h>
+#include <util/config-file.h>
+#include <util/platform.h>
+#include <util/pipe.h>
+#include <util/dstr.h>
+
 #include <intrin.h>
+#include <inttypes.h>
 
 /* =======================================================
  * Windows implementation of OS-specific utility functions
@@ -125,6 +132,88 @@ void util_cpuid(int cpuinfo[4], int flags)
 	return __cpuid(cpuinfo, flags);
 }
 
+static bool enum_luids(void *param, uint32_t idx, uint64_t luid)
+{
+	struct dstr *cmd = (struct dstr *)param;
+	dstr_catf(cmd, " %" PRIx64, luid);
+	UNUSED_PARAMETER(idx);
+	return true;
+}
+
+void check_adapters(struct adapter_info *adapters, size_t *adapter_count)
+{
+	char *test_exe = os_get_executable_path_ptr("obs-qsv-test.exe");
+	struct dstr cmd = {0};
+	struct dstr caps_str = {0};
+	os_process_pipe_t *pp = nullptr;
+	config_t *config = nullptr;
+	const char *error = nullptr;
+	size_t config_adapter_count;
+
+	dstr_copy(&cmd, test_exe);
+	enum_graphics_device_luids(enum_luids, &cmd);
+
+	pp = os_process_pipe_create(cmd.array, "r");
+	if (!pp) {
+		blog(LOG_INFO, "Failed to launch the QSV test process I guess");
+		goto fail;
+	}
+
+	for (;;) {
+		char data[2048];
+		size_t len =
+			os_process_pipe_read(pp, (uint8_t *)data, sizeof(data));
+		if (!len)
+			break;
+
+		dstr_ncat(&caps_str, data, len);
+	}
+
+	if (dstr_is_empty(&caps_str)) {
+		blog(LOG_INFO, "Seems the QSV test subprocess crashed. "
+			       "Better there than here I guess. "
+			       "Let's just skip loading QSV then I suppose.");
+		goto fail;
+	}
+
+	if (config_open_string(&config, caps_str.array) != 0) {
+		blog(LOG_INFO, "Couldn't open QSV configuration string");
+		goto fail;
+	}
+
+	error = config_get_string(config, "error", "string");
+	if (error) {
+		blog(LOG_INFO, "Error querying QSV support: %s", error);
+		goto fail;
+	}
+
+	config_adapter_count = config_num_sections(config);
+
+	if (config_adapter_count < *adapter_count)
+		*adapter_count = config_adapter_count;
+
+	for (size_t i = 0; i < *adapter_count; i++) {
+		char section[16];
+		snprintf(section, sizeof(section), "%d", (int)i);
+
+		struct adapter_info *adapter = &adapters[i];
+		adapter->is_intel =
+			config_get_bool(config, section, "is_intel");
+		adapter->is_dgpu = config_get_bool(config, section, "is_dgpu");
+		adapter->supports_av1 =
+			config_get_bool(config, section, "supports_av1");
+		adapter->supports_hevc =
+			config_get_bool(config, section, "supports_hevc");
+	}
+
+fail:
+	config_close(config);
+	dstr_free(&caps_str);
+	dstr_free(&cmd);
+	os_process_pipe_destroy(pp);
+	bfree(test_exe);
+}
+
 /* (Lain) Functions currently unused */
 #if 0
 void ClearYUVSurfaceVMem(mfxMemId memId)

+ 4 - 100
plugins/obs-qsv11/obs-qsv11-plugin-main.c

@@ -53,14 +53,9 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
-#include <inttypes.h>
 #include <obs-module.h>
-#include <util/windows/device-enum.h>
-#include <util/config-file.h>
-#include <util/platform.h>
-#include <util/pipe.h>
-#include <util/dstr.h>
-#include "QSV_Encoder.h"
+
+#include "common_utils.h"
 
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("obs-qsv11", "en-US")
@@ -78,102 +73,20 @@ extern struct obs_encoder_info obs_qsv_av1_encoder;
 extern struct obs_encoder_info obs_qsv_hevc_encoder_tex;
 extern struct obs_encoder_info obs_qsv_hevc_encoder;
 
-extern bool av1_supported(mfxIMPL impl);
-
-struct adapter_info adapters[MAX_ADAPTERS] = {0};
-size_t adapter_count = 0;
-
-static bool enum_luids(void *param, uint32_t idx, uint64_t luid)
-{
-	struct dstr *cmd = param;
-	dstr_catf(cmd, " %" PRIx64, luid);
-	UNUSED_PARAMETER(idx);
-	return true;
-}
-
 bool obs_module_load(void)
 {
-#if defined(_WIN32)
-	char *test_exe = os_get_executable_path_ptr("obs-qsv-test.exe");
-	struct dstr cmd = {0};
-	struct dstr caps_str = {0};
-	os_process_pipe_t *pp = NULL;
-	config_t *config = NULL;
-
-	dstr_copy(&cmd, test_exe);
-	enum_graphics_device_luids(enum_luids, &cmd);
-
-	pp = os_process_pipe_create(cmd.array, "r");
-	if (!pp) {
-		blog(LOG_INFO, "Failed to launch the QSV test process I guess");
-		goto fail;
-	}
-
-	for (;;) {
-		char data[2048];
-		size_t len =
-			os_process_pipe_read(pp, (uint8_t *)data, sizeof(data));
-		if (!len)
-			break;
-
-		dstr_ncat(&caps_str, data, len);
-	}
-
-	if (dstr_is_empty(&caps_str)) {
-		blog(LOG_INFO, "Seems the QSV test subprocess crashed. "
-			       "Better there than here I guess. "
-			       "Let's just skip loading QSV then I suppose.");
-		goto fail;
-	}
-
-	if (config_open_string(&config, caps_str.array) != 0) {
-		blog(LOG_INFO, "Couldn't open QSV configuration string");
-		goto fail;
-	}
+	adapter_count = MAX_ADAPTERS;
+	check_adapters(adapters, &adapter_count);
 
-	const char *error = config_get_string(config, "error", "string");
-	if (error) {
-		blog(LOG_INFO, "Error querying QSV support: %s", error);
-		goto fail;
-	}
-
-	adapter_count = config_num_sections(config);
 	bool avc_supported = false;
 	bool av1_supported = false;
 	bool hevc_supported = false;
-
-	if (adapter_count > MAX_ADAPTERS)
-		adapter_count = MAX_ADAPTERS;
-
 	for (size_t i = 0; i < adapter_count; i++) {
-		char section[16];
-		snprintf(section, sizeof(section), "%d", (int)i);
-
 		struct adapter_info *adapter = &adapters[i];
-		adapter->is_intel =
-			config_get_bool(config, section, "is_intel");
-		adapter->is_dgpu = config_get_bool(config, section, "is_dgpu");
-		adapter->supports_av1 =
-			config_get_bool(config, section, "supports_av1");
-		adapter->supports_hevc =
-			config_get_bool(config, section, "supports_hevc");
-
 		avc_supported |= adapter->is_intel;
 		av1_supported |= adapter->supports_av1;
 		hevc_supported |= adapter->supports_hevc;
 	}
-#else
-	// We could lift the VA-API query from obs-ffmpeg here.
-	adapter_count = 1;
-	struct adapter_info *adapter = &adapters[0];
-	adapter->is_intel = true;
-	adapter->is_dgpu = true;
-	adapter->supports_av1 = true;
-	adapter->supports_hevc = true;
-	bool avc_supported = true;
-	bool hevc_supported = true;
-	bool av1_supported = true;
-#endif
 
 	if (avc_supported) {
 		obs_register_encoder(&obs_qsv_encoder_tex_v2);
@@ -192,14 +105,5 @@ bool obs_module_load(void)
 	}
 #endif
 
-fail:
-#if defined(_WIN32)
-	config_close(config);
-	dstr_free(&caps_str);
-	dstr_free(&cmd);
-	os_process_pipe_destroy(pp);
-	bfree(test_exe);
-#endif
-
 	return true;
 }

+ 2 - 1
plugins/obs-qsv11/obs-qsv11.c

@@ -65,6 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <obs-avc.h>
 
 #include "QSV_Encoder.h"
+#include "common_utils.h"
 
 #define do_log(level, format, ...)                 \
 	blog(level, "[qsv encoder: '%s'] " format, \
@@ -517,7 +518,7 @@ static void update_params(struct obs_qsv *obsqsv, obs_data_t *settings)
 	bool cbr_override = obs_data_get_bool(settings, "cbr");
 	int bFrames = (int)obs_data_get_int(settings, "bframes");
 	bool enhancements = obs_data_get_bool(settings, "enhancements");
-	const char *codec;
+	const char *codec = "";
 
 	if (obs_data_has_user_value(settings, "bf"))
 		bFrames = (int)obs_data_get_int(settings, "bf");