From patchwork Thu Sep 7 21:56:19 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Thompson X-Patchwork-Id: 5050 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.15.201 with SMTP id 70csp697292jao; Thu, 7 Sep 2017 14:57:12 -0700 (PDT) X-Received: by 10.223.169.51 with SMTP id u48mr468780wrc.270.1504821432882; Thu, 07 Sep 2017 14:57:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1504821432; cv=none; d=google.com; s=arc-20160816; b=eJiS/ELbXjkSLhCIxuiyuUgUO5f8M9J0Wpc1IiNFPId5rEBMVrCSOfAS11Ge27BkRR 3jfM0fTDIdJA+Mxg1+iQCbU159InOM9taaj5W5j1g67SHNFP0VTeoqa+NcM85gwX3HH9 GtxV6uBWTeBB2eBcKPYLMsWCcTFntH0+K8n4Jitce2CeFUy+uNxaHleE6YGP4A7tUSiw g8myCSkVR+DnDpgpi+wGHszvsBxJfJfME7vbzGfJsceDJZMQj2hCmUobGBBF5ioiSyT5 qCavbWKYyCPjNQBazWM0gvxnoyqdHtPzK/BhtTB8Qi16WGSe9MgnhQNiTCGufi59Mtl+ A5tw== 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=5FtSxmIsKgOy1ZETfqG5y2f9/1shyUC26E4Ma5i07/U=; b=U9dKKKyaaI+g/NAkLhEHAuJGblyK/S5tDsNPzeHoKExIrzE7h87tgiGD6oKbolv1AV ZyM1fGi7hg1fpma15feVPNHufC/xL1GLAMrfYU4wFXu3OPwjziz04HZ9g8SmcMQ8QHLH uZK4AIWuS/AraEfn6pAtUrBd1KvzDWXg2zi0jo27ltsd3wjmNjo/MGaYhNVJtbieJ1j5 nLfzDu2yVfw78u7syG7tvjlhr+xFL0NUoZm9klFAcHZixBsAtqK77KE4xAX6bt/zLI3S oB0Q6XF9lFpLw03Yk2LgI9rAHy6y+nVkzwPYeErbftHGPuHyPIQKKoEZJd4uiWtC4rbo 96Aw== 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=oX4ar89B; 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 w16si233009wrg.297.2017.09.07.14.57.12; Thu, 07 Sep 2017 14:57:12 -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=oX4ar89B; 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 8C5A3689F64; Fri, 8 Sep 2017 00:56:32 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr0-f169.google.com (mail-wr0-f169.google.com [209.85.128.169]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 8626D689F3A for ; Fri, 8 Sep 2017 00:56:25 +0300 (EEST) Received: by mail-wr0-f169.google.com with SMTP id o42so1634711wrb.3 for ; Thu, 07 Sep 2017 14:56:29 -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=8Glcbn4Du6Wew5S5nIvaq0IgbSWqg5csf0y9Qx80w+E=; b=oX4ar89BVAEOiOI+mjofQGRSftw+n/l7LKRUd+an5WcGBpxC8OLLn4T8gi6wMreGVG xndouNQThz87hqWli1saWkPRUu+ME99f86YL4OOpsG+W5K8NzhG3pu4iSQiIbOrIkOxi pSwGDNg/Toth6EStocau34UBoXSSqdluxk3ucPQmLWh39GzZIRAeXPQOS7tSV2FjmD10 o7ANsr7dYa0sbG1bDE/aze6DrEKotxd+rdalCePAC6Ak6GocgNrDLBdV/X9p2M4R3/8K ajarMt0lZujlaGTSAU7JQLS8m+lSNoPGCO5sQjAAaG1b+WvkGPY3B2YMEuTM3w7BsRRk aBNQ== 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=8Glcbn4Du6Wew5S5nIvaq0IgbSWqg5csf0y9Qx80w+E=; b=ddxPn6BNlnsyBRDhy7HQHaZNJGNWdOOxTMnqzLvUc6XbIV2u8PoNFX1fdaJzVHqTyv 07Mo2BLOAcQZtazmpX2Zdoqa9P/ESeuHJqFLaECLkfKgNSz+gHPZjsQG6QzB3jgpjt6s 7c7aYSakbkUphkSEeRPXetLTutVltKPFQVtX1GiTV4ZmliBktXHIf173q5wBnd/qbzq6 y0QIK+dETSAkR1CioUkPuqDp2wSiNyHRlFN9JpvCzqoC6AY03M9YVkRBbuDgirV4+saJ +kkREqM+3bc8qM10q/QTNXyGWRiuDN7FoORaZViiMzwYhjAmjlt18uQ+c3aAkjWOG9T1 mqtA== X-Gm-Message-State: AHPjjUiEdRqnpp8lkM4IB0T5BB8kvBWEshOj9Sj9aozm/D+PAJrDp/xm aEdcqynZTX3SQjQvc/g= X-Google-Smtp-Source: ADKCNb7fxpfEQTlmaNy+KO938rda89Bty2iRgk1q5C9uAlv5OVLOCMVZoT1UbC8sk59oZnVgor5Llg== X-Received: by 10.223.128.33 with SMTP id 30mr473524wrk.9.1504821388765; Thu, 07 Sep 2017 14:56:28 -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 z51sm577870wrb.22.2017.09.07.14.56.27 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 07 Sep 2017 14:56:27 -0700 (PDT) From: Mark Thompson To: ffmpeg-devel@ffmpeg.org Date: Thu, 7 Sep 2017 22:56:19 +0100 Message-Id: <20170907215619.3182-5-sw@jkqxz.net> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170907215619.3182-1-sw@jkqxz.net> References: <20170907215619.3182-1-sw@jkqxz.net> Subject: [FFmpeg-devel] [PATCH 5/5] 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" --- Now sets the trusted packet flag; otherwise unchanged. configure | 1 + libavdevice/Makefile | 1 + libavdevice/alldevices.c | 1 + libavdevice/kmsgrab.c | 455 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 458 insertions(+) create mode 100644 libavdevice/kmsgrab.c diff --git a/configure b/configure index 6581c53c1a..76a7591ceb 100755 --- a/configure +++ b/configure @@ -3040,6 +3040,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 2a27d20388..f40f4d5298 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 38010e288a..b31558bcb5 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..d0b9cf5001 --- /dev/null +++ b/libavdevice/kmsgrab.c @@ -0,0 +1,455 @@ +/* + * 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; + pkt->flags = AV_PKT_FLAG_TRUSTED; + + 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, +};