From patchwork Sat Sep 2 22:21:35 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Thompson X-Patchwork-Id: 4959 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.15.201 with SMTP id 70csp1819081jao; Sat, 2 Sep 2017 15:22:09 -0700 (PDT) X-Received: by 10.28.152.78 with SMTP id a75mr1287310wme.143.1504390929256; Sat, 02 Sep 2017 15:22:09 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1504390929; cv=none; d=google.com; s=arc-20160816; b=YOpw754OehDxnaS4Eh0wdyNmGyJziaCXhWfjOjZ0TGNZweLkjWluHwX8IUNA5ZtcvM BAD5XFdyyMJe4IWx3+F7XsKEGLUedLvvdIdJJe4st0ABRQPU3WwO0JK05VeVw+rHYUnE oBveXZHHfvR/eGv5s4CFKxkyr2Bidd8jTyBACJAqiL7IjT65I8avXMVVZlkhP9rQyEqs w3GKjWSRUrKVD8s0b010ySdZYxwFRjYYjsSvbmPllcW81RnTjTQdhg0j5qjgj2D8ipXB 3T5Hg/mFj2a6WqkTkAJoKo391RwfPmNN0sQkpNBj6voHLGEFCgXarmWJjWVqjsq/tXyI 3lUw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:references:in-reply-to:message-id:date :to:from:dkim-signature:delivered-to:arc-authentication-results; bh=n8rpIBvGJ68swwW6TxfzvGGJXL+srxtp0c1hixarPUs=; b=ZwxtFmbUYe5LWH1SgHYGwWekhiPD86S2m2+F5hNJQFyPpmUKUSE0ncxJo7yqshSjOC 7krxv+xHTasmlqGLWH7mRdAwJrbO7x7gE+0cRx/C1cixKa2gwAcOw9mboXUqNuYDUNLB DbIciVrPyJn9cMAwN8qoVxNkqpEfiYi21R0GdVHrsQxitth8f129rEHKL7aHPRkZRb7E umVsV/KXfAUPRyDTDSw8K8T9+PffIZ+hSOwXhc6Kh968Fat8fOga0uvv8V27TboKhD97 KZu3WqwdcVuiuv8y/q+fTI8JLGgbRvBa7seNLcOUI4k3jGy4IbHzMYIl0Okojayy9aY+ QiUA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@jkqxz-net.20150623.gappssmtp.com header.s=20150623 header.b=Q6ShAPND; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id p101si2493164wrb.392.2017.09.02.15.22.08; Sat, 02 Sep 2017 15:22:09 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@jkqxz-net.20150623.gappssmtp.com header.s=20150623 header.b=Q6ShAPND; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 59736689F12; Sun, 3 Sep 2017 01:21:50 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr0-f170.google.com (mail-wr0-f170.google.com [209.85.128.170]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5F35A689EC6 for ; Sun, 3 Sep 2017 01:21:44 +0300 (EEST) Received: by mail-wr0-f170.google.com with SMTP id p14so7260211wrg.3 for ; Sat, 02 Sep 2017 15:21:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jkqxz-net.20150623.gappssmtp.com; s=20150623; h=from:to:subject:date:message-id:in-reply-to:references; bh=9n45eA2IR/en9ViuFc2Cq3Iyo9NyT9vQKQje2CIJpjQ=; b=Q6ShAPNDNTRFUTtYav34UZOyBC+Rqri6hn3401zdfA2YP10IQb3T7XmGLQE4cfcBkX dPlkZUfTfWawIt9FyjO8pxKzq/1pSAFHlHASDJa0kZxyPxxoIdaM/5TwG9Bp5UhvpcWr jagMzWEiSU3XUdKBrS+tXe+8WQFLqY2kdSaDksKIyzA7HB6wIlcNaevKhDKowxmc2LzI XMB6gwFKIHucpX8Tezpmn47F5+eOV4fmjdtRnLw1rTsxRUVAaNDcMSdNPt3/zlGRX1n5 ubAmUe9n1nrFhF6aMyk6eUfC0TXYFIHZhUwIoskxLgCQChwz/Vk4EhmMGjuCafNF9r5h oiPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references; bh=9n45eA2IR/en9ViuFc2Cq3Iyo9NyT9vQKQje2CIJpjQ=; b=r6B+8Awos7691e13WR+QpS9hb4ev5nucX4YEF5XxMXBNhZLB9HksZd3G+x/qNtNFTR l/5H0j1Xp4Dv0t1mr+rd4xoTOQJ0VIcl6egCPMrP6jhRvSX1zu9U5AhbbDTnTmXTT9eJ 9mVwWdRCLMpk1Nm8z5bfkE7DlzWxSNLUbvSV5FMUsKOfdzMMRDq3nCrNvqf/BMYx6RjA Pdn/7UVQfxbnVHDBzwoNay69Kxj6R57AQrBQcJ2cit1G2ttiH0F5ICoho5XvW9MB3QMo mdojU9260rES2VFtCa5kVjfhiz1a3Hul+a/QYs6U66RJsGev/nk38BMKXYbkgO29a6tl w77A== X-Gm-Message-State: AHPjjUjcNNdzVAooUJKheOKgcg6csHJZppDvWifwYxt/HPuE9hsCET6E hzVxX46olZSjcY9Oin0= X-Google-Smtp-Source: ADKCNb5LWKmARY1OhpiBEwGaEELae+hc5f+sMVWV+Kt7NQk9uigeTF6imLa2yfqYivOg30aRNLbyVQ== X-Received: by 10.223.135.247 with SMTP id c52mr3055292wrc.27.1504390905476; Sat, 02 Sep 2017 15:21:45 -0700 (PDT) Received: from rywe.jkqxz.net (cpc91242-cmbg18-2-0-cust650.5-4.cable.virginm.net. [82.8.130.139]) by smtp.gmail.com with ESMTPSA id w10sm2531582wra.47.2017.09.02.15.21.44 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 02 Sep 2017 15:21:44 -0700 (PDT) From: Mark Thompson To: ffmpeg-devel@ffmpeg.org Date: Sat, 2 Sep 2017 23:21:35 +0100 Message-Id: <20170902222135.17862-4-sw@jkqxz.net> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170902222135.17862-1-sw@jkqxz.net> References: <20170902222135.17862-1-sw@jkqxz.net> Subject: [FFmpeg-devel] [PATCH 4/4] lavd: Add KMS frame grabber X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" --- The idea here is to be able to capture the screen with little CPU interaction by encoding KMS scanout planes. Given X running on KMS, it can capture both X and virtual consoles, including transitions between them while recording. Unfortunately, it's rather inconvenient to use: the KMS API doesn't quite support the necessary set of things to work all the parameters out cleanly - framebuffer parameters (including the pixel format) need to be supplied by the user. For finding plane/CRTC IDs, the libdrm modetest program is useful (luckily the IDs are all global). Formats may have to be found by guessing until the output looks right. It also requires either DRM master or CAP_SYS_ADMIN to run (needed to access the framebuffer attached to the scanout plane). On Intel gen9 / Linux 4.12, I can capture and encode with: ./ffmpeg_g -y -framerate 60 -format bgr0 -crtc_id 26 -f kmsgrab -i - -init_hw_device vaapi=v:/dev/dri/renderD128 -filter_hw_device v -vf 'hwmap,scale_vaapi=w=1920:h=1080:format=nv12' -c:v h264_vaapi -frames:v 1000 out.mp4 There is some tiling going on which should need the format modifier to be set, but the Intel VAAPI driver is able to find it by magic so it isn't required for this example. On the other hand, mapping to the CPU does not give a sensible result because of that. Given how tricky it is to use, I'm not really sure whether it is actually useful for this to be in ffmpeg. On the other hand, it does use a lot less CPU than xcbgrab! Thoughts welcome. configure | 1 + libavdevice/Makefile | 1 + libavdevice/alldevices.c | 1 + libavdevice/kmsgrab.c | 454 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 libavdevice/kmsgrab.c diff --git a/configure b/configure index 13060549d1..9b35032381 100755 --- a/configure +++ b/configure @@ -3035,6 +3035,7 @@ gdigrab_indev_select="bmp_decoder" iec61883_indev_deps="libiec61883" jack_indev_deps="jack" jack_indev_deps_any="sem_timedwait dispatch_dispatch_h" +kmsgrab_indev_deps="libdrm" lavfi_indev_deps="avfilter" libcdio_indev_deps="libcdio" libdc1394_indev_deps="libdc1394" diff --git a/libavdevice/Makefile b/libavdevice/Makefile index cd077b292e..5fd6092306 100644 --- a/libavdevice/Makefile +++ b/libavdevice/Makefile @@ -32,6 +32,7 @@ OBJS-$(CONFIG_FBDEV_OUTDEV) += fbdev_enc.o \ OBJS-$(CONFIG_GDIGRAB_INDEV) += gdigrab.o OBJS-$(CONFIG_IEC61883_INDEV) += iec61883.o OBJS-$(CONFIG_JACK_INDEV) += jack.o timefilter.o +OBJS-$(CONFIG_KMSGRAB_INDEV) += kmsgrab.o OBJS-$(CONFIG_LAVFI_INDEV) += lavfi.o OBJS-$(CONFIG_OPENAL_INDEV) += openal-dec.o OBJS-$(CONFIG_OPENGL_OUTDEV) += opengl_enc.o diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c index 8d1cb8648f..e41806766d 100644 --- a/libavdevice/alldevices.c +++ b/libavdevice/alldevices.c @@ -53,6 +53,7 @@ static void register_all(void) REGISTER_INDEV (GDIGRAB, gdigrab); REGISTER_INDEV (IEC61883, iec61883); REGISTER_INDEV (JACK, jack); + REGISTER_INDEV (KMSGRAB, kmsgrab); REGISTER_INDEV (LAVFI, lavfi); REGISTER_INDEV (OPENAL, openal); REGISTER_OUTDEV (OPENGL, opengl); diff --git a/libavdevice/kmsgrab.c b/libavdevice/kmsgrab.c new file mode 100644 index 0000000000..0f05b49255 --- /dev/null +++ b/libavdevice/kmsgrab.c @@ -0,0 +1,454 @@ +/* + * KMS/DRM input device + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_drm.h" +#include "libavutil/internal.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/pixfmt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" + +#include "libavformat/avformat.h" +#include "libavformat/internal.h" + +typedef struct KMSGrabContext { + const AVClass *class; + + AVBufferRef *device_ref; + AVHWDeviceContext *device; + AVDRMDeviceContext *hwctx; + + AVBufferRef *frames_ref; + AVHWFramesContext *frames; + + uint32_t plane_id; + uint32_t drm_format; + unsigned int width; + unsigned int height; + + int64_t frame_delay; + int64_t frame_last; + + const char *device_path; + enum AVPixelFormat format; + int64_t drm_format_modifier; + int64_t source_plane; + int64_t source_crtc; + AVRational framerate; +} KMSGrabContext; + +static void kmsgrab_free_desc(void *opaque, uint8_t *data) +{ + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*)data; + + close(desc->objects[0].fd); + + av_free(desc); +} + +static void kmsgrab_free_frame(void *opaque, uint8_t *data) +{ + AVFrame *frame = (AVFrame*)data; + + av_frame_free(&frame); +} + +static int kmsgrab_read_packet(AVFormatContext *avctx, AVPacket *pkt) +{ + KMSGrabContext *ctx = avctx->priv_data; + drmModePlane *plane; + drmModeFB *fb; + AVDRMFrameDescriptor *desc; + AVFrame *frame; + int64_t now; + int err, fd; + + now = av_gettime(); + if (ctx->frame_last) { + int64_t delay; + while (1) { + delay = ctx->frame_last + ctx->frame_delay - now; + if (delay <= 0) + break; + av_usleep(delay); + now = av_gettime(); + } + } + ctx->frame_last = now; + + plane = drmModeGetPlane(ctx->hwctx->fd, ctx->plane_id); + if (!plane) { + av_log(avctx, AV_LOG_ERROR, "Failed to get plane " + "%"PRIu32".\n", ctx->plane_id); + return AVERROR(EIO); + } + if (!plane->fb_id) { + av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" no longer has " + "an associated framebuffer.\n", ctx->plane_id); + return AVERROR(EIO); + } + + fb = drmModeGetFB(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->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); + return AVERROR(EIO); + } + if (!fb->handle) { + av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer.\n"); + return AVERROR(EIO); + } + + err = drmPrimeHandleToFD(ctx->hwctx->fd, fb->handle, O_RDONLY, &fd); + if (err < 0) { + err = errno; + av_log(avctx, AV_LOG_ERROR, "Failed to get PRIME fd from " + "framebuffer handle: %s.\n", strerror(errno)); + return AVERROR(err); + } + + desc = av_mallocz(sizeof(*desc)); + if (!desc) + return AVERROR(ENOMEM); + + *desc = (AVDRMFrameDescriptor) { + .nb_objects = 1, + .objects[0] = { + .fd = fd, + .size = fb->height * fb->pitch, + .format_modifier = ctx->drm_format_modifier, + }, + .nb_layers = 1, + .layers[0] = { + .format = ctx->drm_format, + .nb_planes = 1, + .planes[0] = { + .object_index = 0, + .offset = 0, + .pitch = fb->pitch, + }, + }, + }; + + frame = av_frame_alloc(); + if (!frame) + return AVERROR(ENOMEM); + + frame->hw_frames_ctx = av_buffer_ref(ctx->frames_ref); + if (!frame->hw_frames_ctx) + return AVERROR(ENOMEM); + + frame->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc), + &kmsgrab_free_desc, avctx, 0); + if (!frame->buf[0]) + return AVERROR(ENOMEM); + + frame->data[0] = (uint8_t*)desc; + frame->format = AV_PIX_FMT_DRM_PRIME; + frame->width = fb->width; + frame->height = fb->height; + + drmModeFreeFB(fb); + drmModeFreePlane(plane); + + pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), + &kmsgrab_free_frame, avctx, 0); + if (!pkt->buf) + return AVERROR(ENOMEM); + + pkt->data = (uint8_t*)frame; + pkt->size = sizeof(*frame); + pkt->pts = now; + + return 0; +} + +static const struct { + enum AVPixelFormat pixfmt; + uint32_t drm_format; +} kmsgrab_formats[] = { + { AV_PIX_FMT_GRAY8, DRM_FORMAT_R8 }, + { AV_PIX_FMT_GRAY16LE, DRM_FORMAT_R16 }, + { AV_PIX_FMT_RGB24, DRM_FORMAT_RGB888 }, + { AV_PIX_FMT_BGR24, DRM_FORMAT_BGR888 }, + { AV_PIX_FMT_0RGB, DRM_FORMAT_XRGB8888 }, + { AV_PIX_FMT_0BGR, DRM_FORMAT_XBGR8888 }, + { AV_PIX_FMT_RGB0, DRM_FORMAT_RGBX8888 }, + { AV_PIX_FMT_BGR0, DRM_FORMAT_BGRX8888 }, + { AV_PIX_FMT_ARGB, DRM_FORMAT_ARGB8888 }, + { AV_PIX_FMT_ABGR, DRM_FORMAT_ABGR8888 }, + { AV_PIX_FMT_RGBA, DRM_FORMAT_RGBA8888 }, + { AV_PIX_FMT_BGRA, DRM_FORMAT_BGRA8888 }, + { AV_PIX_FMT_YUYV422, DRM_FORMAT_YUYV }, + { AV_PIX_FMT_YVYU422, DRM_FORMAT_YVYU }, + { AV_PIX_FMT_UYVY422, DRM_FORMAT_UYVY }, + { AV_PIX_FMT_NV12, DRM_FORMAT_NV12 }, + { AV_PIX_FMT_YUV420P, DRM_FORMAT_YUV420 }, + { AV_PIX_FMT_YUV422P, DRM_FORMAT_YUV422 }, + { AV_PIX_FMT_YUV444P, DRM_FORMAT_YUV444 }, +}; + +static av_cold int kmsgrab_read_header(AVFormatContext *avctx) +{ + KMSGrabContext *ctx = avctx->priv_data; + drmModePlaneRes *plane_res = NULL; + drmModePlane *plane = NULL; + drmModeFB *fb = NULL; + AVStream *stream; + int err, i; + + for (i = 0; i < FF_ARRAY_ELEMS(kmsgrab_formats); i++) { + if (kmsgrab_formats[i].pixfmt == ctx->format) { + ctx->drm_format = kmsgrab_formats[i].drm_format; + break; + } + } + if (i >= FF_ARRAY_ELEMS(kmsgrab_formats)) { + av_log(avctx, AV_LOG_ERROR, "Unsupported format %s.\n", + av_get_pix_fmt_name(ctx->format)); + return AVERROR(EINVAL); + } + + err = av_hwdevice_ctx_create(&ctx->device_ref, AV_HWDEVICE_TYPE_DRM, + ctx->device_path, NULL, 0); + if (err < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to open DRM device.\n"); + return err; + } + ctx->device = (AVHWDeviceContext*) ctx->device_ref->data; + ctx->hwctx = (AVDRMDeviceContext*)ctx->device->hwctx; + + err = drmSetClientCap(ctx->hwctx->fd, + DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (err < 0) { + av_log(avctx, AV_LOG_WARNING, "Failed to set universal planes " + "capability: primary planes will not be usable.\n"); + } + + if (ctx->source_plane > 0) { + plane = drmModeGetPlane(ctx->hwctx->fd, ctx->source_plane); + if (!plane) { + err = errno; + av_log(avctx, AV_LOG_ERROR, "Failed to get plane %"PRId64": " + "%s.\n", ctx->source_plane, strerror(err)); + err = AVERROR(err); + goto fail; + } + + if (plane->fb_id == 0) { + av_log(avctx, AV_LOG_ERROR, "Plane %"PRId64" does not have " + "an attached framebuffer.\n", ctx->source_plane); + err = AVERROR(EINVAL); + goto fail; + } + } else { + plane_res = drmModeGetPlaneResources(ctx->hwctx->fd); + if (!plane_res) { + av_log(avctx, AV_LOG_ERROR, "Failed to get plane " + "resources: %s.\n", strerror(errno)); + err = AVERROR(EINVAL); + goto fail; + } + + for (i = 0; i < plane_res->count_planes; i++) { + plane = drmModeGetPlane(ctx->hwctx->fd, + plane_res->planes[i]); + if (!plane) { + err = errno; + av_log(avctx, AV_LOG_VERBOSE, "Failed to get " + "plane %"PRIu32": %s.\n", + plane_res->planes[i], strerror(err)); + continue; + } + + av_log(avctx, AV_LOG_DEBUG, "Plane %"PRIu32": " + "CRTC %"PRIu32" FB %"PRIu32".\n", + plane->plane_id, plane->crtc_id, plane->fb_id); + + if ((ctx->source_crtc > 0 && + plane->crtc_id != ctx->source_crtc) || + plane->fb_id == 0) { + // Either not connected to the target source CRTC + // or not active. + drmModeFreePlane(plane); + plane = NULL; + continue; + } + + break; + } + + if (i == plane_res->count_planes) { + if (ctx->source_crtc > 0) { + av_log(avctx, AV_LOG_ERROR, "No usable planes found on " + "CRTC %"PRId64".\n", ctx->source_crtc); + } else { + av_log(avctx, AV_LOG_ERROR, "No usable planes found.\n"); + } + err = AVERROR(EINVAL); + goto fail; + } + + av_log(avctx, AV_LOG_INFO, "Using plane %"PRIu32" to " + "locate framebuffers.\n", plane->plane_id); + } + + ctx->plane_id = plane->plane_id; + + 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; + } + + 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); + + 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); + if (!stream) { + err = AVERROR(ENOMEM); + goto fail; + } + + 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->format = AV_PIX_FMT_DRM_PRIME; + + avpriv_set_pts_info(stream, 64, 1, 1000000); + + ctx->frames_ref = av_hwframe_ctx_alloc(ctx->device_ref); + if (!ctx->frames_ref) { + err = AVERROR(ENOMEM); + goto fail; + } + ctx->frames = (AVHWFramesContext*)ctx->frames_ref->data; + + ctx->frames->format = AV_PIX_FMT_DRM_PRIME; + ctx->frames->sw_format = ctx->format, + ctx->frames->width = fb->width; + ctx->frames->height = fb->height; + + err = av_hwframe_ctx_init(ctx->frames_ref); + if (err < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to initialise " + "hardware frames context: %d.\n", err); + goto fail; + } + + ctx->frame_delay = av_rescale_q(1, (AVRational) { ctx->framerate.den, + ctx->framerate.num }, AV_TIME_BASE_Q); + + err = 0; +fail: + if (plane_res) + drmModeFreePlaneResources(plane_res); + if (plane) + drmModeFreePlane(plane); + if (fb) + drmModeFreeFB(fb); + + return err; +} + +static av_cold int kmsgrab_read_close(AVFormatContext *avctx) +{ + KMSGrabContext *ctx = avctx->priv_data; + + av_buffer_unref(&ctx->frames_ref); + av_buffer_unref(&ctx->device_ref); + + return 0; +} + +#define OFFSET(x) offsetof(KMSGrabContext, x) +#define FLAGS AV_OPT_FLAG_DECODING_PARAM +static const AVOption options[] = { + { "device", "DRM device path", + OFFSET(device_path), AV_OPT_TYPE_STRING, + { .str = "/dev/dri/card0" }, 0, 0, FLAGS }, + { "format", "Pixel format for framebuffer", + OFFSET(format), AV_OPT_TYPE_PIXEL_FMT, + { .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 }, + { "crtc_id", "CRTC ID to define capture source", + OFFSET(source_crtc), AV_OPT_TYPE_INT64, + { .i64 = 0 }, 0, UINT32_MAX, FLAGS }, + { "plane_id", "Plane ID to define capture source", + OFFSET(source_plane), AV_OPT_TYPE_INT64, + { .i64 = 0 }, 0, UINT32_MAX, FLAGS }, + { "framerate", "Framerate to capture at", + OFFSET(framerate), AV_OPT_TYPE_RATIONAL, + { .dbl = 30.0 }, 0, 1000, FLAGS }, + { NULL }, +}; + +static const AVClass kmsgrab_class = { + .class_name = "kmsgrab indev", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_kmsgrab_demuxer = { + .name = "kmsgrab", + .long_name = NULL_IF_CONFIG_SMALL("KMS screen capture"), + .priv_data_size = sizeof(KMSGrabContext), + .read_header = &kmsgrab_read_header, + .read_packet = &kmsgrab_read_packet, + .read_close = &kmsgrab_read_close, + .flags = AVFMT_NOFILE, + .priv_class = &kmsgrab_class, +};