From patchwork Sat Jan 16 22:12:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Caramelli X-Patchwork-Id: 24981 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id B390544A1A7 for ; Sun, 17 Jan 2021 00:42:15 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 81DD36806BC; Sun, 17 Jan 2021 00:42:15 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lj1-f177.google.com (mail-lj1-f177.google.com [209.85.208.177]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7FB5C68047E for ; Sun, 17 Jan 2021 00:42:09 +0200 (EET) Received: by mail-lj1-f177.google.com with SMTP id m10so14346262lji.1 for ; Sat, 16 Jan 2021 14:42:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=ynTFU4rWZ3Fo375q257fSc1fjDqtzh68eLMQGLpGNw4=; b=cvwqS1nwFTHdY/LbARvPe75k68j677pwxzxCnDlMlULQD+OsCuoiqJ2BQsAOsOny1x jZUD3iP17wVUo2BQs96fUUQdKxWuAMpWn1G+IWgq5PYk0tbqMPJh2ROFomKD/LS12w2B lVQsS0pvR+56dCvw34ukmZeQ4cbcLyIKgzJ5gGxRkAIa+1SBpkthFRfmX5S7F+TvzRsa 4iKLhZ5v0BP3o3tc+j+laMO/P5fIO2QjpyP6S7pcC779fXC7dD+vTTeQFLJyImGZUxBA pes309a0xPoJZ5OPllcTfk4itqb/cQgiujdOgj0AGDtiQQQO83GfBVbc+O3krkyhRC27 0Ntw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=ynTFU4rWZ3Fo375q257fSc1fjDqtzh68eLMQGLpGNw4=; b=NHxAf/C2QqDIcAP9uSFcFBu8/5ZAUPuNiuY3xVctthKtqBKmlbJ4vM6l8gdp0b+MzQ IVJ1RJzPKAF1qsBomxG/EDeb/m5EXIvx9uZ7FsTMMptLcmm4impTOLyfzZCrvJsxxaWL eGqssXmCEPHJRldROQFfIStJxYCTEiNU1xxmCrky+XiE5z8mzn8yS6VONf0eXeVT/UhD p4XTOMUG/IU0VzrT/iDQcKD+IhrMj+/UrnKxDBICbyyq7CmY6GOOIJDWrgGERZZ57Sxl Dd+iz2FpcGNmgXjpQjyV76TaPuKSz+iXmobu4MZ8PkjThzvFRSmHCsPaYn9OEDlrJOWb mqAg== X-Gm-Message-State: AOAM532/3OROwY7oZI4zfaAjy+JSGneq1Xf0sKtbwO8pkfPfrYHdobdr ULcf29ruiyWMDVQxnUKL2pWOUX4faGi4WeXHOEh1YWtoMjI= X-Google-Smtp-Source: ABdhPJy7UW3NrUqmPGXWffHRd1gU/hcuUPHJtPF+76GRufvuaeO1t5vySrdZmz9Kcss4IJuYWgtNnA2EWbCCjutcaR0= X-Received: by 2002:a19:2254:: with SMTP id i81mr8797787lfi.422.1610835062440; Sat, 16 Jan 2021 14:11:02 -0800 (PST) MIME-Version: 1.0 From: Nicolas Caramelli Date: Sat, 16 Jan 2021 23:12:57 +0100 Message-ID: To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH] libavdevice: Add KMS/DRM output device 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 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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 Subject: [PATCH] libavdevice: Add KMS/DRM output device Signed-off-by: Nicolas Caramelli --- 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 +#include +#include +#include +#include + +#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, +};