This patch adds KMS/DRM output device for rendering a video stream
using KMS/DRM dumb buffer.
The proposed implementation is very basic, only bgr0 pixel format is
currently supported (the most common format with KMS/DRM).
To enable this output device you need to configure FFmpeg with --enable-libdrm.
Example: ffmpeg -re -i INPUT -pix_fmt bgr0 -f kmsdumb /dev/dri/card0

Nicolas Caramelli
From 939bbbf9feeda3e94f61f2ca3b3c536702e2457a Mon Sep 17 00:00:00 2001
From: Nicolas Caramelli <caramelli.de...@gmail.com>
Date: Sat, 16 Jan 2021 22:55:12 +0100
Subject: [PATCH] libavdevice: Add KMS/DRM output device

Signed-off-by: Nicolas Caramelli <caramelli.de...@gmail.com>
---
 configure                |   1 +
 doc/outdevs.texi         |  14 +++
 libavdevice/Makefile     |   1 +
 libavdevice/alldevices.c |   1 +
 libavdevice/kmsdumb.c    | 246 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 263 insertions(+)
 create mode 100644 libavdevice/kmsdumb.c

diff --git a/configure b/configure
index 900505756b..1baddbc665 100755
--- a/configure
+++ b/configure
@@ -3417,6 +3417,7 @@ gdigrab_indev_select="bmp_decoder"
 iec61883_indev_deps="libiec61883"
 jack_indev_deps="libjack"
 jack_indev_deps_any="sem_timedwait dispatch_dispatch_h"
+kmsdumb_outdev_deps="libdrm"
 kmsgrab_indev_deps="libdrm"
 lavfi_indev_deps="avfilter"
 libcdio_indev_deps="libcdio"
diff --git a/doc/outdevs.texi b/doc/outdevs.texi
index aaf247995c..b458632c40 100644
--- a/doc/outdevs.texi
+++ b/doc/outdevs.texi
@@ -266,6 +266,20 @@ ffmpeg -re -i INPUT -c:v rawvideo -pix_fmt bgra -f fbdev /dev/fb0
 
 See also @url{http://linux-fbdev.sourceforge.net/}, and fbset(1).
 
+@section kmsdumb
+
+KMS/DRM output device.
+
+This output device allows one to show a video stream using KMS/DRM dumb buffer.
+Only bgr0 pixel format is currently supported (the most common format with KMS/DRM).
+To enable this output device you need to configure FFmpeg with --enable-libdrm.
+
+@subsection Examples
+Play a file on KMS/DRM device @file{/dev/dri/card0}.
+@example
+ffmpeg -re -i INPUT -pix_fmt bgr0 -f kmsdumb /dev/dri/card0
+@end example
+
 @section opengl
 OpenGL output device.
 
diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index 0dfe47a1f4..965093dc0c 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -31,6 +31,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_KMSDUMB_OUTDEV)            += kmsdumb.o
 OBJS-$(CONFIG_KMSGRAB_INDEV)             += kmsgrab.o
 OBJS-$(CONFIG_LAVFI_INDEV)               += lavfi.o
 OBJS-$(CONFIG_OPENAL_INDEV)              += openal-dec.o
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
index 92b27a1d14..9a982b60b1 100644
--- a/libavdevice/alldevices.c
+++ b/libavdevice/alldevices.c
@@ -39,6 +39,7 @@ extern AVOutputFormat ff_fbdev_muxer;
 extern AVInputFormat  ff_gdigrab_demuxer;
 extern AVInputFormat  ff_iec61883_demuxer;
 extern AVInputFormat  ff_jack_demuxer;
+extern AVOutputFormat ff_kmsdumb_muxer;
 extern AVInputFormat  ff_kmsgrab_demuxer;
 extern AVInputFormat  ff_lavfi_demuxer;
 extern AVInputFormat  ff_openal_demuxer;
diff --git a/libavdevice/kmsdumb.c b/libavdevice/kmsdumb.c
new file mode 100644
index 0000000000..6312773ff5
--- /dev/null
+++ b/libavdevice/kmsdumb.c
@@ -0,0 +1,246 @@
+/*
+ * KMS/DRM ouput 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 <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "avdevice.h"
+
+typedef struct {
+    AVClass *class;
+    int fd;
+    uint32_t connector_id;
+    uint32_t encoder_id;
+    uint32_t crtc_id;
+    drmModeCrtc *crtc;
+    drmModeModeInfo mode_info;
+    uint32_t handle;
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t size;
+    uint32_t fb_id;
+    uint8_t *data;
+} KMSDumbContext;
+
+static av_cold int kmsdumb_write_trailer(AVFormatContext *s)
+{
+    KMSDumbContext *kmsdumb = s->priv_data;
+    struct drm_mode_destroy_dumb dreq;
+
+    if (kmsdumb->data) {
+        munmap(kmsdumb->data, kmsdumb->size);
+        kmsdumb->data = NULL;
+    }
+
+    if (kmsdumb->fb_id) {
+        drmModeRmFB(kmsdumb->fd, kmsdumb->fb_id);
+        kmsdumb->fb_id = 0;
+    }
+
+    if (kmsdumb->handle) {
+        memset(&dreq, 0, sizeof(struct drm_mode_destroy_dumb));
+        dreq.handle = kmsdumb->handle;
+        drmIoctl(kmsdumb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+        kmsdumb->handle = 0;
+    }
+
+    if (kmsdumb->crtc) {
+        drmModeSetCrtc(kmsdumb->fd,
+                       kmsdumb->crtc->crtc_id, kmsdumb->crtc->buffer_id,
+                       kmsdumb->crtc->x, kmsdumb->crtc->y,
+                       &kmsdumb->connector_id, 1, &kmsdumb->crtc->mode);
+        drmModeFreeCrtc(kmsdumb->crtc);
+        kmsdumb->crtc = NULL;
+    }
+
+    close(kmsdumb->fd);
+
+    return 0;
+}
+
+static av_cold int kmsdumb_write_header(AVFormatContext *s)
+{
+    KMSDumbContext *kmsdumb = s->priv_data;
+    drmModeRes *res;
+    drmModeConnector *connector;
+    drmModeEncoder *encoder;
+    struct drm_mode_create_dumb creq;
+    struct drm_mode_map_dumb mreq;
+    int ret;
+
+    if (s->nb_streams > 1 ||
+        s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO ||
+        s->streams[0]->codecpar->codec_id != AV_CODEC_ID_RAWVIDEO) {
+        av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (s->streams[0]->codecpar->format != AV_PIX_FMT_BGR0) {
+        av_log(s, AV_LOG_ERROR, "Unsupported pixel format."
+               " Only AV_PIX_FMT_BGR0 is currently supported.\n");
+        return AVERROR(EINVAL);
+    }
+
+    if ((kmsdumb->fd = avpriv_open(s->url, O_RDWR)) == -1) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to open DRM device '%s': %s\n", s->url, av_err2str(ret));
+        return ret;
+    }
+
+    res = drmModeGetResources(kmsdumb->fd);
+    if (!res) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to get resources: %s\n", av_err2str(ret));
+        goto fail;
+    }
+    kmsdumb->connector_id = res->connectors[0];
+    drmModeFreeResources(res);
+
+    connector = drmModeGetConnector(kmsdumb->fd, kmsdumb->connector_id);
+    if (!connector) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to get connector: %s\n", av_err2str(ret));
+        goto fail;
+    }
+    kmsdumb->encoder_id = connector->encoder_id;
+    memcpy(&kmsdumb->mode_info, &connector->modes[0], sizeof(drmModeModeInfo));
+    drmModeFreeConnector(connector);
+
+    encoder = drmModeGetEncoder(kmsdumb->fd, kmsdumb->encoder_id);
+    if (!encoder) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to get encoder: %s\n", av_err2str(ret));
+        goto fail;
+    }
+    kmsdumb->crtc_id = encoder->crtc_id;
+    drmModeFreeEncoder(encoder);
+
+    kmsdumb->crtc = drmModeGetCrtc(kmsdumb->fd, kmsdumb->crtc_id);
+    if (!kmsdumb->crtc) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to get CRTC: %s\n", av_err2str(ret));
+        goto fail;
+    }
+
+    memset(&creq, 0, sizeof(struct drm_mode_create_dumb));
+    creq.width = kmsdumb->mode_info.hdisplay;
+    creq.height = kmsdumb->mode_info.vdisplay;
+    creq.bpp = 32;
+    if (drmIoctl(kmsdumb->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to create dumb buffer: %s\n", av_err2str(ret));
+        goto fail;
+    }
+
+    kmsdumb->handle = creq.handle;
+    kmsdumb->width = creq.width;
+    kmsdumb->height = creq.height;
+    kmsdumb->pitch = creq.pitch;
+    kmsdumb->size = creq.size;
+
+    if (drmModeAddFB(kmsdumb->fd, kmsdumb->width, kmsdumb->height, 24, 32,
+                     kmsdumb->pitch, kmsdumb->handle, &kmsdumb->fb_id) == -1) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to create framebuffer object: %s\n", av_err2str(ret));
+        goto fail;
+    }
+
+    memset(&mreq, 0, sizeof(struct drm_mode_map_dumb));
+    mreq.handle = kmsdumb->handle;
+    if (drmIoctl(kmsdumb->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq) == -1) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to map dumb buffer: %s\n", av_err2str(ret));
+        goto fail;
+    }
+
+    kmsdumb->data = mmap(NULL, kmsdumb->size, PROT_WRITE, MAP_SHARED,
+                         kmsdumb->fd, mreq.offset);
+    if (kmsdumb->data == MAP_FAILED) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR,
+               "Failed to mmap dumb buffer: %s\n", av_err2str(ret));
+        goto fail;
+    }
+
+    return 0;
+  fail:
+    kmsdumb_write_trailer(s);
+    return ret;
+}
+
+static int kmsdumb_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    KMSDumbContext *kmsdumb = s->priv_data;
+    uint32_t video_width = s->streams[0]->codecpar->width;
+    uint32_t video_height = s->streams[0]->codecpar->height;
+    uint32_t disp_height = FFMIN(kmsdumb->height, video_height);
+    uint32_t bytes_to_copy = FFMIN(kmsdumb->width, video_width) * 4;
+    uint32_t video_pitch = video_width * 4;
+    uint8_t *pin = pkt->data;
+    uint8_t *pout = kmsdumb->data;
+    int i, ret;
+
+    for (i = 0; i < disp_height; i++) {
+        memcpy(pout, pin, bytes_to_copy);
+        pout += kmsdumb->pitch;
+        pin += video_pitch;
+    }
+
+    if (drmModeSetCrtc(kmsdumb->fd, kmsdumb->crtc_id, kmsdumb->fb_id, 0, 0,
+                       &kmsdumb->connector_id, 1, &kmsdumb->mode_info) == -1) {
+        ret = AVERROR(errno);
+        av_log(s, AV_LOG_ERROR, "Failed to set CRTC: %s\n", av_err2str(ret));
+        return ret;
+    }
+
+    return 0;
+}
+
+static const AVClass kmsdumb_class = {
+    .class_name = "kmsdumb outdev",
+    .item_name  = av_default_item_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
+};
+
+AVOutputFormat ff_kmsdumb_muxer = {
+    .name           = "kmsdumb",
+    .long_name      = NULL_IF_CONFIG_SMALL("KMS/DRM dumb buffer"),
+    .priv_data_size = sizeof(KMSDumbContext),
+    .audio_codec    = AV_CODEC_ID_NONE,
+    .video_codec    = AV_CODEC_ID_RAWVIDEO,
+    .write_header   = kmsdumb_write_header,
+    .write_packet   = kmsdumb_write_packet,
+    .write_trailer  = kmsdumb_write_trailer,
+    .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
+    .priv_class     = &kmsdumb_class,
+};
-- 
2.12.5

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

Reply via email to