kmssink: add dmabuf support
authorVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Wed, 10 Feb 2016 19:43:47 +0000 (20:43 +0100)
committerVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Mon, 11 Apr 2016 17:57:48 +0000 (19:57 +0200)
This patch will enable the import of dmabufs into a KMS buffer using
the PRIME kernel interface.

If the driver does not support prime import, the method is skipped.

It has been tested with a Freescale I.MX6 board.

https://bugzilla.gnome.org/show_bug.cgi?id=761059

configure.ac
sys/kms/Makefile.am
sys/kms/gstkmsallocator.c
sys/kms/gstkmsallocator.h
sys/kms/gstkmssink.c
sys/kms/gstkmssink.h

index 8410645..a63e983 100644 (file)
@@ -2326,6 +2326,7 @@ dnl *** kms ***
 translit(dnm, m, l) AM_CONDITIONAL(USE_KMS, true)
 AG_GST_CHECK_FEATURE(KMS, [drm/kms libraries], kms, [
   AG_GST_PKG_CHECK_MODULES(GST_VIDEO, gstreamer-video-1.0)
+  AG_GST_PKG_CHECK_MODULES(GST_ALLOCATORS, gstreamer-allocators-1.0)
   PKG_CHECK_MODULES([DRM], [libdrm libkms], HAVE_KMS=yes, HAVE_KMS=no)
 ])
 
index 316b492..9b12c72 100644 (file)
@@ -11,6 +11,7 @@ libgstkmssink_la_CFLAGS =                     \
        $(GST_PLUGINS_BASE_CFLAGS)              \
        $(GST_BASE_CFLAGS)                      \
        $(GST_VIDEO_CFLAGS)                     \
+       $(GST_ALLOCATORS_CFLAGS)                \
        $(GST_CFLAGS)                           \
        $(DRM_CFLAGS)                           \
        $(NULL)
@@ -19,6 +20,7 @@ libgstkmssink_la_LIBADD =                     \
        $(GST_PLUGINS_BASE_LIBS)                \
        $(GST_BASE_LIBS)                        \
        $(GST_VIDEO_LIBS)                       \
+       $(GST_ALLOCATORS_LIBS)                  \
        $(GST_LIBS)                             \
        $(DRM_LIBS)                             \
        $(NULL)
index 5798b65..5c23c92 100644 (file)
@@ -99,6 +99,51 @@ ensure_kms_driver (GstKMSAllocator * alloc)
 }
 
 static void
+gst_kms_allocator_memory_reset (GstKMSAllocator * allocator, GstKMSMemory * mem)
+{
+  if (mem->fb_id) {
+    GST_DEBUG_OBJECT (allocator, "removing fb id %d", mem->fb_id);
+    drmModeRmFB (allocator->priv->fd, mem->fb_id);
+    mem->fb_id = 0;
+  }
+
+  if (!ensure_kms_driver (allocator))
+    return;
+
+  if (mem->bo) {
+    kms_bo_destroy (&mem->bo);
+    mem->bo = NULL;
+  }
+}
+
+static gboolean
+gst_kms_allocator_memory_create (GstKMSAllocator * allocator,
+    GstKMSMemory * kmsmem, GstVideoInfo * vinfo)
+{
+  gint ret;
+  guint attrs[] = {
+    KMS_WIDTH, GST_VIDEO_INFO_WIDTH (vinfo),
+    KMS_HEIGHT, GST_VIDEO_INFO_HEIGHT (vinfo),
+    KMS_TERMINATE_PROP_LIST,
+  };
+
+  if (kmsmem->bo)
+    return TRUE;
+
+  if (!ensure_kms_driver (allocator))
+    return FALSE;
+
+  ret = kms_bo_create (allocator->priv->driver, attrs, &kmsmem->bo);
+  if (ret) {
+    GST_ERROR_OBJECT (allocator, "Failed to create buffer object: %s (%d)",
+        strerror (-ret), ret);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static void
 gst_kms_allocator_free (GstAllocator * allocator, GstMemory * mem)
 {
   GstKMSAllocator *alloc;
@@ -107,12 +152,7 @@ gst_kms_allocator_free (GstAllocator * allocator, GstMemory * mem)
   alloc = GST_KMS_ALLOCATOR (allocator);
   kmsmem = (GstKMSMemory *) mem;
 
-  if (kmsmem->fb_id)
-    drmModeRmFB (alloc->priv->fd, kmsmem->fb_id);
-
-  if (ensure_kms_driver (alloc) && kmsmem->bo)
-    kms_bo_destroy (&kmsmem->bo);
-
+  gst_kms_allocator_memory_reset (alloc, kmsmem);
   g_slice_free (GstKMSMemory, kmsmem);
 }
 
@@ -272,13 +312,15 @@ gst_kms_allocator_add_fb (GstKMSAllocator * alloc, GstKMSMemory * kmsmem,
 
   if (kmsmem->bo) {
     kms_bo_get_prop (kmsmem->bo, KMS_HANDLE, &bo_handles[0]);
+    for (i = 1; i < 4; i++)
+      bo_handles[i] = bo_handles[0];
   } else {
-    return FALSE;
+    for (i = 0; i < 4; i++)
+      bo_handles[i] = kmsmem->gem_handle[i];
   }
 
-  /* @FIXME: fill the other handles */
-  bo_handles[1] = bo_handles[0];
-  bo_handles[2] = bo_handles[0];
+  GST_DEBUG_OBJECT (alloc, "bo handles: %d, %d, %d, %d", bo_handles[0],
+      bo_handles[1], bo_handles[2], bo_handles[3]);
 
   for (i = 0; i < 4; i++) {
     offsets[i] = GST_VIDEO_INFO_PLANE_OFFSET (vinfo, i);
@@ -295,38 +337,38 @@ gst_kms_allocator_add_fb (GstKMSAllocator * alloc, GstKMSMemory * kmsmem,
   return TRUE;
 }
 
-GstMemory *
-gst_kms_allocator_bo_alloc (GstAllocator * allocator, GstVideoInfo * vinfo)
+static GstMemory *
+gst_kms_allocator_alloc_empty (GstAllocator * allocator, GstVideoInfo * vinfo)
 {
-  GstKMSAllocator *alloc;
   GstKMSMemory *kmsmem;
   GstMemory *mem;
-  int ret;
-  guint attrs[] = {
-    KMS_WIDTH, GST_VIDEO_INFO_WIDTH (vinfo),
-    KMS_HEIGHT, GST_VIDEO_INFO_HEIGHT (vinfo),
-    KMS_TERMINATE_PROP_LIST,
-  };
 
   kmsmem = g_slice_new0 (GstKMSMemory);
   if (!kmsmem)
     return NULL;
-
   mem = GST_MEMORY_CAST (kmsmem);
+
   gst_memory_init (mem, GST_MEMORY_FLAG_NO_SHARE, allocator, NULL,
       GST_VIDEO_INFO_SIZE (vinfo), 0, 0, GST_VIDEO_INFO_SIZE (vinfo));
 
-  alloc = GST_KMS_ALLOCATOR (allocator);
-  if (!ensure_kms_driver (alloc))
-    goto fail;
+  return mem;
+}
+
+GstMemory *
+gst_kms_allocator_bo_alloc (GstAllocator * allocator, GstVideoInfo * vinfo)
+{
+  GstKMSAllocator *alloc;
+  GstKMSMemory *kmsmem;
+  GstMemory *mem;
 
+  mem = gst_kms_allocator_alloc_empty (allocator, vinfo);
+  if (!mem)
+    return NULL;
+
+  alloc = GST_KMS_ALLOCATOR (allocator);
   kmsmem = (GstKMSMemory *) mem;
-  ret = kms_bo_create (alloc->priv->driver, attrs, &kmsmem->bo);
-  if (ret) {
-    GST_ERROR_OBJECT (alloc, "Failed to create buffer object: %s (%d)",
-        strerror (-ret), ret);
+  if (!gst_kms_allocator_memory_create (alloc, kmsmem, vinfo))
     goto fail;
-  }
   if (!gst_kms_allocator_add_fb (alloc, kmsmem, vinfo))
     goto fail;
 
@@ -337,3 +379,47 @@ fail:
   gst_memory_unref (mem);
   return NULL;
 }
+
+GstKMSMemory *
+gst_kms_allocator_dmabuf_import (GstAllocator * allocator, gint * prime_fds,
+    gint n_planes, GstVideoInfo * vinfo)
+{
+  GstKMSAllocator *alloc;
+  GstMemory *mem;
+  GstKMSMemory *tmp;
+  gint i, ret;
+
+  g_return_val_if_fail (n_planes <= GST_VIDEO_MAX_PLANES, FALSE);
+
+  mem = gst_kms_allocator_alloc_empty (allocator, vinfo);
+  if (!mem)
+    return FALSE;
+
+  tmp = (GstKMSMemory *) mem;
+  alloc = GST_KMS_ALLOCATOR (allocator);
+  for (i = 0; i < n_planes; i++) {
+    ret = drmPrimeFDToHandle (alloc->priv->fd, prime_fds[i],
+        &tmp->gem_handle[i]);
+    if (ret)
+      goto import_fd_failed;
+  }
+
+  if (!gst_kms_allocator_add_fb (alloc, tmp, vinfo))
+    goto failed;
+
+  return tmp;
+
+  /* ERRORS */
+import_fd_failed:
+  {
+    GST_ERROR_OBJECT (alloc, "Failed to import prime fd %d: %s (%d)",
+        prime_fds[i], strerror (-ret), ret);
+    /* fallback */
+  }
+
+failed:
+  {
+    gst_memory_unref (mem);
+    return NULL;
+  }
+}
index d77166f..fa5c349 100644 (file)
@@ -55,6 +55,7 @@ struct _GstKMSMemory
   GstMemory parent;
 
   guint32 fb_id;
+  guint32 gem_handle[GST_VIDEO_MAX_PLANES];
   struct kms_bo *bo;
 };
 
@@ -78,6 +79,11 @@ GstAllocator* gst_kms_allocator_new (gint fd);
 GstMemory*    gst_kms_allocator_bo_alloc (GstAllocator *allocator,
                                          GstVideoInfo *vinfo);
 
+GstKMSMemory* gst_kms_allocator_dmabuf_import (GstAllocator *allocator,
+                                              gint *prime_fds,
+                                              gint n_planes,
+                                              GstVideoInfo *vinfo);
+
 G_END_DECLS
 
 
index cd1401f..66777e3 100644 (file)
@@ -43,6 +43,7 @@
 #endif
 
 #include <gst/video/video.h>
+#include <gst/allocators/gstdmabuf.h>
 
 #include <drm.h>
 #include <xf86drm.h>
@@ -259,6 +260,7 @@ get_drm_caps (GstKMSSink * self)
 {
   gint ret;
   guint64 has_dumb_buffer;
+  guint64 has_prime;
 
   has_dumb_buffer = 0;
   ret = drmGetCap (self->fd, DRM_CAP_DUMB_BUFFER, &has_dumb_buffer);
@@ -269,6 +271,16 @@ get_drm_caps (GstKMSSink * self)
     return FALSE;
   }
 
+  has_prime = 0;
+  ret = drmGetCap (self->fd, DRM_CAP_PRIME, &has_prime);
+  if (ret)
+    GST_WARNING_OBJECT (self, "could not get prime capability");
+  else
+    self->has_prime_import = (gboolean) (has_prime & DRM_PRIME_CAP_IMPORT);
+
+  GST_INFO_OBJECT (self, "prime import (%s)",
+      self->has_prime_import ? "✓" : "✗");
+
   return TRUE;
 }
 
@@ -736,6 +748,128 @@ gst_kms_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
   }
 }
 
+static GstMemory *
+get_cached_kmsmem (GstMemory * mem)
+{
+  return gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
+      g_quark_from_static_string ("kmsmem"));
+}
+
+static void
+set_cached_kmsmem (GstMemory * mem, GstMemory * kmsmem)
+{
+  return gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
+      g_quark_from_static_string ("kmsmem"), kmsmem,
+      (GDestroyNotify) gst_memory_unref);
+}
+
+static gsize
+get_plane_data_size (GstVideoInfo * info, guint plane)
+{
+  gint padded_height;
+  gsize plane_size;
+
+  padded_height = info->height;
+  padded_height =
+      GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, plane, padded_height);
+
+  plane_size = GST_VIDEO_INFO_PLANE_STRIDE (info, plane) * padded_height;
+
+  return plane_size;
+}
+
+static gboolean
+gst_kms_sink_import_dmabuf (GstKMSSink * self, GstBuffer * inbuf,
+    GstBuffer ** outbuf)
+{
+  gint prime_fds[GST_VIDEO_MAX_PLANES] = { 0, };
+  GstVideoMeta *meta;
+  guint i, n_mem, n_planes;
+  GstKMSMemory *kmsmem;
+  guint mems_idx[GST_VIDEO_MAX_PLANES];
+  gsize mems_skip[GST_VIDEO_MAX_PLANES];
+  GstMemory *mems[GST_VIDEO_MAX_PLANES];
+
+  if (!self->has_prime_import)
+    return FALSE;
+
+  /* This will eliminate most non-dmabuf out there */
+  if (!gst_is_dmabuf_memory (gst_buffer_peek_memory (inbuf, 0)))
+    return FALSE;
+
+  n_planes = GST_VIDEO_INFO_N_PLANES (&self->vinfo);
+  n_mem = gst_buffer_n_memory (inbuf);
+  meta = gst_buffer_get_video_meta (inbuf);
+
+  /* We cannot have multiple dmabuf per plane */
+  if (n_mem > n_planes)
+    return FALSE;
+
+  /* Update video info based on video meta */
+  if (meta) {
+    GST_VIDEO_INFO_WIDTH (&self->vinfo) = meta->width;
+    GST_VIDEO_INFO_HEIGHT (&self->vinfo) = meta->height;
+
+    for (i = 0; i < meta->n_planes; i++) {
+      GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i) = meta->offset[i];
+      GST_VIDEO_INFO_PLANE_STRIDE (&self->vinfo, i) = meta->stride[i];
+    }
+  }
+
+  /* Find and validate all memories */
+  for (i = 0; i < n_planes; i++) {
+    guint plane_size;
+    guint length;
+
+    plane_size = get_plane_data_size (&self->vinfo, i);
+    if (!gst_buffer_find_memory (inbuf,
+            GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i), plane_size,
+            &mems_idx[i], &length, &mems_skip[i]))
+      return FALSE;
+
+    /* We can't have more then one dmabuf per plane */
+    if (length != 1)
+      return FALSE;
+
+    mems[i] = gst_buffer_peek_memory (inbuf, mems_idx[i]);
+
+    /* And all memory found must be dmabuf */
+    if (!gst_is_dmabuf_memory (mems[i]))
+      return FALSE;
+  }
+
+  kmsmem = (GstKMSMemory *) get_cached_kmsmem (mems[0]);
+  if (kmsmem) {
+    GST_LOG_OBJECT (self, "found KMS mem %p in DMABuf mem %p with fb id = %d",
+        kmsmem, mems[0], kmsmem->fb_id);
+    goto wrap_mem;
+  }
+
+  for (i = 0; i < n_planes; i++)
+    prime_fds[i] = gst_dmabuf_memory_get_fd (mems[i]);
+
+  GST_LOG_OBJECT (self, "found these prime ids: %d, %d, %d, %d", prime_fds[0],
+      prime_fds[1], prime_fds[2], prime_fds[3]);
+
+  kmsmem = gst_kms_allocator_dmabuf_import (self->allocator, prime_fds,
+      n_planes, &self->vinfo);
+  if (!kmsmem)
+    return FALSE;
+
+  GST_LOG_OBJECT (self, "setting KMS mem %p to DMABuf mem %p with fb id = %d",
+      kmsmem, mems[0], kmsmem->fb_id);
+  set_cached_kmsmem (mems[0], GST_MEMORY_CAST (kmsmem));
+
+wrap_mem:
+  *outbuf = gst_buffer_new ();
+  if (!*outbuf)
+    return FALSE;
+  gst_buffer_append_memory (*outbuf, gst_memory_ref (GST_MEMORY_CAST (kmsmem)));
+  gst_buffer_add_parent_buffer_meta (*outbuf, inbuf);
+
+  return TRUE;
+}
+
 static GstBuffer *
 gst_kms_sink_get_input_buffer (GstKMSSink * self, GstBuffer * inbuf)
 {
@@ -752,10 +886,15 @@ gst_kms_sink_get_input_buffer (GstKMSSink * self, GstBuffer * inbuf)
   if (gst_is_kms_memory (mem))
     return gst_buffer_ref (inbuf);
 
+  buf = NULL;
+  if (gst_kms_sink_import_dmabuf (self, inbuf, &buf))
+    return buf;
+
+  GST_LOG_OBJECT (self, "frame copy");
+
   if (!gst_buffer_pool_set_active (self->pool, TRUE))
     goto activate_pool_failed;
 
-  buf = NULL;
   ret = gst_buffer_pool_acquire_buffer (self->pool, &buf, NULL);
   if (ret != GST_FLOW_OK)
     goto create_buffer_failed;
index 9cc2f2a..90700b4 100644 (file)
@@ -55,6 +55,9 @@ struct _GstKMSSink {
 
   guint16 hdisplay, vdisplay;
 
+  /* capabilities */
+  gboolean has_prime_import;
+
   GstVideoInfo vinfo;
   GstCaps *allowed_caps;
   GstBufferPool *pool;