mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-30 19:32:13 +00:00
790f793844
There are lots of files that don't need it: The number of object files that actually need it went down from 2011 to 884 here. Keep it for external users in order to not cause breakages. Also improve the other headers a bit while just at it. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
723 lines
22 KiB
C
723 lines
22 KiB
C
/*
|
|
* 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 <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <drm.h>
|
|
#include <drm_fourcc.h>
|
|
#include <drm_mode.h>
|
|
#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"
|
|
#include "libavutil/mathematics.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/pixfmt.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/time.h"
|
|
|
|
#include "libavformat/avformat.h"
|
|
#include "libavformat/demux.h"
|
|
#include "libavformat/internal.h"
|
|
|
|
typedef struct KMSGrabContext {
|
|
const AVClass *class;
|
|
|
|
AVBufferRef *device_ref;
|
|
AVHWDeviceContext *device;
|
|
AVDRMDeviceContext *hwctx;
|
|
int fb2_available;
|
|
|
|
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;
|
|
int i;
|
|
|
|
for (i = 0; i < desc->nb_objects; i++)
|
|
close(desc->objects[i].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_get_fb(AVFormatContext *avctx,
|
|
drmModePlane *plane,
|
|
AVDRMFrameDescriptor *desc)
|
|
{
|
|
KMSGrabContext *ctx = avctx->priv_data;
|
|
drmModeFB *fb = NULL;
|
|
int err, fd;
|
|
|
|
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;
|
|
}
|
|
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->handle) {
|
|
av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer.\n");
|
|
err = AVERROR(EIO);
|
|
goto fail;
|
|
}
|
|
|
|
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(err));
|
|
err = AVERROR(err);
|
|
goto fail;
|
|
}
|
|
|
|
*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,
|
|
},
|
|
},
|
|
};
|
|
|
|
err = 0;
|
|
fail:
|
|
drmModeFreeFB(fb);
|
|
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;
|
|
uint64_t modifier = ctx->drm_format_modifier;
|
|
|
|
fb = drmModeGetFB2(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));
|
|
return AVERROR(err);
|
|
}
|
|
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->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;
|
|
}
|
|
|
|
if (fb->flags & DRM_MODE_FB_MODIFIERS)
|
|
modifier = fb->modifier;
|
|
|
|
*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 = errno;
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get PRIME fd from "
|
|
"framebuffer handle: %s.\n", strerror(err));
|
|
err = AVERROR(err);
|
|
goto fail;
|
|
}
|
|
|
|
obj = nb_objects++;
|
|
desc->objects[obj] = (AVDRMObjectDescriptor) {
|
|
.fd = fd,
|
|
.size = size,
|
|
.format_modifier = 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;
|
|
drmModePlane *plane = NULL;
|
|
AVDRMFrameDescriptor *desc = NULL;
|
|
AVFrame *frame = NULL;
|
|
int64_t now;
|
|
int err;
|
|
|
|
now = av_gettime_relative();
|
|
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_relative();
|
|
}
|
|
}
|
|
ctx->frame_last = now;
|
|
now = av_gettime();
|
|
|
|
plane = drmModeGetPlane(ctx->hwctx->fd, ctx->plane_id);
|
|
if (!plane) {
|
|
err = errno;
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get plane "
|
|
"%"PRIu32": %s.\n", ctx->plane_id, strerror(err));
|
|
err = AVERROR(err);
|
|
goto fail;
|
|
}
|
|
if (!plane->fb_id) {
|
|
av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" no longer has "
|
|
"an associated framebuffer.\n", ctx->plane_id);
|
|
err = AVERROR(EIO);
|
|
goto fail;
|
|
}
|
|
|
|
desc = av_mallocz(sizeof(*desc));
|
|
if (!desc) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
#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;
|
|
|
|
frame = av_frame_alloc();
|
|
if (!frame) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
frame->hw_frames_ctx = av_buffer_ref(ctx->frames_ref);
|
|
if (!frame->hw_frames_ctx) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
frame->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc),
|
|
&kmsgrab_free_desc, avctx, 0);
|
|
if (!frame->buf[0]) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
frame->data[0] = (uint8_t*)desc;
|
|
frame->format = AV_PIX_FMT_DRM_PRIME;
|
|
frame->width = ctx->width;
|
|
frame->height = ctx->height;
|
|
|
|
drmModeFreePlane(plane);
|
|
plane = NULL;
|
|
desc = NULL;
|
|
|
|
pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame),
|
|
&kmsgrab_free_frame, avctx, 0);
|
|
if (!pkt->buf) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
pkt->data = (uint8_t*)frame;
|
|
pkt->size = sizeof(*frame);
|
|
pkt->pts = now;
|
|
pkt->flags |= AV_PKT_FLAG_TRUSTED;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
drmModeFreePlane(plane);
|
|
av_freep(&desc);
|
|
av_frame_free(&frame);
|
|
return err;
|
|
}
|
|
|
|
static const struct {
|
|
enum AVPixelFormat pixfmt;
|
|
uint32_t drm_format;
|
|
} kmsgrab_formats[] = {
|
|
// Monochrome.
|
|
#ifdef DRM_FORMAT_R8
|
|
{ AV_PIX_FMT_GRAY8, DRM_FORMAT_R8 },
|
|
#endif
|
|
#ifdef DRM_FORMAT_R16
|
|
{ AV_PIX_FMT_GRAY16LE, DRM_FORMAT_R16 },
|
|
{ AV_PIX_FMT_GRAY16BE, DRM_FORMAT_R16 | DRM_FORMAT_BIG_ENDIAN },
|
|
#endif
|
|
// <8-bit RGB.
|
|
{ AV_PIX_FMT_BGR8, DRM_FORMAT_BGR233 },
|
|
{ AV_PIX_FMT_RGB555LE, DRM_FORMAT_XRGB1555 },
|
|
{ AV_PIX_FMT_RGB555BE, DRM_FORMAT_XRGB1555 | DRM_FORMAT_BIG_ENDIAN },
|
|
{ AV_PIX_FMT_BGR555LE, DRM_FORMAT_XBGR1555 },
|
|
{ AV_PIX_FMT_BGR555BE, DRM_FORMAT_XBGR1555 | DRM_FORMAT_BIG_ENDIAN },
|
|
{ AV_PIX_FMT_RGB565LE, DRM_FORMAT_RGB565 },
|
|
{ AV_PIX_FMT_RGB565BE, DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN },
|
|
{ AV_PIX_FMT_BGR565LE, DRM_FORMAT_BGR565 },
|
|
{ AV_PIX_FMT_BGR565BE, DRM_FORMAT_BGR565 | DRM_FORMAT_BIG_ENDIAN },
|
|
// 8-bit RGB.
|
|
{ AV_PIX_FMT_RGB24, DRM_FORMAT_RGB888 },
|
|
{ AV_PIX_FMT_BGR24, DRM_FORMAT_BGR888 },
|
|
{ AV_PIX_FMT_0RGB, DRM_FORMAT_BGRX8888 },
|
|
{ AV_PIX_FMT_0BGR, DRM_FORMAT_RGBX8888 },
|
|
{ AV_PIX_FMT_RGB0, DRM_FORMAT_XBGR8888 },
|
|
{ AV_PIX_FMT_BGR0, DRM_FORMAT_XRGB8888 },
|
|
{ AV_PIX_FMT_ARGB, DRM_FORMAT_BGRA8888 },
|
|
{ AV_PIX_FMT_ABGR, DRM_FORMAT_RGBA8888 },
|
|
{ AV_PIX_FMT_RGBA, DRM_FORMAT_ABGR8888 },
|
|
{ AV_PIX_FMT_BGRA, DRM_FORMAT_ARGB8888 },
|
|
// 10-bit RGB.
|
|
{ AV_PIX_FMT_X2RGB10LE, DRM_FORMAT_XRGB2101010 },
|
|
{ AV_PIX_FMT_X2RGB10BE, DRM_FORMAT_XRGB2101010 | DRM_FORMAT_BIG_ENDIAN },
|
|
// 8-bit YUV 4:2:0.
|
|
{ AV_PIX_FMT_NV12, DRM_FORMAT_NV12 },
|
|
// 8-bit YUV 4:2:2.
|
|
{ AV_PIX_FMT_YUYV422, DRM_FORMAT_YUYV },
|
|
{ AV_PIX_FMT_YVYU422, DRM_FORMAT_YVYU },
|
|
{ AV_PIX_FMT_UYVY422, DRM_FORMAT_UYVY },
|
|
};
|
|
|
|
static av_cold int kmsgrab_read_header(AVFormatContext *avctx)
|
|
{
|
|
KMSGrabContext *ctx = avctx->priv_data;
|
|
drmModePlaneRes *plane_res = NULL;
|
|
drmModePlane *plane = NULL;
|
|
drmModeFB *fb = NULL;
|
|
#if HAVE_LIBDRM_GETFB2
|
|
drmModeFB2 *fb2 = NULL;
|
|
#endif
|
|
AVStream *stream;
|
|
int err, i;
|
|
|
|
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) {
|
|
err = errno;
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get plane "
|
|
"resources: %s.\n", strerror(err));
|
|
err = AVERROR(err);
|
|
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;
|
|
|
|
#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 = errno;
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get "
|
|
"framebuffer %"PRIu32": %s.\n",
|
|
plane->fb_id, strerror(err));
|
|
err = AVERROR(err);
|
|
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;
|
|
}
|
|
|
|
for (i = 0; i < FF_ARRAY_ELEMS(kmsgrab_formats); i++) {
|
|
if (kmsgrab_formats[i].drm_format == fb2->pixel_format) {
|
|
if (ctx->format != AV_PIX_FMT_NONE &&
|
|
ctx->format != kmsgrab_formats[i].pixfmt) {
|
|
av_log(avctx, AV_LOG_ERROR, "Framebuffer pixel format "
|
|
"%"PRIx32" does not match expected format.\n",
|
|
fb2->pixel_format);
|
|
err = AVERROR(EINVAL);
|
|
goto fail;
|
|
}
|
|
ctx->drm_format = fb2->pixel_format;
|
|
ctx->format = kmsgrab_formats[i].pixfmt;
|
|
break;
|
|
}
|
|
}
|
|
if (i == FF_ARRAY_ELEMS(kmsgrab_formats)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Framebuffer pixel format "
|
|
"%"PRIx32" is not a known supported format.\n",
|
|
fb2->pixel_format);
|
|
err = AVERROR(EINVAL);
|
|
goto fail;
|
|
}
|
|
|
|
if (fb2->flags & DRM_MODE_FB_MODIFIERS) {
|
|
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;
|
|
}
|
|
}
|
|
av_log(avctx, AV_LOG_VERBOSE, "Format is %s, from "
|
|
"DRM format %"PRIx32" modifier %"PRIx64".\n",
|
|
av_get_pix_fmt_name(ctx->format),
|
|
ctx->drm_format, ctx->drm_format_modifier);
|
|
|
|
ctx->fb2_available = 1;
|
|
}
|
|
#endif
|
|
|
|
if (!ctx->fb2_available) {
|
|
if (ctx->format == AV_PIX_FMT_NONE) {
|
|
// Backward compatibility: assume BGR0 if no format supplied.
|
|
ctx->format = AV_PIX_FMT_BGR0;
|
|
}
|
|
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);
|
|
}
|
|
|
|
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 = ctx->width;
|
|
stream->codecpar->height = ctx->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 = ctx->width;
|
|
ctx->frames->height = ctx->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:
|
|
drmModeFreePlaneResources(plane_res);
|
|
drmModeFreePlane(plane);
|
|
drmModeFreeFB(fb);
|
|
#if HAVE_LIBDRM_GETFB2
|
|
drmModeFreeFB2(fb2);
|
|
#endif
|
|
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_NONE }, -1, INT32_MAX, FLAGS },
|
|
{ "format_modifier", "DRM format modifier for framebuffer",
|
|
OFFSET(drm_format_modifier), AV_OPT_TYPE_INT64,
|
|
{ .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 },
|
|
{ "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,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
|
|
};
|
|
|
|
const FFInputFormat ff_kmsgrab_demuxer = {
|
|
.p.name = "kmsgrab",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("KMS screen capture"),
|
|
.p.flags = AVFMT_NOFILE,
|
|
.p.priv_class = &kmsgrab_class,
|
|
.priv_data_size = sizeof(KMSGrabContext),
|
|
.read_header = &kmsgrab_read_header,
|
|
.read_packet = &kmsgrab_read_packet,
|
|
.read_close = &kmsgrab_read_close,
|
|
};
|