diff mbox series

[FFmpeg-devel,2/4] kmsgrab: Use GetFB2 if available

Message ID 20200705154946.401280-2-sw@jkqxz.net
State New
Headers show
Series [FFmpeg-devel,1/4] kmsgrab: Refactor and clean error cases
Related show

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Mark Thompson July 5, 2020, 3:49 p.m. UTC
The most useful feature here is the ability to automatically extract the
framebuffer format and modifiers.  It also makes support for multi-plane
framebuffers possible, though they will need to be added to the format
table to work (not tested by me).

This requires libdrm 2.4.101 (from April 2020) to build, so it includes a
configure check to allow compatibility with existing distributions.  Even
with libdrm support, it still won't do anything at runtime if you are
running Linux < 5.7 (before June 2020).
---
This has been hanging around for a while waiting for the GETFB2 ioctl() to actually make it into stable Linux, which it now is with 5.7.


 configure             |   4 +
 libavdevice/kmsgrab.c | 221 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 203 insertions(+), 22 deletions(-)
diff mbox series

Patch

diff --git a/configure b/configure
index bdfd731602..2900acc687 100755
--- a/configure
+++ b/configure
@@ -2323,6 +2323,7 @@  HAVE_LIST="
     $THREADS_LIST
     $TOOLCHAIN_FEATURES
     $TYPES_LIST
+    libdrm_getfb2
     makeinfo
     makeinfo_html
     opencl_d3d11
@@ -6631,6 +6632,9 @@  test_cpp <<EOF && enable uwp && d3d11va_extralibs="-ldxgi -ld3d11"
 #endif
 EOF
 
+enabled libdrm &&
+    check_pkg_config libdrm_getfb2 libdrm "xf86drmMode.h" drmModeGetFB2
+
 enabled vaapi &&
     check_pkg_config vaapi "libva >= 0.35.0" "va/va.h" vaInitialize
 
diff --git a/libavdevice/kmsgrab.c b/libavdevice/kmsgrab.c
index 47ba15ca07..3e89c3f445 100644
--- a/libavdevice/kmsgrab.c
+++ b/libavdevice/kmsgrab.c
@@ -27,6 +27,11 @@ 
 #include <xf86drm.h>
 #include <xf86drmMode.h>
 
+// Required for compatibility when building against libdrm < 2.4.83.
+#ifndef DRM_FORMAT_MOD_INVALID
+#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
+#endif
+
 #include "libavutil/hwcontext.h"
 #include "libavutil/hwcontext_drm.h"
 #include "libavutil/internal.h"
@@ -45,6 +50,7 @@  typedef struct KMSGrabContext {
     AVBufferRef *device_ref;
     AVHWDeviceContext *device;
     AVDRMDeviceContext *hwctx;
+    int fb2_available;
 
     AVBufferRef *frames_ref;
     AVHWFramesContext *frames;
@@ -68,8 +74,10 @@  typedef struct KMSGrabContext {
 static void kmsgrab_free_desc(void *opaque, uint8_t *data)
 {
     AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*)data;
+    int i;
 
-    close(desc->objects[0].fd);
+    for (i = 0; i < desc->nb_objects; i++)
+        close(desc->objects[i].fd);
 
     av_free(desc);
 }
@@ -142,6 +150,114 @@  fail:
     return err;
 }
 
+#if HAVE_LIBDRM_GETFB2
+static int kmsgrab_get_fb2(AVFormatContext *avctx,
+                           drmModePlane *plane,
+                           AVDRMFrameDescriptor *desc)
+{
+    KMSGrabContext *ctx = avctx->priv_data;
+    drmModeFB2 *fb;
+    int err, i, nb_objects;
+
+    fb = drmModeGetFB2(ctx->hwctx->fd, plane->fb_id);
+    if (!fb) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to get framebuffer "
+               "%"PRIu32".\n", plane->fb_id);
+        return AVERROR(EIO);
+    }
+    if (fb->pixel_format != ctx->drm_format) {
+        av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer "
+               "format changed: now %"PRIx32".\n",
+               ctx->plane_id, fb->pixel_format);
+        err = AVERROR(EIO);
+        goto fail;
+    }
+    if (fb->modifier != ctx->drm_format_modifier) {
+        av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer "
+               "format modifier changed: now %"PRIx64".\n",
+               ctx->plane_id, fb->modifier);
+        err = AVERROR(EIO);
+        goto fail;
+    }
+    if (fb->width != ctx->width || fb->height != ctx->height) {
+        av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer "
+               "dimensions changed: now %"PRIu32"x%"PRIu32".\n",
+               ctx->plane_id, fb->width, fb->height);
+        err = AVERROR(EIO);
+        goto fail;
+    }
+    if (!fb->handles[0]) {
+        av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer.\n");
+        err = AVERROR(EIO);
+        goto fail;
+    }
+
+    *desc = (AVDRMFrameDescriptor) {
+        .nb_layers = 1,
+        .layers[0] = {
+            .format = ctx->drm_format,
+        },
+    };
+
+    nb_objects = 0;
+    for (i = 0; i < 4 && fb->handles[i]; i++) {
+        size_t size;
+        int dup = 0, j, obj;
+
+        size = fb->offsets[i] + fb->height * fb->pitches[i];
+
+        for (j = 0; j < i; j++) {
+            if (fb->handles[i] == fb->handles[j]) {
+                dup = 1;
+                break;
+            }
+        }
+        if (dup) {
+            obj = desc->layers[0].planes[j].object_index;
+
+            if (desc->objects[j].size < size)
+                desc->objects[j].size = size;
+
+            desc->layers[0].planes[i] = (AVDRMPlaneDescriptor) {
+                .object_index = obj,
+                .offset       = fb->offsets[i],
+                .pitch        = fb->pitches[i],
+            };
+
+        } else {
+            int fd;
+            err = drmPrimeHandleToFD(ctx->hwctx->fd, fb->handles[i],
+                                     O_RDONLY, &fd);
+            if (err < 0) {
+                err = AVERROR(errno);
+                av_log(avctx, AV_LOG_ERROR, "Failed to get PRIME fd from "
+                       "framebuffer handle: %s.\n", strerror(errno));
+                goto fail;
+            }
+
+            obj = nb_objects++;
+            desc->objects[obj] = (AVDRMObjectDescriptor) {
+                .fd              = fd,
+                .size            = size,
+                .format_modifier = fb->modifier,
+            };
+            desc->layers[0].planes[i] = (AVDRMPlaneDescriptor) {
+                .object_index = obj,
+                .offset       = fb->offsets[i],
+                .pitch        = fb->pitches[i],
+            };
+        }
+    }
+    desc->nb_objects = nb_objects;
+    desc->layers[0].nb_planes = i;
+
+    err = 0;
+fail:
+    drmModeFreeFB2(fb);
+    return err;
+}
+#endif
+
 static int kmsgrab_read_packet(AVFormatContext *avctx, AVPacket *pkt)
 {
     KMSGrabContext *ctx = avctx->priv_data;
@@ -184,7 +300,12 @@  static int kmsgrab_read_packet(AVFormatContext *avctx, AVPacket *pkt)
         goto fail;
     }
 
-    err = kmsgrab_get_fb(avctx, plane, desc);
+#if HAVE_LIBDRM_GETFB2
+    if (ctx->fb2_available)
+        err = kmsgrab_get_fb2(avctx, plane, desc);
+    else
+#endif
+        err = kmsgrab_get_fb(avctx, plane, desc);
     if (err < 0)
         goto fail;
 
@@ -278,6 +399,9 @@  static av_cold int kmsgrab_read_header(AVFormatContext *avctx)
     drmModePlaneRes *plane_res = NULL;
     drmModePlane *plane = NULL;
     drmModeFB *fb = NULL;
+#if HAVE_LIBDRM_GETFB2
+    drmModeFB2 *fb2 = NULL;
+#endif
     AVStream *stream;
     int err, i;
 
@@ -379,28 +503,78 @@  static av_cold int kmsgrab_read_header(AVFormatContext *avctx)
 
     ctx->plane_id = plane->plane_id;
 
-    fb = drmModeGetFB(ctx->hwctx->fd, plane->fb_id);
-    if (!fb) {
-        err = errno;
+#if HAVE_LIBDRM_GETFB2
+    fb2 = drmModeGetFB2(ctx->hwctx->fd, plane->fb_id);
+    if (!fb2 && errno == ENOSYS) {
+        av_log(avctx, AV_LOG_INFO, "GETFB2 not supported, "
+               "will try to use GETFB instead.\n");
+    } else if (!fb2) {
+        err = AVERROR(err);
         av_log(avctx, AV_LOG_ERROR, "Failed to get "
                "framebuffer %"PRIu32": %s.\n",
-               plane->fb_id, strerror(err));
-        err = AVERROR(err);
+               plane->fb_id, strerror(errno));
         goto fail;
+    } else {
+        av_log(avctx, AV_LOG_INFO, "Template framebuffer is "
+               "%"PRIu32": %"PRIu32"x%"PRIu32" "
+               "format %"PRIx32" modifier %"PRIx64" flags %"PRIx32".\n",
+               fb2->fb_id, fb2->width, fb2->height,
+               fb2->pixel_format, fb2->modifier, fb2->flags);
+
+        ctx->width  = fb2->width;
+        ctx->height = fb2->height;
+
+        if (!fb2->handles[0]) {
+            av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer: "
+                   "maybe you need some additional capabilities?\n");
+            err = AVERROR(EINVAL);
+            goto fail;
+        }
+        if (ctx->drm_format != fb2->pixel_format) {
+            av_log(avctx, AV_LOG_ERROR, "Framebuffer pixel format "
+                   "%"PRIx32" does not match expected format.\n",
+                   fb2->pixel_format);
+            err = AVERROR(EINVAL);
+            goto fail;
+        }
+        if (ctx->drm_format_modifier != DRM_FORMAT_MOD_INVALID &&
+            ctx->drm_format_modifier != fb2->modifier) {
+            av_log(avctx, AV_LOG_ERROR, "Framebuffer format modifier "
+                   "%"PRIx64" does not match expected modifier.\n",
+                   fb2->modifier);
+            err = AVERROR(EINVAL);
+            goto fail;
+        } else {
+            ctx->drm_format_modifier = fb2->modifier;
+        }
+        ctx->fb2_available = 1;
     }
+#endif
 
-    av_log(avctx, AV_LOG_INFO, "Template framebuffer is %"PRIu32": "
-           "%"PRIu32"x%"PRIu32" %"PRIu32"bpp %"PRIu32"b depth.\n",
-           fb->fb_id, fb->width, fb->height, fb->bpp, fb->depth);
+    if (!ctx->fb2_available) {
+        fb = drmModeGetFB(ctx->hwctx->fd, plane->fb_id);
+        if (!fb) {
+            err = errno;
+            av_log(avctx, AV_LOG_ERROR, "Failed to get "
+                   "framebuffer %"PRIu32": %s.\n",
+                   plane->fb_id, strerror(err));
+            err = AVERROR(err);
+            goto fail;
+        }
 
-    ctx->width  = fb->width;
-    ctx->height = fb->height;
+        av_log(avctx, AV_LOG_INFO, "Template framebuffer is %"PRIu32": "
+               "%"PRIu32"x%"PRIu32" %"PRIu32"bpp %"PRIu32"b depth.\n",
+               fb->fb_id, fb->width, fb->height, fb->bpp, fb->depth);
 
-    if (!fb->handle) {
-        av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer: "
-               "maybe you need some additional capabilities?\n");
-        err = AVERROR(EINVAL);
-        goto fail;
+        ctx->width  = fb->width;
+        ctx->height = fb->height;
+
+        if (!fb->handle) {
+            av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer: "
+                   "maybe you need some additional capabilities?\n");
+            err = AVERROR(EINVAL);
+            goto fail;
+        }
     }
 
     stream = avformat_new_stream(avctx, NULL);
@@ -411,8 +585,8 @@  static av_cold int kmsgrab_read_header(AVFormatContext *avctx)
 
     stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
     stream->codecpar->codec_id   = AV_CODEC_ID_WRAPPED_AVFRAME;
-    stream->codecpar->width      = fb->width;
-    stream->codecpar->height     = fb->height;
+    stream->codecpar->width      = ctx->width;
+    stream->codecpar->height     = ctx->height;
     stream->codecpar->format     = AV_PIX_FMT_DRM_PRIME;
 
     avpriv_set_pts_info(stream, 64, 1, 1000000);
@@ -426,8 +600,8 @@  static av_cold int kmsgrab_read_header(AVFormatContext *avctx)
 
     ctx->frames->format    = AV_PIX_FMT_DRM_PRIME;
     ctx->frames->sw_format = ctx->format,
-    ctx->frames->width     = fb->width;
-    ctx->frames->height    = fb->height;
+    ctx->frames->width     = ctx->width;
+    ctx->frames->height    = ctx->height;
 
     err = av_hwframe_ctx_init(ctx->frames_ref);
     if (err < 0) {
@@ -444,6 +618,9 @@  fail:
     drmModeFreePlaneResources(plane_res);
     drmModeFreePlane(plane);
     drmModeFreeFB(fb);
+#if HAVE_LIBDRM_GETFB2
+    drmModeFreeFB2(fb2);
+#endif
     return err;
 }
 
@@ -468,7 +645,7 @@  static const AVOption options[] = {
       { .i64 = AV_PIX_FMT_BGR0 }, 0, UINT32_MAX, FLAGS },
     { "format_modifier", "DRM format modifier for framebuffer",
       OFFSET(drm_format_modifier), AV_OPT_TYPE_INT64,
-      { .i64 = DRM_FORMAT_MOD_NONE }, 0, INT64_MAX, FLAGS },
+      { .i64 = DRM_FORMAT_MOD_INVALID }, 0, INT64_MAX, FLAGS },
     { "crtc_id", "CRTC ID to define capture source",
       OFFSET(source_crtc), AV_OPT_TYPE_INT64,
       { .i64 = 0 }, 0, UINT32_MAX, FLAGS },