/* * gstvaapidisplay_drm.c - VA/DRM display abstraction * * Copyright (C) 2012 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ /** * SECTION:gstvaapidisplay_drm * @short_description: VA/DRM display abstraction */ #include "sysdeps.h" #include #include #include #include #include #include #include "gstvaapiutils.h" #include "gstvaapidisplay_priv.h" #include "gstvaapidisplay_drm.h" #include "gstvaapidisplay_drm_priv.h" #define DEBUG 1 #include "gstvaapidebug.h" #define NAME_PREFIX "DRM:" #define NAME_PREFIX_LENGTH 4 static inline gboolean is_device_path(const gchar *device_path) { return strncmp(device_path, NAME_PREFIX, NAME_PREFIX_LENGTH) == 0; } static gboolean compare_device_path(gconstpointer a, gconstpointer b, gpointer user_data) { const gchar *cached_name = a; const gchar *tested_name = b; if (!cached_name || !is_device_path(cached_name)) return FALSE; g_return_val_if_fail(tested_name && is_device_path(tested_name), FALSE); cached_name += NAME_PREFIX_LENGTH; tested_name += NAME_PREFIX_LENGTH; return strcmp(cached_name, tested_name) == 0; } /* Get default device path. Actually, the first match in the DRM subsystem */ static const gchar * get_default_device_path(GstVaapiDisplay *display) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); const gchar *syspath, *devpath; struct udev *udev = NULL; struct udev_device *device, *parent; struct udev_enumerate *e = NULL; struct udev_list_entry *l; int fd; if (!priv->device_path_default) { udev = udev_new(); if (!udev) goto end; e = udev_enumerate_new(udev); if (!e) goto end; udev_enumerate_add_match_subsystem(e, "drm"); udev_enumerate_scan_devices(e); udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) { syspath = udev_list_entry_get_name(l); device = udev_device_new_from_syspath(udev, syspath); parent = udev_device_get_parent(device); if (strcmp(udev_device_get_subsystem(parent), "pci") != 0) { udev_device_unref(device); continue; } devpath = udev_device_get_devnode(device); fd = open(devpath, O_RDWR|O_CLOEXEC); if (fd < 0) { udev_device_unref(device); continue; } priv->device_path_default = g_strdup(devpath); close(fd); udev_device_unref(device); break; } end: if (e) udev_enumerate_unref(e); if (udev) udev_unref(udev); } return priv->device_path_default; } /* Reconstruct a device path without our prefix */ static const gchar * get_device_path(GstVaapiDisplay *display) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); const gchar *device_path = priv->device_path; if (!device_path) return NULL; g_return_val_if_fail(is_device_path(device_path), NULL); device_path += NAME_PREFIX_LENGTH; if (*device_path == '\0') return NULL; return device_path; } /* Mangle device path with our prefix */ static gboolean set_device_path(GstVaapiDisplay *display, const gchar *device_path) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); g_free(priv->device_path); priv->device_path = NULL; if (!device_path) { device_path = get_default_device_path(display); if (!device_path) return FALSE; } priv->device_path = g_strdup_printf("%s%s", NAME_PREFIX, device_path); return priv->device_path != NULL; } /* Set device path from file descriptor */ static gboolean set_device_path_from_fd(GstVaapiDisplay *display, gint drm_device) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); const gchar *busid, *path, *str; gsize busid_length, path_length; struct udev *udev = NULL; struct udev_device *device; struct udev_enumerate *e = NULL; struct udev_list_entry *l; gboolean success = FALSE; g_free(priv->device_path); priv->device_path = NULL; if (drm_device < 0) goto end; busid = drmGetBusid(drm_device); if (!busid) goto end; if (strncmp(busid, "pci:", 4) != 0) goto end; busid += 4; busid_length = strlen(busid); udev = udev_new(); if (!udev) goto end; e = udev_enumerate_new(udev); if (!e) goto end; udev_enumerate_add_match_subsystem(e, "drm"); udev_enumerate_scan_devices(e); udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) { path = udev_list_entry_get_name(l); str = strstr(path, busid); if (!str || str <= path || str[-1] != '/') continue; path_length = strlen(path); if (str + busid_length >= path + path_length) continue; if (strncmp(&str[busid_length], "/drm/card", 9) != 0) continue; device = udev_device_new_from_syspath(udev, path); if (!device) continue; path = udev_device_get_devnode(device); priv->device_path = g_strdup_printf("%s%s", NAME_PREFIX, path); udev_device_unref(device); break; } success = TRUE; end: if (e) udev_enumerate_unref(e); if (udev) udev_unref(udev); return success; } static gboolean gst_vaapi_display_drm_bind_display(GstVaapiDisplay *display, gpointer native_display) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); priv->drm_device = GPOINTER_TO_INT(native_display); priv->use_foreign_display = TRUE; if (!set_device_path_from_fd(display, priv->drm_device)) return FALSE; return TRUE; } static gboolean gst_vaapi_display_drm_open_display(GstVaapiDisplay *display, const gchar *name) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); GstVaapiDisplayCache *cache; const GstVaapiDisplayInfo *info; cache = gst_vaapi_display_get_cache(); g_return_val_if_fail(cache != NULL, FALSE); if (!set_device_path(display, name)) return FALSE; info = gst_vaapi_display_cache_lookup_by_name(cache, priv->device_path, compare_device_path, NULL); if (info) { priv->drm_device = GPOINTER_TO_INT(info->native_display); priv->use_foreign_display = TRUE; } else { priv->drm_device = open(get_device_path(display), O_RDWR|O_CLOEXEC); if (priv->drm_device < 0) return FALSE; priv->use_foreign_display = FALSE; } return TRUE; } static void gst_vaapi_display_drm_close_display(GstVaapiDisplay *display) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); if (priv->drm_device >= 0) { if (!priv->use_foreign_display) close(priv->drm_device); priv->drm_device = -1; } if (priv->device_path) { g_free(priv->device_path); priv->device_path = NULL; } if (priv->device_path_default) { g_free(priv->device_path_default); priv->device_path_default = NULL; } } static gboolean gst_vaapi_display_drm_get_display_info(GstVaapiDisplay *display, GstVaapiDisplayInfo *info) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); GstVaapiDisplayCache *cache; const GstVaapiDisplayInfo *cached_info; /* Return any cached info even if child has its own VA display */ cache = gst_vaapi_display_get_cache(); if (!cache) return FALSE; cached_info = gst_vaapi_display_cache_lookup_by_native_display( cache, GINT_TO_POINTER(priv->drm_device)); if (cached_info) { *info = *cached_info; return TRUE; } /* Otherwise, create VA display if there is none already */ info->native_display = GINT_TO_POINTER(priv->drm_device); info->display_name = priv->device_path; if (!info->va_display) { info->va_display = vaGetDisplayDRM(priv->drm_device); if (!info->va_display) return FALSE; info->display_type = GST_VAAPI_DISPLAY_TYPE_DRM; } return TRUE; } static void gst_vaapi_display_drm_init(GstVaapiDisplay *display) { GstVaapiDisplayDRMPrivate * const priv = GST_VAAPI_DISPLAY_DRM_PRIVATE(display); priv->drm_device = -1; } static void gst_vaapi_display_drm_class_init(GstVaapiDisplayDRMClass *klass) { GstVaapiMiniObjectClass * const object_class = GST_VAAPI_MINI_OBJECT_CLASS(klass); GstVaapiDisplayClass * const dpy_class = GST_VAAPI_DISPLAY_CLASS(klass); gst_vaapi_display_class_init(&klass->parent_class); object_class->size = sizeof(GstVaapiDisplayDRM); dpy_class->init = gst_vaapi_display_drm_init; dpy_class->bind_display = gst_vaapi_display_drm_bind_display; dpy_class->open_display = gst_vaapi_display_drm_open_display; dpy_class->close_display = gst_vaapi_display_drm_close_display; dpy_class->get_display = gst_vaapi_display_drm_get_display_info; } static inline const GstVaapiDisplayClass * gst_vaapi_display_drm_class(void) { static GstVaapiDisplayDRMClass g_class; static gsize g_class_init = FALSE; if (g_once_init_enter(&g_class_init)) { gst_vaapi_display_drm_class_init(&g_class); g_once_init_leave(&g_class_init, TRUE); } return GST_VAAPI_DISPLAY_CLASS(&g_class); } /** * gst_vaapi_display_drm_new: * @device_path: the DRM device path * * Opens an DRM file descriptor using @device_path and returns a newly * allocated #GstVaapiDisplay object. The DRM display will be cloed * when the reference count of the object reaches zero. * * If @device_path is NULL, the DRM device path will be automatically * determined as the first positive match in the list of available DRM * devices. * * Return value: a newly allocated #GstVaapiDisplay object */ GstVaapiDisplay * gst_vaapi_display_drm_new(const gchar *device_path) { return gst_vaapi_display_new(gst_vaapi_display_drm_class(), GST_VAAPI_DISPLAY_INIT_FROM_DISPLAY_NAME, (gpointer)device_path); } /** * gst_vaapi_display_drm_new_with_device: * @device: an open DRM device (file descriptor) * * Creates a #GstVaapiDisplay based on the open DRM @device. The * caller still owns the device file descriptor and must call close() * when all #GstVaapiDisplay references are released. Doing so too * early can yield undefined behaviour. * * Return value: a newly allocated #GstVaapiDisplay object */ GstVaapiDisplay * gst_vaapi_display_drm_new_with_device(gint device) { g_return_val_if_fail(device >= 0, NULL); return gst_vaapi_display_new(gst_vaapi_display_drm_class(), GST_VAAPI_DISPLAY_INIT_FROM_NATIVE_DISPLAY, GINT_TO_POINTER(device)); } /** * gst_vaapi_display_drm_get_device: * @display: a #GstVaapiDisplayDRM * * Returns the underlying DRM device file descriptor that was created * by gst_vaapi_display_drm_new() or that was bound from * gst_vaapi_display_drm_new_with_device(). * * Return value: the DRM file descriptor attached to @display */ gint gst_vaapi_display_drm_get_device(GstVaapiDisplayDRM *display) { g_return_val_if_fail(GST_VAAPI_IS_DISPLAY_DRM(display), -1); return GST_VAAPI_DISPLAY_DRM_DEVICE(display); } /** * gst_vaapi_display_drm_get_device_path: * @display: a #GstVaapiDisplayDRM * * Returns the underlying DRM device path name was created by * gst_vaapi_display_drm_new() or that was bound from * gst_vaapi_display_drm_new_with_device(). * * Note: the #GstVaapiDisplayDRM object owns the resulting string, so * it shall not be deallocated. * * Return value: the DRM device path name attached to @display */ const gchar * gst_vaapi_display_drm_get_device_path(GstVaapiDisplayDRM *display) { g_return_val_if_fail(GST_VAAPI_IS_DISPLAY_DRM(display), NULL); return get_device_path(GST_VAAPI_DISPLAY_CAST(display)); }