├── .gitignore ├── h264.FVDO_Freeway_720p.264 ├── run.sh ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | ffmpeg-drm -------------------------------------------------------------------------------- /h264.FVDO_Freeway_720p.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BayLibre/ffmpeg-drm/HEAD/h264.FVDO_Freeway_720p.264 -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | ./ffmpeg-drm --video ./h264.FVDO_Freeway_720p.264 --codec h264_v4l2m2m --width 1280 --height 720 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFMPEG DRM 2 | 3 | This code shall be used to validate the drmprime enabled ffmpeg releases 4 | 5 | ## Author 6 | 7 | * **jramirez@baylibre.com** 8 | 9 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * FFMPEG DRM/KMS example application 3 | * Jorge Ramirez-Ortiz 4 | * 5 | * Main file of the application 6 | * Based on code from: 7 | * 2001 Fabrice Bellard (FFMPEG/doc/examples/decode_video.c 8 | * 2018 Stanimir Varbanov (v4l2-decode/src/drm.c) 9 | * 10 | * This code has been tested on Linaro's Dragonboard 820c 11 | * kernel v4.14.15, venus decoder 12 | * ffmpeg 4.0 + lrusacks ffmpeg/DRM support + review 13 | * https://github.com/ldts/ffmpeg branch lrusak/v4l2-drmprime 14 | * 15 | * Copyright (c) 2018 Baylibre 16 | * 17 | * Licensed under the Apache License, Version 2.0 (the "License"); 18 | * you may not use this file except in compliance with the License. 19 | * You may obtain a copy of the License at 20 | * 21 | * http://www.apache.org/licenses/LICENSE-2.0 22 | * 23 | * Unless required by applicable law or agreed to in writing, software 24 | * distributed under the License is distributed on an "AS IS" BASIS, 25 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | * See the License for the specific language governing permissions and 27 | * limitations under the License. 28 | * 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | 51 | #define ALIGN(x, a) ((x) + (a - 1)) & (~(a - 1)) 52 | #define DRM_ALIGN(val, align) ((val + (align - 1)) & ~(align - 1)) 53 | 54 | #define INBUF_SIZE 4096 55 | 56 | struct drm_buffer { 57 | unsigned int fourcc; 58 | unsigned int bo_handle; 59 | unsigned int fb_handle; 60 | int dbuf_fd; 61 | void *mmap_buf; 62 | uint32_t pitches[4]; 63 | uint32_t offsets[4]; 64 | uint32_t bo_handles[4]; 65 | }; 66 | 67 | struct drm_dev { 68 | int fd; 69 | uint32_t conn_id, enc_id, crtc_id, fb_id, plane_id, crtc_idx; 70 | uint32_t width, height; 71 | uint32_t pitch, size, handle; 72 | drmModeModeInfo mode; 73 | drmModeCrtc *saved_crtc; 74 | struct drm_dev *next; 75 | }; 76 | 77 | static struct drm_dev *pdev; 78 | static unsigned int drm_format; 79 | 80 | #define DBG_TAG " ffmpeg-drm" 81 | 82 | #define print(msg, ...) \ 83 | do { \ 84 | struct timeval tv; \ 85 | gettimeofday(&tv, NULL); \ 86 | fprintf(stderr, "%08u:%08u :" msg, \ 87 | (uint32_t)tv.tv_sec, \ 88 | (uint32_t)tv.tv_usec, ##__VA_ARGS__); \ 89 | } while (0) 90 | 91 | #define err(msg, ...) print("error: " msg "\n", ##__VA_ARGS__) 92 | #define info(msg, ...) print(msg "\n", ##__VA_ARGS__) 93 | #define dbg(msg, ...) print(DBG_TAG ": " msg "\n", ##__VA_ARGS__) 94 | 95 | int drm_dmabuf_set_plane(struct drm_buffer *buf, uint32_t width, 96 | uint32_t height, int fullscreen) 97 | { 98 | uint32_t crtc_w, crtc_h; 99 | 100 | crtc_w = width; 101 | crtc_h = height; 102 | 103 | if (fullscreen) { 104 | crtc_w = pdev->width; 105 | crtc_h = pdev->height; 106 | } 107 | 108 | return drmModeSetPlane(pdev->fd, pdev->plane_id, pdev->crtc_id, 109 | buf->fb_handle, 0, 110 | 0, 0, crtc_w, crtc_h, 111 | 0, 0, width << 16, height << 16); 112 | } 113 | 114 | static int drm_dmabuf_import(struct drm_buffer *buf, unsigned int width, 115 | unsigned int height) 116 | { 117 | return drmPrimeFDToHandle(pdev->fd, buf->dbuf_fd, &buf->bo_handle); 118 | } 119 | 120 | static int drm_dmabuf_addfb(struct drm_buffer *buf, uint32_t width, uint32_t height) 121 | { 122 | int ret; 123 | 124 | if (width > pdev->width) 125 | width = pdev->width; 126 | if (height > pdev->height) 127 | height = pdev->height; 128 | 129 | width = ALIGN(width, 8); 130 | 131 | uint32_t stride = DRM_ALIGN(width, 128); 132 | uint32_t y_scanlines = DRM_ALIGN(height, 32); 133 | 134 | ret = drmModeAddFB2(pdev->fd, width, height, buf->fourcc, buf->bo_handles, 135 | buf->pitches, buf->offsets, &buf->fb_handle, 0); 136 | if (ret) { 137 | err("drmModeAddFB2 failed: %d (%s)\n", ret, strerror(errno)); 138 | return ret; 139 | } 140 | 141 | return 0; 142 | } 143 | 144 | static int find_plane(int fd, unsigned int fourcc, uint32_t *plane_id, 145 | uint32_t crtc_id, uint32_t crtc_idx) 146 | { 147 | drmModePlaneResPtr planes; 148 | drmModePlanePtr plane; 149 | unsigned int i; 150 | unsigned int j; 151 | int ret = 0; 152 | unsigned int format = fourcc; 153 | 154 | planes = drmModeGetPlaneResources(fd); 155 | if (!planes) { 156 | err("drmModeGetPlaneResources failed\n"); 157 | return -1; 158 | } 159 | 160 | info("drm: found planes %u", planes->count_planes); 161 | 162 | for (i = 0; i < planes->count_planes; ++i) { 163 | plane = drmModeGetPlane(fd, planes->planes[i]); 164 | if (!plane) { 165 | err("drmModeGetPlane failed: %s\n", strerror(errno)); 166 | break; 167 | } 168 | 169 | if (!(plane->possible_crtcs & (1 << crtc_idx))) { 170 | drmModeFreePlane(plane); 171 | continue; 172 | } 173 | 174 | for (j = 0; j < plane->count_formats; ++j) { 175 | if (plane->formats[j] == format) 176 | break; 177 | } 178 | 179 | if (j == plane->count_formats) { 180 | drmModeFreePlane(plane); 181 | continue; 182 | } 183 | 184 | *plane_id = plane->plane_id; 185 | drmModeFreePlane(plane); 186 | break; 187 | } 188 | 189 | if (i == planes->count_planes) 190 | ret = -1; 191 | 192 | drmModeFreePlaneResources(planes); 193 | 194 | return ret; 195 | } 196 | 197 | static struct drm_dev *drm_find_dev(int fd) 198 | { 199 | int i; 200 | struct drm_dev *dev = NULL, *dev_head = NULL; 201 | drmModeRes *res; 202 | drmModeConnector *conn; 203 | drmModeEncoder *enc; 204 | drmModeCrtc *crtc = NULL; 205 | 206 | if ((res = drmModeGetResources(fd)) == NULL) { 207 | err("drmModeGetResources() failed"); 208 | return NULL; 209 | } 210 | 211 | if (res->count_crtcs <= 0) { 212 | err("no Crtcs"); 213 | goto free_res; 214 | } 215 | 216 | /* find all available connectors */ 217 | for (i = 0; i < res->count_connectors; i++) { 218 | conn = drmModeGetConnector(fd, res->connectors[i]); 219 | 220 | if (conn) { 221 | if (conn->connection == DRM_MODE_CONNECTED) { 222 | dbg("drm: connector: connected"); 223 | } else if (conn->connection == DRM_MODE_DISCONNECTED) { 224 | dbg("drm: connector: disconnected"); 225 | } else if (conn->connection == DRM_MODE_UNKNOWNCONNECTION) { 226 | dbg("drm: connector: unknownconnection"); 227 | } else { 228 | dbg("drm: connector: unknown"); 229 | } 230 | } 231 | 232 | if (conn != NULL && conn->connection == DRM_MODE_CONNECTED 233 | && conn->count_modes > 0) { 234 | dev = (struct drm_dev *) malloc(sizeof(struct drm_dev)); 235 | memset(dev, 0, sizeof(struct drm_dev)); 236 | 237 | dev->conn_id = conn->connector_id; 238 | dev->enc_id = conn->encoder_id; 239 | dev->next = NULL; 240 | 241 | memcpy(&dev->mode, &conn->modes[0], sizeof(drmModeModeInfo)); 242 | dev->width = conn->modes[0].hdisplay; 243 | dev->height = conn->modes[0].vdisplay; 244 | 245 | if (conn->encoder_id) { 246 | enc = drmModeGetEncoder(fd, conn->encoder_id); 247 | if (!enc) { 248 | err("drmModeGetEncoder() faild"); 249 | goto free_res; 250 | } 251 | if (enc->crtc_id) { 252 | crtc = drmModeGetCrtc(fd, enc->crtc_id); 253 | if (crtc) 254 | dev->crtc_id = enc->crtc_id; 255 | } 256 | } 257 | 258 | drmModeFreeEncoder(enc); 259 | 260 | dev->saved_crtc = NULL; 261 | 262 | /* create dev list */ 263 | dev->next = dev_head; 264 | dev_head = dev; 265 | } 266 | drmModeFreeConnector(conn); 267 | } 268 | 269 | dev->crtc_idx = -1; 270 | 271 | for (i = 0; i < res->count_crtcs; ++i) { 272 | if (dev->crtc_id == res->crtcs[i]) { 273 | dev->crtc_idx = i; 274 | break; 275 | } 276 | } 277 | 278 | if (dev->crtc_idx == -1) 279 | err( "drm: CRTC %u not found\n"); 280 | 281 | free_res: 282 | drmModeFreeResources(res); 283 | 284 | return dev_head; 285 | } 286 | 287 | static int drm_open(const char *path) 288 | { 289 | int fd, flags; 290 | uint64_t has_dumb; 291 | int ret; 292 | 293 | fd = open(path, O_RDWR); 294 | if (fd < 0) { 295 | err("cannot open \"%s\"\n", path); 296 | return -1; 297 | } 298 | 299 | /* set FD_CLOEXEC flag */ 300 | if ((flags = fcntl(fd, F_GETFD)) < 0 || 301 | fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { 302 | err("fcntl FD_CLOEXEC failed\n"); 303 | goto err; 304 | } 305 | 306 | /* check capability */ 307 | ret = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb); 308 | if (ret < 0 || has_dumb == 0) { 309 | err("drmGetCap DRM_CAP_DUMB_BUFFER failed or doesn't have dumb " 310 | "buffer\n"); 311 | goto err; 312 | } 313 | 314 | return fd; 315 | err: 316 | close(fd); 317 | return -1; 318 | } 319 | 320 | static int drm_init(unsigned int fourcc, const char *device) 321 | { 322 | struct drm_dev *dev_head, *dev; 323 | int fd; 324 | int ret; 325 | 326 | fd = drm_open(device); 327 | if (fd < 0) 328 | return -1; 329 | 330 | dev_head = drm_find_dev(fd); 331 | if (dev_head == NULL) { 332 | err("available drm devices not found\n"); 333 | goto err; 334 | } 335 | 336 | dbg("available connector(s)"); 337 | 338 | for (dev = dev_head; dev != NULL; dev = dev->next) { 339 | dbg("connector id:%d", dev->conn_id); 340 | dbg("\tencoder id:%d crtc id:%d fb id:%d", dev->enc_id, 341 | dev->crtc_id, dev->fb_id); 342 | dbg("\twidth:%d height:%d", dev->width, dev->height); 343 | } 344 | 345 | /* FIXME: use first drm_dev */ 346 | dev = dev_head; 347 | dev->fd = fd; 348 | pdev = dev; 349 | 350 | ret = find_plane(fd, fourcc, &dev->plane_id, dev->crtc_id, dev->crtc_idx); 351 | if (ret) { 352 | err("Cannot find plane\n"); 353 | goto err; 354 | } 355 | 356 | info("drm: Found %c%c%c%c plane_id: %x", 357 | (fourcc>>0)&0xff, (fourcc>>8)&0xff, (fourcc>>16)&0xff, (fourcc>>24)&0xff, 358 | dev->plane_id); 359 | 360 | return 0; 361 | 362 | err: 363 | close(fd); 364 | pdev = NULL; 365 | return -1; 366 | } 367 | 368 | static int display(struct drm_buffer *drm_buf, int width, int height) 369 | { 370 | struct drm_gem_close gem_close; 371 | int ret; 372 | 373 | ret = drm_dmabuf_import(drm_buf, width, height); 374 | if (ret) { 375 | err("cannot import dmabuf %d, fd=%d\n", ret, drm_buf->dbuf_fd); 376 | return -EFAULT; 377 | } 378 | drm_buf->bo_handles[0] = drm_buf->bo_handle; 379 | drm_buf->bo_handles[1] = drm_buf->bo_handle; 380 | drm_buf->bo_handles[2] = drm_buf->bo_handle; 381 | drm_buf->bo_handles[3] = drm_buf->bo_handle; 382 | 383 | ret = drm_dmabuf_addfb(drm_buf, width, height); 384 | if (ret) { 385 | err("cannot add framebuffer %d\n", ret); 386 | return -EFAULT; 387 | } 388 | 389 | drm_dmabuf_set_plane(drm_buf, width, height, 1); 390 | 391 | /* WARNING: this will _obviously_ cause the screen to flicker!! 392 | * 393 | * Instead of using some simple stuff to postpone the release actions 394 | * (a list or a ping/ping buffer or whatever) we will just keep it 395 | * this way for clarity. 396 | * 397 | * 1. the client MUST remove the fb_handle 398 | * 2. the client MUST close the bo_handle (GEM object) 399 | * 400 | * Not doing so will cause FFMPEG to _fail_ when releasing the capture 401 | * mmap'ed buffers since the dmabufs are exported to DRM and therefore 402 | * DRM keeps a reference to those buffers. 403 | * 404 | * REQBUFS --> MMAP --> EXPBUF --> fb_handle / bo_handle 405 | * 406 | * ==> releasing the buffers requires the handles to be released 407 | */ 408 | if (drmModeRmFB(pdev->fd, drm_buf->fb_handle)) 409 | err("cant remove fb %d\n", drm_buf->fb_handle); 410 | 411 | memset(&gem_close, 0, sizeof gem_close); 412 | gem_close.handle = drm_buf->bo_handle; 413 | if (drmIoctl(pdev->fd, DRM_IOCTL_GEM_CLOSE, &gem_close) < 0) 414 | err("cant close gem: %s\n", strerror(errno)); 415 | 416 | return 0; 417 | } 418 | 419 | static void decode_and_display(AVCodecContext *dec_ctx, AVFrame *frame, 420 | AVPacket *pkt, const char *device) 421 | { 422 | AVDRMFrameDescriptor *desc = NULL; 423 | struct drm_buffer drm_buf; 424 | int ret; 425 | 426 | ret = avcodec_send_packet(dec_ctx, pkt); 427 | if (ret < 0) { 428 | err("Error sending a packet for decoding\n"); 429 | exit(1); 430 | } 431 | 432 | while (ret >= 0) { 433 | ret = avcodec_receive_frame(dec_ctx, frame); 434 | if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 435 | return; 436 | else if (ret < 0) { 437 | err("Error during decoding\n"); 438 | exit(1); 439 | } 440 | 441 | desc = (AVDRMFrameDescriptor *) frame->data[0]; 442 | drm_buf.dbuf_fd = desc->objects[0].fd; 443 | for (int i = 0; i < desc->layers->nb_planes && i < 4; i++ ) { 444 | drm_buf.pitches[i] = desc->layers->planes[i].pitch; 445 | drm_buf.offsets[i] = desc->layers->planes[i].offset; 446 | } 447 | 448 | if (!pdev) { 449 | /* initialize DRM with the format returned in the frame */ 450 | ret = drm_init(desc->layers[0].format, device); 451 | if (ret) { 452 | err("Error initializing drm\n"); 453 | exit(1); 454 | } 455 | 456 | /* remember the format */ 457 | drm_format = desc->layers[0].format; 458 | } 459 | 460 | /* pass the format in the buffer */ 461 | drm_buf.fourcc = drm_format; 462 | ret = display(&drm_buf, frame->width, frame->height); 463 | if (ret < 0) 464 | return; 465 | } 466 | } 467 | 468 | static const struct option options[] = { 469 | { 470 | #define help_opt 0 471 | .name = "help", 472 | .has_arg = 0, 473 | .flag = NULL, 474 | }, 475 | { 476 | #define video_opt 1 477 | .name = "video", 478 | .has_arg = 1, 479 | .flag = NULL, 480 | }, 481 | { 482 | #define codec_opt 2 483 | .name = "codec", 484 | .has_arg = 1, 485 | .flag = NULL, 486 | }, 487 | { 488 | #define height_opt 3 489 | .name = "height", 490 | .has_arg = 1, 491 | .flag = NULL, 492 | }, 493 | { 494 | #define width_opt 4 495 | .name = "width", 496 | .has_arg = 1, 497 | .flag = NULL, 498 | }, 499 | { 500 | #define device_opt 5 501 | .name = "device", 502 | .has_arg = 1, 503 | .flag = NULL, 504 | }, 505 | { 506 | .name = NULL, 507 | }, 508 | }; 509 | 510 | static void usage(void) 511 | { 512 | fprintf(stderr, "usage: ffmpeg-drm , with:\n"); 513 | fprintf(stderr, "--help display this menu\n"); 514 | fprintf(stderr, "--video= video to display\n"); 515 | fprintf(stderr, "--codec= ffmpeg codec: ie h264_v4l2m2m\n"); 516 | fprintf(stderr, "--width= frame width\n"); 517 | fprintf(stderr, "--height= frame height\n"); 518 | fprintf(stderr, "--device= dri device to use\n"); 519 | fprintf(stderr, "\n"); 520 | } 521 | 522 | int main(int argc, char *argv[]) 523 | { 524 | uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; 525 | AVCodecParserContext *parser; 526 | AVCodecContext *c = NULL; 527 | const AVCodec *codec; 528 | AVFrame *frame; 529 | AVPacket *pkt; 530 | size_t data_size; 531 | uint8_t *data; 532 | FILE *f; 533 | int ret; 534 | int lindex, opt; 535 | unsigned int frame_width = 0, frame_height = 0; 536 | char *codec_name = NULL, *video_name = NULL; 537 | char *device_name = "/dev/dri/card0"; 538 | 539 | for (;;) { 540 | lindex = -1; 541 | 542 | opt = getopt_long_only(argc, argv, "", options, &lindex); 543 | if (opt == EOF) 544 | break; 545 | 546 | switch (lindex) { 547 | case help_opt: 548 | usage(); 549 | exit(0); 550 | case video_opt: 551 | video_name = optarg; 552 | break; 553 | case codec_opt: 554 | codec_name = optarg; 555 | break; 556 | case width_opt: 557 | frame_width = atoi(optarg); 558 | break; 559 | case height_opt: 560 | frame_height = atoi(optarg); 561 | break; 562 | case device_opt: 563 | device_name = optarg; 564 | break; 565 | default: 566 | usage(); 567 | exit(1); 568 | } 569 | } 570 | 571 | if (!frame_width || !frame_height || !codec_name || !video_name) { 572 | usage(); 573 | exit(0); 574 | } 575 | 576 | pkt = av_packet_alloc(); 577 | if (!pkt) { 578 | err("Error allocating packet\n"); 579 | exit(1); 580 | } 581 | 582 | /* set end of buffer to 0 (this ensures that no overreading happens for 583 | damaged MPEG streams) */ 584 | memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); 585 | 586 | /* find the video decoder: ie: h264_v4l2m2m */ 587 | codec = avcodec_find_decoder_by_name(codec_name); 588 | if (!codec) { 589 | err("Codec not found\n"); 590 | exit(1); 591 | } 592 | 593 | parser = av_parser_init(codec->id); 594 | if (!parser) { 595 | err("parser not found\n"); 596 | exit(1); 597 | } 598 | 599 | c = avcodec_alloc_context3(codec); 600 | if (!c) { 601 | err("Could not allocate video codec context\n"); 602 | exit(1); 603 | } 604 | 605 | /* For some codecs, such as msmpeg4 and mpeg4, width and height 606 | MUST be initialized before opening the ffmpeg codec (ie, before 607 | calling avcodec_open2) because this information is not available in 608 | the bitstream). */ 609 | c->pix_fmt = AV_PIX_FMT_DRM_PRIME; /* request a DRM frame */ 610 | c->coded_height = frame_height; 611 | c->coded_width = frame_width; 612 | 613 | /* open it */ 614 | if (avcodec_open2(c, codec, NULL) < 0) { 615 | err("Could not open codec\n"); 616 | exit(1); 617 | } 618 | 619 | f = fopen(video_name, "rb"); 620 | if (!f) { 621 | err("Could not open %s\n", video_name); 622 | exit(1); 623 | } 624 | 625 | frame = av_frame_alloc(); 626 | if (!frame) { 627 | err("Could not allocate video frame\n"); 628 | exit(1); 629 | } 630 | 631 | while (!feof(f)) { 632 | /* read raw data from the input file */ 633 | data_size = fread(inbuf, 1, INBUF_SIZE, f); 634 | if (!data_size) 635 | break; 636 | 637 | /* use the parser to split the data into frames */ 638 | data = inbuf; 639 | while (data_size > 0) { 640 | ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, 641 | data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); 642 | if (ret < 0) { 643 | err("Error while parsing\n"); 644 | exit(1); 645 | } 646 | 647 | data += ret; 648 | data_size -= ret; 649 | 650 | if (pkt->size) 651 | decode_and_display(c, frame, pkt, device_name); 652 | } 653 | } 654 | fclose(f); 655 | 656 | decode_and_display(c, frame, NULL, device_name); 657 | 658 | av_parser_close(parser); 659 | avcodec_free_context(&c); 660 | av_frame_free(&frame); 661 | av_packet_free(&pkt); 662 | 663 | return 0; 664 | } 665 | --------------------------------------------------------------------------------