| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * KMS/DRM input device | ||
| 3 | * | ||
| 4 | * This file is part of FFmpeg. | ||
| 5 | * | ||
| 6 | * FFmpeg is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU Lesser General Public | ||
| 8 | * License as published by the Free Software Foundation; either | ||
| 9 | * version 2.1 of the License, or (at your option) any later version. | ||
| 10 | * | ||
| 11 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 14 | * Lesser General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU Lesser General Public | ||
| 17 | * License along with FFmpeg; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include <fcntl.h> | ||
| 22 | #include <unistd.h> | ||
| 23 | |||
| 24 | #include <drm.h> | ||
| 25 | #include <drm_fourcc.h> | ||
| 26 | #include <drm_mode.h> | ||
| 27 | #include <xf86drm.h> | ||
| 28 | #include <xf86drmMode.h> | ||
| 29 | |||
| 30 | // Required for compatibility when building against libdrm < 2.4.83. | ||
| 31 | #ifndef DRM_FORMAT_MOD_INVALID | ||
| 32 | #define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) | ||
| 33 | #endif | ||
| 34 | |||
| 35 | #include "libavutil/hwcontext.h" | ||
| 36 | #include "libavutil/hwcontext_drm.h" | ||
| 37 | #include "libavutil/internal.h" | ||
| 38 | #include "libavutil/mathematics.h" | ||
| 39 | #include "libavutil/mem.h" | ||
| 40 | #include "libavutil/opt.h" | ||
| 41 | #include "libavutil/pixfmt.h" | ||
| 42 | #include "libavutil/pixdesc.h" | ||
| 43 | #include "libavutil/time.h" | ||
| 44 | |||
| 45 | #include "libavformat/avformat.h" | ||
| 46 | #include "libavformat/demux.h" | ||
| 47 | #include "libavformat/internal.h" | ||
| 48 | |||
| 49 | typedef struct KMSGrabContext { | ||
| 50 | const AVClass *class; | ||
| 51 | |||
| 52 | AVBufferRef *device_ref; | ||
| 53 | AVHWDeviceContext *device; | ||
| 54 | AVDRMDeviceContext *hwctx; | ||
| 55 | int fb2_available; | ||
| 56 | |||
| 57 | AVBufferRef *frames_ref; | ||
| 58 | AVHWFramesContext *frames; | ||
| 59 | |||
| 60 | uint32_t plane_id; | ||
| 61 | uint32_t drm_format; | ||
| 62 | unsigned int width; | ||
| 63 | unsigned int height; | ||
| 64 | |||
| 65 | int64_t frame_delay; | ||
| 66 | int64_t frame_last; | ||
| 67 | |||
| 68 | const char *device_path; | ||
| 69 | enum AVPixelFormat format; | ||
| 70 | int64_t drm_format_modifier; | ||
| 71 | int64_t source_plane; | ||
| 72 | int64_t source_crtc; | ||
| 73 | AVRational framerate; | ||
| 74 | } KMSGrabContext; | ||
| 75 | |||
| 76 | ✗ | static void kmsgrab_free_desc(void *opaque, uint8_t *data) | |
| 77 | { | ||
| 78 | ✗ | AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*)data; | |
| 79 | int i; | ||
| 80 | |||
| 81 | ✗ | for (i = 0; i < desc->nb_objects; i++) | |
| 82 | ✗ | close(desc->objects[i].fd); | |
| 83 | |||
| 84 | ✗ | av_free(desc); | |
| 85 | ✗ | } | |
| 86 | |||
| 87 | ✗ | static void kmsgrab_free_frame(void *opaque, uint8_t *data) | |
| 88 | { | ||
| 89 | ✗ | AVFrame *frame = (AVFrame*)data; | |
| 90 | |||
| 91 | ✗ | av_frame_free(&frame); | |
| 92 | ✗ | } | |
| 93 | |||
| 94 | ✗ | static int kmsgrab_get_fb(AVFormatContext *avctx, | |
| 95 | drmModePlane *plane, | ||
| 96 | AVDRMFrameDescriptor *desc) | ||
| 97 | { | ||
| 98 | ✗ | KMSGrabContext *ctx = avctx->priv_data; | |
| 99 | ✗ | drmModeFB *fb = NULL; | |
| 100 | int err, fd; | ||
| 101 | |||
| 102 | ✗ | fb = drmModeGetFB(ctx->hwctx->fd, plane->fb_id); | |
| 103 | ✗ | if (!fb) { | |
| 104 | ✗ | err = errno; | |
| 105 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get framebuffer " | |
| 106 | "%"PRIu32": %s.\n", plane->fb_id, strerror(err)); | ||
| 107 | ✗ | err = AVERROR(err); | |
| 108 | ✗ | goto fail; | |
| 109 | } | ||
| 110 | ✗ | if (fb->width != ctx->width || fb->height != ctx->height) { | |
| 111 | ✗ | av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer " | |
| 112 | "dimensions changed: now %"PRIu32"x%"PRIu32".\n", | ||
| 113 | ctx->plane_id, fb->width, fb->height); | ||
| 114 | ✗ | err = AVERROR(EIO); | |
| 115 | ✗ | goto fail; | |
| 116 | } | ||
| 117 | ✗ | if (!fb->handle) { | |
| 118 | ✗ | av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer.\n"); | |
| 119 | ✗ | err = AVERROR(EIO); | |
| 120 | ✗ | goto fail; | |
| 121 | } | ||
| 122 | |||
| 123 | ✗ | err = drmPrimeHandleToFD(ctx->hwctx->fd, fb->handle, O_RDONLY, &fd); | |
| 124 | ✗ | if (err < 0) { | |
| 125 | ✗ | err = errno; | |
| 126 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get PRIME fd from " | |
| 127 | "framebuffer handle: %s.\n", strerror(err)); | ||
| 128 | ✗ | err = AVERROR(err); | |
| 129 | ✗ | goto fail; | |
| 130 | } | ||
| 131 | |||
| 132 | ✗ | *desc = (AVDRMFrameDescriptor) { | |
| 133 | .nb_objects = 1, | ||
| 134 | .objects[0] = { | ||
| 135 | .fd = fd, | ||
| 136 | ✗ | .size = fb->height * fb->pitch, | |
| 137 | ✗ | .format_modifier = ctx->drm_format_modifier, | |
| 138 | }, | ||
| 139 | .nb_layers = 1, | ||
| 140 | .layers[0] = { | ||
| 141 | ✗ | .format = ctx->drm_format, | |
| 142 | .nb_planes = 1, | ||
| 143 | .planes[0] = { | ||
| 144 | .object_index = 0, | ||
| 145 | .offset = 0, | ||
| 146 | ✗ | .pitch = fb->pitch, | |
| 147 | }, | ||
| 148 | }, | ||
| 149 | }; | ||
| 150 | |||
| 151 | ✗ | err = 0; | |
| 152 | ✗ | fail: | |
| 153 | ✗ | drmModeFreeFB(fb); | |
| 154 | ✗ | return err; | |
| 155 | } | ||
| 156 | |||
| 157 | #if HAVE_LIBDRM_GETFB2 | ||
| 158 | ✗ | static int kmsgrab_get_fb2(AVFormatContext *avctx, | |
| 159 | drmModePlane *plane, | ||
| 160 | AVDRMFrameDescriptor *desc) | ||
| 161 | { | ||
| 162 | ✗ | KMSGrabContext *ctx = avctx->priv_data; | |
| 163 | drmModeFB2 *fb; | ||
| 164 | int err, i, nb_objects; | ||
| 165 | ✗ | uint64_t modifier = ctx->drm_format_modifier; | |
| 166 | |||
| 167 | ✗ | fb = drmModeGetFB2(ctx->hwctx->fd, plane->fb_id); | |
| 168 | ✗ | if (!fb) { | |
| 169 | ✗ | err = errno; | |
| 170 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get framebuffer " | |
| 171 | "%"PRIu32": %s.\n", plane->fb_id, strerror(err)); | ||
| 172 | ✗ | return AVERROR(err); | |
| 173 | } | ||
| 174 | ✗ | if (fb->pixel_format != ctx->drm_format) { | |
| 175 | ✗ | av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer " | |
| 176 | "format changed: now %"PRIx32".\n", | ||
| 177 | ctx->plane_id, fb->pixel_format); | ||
| 178 | ✗ | err = AVERROR(EIO); | |
| 179 | ✗ | goto fail; | |
| 180 | } | ||
| 181 | ✗ | if (fb->width != ctx->width || fb->height != ctx->height) { | |
| 182 | ✗ | av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" framebuffer " | |
| 183 | "dimensions changed: now %"PRIu32"x%"PRIu32".\n", | ||
| 184 | ctx->plane_id, fb->width, fb->height); | ||
| 185 | ✗ | err = AVERROR(EIO); | |
| 186 | ✗ | goto fail; | |
| 187 | } | ||
| 188 | ✗ | if (!fb->handles[0]) { | |
| 189 | ✗ | av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer.\n"); | |
| 190 | ✗ | err = AVERROR(EIO); | |
| 191 | ✗ | goto fail; | |
| 192 | } | ||
| 193 | |||
| 194 | ✗ | if (fb->flags & DRM_MODE_FB_MODIFIERS) | |
| 195 | ✗ | modifier = fb->modifier; | |
| 196 | |||
| 197 | ✗ | *desc = (AVDRMFrameDescriptor) { | |
| 198 | .nb_layers = 1, | ||
| 199 | .layers[0] = { | ||
| 200 | ✗ | .format = ctx->drm_format, | |
| 201 | }, | ||
| 202 | }; | ||
| 203 | |||
| 204 | ✗ | nb_objects = 0; | |
| 205 | ✗ | for (i = 0; i < 4 && fb->handles[i]; i++) { | |
| 206 | size_t size; | ||
| 207 | ✗ | int dup = 0, j, obj; | |
| 208 | |||
| 209 | ✗ | size = fb->offsets[i] + fb->height * fb->pitches[i]; | |
| 210 | |||
| 211 | ✗ | for (j = 0; j < i; j++) { | |
| 212 | ✗ | if (fb->handles[i] == fb->handles[j]) { | |
| 213 | ✗ | dup = 1; | |
| 214 | ✗ | break; | |
| 215 | } | ||
| 216 | } | ||
| 217 | ✗ | if (dup) { | |
| 218 | ✗ | obj = desc->layers[0].planes[j].object_index; | |
| 219 | |||
| 220 | ✗ | if (desc->objects[j].size < size) | |
| 221 | ✗ | desc->objects[j].size = size; | |
| 222 | |||
| 223 | ✗ | desc->layers[0].planes[i] = (AVDRMPlaneDescriptor) { | |
| 224 | .object_index = obj, | ||
| 225 | ✗ | .offset = fb->offsets[i], | |
| 226 | ✗ | .pitch = fb->pitches[i], | |
| 227 | }; | ||
| 228 | |||
| 229 | } else { | ||
| 230 | int fd; | ||
| 231 | ✗ | err = drmPrimeHandleToFD(ctx->hwctx->fd, fb->handles[i], | |
| 232 | O_RDONLY, &fd); | ||
| 233 | ✗ | if (err < 0) { | |
| 234 | ✗ | err = errno; | |
| 235 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get PRIME fd from " | |
| 236 | "framebuffer handle: %s.\n", strerror(err)); | ||
| 237 | ✗ | err = AVERROR(err); | |
| 238 | ✗ | goto fail; | |
| 239 | } | ||
| 240 | |||
| 241 | ✗ | obj = nb_objects++; | |
| 242 | ✗ | desc->objects[obj] = (AVDRMObjectDescriptor) { | |
| 243 | .fd = fd, | ||
| 244 | .size = size, | ||
| 245 | .format_modifier = modifier, | ||
| 246 | }; | ||
| 247 | ✗ | desc->layers[0].planes[i] = (AVDRMPlaneDescriptor) { | |
| 248 | .object_index = obj, | ||
| 249 | ✗ | .offset = fb->offsets[i], | |
| 250 | ✗ | .pitch = fb->pitches[i], | |
| 251 | }; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | ✗ | desc->nb_objects = nb_objects; | |
| 255 | ✗ | desc->layers[0].nb_planes = i; | |
| 256 | |||
| 257 | ✗ | err = 0; | |
| 258 | ✗ | fail: | |
| 259 | ✗ | drmModeFreeFB2(fb); | |
| 260 | ✗ | return err; | |
| 261 | } | ||
| 262 | #endif | ||
| 263 | |||
| 264 | ✗ | static int kmsgrab_read_packet(AVFormatContext *avctx, AVPacket *pkt) | |
| 265 | { | ||
| 266 | ✗ | KMSGrabContext *ctx = avctx->priv_data; | |
| 267 | ✗ | drmModePlane *plane = NULL; | |
| 268 | ✗ | AVDRMFrameDescriptor *desc = NULL; | |
| 269 | ✗ | AVFrame *frame = NULL; | |
| 270 | int64_t now; | ||
| 271 | int err; | ||
| 272 | |||
| 273 | ✗ | now = av_gettime_relative(); | |
| 274 | ✗ | if (ctx->frame_last) { | |
| 275 | int64_t delay; | ||
| 276 | while (1) { | ||
| 277 | ✗ | delay = ctx->frame_last + ctx->frame_delay - now; | |
| 278 | ✗ | if (delay <= 0) | |
| 279 | ✗ | break; | |
| 280 | ✗ | av_usleep(delay); | |
| 281 | ✗ | now = av_gettime_relative(); | |
| 282 | } | ||
| 283 | } | ||
| 284 | ✗ | ctx->frame_last = now; | |
| 285 | ✗ | now = av_gettime(); | |
| 286 | |||
| 287 | ✗ | plane = drmModeGetPlane(ctx->hwctx->fd, ctx->plane_id); | |
| 288 | ✗ | if (!plane) { | |
| 289 | ✗ | err = errno; | |
| 290 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get plane " | |
| 291 | "%"PRIu32": %s.\n", ctx->plane_id, strerror(err)); | ||
| 292 | ✗ | err = AVERROR(err); | |
| 293 | ✗ | goto fail; | |
| 294 | } | ||
| 295 | ✗ | if (!plane->fb_id) { | |
| 296 | ✗ | av_log(avctx, AV_LOG_ERROR, "Plane %"PRIu32" no longer has " | |
| 297 | "an associated framebuffer.\n", ctx->plane_id); | ||
| 298 | ✗ | err = AVERROR(EIO); | |
| 299 | ✗ | goto fail; | |
| 300 | } | ||
| 301 | |||
| 302 | ✗ | desc = av_mallocz(sizeof(*desc)); | |
| 303 | ✗ | if (!desc) { | |
| 304 | ✗ | err = AVERROR(ENOMEM); | |
| 305 | ✗ | goto fail; | |
| 306 | } | ||
| 307 | |||
| 308 | #if HAVE_LIBDRM_GETFB2 | ||
| 309 | ✗ | if (ctx->fb2_available) | |
| 310 | ✗ | err = kmsgrab_get_fb2(avctx, plane, desc); | |
| 311 | else | ||
| 312 | #endif | ||
| 313 | ✗ | err = kmsgrab_get_fb(avctx, plane, desc); | |
| 314 | ✗ | if (err < 0) | |
| 315 | ✗ | goto fail; | |
| 316 | |||
| 317 | ✗ | frame = av_frame_alloc(); | |
| 318 | ✗ | if (!frame) { | |
| 319 | ✗ | err = AVERROR(ENOMEM); | |
| 320 | ✗ | goto fail; | |
| 321 | } | ||
| 322 | |||
| 323 | ✗ | frame->hw_frames_ctx = av_buffer_ref(ctx->frames_ref); | |
| 324 | ✗ | if (!frame->hw_frames_ctx) { | |
| 325 | ✗ | err = AVERROR(ENOMEM); | |
| 326 | ✗ | goto fail; | |
| 327 | } | ||
| 328 | |||
| 329 | ✗ | frame->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc), | |
| 330 | &kmsgrab_free_desc, avctx, 0); | ||
| 331 | ✗ | if (!frame->buf[0]) { | |
| 332 | ✗ | err = AVERROR(ENOMEM); | |
| 333 | ✗ | goto fail; | |
| 334 | } | ||
| 335 | |||
| 336 | ✗ | frame->data[0] = (uint8_t*)desc; | |
| 337 | ✗ | frame->format = AV_PIX_FMT_DRM_PRIME; | |
| 338 | ✗ | frame->width = ctx->width; | |
| 339 | ✗ | frame->height = ctx->height; | |
| 340 | |||
| 341 | ✗ | drmModeFreePlane(plane); | |
| 342 | ✗ | plane = NULL; | |
| 343 | ✗ | desc = NULL; | |
| 344 | |||
| 345 | ✗ | pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), | |
| 346 | &kmsgrab_free_frame, avctx, 0); | ||
| 347 | ✗ | if (!pkt->buf) { | |
| 348 | ✗ | err = AVERROR(ENOMEM); | |
| 349 | ✗ | goto fail; | |
| 350 | } | ||
| 351 | |||
| 352 | ✗ | pkt->data = (uint8_t*)frame; | |
| 353 | ✗ | pkt->size = sizeof(*frame); | |
| 354 | ✗ | pkt->pts = now; | |
| 355 | ✗ | pkt->flags |= AV_PKT_FLAG_TRUSTED; | |
| 356 | |||
| 357 | ✗ | return 0; | |
| 358 | |||
| 359 | ✗ | fail: | |
| 360 | ✗ | drmModeFreePlane(plane); | |
| 361 | ✗ | av_freep(&desc); | |
| 362 | ✗ | av_frame_free(&frame); | |
| 363 | ✗ | return err; | |
| 364 | } | ||
| 365 | |||
| 366 | static const struct { | ||
| 367 | enum AVPixelFormat pixfmt; | ||
| 368 | uint32_t drm_format; | ||
| 369 | } kmsgrab_formats[] = { | ||
| 370 | // Monochrome. | ||
| 371 | #ifdef DRM_FORMAT_R8 | ||
| 372 | { AV_PIX_FMT_GRAY8, DRM_FORMAT_R8 }, | ||
| 373 | #endif | ||
| 374 | #ifdef DRM_FORMAT_R16 | ||
| 375 | { AV_PIX_FMT_GRAY16LE, DRM_FORMAT_R16 }, | ||
| 376 | { AV_PIX_FMT_GRAY16BE, DRM_FORMAT_R16 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 377 | #endif | ||
| 378 | // <8-bit RGB. | ||
| 379 | { AV_PIX_FMT_BGR8, DRM_FORMAT_BGR233 }, | ||
| 380 | { AV_PIX_FMT_RGB555LE, DRM_FORMAT_XRGB1555 }, | ||
| 381 | { AV_PIX_FMT_RGB555BE, DRM_FORMAT_XRGB1555 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 382 | { AV_PIX_FMT_BGR555LE, DRM_FORMAT_XBGR1555 }, | ||
| 383 | { AV_PIX_FMT_BGR555BE, DRM_FORMAT_XBGR1555 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 384 | { AV_PIX_FMT_RGB565LE, DRM_FORMAT_RGB565 }, | ||
| 385 | { AV_PIX_FMT_RGB565BE, DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 386 | { AV_PIX_FMT_BGR565LE, DRM_FORMAT_BGR565 }, | ||
| 387 | { AV_PIX_FMT_BGR565BE, DRM_FORMAT_BGR565 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 388 | // 8-bit RGB. | ||
| 389 | { AV_PIX_FMT_RGB24, DRM_FORMAT_RGB888 }, | ||
| 390 | { AV_PIX_FMT_BGR24, DRM_FORMAT_BGR888 }, | ||
| 391 | { AV_PIX_FMT_0RGB, DRM_FORMAT_BGRX8888 }, | ||
| 392 | { AV_PIX_FMT_0BGR, DRM_FORMAT_RGBX8888 }, | ||
| 393 | { AV_PIX_FMT_RGB0, DRM_FORMAT_XBGR8888 }, | ||
| 394 | { AV_PIX_FMT_BGR0, DRM_FORMAT_XRGB8888 }, | ||
| 395 | { AV_PIX_FMT_ARGB, DRM_FORMAT_BGRA8888 }, | ||
| 396 | { AV_PIX_FMT_ABGR, DRM_FORMAT_RGBA8888 }, | ||
| 397 | { AV_PIX_FMT_RGBA, DRM_FORMAT_ABGR8888 }, | ||
| 398 | { AV_PIX_FMT_BGRA, DRM_FORMAT_ARGB8888 }, | ||
| 399 | // 10-bit RGB. | ||
| 400 | { AV_PIX_FMT_X2RGB10LE, DRM_FORMAT_XRGB2101010 }, | ||
| 401 | { AV_PIX_FMT_X2RGB10BE, DRM_FORMAT_XRGB2101010 | DRM_FORMAT_BIG_ENDIAN }, | ||
| 402 | // 8-bit YUV 4:2:0. | ||
| 403 | { AV_PIX_FMT_NV12, DRM_FORMAT_NV12 }, | ||
| 404 | // 8-bit YUV 4:2:2. | ||
| 405 | { AV_PIX_FMT_YUYV422, DRM_FORMAT_YUYV }, | ||
| 406 | { AV_PIX_FMT_YVYU422, DRM_FORMAT_YVYU }, | ||
| 407 | { AV_PIX_FMT_UYVY422, DRM_FORMAT_UYVY }, | ||
| 408 | }; | ||
| 409 | |||
| 410 | ✗ | static av_cold int kmsgrab_read_header(AVFormatContext *avctx) | |
| 411 | { | ||
| 412 | ✗ | KMSGrabContext *ctx = avctx->priv_data; | |
| 413 | ✗ | drmModePlaneRes *plane_res = NULL; | |
| 414 | ✗ | drmModePlane *plane = NULL; | |
| 415 | ✗ | drmModeFB *fb = NULL; | |
| 416 | #if HAVE_LIBDRM_GETFB2 | ||
| 417 | ✗ | drmModeFB2 *fb2 = NULL; | |
| 418 | #endif | ||
| 419 | AVStream *stream; | ||
| 420 | int err, i; | ||
| 421 | |||
| 422 | ✗ | err = av_hwdevice_ctx_create(&ctx->device_ref, AV_HWDEVICE_TYPE_DRM, | |
| 423 | ctx->device_path, NULL, 0); | ||
| 424 | ✗ | if (err < 0) { | |
| 425 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to open DRM device.\n"); | |
| 426 | ✗ | return err; | |
| 427 | } | ||
| 428 | ✗ | ctx->device = (AVHWDeviceContext*) ctx->device_ref->data; | |
| 429 | ✗ | ctx->hwctx = (AVDRMDeviceContext*)ctx->device->hwctx; | |
| 430 | |||
| 431 | ✗ | err = drmSetClientCap(ctx->hwctx->fd, | |
| 432 | DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); | ||
| 433 | ✗ | if (err < 0) { | |
| 434 | ✗ | av_log(avctx, AV_LOG_WARNING, "Failed to set universal planes " | |
| 435 | "capability: primary planes will not be usable.\n"); | ||
| 436 | } | ||
| 437 | |||
| 438 | ✗ | if (ctx->source_plane > 0) { | |
| 439 | ✗ | plane = drmModeGetPlane(ctx->hwctx->fd, ctx->source_plane); | |
| 440 | ✗ | if (!plane) { | |
| 441 | ✗ | err = errno; | |
| 442 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get plane %"PRId64": " | |
| 443 | "%s.\n", ctx->source_plane, strerror(err)); | ||
| 444 | ✗ | err = AVERROR(err); | |
| 445 | ✗ | goto fail; | |
| 446 | } | ||
| 447 | |||
| 448 | ✗ | if (plane->fb_id == 0) { | |
| 449 | ✗ | av_log(avctx, AV_LOG_ERROR, "Plane %"PRId64" does not have " | |
| 450 | "an attached framebuffer.\n", ctx->source_plane); | ||
| 451 | ✗ | err = AVERROR(EINVAL); | |
| 452 | ✗ | goto fail; | |
| 453 | } | ||
| 454 | } else { | ||
| 455 | ✗ | plane_res = drmModeGetPlaneResources(ctx->hwctx->fd); | |
| 456 | ✗ | if (!plane_res) { | |
| 457 | ✗ | err = errno; | |
| 458 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get plane " | |
| 459 | "resources: %s.\n", strerror(err)); | ||
| 460 | ✗ | err = AVERROR(err); | |
| 461 | ✗ | goto fail; | |
| 462 | } | ||
| 463 | |||
| 464 | ✗ | for (i = 0; i < plane_res->count_planes; i++) { | |
| 465 | ✗ | plane = drmModeGetPlane(ctx->hwctx->fd, | |
| 466 | ✗ | plane_res->planes[i]); | |
| 467 | ✗ | if (!plane) { | |
| 468 | ✗ | err = errno; | |
| 469 | ✗ | av_log(avctx, AV_LOG_VERBOSE, "Failed to get " | |
| 470 | "plane %"PRIu32": %s.\n", | ||
| 471 | ✗ | plane_res->planes[i], strerror(err)); | |
| 472 | ✗ | continue; | |
| 473 | } | ||
| 474 | |||
| 475 | ✗ | av_log(avctx, AV_LOG_DEBUG, "Plane %"PRIu32": " | |
| 476 | "CRTC %"PRIu32" FB %"PRIu32".\n", | ||
| 477 | plane->plane_id, plane->crtc_id, plane->fb_id); | ||
| 478 | |||
| 479 | ✗ | if ((ctx->source_crtc > 0 && | |
| 480 | ✗ | plane->crtc_id != ctx->source_crtc) || | |
| 481 | ✗ | plane->fb_id == 0) { | |
| 482 | // Either not connected to the target source CRTC | ||
| 483 | // or not active. | ||
| 484 | ✗ | drmModeFreePlane(plane); | |
| 485 | ✗ | plane = NULL; | |
| 486 | ✗ | continue; | |
| 487 | } | ||
| 488 | |||
| 489 | ✗ | break; | |
| 490 | } | ||
| 491 | |||
| 492 | ✗ | if (i == plane_res->count_planes) { | |
| 493 | ✗ | if (ctx->source_crtc > 0) { | |
| 494 | ✗ | av_log(avctx, AV_LOG_ERROR, "No usable planes found on " | |
| 495 | "CRTC %"PRId64".\n", ctx->source_crtc); | ||
| 496 | } else { | ||
| 497 | ✗ | av_log(avctx, AV_LOG_ERROR, "No usable planes found.\n"); | |
| 498 | } | ||
| 499 | ✗ | err = AVERROR(EINVAL); | |
| 500 | ✗ | goto fail; | |
| 501 | } | ||
| 502 | |||
| 503 | ✗ | av_log(avctx, AV_LOG_INFO, "Using plane %"PRIu32" to " | |
| 504 | "locate framebuffers.\n", plane->plane_id); | ||
| 505 | } | ||
| 506 | |||
| 507 | ✗ | ctx->plane_id = plane->plane_id; | |
| 508 | |||
| 509 | #if HAVE_LIBDRM_GETFB2 | ||
| 510 | ✗ | fb2 = drmModeGetFB2(ctx->hwctx->fd, plane->fb_id); | |
| 511 | ✗ | if (!fb2 && errno == ENOSYS) { | |
| 512 | ✗ | av_log(avctx, AV_LOG_INFO, "GETFB2 not supported, " | |
| 513 | "will try to use GETFB instead.\n"); | ||
| 514 | ✗ | } else if (!fb2) { | |
| 515 | ✗ | err = errno; | |
| 516 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get " | |
| 517 | "framebuffer %"PRIu32": %s.\n", | ||
| 518 | plane->fb_id, strerror(err)); | ||
| 519 | ✗ | err = AVERROR(err); | |
| 520 | ✗ | goto fail; | |
| 521 | } else { | ||
| 522 | ✗ | av_log(avctx, AV_LOG_INFO, "Template framebuffer is " | |
| 523 | "%"PRIu32": %"PRIu32"x%"PRIu32" " | ||
| 524 | "format %"PRIx32" modifier %"PRIx64" flags %"PRIx32".\n", | ||
| 525 | fb2->fb_id, fb2->width, fb2->height, | ||
| 526 | fb2->pixel_format, fb2->modifier, fb2->flags); | ||
| 527 | |||
| 528 | ✗ | ctx->width = fb2->width; | |
| 529 | ✗ | ctx->height = fb2->height; | |
| 530 | |||
| 531 | ✗ | if (!fb2->handles[0]) { | |
| 532 | ✗ | av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer: " | |
| 533 | "maybe you need some additional capabilities?\n"); | ||
| 534 | ✗ | err = AVERROR(EINVAL); | |
| 535 | ✗ | goto fail; | |
| 536 | } | ||
| 537 | |||
| 538 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(kmsgrab_formats); i++) { | |
| 539 | ✗ | if (kmsgrab_formats[i].drm_format == fb2->pixel_format) { | |
| 540 | ✗ | if (ctx->format != AV_PIX_FMT_NONE && | |
| 541 | ✗ | ctx->format != kmsgrab_formats[i].pixfmt) { | |
| 542 | ✗ | av_log(avctx, AV_LOG_ERROR, "Framebuffer pixel format " | |
| 543 | "%"PRIx32" does not match expected format.\n", | ||
| 544 | fb2->pixel_format); | ||
| 545 | ✗ | err = AVERROR(EINVAL); | |
| 546 | ✗ | goto fail; | |
| 547 | } | ||
| 548 | ✗ | ctx->drm_format = fb2->pixel_format; | |
| 549 | ✗ | ctx->format = kmsgrab_formats[i].pixfmt; | |
| 550 | ✗ | break; | |
| 551 | } | ||
| 552 | } | ||
| 553 | ✗ | if (i == FF_ARRAY_ELEMS(kmsgrab_formats)) { | |
| 554 | ✗ | av_log(avctx, AV_LOG_ERROR, "Framebuffer pixel format " | |
| 555 | "%"PRIx32" is not a known supported format.\n", | ||
| 556 | fb2->pixel_format); | ||
| 557 | ✗ | err = AVERROR(EINVAL); | |
| 558 | ✗ | goto fail; | |
| 559 | } | ||
| 560 | |||
| 561 | ✗ | if (fb2->flags & DRM_MODE_FB_MODIFIERS) { | |
| 562 | ✗ | if (ctx->drm_format_modifier != DRM_FORMAT_MOD_INVALID && | |
| 563 | ✗ | ctx->drm_format_modifier != fb2->modifier) { | |
| 564 | ✗ | av_log(avctx, AV_LOG_ERROR, "Framebuffer format modifier " | |
| 565 | "%"PRIx64" does not match expected modifier.\n", | ||
| 566 | fb2->modifier); | ||
| 567 | ✗ | err = AVERROR(EINVAL); | |
| 568 | ✗ | goto fail; | |
| 569 | } else { | ||
| 570 | ✗ | ctx->drm_format_modifier = fb2->modifier; | |
| 571 | } | ||
| 572 | } | ||
| 573 | ✗ | av_log(avctx, AV_LOG_VERBOSE, "Format is %s, from " | |
| 574 | "DRM format %"PRIx32" modifier %"PRIx64".\n", | ||
| 575 | av_get_pix_fmt_name(ctx->format), | ||
| 576 | ctx->drm_format, ctx->drm_format_modifier); | ||
| 577 | |||
| 578 | ✗ | ctx->fb2_available = 1; | |
| 579 | } | ||
| 580 | #endif | ||
| 581 | |||
| 582 | ✗ | if (!ctx->fb2_available) { | |
| 583 | ✗ | if (ctx->format == AV_PIX_FMT_NONE) { | |
| 584 | // Backward compatibility: assume BGR0 if no format supplied. | ||
| 585 | ✗ | ctx->format = AV_PIX_FMT_BGR0; | |
| 586 | } | ||
| 587 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(kmsgrab_formats); i++) { | |
| 588 | ✗ | if (kmsgrab_formats[i].pixfmt == ctx->format) { | |
| 589 | ✗ | ctx->drm_format = kmsgrab_formats[i].drm_format; | |
| 590 | ✗ | break; | |
| 591 | } | ||
| 592 | } | ||
| 593 | ✗ | if (i >= FF_ARRAY_ELEMS(kmsgrab_formats)) { | |
| 594 | ✗ | av_log(avctx, AV_LOG_ERROR, "Unsupported format %s.\n", | |
| 595 | av_get_pix_fmt_name(ctx->format)); | ||
| 596 | ✗ | return AVERROR(EINVAL); | |
| 597 | } | ||
| 598 | |||
| 599 | ✗ | fb = drmModeGetFB(ctx->hwctx->fd, plane->fb_id); | |
| 600 | ✗ | if (!fb) { | |
| 601 | ✗ | err = errno; | |
| 602 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to get " | |
| 603 | "framebuffer %"PRIu32": %s.\n", | ||
| 604 | plane->fb_id, strerror(err)); | ||
| 605 | ✗ | err = AVERROR(err); | |
| 606 | ✗ | goto fail; | |
| 607 | } | ||
| 608 | |||
| 609 | ✗ | av_log(avctx, AV_LOG_INFO, "Template framebuffer is %"PRIu32": " | |
| 610 | "%"PRIu32"x%"PRIu32" %"PRIu32"bpp %"PRIu32"b depth.\n", | ||
| 611 | fb->fb_id, fb->width, fb->height, fb->bpp, fb->depth); | ||
| 612 | |||
| 613 | ✗ | ctx->width = fb->width; | |
| 614 | ✗ | ctx->height = fb->height; | |
| 615 | |||
| 616 | ✗ | if (!fb->handle) { | |
| 617 | ✗ | av_log(avctx, AV_LOG_ERROR, "No handle set on framebuffer: " | |
| 618 | "maybe you need some additional capabilities?\n"); | ||
| 619 | ✗ | err = AVERROR(EINVAL); | |
| 620 | ✗ | goto fail; | |
| 621 | } | ||
| 622 | } | ||
| 623 | |||
| 624 | ✗ | stream = avformat_new_stream(avctx, NULL); | |
| 625 | ✗ | if (!stream) { | |
| 626 | ✗ | err = AVERROR(ENOMEM); | |
| 627 | ✗ | goto fail; | |
| 628 | } | ||
| 629 | |||
| 630 | ✗ | stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
| 631 | ✗ | stream->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME; | |
| 632 | ✗ | stream->codecpar->width = ctx->width; | |
| 633 | ✗ | stream->codecpar->height = ctx->height; | |
| 634 | ✗ | stream->codecpar->format = AV_PIX_FMT_DRM_PRIME; | |
| 635 | |||
| 636 | ✗ | avpriv_set_pts_info(stream, 64, 1, 1000000); | |
| 637 | |||
| 638 | ✗ | ctx->frames_ref = av_hwframe_ctx_alloc(ctx->device_ref); | |
| 639 | ✗ | if (!ctx->frames_ref) { | |
| 640 | ✗ | err = AVERROR(ENOMEM); | |
| 641 | ✗ | goto fail; | |
| 642 | } | ||
| 643 | ✗ | ctx->frames = (AVHWFramesContext*)ctx->frames_ref->data; | |
| 644 | |||
| 645 | ✗ | ctx->frames->format = AV_PIX_FMT_DRM_PRIME; | |
| 646 | ✗ | ctx->frames->sw_format = ctx->format, | |
| 647 | ✗ | ctx->frames->width = ctx->width; | |
| 648 | ✗ | ctx->frames->height = ctx->height; | |
| 649 | |||
| 650 | ✗ | err = av_hwframe_ctx_init(ctx->frames_ref); | |
| 651 | ✗ | if (err < 0) { | |
| 652 | ✗ | av_log(avctx, AV_LOG_ERROR, "Failed to initialise " | |
| 653 | "hardware frames context: %d.\n", err); | ||
| 654 | ✗ | goto fail; | |
| 655 | } | ||
| 656 | |||
| 657 | ✗ | ctx->frame_delay = av_rescale_q(1, (AVRational) { ctx->framerate.den, | |
| 658 | ✗ | ctx->framerate.num }, AV_TIME_BASE_Q); | |
| 659 | |||
| 660 | ✗ | err = 0; | |
| 661 | ✗ | fail: | |
| 662 | ✗ | drmModeFreePlaneResources(plane_res); | |
| 663 | ✗ | drmModeFreePlane(plane); | |
| 664 | ✗ | drmModeFreeFB(fb); | |
| 665 | #if HAVE_LIBDRM_GETFB2 | ||
| 666 | ✗ | drmModeFreeFB2(fb2); | |
| 667 | #endif | ||
| 668 | ✗ | return err; | |
| 669 | } | ||
| 670 | |||
| 671 | ✗ | static av_cold int kmsgrab_read_close(AVFormatContext *avctx) | |
| 672 | { | ||
| 673 | ✗ | KMSGrabContext *ctx = avctx->priv_data; | |
| 674 | |||
| 675 | ✗ | av_buffer_unref(&ctx->frames_ref); | |
| 676 | ✗ | av_buffer_unref(&ctx->device_ref); | |
| 677 | |||
| 678 | ✗ | return 0; | |
| 679 | } | ||
| 680 | |||
| 681 | #define OFFSET(x) offsetof(KMSGrabContext, x) | ||
| 682 | #define FLAGS AV_OPT_FLAG_DECODING_PARAM | ||
| 683 | static const AVOption options[] = { | ||
| 684 | { "device", "DRM device path", | ||
| 685 | OFFSET(device_path), AV_OPT_TYPE_STRING, | ||
| 686 | { .str = "/dev/dri/card0" }, 0, 0, FLAGS }, | ||
| 687 | { "format", "Pixel format for framebuffer", | ||
| 688 | OFFSET(format), AV_OPT_TYPE_PIXEL_FMT, | ||
| 689 | { .i64 = AV_PIX_FMT_NONE }, -1, INT32_MAX, FLAGS }, | ||
| 690 | { "format_modifier", "DRM format modifier for framebuffer", | ||
| 691 | OFFSET(drm_format_modifier), AV_OPT_TYPE_INT64, | ||
| 692 | { .i64 = DRM_FORMAT_MOD_INVALID }, 0, INT64_MAX, FLAGS }, | ||
| 693 | { "crtc_id", "CRTC ID to define capture source", | ||
| 694 | OFFSET(source_crtc), AV_OPT_TYPE_INT64, | ||
| 695 | { .i64 = 0 }, 0, UINT32_MAX, FLAGS }, | ||
| 696 | { "plane_id", "Plane ID to define capture source", | ||
| 697 | OFFSET(source_plane), AV_OPT_TYPE_INT64, | ||
| 698 | { .i64 = 0 }, 0, UINT32_MAX, FLAGS }, | ||
| 699 | { "framerate", "Framerate to capture at", | ||
| 700 | OFFSET(framerate), AV_OPT_TYPE_RATIONAL, | ||
| 701 | { .dbl = 30.0 }, 0, 1000, FLAGS }, | ||
| 702 | { NULL }, | ||
| 703 | }; | ||
| 704 | |||
| 705 | static const AVClass kmsgrab_class = { | ||
| 706 | .class_name = "kmsgrab indev", | ||
| 707 | .item_name = av_default_item_name, | ||
| 708 | .option = options, | ||
| 709 | .version = LIBAVUTIL_VERSION_INT, | ||
| 710 | .category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, | ||
| 711 | }; | ||
| 712 | |||
| 713 | const FFInputFormat ff_kmsgrab_demuxer = { | ||
| 714 | .p.name = "kmsgrab", | ||
| 715 | .p.long_name = NULL_IF_CONFIG_SMALL("KMS screen capture"), | ||
| 716 | .p.flags = AVFMT_NOFILE, | ||
| 717 | .p.priv_class = &kmsgrab_class, | ||
| 718 | .priv_data_size = sizeof(KMSGrabContext), | ||
| 719 | .read_header = &kmsgrab_read_header, | ||
| 720 | .read_packet = &kmsgrab_read_packet, | ||
| 721 | .read_close = &kmsgrab_read_close, | ||
| 722 | }; | ||
| 723 |